ds-01 1.0.13 → 1.1.1
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/.turbo/turbo-build.log +3 -2
- package/dist/commands/add.d.ts.map +1 -1
- package/dist/commands/add.js +16 -61
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +11 -45
- package/dist/commands/login.d.ts.map +1 -1
- package/dist/commands/login.js +20 -60
- package/dist/commands/logout.d.ts.map +1 -1
- package/dist/commands/logout.js +0 -2
- package/dist/native/macos-key-helper.swift +34 -71
- package/dist/utils/secureKeyStore.d.ts +0 -5
- package/dist/utils/secureKeyStore.d.ts.map +1 -1
- package/dist/utils/secureKeyStore.js +13 -28
- package/package.json +2 -2
- package/scripts/build-macos-helper.js +52 -0
- package/src/commands/add.ts +66 -210
- package/src/commands/init.ts +26 -51
- package/src/commands/login.ts +71 -244
- package/src/commands/logout.ts +0 -2
- package/src/native/macos-key-helper.swift +34 -71
- package/src/utils/secureKeyStore.ts +41 -88
- package/src/test-key.ts +0 -20
- package/src/utils/deviceKey.ts +0 -19
|
@@ -8,17 +8,20 @@ func fail(_ message: String) -> Never {
|
|
|
8
8
|
exit(1)
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
-
// Find
|
|
11
|
+
// Find our existing private key in the current user's Keychain.
|
|
12
12
|
func findPrivateKey() -> SecKey? {
|
|
13
13
|
let query: [String: Any] = [
|
|
14
|
-
kSecClass as String:
|
|
14
|
+
kSecClass as String:
|
|
15
|
+
kSecClassKey,
|
|
16
|
+
|
|
15
17
|
kSecAttrApplicationTag as String:
|
|
16
18
|
keyTag.data(using: .utf8)!,
|
|
19
|
+
|
|
17
20
|
kSecAttrKeyType as String:
|
|
18
21
|
kSecAttrKeyTypeECSECPrimeRandom,
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
+
|
|
23
|
+
kSecReturnRef as String:
|
|
24
|
+
true
|
|
22
25
|
]
|
|
23
26
|
|
|
24
27
|
var result: CFTypeRef?
|
|
@@ -35,54 +38,16 @@ func findPrivateKey() -> SecKey? {
|
|
|
35
38
|
return (result as! SecKey)
|
|
36
39
|
}
|
|
37
40
|
|
|
41
|
+
|
|
42
|
+
// Create a persistent EC P-256 private key.
|
|
38
43
|
func createKey() {
|
|
39
44
|
|
|
40
|
-
//
|
|
45
|
+
// Don't create a second key if one already exists.
|
|
41
46
|
if findPrivateKey() != nil {
|
|
42
47
|
print("EXISTS")
|
|
43
48
|
return
|
|
44
49
|
}
|
|
45
50
|
|
|
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
|
-
]
|
|
85
|
-
|
|
86
51
|
let attributes: [String: Any] = [
|
|
87
52
|
|
|
88
53
|
// EC P-256 key.
|
|
@@ -92,14 +57,15 @@ func createKey() {
|
|
|
92
57
|
kSecAttrKeySizeInBits as String:
|
|
93
58
|
256,
|
|
94
59
|
|
|
95
|
-
//
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
60
|
+
// Store the private key permanently in Keychain.
|
|
61
|
+
kSecPrivateKeyAttrs as String: [
|
|
62
|
+
|
|
63
|
+
kSecAttrIsPermanent as String:
|
|
64
|
+
true,
|
|
99
65
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
66
|
+
kSecAttrApplicationTag as String:
|
|
67
|
+
keyTag.data(using: .utf8)!
|
|
68
|
+
]
|
|
103
69
|
]
|
|
104
70
|
|
|
105
71
|
var error: Unmanaged<CFError>?
|
|
@@ -111,28 +77,30 @@ func createKey() {
|
|
|
111
77
|
|
|
112
78
|
if let error {
|
|
113
79
|
fail(
|
|
114
|
-
"
|
|
80
|
+
"Keychain key creation failed: " +
|
|
115
81
|
error.takeRetainedValue().localizedDescription
|
|
116
82
|
)
|
|
117
83
|
}
|
|
118
84
|
|
|
119
|
-
fail("
|
|
85
|
+
fail("Keychain key creation failed.")
|
|
120
86
|
}
|
|
121
87
|
|
|
122
88
|
print("CREATED")
|
|
123
89
|
}
|
|
124
90
|
|
|
125
91
|
|
|
126
|
-
// Return
|
|
92
|
+
// Return only the public key.
|
|
127
93
|
//
|
|
128
94
|
// The private key is never exported.
|
|
129
95
|
func publicKey() {
|
|
130
96
|
|
|
131
97
|
guard let privateKey = findPrivateKey() else {
|
|
132
|
-
fail("DS01
|
|
98
|
+
fail("DS01 device key not found.")
|
|
133
99
|
}
|
|
134
100
|
|
|
135
|
-
guard let publicKey = SecKeyCopyPublicKey(
|
|
101
|
+
guard let publicKey = SecKeyCopyPublicKey(
|
|
102
|
+
privateKey
|
|
103
|
+
) else {
|
|
136
104
|
fail("Unable to obtain public key.")
|
|
137
105
|
}
|
|
138
106
|
|
|
@@ -159,11 +127,11 @@ func publicKey() {
|
|
|
159
127
|
}
|
|
160
128
|
|
|
161
129
|
|
|
162
|
-
// Sign data using the
|
|
130
|
+
// Sign data using the Keychain private key.
|
|
163
131
|
func sign(_ base64Data: String) {
|
|
164
132
|
|
|
165
133
|
guard let privateKey = findPrivateKey() else {
|
|
166
|
-
fail("DS01
|
|
134
|
+
fail("DS01 device key not found.")
|
|
167
135
|
}
|
|
168
136
|
|
|
169
137
|
guard let data = Data(
|
|
@@ -180,9 +148,7 @@ func sign(_ base64Data: String) {
|
|
|
180
148
|
.sign,
|
|
181
149
|
algorithm
|
|
182
150
|
) else {
|
|
183
|
-
fail(
|
|
184
|
-
"Secure Enclave key does not support signing."
|
|
185
|
-
)
|
|
151
|
+
fail("Device key does not support signing.")
|
|
186
152
|
}
|
|
187
153
|
|
|
188
154
|
var error: Unmanaged<CFError>?
|
|
@@ -196,12 +162,12 @@ func sign(_ base64Data: String) {
|
|
|
196
162
|
|
|
197
163
|
if let error {
|
|
198
164
|
fail(
|
|
199
|
-
"
|
|
165
|
+
"Signing failed: " +
|
|
200
166
|
error.takeRetainedValue().localizedDescription
|
|
201
167
|
)
|
|
202
168
|
}
|
|
203
169
|
|
|
204
|
-
fail("
|
|
170
|
+
fail("Signing failed.")
|
|
205
171
|
}
|
|
206
172
|
|
|
207
173
|
print(
|
|
@@ -210,7 +176,7 @@ func sign(_ base64Data: String) {
|
|
|
210
176
|
}
|
|
211
177
|
|
|
212
178
|
|
|
213
|
-
//
|
|
179
|
+
// Delete the DS01 private key from Keychain.
|
|
214
180
|
func deleteKey() {
|
|
215
181
|
|
|
216
182
|
let query: [String: Any] = [
|
|
@@ -221,10 +187,7 @@ func deleteKey() {
|
|
|
221
187
|
keyTag.data(using: .utf8)!,
|
|
222
188
|
|
|
223
189
|
kSecAttrKeyType as String:
|
|
224
|
-
kSecAttrKeyTypeECSECPrimeRandom
|
|
225
|
-
|
|
226
|
-
kSecAttrTokenID as String:
|
|
227
|
-
kSecAttrTokenIDSecureEnclave
|
|
190
|
+
kSecAttrKeyTypeECSECPrimeRandom
|
|
228
191
|
]
|
|
229
192
|
|
|
230
193
|
let status = SecItemDelete(
|
|
@@ -235,7 +198,7 @@ func deleteKey() {
|
|
|
235
198
|
status != errSecItemNotFound {
|
|
236
199
|
|
|
237
200
|
fail(
|
|
238
|
-
"Unable to delete DS01
|
|
201
|
+
"Unable to delete DS01 device key."
|
|
239
202
|
)
|
|
240
203
|
}
|
|
241
204
|
|
|
@@ -11,13 +11,16 @@ const execFileAsync = promisify(execFile);
|
|
|
11
11
|
const KEY_NAME = "DS01-CLI-Device-Key";
|
|
12
12
|
|
|
13
13
|
const __filename = fileURLToPath(import.meta.url);
|
|
14
|
+
// import.meta.ur -- current module ka location URL form mein deta hai
|
|
15
|
+
|
|
14
16
|
const __dirname = path.dirname(__filename);
|
|
17
|
+
//C:\Projects\DS01\cli\dist\utils
|
|
18
|
+
|
|
19
|
+
// __filename = where is THIS FILE?
|
|
20
|
+
// __dirname = which folder contains THIS FILE?
|
|
15
21
|
|
|
16
22
|
function getMacHelperPath(): string {
|
|
17
|
-
return path.resolve(
|
|
18
|
-
__dirname,
|
|
19
|
-
"../native/macos-key-helper.swift",
|
|
20
|
-
);
|
|
23
|
+
return path.resolve(__dirname, "../native/macos-key-helper.swift");
|
|
21
24
|
}
|
|
22
25
|
|
|
23
26
|
function ensureSupportedOS(): void {
|
|
@@ -30,11 +33,7 @@ function ensureSupportedOS(): void {
|
|
|
30
33
|
}
|
|
31
34
|
}
|
|
32
35
|
|
|
33
|
-
|
|
34
|
-
* ---------------------------------------------------------
|
|
35
|
-
* WINDOWS
|
|
36
|
-
* ---------------------------------------------------------
|
|
37
|
-
*/
|
|
36
|
+
// for windows
|
|
38
37
|
|
|
39
38
|
async function createWindowsDeviceKey(): Promise<void> {
|
|
40
39
|
const script = `
|
|
@@ -102,21 +101,10 @@ Write-Output "CREATED"
|
|
|
102
101
|
const result = stdout.trim();
|
|
103
102
|
|
|
104
103
|
if (result !== "CREATED" && result !== "EXISTS") {
|
|
105
|
-
throw new Error(
|
|
106
|
-
"Failed to create Windows DS01 device key.",
|
|
107
|
-
);
|
|
104
|
+
throw new Error("Failed to create Windows DS01 device key.");
|
|
108
105
|
}
|
|
109
106
|
}
|
|
110
107
|
|
|
111
|
-
/**
|
|
112
|
-
* Find exactly one valid DS01 certificate.
|
|
113
|
-
*
|
|
114
|
-
* We intentionally do not use Select-Object -First 1.
|
|
115
|
-
*
|
|
116
|
-
* 0 certificates -> error
|
|
117
|
-
* 1 certificate -> use it
|
|
118
|
-
* 2+ certificates -> error
|
|
119
|
-
*/
|
|
120
108
|
function getWindowsCertificateLookup(): string {
|
|
121
109
|
return `
|
|
122
110
|
$certificates = @(Get-ChildItem Cert:\\CurrentUser\\My |
|
|
@@ -138,6 +126,12 @@ $cert = [System.Security.Cryptography.X509Certificates.X509Certificate2]$certifi
|
|
|
138
126
|
`;
|
|
139
127
|
}
|
|
140
128
|
|
|
129
|
+
// cert - just a variable holding the certificate object
|
|
130
|
+
// X509Certificate2 is a Windows/.NET certificate object
|
|
131
|
+
// that gives the code a standard way to work with the certificate and its associated key information
|
|
132
|
+
// X509Certificate2 a class/type, rather than simply a "format"
|
|
133
|
+
|
|
134
|
+
|
|
141
135
|
async function getWindowsPublicKey(): Promise<string> {
|
|
142
136
|
const script = `
|
|
143
137
|
$ErrorActionPreference = "Stop"
|
|
@@ -165,19 +159,14 @@ ${getWindowsCertificateLookup()}
|
|
|
165
159
|
const certificate = stdout.trim();
|
|
166
160
|
|
|
167
161
|
if (!certificate) {
|
|
168
|
-
throw new Error(
|
|
169
|
-
"Unable to read Windows DS01 public certificate.",
|
|
170
|
-
);
|
|
162
|
+
throw new Error("Unable to read Windows DS01 public certificate.");
|
|
171
163
|
}
|
|
172
164
|
|
|
173
165
|
return certificate;
|
|
174
166
|
}
|
|
175
167
|
|
|
176
|
-
async function signWindowsDeviceKey(
|
|
177
|
-
data
|
|
178
|
-
): Promise<Uint8Array> {
|
|
179
|
-
const dataBase64 =
|
|
180
|
-
Buffer.from(data).toString("base64");
|
|
168
|
+
async function signWindowsDeviceKey(data: Uint8Array): Promise<Uint8Array> {
|
|
169
|
+
const dataBase64 = Buffer.from(data).toString("base64");
|
|
181
170
|
|
|
182
171
|
const script = `
|
|
183
172
|
$ErrorActionPreference = "Stop"
|
|
@@ -226,17 +215,16 @@ finally {
|
|
|
226
215
|
},
|
|
227
216
|
);
|
|
228
217
|
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
|
|
229
221
|
const signature = stdout.trim();
|
|
230
222
|
|
|
231
223
|
if (!signature) {
|
|
232
|
-
throw new Error(
|
|
233
|
-
"Failed to sign data with Windows device key.",
|
|
234
|
-
);
|
|
224
|
+
throw new Error("Failed to sign data with Windows device key.");
|
|
235
225
|
}
|
|
236
226
|
|
|
237
|
-
return new Uint8Array(
|
|
238
|
-
Buffer.from(signature, "base64"),
|
|
239
|
-
);
|
|
227
|
+
return new Uint8Array(Buffer.from(signature, "base64"));
|
|
240
228
|
}
|
|
241
229
|
|
|
242
230
|
async function deleteWindowsDeviceKey(): Promise<void> {
|
|
@@ -277,18 +265,10 @@ Write-Output "DELETED"
|
|
|
277
265
|
);
|
|
278
266
|
}
|
|
279
267
|
|
|
280
|
-
|
|
281
|
-
* ---------------------------------------------------------
|
|
282
|
-
* MACOS
|
|
283
|
-
* ---------------------------------------------------------
|
|
284
|
-
*/
|
|
268
|
+
// for Mac
|
|
285
269
|
|
|
286
270
|
async function runMacHelper(
|
|
287
|
-
operation:
|
|
288
|
-
| "create"
|
|
289
|
-
| "public"
|
|
290
|
-
| "sign"
|
|
291
|
-
| "delete",
|
|
271
|
+
operation: "create" | "public" | "sign" | "delete",
|
|
292
272
|
data?: Uint8Array,
|
|
293
273
|
): Promise<string> {
|
|
294
274
|
const helper = getMacHelperPath();
|
|
@@ -296,76 +276,51 @@ async function runMacHelper(
|
|
|
296
276
|
const args = [helper, operation];
|
|
297
277
|
|
|
298
278
|
if (data) {
|
|
299
|
-
args.push(
|
|
300
|
-
Buffer.from(data).toString("base64"),
|
|
301
|
-
);
|
|
279
|
+
args.push(Buffer.from(data).toString("base64"));
|
|
302
280
|
}
|
|
303
281
|
|
|
304
|
-
const { stdout } = await execFileAsync(
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
{
|
|
308
|
-
windowsHide: true,
|
|
309
|
-
},
|
|
310
|
-
);
|
|
282
|
+
const { stdout } = await execFileAsync("swift", args, {
|
|
283
|
+
windowsHide: true,
|
|
284
|
+
});
|
|
311
285
|
|
|
312
286
|
return stdout.trim();
|
|
313
287
|
}
|
|
314
288
|
|
|
315
289
|
async function createMacDeviceKey(): Promise<void> {
|
|
316
|
-
const result =
|
|
317
|
-
await runMacHelper("create");
|
|
290
|
+
const result = await runMacHelper("create");
|
|
318
291
|
|
|
319
|
-
if (
|
|
320
|
-
result !== "CREATED" &&
|
|
321
|
-
result !== "EXISTS"
|
|
322
|
-
) {
|
|
292
|
+
if (result !== "CREATED" && result !== "EXISTS") {
|
|
323
293
|
throw new Error(
|
|
324
|
-
result ||
|
|
325
|
-
"Failed to create macOS Secure Enclave device key.",
|
|
294
|
+
result || "Failed to create macOS Secure Enclave device key.",
|
|
326
295
|
);
|
|
327
296
|
}
|
|
328
297
|
}
|
|
329
298
|
|
|
330
299
|
async function getMacPublicKey(): Promise<string> {
|
|
331
|
-
const result =
|
|
332
|
-
await runMacHelper("public");
|
|
300
|
+
const result = await runMacHelper("public");
|
|
333
301
|
|
|
334
302
|
if (!result) {
|
|
335
|
-
throw new Error(
|
|
336
|
-
"Unable to read macOS DS01 public key.",
|
|
337
|
-
);
|
|
303
|
+
throw new Error("Unable to read macOS DS01 public key.");
|
|
338
304
|
}
|
|
339
305
|
|
|
340
306
|
return result;
|
|
341
307
|
}
|
|
342
308
|
|
|
343
|
-
async function signMacDeviceKey(
|
|
344
|
-
|
|
345
|
-
): Promise<Uint8Array> {
|
|
346
|
-
const result =
|
|
347
|
-
await runMacHelper("sign", data);
|
|
309
|
+
async function signMacDeviceKey(data: Uint8Array): Promise<Uint8Array> {
|
|
310
|
+
const result = await runMacHelper("sign", data);
|
|
348
311
|
|
|
349
312
|
if (!result) {
|
|
350
|
-
throw new Error(
|
|
351
|
-
"Failed to sign data with macOS Secure Enclave key.",
|
|
352
|
-
);
|
|
313
|
+
throw new Error("Failed to sign data with macOS Secure Enclave key.");
|
|
353
314
|
}
|
|
354
315
|
|
|
355
|
-
return new Uint8Array(
|
|
356
|
-
Buffer.from(result, "base64"),
|
|
357
|
-
);
|
|
316
|
+
return new Uint8Array(Buffer.from(result, "base64"));
|
|
358
317
|
}
|
|
359
318
|
|
|
360
319
|
async function deleteMacDeviceKey(): Promise<void> {
|
|
361
320
|
await runMacHelper("delete");
|
|
362
321
|
}
|
|
363
322
|
|
|
364
|
-
|
|
365
|
-
* ---------------------------------------------------------
|
|
366
|
-
* PUBLIC API
|
|
367
|
-
* ---------------------------------------------------------
|
|
368
|
-
*/
|
|
323
|
+
// Public APIs which we use this for creating keys and certificate
|
|
369
324
|
|
|
370
325
|
export async function createDeviceKey(): Promise<void> {
|
|
371
326
|
ensureSupportedOS();
|
|
@@ -388,9 +343,7 @@ export async function getDevicePublicKey(): Promise<string> {
|
|
|
388
343
|
return getMacPublicKey();
|
|
389
344
|
}
|
|
390
345
|
|
|
391
|
-
export async function signWithDeviceKey(
|
|
392
|
-
data: Uint8Array,
|
|
393
|
-
): Promise<Uint8Array> {
|
|
346
|
+
export async function signWithDeviceKey(data: Uint8Array): Promise<Uint8Array> {
|
|
394
347
|
ensureSupportedOS();
|
|
395
348
|
|
|
396
349
|
if (os.platform() === "win32") {
|
|
@@ -409,4 +362,4 @@ export async function deleteDeviceKey(): Promise<void> {
|
|
|
409
362
|
}
|
|
410
363
|
|
|
411
364
|
await deleteMacDeviceKey();
|
|
412
|
-
}
|
|
365
|
+
}
|
package/src/test-key.ts
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
createDeviceKey,
|
|
3
|
-
getDevicePublicKey,
|
|
4
|
-
signWithDeviceKey,
|
|
5
|
-
} from "./utils/secureKeyStore.js";
|
|
6
|
-
|
|
7
|
-
await createDeviceKey();
|
|
8
|
-
|
|
9
|
-
const publicKey = await getDevicePublicKey();
|
|
10
|
-
|
|
11
|
-
console.log("Public key:", publicKey);
|
|
12
|
-
|
|
13
|
-
const message = new TextEncoder().encode("hello DS01");
|
|
14
|
-
|
|
15
|
-
const signature = await signWithDeviceKey(message);
|
|
16
|
-
|
|
17
|
-
console.log(
|
|
18
|
-
"Signature:",
|
|
19
|
-
Buffer.from(signature).toString("base64"),
|
|
20
|
-
);
|
package/src/utils/deviceKey.ts
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import { webcrypto } from "node:crypto";
|
|
2
|
-
|
|
3
|
-
const { subtle } = webcrypto;
|
|
4
|
-
|
|
5
|
-
export async function generateDeviceKeyPair() {
|
|
6
|
-
// Generate an asymmetric key pair:
|
|
7
|
-
// - publicKey → can be shared with the server
|
|
8
|
-
// - privateKey → stays on this device
|
|
9
|
-
const keyPair = await subtle.generateKey(
|
|
10
|
-
{
|
|
11
|
-
name: "ECDSA",
|
|
12
|
-
namedCurve: "P-256",
|
|
13
|
-
},
|
|
14
|
-
false, // 🔐 Private key is NON-EXPORTABLE
|
|
15
|
-
["sign", "verify"],
|
|
16
|
-
);
|
|
17
|
-
|
|
18
|
-
return keyPair;
|
|
19
|
-
}
|