Skip to content

Commit

Permalink
Merge pull request #67 from outfoxx/fix/trust-failure-formatting
Browse files Browse the repository at this point in the history
More complete error information for trust evaluation failures
  • Loading branch information
kdubb committed May 21, 2023
2 parents b1e1d45 + 686cd42 commit 142698c
Show file tree
Hide file tree
Showing 4 changed files with 223 additions and 21 deletions.
114 changes: 114 additions & 0 deletions Sources/ShieldSecurity/Errors.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
//
// Errors.swift
// Shield
//
// Copyright 漏 2021 Outfox, inc.
//
//
// Distributed under the MIT License, See LICENSE for details.
//

import Foundation

extension CFError {

var humanReadableDescription: String {
humanReadableDescriptionLines.joined(separator: "\n")
}

var humanReadableDescriptionLines: [String] {

guard let userInfo = CFErrorCopyUserInfo(self) as? [CFString: Any] else {
return [CFErrorCopyDescription(self) as String]
}

var lines: [String] = []

lines += [
"Code: \(CFErrorGetCode(self))",
"Domain: \(CFErrorGetDomain(self)!)",
]

if let localizedErrorDesc = userInfo[kCFErrorLocalizedDescriptionKey] {
lines.append("Description: \(localizedErrorDesc)")
}

if let localizedFailureReasonDesc = userInfo[kCFErrorLocalizedFailureReasonKey] {
lines.append("Failure Reason: \(localizedFailureReasonDesc)")
}

if let localizedRecoverySuggestion = userInfo[kCFErrorLocalizedRecoverySuggestionKey] {
lines.append("Recovery Suggestion: \(localizedRecoverySuggestion)")
}

if let underlyingError = userInfo[kCFErrorUnderlyingErrorKey] {
lines += describe(title: "Underlying Error", error: underlyingError)
}

return lines
}

}

extension NSError {

var humanReadableDescription: String {
humanReadableDescriptionLines.joined(separator: "\n")
}

var humanReadableDescriptionLines: [String] {

var lines: [String] = []

lines += [
"Code: \(code)",
"Domain: \(domain)",
"Description: \(localizedDescription)",
]

if let localizedFailureReason {
lines.append("Failure Reason: \(localizedFailureReason)")
}

if let localizedRecoverySuggestion {
lines.append("Recovery Suggestion: \(localizedRecoverySuggestion)")
}

if let localizedRecoveryOptions {
lines += ["Recovery Options:"] + localizedRecoveryOptions.enumerated().map { idx, option in
" Option \(idx): \(option)"
}
}

if let underlyingError = userInfo[NSUnderlyingErrorKey] as? NSError {
lines += describe(title: "Underlying Error", error: underlyingError)
}

if #available(macOS 11.3, iOS 14.5, watchOS 7.4, tvOS 14.5, *) {

if let underlyingErrors = userInfo[NSMultipleUnderlyingErrorsKey] as? NSError {
lines += ["Underlying Errors:"] + describe(title: "Error 0", error: underlyingErrors)
}

if let underlyignErrors = userInfo[NSMultipleUnderlyingErrorsKey] as? NSArray {
lines += ["Underlying Errors:"] + underlyignErrors.enumerated().flatMap { idx, error in
return describe(title: "Error \(idx)", error: error)
}.map { " \($0)" }
}

}

return lines
}

}

private func describe(title: String, error: Any) -> [String] {
if let error = error as? NSError {
return ["\(title):"] + error.humanReadableDescriptionLines.map { " \($0)" }
}
else {
let error = error as! CFError // swiftlint:disable:this force_cast
return ["\(title):"] + error.humanReadableDescriptionLines.map { " \($0)" }
}
}
30 changes: 15 additions & 15 deletions Sources/ShieldSecurity/SecCertificate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -163,26 +163,18 @@ public extension SecCertificate {
return
}

let errorDesc: String
if let error = error {
errorDesc = (CFErrorCopyFailureReason(error) ?? CFErrorCopyDescription(error)) as String? ?? ""
}
else {
errorDesc = ""
}

logTrustEvaluation(level: .error,
trust: trust,
result: trustResult,
description: errorDesc)
error: error)

