Skip to content

Commit b8b628c

Browse files
committed
Merge branch 'develop'
2 parents d9f0ef3 + b66f3b8 commit b8b628c

25 files changed

+661
-505
lines changed

README.md

Lines changed: 26 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -172,26 +172,35 @@ if case let .persistentReference(data) = try keychain.store(
172172

173173
#### CryptoKit
174174

175-
`SwiftSecurity` lets you natively store `CryptoKit` keys as native `SecKey` instances. [Keys supporting such conversion](https://developer.apple.com/documentation/cryptokit/storing_cryptokit_keys_in_the_keychain), like `P256`/`P384`/`P521.PrivateKey`, conform to `SecKeyConvertible` protocol.
175+
`SwiftSecurity` lets you natively store `CryptoKit` keys as native `SecKey` instances. [Keys supporting such conversion](https://developer.apple.com/documentation/cryptokit/storing_cryptokit_keys_in_the_keychain), like `P256`/`P384`/`P521`, conform to `SecKeyConvertible` protocol.
176176

177177
```swift
178178
// Store private key
179179
let privateKey = P256.KeyAgreement.PrivateKey()
180-
try keychain.store(privateKey, query: .privateKey(for: "Alice"))
180+
try keychain.store(privateKey, query: .key(for: "Alice"))
181181

182182
// Retrieve private key (+ public key)
183183
let privateKey: P256.KeyAgreement.PrivateKey? = try keychain.retrieve(.privateKey(for: "Alice"))
184-
let publicKey = privateKey.publicKey
184+
let publicKey = privateKey.publicKey /* Recommended */
185+
186+
// Store public key. Not recommended, as you can generate it
187+
try keychain.store(
188+
publicKey,
189+
query: .key(for: "Alice", descriptor: .ecsecPrimeRandom(.public))
190+
)
185191
```
186192

187-
Other key types, like `Curve25519.PrivateKey`, `SymmetricKey`, `SecureEnclave.P256.PrivateKey`, have no direct keychain corollary. In particular, `SecureEnclave.P256` is a persistent reference to the key inside `Secure Enclave`, not the key itself. These keys conform to `SecDataConvertible`, so store them as follows:
193+
Other key types from `CryptoKit`, like `SymmetricKey`, `Curve25519`, `SecureEnclave.P256`, have no direct keychain corollary. In particular, `SecureEnclave.P256` is a persistent reference to the key inside `Secure Enclave`, not the key itself. These keys conform to `SecDataConvertible`, so store them as follows:
188194

189195
```swift
190196
// Store symmetric key
191197
let symmetricKey = SymmetricKey(size: .bits256)
192198
try keychain.store(symmetricKey, query: .credential(for: "Chat"))
193199
```
194200

201+
> [!NOTE]
202+
> `SecKey` is intended for asymmetric key storage. Only `ECPrimeRandom` (`CryptoKit -> P256/384/512`) and `RSA` algorithms are supported. See [On Cryptographic Key Formats](https://developer.apple.com/forums/thread/680554) for more info.
203+
195204
#### Certificate
196205

197206
DER-Encoded X.509 Certificate.
@@ -207,9 +216,9 @@ try keychain.store(certificate, query: .certificate(for: "Root CA"))
207216

208217
If your project uses [apple/swift-certificates](https://github.com/apple/swift-certificates) package, the `Certificate` will offer more functionality. In case of `Swift Package Manager` dependency resolve issues, copy `SecCertificateConvertible` conformance directly to your project.
209218

210-
#### Identity
219+
#### Digital Identity
211220

212-
A digital identity (`SecIdentity`) is the combination of a certificate and the private key that matches the public key within certificate.
221+
A digital identity is the combination of a certificate and the private key that matches the public key within certificate.
213222

214223
```swift
215224
// Import digital identity from `PKCS #12` data
@@ -369,18 +378,19 @@ You can store, retrieve, and remove various types of values.
369378

370379
```swift
371380
Foundation:
372-
- Data // GenericPassword, InternetPassword
373-
- String // GenericPassword, InternetPassword
381+
- Data /* GenericPassword, InternetPassword */
382+
- String /* GenericPassword, InternetPassword */
374383
CryptoKit:
375-
- SymmetricKey // GenericPassword
376-
- Curve25519 -> PrivateKey // GenericPassword
377-
- SecureEnclave.P256 -> PrivateKey // GenericPassword (SE's Key Data is Persistent Reference)
378-
- P256, P384, P521 -> PrivateKey // SecKey (ANSI x9.63 Elliptic Curves)
384+
- SymmetricKey /* GenericPassword */
385+
- Curve25519 -> PrivateKey /* GenericPassword */
386+
- SecureEnclave.P256 -> PrivateKey /* GenericPassword (SE's Key Data is Persistent Reference) */
387+
- P256, P384, P521 -> PrivateKey /* SecKey (ANSI x9.63 Elliptic Curves) */
379388
X509 (external package `apple/swift-certificates`):
380-
- Certificate // SecCertificate
389+
- Certificate /* SecCertificate */
381390
SwiftSecurity:
382-
- Certificate // SecCertificate (Drop-in replacement for X509.Certificate)
383-
- PKCS12.Blob // Import as SecIdentity (SecCertificate and SecKey)
391+
- Certificate /* SecCertificate */
392+
- PKCS12.Blob: /* Import as SecIdentity */
393+
- DigitalIdentity /* SecIdentity (The Pair of SecCertificate and SecKey) */
384394
```
385395

386396
To add support for custom types, you can extend them by conforming to the following protocols.
@@ -456,6 +466,7 @@ The framework’s default behavior provides a reasonable balance between conveni
456466
* [Sharing access to keychain items among a collection of apps](https://developer.apple.com/documentation/security/keychain_services/keychain_items/sharing_access_to_keychain_items_among_a_collection_of_apps/)
457467
* [Storing CryptoKit Keys in the Keychain](https://developer.apple.com/documentation/cryptokit/storing_cryptokit_keys_in_the_keychain)
458468
* [TN3137: On Mac keychain APIs and implementations](https://developer.apple.com/documentation/technotes/tn3137-on-mac-keychains)
469+
* [On Cryptographic Key Formats](https://developer.apple.com/forums/thread/680554)
459470
* [SecItem: Fundamentals](https://developer.apple.com/forums/thread/724023)
460471
* [SecItem: Pitfalls and Best Practices](https://developer.apple.com/forums/thread/724013)
461472

Sources/SwiftSecurity/CryptoKit/SecCertificateConvertible.swift

Lines changed: 27 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -10,42 +10,21 @@ import Security
1010

1111
// MARK: - DER-Encoded X.509 Certificate
1212

13-
public protocol SecCertificateConvertible {
13+
public protocol SecCertificateConvertible: SecCertificateRepresentable {
1414
/// Creates a certificate from an DER-encoded X.509 data representation.
1515
init<Bytes>(derRepresentation: Bytes) throws where Bytes: ContiguousBytes
1616

17-
/// Creates a certificate from a raw representation.
18-
init(rawRepresentation certificateRef: SecCertificate)
19-
2017
/// A DER-Encoded X.509 data representation.
2118
var derRepresentation: Data { get }
22-
23-
/// A raw representation of the X.509 Certificate.
24-
var rawRepresentation: SecCertificate { get }
2519
}
2620

27-
extension SecCertificateConvertible {
28-
public init(rawRepresentation certificateRef: SecCertificate) {
29-
do {
30-
try self.init(derRepresentation: SecCertificateCopyData(certificateRef) as Data)
31-
} catch{
32-
fatalError(error.localizedDescription)
33-
}
34-
}
35-
36-
public var rawRepresentation: SecCertificate {
37-
guard let certificateRef = SecCertificateCreateWithData(nil, derRepresentation as CFData) else {
38-
fatalError("derRepresentation is not a valid DER-encoded data")
39-
}
40-
return certificateRef
41-
}
42-
}
21+
extension SwiftSecurity.Certificate: SecCertificateConvertible { }
4322

4423
#if canImport(X509)
4524
import X509
4625
import SwiftASN1
4726

48-
extension Certificate: SecCertificateConvertible {
27+
extension X509.Certificate: SecCertificateConvertible {
4928
public init<D>(derRepresentation data: D) throws where D: ContiguousBytes {
5029
try self.init(derEncoded: data.withUnsafeBytes { bytes in
5130
let contiguousBytes = bytes.bindMemory(to: UInt8.self)
@@ -64,24 +43,32 @@ extension Certificate: SecCertificateConvertible {
6443
}
6544
}
6645
}
67-
#else
68-
public struct Certificate: SecCertificateConvertible {
69-
public let derRepresentation: Data
70-
public let rawRepresentation: SecCertificate
46+
#endif
47+
48+
// MARK: - SecCertificate
49+
50+
public protocol SecCertificateRepresentable {
51+
/// Creates a certificate from a raw representation.
52+
init(certificate secCertificate: SecCertificate)
7153

72-
public init<Bytes>(derRepresentation data: Bytes) throws where Bytes : ContiguousBytes {
73-
self.derRepresentation = data.withUnsafeBytes { (bytes: UnsafeRawBufferPointer) in
74-
return Data(bytes)
75-
}
76-
guard let certificateRef = SecCertificateCreateWithData(nil, derRepresentation as CFData) else {
77-
throw SwiftSecurityError.invalidParameter
54+
/// A certificate reference.
55+
var secCertificate: SecCertificate { get }
56+
}
57+
58+
extension SecCertificateConvertible {
59+
public init(certificate secCertificate: SecCertificate) {
60+
do {
61+
try self.init(derRepresentation: SecCertificateCopyData(secCertificate) as Data)
62+
} catch{
63+
fatalError(error.localizedDescription)
7864
}
79-
self.rawRepresentation = certificateRef
8065
}
81-
82-
public init(rawRepresentation certificateRef: SecCertificate) {
83-
self.derRepresentation = SecCertificateCopyData(certificateRef) as Data
84-
self.rawRepresentation = certificateRef
66+
67+
public var secCertificate: SecCertificate {
68+
if let secCertificate = SecCertificateCreateWithData(nil, derRepresentation as CFData) {
69+
return secCertificate
70+
} else {
71+
fatalError("derRepresentation is not a valid DER-encoded data")
72+
}
8573
}
8674
}
87-
#endif

Sources/SwiftSecurity/CryptoKit/SecDataConvertible.swift

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,9 @@ public protocol SecDataConvertible {
1919
// MARK: - CryptoKit
2020

2121
extension Curve25519.KeyAgreement.PrivateKey: SecDataConvertible {}
22+
extension Curve25519.KeyAgreement.PublicKey: SecDataConvertible {}
2223
extension Curve25519.Signing.PrivateKey: SecDataConvertible {}
24+
extension Curve25519.Signing.PublicKey: SecDataConvertible {}
2325

2426
extension SymmetricKey: SecDataConvertible {
2527
public init<D>(rawRepresentation data: D) throws where D: ContiguousBytes {
@@ -60,7 +62,7 @@ extension SecureEnclave.P256.Signing.PrivateKey: SecDataConvertible {
6062
}
6163
}
6264

63-
// MARK: - Other Data Types
65+
// MARK: - Foundation
6466

6567
extension Data: SecDataConvertible {
6668
public init<D>(rawRepresentation data: D) throws where D : ContiguousBytes {

Sources/SwiftSecurity/CryptoKit/SecIdentityConvertible.swift

Lines changed: 13 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -7,36 +7,22 @@
77

88
import Foundation
99

10-
public protocol SecIdentityConvertible {
11-
/// Creates an identity from a raw representation.
12-
init(rawRepresentation: SecIdentity)
13-
14-
/// A raw representation of the identity.
15-
var rawRepresentation: SecIdentity { get throws }
10+
public protocol SecIdentityConvertible<Certificate>: SecIdentityRepresentable {
11+
associatedtype Certificate: SecCertificateConvertible
1612
}
1713

18-
public struct Identity: SecIdentityConvertible {
19-
/// Creates an identity from a raw representation.
20-
public init(rawRepresentation identityRef: SecIdentity) {
21-
self.rawRepresentation = identityRef
22-
}
23-
24-
/// Creates an identity from a raw representation.
25-
public let rawRepresentation: SecIdentity
14+
// MARK: - SwiftSecurity
15+
16+
extension DigitalIdentity: SecIdentityConvertible {
17+
public typealias Certificate = SwiftSecurity.Certificate
2618
}
2719

28-
#if os(macOS)
29-
import Security
20+
// MARK: - SecIdentity
3021

31-
extension Identity {
32-
public init?<T: SecCertificateConvertible>(certificate: T) {
33-
var identityRef: SecIdentity?
34-
SecIdentityCreateWithCertificate(nil, certificate.rawRepresentation, &identityRef)
35-
if let identityRef {
36-
self.rawRepresentation = identityRef
37-
} else {
38-
return nil
39-
}
40-
}
22+
public protocol SecIdentityRepresentable {
23+
/// Creates an identity from a raw representation.
24+
init(identity secIdentity: SecIdentity)
25+
26+
/// An identity reference.
27+
var secIdentity: SecIdentity { get throws }
4128
}
42-
#endif

Sources/SwiftSecurity/CryptoKit/SecKeyConvertible.swift

Lines changed: 102 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,17 +10,112 @@ import CryptoKit
1010

1111
// MARK: - NIST Key
1212

13-
public protocol SecKeyConvertible {
13+
public protocol SecKeyConvertible: SecKeyRepresentable {
1414
/// Creates a key from an X9.63 representation.
1515
init<Bytes>(x963Representation: Bytes) throws where Bytes: ContiguousBytes
1616

1717
/// An X9.63 representation of the key.
1818
var x963Representation: Data { get }
1919
}
2020

21-
extension P256.Signing.PrivateKey: SecKeyConvertible {}
22-
extension P256.KeyAgreement.PrivateKey: SecKeyConvertible {}
23-
extension P384.Signing.PrivateKey: SecKeyConvertible {}
24-
extension P384.KeyAgreement.PrivateKey: SecKeyConvertible {}
25-
extension P521.Signing.PrivateKey: SecKeyConvertible {}
26-
extension P521.KeyAgreement.PrivateKey: SecKeyConvertible {}
21+
// MARK: - CryptoKit
22+
23+
extension P256.Signing.PrivateKey: SecKeyConvertible {
24+
public var descriptor: SecKeyDescriptor { .ecsecPrimeRandom(.private) }
25+
}
26+
27+
extension P256.Signing.PublicKey: SecKeyConvertible {
28+
public var descriptor: SecKeyDescriptor { .ecsecPrimeRandom(.public) }
29+
}
30+
31+
extension P256.KeyAgreement.PrivateKey: SecKeyConvertible {
32+
public var descriptor: SecKeyDescriptor { .ecsecPrimeRandom(.private) }
33+
}
34+
35+
extension P256.KeyAgreement.PublicKey: SecKeyConvertible {
36+
public var descriptor: SecKeyDescriptor { .ecsecPrimeRandom(.public) }
37+
}
38+
39+
extension P384.Signing.PrivateKey: SecKeyConvertible {
40+
public var descriptor: SecKeyDescriptor { .ecsecPrimeRandom(.private) }
41+
}
42+
43+
extension P384.Signing.PublicKey: SecKeyConvertible {
44+
public var descriptor: SecKeyDescriptor { .ecsecPrimeRandom(.public) }
45+
}
46+
47+
extension P384.KeyAgreement.PrivateKey: SecKeyConvertible {
48+
public var descriptor: SecKeyDescriptor { .ecsecPrimeRandom(.private) }
49+
}
50+
51+
extension P384.KeyAgreement.PublicKey: SecKeyConvertible {
52+
public var descriptor: SecKeyDescriptor { .ecsecPrimeRandom(.public) }
53+
}
54+
55+
extension P521.Signing.PrivateKey: SecKeyConvertible {
56+
public var descriptor: SecKeyDescriptor { .ecsecPrimeRandom(.private) }
57+
}
58+
59+
extension P521.Signing.PublicKey: SecKeyConvertible {
60+
public var descriptor: SecKeyDescriptor { .ecsecPrimeRandom(.public) }
61+
}
62+
63+
extension P521.KeyAgreement.PrivateKey: SecKeyConvertible {
64+
public var descriptor: SecKeyDescriptor { .ecsecPrimeRandom(.private) }
65+
}
66+
67+
extension P521.KeyAgreement.PublicKey: SecKeyConvertible {
68+
public var descriptor: SecKeyDescriptor { .ecsecPrimeRandom(.public) }
69+
}
70+
71+
// MARK: - SecKey
72+
73+
public protocol SecKeyRepresentable {
74+
/// A key descriptor for storage.
75+
var descriptor: SecKeyDescriptor { get }
76+
77+
/// A key reference.
78+
var secKey: SecKey { get throws }
79+
}
80+
81+
extension SecKeyConvertible {
82+
public var secKey: SecKey {
83+
get throws {
84+
precondition(descriptor.keyType == .ecsecPrimeRandom, "RSA is currently unsupported")
85+
var error: Unmanaged<CFError>?
86+
guard let secKey: SecKey = SecKeyCreateWithData(x963Representation as CFData, [
87+
kSecAttrKeyType: descriptor.keyType.rawValue,
88+
kSecAttrKeyClass: descriptor.keyClass.rawValue
89+
] as CFDictionary, &error) else {
90+
if let error = error?.takeRetainedValue() {
91+
throw SwiftSecurityError(error: error)
92+
}
93+
throw SwiftSecurityError.invalidParameter
94+
}
95+
return secKey
96+
}
97+
}
98+
}
99+
100+
public struct SecKeyDescriptor {
101+
public var keyType: KeyType
102+
public var keyClass: KeyClass
103+
104+
public static func rsa(_ keyClass: KeyClass) -> SecKeyDescriptor {
105+
switch keyClass {
106+
case .public:
107+
SecKeyDescriptor(keyType: .rsa, keyClass: .public)
108+
case .private:
109+
SecKeyDescriptor(keyType: .rsa, keyClass: .private)
110+
}
111+
}
112+
113+
public static func ecsecPrimeRandom(_ keyClass: KeyClass) -> SecKeyDescriptor {
114+
switch keyClass {
115+
case .public:
116+
SecKeyDescriptor(keyType: .ecsecPrimeRandom, keyClass: .public)
117+
case .private:
118+
SecKeyDescriptor(keyType: .ecsecPrimeRandom, keyClass: .private)
119+
}
120+
}
121+
}
File renamed without changes.

0 commit comments

Comments
 (0)