ds-01 1.0.12 → 1.0.14

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,31 +8,20 @@ func fail(_ message: String) -> Never {
8
8
  exit(1)
9
9
  }
10
10
 
11
- func getAccessControl() -> SecAccessControl {
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
- }
11
+ // Find our existing private key in the current user's Keychain.
12
+ func findPrivateKey() -> SecKey? {
13
+ let query: [String: Any] = [
14
+ kSecClass as String:
15
+ kSecClassKey,
23
16
 
24
- fail("Unable to create Secure Enclave access control.")
25
- }
17
+ kSecAttrApplicationTag as String:
18
+ keyTag.data(using: .utf8)!,
26
19
 
27
- return access
28
- }
20
+ kSecAttrKeyType as String:
21
+ kSecAttrKeyTypeECSECPrimeRandom,
29
22
 
30
- func findPrivateKey() -> SecKey? {
31
- let query: [String: Any] = [
32
- kSecClass as String: kSecClassKey,
33
- kSecAttrApplicationTag as String: keyTag.data(using: .utf8)!,
34
- kSecAttrKeyType as String: kSecAttrKeyTypeECSECPrimeRandom,
35
- kSecReturnRef as String: true
23
+ kSecReturnRef as String:
24
+ true
36
25
  ]
37
26
 
38
27
  var result: CFTypeRef?
@@ -49,33 +38,33 @@ func findPrivateKey() -> SecKey? {
49
38
  return (result as! SecKey)
50
39
  }
51
40
 
41
+
42
+ // Create a persistent EC P-256 private key.
52
43
  func createKey() {
44
+
45
+ // Don't create a second key if one already exists.
53
46
  if findPrivateKey() != nil {
54
47
  print("EXISTS")
55
48
  return
56
49
  }
57
50
 
58
- let access = getAccessControl()
59
-
60
51
  let attributes: [String: Any] = [
52
+
53
+ // EC P-256 key.
61
54
  kSecAttrKeyType as String:
62
55
  kSecAttrKeyTypeECSECPrimeRandom,
63
56
 
64
57
  kSecAttrKeySizeInBits as String:
65
58
  256,
66
59
 
67
- kSecAttrTokenID as String:
68
- kSecAttrTokenIDSecureEnclave,
69
-
60
+ // Store the private key permanently in Keychain.
70
61
  kSecPrivateKeyAttrs as String: [
62
+
71
63
  kSecAttrIsPermanent as String:
72
64
  true,
73
65
 
74
66
  kSecAttrApplicationTag as String:
75
- keyTag.data(using: .utf8)!,
76
-
77
- kSecAttrAccessControl as String:
78
- access
67
+ keyTag.data(using: .utf8)!
79
68
  ]
80
69
  ]
81
70
 
@@ -85,22 +74,33 @@ func createKey() {
85
74
  attributes as CFDictionary,
86
75
  &error
87
76
  ) != nil else {
77
+
88
78
  if let error {
89
- fail(error.takeRetainedValue().localizedDescription)
79
+ fail(
80
+ "Keychain key creation failed: " +
81
+ error.takeRetainedValue().localizedDescription
82
+ )
90
83
  }
91
84
 
92
- fail("Unable to create Secure Enclave key.")
85
+ fail("Keychain key creation failed.")
93
86
  }
94
87
 
95
88
  print("CREATED")
96
89
  }
97
90
 
91
+
92
+ // Return only the public key.
93
+ //
94
+ // The private key is never exported.
98
95
  func publicKey() {
96
+
99
97
  guard let privateKey = findPrivateKey() else {
100
- fail("DS01 Secure Enclave key not found.")
98
+ fail("DS01 device key not found.")
101
99
  }
102
100
 
103
- guard let publicKey = SecKeyCopyPublicKey(privateKey) else {
101
+ guard let publicKey = SecKeyCopyPublicKey(
102
+ privateKey
103
+ ) else {
104
104
  fail("Unable to obtain public key.")
105
105
  }
106
106
 
@@ -110,35 +110,45 @@ func publicKey() {
110
110
  publicKey,
111
111
  &error
112
112
  ) else {
113
+
113
114
  if let error {
114
- fail(error.takeRetainedValue().localizedDescription)
115
+ fail(
116
+ "Unable to export public key: " +
117
+ error.takeRetainedValue().localizedDescription
118
+ )
115
119
  }
116
120
 
117
121
  fail("Unable to export public key.")
118
122
  }
119
123
 
120
- let base64 = (data as Data).base64EncodedString()
121
-
122
- print(base64)
124
+ print(
125
+ (data as Data).base64EncodedString()
126
+ )
123
127
  }
124
128
 
129
+
130
+ // Sign data using the Keychain private key.
125
131
  func sign(_ base64Data: String) {
132
+
126
133
  guard let privateKey = findPrivateKey() else {
127
- fail("DS01 Secure Enclave key not found.")
134
+ fail("DS01 device key not found.")
128
135
  }
129
136
 
130
- guard let data = Data(base64Encoded: base64Data) else {
137
+ guard let data = Data(
138
+ base64Encoded: base64Data
139
+ ) else {
131
140
  fail("Invalid input data.")
132
141
  }
133
142
 
134
- let algorithm = SecKeyAlgorithm.ecdsaSignatureMessageX962SHA256
143
+ let algorithm =
144
+ SecKeyAlgorithm.ecdsaSignatureMessageX962SHA256
135
145
 
136
146
  guard SecKeyIsAlgorithmSupported(
137
147
  privateKey,
138
148
  .sign,
139
149
  algorithm
140
150
  ) else {
141
- fail("Secure Enclave key does not support signing.")
151
+ fail("Device key does not support signing.")
142
152
  }
143
153
 
144
154
  var error: Unmanaged<CFError>?
@@ -149,21 +159,33 @@ func sign(_ base64Data: String) {
149
159
  data as CFData,
150
160
  &error
151
161
  ) else {
162
+
152
163
  if let error {
153
- fail(error.takeRetainedValue().localizedDescription)
164
+ fail(
165
+ "Signing failed: " +
166
+ error.takeRetainedValue().localizedDescription
167
+ )
154
168
  }
155
169
 
156
- fail("Secure Enclave signing failed.")
170
+ fail("Signing failed.")
157
171
  }
158
172
 
159
- print((signature as Data).base64EncodedString())
173
+ print(
174
+ (signature as Data).base64EncodedString()
175
+ )
160
176
  }
161
177
 
178
+
179
+ // Delete the DS01 private key from Keychain.
162
180
  func deleteKey() {
181
+
163
182
  let query: [String: Any] = [
164
- kSecClass as String: kSecClassKey,
183
+ kSecClass as String:
184
+ kSecClassKey,
185
+
165
186
  kSecAttrApplicationTag as String:
166
187
  keyTag.data(using: .utf8)!,
188
+
167
189
  kSecAttrKeyType as String:
168
190
  kSecAttrKeyTypeECSECPrimeRandom
169
191
  ]
@@ -174,17 +196,23 @@ func deleteKey() {
174
196
 
175
197
  if status != errSecSuccess &&
176
198
  status != errSecItemNotFound {
177
- fail("Unable to delete DS01 Secure Enclave key.")
199
+
200
+ fail(
201
+ "Unable to delete DS01 device key."
202
+ )
178
203
  }
179
204
 
180
205
  print("DELETED")
181
206
  }
182
207
 
208
+
209
+ // Command dispatcher.
183
210
  guard CommandLine.arguments.count >= 2 else {
184
211
  fail("Missing operation.")
185
212
  }
186
213
 
187
214
  switch CommandLine.arguments[1] {
215
+
188
216
  case "create":
189
217
  createKey()
190
218
 
@@ -192,6 +220,7 @@ case "public":
192
220
  publicKey()
193
221
 
194
222
  case "sign":
223
+
195
224
  guard CommandLine.arguments.count >= 3 else {
196
225
  fail("Missing data to sign.")
197
226
  }
package/package.json CHANGED
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "name": "ds-01",
3
- "version": "1.0.12",
3
+ "version": "1.0.14",
4
4
  "description": "Make your site best with DS01",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "ds01": "dist/index.js"
8
8
  },
9
9
  "scripts": {
10
- "build": "tsc && node scripts/copy-native.js"
10
+ "build": "tsc && node scripts/copy-native.js && node scripts/build-macos-helper.js"
11
11
  },
12
12
  "publishConfig": {
13
13
  "access": "public"
@@ -0,0 +1,52 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { execFileSync } from "node:child_process";
4
+
5
+ if (process.platform !== "darwin") {
6
+ console.log("Skipping macOS native helper build.");
7
+ process.exit(0);
8
+ }
9
+
10
+ const root = process.cwd();
11
+
12
+ const source = path.join(
13
+ root,
14
+ "src",
15
+ "native",
16
+ "macos-key-helper.swift",
17
+ );
18
+
19
+ const nativeDir = path.join(
20
+ root,
21
+ "dist",
22
+ "native",
23
+ );
24
+
25
+ const output = path.join(
26
+ nativeDir,
27
+ "macos-key-helper",
28
+ );
29
+
30
+ if (!fs.existsSync(source)) {
31
+ throw new Error(`Swift helper not found: ${source}`);
32
+ }
33
+
34
+ fs.mkdirSync(nativeDir, {
35
+ recursive: true,
36
+ });
37
+
38
+ console.log("Building macOS native helper...");
39
+
40
+ execFileSync(
41
+ "swiftc",
42
+ [
43
+ source,
44
+ "-o",
45
+ output,
46
+ ],
47
+ {
48
+ stdio: "inherit",
49
+ },
50
+ );
51
+
52
+ console.log("macOS native helper built successfully.");
@@ -12,4 +12,4 @@ fs.mkdirSync(destinationDir, { recursive: true });
12
12
 
13
13
  fs.copyFileSync(source, destination);
14
14
 
15
- console.log("✅ Copied macOS native helper.");
15
+
@@ -8,31 +8,20 @@ func fail(_ message: String) -> Never {
8
8
  exit(1)
9
9
  }
10
10
 
11
- func getAccessControl() -> SecAccessControl {
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
- }
11
+ // Find our existing private key in the current user's Keychain.
12
+ func findPrivateKey() -> SecKey? {
13
+ let query: [String: Any] = [
14
+ kSecClass as String:
15
+ kSecClassKey,
23
16
 
24
- fail("Unable to create Secure Enclave access control.")
25
- }
17
+ kSecAttrApplicationTag as String:
18
+ keyTag.data(using: .utf8)!,
26
19
 
27
- return access
28
- }
20
+ kSecAttrKeyType as String:
21
+ kSecAttrKeyTypeECSECPrimeRandom,
29
22
 
30
- func findPrivateKey() -> SecKey? {
31
- let query: [String: Any] = [
32
- kSecClass as String: kSecClassKey,
33
- kSecAttrApplicationTag as String: keyTag.data(using: .utf8)!,
34
- kSecAttrKeyType as String: kSecAttrKeyTypeECSECPrimeRandom,
35
- kSecReturnRef as String: true
23
+ kSecReturnRef as String:
24
+ true
36
25
  ]
37
26
 
38
27
  var result: CFTypeRef?
@@ -49,33 +38,33 @@ func findPrivateKey() -> SecKey? {
49
38
  return (result as! SecKey)
50
39
  }
51
40
 
41
+
42
+ // Create a persistent EC P-256 private key.
52
43
  func createKey() {
44
+
45
+ // Don't create a second key if one already exists.
53
46
  if findPrivateKey() != nil {
54
47
  print("EXISTS")
55
48
  return
56
49
  }
57
50
 
58
- let access = getAccessControl()
59
-
60
51
  let attributes: [String: Any] = [
52
+
53
+ // EC P-256 key.
61
54
  kSecAttrKeyType as String:
62
55
  kSecAttrKeyTypeECSECPrimeRandom,
63
56
 
64
57
  kSecAttrKeySizeInBits as String:
65
58
  256,
66
59
 
67
- kSecAttrTokenID as String:
68
- kSecAttrTokenIDSecureEnclave,
69
-
60
+ // Store the private key permanently in Keychain.
70
61
  kSecPrivateKeyAttrs as String: [
62
+
71
63
  kSecAttrIsPermanent as String:
72
64
  true,
73
65
 
74
66
  kSecAttrApplicationTag as String:
75
- keyTag.data(using: .utf8)!,
76
-
77
- kSecAttrAccessControl as String:
78
- access
67
+ keyTag.data(using: .utf8)!
79
68
  ]
80
69
  ]
81
70
 
@@ -85,22 +74,33 @@ func createKey() {
85
74
  attributes as CFDictionary,
86
75
  &error
87
76
  ) != nil else {
77
+
88
78
  if let error {
89
- fail(error.takeRetainedValue().localizedDescription)
79
+ fail(
80
+ "Keychain key creation failed: " +
81
+ error.takeRetainedValue().localizedDescription
82
+ )
90
83
  }
91
84
 
92
- fail("Unable to create Secure Enclave key.")
85
+ fail("Keychain key creation failed.")
93
86
  }
94
87
 
95
88
  print("CREATED")
96
89
  }
97
90
 
91
+
92
+ // Return only the public key.
93
+ //
94
+ // The private key is never exported.
98
95
  func publicKey() {
96
+
99
97
  guard let privateKey = findPrivateKey() else {
100
- fail("DS01 Secure Enclave key not found.")
98
+ fail("DS01 device key not found.")
101
99
  }
102
100
 
103
- guard let publicKey = SecKeyCopyPublicKey(privateKey) else {
101
+ guard let publicKey = SecKeyCopyPublicKey(
102
+ privateKey
103
+ ) else {
104
104
  fail("Unable to obtain public key.")
105
105
  }
106
106
 
@@ -110,35 +110,45 @@ func publicKey() {
110
110
  publicKey,
111
111
  &error
112
112
  ) else {
113
+
113
114
  if let error {
114
- fail(error.takeRetainedValue().localizedDescription)
115
+ fail(
116
+ "Unable to export public key: " +
117
+ error.takeRetainedValue().localizedDescription
118
+ )
115
119
  }
116
120
 
117
121
  fail("Unable to export public key.")
118
122
  }
119
123
 
120
- let base64 = (data as Data).base64EncodedString()
121
-
122
- print(base64)
124
+ print(
125
+ (data as Data).base64EncodedString()
126
+ )
123
127
  }
124
128
 
129
+
130
+ // Sign data using the Keychain private key.
125
131
  func sign(_ base64Data: String) {
132
+
126
133
  guard let privateKey = findPrivateKey() else {
127
- fail("DS01 Secure Enclave key not found.")
134
+ fail("DS01 device key not found.")
128
135
  }
129
136
 
130
- guard let data = Data(base64Encoded: base64Data) else {
137
+ guard let data = Data(
138
+ base64Encoded: base64Data
139
+ ) else {
131
140
  fail("Invalid input data.")
132
141
  }
133
142
 
134
- let algorithm = SecKeyAlgorithm.ecdsaSignatureMessageX962SHA256
143
+ let algorithm =
144
+ SecKeyAlgorithm.ecdsaSignatureMessageX962SHA256
135
145
 
136
146
  guard SecKeyIsAlgorithmSupported(
137
147
  privateKey,
138
148
  .sign,
139
149
  algorithm
140
150
  ) else {
141
- fail("Secure Enclave key does not support signing.")
151
+ fail("Device key does not support signing.")
142
152
  }
143
153
 
144
154
  var error: Unmanaged<CFError>?
@@ -149,21 +159,33 @@ func sign(_ base64Data: String) {
149
159
  data as CFData,
150
160
  &error
151
161
  ) else {
162
+
152
163
  if let error {
153
- fail(error.takeRetainedValue().localizedDescription)
164
+ fail(
165
+ "Signing failed: " +
166
+ error.takeRetainedValue().localizedDescription
167
+ )
154
168
  }
155
169
 
156
- fail("Secure Enclave signing failed.")
170
+ fail("Signing failed.")
157
171
  }
158
172
 
159
- print((signature as Data).base64EncodedString())
173
+ print(
174
+ (signature as Data).base64EncodedString()
175
+ )
160
176
  }
161
177
 
178
+
179
+ // Delete the DS01 private key from Keychain.
162
180
  func deleteKey() {
181
+
163
182
  let query: [String: Any] = [
164
- kSecClass as String: kSecClassKey,
183
+ kSecClass as String:
184
+ kSecClassKey,
185
+
165
186
  kSecAttrApplicationTag as String:
166
187
  keyTag.data(using: .utf8)!,
188
+
167
189
  kSecAttrKeyType as String:
168
190
  kSecAttrKeyTypeECSECPrimeRandom
169
191
  ]
@@ -174,17 +196,23 @@ func deleteKey() {
174
196
 
175
197
  if status != errSecSuccess &&
176
198
  status != errSecItemNotFound {
177
- fail("Unable to delete DS01 Secure Enclave key.")
199
+
200
+ fail(
201
+ "Unable to delete DS01 device key."
202
+ )
178
203
  }
179
204
 
180
205
  print("DELETED")
181
206
  }
182
207
 
208
+
209
+ // Command dispatcher.
183
210
  guard CommandLine.arguments.count >= 2 else {
184
211
  fail("Missing operation.")
185
212
  }
186
213
 
187
214
  switch CommandLine.arguments[1] {
215
+
188
216
  case "create":
189
217
  createKey()
190
218
 
@@ -192,6 +220,7 @@ case "public":
192
220
  publicKey()
193
221
 
194
222
  case "sign":
223
+
195
224
  guard CommandLine.arguments.count >= 3 else {
196
225
  fail("Missing data to sign.")
197
226
  }