ds-01 1.0.11 → 1.0.12
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.
- package/dist/native/macos-key-helper.swift +206 -0
- package/package.json +2 -2
- package/scripts/copy-native.js +15 -0
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
import Security
|
|
3
|
+
|
|
4
|
+
let keyTag = "com.ds01.cli.device-key"
|
|
5
|
+
|
|
6
|
+
func fail(_ message: String) -> Never {
|
|
7
|
+
fputs(message + "\n", stderr)
|
|
8
|
+
exit(1)
|
|
9
|
+
}
|
|
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
|
+
}
|
|
23
|
+
|
|
24
|
+
fail("Unable to create Secure Enclave access control.")
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return access
|
|
28
|
+
}
|
|
29
|
+
|
|
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
|
|
36
|
+
]
|
|
37
|
+
|
|
38
|
+
var result: CFTypeRef?
|
|
39
|
+
|
|
40
|
+
let status = SecItemCopyMatching(
|
|
41
|
+
query as CFDictionary,
|
|
42
|
+
&result
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
guard status == errSecSuccess else {
|
|
46
|
+
return nil
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return (result as! SecKey)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
func createKey() {
|
|
53
|
+
if findPrivateKey() != nil {
|
|
54
|
+
print("EXISTS")
|
|
55
|
+
return
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
let access = getAccessControl()
|
|
59
|
+
|
|
60
|
+
let attributes: [String: Any] = [
|
|
61
|
+
kSecAttrKeyType as String:
|
|
62
|
+
kSecAttrKeyTypeECSECPrimeRandom,
|
|
63
|
+
|
|
64
|
+
kSecAttrKeySizeInBits as String:
|
|
65
|
+
256,
|
|
66
|
+
|
|
67
|
+
kSecAttrTokenID as String:
|
|
68
|
+
kSecAttrTokenIDSecureEnclave,
|
|
69
|
+
|
|
70
|
+
kSecPrivateKeyAttrs as String: [
|
|
71
|
+
kSecAttrIsPermanent as String:
|
|
72
|
+
true,
|
|
73
|
+
|
|
74
|
+
kSecAttrApplicationTag as String:
|
|
75
|
+
keyTag.data(using: .utf8)!,
|
|
76
|
+
|
|
77
|
+
kSecAttrAccessControl as String:
|
|
78
|
+
access
|
|
79
|
+
]
|
|
80
|
+
]
|
|
81
|
+
|
|
82
|
+
var error: Unmanaged<CFError>?
|
|
83
|
+
|
|
84
|
+
guard SecKeyCreateRandomKey(
|
|
85
|
+
attributes as CFDictionary,
|
|
86
|
+
&error
|
|
87
|
+
) != nil else {
|
|
88
|
+
if let error {
|
|
89
|
+
fail(error.takeRetainedValue().localizedDescription)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
fail("Unable to create Secure Enclave key.")
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
print("CREATED")
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
func publicKey() {
|
|
99
|
+
guard let privateKey = findPrivateKey() else {
|
|
100
|
+
fail("DS01 Secure Enclave key not found.")
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
guard let publicKey = SecKeyCopyPublicKey(privateKey) else {
|
|
104
|
+
fail("Unable to obtain public key.")
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
var error: Unmanaged<CFError>?
|
|
108
|
+
|
|
109
|
+
guard let data = SecKeyCopyExternalRepresentation(
|
|
110
|
+
publicKey,
|
|
111
|
+
&error
|
|
112
|
+
) else {
|
|
113
|
+
if let error {
|
|
114
|
+
fail(error.takeRetainedValue().localizedDescription)
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
fail("Unable to export public key.")
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
let base64 = (data as Data).base64EncodedString()
|
|
121
|
+
|
|
122
|
+
print(base64)
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
func sign(_ base64Data: String) {
|
|
126
|
+
guard let privateKey = findPrivateKey() else {
|
|
127
|
+
fail("DS01 Secure Enclave key not found.")
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
guard let data = Data(base64Encoded: base64Data) else {
|
|
131
|
+
fail("Invalid input data.")
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
let algorithm = SecKeyAlgorithm.ecdsaSignatureMessageX962SHA256
|
|
135
|
+
|
|
136
|
+
guard SecKeyIsAlgorithmSupported(
|
|
137
|
+
privateKey,
|
|
138
|
+
.sign,
|
|
139
|
+
algorithm
|
|
140
|
+
) else {
|
|
141
|
+
fail("Secure Enclave key does not support signing.")
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
var error: Unmanaged<CFError>?
|
|
145
|
+
|
|
146
|
+
guard let signature = SecKeyCreateSignature(
|
|
147
|
+
privateKey,
|
|
148
|
+
algorithm,
|
|
149
|
+
data as CFData,
|
|
150
|
+
&error
|
|
151
|
+
) else {
|
|
152
|
+
if let error {
|
|
153
|
+
fail(error.takeRetainedValue().localizedDescription)
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
fail("Secure Enclave signing failed.")
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
print((signature as Data).base64EncodedString())
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
func deleteKey() {
|
|
163
|
+
let query: [String: Any] = [
|
|
164
|
+
kSecClass as String: kSecClassKey,
|
|
165
|
+
kSecAttrApplicationTag as String:
|
|
166
|
+
keyTag.data(using: .utf8)!,
|
|
167
|
+
kSecAttrKeyType as String:
|
|
168
|
+
kSecAttrKeyTypeECSECPrimeRandom
|
|
169
|
+
]
|
|
170
|
+
|
|
171
|
+
let status = SecItemDelete(
|
|
172
|
+
query as CFDictionary
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
if status != errSecSuccess &&
|
|
176
|
+
status != errSecItemNotFound {
|
|
177
|
+
fail("Unable to delete DS01 Secure Enclave key.")
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
print("DELETED")
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
guard CommandLine.arguments.count >= 2 else {
|
|
184
|
+
fail("Missing operation.")
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
switch CommandLine.arguments[1] {
|
|
188
|
+
case "create":
|
|
189
|
+
createKey()
|
|
190
|
+
|
|
191
|
+
case "public":
|
|
192
|
+
publicKey()
|
|
193
|
+
|
|
194
|
+
case "sign":
|
|
195
|
+
guard CommandLine.arguments.count >= 3 else {
|
|
196
|
+
fail("Missing data to sign.")
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
sign(CommandLine.arguments[2])
|
|
200
|
+
|
|
201
|
+
case "delete":
|
|
202
|
+
deleteKey()
|
|
203
|
+
|
|
204
|
+
default:
|
|
205
|
+
fail("Unknown operation.")
|
|
206
|
+
}
|
package/package.json
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ds-01",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.12",
|
|
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"
|
|
10
|
+
"build": "tsc && node scripts/copy-native.js"
|
|
11
11
|
},
|
|
12
12
|
"publishConfig": {
|
|
13
13
|
"access": "public"
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
|
|
4
|
+
const source = path.resolve("src/native/macos-key-helper.swift");
|
|
5
|
+
const destinationDir = path.resolve("dist/native");
|
|
6
|
+
const destination = path.join(
|
|
7
|
+
destinationDir,
|
|
8
|
+
"macos-key-helper.swift",
|
|
9
|
+
);
|
|
10
|
+
|
|
11
|
+
fs.mkdirSync(destinationDir, { recursive: true });
|
|
12
|
+
|
|
13
|
+
fs.copyFileSync(source, destination);
|
|
14
|
+
|
|
15
|
+
console.log("✅ Copied macOS native helper.");
|