From 66752ad6121bd95c4d6ce8170bbb50d8532bd077 Mon Sep 17 00:00:00 2001 From: Gelareh Taban Date: Tue, 26 Sep 2023 17:43:25 -0700 Subject: [PATCH 1/3] initial commit adding accessGroup to the Trifle API and underlying APIs. --- ios/Example/Podfile.lock | 4 +- .../Sources/Common/KeychainQueries.swift | 6 ++- .../Digital Signature/ContentSigner.swift | 2 +- .../DigitalSignatureKeyManager.swift | 4 +- .../DigitalSignatureSigner.swift | 8 ++-- .../DigitalSignatureVerifier.swift | 4 +- ...ureEnclaveDigitalSignatureKeyManager.swift | 37 ++++++++++--------- .../SecureEnclaveKeychainQueries.swift | 22 ++++++++--- .../PKCS10CertificateRequestBuilder.swift | 7 ++-- ios/Trifle/Sources/Trifle.swift | 22 ++++++++--- 10 files changed, 72 insertions(+), 44 deletions(-) diff --git a/ios/Example/Podfile.lock b/ios/Example/Podfile.lock index 00c7307..a66c2a4 100644 --- a/ios/Example/Podfile.lock +++ b/ios/Example/Podfile.lock @@ -1,5 +1,5 @@ PODS: - - Trifle (0.2.3): + - Trifle (0.2.4): - Wire (~> 4) - Wire (4.5.1) - WireCompiler (4.5.1) @@ -18,7 +18,7 @@ EXTERNAL SOURCES: :path: "../../" SPEC CHECKSUMS: - Trifle: 1bc03d4dadabef03a928fc05924bbd7df1a53dd0 + Trifle: bb8422a8904ddfa192d5b7beedfbf5505087c947 Wire: b07a2ff1c4cd4b71f5ae26771cdd13fc7868c9df WireCompiler: 417c2ac583c01de328010738658758556ea92a92 diff --git a/ios/Trifle/Sources/Common/KeychainQueries.swift b/ios/Trifle/Sources/Common/KeychainQueries.swift index 3e945f3..030b8e8 100644 --- a/ios/Trifle/Sources/Common/KeychainQueries.swift +++ b/ios/Trifle/Sources/Common/KeychainQueries.swift @@ -13,8 +13,12 @@ protocol KeychainQueries { - parameter applicationTag: an application tag used to distinguish the key from other keys in the keychain - parameter returnRef: whether a reference to `SecKey` should be returned + - parameter accessGroup: the access group the security item belongs to. If no access group is set, then the + calling app's default access group is used. */ - static func getQuery(with applicationTag: String, returnRef: Bool) -> NSMutableDictionary + static func getQuery(with applicationTag: String, + returnRef: Bool, + _ accessGroup: String? ) -> NSMutableDictionary } // MARK: - diff --git a/ios/Trifle/Sources/Digital Signature/ContentSigner.swift b/ios/Trifle/Sources/Digital Signature/ContentSigner.swift index f12589a..aa77152 100644 --- a/ios/Trifle/Sources/Digital Signature/ContentSigner.swift +++ b/ios/Trifle/Sources/Digital Signature/ContentSigner.swift @@ -17,5 +17,5 @@ protocol ContentSigner: DigitalSignatureSigner { operation fails if the key is not exportable - returns: the signing public key along with its key type */ - func exportPublicKey(_ tag: String) throws -> SigningPublicKey + func exportPublicKey(_ tag: String, _ accessGroup: String?) throws -> SigningPublicKey } diff --git a/ios/Trifle/Sources/Digital Signature/DigitalSignatureKeyManager.swift b/ios/Trifle/Sources/Digital Signature/DigitalSignatureKeyManager.swift index 937a9e3..c25ddda 100644 --- a/ios/Trifle/Sources/Digital Signature/DigitalSignatureKeyManager.swift +++ b/ios/Trifle/Sources/Digital Signature/DigitalSignatureKeyManager.swift @@ -18,7 +18,7 @@ protocol DigitalSignatureKeyManager { - throws: an error type `CryptographicKeyError` - returns: the private key for signing */ - func signingKey(_ tag: String) throws -> PrivateKey + func signingKey(_ tag: String, _ accessGroup: String?) throws -> PrivateKey /** Fetches the verifying key @@ -26,5 +26,5 @@ protocol DigitalSignatureKeyManager { - throws: an error type `CryptographicKeyError` - returns: the public key for verifying */ - func verifyingKey(_ tag: String) throws -> PublicKey + func verifyingKey(_ tag: String, _ accessGroup: String?) throws -> PublicKey } diff --git a/ios/Trifle/Sources/Digital Signature/DigitalSignatureSigner.swift b/ios/Trifle/Sources/Digital Signature/DigitalSignatureSigner.swift index d26f8ed..9ad6435 100644 --- a/ios/Trifle/Sources/Digital Signature/DigitalSignatureSigner.swift +++ b/ios/Trifle/Sources/Digital Signature/DigitalSignatureSigner.swift @@ -8,17 +8,17 @@ import Foundation /// Signer to perform digital signature signing operations public protocol DigitalSignatureSigner { /** - Generates a tag associated with a signing key pair and stores it to the keychain. + Generates a tag associated with a signing key pair and stores it to the keychain for the specified access group. Returns the associated tag value. - throws: an error type `CryptographicKeyError` when the operation fails if the key is not exportable - returns: tag value */ - func generateTag() throws -> String + func generateTag(_ accessGroup: String?) throws -> String /** - Signs a blob of data with the underlying signing key + Signs a blob of data with the underlying signing key for the specified access group. - throws: an error type `CryptographicKeyError` if the signing operation cannot be completed due to the underlying @@ -26,5 +26,5 @@ public protocol DigitalSignatureSigner { - returns: the digital signature which includes the signature data and the algorithm used for signing */ - func sign(for tag: String, with data: Data) throws -> DigitalSignature + func sign(for tag: String, with data: Data, _ accessGroup: String?) throws -> DigitalSignature } diff --git a/ios/Trifle/Sources/Digital Signature/DigitalSignatureVerifier.swift b/ios/Trifle/Sources/Digital Signature/DigitalSignatureVerifier.swift index e851717..c2163f9 100644 --- a/ios/Trifle/Sources/Digital Signature/DigitalSignatureVerifier.swift +++ b/ios/Trifle/Sources/Digital Signature/DigitalSignatureVerifier.swift @@ -8,12 +8,12 @@ import Foundation /// Verifier to perform digital signature verification operations public protocol DigitalSignatureVerifier { /** - Verifies a blob of data with a supplied signature using the underlying verifying key + Verifies a blob of data with a supplied signature using the underlying verifying key in the specified access group. - throws: an error type `CryptographicKeyError` if the verifying operation cannot be completed due to the underlying verifying key being unavailable. - returns: true if the signature is verified, false otherwise */ - func verify(for tag: String, data: Data, with signature: Data) throws -> Bool + func verify(for tag: String, data: Data, with signature: Data, _ accessGroup: String?) throws -> Bool } diff --git a/ios/Trifle/Sources/Digital Signature/Secure Enclave/SecureEnclaveDigitalSignatureKeyManager.swift b/ios/Trifle/Sources/Digital Signature/Secure Enclave/SecureEnclaveDigitalSignatureKeyManager.swift index 7c62751..10089b2 100644 --- a/ios/Trifle/Sources/Digital Signature/Secure Enclave/SecureEnclaveDigitalSignatureKeyManager.swift +++ b/ios/Trifle/Sources/Digital Signature/Secure Enclave/SecureEnclaveDigitalSignatureKeyManager.swift @@ -36,17 +36,17 @@ public class SecureEnclaveDigitalSignatureKeyManager // MARK: - Public Methods (DigitalSignatureSigner) - public func generateTag() throws -> String { + public func generateTag(_ accessGroup: String? = nil) throws -> String { let tag = tagFormat.replacingOccurrences( of: "{{uuid}}", with: UUID().uuidString ) - try Self.generateKeypair(tag) + try Self.generateKeypair(tag, accessGroup) return tag } - public func sign(for tag: String, with data: Data) throws -> DigitalSignature { - let signature = try signingKey(tag).sign(with: data) + public func sign(for tag: String, with data: Data, _ accessGroup: String? = nil) throws -> DigitalSignature { + let signature = try signingKey(tag, accessGroup).sign(with: data) return DigitalSignature( signingAlgorithm: Self.keyInfo.signingAlgorithm, data: signature @@ -55,24 +55,24 @@ public class SecureEnclaveDigitalSignatureKeyManager // MARK: - Public Methods (DigitalSignatureVerifier) - public func verify(for tag: String, data: Data, with signature: Data) throws -> Bool { - return try verifyingKey(tag).verify(data: data, with: signature) + public func verify(for tag: String, data: Data, with signature: Data, _ accessGroup: String? = nil) throws -> Bool { + return try verifyingKey(tag, accessGroup).verify(data: data, with: signature) } // MARK: - Internal Methods (ContentSigner) - internal func exportPublicKey(_ tag: String) throws -> SigningPublicKey { + internal func exportPublicKey(_ tag: String, _ accessGroup: String?) throws -> SigningPublicKey { return SigningPublicKey( keyInfo: Self.keyInfo, - data: try verifyingKey(tag).export() + data: try verifyingKey(tag, accessGroup).export() ) } // MARK: - Internal Methods (DigitalSignatureKeyManager) - internal func signingKey(_ tag: String) throws -> SecureEnclaveSigningKey { + internal func signingKey(_ tag: String, _ accessGroup: String?) throws -> SecureEnclaveSigningKey { var keyRef: CFTypeRef? - let preparedQuery = SecureEnclaveKeychainQueries.getQuery(with: tag, returnRef: true) + let preparedQuery = SecureEnclaveKeychainQueries.getQuery(with: tag, returnRef: true, accessGroup) guard case let status = SecItemCopyMatching(preparedQuery, &keyRef), status == errSecSuccess, keyRef != nil else { @@ -82,8 +82,8 @@ public class SecureEnclaveDigitalSignatureKeyManager return try SecureEnclaveSigningKey(keyRef as! SecKey, Self.keyInfo.signingAlgorithm.attrs) } - internal func verifyingKey(_ tag: String) throws -> SecureEnclaveVerifyingKey { - let signingKey = try signingKey(tag) + internal func verifyingKey(_ tag: String, _ accessGroup: String?) throws -> SecureEnclaveVerifyingKey { + let signingKey = try signingKey(tag, accessGroup) guard let publicKey = SecKeyCopyPublicKey(signingKey.privateKey) else { throw CryptographicKeyError.unavailablePublicKey @@ -94,12 +94,13 @@ public class SecureEnclaveDigitalSignatureKeyManager // MARK: - - private static func generateKeypair(_ tag: String) throws { + private static func generateKeypair(_ tag: String, _ accessGroup: String?) throws { let (keyType, keySize) = Self.keyInfo.curve.attrs let attributes = try SecureEnclaveKeychainQueries.attributes( with: tag, keyType: keyType, - keySize: keySize + keySize: keySize, + accessGroup: accessGroup ) var error: Unmanaged? @@ -108,8 +109,8 @@ public class SecureEnclaveDigitalSignatureKeyManager } } - internal static func deleteKeypair(_ tag: String) throws -> Bool { - let preparedQuery = SecureEnclaveKeychainQueries.getQuery(with: tag) + internal static func deleteKeypair(_ tag: String, _ accessGroup: String?) throws -> Bool { + let preparedQuery = SecureEnclaveKeychainQueries.getQuery(with: tag, accessGroup) let status = SecItemDelete(preparedQuery) switch status { @@ -120,8 +121,8 @@ public class SecureEnclaveDigitalSignatureKeyManager } } - internal static func keyExists(_ tag: String) throws -> Bool { - let preparedQuery = SecureEnclaveKeychainQueries.getQuery(with: tag) + internal static func keyExists(_ tag: String, _ accessGroup: String?) throws -> Bool { + let preparedQuery = SecureEnclaveKeychainQueries.getQuery(with: tag, accessGroup) let status = SecItemCopyMatching(preparedQuery, nil) switch status { case errSecSuccess: diff --git a/ios/Trifle/Sources/Digital Signature/Secure Enclave/SecureEnclaveKeychainQueries.swift b/ios/Trifle/Sources/Digital Signature/Secure Enclave/SecureEnclaveKeychainQueries.swift index 501c152..6da529a 100644 --- a/ios/Trifle/Sources/Digital Signature/Secure Enclave/SecureEnclaveKeychainQueries.swift +++ b/ios/Trifle/Sources/Digital Signature/Secure Enclave/SecureEnclaveKeychainQueries.swift @@ -33,7 +33,8 @@ internal struct SecureEnclaveKeychainQueries: KeychainQueries { internal static func attributes( with applicationTag: String, keyType: CFString, - keySize: Int + keySize: Int, + accessGroup: String? ) throws -> NSMutableDictionary { let attributes: NSMutableDictionary = [ kSecAttrKeyType: keyType, @@ -44,7 +45,11 @@ internal struct SecureEnclaveKeychainQueries: KeychainQueries { kSecAttrAccessControl: try Self.access, ] as [CFString: Any], ] - + // if accessGroup is nil, the calling application's access group will be used by default + if let accessGroup = accessGroup { + attributes[kSecAttrAccessGroupToken] = accessGroup + } + #if !targetEnvironment(simulator) attributes.setValue(kSecAttrTokenIDSecureEnclave, forKey: kSecAttrTokenID as String) #endif @@ -54,13 +59,20 @@ internal struct SecureEnclaveKeychainQueries: KeychainQueries { // MARK: - KeychainQueries - internal static func getQuery(with applicationTag: String, returnRef: Bool = false) -> NSMutableDictionary { - return [ + internal static func getQuery(with applicationTag: String, + returnRef: Bool = false, + _ accessGroup: String? ) -> NSMutableDictionary { + let attributes: NSMutableDictionary = [ kSecClass as String: kSecClassKey, kSecAttrApplicationTag as String: applicationTag.data(using: .utf8)!, kSecAttrKeyType as String: kSecAttrKeyTypeECSECPrimeRandom, - kSecReturnRef as String: returnRef, + kSecReturnRef as String: returnRef, ] + // if accessGroup is nil, the calling application's access group will be used by default + if let accessGroup = accessGroup { + attributes[kSecAttrAccessGroupToken] = accessGroup + } + return attributes } } diff --git a/ios/Trifle/Sources/PKCS10/PKCS10CertificateRequestBuilder.swift b/ios/Trifle/Sources/PKCS10/PKCS10CertificateRequestBuilder.swift index f9d5e86..daac376 100644 --- a/ios/Trifle/Sources/PKCS10/PKCS10CertificateRequestBuilder.swift +++ b/ios/Trifle/Sources/PKCS10/PKCS10CertificateRequestBuilder.swift @@ -35,16 +35,17 @@ extension PKCS10CertificationRequest { return self } - func sign(for tag: String, with signer: ContentSigner) throws + func sign(for tag: String, with signer: ContentSigner, _ accessGroup: String? = nil) throws -> PKCS10CertificationRequest { let certificateRequestInfo = try buildCertificateRequestInfo( - with: try signer.exportPublicKey(tag) + with: try signer.exportPublicKey(tag, accessGroup) ) let signature = try signer.sign( for: tag, - with: Data(certificateRequestInfo.octets) + with: Data(certificateRequestInfo.octets), + accessGroup ) let certificationRequest = try ASN1Sequence([ diff --git a/ios/Trifle/Sources/Trifle.swift b/ios/Trifle/Sources/Trifle.swift index c3e3a6e..ba8c56b 100644 --- a/ios/Trifle/Sources/Trifle.swift +++ b/ios/Trifle/Sources/Trifle.swift @@ -15,6 +15,8 @@ public class Trifle { private let envelopeDataVersion = UInt32(0) private let contentSigner: ContentSigner + + private let accessGroup: String? /** Initialize the SDK with the key tag that is passed in. @@ -23,10 +25,18 @@ public class Trifle { certificate request and to sign messages. The library (Trifle) will automatically try to choose the best algorithm and key type available on this device. + + AccessGroup specifies the access group the Trifle key belongs to. Specifying this + attribute will mean that all key related APIs will be limited to the specified access group + (of which the calling application must be a member to obtain matching results.) + It is recommended that this value is set. + + If the access group is not set, Trifle keys are created in the application's default access group. */ - public init(reverseDomain: String) throws { + public init(reverseDomain: String, accessGroup: String? = nil) throws { self.contentSigner = try SecureEnclaveDigitalSignatureKeyManager(reverseDomain: reverseDomain) + self.accessGroup = accessGroup } /** @@ -42,7 +52,7 @@ public class Trifle { */ public func generateKeyHandle() throws -> KeyHandle { // currently we support only (Secure Enclave, EC-P256) - return TrifleKeyHandle(tag: try contentSigner.generateTag()) + return TrifleKeyHandle(tag: try contentSigner.generateTag(accessGroup)) } /** @@ -54,7 +64,7 @@ public class Trifle { // Other types of validity check to be added later eg type of key // right now we only check if key exists in key chain - return try SecureEnclaveDigitalSignatureKeyManager.keyExists(keyHandle.tag) + return try SecureEnclaveDigitalSignatureKeyManager.keyExists(keyHandle.tag, accessGroup) } /** @@ -71,7 +81,7 @@ public class Trifle { // Other types of validity check to be added later eg type of key // right now we only check if key exists in key chain - return try SecureEnclaveDigitalSignatureKeyManager.deleteKeypair(keyHandle.tag) + return try SecureEnclaveDigitalSignatureKeyManager.deleteKeypair(keyHandle.tag, accessGroup) } /** @@ -91,7 +101,7 @@ public class Trifle { ) throws -> TrifleCertificateRequest { let csr = try PKCS10CertificationRequest.Builder() .addName(.commonName(entity)) - .sign(for: keyHandle.tag, with: contentSigner) + .sign(for: keyHandle.tag, with: contentSigner, accessGroup) let csrData = try ProtoEncoder().encode(MobileCertificateRequest( version: Self.mobileCertificateRequestVersion, @@ -144,7 +154,7 @@ public class Trifle { // if key handle is invalid, an error is thrown let signedData = try ProtoEncoder().encode(SignedData( enveloped_data: serializedData, - signature: try contentSigner.sign(for: keyHandle.tag, with: serializedData).data, + signature: try contentSigner.sign(for: keyHandle.tag, with: serializedData, accessGroup).data, certificates: certificates.map({ trifleCert in return trifleCert.getCertificate() }))) return try TrifleSignedData.deserialize(data: signedData) From 233db3769b818952814ced5bcdeebf220e418093 Mon Sep 17 00:00:00 2001 From: Alvin See Date: Wed, 27 Sep 2023 17:13:27 +0900 Subject: [PATCH 2/3] Fixup with unit test --- ios/Example/Podfile.lock | 2 +- ios/Example/Tests/TrifleTests.swift | 17 ++++++++++++ ios/Example/Trifle.xcodeproj/project.pbxproj | 6 ++++- ios/Example/Trifle_Example.entitlements | 10 +++++++ .../Sources/Common/KeychainQueries.swift | 10 ++++--- .../Digital Signature/ContentSigner.swift | 2 +- .../DigitalSignatureKeyManager.swift | 4 +-- .../DigitalSignatureSigner.swift | 8 +++--- .../DigitalSignatureVerifier.swift | 4 +-- ...ureEnclaveDigitalSignatureKeyManager.swift | 26 ++++++++++--------- .../SecureEnclaveKeychainQueries.swift | 11 ++++---- .../PKCS10CertificateRequestBuilder.swift | 7 +++-- ios/Trifle/Sources/Trifle.swift | 12 +++++---- 13 files changed, 78 insertions(+), 41 deletions(-) create mode 100644 ios/Example/Trifle_Example.entitlements diff --git a/ios/Example/Podfile.lock b/ios/Example/Podfile.lock index a66c2a4..6bf6183 100644 --- a/ios/Example/Podfile.lock +++ b/ios/Example/Podfile.lock @@ -24,4 +24,4 @@ SPEC CHECKSUMS: PODFILE CHECKSUM: 4b8d6c2fc4c9668821977302cb1ff5c5891d9920 -COCOAPODS: 1.12.1 +COCOAPODS: 1.12.0 diff --git a/ios/Example/Tests/TrifleTests.swift b/ios/Example/Tests/TrifleTests.swift index 7122c35..acbde4c 100644 --- a/ios/Example/Tests/TrifleTests.swift +++ b/ios/Example/Tests/TrifleTests.swift @@ -145,6 +145,23 @@ final class TrifleTests: XCTestCase { // of our SDK instance and the key handle XCTAssertNotNil(signData2, "This is TODO - once this is validated, this test should FAIL") } + + func testDifferentAccessGroup_fail() throws { + let trifle = try Trifle(reverseDomain: TestFixtures.reverseDomain, accessGroup: "group.app.cash") + let otherTrifle = try Trifle(reverseDomain: TestFixtures.reverseDomain, accessGroup: "group.app.not.cash") + + let deviceCertificate = try TrifleCertificate.deserialize(data: TestFixtures.deviceTrifleCertEncoded2!) + let rootCertificate = try TrifleCertificate.deserialize(data: TestFixtures.rootTrifleCertEncoded!) + + let keyHandle = try trifle.generateKeyHandle() + + XCTAssertThrowsError(try otherTrifle.createSignedData( + data: TestFixtures.data, + keyHandle: keyHandle, + certificates: [deviceCertificate, rootCertificate]), + "unavailable keyPair" + ) + } func testSignEmptyData_fail() throws { let trifle = try Trifle(reverseDomain: TestFixtures.reverseDomain) diff --git a/ios/Example/Trifle.xcodeproj/project.pbxproj b/ios/Example/Trifle.xcodeproj/project.pbxproj index d064315..3d9b19b 100644 --- a/ios/Example/Trifle.xcodeproj/project.pbxproj +++ b/ios/Example/Trifle.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 52; + objectVersion = 54; objects = { /* Begin PBXBuildFile section */ @@ -65,6 +65,7 @@ B0075494B7FD4A77B59913B3 /* Pods-Trifle_Example.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Trifle_Example.release.xcconfig"; path = "Target Support Files/Pods-Trifle_Example/Pods-Trifle_Example.release.xcconfig"; sourceTree = ""; }; B9892766F650E70A1D9BC557 /* Pods_Trifle_macOS_Dummy.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Trifle_macOS_Dummy.framework; sourceTree = BUILT_PRODUCTS_DIR; }; C72A495F71F70549F960896D /* Pods_Trifle_Tests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Trifle_Tests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + E1037DEF2AC642280015EAD1 /* Trifle_Example.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Trifle_Example.entitlements; sourceTree = ""; }; E103FF9A29AD90F5004011C2 /* CertificateDecodableTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CertificateDecodableTest.swift; sourceTree = ""; }; E10D7FCB299410D0002E6B72 /* DEREncodableTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DEREncodableTest.swift; sourceTree = ""; }; E12D7D7E29B71649003D1FB1 /* Trifle.podspec */ = {isa = PBXFileReference; lastKnownFileType = text; name = Trifle.podspec; path = ../../Trifle.podspec; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.ruby; }; @@ -119,6 +120,7 @@ 607FACC71AFB9204008FA782 = { isa = PBXGroup; children = ( + E1037DEF2AC642280015EAD1 /* Trifle_Example.entitlements */, 607FACF51AFB993E008FA782 /* Podspec Metadata */, 607FACD21AFB9204008FA782 /* Example for Trifle */, 607FACE81AFB9204008FA782 /* Tests */, @@ -621,6 +623,7 @@ baseConfigurationReference = 14D99F6F8B2606DBCF35AE93 /* Pods-Trifle_Example.debug.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_ENTITLEMENTS = Trifle_Example.entitlements; DEVELOPMENT_TEAM = ""; INFOPLIST_FILE = Trifle/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -641,6 +644,7 @@ baseConfigurationReference = B0075494B7FD4A77B59913B3 /* Pods-Trifle_Example.release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_ENTITLEMENTS = Trifle_Example.entitlements; DEVELOPMENT_TEAM = ""; INFOPLIST_FILE = Trifle/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; diff --git a/ios/Example/Trifle_Example.entitlements b/ios/Example/Trifle_Example.entitlements new file mode 100644 index 0000000..c02d8d3 --- /dev/null +++ b/ios/Example/Trifle_Example.entitlements @@ -0,0 +1,10 @@ + + + + + com.apple.security.application-groups + + group.app.cash + + + diff --git a/ios/Trifle/Sources/Common/KeychainQueries.swift b/ios/Trifle/Sources/Common/KeychainQueries.swift index 030b8e8..d6095aa 100644 --- a/ios/Trifle/Sources/Common/KeychainQueries.swift +++ b/ios/Trifle/Sources/Common/KeychainQueries.swift @@ -12,13 +12,15 @@ protocol KeychainQueries { Constructs a query dictionary using the a given application tag - parameter applicationTag: an application tag used to distinguish the key from other keys in the keychain - - parameter returnRef: whether a reference to `SecKey` should be returned - parameter accessGroup: the access group the security item belongs to. If no access group is set, then the calling app's default access group is used. + - parameter returnRef: whether a reference to `SecKey` should be returned */ - static func getQuery(with applicationTag: String, - returnRef: Bool, - _ accessGroup: String? ) -> NSMutableDictionary + static func getQuery( + with applicationTag: String, + _ accessGroup: String?, + returnRef: Bool + ) -> NSMutableDictionary } // MARK: - diff --git a/ios/Trifle/Sources/Digital Signature/ContentSigner.swift b/ios/Trifle/Sources/Digital Signature/ContentSigner.swift index aa77152..f12589a 100644 --- a/ios/Trifle/Sources/Digital Signature/ContentSigner.swift +++ b/ios/Trifle/Sources/Digital Signature/ContentSigner.swift @@ -17,5 +17,5 @@ protocol ContentSigner: DigitalSignatureSigner { operation fails if the key is not exportable - returns: the signing public key along with its key type */ - func exportPublicKey(_ tag: String, _ accessGroup: String?) throws -> SigningPublicKey + func exportPublicKey(_ tag: String) throws -> SigningPublicKey } diff --git a/ios/Trifle/Sources/Digital Signature/DigitalSignatureKeyManager.swift b/ios/Trifle/Sources/Digital Signature/DigitalSignatureKeyManager.swift index c25ddda..937a9e3 100644 --- a/ios/Trifle/Sources/Digital Signature/DigitalSignatureKeyManager.swift +++ b/ios/Trifle/Sources/Digital Signature/DigitalSignatureKeyManager.swift @@ -18,7 +18,7 @@ protocol DigitalSignatureKeyManager { - throws: an error type `CryptographicKeyError` - returns: the private key for signing */ - func signingKey(_ tag: String, _ accessGroup: String?) throws -> PrivateKey + func signingKey(_ tag: String) throws -> PrivateKey /** Fetches the verifying key @@ -26,5 +26,5 @@ protocol DigitalSignatureKeyManager { - throws: an error type `CryptographicKeyError` - returns: the public key for verifying */ - func verifyingKey(_ tag: String, _ accessGroup: String?) throws -> PublicKey + func verifyingKey(_ tag: String) throws -> PublicKey } diff --git a/ios/Trifle/Sources/Digital Signature/DigitalSignatureSigner.swift b/ios/Trifle/Sources/Digital Signature/DigitalSignatureSigner.swift index 9ad6435..d26f8ed 100644 --- a/ios/Trifle/Sources/Digital Signature/DigitalSignatureSigner.swift +++ b/ios/Trifle/Sources/Digital Signature/DigitalSignatureSigner.swift @@ -8,17 +8,17 @@ import Foundation /// Signer to perform digital signature signing operations public protocol DigitalSignatureSigner { /** - Generates a tag associated with a signing key pair and stores it to the keychain for the specified access group. + Generates a tag associated with a signing key pair and stores it to the keychain. Returns the associated tag value. - throws: an error type `CryptographicKeyError` when the operation fails if the key is not exportable - returns: tag value */ - func generateTag(_ accessGroup: String?) throws -> String + func generateTag() throws -> String /** - Signs a blob of data with the underlying signing key for the specified access group. + Signs a blob of data with the underlying signing key - throws: an error type `CryptographicKeyError` if the signing operation cannot be completed due to the underlying @@ -26,5 +26,5 @@ public protocol DigitalSignatureSigner { - returns: the digital signature which includes the signature data and the algorithm used for signing */ - func sign(for tag: String, with data: Data, _ accessGroup: String?) throws -> DigitalSignature + func sign(for tag: String, with data: Data) throws -> DigitalSignature } diff --git a/ios/Trifle/Sources/Digital Signature/DigitalSignatureVerifier.swift b/ios/Trifle/Sources/Digital Signature/DigitalSignatureVerifier.swift index c2163f9..e851717 100644 --- a/ios/Trifle/Sources/Digital Signature/DigitalSignatureVerifier.swift +++ b/ios/Trifle/Sources/Digital Signature/DigitalSignatureVerifier.swift @@ -8,12 +8,12 @@ import Foundation /// Verifier to perform digital signature verification operations public protocol DigitalSignatureVerifier { /** - Verifies a blob of data with a supplied signature using the underlying verifying key in the specified access group. + Verifies a blob of data with a supplied signature using the underlying verifying key - throws: an error type `CryptographicKeyError` if the verifying operation cannot be completed due to the underlying verifying key being unavailable. - returns: true if the signature is verified, false otherwise */ - func verify(for tag: String, data: Data, with signature: Data, _ accessGroup: String?) throws -> Bool + func verify(for tag: String, data: Data, with signature: Data) throws -> Bool } diff --git a/ios/Trifle/Sources/Digital Signature/Secure Enclave/SecureEnclaveDigitalSignatureKeyManager.swift b/ios/Trifle/Sources/Digital Signature/Secure Enclave/SecureEnclaveDigitalSignatureKeyManager.swift index 10089b2..b0f9d58 100644 --- a/ios/Trifle/Sources/Digital Signature/Secure Enclave/SecureEnclaveDigitalSignatureKeyManager.swift +++ b/ios/Trifle/Sources/Digital Signature/Secure Enclave/SecureEnclaveDigitalSignatureKeyManager.swift @@ -23,20 +23,22 @@ public class SecureEnclaveDigitalSignatureKeyManager // MARK: - Private Properties private let tagFormat: String + private let accessGroup: String? // MARK: - Initialization - public init(reverseDomain: String) throws { + public init(reverseDomain: String, accessGroup: String? = nil) throws { guard !reverseDomain.isEmpty else { // tag should not be empty throw TrifleError.invalidInput("Reverse domain cannot be empty") } self.tagFormat = reverseDomain + ".sign.{{uuid}}" + self.accessGroup = accessGroup } // MARK: - Public Methods (DigitalSignatureSigner) - public func generateTag(_ accessGroup: String? = nil) throws -> String { + public func generateTag() throws -> String { let tag = tagFormat.replacingOccurrences( of: "{{uuid}}", with: UUID().uuidString @@ -45,8 +47,8 @@ public class SecureEnclaveDigitalSignatureKeyManager return tag } - public func sign(for tag: String, with data: Data, _ accessGroup: String? = nil) throws -> DigitalSignature { - let signature = try signingKey(tag, accessGroup).sign(with: data) + public func sign(for tag: String, with data: Data) throws -> DigitalSignature { + let signature = try signingKey(tag).sign(with: data) return DigitalSignature( signingAlgorithm: Self.keyInfo.signingAlgorithm, data: signature @@ -55,24 +57,24 @@ public class SecureEnclaveDigitalSignatureKeyManager // MARK: - Public Methods (DigitalSignatureVerifier) - public func verify(for tag: String, data: Data, with signature: Data, _ accessGroup: String? = nil) throws -> Bool { - return try verifyingKey(tag, accessGroup).verify(data: data, with: signature) + public func verify(for tag: String, data: Data, with signature: Data) throws -> Bool { + return try verifyingKey(tag).verify(data: data, with: signature) } // MARK: - Internal Methods (ContentSigner) - internal func exportPublicKey(_ tag: String, _ accessGroup: String?) throws -> SigningPublicKey { + internal func exportPublicKey(_ tag: String) throws -> SigningPublicKey { return SigningPublicKey( keyInfo: Self.keyInfo, - data: try verifyingKey(tag, accessGroup).export() + data: try verifyingKey(tag).export() ) } // MARK: - Internal Methods (DigitalSignatureKeyManager) - internal func signingKey(_ tag: String, _ accessGroup: String?) throws -> SecureEnclaveSigningKey { + internal func signingKey(_ tag: String) throws -> SecureEnclaveSigningKey { var keyRef: CFTypeRef? - let preparedQuery = SecureEnclaveKeychainQueries.getQuery(with: tag, returnRef: true, accessGroup) + let preparedQuery = SecureEnclaveKeychainQueries.getQuery(with: tag, accessGroup, returnRef: true) guard case let status = SecItemCopyMatching(preparedQuery, &keyRef), status == errSecSuccess, keyRef != nil else { @@ -82,8 +84,8 @@ public class SecureEnclaveDigitalSignatureKeyManager return try SecureEnclaveSigningKey(keyRef as! SecKey, Self.keyInfo.signingAlgorithm.attrs) } - internal func verifyingKey(_ tag: String, _ accessGroup: String?) throws -> SecureEnclaveVerifyingKey { - let signingKey = try signingKey(tag, accessGroup) + internal func verifyingKey(_ tag: String) throws -> SecureEnclaveVerifyingKey { + let signingKey = try signingKey(tag) guard let publicKey = SecKeyCopyPublicKey(signingKey.privateKey) else { throw CryptographicKeyError.unavailablePublicKey diff --git a/ios/Trifle/Sources/Digital Signature/Secure Enclave/SecureEnclaveKeychainQueries.swift b/ios/Trifle/Sources/Digital Signature/Secure Enclave/SecureEnclaveKeychainQueries.swift index 6da529a..68d21f1 100644 --- a/ios/Trifle/Sources/Digital Signature/Secure Enclave/SecureEnclaveKeychainQueries.swift +++ b/ios/Trifle/Sources/Digital Signature/Secure Enclave/SecureEnclaveKeychainQueries.swift @@ -47,7 +47,7 @@ internal struct SecureEnclaveKeychainQueries: KeychainQueries { ] // if accessGroup is nil, the calling application's access group will be used by default if let accessGroup = accessGroup { - attributes[kSecAttrAccessGroupToken] = accessGroup + attributes[kSecAttrAccessGroup] = accessGroup } #if !targetEnvironment(simulator) @@ -59,9 +59,10 @@ internal struct SecureEnclaveKeychainQueries: KeychainQueries { // MARK: - KeychainQueries - internal static func getQuery(with applicationTag: String, - returnRef: Bool = false, - _ accessGroup: String? ) -> NSMutableDictionary { + internal static func getQuery(with applicationTag: String, + _ accessGroup: String?, + returnRef: Bool = false + ) -> NSMutableDictionary { let attributes: NSMutableDictionary = [ kSecClass as String: kSecClassKey, kSecAttrApplicationTag as String: applicationTag.data(using: .utf8)!, @@ -70,7 +71,7 @@ internal struct SecureEnclaveKeychainQueries: KeychainQueries { ] // if accessGroup is nil, the calling application's access group will be used by default if let accessGroup = accessGroup { - attributes[kSecAttrAccessGroupToken] = accessGroup + attributes[kSecAttrAccessGroup] = accessGroup } return attributes } diff --git a/ios/Trifle/Sources/PKCS10/PKCS10CertificateRequestBuilder.swift b/ios/Trifle/Sources/PKCS10/PKCS10CertificateRequestBuilder.swift index daac376..f9d5e86 100644 --- a/ios/Trifle/Sources/PKCS10/PKCS10CertificateRequestBuilder.swift +++ b/ios/Trifle/Sources/PKCS10/PKCS10CertificateRequestBuilder.swift @@ -35,17 +35,16 @@ extension PKCS10CertificationRequest { return self } - func sign(for tag: String, with signer: ContentSigner, _ accessGroup: String? = nil) throws + func sign(for tag: String, with signer: ContentSigner) throws -> PKCS10CertificationRequest { let certificateRequestInfo = try buildCertificateRequestInfo( - with: try signer.exportPublicKey(tag, accessGroup) + with: try signer.exportPublicKey(tag) ) let signature = try signer.sign( for: tag, - with: Data(certificateRequestInfo.octets), - accessGroup + with: Data(certificateRequestInfo.octets) ) let certificationRequest = try ASN1Sequence([ diff --git a/ios/Trifle/Sources/Trifle.swift b/ios/Trifle/Sources/Trifle.swift index ba8c56b..95fd774 100644 --- a/ios/Trifle/Sources/Trifle.swift +++ b/ios/Trifle/Sources/Trifle.swift @@ -34,8 +34,10 @@ public class Trifle { If the access group is not set, Trifle keys are created in the application's default access group. */ public init(reverseDomain: String, accessGroup: String? = nil) throws { - self.contentSigner = - try SecureEnclaveDigitalSignatureKeyManager(reverseDomain: reverseDomain) + self.contentSigner = try SecureEnclaveDigitalSignatureKeyManager( + reverseDomain: reverseDomain, + accessGroup: accessGroup + ) self.accessGroup = accessGroup } @@ -52,7 +54,7 @@ public class Trifle { */ public func generateKeyHandle() throws -> KeyHandle { // currently we support only (Secure Enclave, EC-P256) - return TrifleKeyHandle(tag: try contentSigner.generateTag(accessGroup)) + return TrifleKeyHandle(tag: try contentSigner.generateTag()) } /** @@ -101,7 +103,7 @@ public class Trifle { ) throws -> TrifleCertificateRequest { let csr = try PKCS10CertificationRequest.Builder() .addName(.commonName(entity)) - .sign(for: keyHandle.tag, with: contentSigner, accessGroup) + .sign(for: keyHandle.tag, with: contentSigner) let csrData = try ProtoEncoder().encode(MobileCertificateRequest( version: Self.mobileCertificateRequestVersion, @@ -154,7 +156,7 @@ public class Trifle { // if key handle is invalid, an error is thrown let signedData = try ProtoEncoder().encode(SignedData( enveloped_data: serializedData, - signature: try contentSigner.sign(for: keyHandle.tag, with: serializedData, accessGroup).data, + signature: try contentSigner.sign(for: keyHandle.tag, with: serializedData).data, certificates: certificates.map({ trifleCert in return trifleCert.getCertificate() }))) return try TrifleSignedData.deserialize(data: signedData) From 9071de7373fd6f0f1701670d324bf3bbccb9e672 Mon Sep 17 00:00:00 2001 From: Gelareh Taban Date: Thu, 28 Sep 2023 17:00:16 -0700 Subject: [PATCH 3/3] add documentation for access groups --- README.md | 3 ++- ios/Trifle/Sources/Trifle.swift | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 70f678f..0b34b7d 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,8 @@ Trifle SDK is implemented on client side in iOS and Android and on server side i ```swift // App start up -let trifle = try Trifle(reverseDomain: abc) +// The access group value must be added to the App Groups [entitlement file](https://developer.apple.com/documentation/bundleresources/entitlements/com_apple_security_application-groups). +let trifle = try Trifle(reverseDomain: abc, accessGroup: "group.trifle.cash.app") // Check if a key already exists. // If no key exists, generate a public key pair diff --git a/ios/Trifle/Sources/Trifle.swift b/ios/Trifle/Sources/Trifle.swift index 95fd774..f759397 100644 --- a/ios/Trifle/Sources/Trifle.swift +++ b/ios/Trifle/Sources/Trifle.swift @@ -30,6 +30,7 @@ public class Trifle { attribute will mean that all key related APIs will be limited to the specified access group (of which the calling application must be a member to obtain matching results.) It is recommended that this value is set. + ** This value must be added to the App Group entitlement file. ** If the access group is not set, Trifle keys are created in the application's default access group. */