diff --git a/.gitignore b/.gitignore
index 7862e4f43..fd4a2a4ce 100644
--- a/.gitignore
+++ b/.gitignore
@@ -6,4 +6,3 @@
/TestResults
/ShieldHost/**/xcuserdata/
-/ShieldHost/**/project.xcworkspace/
\ No newline at end of file
diff --git a/ShieldHost/ShieldHost Watch App/ShieldHost Watch App.entitlements b/ShieldHost/ShieldHost Watch App/ShieldHost Watch App.entitlements
new file mode 100644
index 000000000..0c67376eb
--- /dev/null
+++ b/ShieldHost/ShieldHost Watch App/ShieldHost Watch App.entitlements
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/ShieldHost/ShieldHost.xcodeproj/project.pbxproj b/ShieldHost/ShieldHost.xcodeproj/project.pbxproj
index 6b35577ec..c27440a26 100644
--- a/ShieldHost/ShieldHost.xcodeproj/project.pbxproj
+++ b/ShieldHost/ShieldHost.xcodeproj/project.pbxproj
@@ -8,8 +8,6 @@
/* Begin PBXBuildFile section */
AA2151F02975D9CF0072F6CA /* ShieldHostApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA2151EF2975D9CF0072F6CA /* ShieldHostApp.swift */; };
- AA2151F42975D9D00072F6CA /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = AA2151F32975D9D00072F6CA /* Assets.xcassets */; };
- AA2151F82975D9D00072F6CA /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = AA2151F72975D9D00072F6CA /* Preview Assets.xcassets */; };
AA2152422975DF5F0072F6CA /* CertificationRequestBuilderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA2152342975DF5F0072F6CA /* CertificationRequestBuilderTests.swift */; };
AA2152442975DF5F0072F6CA /* HmacTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA2152362975DF5F0072F6CA /* HmacTests.swift */; };
AA2152452975DF5F0072F6CA /* OIDTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA2152372975DF5F0072F6CA /* OIDTests.swift */; };
@@ -26,8 +24,6 @@
AA2152632975E3600072F6CA /* Shield in Frameworks */ = {isa = PBXBuildFile; productRef = AA2152622975E3600072F6CA /* Shield */; };
AA5768A12975E7C300142200 /* ShieldHost Watch App.app in Embed Watch Content */ = {isa = PBXBuildFile; fileRef = AA5768A02975E7C300142200 /* ShieldHost Watch App.app */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
AA5768A62975E7C300142200 /* ShieldHostApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA5768A52975E7C300142200 /* ShieldHostApp.swift */; };
- AA5768AA2975E7C400142200 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = AA5768A92975E7C400142200 /* Assets.xcassets */; };
- AA5768AD2975E7C400142200 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = AA5768AC2975E7C400142200 /* Preview Assets.xcassets */; };
AA5768E02975E85E00142200 /* Shield in Frameworks */ = {isa = PBXBuildFile; productRef = AA5768DF2975E85E00142200 /* Shield */; };
AA5768E12975E87C00142200 /* HmacTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA2152362975DF5F0072F6CA /* HmacTests.swift */; };
AA5768E22975E87C00142200 /* DistinguishedNameComposerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA2152382975DF5F0072F6CA /* DistinguishedNameComposerTests.swift */; };
@@ -42,6 +38,8 @@
AA5768EC2975E87C00142200 /* DigestTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA21523D2975DF5F0072F6CA /* DigestTests.swift */; };
AA5768ED2975E87C00142200 /* CertificateBuilderECTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA21523A2975DF5F0072F6CA /* CertificateBuilderECTests.swift */; };
AA5768EE2975E87C00142200 /* DistinguishedNameParserTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA2152402975DF5F0072F6CA /* DistinguishedNameParserTests.swift */; };
+ AAC4C7862ADDFFAD00487E0A /* Utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAC4C7852ADDFFAD00487E0A /* Utils.swift */; };
+ AAC4C7872ADDFFAD00487E0A /* Utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAC4C7852ADDFFAD00487E0A /* Utils.swift */; };
AADD77C929A3E278005D0955 /* CertificateDecoderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AADD77C629A3E278005D0955 /* CertificateDecoderTests.swift */; };
AADD77CA29A3E278005D0955 /* CertificateDecoderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AADD77C629A3E278005D0955 /* CertificateDecoderTests.swift */; };
/* End PBXBuildFile section */
@@ -87,10 +85,7 @@
/* Begin PBXFileReference section */
AA2151EC2975D9CF0072F6CA /* ShieldHost.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = ShieldHost.app; sourceTree = BUILT_PRODUCTS_DIR; };
AA2151EF2975D9CF0072F6CA /* ShieldHostApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShieldHostApp.swift; sourceTree = ""; };
- AA2151F12975D9CF0072F6CA /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; };
- AA2151F32975D9D00072F6CA /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
AA2151F52975D9D00072F6CA /* ShieldHost.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = ShieldHost.entitlements; sourceTree = ""; };
- AA2151F72975D9D00072F6CA /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; };
AA2151FD2975D9D00072F6CA /* ShieldHostTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = ShieldHostTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
AA21521F2975DCA40072F6CA /* Shield */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = Shield; path = ..; sourceTree = ""; };
AA2152342975DF5F0072F6CA /* CertificationRequestBuilderTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CertificationRequestBuilderTests.swift; sourceTree = ""; };
@@ -109,10 +104,9 @@
AA57689B2975E7C300142200 /* ShieldHost Watch Container.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "ShieldHost Watch Container.app"; sourceTree = BUILT_PRODUCTS_DIR; };
AA5768A02975E7C300142200 /* ShieldHost Watch App.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "ShieldHost Watch App.app"; sourceTree = BUILT_PRODUCTS_DIR; };
AA5768A52975E7C300142200 /* ShieldHostApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShieldHostApp.swift; sourceTree = ""; };
- AA5768A72975E7C300142200 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; };
- AA5768A92975E7C400142200 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
- AA5768AC2975E7C400142200 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; };
AA5768B22975E7C400142200 /* ShieldHost Watch AppTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "ShieldHost Watch AppTests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; };
+ AAC4C7812ADDCBE600487E0A /* ShieldHost Watch App.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "ShieldHost Watch App.entitlements"; sourceTree = ""; };
+ AAC4C7852ADDFFAD00487E0A /* Utils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Utils.swift; sourceTree = ""; };
AADD77C629A3E278005D0955 /* CertificateDecoderTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CertificateDecoderTests.swift; sourceTree = ""; };
/* End PBXFileReference section */
@@ -178,22 +172,11 @@
isa = PBXGroup;
children = (
AA2151EF2975D9CF0072F6CA /* ShieldHostApp.swift */,
- AA2151F12975D9CF0072F6CA /* ContentView.swift */,
- AA2151F32975D9D00072F6CA /* Assets.xcassets */,
AA2151F52975D9D00072F6CA /* ShieldHost.entitlements */,
- AA2151F62975D9D00072F6CA /* Preview Content */,
);
path = ShieldHost;
sourceTree = "";
};
- AA2151F62975D9D00072F6CA /* Preview Content */ = {
- isa = PBXGroup;
- children = (
- AA2151F72975D9D00072F6CA /* Preview Assets.xcassets */,
- );
- path = "Preview Content";
- sourceTree = "";
- };
AA21521E2975DCA40072F6CA /* Packages */ = {
isa = PBXGroup;
children = (
@@ -219,6 +202,7 @@
AA21523F2975DF5F0072F6CA /* SecIdentityTests.swift */,
AA2152402975DF5F0072F6CA /* DistinguishedNameParserTests.swift */,
AA2152412975DF5F0072F6CA /* SecKeyPairTests.swift */,
+ AAC4C7852ADDFFAD00487E0A /* Utils.swift */,
);
name = Tests;
path = ../Tests;
@@ -234,22 +218,12 @@
AA5768A42975E7C300142200 /* ShieldHost Watch App */ = {
isa = PBXGroup;
children = (
+ AAC4C7812ADDCBE600487E0A /* ShieldHost Watch App.entitlements */,
AA5768A52975E7C300142200 /* ShieldHostApp.swift */,
- AA5768A72975E7C300142200 /* ContentView.swift */,
- AA5768A92975E7C400142200 /* Assets.xcassets */,
- AA5768AB2975E7C400142200 /* Preview Content */,
);
path = "ShieldHost Watch App";
sourceTree = "";
};
- AA5768AB2975E7C400142200 /* Preview Content */ = {
- isa = PBXGroup;
- children = (
- AA5768AC2975E7C400142200 /* Preview Assets.xcassets */,
- );
- path = "Preview Content";
- sourceTree = "";
- };
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
@@ -402,8 +376,6 @@
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
- AA2151F82975D9D00072F6CA /* Preview Assets.xcassets in Resources */,
- AA2151F42975D9D00072F6CA /* Assets.xcassets in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -425,8 +397,6 @@
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
- AA5768AD2975E7C400142200 /* Preview Assets.xcassets in Resources */,
- AA5768AA2975E7C400142200 /* Assets.xcassets in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -457,6 +427,7 @@
AA2152452975DF5F0072F6CA /* OIDTests.swift in Sources */,
AA2152462975DF5F0072F6CA /* DistinguishedNameComposerTests.swift in Sources */,
AA2152492975DF5F0072F6CA /* SecKeyTests.swift in Sources */,
+ AAC4C7862ADDFFAD00487E0A /* Utils.swift in Sources */,
AA21524D2975DF5F0072F6CA /* SecIdentityTests.swift in Sources */,
AA21524A2975DF5F0072F6CA /* SecCertificateTests.swift in Sources */,
AA21524E2975DF5F0072F6CA /* DistinguishedNameParserTests.swift in Sources */,
@@ -486,6 +457,7 @@
AA5768EE2975E87C00142200 /* DistinguishedNameParserTests.swift in Sources */,
AA5768E72975E87C00142200 /* SecIdentityTests.swift in Sources */,
AA5768EC2975E87C00142200 /* DigestTests.swift in Sources */,
+ AAC4C7872ADDFFAD00487E0A /* Utils.swift in Sources */,
AA5768E82975E87C00142200 /* CryptorTests.swift in Sources */,
AA5768E22975E87C00142200 /* DistinguishedNameComposerTests.swift in Sources */,
AA5768ED2975E87C00142200 /* CertificateBuilderECTests.swift in Sources */,
@@ -765,8 +737,10 @@
AA5768C52975E7C500142200 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
+ CODE_SIGN_ENTITLEMENTS = "ShieldHost Watch App/ShieldHost Watch App.entitlements";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
+ DEVELOPMENT_TEAM = "";
ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_KEY_CFBundleDisplayName = ShieldHost;
@@ -790,8 +764,10 @@
AA5768C62975E7C500142200 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
+ CODE_SIGN_ENTITLEMENTS = "ShieldHost Watch App/ShieldHost Watch App.entitlements";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
+ DEVELOPMENT_TEAM = "";
ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_KEY_CFBundleDisplayName = ShieldHost;
@@ -819,6 +795,7 @@
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
+ DEVELOPMENT_TEAM = "";
INFOPLIST_KEY_CFBundleDisplayName = ShieldHost;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = io.outfoxx.ShieldHost;
@@ -834,6 +811,7 @@
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
+ DEVELOPMENT_TEAM = "";
INFOPLIST_KEY_CFBundleDisplayName = ShieldHost;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = io.outfoxx.ShieldHost;
@@ -851,6 +829,7 @@
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
+ DEVELOPMENT_TEAM = "";
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = "io.outfoxx.ShieldHost-Watch-AppTests";
@@ -870,6 +849,7 @@
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
+ DEVELOPMENT_TEAM = "";
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = "io.outfoxx.ShieldHost-Watch-AppTests";
diff --git a/ShieldHost/ShieldHost.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/ShieldHost/ShieldHost.xcodeproj/project.xcworkspace/contents.xcworkspacedata
new file mode 100644
index 000000000..919434a62
--- /dev/null
+++ b/ShieldHost/ShieldHost.xcodeproj/project.xcworkspace/contents.xcworkspacedata
@@ -0,0 +1,7 @@
+
+
+
+
+
diff --git a/ShieldHost/ShieldHost.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/ShieldHost/ShieldHost.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
new file mode 100644
index 000000000..18d981003
--- /dev/null
+++ b/ShieldHost/ShieldHost.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
@@ -0,0 +1,8 @@
+
+
+
+
+ IDEDidComputeMac32BitWarning
+
+
+
diff --git a/ShieldHost/ShieldHost.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/ShieldHost/ShieldHost.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved
new file mode 100644
index 000000000..756a62bbd
--- /dev/null
+++ b/ShieldHost/ShieldHost.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved
@@ -0,0 +1,88 @@
+{
+ "object": {
+ "pins": [
+ {
+ "package": "BigInt",
+ "repositoryURL": "https://github.com/attaswift/BigInt.git",
+ "state": {
+ "branch": null,
+ "revision": "0ed110f7555c34ff468e72e1686e59721f2b0da6",
+ "version": "5.3.0"
+ }
+ },
+ {
+ "package": "Float16",
+ "repositoryURL": "https://github.com/SusanDoggie/Float16.git",
+ "state": {
+ "branch": null,
+ "revision": "936ae66adccf1c91bcaeeb9c0cddde78a13695c3",
+ "version": "1.1.1"
+ }
+ },
+ {
+ "package": "PotentCodables",
+ "repositoryURL": "https://github.com/outfoxx/PotentCodables.git",
+ "state": {
+ "branch": null,
+ "revision": "0c423eb5fdbbefffd36926430bf99f9f998c0cad",
+ "version": "3.1.1"
+ }
+ },
+ {
+ "package": "Regex",
+ "repositoryURL": "https://github.com/sharplet/Regex.git",
+ "state": {
+ "branch": null,
+ "revision": "76c2b73d4281d77fc3118391877efd1bf972f515",
+ "version": "2.1.1"
+ }
+ },
+ {
+ "package": "swift-algorithms",
+ "repositoryURL": "https://github.com/apple/swift-algorithms",
+ "state": {
+ "branch": null,
+ "revision": "b14b7f4c528c942f121c8b860b9410b2bf57825e",
+ "version": "1.0.0"
+ }
+ },
+ {
+ "package": "swift-collections",
+ "repositoryURL": "https://github.com/apple/swift-collections.git",
+ "state": {
+ "branch": null,
+ "revision": "937e904258d22af6e447a0b72c0bc67583ef64a2",
+ "version": "1.0.4"
+ }
+ },
+ {
+ "package": "SwiftDocCPlugin",
+ "repositoryURL": "https://github.com/apple/swift-docc-plugin",
+ "state": {
+ "branch": null,
+ "revision": "10bc670db657d11bdd561e07de30a9041311b2b1",
+ "version": "1.1.0"
+ }
+ },
+ {
+ "package": "SymbolKit",
+ "repositoryURL": "https://github.com/apple/swift-docc-symbolkit",
+ "state": {
+ "branch": null,
+ "revision": "b45d1f2ed151d057b54504d653e0da5552844e34",
+ "version": "1.0.0"
+ }
+ },
+ {
+ "package": "swift-numerics",
+ "repositoryURL": "https://github.com/apple/swift-numerics",
+ "state": {
+ "branch": null,
+ "revision": "0a5bc04095a675662cf24757cc0640aa2204253b",
+ "version": "1.0.2"
+ }
+ }
+ ]
+ },
+ "version": 1
+}
diff --git a/Sources/ShieldSecurity/AlgorithmIdentifier.swift b/Sources/ShieldSecurity/AlgorithmIdentifier.swift
index ce935c58a..6a2090cd1 100644
--- a/Sources/ShieldSecurity/AlgorithmIdentifier.swift
+++ b/Sources/ShieldSecurity/AlgorithmIdentifier.swift
@@ -68,7 +68,7 @@ public extension AlgorithmIdentifier {
case .ec:
let curve: OID
- switch try publicKey.attributes()[kSecAttrKeySizeInBits as String] as? Int ?? 0 {
+ switch try publicKey.keyAttributes()[kSecAttrKeySizeInBits as String] as? Int ?? 0 {
case 192:
// P-192, secp192r1
curve = iso.memberBody.us.ansix962.curves.prime.prime192v1.oid
diff --git a/Sources/ShieldSecurity/SecAccessibility.swift b/Sources/ShieldSecurity/SecAccessibility.swift
new file mode 100644
index 000000000..bb11b244a
--- /dev/null
+++ b/Sources/ShieldSecurity/SecAccessibility.swift
@@ -0,0 +1,55 @@
+//
+// SecAccessibility.swift
+// Shield
+//
+// Copyright © 2021 Outfox, inc.
+//
+//
+// Distributed under the MIT License, See LICENSE for details.
+//
+
+import Security
+
+
+public enum SecAccessibility: Equatable {
+ case `default`
+ case unlocked(afterFirst: Bool, shared: Bool)
+ case passcodeEnabled
+#if ACCESSIBILITY_ALWAYS_ENABLED
+ case always(shared: Bool)
+#endif
+}
+
+
+extension SecAccessibility {
+
+ var attr: Any {
+
+ switch self {
+
+#if ACCESSIBILITY_ALWAYS_ENABLED
+ case .always(shared: true):
+ return kSecAttrAccessibleAlways as String
+
+ case .always(shared: false):
+ return kSecAttrAccessibleAlwaysThisDeviceOnly as String
+#endif
+
+ case .unlocked(afterFirst: true, shared: true):
+ return kSecAttrAccessibleAfterFirstUnlock as String
+
+ case .unlocked(afterFirst: true, shared: false):
+ return kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly as String
+
+ case .unlocked(afterFirst: false, shared: true), .default:
+ return kSecAttrAccessibleWhenUnlocked as String
+
+ case .unlocked(afterFirst: false, shared: false):
+ return kSecAttrAccessibleWhenUnlockedThisDeviceOnly as String
+
+ case .passcodeEnabled:
+ return kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly as String
+ }
+ }
+
+}
diff --git a/Sources/ShieldSecurity/SecCertificate.swift b/Sources/ShieldSecurity/SecCertificate.swift
index 53ef72453..9be76e4d2 100644
--- a/Sources/ShieldSecurity/SecCertificate.swift
+++ b/Sources/ShieldSecurity/SecCertificate.swift
@@ -29,6 +29,7 @@ public enum SecCertificateError: Int, Error {
case trustValidationError = 5
case publicKeyRetrievalFailed = 6
case parsingFailed = 7
+ case saveDuplicate = 8
}
@@ -224,49 +225,52 @@ public extension SecCertificate {
func attributes() throws -> [String: Any] {
- #if os(iOS) || os(watchOS) || os(tvOS)
-
- let query = [
- kSecReturnAttributes as String: kCFBooleanTrue!,
- kSecValueRef as String: self,
- ] as [String: Any] as CFDictionary
-
- var data: CFTypeRef?
+ let query: [String: Any] = [
+ kSecReturnAttributes as String: true,
+ kSecValueRef as String: self,
+ kSecUseDataProtectionKeychain as String: true,
+ ]
- let status = SecItemCopyMatching(query as CFDictionary, &data)
- if status != errSecSuccess {
- throw SecCertificateError.queryFailed
- }
+ var data: CFTypeRef?
+ let status = SecItemCopyMatching(query as CFDictionary, &data)
- #elseif os(macOS)
+ guard status == errSecSuccess, let attrs = data as? [String: Any] else {
+ throw SecCertificateError.queryFailed
+ }
- let query: [String: Any] = [
- kSecReturnAttributes as String: kCFBooleanTrue!,
- kSecUseItemList as String: [self] as CFArray,
- ]
+ return attrs
+ }
- var data: AnyObject?
+ func save(accessibility: SecAccessibility = .default) throws {
- let status = SecItemCopyMatching(query as CFDictionary, &data)
- if status != errSecSuccess {
- throw SecCertificateError.queryFailed
- }
+ let query: [String: Any] = [
+ kSecClass as String: kSecClassCertificate,
+ kSecAttrLabel as String: UUID().uuidString,
+ kSecValueRef as String: self,
+ kSecAttrAccessible as String: accessibility.attr,
+ kSecUseDataProtectionKeychain as String: true,
+ ]
- #endif
+ var data: CFTypeRef?
+ let status = SecItemAdd(query as CFDictionary, &data)
- return data as! [String: Any] // swiftlint:disable:this force_cast
+ if status == errSecDuplicateItem {
+ throw SecCertificateError.saveDuplicate
+ }
+ else if status != errSecSuccess {
+ throw SecCertificateError.saveFailed
+ }
}
- func save() throws {
+ func delete() throws {
- let query = [
+ let query: [String: Any] = [
kSecClass as String: kSecClassCertificate,
kSecValueRef as String: self,
- ] as [String: Any] as CFDictionary
-
- var data: CFTypeRef?
+ kSecUseDataProtectionKeychain as String: true,
+ ]
- let status = SecItemAdd(query, &data)
+ let status = SecItemDelete(query as CFDictionary)
if status != errSecSuccess {
throw SecCertificateError.saveFailed
diff --git a/Sources/ShieldSecurity/SecIdentity.swift b/Sources/ShieldSecurity/SecIdentity.swift
index d101d5a1b..fd97c9efc 100644
--- a/Sources/ShieldSecurity/SecIdentity.swift
+++ b/Sources/ShieldSecurity/SecIdentity.swift
@@ -25,35 +25,40 @@ public extension SecIdentity {
case copyCertificateFailed
}
- static func create(certificate: SecCertificate, keyPair: SecKeyPair) throws -> SecIdentity {
+ static func create(
+ certificate: SecCertificate,
+ keyPair: SecKeyPair,
+ accessibility: SecAccessibility = .default
+ ) throws -> SecIdentity {
- return try create(certificate: certificate, privateKey: keyPair.privateKey)
+ return try create(certificate: certificate, privateKey: keyPair.privateKey, accessibility: accessibility)
}
- static func create(certificate: SecCertificate, privateKey: SecKey) throws -> SecIdentity {
+ static func create(
+ certificate: SecCertificate,
+ privateKey: SecKey,
+ accessibility: SecAccessibility = .default
+ ) throws -> SecIdentity {
do {
- try privateKey.save()
- }
- catch SecKey.Error.saveDuplicate {
- // Allowable...
+ do {
+ try privateKey.save(accessibility: accessibility)
+ }
+ catch SecKey.Error.saveDuplicate {
+ // Allowable...
+ }
+
+ do {
+ try certificate.save(accessibility: accessibility)
+ }
+ catch SecKey.Error.saveDuplicate {
+ // Allowable...
+ }
}
catch {
- throw Error.saveFailed
- }
-
- let query: [String: Any] = [
- kSecClass as String: kSecClassCertificate,
- kSecAttrLabel as String: UUID().uuidString,
- kSecValueRef as String: certificate,
- ]
-
- var data: CFTypeRef?
-
- let status = SecItemAdd(query as CFDictionary, &data)
-
- if status != errSecSuccess {
try? privateKey.delete()
+ try? certificate.delete()
+
throw Error.saveFailed
}
diff --git a/Sources/ShieldSecurity/SecKey.swift b/Sources/ShieldSecurity/SecKey.swift
index 0e0cccd8e..eaa3fbbd0 100644
--- a/Sources/ShieldSecurity/SecKey.swift
+++ b/Sources/ShieldSecurity/SecKey.swift
@@ -62,6 +62,7 @@ public extension SecKey {
let query: [String: Any] = [
kSecValueRef as String: self,
kSecReturnPersistentRef as String: kCFBooleanTrue!,
+ kSecUseDataProtectionKeychain as String: true,
]
var ref: CFTypeRef?
@@ -80,6 +81,7 @@ public extension SecKey {
let query: [String: Any] = [
kSecValuePersistentRef as String: pref,
kSecReturnRef as String: kCFBooleanTrue!,
+ kSecUseDataProtectionKeychain as String: true,
]
var ref: CFTypeRef?
@@ -108,8 +110,8 @@ public extension SecKey {
var error: Unmanaged?
- guard let key = SecKeyCreateWithData(data as CFData, attrs, &error), error == nil else {
- throw error!.takeRetainedValue()
+ guard let key = SecKeyCreateWithData(data as CFData, attrs, &error) else {
+ throw error?.takeRetainedValue() ?? Error.importFailed
}
return key
@@ -120,13 +122,13 @@ public extension SecKey {
var error: Unmanaged?
guard let data = SecKeyCopyExternalRepresentation(self, &error) else {
- throw error!.takeRetainedValue()
+ throw error?.takeRetainedValue() ?? Error.exportFailed
}
return data as Data
}
- func attributes() throws -> [String: Any] {
+ func keyAttributes() throws -> [String: Any] {
guard let attrs = SecKeyCopyAttributes(self) as? [String: Any] else {
throw Error.noAttributes
@@ -135,6 +137,24 @@ public extension SecKey {
return attrs
}
+ func attributes() throws -> [String: Any] {
+
+ let query: [String: Any] = [
+ kSecReturnAttributes as String: kCFBooleanTrue!,
+ kSecValueRef as String: self,
+ kSecUseDataProtectionKeychain as String: true,
+ ]
+
+ var data: CFTypeRef?
+
+ let status = SecItemCopyMatching(query as CFDictionary, &data)
+ guard status == errSecSuccess, let attrs = data as? [String: Any] else {
+ throw Error.build(error: .queryFailed, message: "Unable to load attributes", status: status)
+ }
+
+ return attrs
+ }
+
func keyType() throws -> SecKeyType {
let typeStr = try self.type() as CFString
@@ -148,7 +168,7 @@ public extension SecKey {
func type() throws -> String {
- let attrs = try attributes()
+ let attrs = try keyAttributes()
// iOS 10 SecKeyCopyAttributes returns string values, SecItemCopyMatching returns number values
guard let type = (attrs[kSecAttrKeyType as String] as? NSNumber)?
@@ -159,14 +179,16 @@ public extension SecKey {
return type
}
- func save() throws {
+ func save(accessibility: SecAccessibility = .default) throws {
- let attrs = try attributes()
+ let attrs = try keyAttributes()
let query: [String: Any] = [
kSecClass as String: kSecClassKey,
kSecAttrKeyClass as String: attrs[kSecAttrKeyClass as String]!,
kSecValueRef as String: self,
+ kSecUseDataProtectionKeychain as String: true,
+ kSecAttrAccessible as String: accessibility.attr,
]
let status = SecItemAdd(query as CFDictionary, nil)
@@ -182,7 +204,16 @@ public extension SecKey {
func delete() throws {
- try SecKey.delete(persistentReference: try persistentReference())
+ let query: [String: Any] = [
+ kSecClass as String: kSecClassKey,
+ kSecValueRef as String: self,
+ kSecUseDataProtectionKeychain as String: true,
+ ]
+
+ let status = SecItemDelete(query as CFDictionary)
+ if status != errSecSuccess {
+ throw Error.deleteFailed
+ }
}
static func delete(persistentReference ref: Data) throws {
@@ -190,6 +221,7 @@ public extension SecKey {
let query: [String: Any] = [
kSecClass as String: kSecClassKey,
kSecValuePersistentRef as String: ref,
+ kSecUseDataProtectionKeychain as String: true,
]
let status = SecItemDelete(query as CFDictionary)
@@ -235,12 +267,7 @@ public extension SecKey {
var error: Unmanaged?
guard let cipherText = SecKeyCreateEncryptedData(self, algorithm, plainText as CFData, &error) else {
- if let error = error {
- throw error.takeRetainedValue()
- }
- else {
- throw Error.decryptionFailed
- }
+ throw error?.takeRetainedValue() ?? Error.encryptionFailed
}
return cipherText as Data
@@ -283,12 +310,7 @@ public extension SecKey {
var error: Unmanaged?
guard let plainText = SecKeyCreateDecryptedData(self, algorithm, cipherText as CFData, &error) else {
- if let error = error {
- throw error.takeRetainedValue()
- }
- else {
- throw Error.decryptionFailed
- }
+ throw error?.takeRetainedValue() ?? Error.decryptionFailed
}
return plainText as Data
@@ -376,7 +398,7 @@ public extension SecKey {
var error: Unmanaged?
guard let signature = SecKeyCreateSignature(self, algorithm, data as CFData, &error) else {
- throw error!.takeRetainedValue()
+ throw error?.takeRetainedValue() ?? Error.signFailed
}
return signature as Data
diff --git a/Sources/ShieldSecurity/SecKeyPair.swift b/Sources/ShieldSecurity/SecKeyPair.swift
index 0829ebf2b..8d3c53f25 100644
--- a/Sources/ShieldSecurity/SecKeyPair.swift
+++ b/Sources/ShieldSecurity/SecKeyPair.swift
@@ -87,6 +87,8 @@ public struct SecKeyPair {
public enum Flag {
/// Generate the key pair in the secure enclave.
case secureEnclave
+ /// Should the key be saved in the keychain automatically.
+ case permanent
}
/// Type of key pair to generate ``SecKeyType/ec`` or ``SecKeyType/rsa``.
@@ -120,36 +122,45 @@ public struct SecKeyPair {
///
/// - Parameters:
/// - label: User-visible label of the keys (optional).
- /// - flat: Flags controlling the generation of the key pair.
+ /// - flags: Flags controlling the generation of the key pair.
+ /// - accessibility: Accessibility of the generated key pair.
/// - Returns: Generated key pair.
/// - Throws: Errors are thrown when the key generation of persistence to the kaychain fails.
///
- public func generate(label: String? = nil, flags: Set = []) throws -> SecKeyPair {
+ public func generate(
+ label: String? = nil,
+ flags: Set = [.permanent],
+ accessibility: SecAccessibility = .default
+ ) throws -> SecKeyPair {
guard let type = type else { fatalError("missing key type") }
guard let keySize = keySize else { fatalError("missing key size") }
- var attrs: [CFString: Any] = [
- kSecAttrKeyType: type.systemValue,
- kSecAttrKeySizeInBits: keySize,
- kSecAttrIsPermanent: true,
+ let isPermanent = flags.contains(.permanent) || flags.contains(.secureEnclave) || accessibility != .default
+
+ var attrs: [String: Any] = [
+ kSecAttrKeyType as String: type.systemValue,
+ kSecAttrKeySizeInBits as String: keySize,
+ kSecAttrIsPermanent as String: isPermanent,
+ kSecAttrAccessible as String: accessibility.attr,
+ kSecUseDataProtectionKeychain as String: true,
]
if let label = label {
- attrs[kSecAttrLabel] = label
+ attrs[kSecAttrLabel as String] = label
}
if flags.contains(.secureEnclave) {
- attrs[kSecAttrTokenID] = kSecAttrTokenIDSecureEnclave
+ attrs[kSecAttrTokenID as String] = kSecAttrTokenIDSecureEnclave
}
var error: Unmanaged?
guard let privateKey = SecKeyCreateRandomKey(attrs as CFDictionary, &error) else {
- throw SecKeyPair.Error.generateFailed
+ throw error?.takeRetainedValue() ?? SecKeyPair.Error.generateFailed
}
guard let publicKey = SecKeyCopyPublicKey(privateKey) else {
- throw SecKeyPair.Error.failedToCopyPublicKeyFromPrivateKey
+ throw SecKeyPair.Error.failedToCopyPublicKeyFromPrivateKey
}
return SecKeyPair(privateKey: privateKey, publicKey: publicKey)
@@ -223,10 +234,10 @@ public struct SecKeyPair {
///
/// - Throws: Errors are thrown if either of the keys could not be saved.
///
- public func save() throws {
+ public func save(accessibility: SecAccessibility = .default) throws {
- try privateKey.save()
- try publicKey.save()
+ try privateKey.save(accessibility: accessibility)
+ try publicKey.save(accessibility: accessibility)
}
/// Delete the public and private key from the `Keychain`.
@@ -619,12 +630,12 @@ private extension SecKey {
return PrivateKeyInfo(version: .zero,
privateKeyAlgorithm: .init(algorithm: iso.memberBody.us.ansix962.keyType.ecPublicKey.oid,
parameters: ASN1.objectIdentifier(curveOID.fields)),
- privateKey: privateKeyData)
+ privateKey: privateKeyData)
}
func getECCurveAndNumberSize() throws -> (OID, Int) {
- switch try attributes()[kSecAttrKeySizeInBits as String] as? Int ?? 0 {
+ switch try keyAttributes()[kSecAttrKeySizeInBits as String] as? Int ?? 0 {
case 192:
// P-192, secp192r1
return (iso.memberBody.us.ansix962.curves.prime.prime192v1.oid, 24)
diff --git a/Tests/CertificateBuilderECTests.swift b/Tests/CertificateBuilderECTests.swift
index 1294b014c..a274f74ff 100644
--- a/Tests/CertificateBuilderECTests.swift
+++ b/Tests/CertificateBuilderECTests.swift
@@ -15,19 +15,15 @@ import XCTest
class CertificateBuilderECTests: XCTestCase {
- let outputEnabled = false
- static var keyPair: SecKeyPair!
-
- override class func setUp() {
- // Keys are comparatively slow to generate... so we do it once
- guard let keyPair = try? SecKeyPair.Builder(type: .ec, keySize: 256).generate(label: "Test") else {
- return XCTFail("Key pair generation failed")
- }
- Self.keyPair = keyPair
+ static let outputEnabled = false
+ var keyPair: SecKeyPair!
+
+ override func setUpWithError() throws {
+ keyPair = try generateTestKeyPairChecked(type: .ec, keySize: 256, flags: [])
}
- override class func tearDown() {
- try? keyPair.delete()
+ override func tearDownWithError() throws {
+ try? keyPair?.delete()
}
func testBuildVer1() throws {
@@ -38,10 +34,10 @@ class CertificateBuilderECTests: XCTestCase {
let cert =
try Certificate.Builder()
.subject(name: subject)
- .publicKey(keyPair: Self.keyPair)
+ .publicKey(keyPair: keyPair)
.issuer(name: issuer)
.valid(for: 86400 * 365)
- .build(signingKey: Self.keyPair.privateKey, digestAlgorithm: .sha256)
+ .build(signingKey: keyPair.privateKey, digestAlgorithm: .sha256)
output(cert)
@@ -59,10 +55,10 @@ class CertificateBuilderECTests: XCTestCase {
let cert =
try Certificate.Builder()
.subject(name: subject, uniqueID: subjectID)
- .publicKey(keyPair: Self.keyPair)
+ .publicKey(keyPair: keyPair)
.issuer(name: issuer)
.valid(for: 86400 * 365)
- .build(signingKey: Self.keyPair.privateKey, digestAlgorithm: .sha256)
+ .build(signingKey: keyPair.privateKey, digestAlgorithm: .sha256)
output(cert)
@@ -81,10 +77,10 @@ class CertificateBuilderECTests: XCTestCase {
let cert =
try Certificate.Builder()
.subject(name: subject)
- .publicKey(keyPair: Self.keyPair)
+ .publicKey(keyPair: keyPair)
.issuer(name: issuer, uniqueID: issuerID)
.valid(for: 86400 * 365)
- .build(signingKey: Self.keyPair.privateKey, digestAlgorithm: .sha256)
+ .build(signingKey: keyPair.privateKey, digestAlgorithm: .sha256)
output(cert)
@@ -104,10 +100,10 @@ class CertificateBuilderECTests: XCTestCase {
let cert =
try Certificate.Builder()
.subject(name: subject, uniqueID: subjectID)
- .publicKey(keyPair: Self.keyPair)
+ .publicKey(keyPair: keyPair)
.issuer(name: issuer, uniqueID: issuerID)
.valid(for: 86400 * 365)
- .build(signingKey: Self.keyPair.privateKey, digestAlgorithm: .sha256)
+ .build(signingKey: keyPair.privateKey, digestAlgorithm: .sha256)
output(cert)
@@ -129,14 +125,14 @@ class CertificateBuilderECTests: XCTestCase {
try Certificate.Builder()
.subject(name: subject, uniqueID: subjectID)
.addSubjectAlternativeNames(names: .dnsName("github.com/outfoxx/Shield"))
- .publicKey(keyPair: Self.keyPair)
+ .publicKey(keyPair: keyPair)
.extendedKeyUsage(
keyPurposes: [iso.org.dod.internet.security.mechanisms.pkix.kp.serverAuth.oid],
isCritical: false
)
.issuer(name: issuer, uniqueID: issuerID)
.valid(for: 86400 * 365)
- .build(signingKey: Self.keyPair.privateKey, digestAlgorithm: .sha256)
+ .build(signingKey: keyPair.privateKey, digestAlgorithm: .sha256)
output(cert)
@@ -156,10 +152,10 @@ class CertificateBuilderECTests: XCTestCase {
try Certificate.Builder()
.subject(name: subject)
.addSubjectAlternativeNames(names: .dnsName("github.com/outfoxx/Shield"))
- .publicKey(keyPair: Self.keyPair)
+ .publicKey(keyPair: keyPair)
.issuer(name: issuer)
.valid(for: 86400 * 365)
- .build(signingKey: Self.keyPair.privateKey, digestAlgorithm: .sha256)
+ .build(signingKey: keyPair.privateKey, digestAlgorithm: .sha256)
output(cert)
@@ -179,18 +175,18 @@ class CertificateBuilderECTests: XCTestCase {
try Certificate.Builder()
.subject(name: subject, uniqueID: subjectID)
.addSubjectAlternativeNames(names: .dnsName("github.com/outfoxx/Shield"))
- .publicKey(keyPair: Self.keyPair)
+ .publicKey(keyPair: keyPair)
.issuer(name: issuer, uniqueID: issuerID)
.addIssuerAlternativeNames(names: .dnsName("github.com/outfoxx/Shield/CA"))
.basicConstraints(ca: true)
.authorityKeyIdentifier(
- Digester.digest(Self.keyPair.encodedPublicKey(), using: .sha1),
+ Digester.digest(keyPair.encodedPublicKey(), using: .sha1),
certIssuer: [.dnsName("github.com/outfoxx/Shield/CA")],
certSerialNumber: Certificate.Builder.randomSerialNumber()
)
.computeSubjectKeyIdentifier()
.valid(for: 86400 * 365)
- .build(signingKey: Self.keyPair.privateKey, digestAlgorithm: .sha256)
+ .build(signingKey: keyPair.privateKey, digestAlgorithm: .sha256)
output(cert)
@@ -208,8 +204,8 @@ class CertificateBuilderECTests: XCTestCase {
let csrData =
try CertificationRequest.Builder()
.subject(name: NameBuilder().add("Shield Subject", forTypeName: "CN").name)
- .publicKey(keyPair: Self.keyPair)
- .build(signingKey: Self.keyPair.privateKey, digestAlgorithm: .sha256)
+ .publicKey(keyPair: keyPair)
+ .build(signingKey: keyPair.privateKey, digestAlgorithm: .sha256)
.encoded()
let csr = try ASN1Decoder.decode(CertificationRequest.self, from: csrData)
@@ -222,7 +218,7 @@ class CertificateBuilderECTests: XCTestCase {
.request(csr)
.issuer(name: csr.certificationRequestInfo.subject)
.valid(for: 86400 * 365)
- .build(signingKey: Self.keyPair.privateKey, digestAlgorithm: .sha256)
+ .build(signingKey: keyPair.privateKey, digestAlgorithm: .sha256)
output(cert)
@@ -237,12 +233,12 @@ class CertificateBuilderECTests: XCTestCase {
try CertificationRequest.Builder()
.subject(name: NameBuilder().add("Shield Subject", forTypeName: "CN").name)
.addAlternativeNames(names: altNames)
- .publicKey(keyPair: Self.keyPair, usage: [.dataEncipherment])
+ .publicKey(keyPair: keyPair, usage: [.dataEncipherment])
.extendedKeyUsage(
keyPurposes: [iso.org.dod.internet.security.mechanisms.pkix.kp.serverAuth.oid],
isCritical: false
)
- .build(signingKey: Self.keyPair.privateKey, digestAlgorithm: .sha256)
+ .build(signingKey: keyPair.privateKey, digestAlgorithm: .sha256)
.encoded()
let csr = try ASN1Decoder.decode(CertificationRequest.self, from: csrData)
@@ -255,7 +251,7 @@ class CertificateBuilderECTests: XCTestCase {
.request(csr)
.issuer(name: csr.certificationRequestInfo.subject)
.valid(for: 86400 * 365)
- .build(signingKey: Self.keyPair.privateKey, digestAlgorithm: .sha256)
+ .build(signingKey: keyPair.privateKey, digestAlgorithm: .sha256)
output(cert)
@@ -264,7 +260,7 @@ class CertificateBuilderECTests: XCTestCase {
}
func output(_ value: Encodable & SchemaSpecified) {
- guard outputEnabled else { return }
+ guard Self.outputEnabled else { return }
guard let data = try? value.encoded().base64EncodedString() else { return }
print(data)
}
diff --git a/Tests/CertificateBuilderRSATests.swift b/Tests/CertificateBuilderRSATests.swift
index f5b6eea2c..8a0be2221 100644
--- a/Tests/CertificateBuilderRSATests.swift
+++ b/Tests/CertificateBuilderRSATests.swift
@@ -15,19 +15,15 @@ import XCTest
class CertificateBuilderRSATests: XCTestCase {
- let outputEnabled = false
- static var keyPair: SecKeyPair!
-
- override class func setUp() {
- // Keys are comparatively slow to generate... so we do it once
- guard let keyPair = try? SecKeyPair.Builder(type: .rsa, keySize: 2048).generate(label: "Test") else {
- return XCTFail("Key pair generation failed")
- }
- Self.keyPair = keyPair
+ static let outputEnabled = false
+ var keyPair: SecKeyPair!
+
+ override func setUpWithError() throws {
+ keyPair = try generateTestKeyPairChecked(type: .rsa, keySize: 2048, flags: [])
}
- override class func tearDown() {
- try? keyPair.delete()
+ override func tearDownWithError() throws {
+ try? keyPair?.delete()
}
func testBuildVer1() throws {
@@ -38,10 +34,10 @@ class CertificateBuilderRSATests: XCTestCase {
let cert =
try Certificate.Builder()
.subject(name: subject)
- .publicKey(keyPair: Self.keyPair)
+ .publicKey(keyPair: keyPair)
.issuer(name: issuer)
.valid(for: 86400 * 365)
- .build(signingKey: Self.keyPair.privateKey, digestAlgorithm: .sha256)
+ .build(signingKey: keyPair.privateKey, digestAlgorithm: .sha256)
output(cert)
@@ -59,10 +55,10 @@ class CertificateBuilderRSATests: XCTestCase {
let cert =
try Certificate.Builder()
.subject(name: subject, uniqueID: subjectID)
- .publicKey(keyPair: Self.keyPair)
+ .publicKey(keyPair: keyPair)
.issuer(name: issuer)
.valid(for: 86400 * 365)
- .build(signingKey: Self.keyPair.privateKey, digestAlgorithm: .sha256)
+ .build(signingKey: keyPair.privateKey, digestAlgorithm: .sha256)
output(cert)
@@ -81,10 +77,10 @@ class CertificateBuilderRSATests: XCTestCase {
let cert =
try Certificate.Builder()
.subject(name: subject)
- .publicKey(keyPair: Self.keyPair)
+ .publicKey(keyPair: keyPair)
.issuer(name: issuer, uniqueID: issuerID)
.valid(for: 86400 * 365)
- .build(signingKey: Self.keyPair.privateKey, digestAlgorithm: .sha256)
+ .build(signingKey: keyPair.privateKey, digestAlgorithm: .sha256)
output(cert)
@@ -104,10 +100,10 @@ class CertificateBuilderRSATests: XCTestCase {
let cert =
try Certificate.Builder()
.subject(name: subject, uniqueID: subjectID)
- .publicKey(keyPair: Self.keyPair)
+ .publicKey(keyPair: keyPair)
.issuer(name: issuer, uniqueID: issuerID)
.valid(for: 86400 * 365)
- .build(signingKey: Self.keyPair.privateKey, digestAlgorithm: .sha256)
+ .build(signingKey: keyPair.privateKey, digestAlgorithm: .sha256)
output(cert)
@@ -129,14 +125,14 @@ class CertificateBuilderRSATests: XCTestCase {
try Certificate.Builder()
.subject(name: subject, uniqueID: subjectID)
.addSubjectAlternativeNames(names: .dnsName("github.com/outfoxx/Shield"))
- .publicKey(keyPair: Self.keyPair)
+ .publicKey(keyPair: keyPair)
.extendedKeyUsage(
keyPurposes: [iso.org.dod.internet.security.mechanisms.pkix.kp.serverAuth.oid],
isCritical: false
)
.issuer(name: issuer, uniqueID: issuerID)
.valid(for: 86400 * 365)
- .build(signingKey: Self.keyPair.privateKey, digestAlgorithm: .sha256)
+ .build(signingKey: keyPair.privateKey, digestAlgorithm: .sha256)
output(cert)
@@ -156,10 +152,10 @@ class CertificateBuilderRSATests: XCTestCase {
try Certificate.Builder()
.subject(name: subject)
.addSubjectAlternativeNames(names: .dnsName("github.com/outfoxx/Shield"))
- .publicKey(keyPair: Self.keyPair)
+ .publicKey(keyPair: keyPair)
.issuer(name: issuer)
.valid(for: 86400 * 365)
- .build(signingKey: Self.keyPair.privateKey, digestAlgorithm: .sha256)
+ .build(signingKey: keyPair.privateKey, digestAlgorithm: .sha256)
output(cert)
@@ -179,18 +175,18 @@ class CertificateBuilderRSATests: XCTestCase {
try Certificate.Builder()
.subject(name: subject, uniqueID: subjectID)
.addSubjectAlternativeNames(names: .dnsName("github.com/outfoxx/Shield"))
- .publicKey(keyPair: Self.keyPair)
+ .publicKey(keyPair: keyPair)
.issuer(name: issuer, uniqueID: issuerID)
.addIssuerAlternativeNames(names: .dnsName("github.com/outfoxx/Shield/CA"))
.basicConstraints(ca: true)
.authorityKeyIdentifier(
- Digester.digest(Self.keyPair.encodedPublicKey(), using: .sha1),
+ Digester.digest(keyPair.encodedPublicKey(), using: .sha1),
certIssuer: [.dnsName("github.com/outfoxx/Shield/CA")],
certSerialNumber: Certificate.Builder.randomSerialNumber()
)
.computeSubjectKeyIdentifier()
.valid(for: 86400 * 365)
- .build(signingKey: Self.keyPair.privateKey, digestAlgorithm: .sha256)
+ .build(signingKey: keyPair.privateKey, digestAlgorithm: .sha256)
output(cert)
@@ -208,8 +204,8 @@ class CertificateBuilderRSATests: XCTestCase {
let csrData =
try CertificationRequest.Builder()
.subject(name: NameBuilder().add("Shield Subject", forTypeName: "CN").name)
- .publicKey(keyPair: Self.keyPair)
- .build(signingKey: Self.keyPair.privateKey, digestAlgorithm: .sha256)
+ .publicKey(keyPair: keyPair)
+ .build(signingKey: keyPair.privateKey, digestAlgorithm: .sha256)
.encoded()
let csr = try ASN1Decoder.decode(CertificationRequest.self, from: csrData)
@@ -222,7 +218,7 @@ class CertificateBuilderRSATests: XCTestCase {
.request(csr)
.issuer(name: csr.certificationRequestInfo.subject)
.valid(for: 86400 * 365)
- .build(signingKey: Self.keyPair.privateKey, digestAlgorithm: .sha256)
+ .build(signingKey: keyPair.privateKey, digestAlgorithm: .sha256)
output(cert)
@@ -237,12 +233,12 @@ class CertificateBuilderRSATests: XCTestCase {
try CertificationRequest.Builder()
.subject(name: NameBuilder().add("Shield Subject", forTypeName: "CN").name)
.addAlternativeNames(names: altNames)
- .publicKey(keyPair: Self.keyPair, usage: [.dataEncipherment])
+ .publicKey(keyPair: keyPair, usage: [.dataEncipherment])
.extendedKeyUsage(
keyPurposes: [iso.org.dod.internet.security.mechanisms.pkix.kp.serverAuth.oid],
isCritical: false
)
- .build(signingKey: Self.keyPair.privateKey, digestAlgorithm: .sha256)
+ .build(signingKey: keyPair.privateKey, digestAlgorithm: .sha256)
.encoded()
let csr = try ASN1Decoder.decode(CertificationRequest.self, from: csrData)
@@ -255,7 +251,7 @@ class CertificateBuilderRSATests: XCTestCase {
.request(csr)
.issuer(name: csr.certificationRequestInfo.subject)
.valid(for: 86400 * 365)
- .build(signingKey: Self.keyPair.privateKey, digestAlgorithm: .sha256)
+ .build(signingKey: keyPair.privateKey, digestAlgorithm: .sha256)
output(cert)
@@ -264,7 +260,7 @@ class CertificateBuilderRSATests: XCTestCase {
}
func output(_ value: Encodable & SchemaSpecified) {
- guard outputEnabled else { return }
+ guard Self.outputEnabled else { return }
guard let data = try? value.encoded().base64EncodedString() else { return }
print(data)
}
diff --git a/Tests/CertificateDecoderTests.swift b/Tests/CertificateDecoderTests.swift
index 0a14bbbe0..1a30447b7 100644
--- a/Tests/CertificateDecoderTests.swift
+++ b/Tests/CertificateDecoderTests.swift
@@ -17,10 +17,12 @@ import XCTest
class CertificateDecoderTests: XCTestCase {
+ static let outputEnabled = false
+
func testDecodingTestCerts() throws {
for (title, pem) in Self.certificates {
- print("Checking: \(title)")
+ output("Checking: \(title)")
guard let testCert = try SecCertificate.load(pem: pem).first else {
XCTFail("Failed to load '\(title)'")
continue
@@ -28,44 +30,44 @@ class CertificateDecoderTests: XCTestCase {
do {
let decoded = try ASN1.Decoder.decode(Certificate.self, from: testCert.derEncoded)
let subjectName = try NameStringComposer().append(rdnSequence: decoded.tbsCertificate.subject).string
- print("Version: \(decoded.tbsCertificate.version)")
- print("Serial Number: \(decoded.tbsCertificate.serialNumber.magnitude.serialize())")
- print("Subject: \(subjectName)")
+ output("Version: \(decoded.tbsCertificate.version)")
+ output("Serial Number: \(decoded.tbsCertificate.serialNumber.magnitude.serialize())")
+ output("Subject: \(subjectName)")
let issuerName = try NameStringComposer().append(rdnSequence: decoded.tbsCertificate.issuer).string
- print("Issuer: \(issuerName)")
- print("Validity:")
- print(" Not Before: \(decoded.tbsCertificate.validity.notBefore.zonedDate.iso8601EncodedString())")
- print(" Not After: \(decoded.tbsCertificate.validity.notAfter.zonedDate.iso8601EncodedString())")
+ output("Issuer: \(issuerName)")
+ output("Validity:")
+ output(" Not Before: \(decoded.tbsCertificate.validity.notBefore.zonedDate.iso8601EncodedString())")
+ output(" Not After: \(decoded.tbsCertificate.validity.notAfter.zonedDate.iso8601EncodedString())")
for ext in decoded.tbsCertificate.extensions ?? [] {
- print("Extension:")
+ output("Extension:")
switch ext.extnID {
case iso_itu.ds.certificateExtension.basicConstraints.oid:
let basicContstraints = try ASN1Decoder.decode(BasicConstraints.self, from: ext.extnValue)
- print(" \(basicContstraints)")
+ output(" \(basicContstraints)")
case iso_itu.ds.certificateExtension.extKeyUsage.oid:
let extKeyUsage = try ASN1Decoder.decode(ExtKeyUsage.self, from: ext.extnValue)
- print(" \(extKeyUsage)")
+ output(" \(extKeyUsage)")
case iso_itu.ds.certificateExtension.subjectKeyIdentifier.oid:
let subjKeyId = try ASN1Decoder.decode(SubjectKeyIdentifier.self, from: ext.extnValue)
- print(" \(subjKeyId)")
+ output(" \(subjKeyId)")
case iso_itu.ds.certificateExtension.authorityKeyIdentifier.oid:
let authKeyId = try ASN1Decoder.decode(AuthorityKeyIdentifier.self, from: ext.extnValue)
- print(" \(authKeyId)")
+ output(" \(authKeyId)")
case iso_itu.ds.certificateExtension.subjectAltName.oid:
let subjectAltName = try ASN1Decoder.decode(SubjectAltName.self, from: ext.extnValue)
- print(" \(subjectAltName)")
+ output(" \(subjectAltName)")
case iso_itu.ds.certificateExtension.issuerAltName.oid:
let issuerAltName = try ASN1Decoder.decode(IssuerAltName.self, from: ext.extnValue)
- print(" \(issuerAltName)")
+ output(" \(issuerAltName)")
case let oid:
- print(" Unknown: \(oid)")
+ output(" Unknown: \(oid)")
}
}
}
@@ -175,4 +177,15 @@ class CertificateDecoderTests: XCTestCase {
""",
]
+ func output(_ value: String) {
+ guard Self.outputEnabled else { return }
+ print(value)
+ }
+
+ func output(_ value: Encodable & SchemaSpecified) {
+ guard Self.outputEnabled else { return }
+ guard let data = try? value.encoded().base64EncodedString() else { return }
+ output(data)
+ }
+
}
diff --git a/Tests/CertificationRequestBuilderTests.swift b/Tests/CertificationRequestBuilderTests.swift
index 5e043d443..518ed76be 100644
--- a/Tests/CertificationRequestBuilderTests.swift
+++ b/Tests/CertificationRequestBuilderTests.swift
@@ -16,18 +16,14 @@ import XCTest
class CertificationRequestBuilderTests: XCTestCase {
static let outputEnabled = false
- static var keyPair: SecKeyPair!
-
- override class func setUp() {
- // Keys are comparatively slow to generate... so we do it once
- guard let keyPair = try? SecKeyPair.Builder(type: .rsa, keySize: 2048).generate(label: "Test") else {
- return XCTFail("Key pair generation failed")
- }
- Self.keyPair = keyPair
+ var keyPair: SecKeyPair!
+
+ override func setUpWithError() throws {
+ keyPair = try generateTestKeyPairChecked(type: .rsa, keySize: 2048, flags: [])
}
- override class func tearDown() {
- try? keyPair.delete()
+ override func tearDownWithError() throws {
+ try? keyPair?.delete()
}
func testBuildParse() throws {
@@ -36,9 +32,9 @@ class CertificationRequestBuilderTests: XCTestCase {
let csr =
try CertificationRequest.Builder()
.subject(name: NameBuilder().add("Outfox Signing", forTypeName: "CN").name)
- .publicKey(keyPair: Self.keyPair, usage: [.keyCertSign, .cRLSign])
+ .publicKey(keyPair: keyPair, usage: [.keyCertSign, .cRLSign])
.extendedKeyUsage(keyPurposes: [keyPurpose.clientAuth.oid, keyPurpose.serverAuth.oid], isCritical: true)
- .build(signingKey: Self.keyPair.privateKey, digestAlgorithm: .sha256)
+ .build(signingKey: keyPair.privateKey, digestAlgorithm: .sha256)
output(csr)
@@ -73,9 +69,9 @@ class CertificationRequestBuilderTests: XCTestCase {
try CertificationRequest.Builder()
.subject(name: NameBuilder().add("Outfox Signing", forTypeName: "CN").name)
.addAlternativeNames(names: sans)
- .publicKey(keyPair: Self.keyPair, usage: [.keyCertSign, .cRLSign])
+ .publicKey(keyPair: keyPair, usage: [.keyCertSign, .cRLSign])
.extendedKeyUsage(keyPurposes: [keyPurpose.clientAuth.oid, keyPurpose.serverAuth.oid], isCritical: true)
- .build(signingKey: Self.keyPair.privateKey, digestAlgorithm: .sha256)
+ .build(signingKey: keyPair.privateKey, digestAlgorithm: .sha256)
output(csr)
diff --git a/Tests/SecCertificateTests.swift b/Tests/SecCertificateTests.swift
index 9649210af..fa6aca830 100644
--- a/Tests/SecCertificateTests.swift
+++ b/Tests/SecCertificateTests.swift
@@ -14,20 +14,79 @@ import XCTest
class SecCertificateTests: XCTestCase {
static let outputEnabled = false
- static var keyPair: SecKeyPair!
+ var keyPair: SecKeyPair!
- override class func setUp() {
- // Keys are comparatively slow to generate... so we do it once
- guard let keyPair = try? SecKeyPair.Builder(type: .rsa, keySize: 2048).generate(label: "Test") else {
- return XCTFail("Key pair generation failed")
- }
- Self.keyPair = keyPair
+ override func setUpWithError() throws {
+ keyPair = try generateTestKeyPairChecked(type: .rsa, keySize: 2048, flags: [])
}
- override class func tearDown() {
- try? keyPair.delete()
+ override func tearDownWithError() throws {
+ try? keyPair?.delete()
}
+ func testAttributesFailForNonPermanentCerts() throws {
+
+ let subjectName = try NameBuilder()
+ .add("Unit Testing", forTypeName: "CN")
+ .add("123456", forTypeName: "UID")
+ .name
+
+ let issuerName = try NameBuilder()
+ .add("Test Issuer", forTypeName: "CN")
+ .name
+
+ let certData =
+ try Certificate.Builder()
+ .subject(name: subjectName)
+ .issuer(name: issuerName)
+ .publicKey(keyPair: keyPair, usage: [.keyCertSign, .cRLSign])
+ .valid(for: 86400 * 5)
+ .build(signingKey: keyPair.privateKey, digestAlgorithm: .sha256)
+ .encoded()
+
+ let cert = try SecCertificate.from(data: certData)
+ XCTAssertThrowsError(try cert.attributes())
+ }
+
+ func testSaveAcccessibilityUnlockedNotShared() throws {
+
+ let subjectName = try NameBuilder()
+ .add("Unit Testing", forTypeName: "CN")
+ .add("123456", forTypeName: "UID")
+ .name
+
+ let issuerName = try NameBuilder()
+ .add("Test Issuer", forTypeName: "CN")
+ .name
+
+ let certData =
+ try Certificate.Builder()
+ .subject(name: subjectName)
+ .issuer(name: issuerName)
+ .publicKey(keyPair: keyPair, usage: [.keyCertSign, .cRLSign])
+ .valid(for: 86400 * 5)
+ .build(signingKey: keyPair.privateKey, digestAlgorithm: .sha256)
+ .encoded()
+
+ output(certData)
+
+ let cert = try SecCertificate.from(data: certData)
+ defer { try? cert.delete() }
+
+ do {
+ try cert.save(accessibility: .unlocked(afterFirst: true, shared: false))
+ }
+ catch SecCertificateError.saveFailed {
+ #if os(macOS)
+ throw XCTSkip("Missing keychain entitlement")
+ #endif
+ }
+
+ let attrs = try cert.attributes()
+ XCTAssertEqual(attrs[kSecAttrAccessible as String] as? String, kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly as String)
+ }
+
+
func testCertificateProperties() throws {
let subjectName = try NameBuilder()
@@ -43,9 +102,9 @@ class SecCertificateTests: XCTestCase {
try Certificate.Builder()
.subject(name: subjectName)
.issuer(name: issuerName)
- .publicKey(keyPair: Self.keyPair, usage: [.keyCertSign, .cRLSign])
+ .publicKey(keyPair: keyPair, usage: [.keyCertSign, .cRLSign])
.valid(for: 86400 * 5)
- .build(signingKey: Self.keyPair.privateKey, digestAlgorithm: .sha256)
+ .build(signingKey: keyPair.privateKey, digestAlgorithm: .sha256)
.encoded()
output(certData)
@@ -71,16 +130,16 @@ class SecCertificateTests: XCTestCase {
try Certificate.Builder()
.subject(name: subjectName)
.issuer(name: issuerName)
- .publicKey(keyPair: Self.keyPair, usage: [.keyCertSign, .cRLSign])
+ .publicKey(keyPair: keyPair, usage: [.keyCertSign, .cRLSign])
.valid(for: 86400 * 5)
- .build(signingKey: Self.keyPair.privateKey, digestAlgorithm: .sha256)
+ .build(signingKey: keyPair.privateKey, digestAlgorithm: .sha256)
.encoded()
output(certData)
let cert = try SecCertificate.from(data: certData)
- XCTAssertEqual(try cert.publicKey?.encode(), try Self.keyPair.publicKey.encode())
+ XCTAssertEqual(try cert.publicKey?.encode(), try keyPair.publicKey.encode())
}
func testPEM() throws {
@@ -98,9 +157,9 @@ class SecCertificateTests: XCTestCase {
try Certificate.Builder()
.subject(name: subjectName)
.issuer(name: issuerName)
- .publicKey(keyPair: Self.keyPair, usage: [.keyCertSign, .cRLSign])
+ .publicKey(keyPair: keyPair, usage: [.keyCertSign, .cRLSign])
.valid(for: 86400 * 5)
- .build(signingKey: Self.keyPair.privateKey, digestAlgorithm: .sha256)
+ .build(signingKey: keyPair.privateKey, digestAlgorithm: .sha256)
.encoded()
let certSec = try SecCertificate.from(data: certData)
@@ -124,9 +183,9 @@ class SecCertificateTests: XCTestCase {
try Certificate.Builder()
.subject(name: subjectName)
.issuer(name: issuerName)
- .publicKey(keyPair: Self.keyPair, usage: [.keyCertSign, .cRLSign])
+ .publicKey(keyPair: keyPair, usage: [.keyCertSign, .cRLSign])
.valid(for: 86400 * 5)
- .build(signingKey: Self.keyPair.privateKey, digestAlgorithm: .sha256)
+ .build(signingKey: keyPair.privateKey, digestAlgorithm: .sha256)
.encoded()
let certSec = try SecCertificate.from(data: certData)
@@ -140,25 +199,25 @@ class SecCertificateTests: XCTestCase {
let rootName = try NameBuilder().add("Unit Testing Root", forTypeName: "CN").name
let rootID = try Random.generate(count: 10)
let rootSerialNumber = try Certificate.Builder.randomSerialNumber()
- let rootKeyHash = try Digester.digest(Self.keyPair.encodedPublicKey(), using: .sha1)
+ let rootKeyHash = try Digester.digest(keyPair.encodedPublicKey(), using: .sha1)
let rootCertData =
try Certificate.Builder()
.serialNumber(rootSerialNumber)
.subject(name: rootName, uniqueID: rootID)
.subjectAlternativeNames(names: .dnsName("io.outfoxx.shield.tests.ca"))
- .publicKey(keyPair: Self.keyPair, usage: [.keyCertSign, .cRLSign])
+ .publicKey(keyPair: keyPair, usage: [.keyCertSign, .cRLSign])
.subjectKeyIdentifier(rootKeyHash)
.issuer(name: rootName)
.issuerAlternativeNames(names: .dnsName("io.outfoxx.shield.tests.ca"))
.basicConstraints(ca: true)
.valid(for: 86400 * 5)
- .build(signingKey: Self.keyPair.privateKey, digestAlgorithm: .sha256)
+ .build(signingKey: keyPair.privateKey, digestAlgorithm: .sha256)
.encoded()
output(rootCertData)
let rootCert = try SecCertificate.from(data: rootCertData)
- let certKeyPair = try SecKeyPair.Builder(type: .ec, keySize: 256).generate()
+ let certKeyPair = try generateTestKeyPairChecked(type: .ec, keySize: 256, flags: [])
defer { try? certKeyPair.delete() }
let certName = try NameBuilder().add("Unit Testing", forTypeName: "CN").name
@@ -175,7 +234,7 @@ class SecCertificateTests: XCTestCase {
.issuerAlternativeNames(names: .dnsName("io.outfoxx.shield.tests.ca"))
.authorityKeyIdentifier(rootKeyHash, certIssuer: [.directoryName(rootName)], certSerialNumber: rootSerialNumber)
.valid(for: 86400 * 5)
- .build(signingKey: Self.keyPair.privateKey, digestAlgorithm: .sha256)
+ .build(signingKey: keyPair.privateKey, digestAlgorithm: .sha256)
.encoded()
output(certData)
@@ -206,25 +265,25 @@ class SecCertificateTests: XCTestCase {
let rootName = try NameBuilder().add("Unit Testing Root", forTypeName: "CN").name
let rootID = try Random.generate(count: 10)
let rootSerialNumber = try Certificate.Builder.randomSerialNumber()
- let rootKeyHash = try Digester.digest(Self.keyPair.encodedPublicKey(), using: .sha1)
+ let rootKeyHash = try Digester.digest(keyPair.encodedPublicKey(), using: .sha1)
let rootCertData =
try Certificate.Builder()
.serialNumber(rootSerialNumber)
.subject(name: rootName, uniqueID: rootID)
.subjectAlternativeNames(names: .dnsName("io.outfoxx.shield.tests.ca"))
- .publicKey(keyPair: Self.keyPair, usage: [.keyCertSign, .cRLSign])
+ .publicKey(keyPair: keyPair, usage: [.keyCertSign, .cRLSign])
.subjectKeyIdentifier(rootKeyHash)
.issuer(name: rootName)
.issuerAlternativeNames(names: .dnsName("io.outfoxx.shield.tests.ca"))
.basicConstraints(ca: true)
.valid(for: 86400 * 5)
- .build(signingKey: Self.keyPair.privateKey, digestAlgorithm: .sha256)
+ .build(signingKey: keyPair.privateKey, digestAlgorithm: .sha256)
.encoded()
output(rootCertData)
let rootCert = try SecCertificate.from(data: rootCertData)
- let certKeyPair = try SecKeyPair.Builder(type: .ec, keySize: 256).generate()
+ let certKeyPair = try generateTestKeyPairChecked(type: .ec, keySize: 256, flags: [])
defer { try? certKeyPair.delete() }
let certName = try NameBuilder().add("Unit Testing", forTypeName: "CN").name
@@ -241,7 +300,7 @@ class SecCertificateTests: XCTestCase {
.issuerAlternativeNames(names: .dnsName("io.outfoxx.shield.tests.ca"))
.authorityKeyIdentifier(rootKeyHash, certIssuer: [.directoryName(rootName)], certSerialNumber: rootSerialNumber)
.valid(for: 86400 * 5)
- .build(signingKey: Self.keyPair.privateKey, digestAlgorithm: .sha256)
+ .build(signingKey: keyPair.privateKey, digestAlgorithm: .sha256)
.encoded()
output(certData)
@@ -262,13 +321,13 @@ class SecCertificateTests: XCTestCase {
try Certificate.Builder()
.subject(name: rootName)
.issuer(name: rootName)
- .publicKey(keyPair: Self.keyPair, usage: [.keyCertSign])
+ .publicKey(keyPair: keyPair, usage: [.keyCertSign])
.valid(for: 86400 * 5)
- .build(signingKey: Self.keyPair.privateKey, digestAlgorithm: .sha256)
+ .build(signingKey: keyPair.privateKey, digestAlgorithm: .sha256)
.encoded()
)
- let certKeyPair = try SecKeyPair.Builder(type: .ec, keySize: 256).generate()
+ let certKeyPair = try generateTestKeyPairChecked(type: .ec, keySize: 256, flags: [])
defer { try? certKeyPair.delete() }
let certName = try NameBuilder().add("Unit Testing", forTypeName: "CN").name
@@ -310,13 +369,13 @@ class SecCertificateTests: XCTestCase {
try Certificate.Builder()
.subject(name: rootName)
.issuer(name: rootName)
- .publicKey(keyPair: Self.keyPair, usage: [.keyCertSign])
+ .publicKey(keyPair: keyPair, usage: [.keyCertSign])
.valid(for: 86400 * 5)
- .build(signingKey: Self.keyPair.privateKey, digestAlgorithm: .sha256)
+ .build(signingKey: keyPair.privateKey, digestAlgorithm: .sha256)
.encoded()
)
- let certKeyPair = try SecKeyPair.Builder(type: .ec, keySize: 256).generate()
+ let certKeyPair = try generateTestKeyPairChecked(type: .ec, keySize: 256, flags: [])
defer { try? certKeyPair.delete() }
let certName = try NameBuilder().add("Unit Testing", forTypeName: "CN").name
@@ -327,7 +386,7 @@ class SecCertificateTests: XCTestCase {
.issuer(name: rootName)
.publicKey(keyPair: certKeyPair, usage: [.keyEncipherment])
.valid(for: 86400 * 5)
- .build(signingKey: Self.keyPair.privateKey, digestAlgorithm: .sha256)
+ .build(signingKey: keyPair.privateKey, digestAlgorithm: .sha256)
.encoded()
)
diff --git a/Tests/SecIdentityTests.swift b/Tests/SecIdentityTests.swift
index 5b8fab885..1ca0e84d6 100644
--- a/Tests/SecIdentityTests.swift
+++ b/Tests/SecIdentityTests.swift
@@ -16,15 +16,15 @@ class SecIdentityTests: XCTestCase {
func testBuildAndFetch() throws {
+ let keyPair = try generateTestKeyPairChecked(type: .rsa, keySize: 2048, flags: [])
+ defer { try? keyPair.delete() }
+
let subject: Name = try NameBuilder()
.add("Test Guy", forTypeName: "CN")
.add("Test Corp", forTypeName: "O")
.add("TC", forTypeName: "C")
.name
- let keyPair = try SecKeyPair.Builder(type: .rsa, keySize: 2048).generate(label: "Test")
- defer { try? keyPair.delete() }
-
// Build a self-signed certificate for importing
let cert =
try Certificate.Builder()
@@ -43,8 +43,21 @@ class SecIdentityTests: XCTestCase {
}
// Ensure all went well
- let ident = try SecIdentity.create(certificate: cert, keyPair: keyPair)
- defer {}
+ let ident: SecIdentity
+ do {
+ ident = try SecIdentity.create(certificate: cert, keyPair: keyPair)
+ }
+ catch SecIdentity.Error.saveFailed {
+ #if os(macOS)
+ throw XCTSkip("Missing keychain entitlement")
+ #else
+ return XCTFail("Save failed")
+ #endif
+ }
+ defer {
+ try? cert.delete()
+ try? keyPair.delete()
+ }
XCTAssertNotNil(ident)
XCTAssertNotNil(try ident.certificate())
diff --git a/Tests/SecKeyPairTests.swift b/Tests/SecKeyPairTests.swift
index 939886567..02298bb1e 100644
--- a/Tests/SecKeyPairTests.swift
+++ b/Tests/SecKeyPairTests.swift
@@ -15,28 +15,17 @@ import XCTest
class SecKeyPairTests: XCTestCase {
- var rsaKeyPair: SecKeyPair!
- var ecKeyPair: SecKeyPair!
-
- override func setUpWithError() throws {
- try super.setUpWithError()
-
- rsaKeyPair = try SecKeyPair.Builder(type: .rsa, keySize: 2048).generate(label: "Test RSA Key")
- ecKeyPair = try SecKeyPair.Builder(type: .ec, keySize: 256).generate(label: "Test EC Key")
- }
+ var keyPair: SecKeyPair!
override func tearDownWithError() throws {
-
- try? rsaKeyPair?.delete()
- try? ecKeyPair?.delete()
-
- try super.tearDownWithError()
+ try? keyPair?.delete()
}
func testGeneratedRSA() throws {
+ keyPair = try generateTestKeyPairChecked(type: .rsa, keySize: 2048)
let privateKeyAttrs = [
- kSecAttrLabel: "Test RSA Key",
+ kSecAttrLabel: "Test RSA Key Pair",
kSecClass: kSecClassKey,
kSecAttrKeyClass: kSecAttrKeyClassPrivate,
kSecReturnRef: kCFBooleanTrue!,
@@ -46,20 +35,21 @@ class SecKeyPairTests: XCTestCase {
XCTAssertNotNil(privateKeyRef)
let publicKeyAttrs = [
- kSecAttrLabel: "Test RSA Key",
+ kSecAttrLabel: "Test RSA Key Pair",
kSecClass: kSecClassKey,
kSecAttrKeyClass: kSecAttrKeyClassPublic,
kSecReturnRef: kCFBooleanTrue!,
] as [String: Any] as CFDictionary
var publicKeyRef: CFTypeRef?
- XCTAssertEqual(SecItemCopyMatching(publicKeyAttrs as CFDictionary, &publicKeyRef), errSecSuccess)
+ XCTAssertEqual(SecItemCopyMatching(publicKeyAttrs, &publicKeyRef), errSecSuccess)
XCTAssertNotNil(publicKeyRef)
}
func testGeneratedEC() throws {
+ keyPair = try generateTestKeyPairChecked(type: .ec, keySize: 256)
let privateKeyAttrs = [
- kSecAttrLabel: "Test EC Key",
+ kSecAttrLabel: "Test EC Key Pair",
kSecClass: kSecClassKey,
kSecAttrKeyClass: kSecAttrKeyClassPrivate,
kSecReturnRef: kCFBooleanTrue!,
@@ -69,45 +59,50 @@ class SecKeyPairTests: XCTestCase {
XCTAssertNotNil(privateKeyRef)
let publicKeyAttrs = [
- kSecAttrLabel: "Test EC Key",
+ kSecAttrLabel: "Test EC Key Pair",
kSecClass: kSecClassKey,
kSecAttrKeyClass: kSecAttrKeyClassPublic,
kSecReturnRef: kCFBooleanTrue!,
] as [String: Any] as CFDictionary
var publicKeyRef: CFTypeRef?
- XCTAssertEqual(SecItemCopyMatching(publicKeyAttrs as CFDictionary, &publicKeyRef), errSecSuccess)
+ XCTAssertEqual(SecItemCopyMatching(publicKeyAttrs, &publicKeyRef), errSecSuccess)
XCTAssertNotNil(publicKeyRef)
}
func testInitECFromExternalPrivateKey() throws {
+ keyPair = try generateTestKeyPairChecked(type: .ec, keySize: 256, flags: [])
- let external = try ecKeyPair.privateKey.encode()
+ let external = try keyPair.privateKey.encode()
- XCTAssertNoThrow(try SecKeyPair(type: ecKeyPair.privateKey.keyType(), privateKeyData: external))
+ XCTAssertNoThrow(try SecKeyPair(type: keyPair.privateKey.keyType(), privateKeyData: external))
}
func testInitRSAFromExternalPrivateKey() throws {
+ keyPair = try generateTestKeyPairChecked(type: .rsa, keySize: 2048, flags: [])
- let external = try rsaKeyPair.privateKey.encode()
+ let external = try keyPair.privateKey.encode()
- XCTAssertNoThrow(try SecKeyPair(type: rsaKeyPair.privateKey.keyType(), privateKeyData: external))
+ XCTAssertNoThrow(try SecKeyPair(type: keyPair.privateKey.keyType(), privateKeyData: external))
}
func testPersistentLoadRSA() throws {
+ keyPair = try generateTestKeyPairChecked(type: .rsa, keySize: 2048, flags: [.permanent])
- let (privateKeyRef, publicKeyRef) = try rsaKeyPair.persistentReferences()
+ let (privateKeyRef, publicKeyRef) = try keyPair.persistentReferences()
XCTAssertNotNil(try SecKeyPair(privateKeyRef: privateKeyRef, publicKeyRef: publicKeyRef))
}
func testPersistentLoadEC() throws {
+ keyPair = try generateTestKeyPairChecked(type: .ec, keySize: 256, flags: [.permanent])
- let (privateKeyRef, publicKeyRef) = try ecKeyPair.persistentReferences()
+ let (privateKeyRef, publicKeyRef) = try keyPair.persistentReferences()
XCTAssertNotNil(try SecKeyPair(privateKeyRef: privateKeyRef, publicKeyRef: publicKeyRef))
}
func testCertificateMatching() throws {
+ keyPair = try generateTestKeyPairChecked(type: .rsa, keySize: 2048, flags: [])
let name = try NameBuilder().add("Unit Testing", forTypeName: "CN").name
@@ -115,9 +110,9 @@ class SecKeyPairTests: XCTestCase {
try Certificate.Builder()
.subject(name: name)
.issuer(name: name)
- .publicKey(keyPair: rsaKeyPair, usage: [.keyEncipherment])
+ .publicKey(keyPair: keyPair, usage: [.keyEncipherment])
.valid(for: 86400 * 5)
- .build(signingKey: rsaKeyPair.privateKey, digestAlgorithm: .sha256)
+ .build(signingKey: keyPair.privateKey, digestAlgorithm: .sha256)
.encoded()
let cert = SecCertificateCreateWithData(nil, certData as CFData)!
@@ -127,7 +122,7 @@ class SecKeyPairTests: XCTestCase {
DispatchQueue.global(qos: .userInitiated).async {
defer { finishedX.fulfill() }
- let result = self.rsaKeyPair.matchesCertificate(certificate: cert, trustedCertificates: [cert])
+ let result = self.keyPair.matchesCertificate(certificate: cert, trustedCertificates: [cert])
XCTAssertTrue(result)
}
@@ -137,6 +132,7 @@ class SecKeyPairTests: XCTestCase {
#if swift(>=5.5)
func testCertificateMatchingAsync() async throws {
+ keyPair = try generateTestKeyPairChecked(type: .rsa, keySize: 2048, flags: [])
let name = try NameBuilder().add("Unit Testing", forTypeName: "CN").name
@@ -144,21 +140,22 @@ class SecKeyPairTests: XCTestCase {
try Certificate.Builder()
.subject(name: name)
.issuer(name: name)
- .publicKey(keyPair: rsaKeyPair, usage: [.keyEncipherment])
+ .publicKey(keyPair: keyPair, usage: [.keyEncipherment])
.valid(for: 86400 * 5)
- .build(signingKey: rsaKeyPair.privateKey, digestAlgorithm: .sha256)
+ .build(signingKey: keyPair.privateKey, digestAlgorithm: .sha256)
.encoded()
let cert = SecCertificateCreateWithData(nil, certData as CFData)!
- let result = await self.rsaKeyPair.matchesCertificate(certificate: cert, trustedCertificates: [cert])
+ let result = await self.keyPair.matchesCertificate(certificate: cert, trustedCertificates: [cert])
XCTAssertTrue(result)
}
#endif
func testImportExportEncryptedRSA() throws {
+ keyPair = try generateTestKeyPairChecked(type: .rsa, keySize: 2048, flags: [])
- let exportedKeyData = try rsaKeyPair.export(password: "123")
+ let exportedKeyData = try keyPair.export(password: "123")
let importedKeyPair = try SecKeyPair.import(data: exportedKeyData, password: "123")
@@ -166,7 +163,7 @@ class SecKeyPairTests: XCTestCase {
let plainText = try Random.generate(count: 171)
- let cipherText1 = try rsaKeyPair.publicKey.encrypt(plainText: plainText, padding: .oaep)
+ let cipherText1 = try keyPair.publicKey.encrypt(plainText: plainText, padding: .oaep)
let plainText2 = try importedKeyPair.privateKey.decrypt(cipherText: cipherText1, padding: .oaep)
@@ -174,12 +171,12 @@ class SecKeyPairTests: XCTestCase {
let cipherText2 = try importedKeyPair.publicKey.encrypt(plainText: plainText, padding: .oaep)
- let plainText3 = try rsaKeyPair.privateKey.decrypt(cipherText: cipherText2, padding: .oaep)
+ let plainText3 = try keyPair.privateKey.decrypt(cipherText: cipherText2, padding: .oaep)
XCTAssertEqual(plainText, plainText3)
- try rsaKeyPair.delete()
- defer { rsaKeyPair = nil }
+ try? keyPair.delete()
+ defer { keyPair = nil }
let cipherText3 = try importedKeyPair.publicKey.encrypt(plainText: plainText, padding: .oaep)
@@ -190,14 +187,15 @@ class SecKeyPairTests: XCTestCase {
}
func testImportExportRSA() throws {
+ keyPair = try generateTestKeyPairChecked(type: .rsa, keySize: 2048, flags: [])
- let exportedKeyData = try rsaKeyPair.export()
+ let exportedKeyData = try keyPair.export()
let importedKeyPair = try SecKeyPair.import(data: exportedKeyData)
let plainText = try Random.generate(count: 171)
- let cipherText1 = try rsaKeyPair.publicKey.encrypt(plainText: plainText, padding: .oaep)
+ let cipherText1 = try keyPair.publicKey.encrypt(plainText: plainText, padding: .oaep)
let plainText2 = try importedKeyPair.privateKey.decrypt(cipherText: cipherText1, padding: .oaep)
@@ -205,8 +203,9 @@ class SecKeyPairTests: XCTestCase {
}
func testImportExportEncryptedEC() throws {
+ keyPair = try generateTestKeyPairChecked(type: .ec, keySize: 256, flags: [])
- let exportedKeyData = try ecKeyPair.export(password: "123")
+ let exportedKeyData = try keyPair.export(password: "123")
_ = try SecKeyPair.import(data: exportedKeyData, password: "123")
@@ -214,87 +213,65 @@ class SecKeyPairTests: XCTestCase {
}
func testImportExportEC192() throws {
+ keyPair = try generateTestKeyPairChecked(type: .ec, keySize: 192, flags: [])
- let ecKeyPair =
- try SecKeyPair.Builder(type: .ec, keySize: 192)
- .generate(label: "Test 192 EC Key")
- defer { try? ecKeyPair.delete() }
-
- XCTAssertThrowsError(try SecKeyPair.import(data: ecKeyPair.export())) { error in
+ XCTAssertThrowsError(try SecKeyPair.import(data: keyPair.export())) { error in
XCTAssertTrue(error is AlgorithmIdentifier.Error)
}
}
func testImportExportEC256() throws {
+ keyPair = try generateTestKeyPairChecked(type: .ec, keySize: 256, flags: [])
- let ecKeyPair =
- try SecKeyPair.Builder(type: .ec, keySize: 256)
- .generate(label: "Test 256 EC Key")
- defer { try? ecKeyPair.delete() }
-
- _ = try SecKeyPair.import(data: ecKeyPair.export())
+ _ = try SecKeyPair.import(data: keyPair.export())
}
func testImportExportEC384() throws {
+ keyPair = try generateTestKeyPairChecked(type: .ec, keySize: 384, flags: [])
- let ecKeyPair =
- try SecKeyPair.Builder(type: .ec, keySize: 384)
- .generate(label: "Test 384 EC Key")
- defer { try? ecKeyPair.delete() }
-
- _ = try SecKeyPair.import(data: ecKeyPair.export())
+ _ = try SecKeyPair.import(data: keyPair.export())
}
func testImportExportEC521() throws {
+ keyPair = try generateTestKeyPairChecked(type: .ec, keySize: 521, flags: [])
- let ecKeyPair =
- try SecKeyPair.Builder(type: .ec, keySize: 521)
- .generate(label: "Test 521 EC Key")
- defer { try? ecKeyPair.delete() }
-
- _ = try SecKeyPair.import(data: ecKeyPair.export())
+ _ = try SecKeyPair.import(data: keyPair.export())
}
- func testCodable() throws {
+ func testCodableRSA() throws {
+ keyPair = try generateTestKeyPairChecked(type: .rsa, keySize: 2048)
+
+ let rsaData = try JSONEncoder().encode(keyPair)
+ let testKeyPair = try JSONDecoder().decode(SecKeyPair.self, from: rsaData)
+ XCTAssertEqual(testKeyPair.privateKey, keyPair.privateKey)
+ XCTAssertEqual(testKeyPair.publicKey, keyPair.publicKey)
+ }
- let rsaData = try JSONEncoder().encode(rsaKeyPair)
- let testRSAKeyPair = try JSONDecoder().decode(SecKeyPair.self, from: rsaData)
- XCTAssertEqual(testRSAKeyPair.privateKey, rsaKeyPair.privateKey)
- XCTAssertEqual(testRSAKeyPair.publicKey, rsaKeyPair.publicKey)
+ func testCodableEC() throws {
+ keyPair = try generateTestKeyPairChecked(type: .ec, keySize: 521)
- let ecData = try JSONEncoder().encode(ecKeyPair)
- let testECKeyPair = try JSONDecoder().decode(SecKeyPair.self, from: ecData)
- XCTAssertEqual(testECKeyPair.privateKey, ecKeyPair.privateKey)
- XCTAssertEqual(testECKeyPair.publicKey, ecKeyPair.publicKey)
+ let ecData = try JSONEncoder().encode(keyPair)
+ let testKeyPair = try JSONDecoder().decode(SecKeyPair.self, from: ecData)
+ XCTAssertEqual(testKeyPair.privateKey, keyPair.privateKey)
+ XCTAssertEqual(testKeyPair.publicKey, keyPair.publicKey)
}
func testGenerateSecureEnclave() throws {
-#if os(macOS)
- try XCTSkipIf(true, "Code signing complexities require this to be disabled for macOS")
-#else
- try XCTSkipUnless(SecureEnclave.isAvailable, "Only runs on iPhone/iPad/AppleTV")
-#endif
-
- let keyPairBuilder = SecKeyPair.Builder(type: .ec, keySize: 256)
+ try XCTSkipUnless(SecureEnclave.isAvailable, "Requires secure enclave")
- var keyPair: SecKeyPair?
- XCTAssertNoThrow(keyPair = try keyPairBuilder.generate(label: "Test Secure Key", flags: [.secureEnclave]))
- XCTAssertNoThrow(try keyPair?.delete())
+ keyPair = try generateTestKeyPairChecked(type: .ec, keySize: 256, flags: [.secureEnclave])
}
func testGeneratedSecureEnclave() throws {
+ try XCTSkipUnless(SecureEnclave.isAvailable, "Requires secure enclave")
#if os(macOS)
try XCTSkipIf(true, "Code signing complexities require this to be disabled for macOS")
-#else
- try XCTSkipUnless(SecureEnclave.isAvailable, "Only runs on iPhone/iPad/AppleTV")
#endif
- let ecKeyPair = try SecKeyPair.Builder(type: .ec, keySize: 256).generate(label: "Test Secure Enclave EC Key",
- flags: [.secureEnclave])
- defer { try? ecKeyPair.delete() }
+ keyPair = try generateTestKeyPairChecked(type: .ec, keySize: 256, flags: [.secureEnclave])
let privateKeyAttrs = [
- kSecAttrLabel: "Test Secure Enclave EC Key",
+ kSecAttrLabel: "Test Secure Enclave EC Key Pair",
kSecClass: kSecClassKey,
kSecAttrKeyClass: kSecAttrKeyClassPrivate,
kSecReturnRef: kCFBooleanTrue!,
@@ -304,13 +281,13 @@ class SecKeyPairTests: XCTestCase {
XCTAssertNotNil(privateKeyRef)
let publicKeyAttrs = [
- kSecAttrLabel: "Test Secure Enclave EC Key",
+ kSecAttrLabel: "Test Secure Enclave EC Key Pair",
kSecClass: kSecClassKey,
kSecAttrKeyClass: kSecAttrKeyClassPublic,
kSecReturnRef: kCFBooleanTrue!,
] as [String: Any] as CFDictionary
var publicKeyRef: CFTypeRef?
- XCTAssertEqual(SecItemCopyMatching(publicKeyAttrs as CFDictionary, &publicKeyRef), errSecSuccess)
+ XCTAssertEqual(SecItemCopyMatching(publicKeyAttrs, &publicKeyRef), errSecSuccess)
XCTAssertNotNil(publicKeyRef)
}
diff --git a/Tests/SecKeyTests.swift b/Tests/SecKeyTests.swift
index 1e76c4482..7e6a2d352 100644
--- a/Tests/SecKeyTests.swift
+++ b/Tests/SecKeyTests.swift
@@ -8,13 +8,55 @@
// Distributed under the MIT License, See LICENSE for details.
//
+import Security
@testable import Shield
import XCTest
class SecKeyTests: XCTestCase {
+ func testAttributesFailForNonPermanentKeys() throws {
+ let keyPair = try generateTestKeyPairChecked(type: .ec, keySize: 256, flags: [])
+ defer { try? keyPair.delete() }
+
+ XCTAssertNoThrow(try keyPair.privateKey.keyAttributes())
+ XCTAssertThrowsError(try keyPair.privateKey.attributes())
+ }
+
+ func testSaveAcccessibilityUnlocked() throws {
+ // Check entitlement
+ try generateTestKeyPairChecked(type: .ec, keySize: 256).delete()
+
+ let keyPair = try generateTestKeyPairChecked(type: .ec, keySize: 256, flags: [])
+ defer { try? keyPair.delete() }
+
+ try keyPair.save(accessibility: .unlocked(afterFirst: false, shared: true))
+
+ let attrs = try keyPair.privateKey.attributes()
+ XCTAssertEqual(attrs[kSecAttrAccessible as String] as? String, kSecAttrAccessibleWhenUnlocked as String)
+ }
+
+ func testGenerateAccessibilityUnlocked() throws {
+ let keyPair = try generateTestKeyPairChecked(type: .ec,
+ keySize: 256,
+ accessibility: .unlocked(afterFirst: false, shared: true))
+ defer { try? keyPair.delete() }
+
+ let attrs = try keyPair.privateKey.attributes()
+ XCTAssertEqual(attrs[kSecAttrAccessible as String] as? String, kSecAttrAccessibleWhenUnlocked as String)
+ }
+
+ func testGenerateAccessibilityAfterFirstUnlockNotShared() throws {
+ let keyPair = try generateTestKeyPairChecked(type: .ec,
+ keySize: 256,
+ accessibility: .unlocked(afterFirst: true, shared: false))
+ defer { try? keyPair.delete() }
+
+ let attrs = try keyPair.privateKey.attributes()
+ XCTAssertEqual(attrs[kSecAttrAccessible as String] as? String, kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly as String)
+ }
+
func testRSA() throws {
- let keyPair = try SecKeyPair.Builder(type: .rsa, keySize: 2048).generate(label: "Test")
+ let keyPair = try generateTestKeyPairChecked(type: .rsa, keySize: 2048, flags: [])
defer { try? keyPair.delete() }
print("Checking: RSA Encrypt/Decrypt")
@@ -36,7 +78,8 @@ class SecKeyTests: XCTestCase {
}
func testEC() throws {
- let keyPair = try SecKeyPair.Builder(type: .ec, keySize: 256).generate(label: "Test")
+ let keyPair = try generateTestKeyPairChecked(type: .ec, keySize: 256, flags: [])
+ defer { try? keyPair.delete() }
print("Checking: EC Encrypt/Decrypt")
testFailedEncryptError(keyPair)
@@ -58,7 +101,7 @@ class SecKeyTests: XCTestCase {
func testECGeneration() throws {
try [192, 256, 384, 521].forEach { keySize in
- let keyPair = try SecKeyPair.Builder(type: .ec, keySize: keySize).generate(label: "Test")
+ let keyPair = try generateTestKeyPairChecked(type: .ec, keySize: keySize, flags: [])
defer { try? keyPair.delete() }
_ = try AlgorithmIdentifier(publicKey: keyPair.publicKey)
diff --git a/Tests/Utils.swift b/Tests/Utils.swift
new file mode 100644
index 000000000..8920e6c7d
--- /dev/null
+++ b/Tests/Utils.swift
@@ -0,0 +1,45 @@
+//
+// Utils.swift
+// Shield
+//
+// Copyright © 2021 Outfox, inc.
+//
+//
+// Distributed under the MIT License, See LICENSE for details.
+//
+
+import Shield
+import Security
+import XCTest
+
+func generateTestKeyPairChecked(
+ type: SecKeyType,
+ keySize: Int,
+ flags: Set = [.permanent],
+ accessibility: SecAccessibility = .default
+) throws -> SecKeyPair {
+ let label: String = [
+ "Test",
+ flags.contains(.secureEnclave) ? "Secure Enclave" : nil,
+ type == .ec ? "EC" : "RSA",
+ "Key Pair",
+ ].compactMap { $0 }.joined(separator: " ")
+
+ do {
+ return try SecKeyPair.Builder(type: type, keySize: keySize).generate(label: label,
+ flags: flags,
+ accessibility: accessibility)
+ }
+ catch let error where isEntitlementMissingError(error) {
+ #if os(macOS)
+ throw XCTSkip("Missing keychain entitlement")
+ #else
+ throw error
+ #endif
+ }
+}
+
+func isEntitlementMissingError(_ error: Error) -> Bool {
+ let error = error as NSError
+ return error.domain == NSOSStatusErrorDomain && error.code == -34018
+}