ds-01 1.0.12 → 1.0.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -8,30 +8,16 @@ func fail(_ message: String) -> Never {
|
|
|
8
8
|
exit(1)
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
var error: Unmanaged<CFError>?
|
|
13
|
-
|
|
14
|
-
guard let access = SecAccessControlCreateWithFlags(
|
|
15
|
-
nil,
|
|
16
|
-
kSecAttrAccessibleWhenUnlockedThisDeviceOnly,
|
|
17
|
-
.privateKeyUsage,
|
|
18
|
-
&error
|
|
19
|
-
) else {
|
|
20
|
-
if let error {
|
|
21
|
-
fail(error.takeRetainedValue().localizedDescription)
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
fail("Unable to create Secure Enclave access control.")
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
return access
|
|
28
|
-
}
|
|
29
|
-
|
|
11
|
+
// Find the existing Secure Enclave private key.
|
|
30
12
|
func findPrivateKey() -> SecKey? {
|
|
31
13
|
let query: [String: Any] = [
|
|
32
14
|
kSecClass as String: kSecClassKey,
|
|
33
|
-
kSecAttrApplicationTag as String:
|
|
34
|
-
|
|
15
|
+
kSecAttrApplicationTag as String:
|
|
16
|
+
keyTag.data(using: .utf8)!,
|
|
17
|
+
kSecAttrKeyType as String:
|
|
18
|
+
kSecAttrKeyTypeECSECPrimeRandom,
|
|
19
|
+
kSecAttrTokenID as String:
|
|
20
|
+
kSecAttrTokenIDSecureEnclave,
|
|
35
21
|
kSecReturnRef as String: true
|
|
36
22
|
]
|
|
37
23
|
|
|
@@ -50,33 +36,70 @@ func findPrivateKey() -> SecKey? {
|
|
|
50
36
|
}
|
|
51
37
|
|
|
52
38
|
func createKey() {
|
|
39
|
+
|
|
40
|
+
// If the key already exists, don't create another one.
|
|
53
41
|
if findPrivateKey() != nil {
|
|
54
42
|
print("EXISTS")
|
|
55
43
|
return
|
|
56
44
|
}
|
|
57
45
|
|
|
58
|
-
|
|
46
|
+
// Keychain access control.
|
|
47
|
+
//
|
|
48
|
+
// WhenUnlockedThisDeviceOnly means:
|
|
49
|
+
// - available only while the device is unlocked
|
|
50
|
+
// - cannot be restored onto another device
|
|
51
|
+
//
|
|
52
|
+
// privateKeyUsage means the key can be used for signing.
|
|
53
|
+
var accessError: Unmanaged<CFError>?
|
|
54
|
+
|
|
55
|
+
guard let access = SecAccessControlCreateWithFlags(
|
|
56
|
+
nil,
|
|
57
|
+
kSecAttrAccessibleWhenUnlockedThisDeviceOnly,
|
|
58
|
+
.privateKeyUsage,
|
|
59
|
+
&accessError
|
|
60
|
+
) else {
|
|
61
|
+
|
|
62
|
+
if let error = accessError {
|
|
63
|
+
fail(
|
|
64
|
+
"Unable to create Keychain access control: " +
|
|
65
|
+
error.takeRetainedValue().localizedDescription
|
|
66
|
+
)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
fail("Unable to create Keychain access control.")
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
let privateAttributes: [String: Any] = [
|
|
73
|
+
|
|
74
|
+
// Permanently store the private key.
|
|
75
|
+
kSecAttrIsPermanent as String: true,
|
|
76
|
+
|
|
77
|
+
// Identifier used to find the key later.
|
|
78
|
+
kSecAttrApplicationTag as String:
|
|
79
|
+
keyTag.data(using: .utf8)!,
|
|
80
|
+
|
|
81
|
+
// Access rules for the private key.
|
|
82
|
+
kSecAttrAccessControl as String:
|
|
83
|
+
access
|
|
84
|
+
]
|
|
59
85
|
|
|
60
86
|
let attributes: [String: Any] = [
|
|
87
|
+
|
|
88
|
+
// EC P-256 key.
|
|
61
89
|
kSecAttrKeyType as String:
|
|
62
90
|
kSecAttrKeyTypeECSECPrimeRandom,
|
|
63
91
|
|
|
64
92
|
kSecAttrKeySizeInBits as String:
|
|
65
93
|
256,
|
|
66
94
|
|
|
95
|
+
// IMPORTANT:
|
|
96
|
+
// The private key lives inside the Secure Enclave.
|
|
67
97
|
kSecAttrTokenID as String:
|
|
68
98
|
kSecAttrTokenIDSecureEnclave,
|
|
69
99
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
kSecAttrApplicationTag as String:
|
|
75
|
-
keyTag.data(using: .utf8)!,
|
|
76
|
-
|
|
77
|
-
kSecAttrAccessControl as String:
|
|
78
|
-
access
|
|
79
|
-
]
|
|
100
|
+
// Private key configuration.
|
|
101
|
+
kSecPrivateKeyAttrs as String:
|
|
102
|
+
privateAttributes
|
|
80
103
|
]
|
|
81
104
|
|
|
82
105
|
var error: Unmanaged<CFError>?
|
|
@@ -85,17 +108,26 @@ func createKey() {
|
|
|
85
108
|
attributes as CFDictionary,
|
|
86
109
|
&error
|
|
87
110
|
) != nil else {
|
|
111
|
+
|
|
88
112
|
if let error {
|
|
89
|
-
fail(
|
|
113
|
+
fail(
|
|
114
|
+
"Secure Enclave key creation failed: " +
|
|
115
|
+
error.takeRetainedValue().localizedDescription
|
|
116
|
+
)
|
|
90
117
|
}
|
|
91
118
|
|
|
92
|
-
fail("
|
|
119
|
+
fail("Secure Enclave key creation failed.")
|
|
93
120
|
}
|
|
94
121
|
|
|
95
122
|
print("CREATED")
|
|
96
123
|
}
|
|
97
124
|
|
|
125
|
+
|
|
126
|
+
// Return ONLY the public key.
|
|
127
|
+
//
|
|
128
|
+
// The private key is never exported.
|
|
98
129
|
func publicKey() {
|
|
130
|
+
|
|
99
131
|
guard let privateKey = findPrivateKey() else {
|
|
100
132
|
fail("DS01 Secure Enclave key not found.")
|
|
101
133
|
}
|
|
@@ -110,35 +142,47 @@ func publicKey() {
|
|
|
110
142
|
publicKey,
|
|
111
143
|
&error
|
|
112
144
|
) else {
|
|
145
|
+
|
|
113
146
|
if let error {
|
|
114
|
-
fail(
|
|
147
|
+
fail(
|
|
148
|
+
"Unable to export public key: " +
|
|
149
|
+
error.takeRetainedValue().localizedDescription
|
|
150
|
+
)
|
|
115
151
|
}
|
|
116
152
|
|
|
117
153
|
fail("Unable to export public key.")
|
|
118
154
|
}
|
|
119
155
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
156
|
+
print(
|
|
157
|
+
(data as Data).base64EncodedString()
|
|
158
|
+
)
|
|
123
159
|
}
|
|
124
160
|
|
|
161
|
+
|
|
162
|
+
// Sign data using the Secure Enclave private key.
|
|
125
163
|
func sign(_ base64Data: String) {
|
|
164
|
+
|
|
126
165
|
guard let privateKey = findPrivateKey() else {
|
|
127
166
|
fail("DS01 Secure Enclave key not found.")
|
|
128
167
|
}
|
|
129
168
|
|
|
130
|
-
guard let data = Data(
|
|
169
|
+
guard let data = Data(
|
|
170
|
+
base64Encoded: base64Data
|
|
171
|
+
) else {
|
|
131
172
|
fail("Invalid input data.")
|
|
132
173
|
}
|
|
133
174
|
|
|
134
|
-
let algorithm =
|
|
175
|
+
let algorithm =
|
|
176
|
+
SecKeyAlgorithm.ecdsaSignatureMessageX962SHA256
|
|
135
177
|
|
|
136
178
|
guard SecKeyIsAlgorithmSupported(
|
|
137
179
|
privateKey,
|
|
138
180
|
.sign,
|
|
139
181
|
algorithm
|
|
140
182
|
) else {
|
|
141
|
-
fail(
|
|
183
|
+
fail(
|
|
184
|
+
"Secure Enclave key does not support signing."
|
|
185
|
+
)
|
|
142
186
|
}
|
|
143
187
|
|
|
144
188
|
var error: Unmanaged<CFError>?
|
|
@@ -149,23 +193,38 @@ func sign(_ base64Data: String) {
|
|
|
149
193
|
data as CFData,
|
|
150
194
|
&error
|
|
151
195
|
) else {
|
|
196
|
+
|
|
152
197
|
if let error {
|
|
153
|
-
fail(
|
|
198
|
+
fail(
|
|
199
|
+
"Secure Enclave signing failed: " +
|
|
200
|
+
error.takeRetainedValue().localizedDescription
|
|
201
|
+
)
|
|
154
202
|
}
|
|
155
203
|
|
|
156
204
|
fail("Secure Enclave signing failed.")
|
|
157
205
|
}
|
|
158
206
|
|
|
159
|
-
print(
|
|
207
|
+
print(
|
|
208
|
+
(signature as Data).base64EncodedString()
|
|
209
|
+
)
|
|
160
210
|
}
|
|
161
211
|
|
|
212
|
+
|
|
213
|
+
// Remove the DS01 key from this Mac.
|
|
162
214
|
func deleteKey() {
|
|
215
|
+
|
|
163
216
|
let query: [String: Any] = [
|
|
164
|
-
kSecClass as String:
|
|
217
|
+
kSecClass as String:
|
|
218
|
+
kSecClassKey,
|
|
219
|
+
|
|
165
220
|
kSecAttrApplicationTag as String:
|
|
166
221
|
keyTag.data(using: .utf8)!,
|
|
222
|
+
|
|
167
223
|
kSecAttrKeyType as String:
|
|
168
|
-
kSecAttrKeyTypeECSECPrimeRandom
|
|
224
|
+
kSecAttrKeyTypeECSECPrimeRandom,
|
|
225
|
+
|
|
226
|
+
kSecAttrTokenID as String:
|
|
227
|
+
kSecAttrTokenIDSecureEnclave
|
|
169
228
|
]
|
|
170
229
|
|
|
171
230
|
let status = SecItemDelete(
|
|
@@ -174,17 +233,23 @@ func deleteKey() {
|
|
|
174
233
|
|
|
175
234
|
if status != errSecSuccess &&
|
|
176
235
|
status != errSecItemNotFound {
|
|
177
|
-
|
|
236
|
+
|
|
237
|
+
fail(
|
|
238
|
+
"Unable to delete DS01 Secure Enclave key."
|
|
239
|
+
)
|
|
178
240
|
}
|
|
179
241
|
|
|
180
242
|
print("DELETED")
|
|
181
243
|
}
|
|
182
244
|
|
|
245
|
+
|
|
246
|
+
// Command dispatcher.
|
|
183
247
|
guard CommandLine.arguments.count >= 2 else {
|
|
184
248
|
fail("Missing operation.")
|
|
185
249
|
}
|
|
186
250
|
|
|
187
251
|
switch CommandLine.arguments[1] {
|
|
252
|
+
|
|
188
253
|
case "create":
|
|
189
254
|
createKey()
|
|
190
255
|
|
|
@@ -192,6 +257,7 @@ case "public":
|
|
|
192
257
|
publicKey()
|
|
193
258
|
|
|
194
259
|
case "sign":
|
|
260
|
+
|
|
195
261
|
guard CommandLine.arguments.count >= 3 else {
|
|
196
262
|
fail("Missing data to sign.")
|
|
197
263
|
}
|
package/package.json
CHANGED
package/scripts/copy-native.js
CHANGED
|
@@ -8,30 +8,16 @@ func fail(_ message: String) -> Never {
|
|
|
8
8
|
exit(1)
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
var error: Unmanaged<CFError>?
|
|
13
|
-
|
|
14
|
-
guard let access = SecAccessControlCreateWithFlags(
|
|
15
|
-
nil,
|
|
16
|
-
kSecAttrAccessibleWhenUnlockedThisDeviceOnly,
|
|
17
|
-
.privateKeyUsage,
|
|
18
|
-
&error
|
|
19
|
-
) else {
|
|
20
|
-
if let error {
|
|
21
|
-
fail(error.takeRetainedValue().localizedDescription)
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
fail("Unable to create Secure Enclave access control.")
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
return access
|
|
28
|
-
}
|
|
29
|
-
|
|
11
|
+
// Find the existing Secure Enclave private key.
|
|
30
12
|
func findPrivateKey() -> SecKey? {
|
|
31
13
|
let query: [String: Any] = [
|
|
32
14
|
kSecClass as String: kSecClassKey,
|
|
33
|
-
kSecAttrApplicationTag as String:
|
|
34
|
-
|
|
15
|
+
kSecAttrApplicationTag as String:
|
|
16
|
+
keyTag.data(using: .utf8)!,
|
|
17
|
+
kSecAttrKeyType as String:
|
|
18
|
+
kSecAttrKeyTypeECSECPrimeRandom,
|
|
19
|
+
kSecAttrTokenID as String:
|
|
20
|
+
kSecAttrTokenIDSecureEnclave,
|
|
35
21
|
kSecReturnRef as String: true
|
|
36
22
|
]
|
|
37
23
|
|
|
@@ -50,33 +36,70 @@ func findPrivateKey() -> SecKey? {
|
|
|
50
36
|
}
|
|
51
37
|
|
|
52
38
|
func createKey() {
|
|
39
|
+
|
|
40
|
+
// If the key already exists, don't create another one.
|
|
53
41
|
if findPrivateKey() != nil {
|
|
54
42
|
print("EXISTS")
|
|
55
43
|
return
|
|
56
44
|
}
|
|
57
45
|
|
|
58
|
-
|
|
46
|
+
// Keychain access control.
|
|
47
|
+
//
|
|
48
|
+
// WhenUnlockedThisDeviceOnly means:
|
|
49
|
+
// - available only while the device is unlocked
|
|
50
|
+
// - cannot be restored onto another device
|
|
51
|
+
//
|
|
52
|
+
// privateKeyUsage means the key can be used for signing.
|
|
53
|
+
var accessError: Unmanaged<CFError>?
|
|
54
|
+
|
|
55
|
+
guard let access = SecAccessControlCreateWithFlags(
|
|
56
|
+
nil,
|
|
57
|
+
kSecAttrAccessibleWhenUnlockedThisDeviceOnly,
|
|
58
|
+
.privateKeyUsage,
|
|
59
|
+
&accessError
|
|
60
|
+
) else {
|
|
61
|
+
|
|
62
|
+
if let error = accessError {
|
|
63
|
+
fail(
|
|
64
|
+
"Unable to create Keychain access control: " +
|
|
65
|
+
error.takeRetainedValue().localizedDescription
|
|
66
|
+
)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
fail("Unable to create Keychain access control.")
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
let privateAttributes: [String: Any] = [
|
|
73
|
+
|
|
74
|
+
// Permanently store the private key.
|
|
75
|
+
kSecAttrIsPermanent as String: true,
|
|
76
|
+
|
|
77
|
+
// Identifier used to find the key later.
|
|
78
|
+
kSecAttrApplicationTag as String:
|
|
79
|
+
keyTag.data(using: .utf8)!,
|
|
80
|
+
|
|
81
|
+
// Access rules for the private key.
|
|
82
|
+
kSecAttrAccessControl as String:
|
|
83
|
+
access
|
|
84
|
+
]
|
|
59
85
|
|
|
60
86
|
let attributes: [String: Any] = [
|
|
87
|
+
|
|
88
|
+
// EC P-256 key.
|
|
61
89
|
kSecAttrKeyType as String:
|
|
62
90
|
kSecAttrKeyTypeECSECPrimeRandom,
|
|
63
91
|
|
|
64
92
|
kSecAttrKeySizeInBits as String:
|
|
65
93
|
256,
|
|
66
94
|
|
|
95
|
+
// IMPORTANT:
|
|
96
|
+
// The private key lives inside the Secure Enclave.
|
|
67
97
|
kSecAttrTokenID as String:
|
|
68
98
|
kSecAttrTokenIDSecureEnclave,
|
|
69
99
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
kSecAttrApplicationTag as String:
|
|
75
|
-
keyTag.data(using: .utf8)!,
|
|
76
|
-
|
|
77
|
-
kSecAttrAccessControl as String:
|
|
78
|
-
access
|
|
79
|
-
]
|
|
100
|
+
// Private key configuration.
|
|
101
|
+
kSecPrivateKeyAttrs as String:
|
|
102
|
+
privateAttributes
|
|
80
103
|
]
|
|
81
104
|
|
|
82
105
|
var error: Unmanaged<CFError>?
|
|
@@ -85,17 +108,26 @@ func createKey() {
|
|
|
85
108
|
attributes as CFDictionary,
|
|
86
109
|
&error
|
|
87
110
|
) != nil else {
|
|
111
|
+
|
|
88
112
|
if let error {
|
|
89
|
-
fail(
|
|
113
|
+
fail(
|
|
114
|
+
"Secure Enclave key creation failed: " +
|
|
115
|
+
error.takeRetainedValue().localizedDescription
|
|
116
|
+
)
|
|
90
117
|
}
|
|
91
118
|
|
|
92
|
-
fail("
|
|
119
|
+
fail("Secure Enclave key creation failed.")
|
|
93
120
|
}
|
|
94
121
|
|
|
95
122
|
print("CREATED")
|
|
96
123
|
}
|
|
97
124
|
|
|
125
|
+
|
|
126
|
+
// Return ONLY the public key.
|
|
127
|
+
//
|
|
128
|
+
// The private key is never exported.
|
|
98
129
|
func publicKey() {
|
|
130
|
+
|
|
99
131
|
guard let privateKey = findPrivateKey() else {
|
|
100
132
|
fail("DS01 Secure Enclave key not found.")
|
|
101
133
|
}
|
|
@@ -110,35 +142,47 @@ func publicKey() {
|
|
|
110
142
|
publicKey,
|
|
111
143
|
&error
|
|
112
144
|
) else {
|
|
145
|
+
|
|
113
146
|
if let error {
|
|
114
|
-
fail(
|
|
147
|
+
fail(
|
|
148
|
+
"Unable to export public key: " +
|
|
149
|
+
error.takeRetainedValue().localizedDescription
|
|
150
|
+
)
|
|
115
151
|
}
|
|
116
152
|
|
|
117
153
|
fail("Unable to export public key.")
|
|
118
154
|
}
|
|
119
155
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
156
|
+
print(
|
|
157
|
+
(data as Data).base64EncodedString()
|
|
158
|
+
)
|
|
123
159
|
}
|
|
124
160
|
|
|
161
|
+
|
|
162
|
+
// Sign data using the Secure Enclave private key.
|
|
125
163
|
func sign(_ base64Data: String) {
|
|
164
|
+
|
|
126
165
|
guard let privateKey = findPrivateKey() else {
|
|
127
166
|
fail("DS01 Secure Enclave key not found.")
|
|
128
167
|
}
|
|
129
168
|
|
|
130
|
-
guard let data = Data(
|
|
169
|
+
guard let data = Data(
|
|
170
|
+
base64Encoded: base64Data
|
|
171
|
+
) else {
|
|
131
172
|
fail("Invalid input data.")
|
|
132
173
|
}
|
|
133
174
|
|
|
134
|
-
let algorithm =
|
|
175
|
+
let algorithm =
|
|
176
|
+
SecKeyAlgorithm.ecdsaSignatureMessageX962SHA256
|
|
135
177
|
|
|
136
178
|
guard SecKeyIsAlgorithmSupported(
|
|
137
179
|
privateKey,
|
|
138
180
|
.sign,
|
|
139
181
|
algorithm
|
|
140
182
|
) else {
|
|
141
|
-
fail(
|
|
183
|
+
fail(
|
|
184
|
+
"Secure Enclave key does not support signing."
|
|
185
|
+
)
|
|
142
186
|
}
|
|
143
187
|
|
|
144
188
|
var error: Unmanaged<CFError>?
|
|
@@ -149,23 +193,38 @@ func sign(_ base64Data: String) {
|
|
|
149
193
|
data as CFData,
|
|
150
194
|
&error
|
|
151
195
|
) else {
|
|
196
|
+
|
|
152
197
|
if let error {
|
|
153
|
-
fail(
|
|
198
|
+
fail(
|
|
199
|
+
"Secure Enclave signing failed: " +
|
|
200
|
+
error.takeRetainedValue().localizedDescription
|
|
201
|
+
)
|
|
154
202
|
}
|
|
155
203
|
|
|
156
204
|
fail("Secure Enclave signing failed.")
|
|
157
205
|
}
|
|
158
206
|
|
|
159
|
-
print(
|
|
207
|
+
print(
|
|
208
|
+
(signature as Data).base64EncodedString()
|
|
209
|
+
)
|
|
160
210
|
}
|
|
161
211
|
|
|
212
|
+
|
|
213
|
+
// Remove the DS01 key from this Mac.
|
|
162
214
|
func deleteKey() {
|
|
215
|
+
|
|
163
216
|
let query: [String: Any] = [
|
|
164
|
-
kSecClass as String:
|
|
217
|
+
kSecClass as String:
|
|
218
|
+
kSecClassKey,
|
|
219
|
+
|
|
165
220
|
kSecAttrApplicationTag as String:
|
|
166
221
|
keyTag.data(using: .utf8)!,
|
|
222
|
+
|
|
167
223
|
kSecAttrKeyType as String:
|
|
168
|
-
kSecAttrKeyTypeECSECPrimeRandom
|
|
224
|
+
kSecAttrKeyTypeECSECPrimeRandom,
|
|
225
|
+
|
|
226
|
+
kSecAttrTokenID as String:
|
|
227
|
+
kSecAttrTokenIDSecureEnclave
|
|
169
228
|
]
|
|
170
229
|
|
|
171
230
|
let status = SecItemDelete(
|
|
@@ -174,17 +233,23 @@ func deleteKey() {
|
|
|
174
233
|
|
|
175
234
|
if status != errSecSuccess &&
|
|
176
235
|
status != errSecItemNotFound {
|
|
177
|
-
|
|
236
|
+
|
|
237
|
+
fail(
|
|
238
|
+
"Unable to delete DS01 Secure Enclave key."
|
|
239
|
+
)
|
|
178
240
|
}
|
|
179
241
|
|
|
180
242
|
print("DELETED")
|
|
181
243
|
}
|
|
182
244
|
|
|
245
|
+
|
|
246
|
+
// Command dispatcher.
|
|
183
247
|
guard CommandLine.arguments.count >= 2 else {
|
|
184
248
|
fail("Missing operation.")
|
|
185
249
|
}
|
|
186
250
|
|
|
187
251
|
switch CommandLine.arguments[1] {
|
|
252
|
+
|
|
188
253
|
case "create":
|
|
189
254
|
createKey()
|
|
190
255
|
|
|
@@ -192,6 +257,7 @@ case "public":
|
|
|
192
257
|
publicKey()
|
|
193
258
|
|
|
194
259
|
case "sign":
|
|
260
|
+
|
|
195
261
|
guard CommandLine.arguments.count >= 3 else {
|
|
196
262
|
fail("Missing data to sign.")
|
|
197
263
|
}
|