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
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
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
|
-
|
|
25
|
-
|
|
17
|
+
kSecAttrApplicationTag as String:
|
|
18
|
+
keyTag.data(using: .utf8)!,
|
|
26
19
|
|
|
27
|
-
|
|
28
|
-
|
|
20
|
+
kSecAttrKeyType as String:
|
|
21
|
+
kSecAttrKeyTypeECSECPrimeRandom,
|
|
29
22
|
|
|
30
|
-
|
|
31
|
-
|
|
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
|
-
|
|
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(
|
|
79
|
+
fail(
|
|
80
|
+
"Keychain key creation failed: " +
|
|
81
|
+
error.takeRetainedValue().localizedDescription
|
|
82
|
+
)
|
|
90
83
|
}
|
|
91
84
|
|
|
92
|
-
fail("
|
|
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
|
|
98
|
+
fail("DS01 device key not found.")
|
|
101
99
|
}
|
|
102
100
|
|
|
103
|
-
guard let publicKey = SecKeyCopyPublicKey(
|
|
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(
|
|
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
|
-
|
|
121
|
-
|
|
122
|
-
|
|
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
|
|
134
|
+
fail("DS01 device key not found.")
|
|
128
135
|
}
|
|
129
136
|
|
|
130
|
-
guard let data = Data(
|
|
137
|
+
guard let data = Data(
|
|
138
|
+
base64Encoded: base64Data
|
|
139
|
+
) else {
|
|
131
140
|
fail("Invalid input data.")
|
|
132
141
|
}
|
|
133
142
|
|
|
134
|
-
let algorithm =
|
|
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("
|
|
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(
|
|
164
|
+
fail(
|
|
165
|
+
"Signing failed: " +
|
|
166
|
+
error.takeRetainedValue().localizedDescription
|
|
167
|
+
)
|
|
154
168
|
}
|
|
155
169
|
|
|
156
|
-
fail("
|
|
170
|
+
fail("Signing failed.")
|
|
157
171
|
}
|
|
158
172
|
|
|
159
|
-
print(
|
|
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:
|
|
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
|
-
|
|
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.
|
|
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.");
|
package/scripts/copy-native.js
CHANGED
|
@@ -8,31 +8,20 @@ func fail(_ message: String) -> Never {
|
|
|
8
8
|
exit(1)
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
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
|
-
|
|
25
|
-
|
|
17
|
+
kSecAttrApplicationTag as String:
|
|
18
|
+
keyTag.data(using: .utf8)!,
|
|
26
19
|
|
|
27
|
-
|
|
28
|
-
|
|
20
|
+
kSecAttrKeyType as String:
|
|
21
|
+
kSecAttrKeyTypeECSECPrimeRandom,
|
|
29
22
|
|
|
30
|
-
|
|
31
|
-
|
|
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
|
-
|
|
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(
|
|
79
|
+
fail(
|
|
80
|
+
"Keychain key creation failed: " +
|
|
81
|
+
error.takeRetainedValue().localizedDescription
|
|
82
|
+
)
|
|
90
83
|
}
|
|
91
84
|
|
|
92
|
-
fail("
|
|
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
|
|
98
|
+
fail("DS01 device key not found.")
|
|
101
99
|
}
|
|
102
100
|
|
|
103
|
-
guard let publicKey = SecKeyCopyPublicKey(
|
|
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(
|
|
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
|
-
|
|
121
|
-
|
|
122
|
-
|
|
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
|
|
134
|
+
fail("DS01 device key not found.")
|
|
128
135
|
}
|
|
129
136
|
|
|
130
|
-
guard let data = Data(
|
|
137
|
+
guard let data = Data(
|
|
138
|
+
base64Encoded: base64Data
|
|
139
|
+
) else {
|
|
131
140
|
fail("Invalid input data.")
|
|
132
141
|
}
|
|
133
142
|
|
|
134
|
-
let algorithm =
|
|
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("
|
|
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(
|
|
164
|
+
fail(
|
|
165
|
+
"Signing failed: " +
|
|
166
|
+
error.takeRetainedValue().localizedDescription
|
|
167
|
+
)
|
|
154
168
|
}
|
|
155
169
|
|
|
156
|
-
fail("
|
|
170
|
+
fail("Signing failed.")
|
|
157
171
|
}
|
|
158
172
|
|
|
159
|
-
print(
|
|
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:
|
|
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
|
-
|
|
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
|
}
|