throw SecCertificateError.trustValidationFailed
}

private func logTrustEvaluation(level: OSLogType,
trust: SecTrust,
result: SecTrustResultType,
description: String) {
error: CFError?) {

var anchorCertificatesArray: CFArray?
let anchorCertificates: [SecCertificate]
Expand All @@ -194,17 +186,25 @@ public extension SecCertificate {
anchorCertificates = []
}

let errorDesc: String
if let error = error {
errorDesc = "\n" + error.humanReadableDescriptionLines.map { " \($0)" }.joined(separator: "\n")
}
else {
errorDesc = "None"
}

Logger.default.log(
level: level,
"""
Trust evaulation failed:
Result: \(trustResultDescription(result: result), privacy: .public),
Description: \(description, privacy: .public)
Result: \(trustResultDescription(result: result), privacy: .public)
Error: \(errorDesc)
Certificate:
\(self, privacy: .public)
Anchor Certificates:
\(anchorCertificates.enumerated().map { (idx, cert) in
"\(idx): \(cert)" }.joined(separator: "\n "), privacy: .public)
"\(idx): \(cert)" }.joined(separator: "\n "), privacy: .public)
"""
)
}
Expand All @@ -224,7 +224,7 @@ public extension SecCertificate {
let query = [
kSecReturnAttributes as String: kCFBooleanTrue!,
kSecValueRef as String: self,
] as CFDictionary
] as [String: Any] as CFDictionary

var data: CFTypeRef?

Expand Down Expand Up @@ -257,7 +257,7 @@ public extension SecCertificate {
let query = [
kSecClass as String: kSecClassCertificate,
kSecValueRef as String: self,
] as CFDictionary
] as [String: Any] as CFDictionary

var data: CFTypeRef?

Expand Down
88 changes: 88 additions & 0 deletions Tests/ErrorsTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
//
// ErrorsTests.swift
// Shield
//
// Copyright 漏 2021 Outfox, inc.
//
//
// Distributed under the MIT License, See LICENSE for details.
//

import Foundation
@testable import ShieldSecurity
import XCTest

class ErrorsTests: XCTestCase {

func testCFErrorHumanReadable() {

let error = CFErrorCreate(nil, "TestErrorDomain" as CFString, -1234, [
kCFErrorLocalizedDescriptionKey: "An Error Occurred" as CFString,
kCFErrorLocalizedFailureReasonKey: "Invalid Parameters" as CFString,
kCFErrorLocalizedRecoverySuggestionKey: "Pass valid parameters" as CFString,
kCFErrorUnderlyingErrorKey: CFErrorCreate(nil, kCFErrorDomainPOSIX, 1, nil)!,
] as [CFString: Any] as CFDictionary)!

XCTAssertEqual(
error.humanReadableDescription,
"""
Code: -1234
Domain: TestErrorDomain
Description: An Error Occurred
Failure Reason: Invalid Parameters
Recovery Suggestion: Pass valid parameters
Underlying Error:
Code: 1
Domain: NSPOSIXErrorDomain
Description: The operation couldn鈥檛 be completed. Operation not permitted
Failure Reason: Operation not permitted
"""
)
}

@available(macOS 11.3, iOS 14.5, watchOS 7.4, tvOS 14.5, *)
func testNSErrorHumanReadable() {

let error = NSError(domain: "TestErrorDomain", code: -1234, userInfo: [
kCFErrorLocalizedDescriptionKey: "An Error Occurred" as CFString,
kCFErrorLocalizedFailureReasonKey: "Invalid Parameters" as CFString,
kCFErrorLocalizedRecoverySuggestionKey: "Pass valid parameters" as CFString,
NSLocalizedRecoveryOptionsErrorKey as CFString: [
"Parameter 1 must be a string",
"Parameter 2 must be a boolean",
],
kCFErrorUnderlyingErrorKey: POSIXError(.EPERM),
NSMultipleUnderlyingErrorsKey as CFString: [POSIXError(.EPERM), POSIXError(.ENOENT)],
] as [String: Any])

XCTAssertEqual(
error.humanReadableDescription,
"""
Code: -1234
Domain: TestErrorDomain
Description: An Error Occurred
Failure Reason: Invalid Parameters
Recovery Suggestion: Pass valid parameters
Recovery Options:
Option 0: Parameter 1 must be a string
Option 1: Parameter 2 must be a boolean
Underlying Error:
Code: 1
Domain: NSPOSIXErrorDomain
Description: The operation couldn鈥檛 be completed. Operation not permitted
Failure Reason: Operation not permitted
Underlying Errors:
Error 0:
Code: 1
Domain: NSPOSIXErrorDomain
Description: The operation couldn鈥檛 be completed. Operation not permitted
Failure Reason: Operation not permitted
Error 1:
Code: 2
Domain: NSPOSIXErrorDomain
Description: The operation couldn鈥檛 be completed. No such file or directory
Failure Reason: No such file or directory
"""
)
}
}
12 changes: 6 additions & 6 deletions Tests/SecKeyPairTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ class SecKeyPairTests: XCTestCase {
kSecClass: kSecClassKey,
kSecAttrKeyClass: kSecAttrKeyClassPrivate,
kSecReturnRef: kCFBooleanTrue!,
] as CFDictionary
] as [String: Any] as CFDictionary
var privateKeyRef: CFTypeRef?
XCTAssertEqual(SecItemCopyMatching(privateKeyAttrs, &privateKeyRef), errSecSuccess)
XCTAssertNotNil(privateKeyRef)
Expand All @@ -50,7 +50,7 @@ class SecKeyPairTests: XCTestCase {
kSecClass: kSecClassKey,
kSecAttrKeyClass: kSecAttrKeyClassPublic,
kSecReturnRef: kCFBooleanTrue!,
] as CFDictionary
] as [String: Any] as CFDictionary
var publicKeyRef: CFTypeRef?
XCTAssertEqual(SecItemCopyMatching(publicKeyAttrs as CFDictionary, &publicKeyRef), errSecSuccess)
XCTAssertNotNil(publicKeyRef)
Expand All @@ -63,7 +63,7 @@ class SecKeyPairTests: XCTestCase {
kSecClass: kSecClassKey,
kSecAttrKeyClass: kSecAttrKeyClassPrivate,
kSecReturnRef: kCFBooleanTrue!,
] as CFDictionary
] as [String: Any] as CFDictionary
var privateKeyRef: CFTypeRef?
XCTAssertEqual(SecItemCopyMatching(privateKeyAttrs, &privateKeyRef), errSecSuccess)
XCTAssertNotNil(privateKeyRef)
Expand All @@ -73,7 +73,7 @@ class SecKeyPairTests: XCTestCase {
kSecClass: kSecClassKey,
kSecAttrKeyClass: kSecAttrKeyClassPublic,
kSecReturnRef: kCFBooleanTrue!,
] as CFDictionary
] as [String: Any] as CFDictionary
var publicKeyRef: CFTypeRef?
XCTAssertEqual(SecItemCopyMatching(publicKeyAttrs as CFDictionary, &publicKeyRef), errSecSuccess)
XCTAssertNotNil(publicKeyRef)
Expand Down Expand Up @@ -236,7 +236,7 @@ class SecKeyPairTests: XCTestCase {
kSecClass: kSecClassKey,
kSecAttrKeyClass: kSecAttrKeyClassPrivate,
kSecReturnRef: kCFBooleanTrue!,
] as CFDictionary
] as [String: Any] as CFDictionary
var privateKeyRef: CFTypeRef?
XCTAssertEqual(SecItemCopyMatching(privateKeyAttrs, &privateKeyRef), errSecSuccess)
XCTAssertNotNil(privateKeyRef)
Expand All @@ -246,7 +246,7 @@ class SecKeyPairTests: XCTestCase {
kSecClass: kSecClassKey,
kSecAttrKeyClass: kSecAttrKeyClassPublic,
kSecReturnRef: kCFBooleanTrue!,
] as CFDictionary
] as [String: Any] as CFDictionary
var publicKeyRef: CFTypeRef?
XCTAssertEqual(SecItemCopyMatching(publicKeyAttrs as CFDictionary, &publicKeyRef), errSecSuccess)
XCTAssertNotNil(publicKeyRef)
Expand Down

0 comments on commit 142698c

Please sign in to comment.