@le-space/orbitdb-identity-provider-webauthn-did 0.1.0 → 0.2.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/README.md +132 -410
- package/package.json +35 -5
- package/src/index.js +58 -503
- package/src/keystore/encryption.js +579 -0
- package/src/keystore/index.js +6 -0
- package/src/keystore/provider.js +555 -0
- package/src/keystore-encryption.js +6 -0
- package/src/varsig/assertion.js +205 -0
- package/src/varsig/credential.js +144 -0
- package/src/varsig/domain.js +11 -0
- package/src/varsig/identity.js +161 -0
- package/src/varsig/index.js +6 -0
- package/src/varsig/provider.js +78 -0
- package/src/varsig/storage.js +46 -0
- package/src/varsig/utils.js +43 -0
- package/src/verification.js +33 -33
- package/src/webauthn/provider.js +542 -0
- package/verification.js +1 -0
package/src/index.js
CHANGED
|
@@ -6,514 +6,30 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import { useIdentityProvider } from '@orbitdb/core';
|
|
9
|
+
import * as KeystoreEncryption from './keystore/encryption.js';
|
|
10
|
+
import { WebAuthnDIDProvider } from './webauthn/provider.js';
|
|
11
|
+
import {
|
|
12
|
+
OrbitDBWebAuthnIdentityProvider,
|
|
13
|
+
OrbitDBWebAuthnIdentityProviderFunction
|
|
14
|
+
} from './keystore/provider.js';
|
|
15
|
+
import {
|
|
16
|
+
WebAuthnVarsigProvider,
|
|
17
|
+
createWebAuthnVarsigIdentity,
|
|
18
|
+
createWebAuthnVarsigIdentities,
|
|
19
|
+
storeWebAuthnVarsigCredential,
|
|
20
|
+
loadWebAuthnVarsigCredential,
|
|
21
|
+
clearWebAuthnVarsigCredential
|
|
22
|
+
} from './varsig/index.js';
|
|
9
23
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
constructor(credentialInfo) {
|
|
15
|
-
this.credentialId = credentialInfo.credentialId;
|
|
16
|
-
this.publicKey = credentialInfo.publicKey;
|
|
17
|
-
this.rawCredentialId = credentialInfo.rawCredentialId;
|
|
18
|
-
this.type = 'webauthn';
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Check if WebAuthn is supported in current browser
|
|
23
|
-
*/
|
|
24
|
-
static isSupported() {
|
|
25
|
-
return window.PublicKeyCredential &&
|
|
26
|
-
typeof window.PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable === 'function';
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Check if platform authenticator (Face ID, Touch ID, Windows Hello) is available
|
|
31
|
-
*/
|
|
32
|
-
static async isPlatformAuthenticatorAvailable() {
|
|
33
|
-
if (!this.isSupported()) return false;
|
|
34
|
-
|
|
35
|
-
try {
|
|
36
|
-
return await window.PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable();
|
|
37
|
-
} catch (error) {
|
|
38
|
-
console.warn('Failed to check platform authenticator availability:', error);
|
|
39
|
-
return false;
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* Create a WebAuthn credential for OrbitDB identity
|
|
45
|
-
* This triggers biometric authentication (Face ID, Touch ID, Windows Hello, etc.)
|
|
46
|
-
*/
|
|
47
|
-
static async createCredential(options = {}) {
|
|
48
|
-
const { userId, displayName, domain } = {
|
|
49
|
-
userId: `orbitdb-user-${Date.now()}`,
|
|
50
|
-
displayName: 'Local-First Peer-to-Peer OrbitDB User',
|
|
51
|
-
domain: window.location.hostname,
|
|
52
|
-
...options
|
|
53
|
-
};
|
|
54
|
-
|
|
55
|
-
if (!this.isSupported()) {
|
|
56
|
-
throw new Error('WebAuthn is not supported in this browser');
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
// Generate challenge for credential creation
|
|
60
|
-
const challenge = crypto.getRandomValues(new Uint8Array(32));
|
|
61
|
-
const userIdBytes = new TextEncoder().encode(userId);
|
|
62
|
-
|
|
63
|
-
try {
|
|
64
|
-
const credential = await navigator.credentials.create({
|
|
65
|
-
publicKey: {
|
|
66
|
-
challenge,
|
|
67
|
-
rp: {
|
|
68
|
-
name: 'OrbitDB Identity',
|
|
69
|
-
id: domain
|
|
70
|
-
},
|
|
71
|
-
user: {
|
|
72
|
-
id: userIdBytes,
|
|
73
|
-
name: userId,
|
|
74
|
-
displayName
|
|
75
|
-
},
|
|
76
|
-
pubKeyCredParams: [
|
|
77
|
-
{ alg: -7, type: 'public-key' }, // ES256 (P-256 curve)
|
|
78
|
-
{ alg: -257, type: 'public-key' } // RS256 fallback
|
|
79
|
-
],
|
|
80
|
-
authenticatorSelection: {
|
|
81
|
-
authenticatorAttachment: 'platform', // Prefer built-in authenticators
|
|
82
|
-
requireResidentKey: false,
|
|
83
|
-
residentKey: 'preferred',
|
|
84
|
-
userVerification: 'required' // Require biometric/PIN
|
|
85
|
-
},
|
|
86
|
-
timeout: 60000,
|
|
87
|
-
attestation: 'none' // Don't need attestation for DID creation
|
|
88
|
-
}
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
if (!credential) {
|
|
92
|
-
throw new Error('Failed to create WebAuthn credential');
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
console.log('✅ WebAuthn credential created successfully, extracting public key...');
|
|
96
|
-
|
|
97
|
-
// Extract public key from credential with timeout
|
|
98
|
-
const publicKey = await Promise.race([
|
|
99
|
-
this.extractPublicKey(credential),
|
|
100
|
-
new Promise((_, reject) => setTimeout(() => reject(new Error('Public key extraction timeout')), 10000))
|
|
101
|
-
]);
|
|
102
|
-
|
|
103
|
-
const result = {
|
|
104
|
-
credentialId: WebAuthnDIDProvider.arrayBufferToBase64url(credential.rawId),
|
|
105
|
-
rawCredentialId: new Uint8Array(credential.rawId),
|
|
106
|
-
publicKey,
|
|
107
|
-
userId,
|
|
108
|
-
displayName,
|
|
109
|
-
attestationObject: new Uint8Array(credential.response.attestationObject)
|
|
110
|
-
};
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
return result;
|
|
114
|
-
|
|
115
|
-
} catch (error) {
|
|
116
|
-
console.error('WebAuthn credential creation failed:', error);
|
|
117
|
-
|
|
118
|
-
// Provide user-friendly error messages
|
|
119
|
-
if (error.name === 'NotAllowedError') {
|
|
120
|
-
throw new Error('Biometric authentication was cancelled or failed');
|
|
121
|
-
} else if (error.name === 'InvalidStateError') {
|
|
122
|
-
throw new Error('A credential with this ID already exists');
|
|
123
|
-
} else if (error.name === 'NotSupportedError') {
|
|
124
|
-
throw new Error('WebAuthn is not supported on this device');
|
|
125
|
-
} else {
|
|
126
|
-
throw new Error(`WebAuthn error: ${error.message}`);
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
/**
|
|
132
|
-
* Extract P-256 public key from WebAuthn credential
|
|
133
|
-
* Parses the CBOR attestation object to get the real public key
|
|
134
|
-
*/
|
|
135
|
-
static async extractPublicKey(credential) {
|
|
136
|
-
try {
|
|
137
|
-
// Import CBOR decoder for parsing attestation object
|
|
138
|
-
const { decode } = await import('cbor-web');
|
|
139
|
-
|
|
140
|
-
const attestationObject = decode(new Uint8Array(credential.response.attestationObject));
|
|
141
|
-
const authData = attestationObject.authData;
|
|
142
|
-
|
|
143
|
-
// Parse authenticator data structure
|
|
144
|
-
// Skip: rpIdHash (32 bytes) + flags (1 byte) + signCount (4 bytes)
|
|
145
|
-
const credentialDataStart = 32 + 1 + 4 + 16 + 2; // +16 for AAGUID, +2 for credentialIdLength
|
|
146
|
-
const credentialIdLength = new DataView(authData.buffer, 32 + 1 + 4 + 16, 2).getUint16(0);
|
|
147
|
-
const publicKeyDataStart = credentialDataStart + credentialIdLength;
|
|
148
|
-
|
|
149
|
-
// Extract and decode the public key (CBOR format)
|
|
150
|
-
const publicKeyData = authData.slice(publicKeyDataStart);
|
|
151
|
-
const publicKeyObject = decode(publicKeyData);
|
|
152
|
-
|
|
153
|
-
// Extract P-256 coordinates (COSE key format)
|
|
154
|
-
return {
|
|
155
|
-
algorithm: publicKeyObject[3], // alg parameter
|
|
156
|
-
x: new Uint8Array(publicKeyObject[-2]), // x coordinate
|
|
157
|
-
y: new Uint8Array(publicKeyObject[-3]), // y coordinate
|
|
158
|
-
keyType: publicKeyObject[1], // kty parameter
|
|
159
|
-
curve: publicKeyObject[-1] // crv parameter
|
|
160
|
-
};
|
|
161
|
-
|
|
162
|
-
} catch (error) {
|
|
163
|
-
console.warn('Failed to extract real public key from WebAuthn credential, using fallback:', error);
|
|
164
|
-
|
|
165
|
-
// Fallback: Create deterministic public key from credential ID
|
|
166
|
-
// This ensures the SAME public key is generated every time for the same credential
|
|
167
|
-
const credentialId = new Uint8Array(credential.rawId);
|
|
168
|
-
|
|
169
|
-
const hash = await crypto.subtle.digest('SHA-256', credentialId);
|
|
170
|
-
const seed = new Uint8Array(hash);
|
|
171
|
-
|
|
172
|
-
// Create a second hash for the y coordinate to ensure uniqueness but determinism
|
|
173
|
-
const yData = new Uint8Array(credentialId.length + 4);
|
|
174
|
-
yData.set(credentialId, 0);
|
|
175
|
-
yData.set([0x59, 0x43, 0x4F, 0x4F], credentialId.length); // "YCOO" marker
|
|
176
|
-
const yHash = await crypto.subtle.digest('SHA-256', yData);
|
|
177
|
-
const ySeed = new Uint8Array(yHash);
|
|
178
|
-
|
|
179
|
-
const fallbackKey = {
|
|
180
|
-
algorithm: -7, // ES256
|
|
181
|
-
x: seed.slice(0, 32), // Use first 32 bytes as x coordinate
|
|
182
|
-
y: ySeed.slice(0, 32), // Deterministic y coordinate based on credential
|
|
183
|
-
keyType: 2, // EC2 key type
|
|
184
|
-
curve: 1 // P-256 curve
|
|
185
|
-
};
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
return fallbackKey;
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
/**
|
|
193
|
-
* Generate DID from WebAuthn credential using did:key format for P-256 keys
|
|
194
|
-
* This ensures compatibility with ucanto and other DID:key implementations
|
|
195
|
-
*/
|
|
196
|
-
static async createDID(credentialInfo) {
|
|
197
|
-
const pubKey = credentialInfo.publicKey;
|
|
198
|
-
if (!pubKey || !pubKey.x || !pubKey.y) {
|
|
199
|
-
throw new Error('Invalid public key: missing x or y coordinates');
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
try {
|
|
203
|
-
// Import multiformats modules with correct exports
|
|
204
|
-
const multiformats = await import('multiformats');
|
|
205
|
-
const varint = multiformats.varint;
|
|
206
|
-
const { base58btc } = await import('multiformats/bases/base58');
|
|
207
|
-
|
|
208
|
-
const x = new Uint8Array(pubKey.x);
|
|
209
|
-
const y = new Uint8Array(pubKey.y);
|
|
210
|
-
|
|
211
|
-
// Determine compression flag based on y coordinate parity
|
|
212
|
-
const yLastByte = y[y.length - 1];
|
|
213
|
-
const compressionFlag = (yLastByte & 1) === 0 ? 0x02 : 0x03;
|
|
214
|
-
|
|
215
|
-
// Create compressed public key: compression_flag + x_coordinate (33 bytes total)
|
|
216
|
-
const compressedPubKey = new Uint8Array(33);
|
|
217
|
-
compressedPubKey[0] = compressionFlag;
|
|
218
|
-
compressedPubKey.set(x, 1);
|
|
219
|
-
|
|
220
|
-
// P-256 multicodec code (0x1200)
|
|
221
|
-
const P256_MULTICODEC = 0x1200;
|
|
222
|
-
const codecLength = varint.encodingLength(P256_MULTICODEC);
|
|
223
|
-
const codecBytes = new Uint8Array(codecLength);
|
|
224
|
-
varint.encodeTo(P256_MULTICODEC, codecBytes, 0);
|
|
225
|
-
|
|
226
|
-
if (codecBytes.length === 0) {
|
|
227
|
-
throw new Error('Failed to encode P256_MULTICODEC with varint');
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
// Combine multicodec prefix + compressed public key
|
|
231
|
-
const multikey = new Uint8Array(codecBytes.length + compressedPubKey.length);
|
|
232
|
-
multikey.set(codecBytes, 0);
|
|
233
|
-
multikey.set(compressedPubKey, codecBytes.length);
|
|
234
|
-
|
|
235
|
-
// Encode as base58btc and create did:key
|
|
236
|
-
const multikeyEncoded = base58btc.encode(multikey);
|
|
237
|
-
return `did:key:${multikeyEncoded}`;
|
|
238
|
-
|
|
239
|
-
} catch (error) {
|
|
240
|
-
console.error('Failed to create proper did:key format, using fallback:', error);
|
|
241
|
-
|
|
242
|
-
// Fallback: create a deterministic did:key using simplified encoding
|
|
243
|
-
const x = new Uint8Array(pubKey.x);
|
|
244
|
-
const y = new Uint8Array(pubKey.y);
|
|
245
|
-
|
|
246
|
-
// Create a hash-based approach for consistency
|
|
247
|
-
const combined = new Uint8Array(x.length + y.length);
|
|
248
|
-
combined.set(x, 0);
|
|
249
|
-
combined.set(y, x.length);
|
|
250
|
-
|
|
251
|
-
// Simple base58-like encoding for fallback
|
|
252
|
-
const base58Chars = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz';
|
|
253
|
-
let encoded = 'z'; // base58btc prefix
|
|
254
|
-
|
|
255
|
-
for (let i = 0; i < Math.min(combined.length, 32); i += 4) {
|
|
256
|
-
const chunk = combined.slice(i, i + 4);
|
|
257
|
-
let value = 0;
|
|
258
|
-
for (let j = 0; j < chunk.length; j++) {
|
|
259
|
-
value = value * 256 + chunk[j];
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
for (let k = 0; k < 6; k++) {
|
|
263
|
-
encoded += base58Chars[value % 58];
|
|
264
|
-
value = Math.floor(value / 58);
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
return `did:key:${encoded}`;
|
|
269
|
-
}
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
/**
|
|
273
|
-
* Sign data using WebAuthn (requires biometric authentication)
|
|
274
|
-
* Creates a persistent signature that can be verified multiple times
|
|
275
|
-
*/
|
|
276
|
-
async sign(data) {
|
|
277
|
-
if (!WebAuthnDIDProvider.isSupported()) {
|
|
278
|
-
throw new Error('WebAuthn is not supported in this browser');
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
try {
|
|
282
|
-
|
|
283
|
-
// For OrbitDB compatibility, we need to create a signature that can be verified
|
|
284
|
-
// against different data. Since WebAuthn private keys are hardware-secured,
|
|
285
|
-
// we'll create a deterministic signature based on our credential and the data.
|
|
286
|
-
|
|
287
|
-
const dataBytes = typeof data === 'string' ? new TextEncoder().encode(data) : new Uint8Array(data);
|
|
288
|
-
|
|
289
|
-
// Create a deterministic challenge based on the credential ID and data
|
|
290
|
-
const combined = new Uint8Array(this.rawCredentialId.length + dataBytes.length);
|
|
291
|
-
combined.set(this.rawCredentialId, 0);
|
|
292
|
-
combined.set(dataBytes, this.rawCredentialId.length);
|
|
293
|
-
const challenge = await crypto.subtle.digest('SHA-256', combined);
|
|
294
|
-
|
|
295
|
-
// Use WebAuthn to authenticate (this proves the user is present and verified)
|
|
296
|
-
const assertion = await navigator.credentials.get({
|
|
297
|
-
publicKey: {
|
|
298
|
-
challenge,
|
|
299
|
-
allowCredentials: [{
|
|
300
|
-
id: this.rawCredentialId,
|
|
301
|
-
type: 'public-key'
|
|
302
|
-
}],
|
|
303
|
-
userVerification: 'required',
|
|
304
|
-
timeout: 60000
|
|
305
|
-
}
|
|
306
|
-
});
|
|
307
|
-
|
|
308
|
-
if (!assertion) {
|
|
309
|
-
throw new Error('WebAuthn authentication failed');
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
// Create a signature that includes the original data and credential proof
|
|
314
|
-
// This allows verification without requiring WebAuthn again
|
|
315
|
-
const webauthnProof = {
|
|
316
|
-
credentialId: this.credentialId,
|
|
317
|
-
dataHash: WebAuthnDIDProvider.arrayBufferToBase64url(await crypto.subtle.digest('SHA-256', dataBytes)),
|
|
318
|
-
authenticatorData: WebAuthnDIDProvider.arrayBufferToBase64url(assertion.response.authenticatorData),
|
|
319
|
-
clientDataJSON: new TextDecoder().decode(assertion.response.clientDataJSON),
|
|
320
|
-
timestamp: Date.now()
|
|
321
|
-
};
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
// Return the proof as a base64url encoded string for OrbitDB
|
|
325
|
-
return WebAuthnDIDProvider.arrayBufferToBase64url(new TextEncoder().encode(JSON.stringify(webauthnProof)));
|
|
326
|
-
|
|
327
|
-
} catch (error) {
|
|
328
|
-
console.error('WebAuthn signing failed:', error);
|
|
329
|
-
|
|
330
|
-
if (error.name === 'NotAllowedError') {
|
|
331
|
-
throw new Error('Biometric authentication was cancelled');
|
|
332
|
-
} else {
|
|
333
|
-
throw new Error(`WebAuthn signing error: ${error.message}`);
|
|
334
|
-
}
|
|
335
|
-
}
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
/**
|
|
339
|
-
* Verify WebAuthn signature/proof for OrbitDB compatibility
|
|
340
|
-
*/
|
|
341
|
-
async verify(signatureData) {
|
|
342
|
-
try {
|
|
343
|
-
// Decode the WebAuthn proof object
|
|
344
|
-
const proofBytes = WebAuthnDIDProvider.base64urlToArrayBuffer(signatureData);
|
|
345
|
-
const proofText = new TextDecoder().decode(proofBytes);
|
|
346
|
-
const webauthnProof = JSON.parse(proofText);
|
|
347
|
-
|
|
348
|
-
// Verify this proof was created by the same credential
|
|
349
|
-
if (webauthnProof.credentialId !== this.credentialId) {
|
|
350
|
-
console.warn('Credential ID mismatch in WebAuthn proof verification');
|
|
351
|
-
return false;
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
// For OrbitDB, we need flexible verification that works with different data
|
|
355
|
-
// The proof contains the original data hash, so we can verify the proof is valid
|
|
356
|
-
// without requiring the exact same data to be passed to verify()
|
|
357
|
-
|
|
358
|
-
// Verify the client data indicates a successful WebAuthn authentication
|
|
359
|
-
try {
|
|
360
|
-
const clientData = JSON.parse(webauthnProof.clientDataJSON);
|
|
361
|
-
if (clientData.type !== 'webauthn.get') {
|
|
362
|
-
console.warn('Invalid WebAuthn proof type');
|
|
363
|
-
return false;
|
|
364
|
-
}
|
|
365
|
-
} catch {
|
|
366
|
-
console.warn('Invalid client data in WebAuthn proof');
|
|
367
|
-
return false;
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
// Verify the proof is recent (within 5 minutes)
|
|
371
|
-
const proofAge = Date.now() - webauthnProof.timestamp;
|
|
372
|
-
if (proofAge > 5 * 60 * 1000) {
|
|
373
|
-
console.warn('WebAuthn proof is too old');
|
|
374
|
-
return false;
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
// Verify the authenticator data is present
|
|
378
|
-
if (!webauthnProof.authenticatorData) {
|
|
379
|
-
console.warn('Missing authenticator data in WebAuthn proof');
|
|
380
|
-
return false;
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
return true;
|
|
384
|
-
|
|
385
|
-
} catch (error) {
|
|
386
|
-
console.error('WebAuthn proof verification failed:', error);
|
|
387
|
-
return false;
|
|
388
|
-
}
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
/**
|
|
392
|
-
* Utility: Convert ArrayBuffer to base64url
|
|
393
|
-
*/
|
|
394
|
-
static arrayBufferToBase64url(buffer) {
|
|
395
|
-
const bytes = new Uint8Array(buffer);
|
|
396
|
-
let binary = '';
|
|
397
|
-
for (let i = 0; i < bytes.byteLength; i++) {
|
|
398
|
-
binary += String.fromCharCode(bytes[i]);
|
|
399
|
-
}
|
|
400
|
-
return btoa(binary)
|
|
401
|
-
.replace(/\+/g, '-')
|
|
402
|
-
.replace(/\//g, '_')
|
|
403
|
-
.replace(/=/g, '');
|
|
404
|
-
}
|
|
405
|
-
|
|
406
|
-
/**
|
|
407
|
-
* Utility: Convert base64url to ArrayBuffer
|
|
408
|
-
*/
|
|
409
|
-
static base64urlToArrayBuffer(base64url) {
|
|
410
|
-
const base64 = base64url.replace(/-/g, '+').replace(/_/g, '/');
|
|
411
|
-
const binary = atob(base64);
|
|
412
|
-
const buffer = new ArrayBuffer(binary.length);
|
|
413
|
-
const bytes = new Uint8Array(buffer);
|
|
414
|
-
for (let i = 0; i < binary.length; i++) {
|
|
415
|
-
bytes[i] = binary.charCodeAt(i);
|
|
416
|
-
}
|
|
417
|
-
return buffer;
|
|
418
|
-
}
|
|
419
|
-
}
|
|
420
|
-
/**
|
|
421
|
-
* OrbitDB Identity Provider that uses WebAuthn
|
|
422
|
-
*/
|
|
423
|
-
export class OrbitDBWebAuthnIdentityProvider {
|
|
424
|
-
constructor({ webauthnCredential }) {
|
|
425
|
-
this.credential = webauthnCredential;
|
|
426
|
-
this.webauthnProvider = new WebAuthnDIDProvider(webauthnCredential);
|
|
427
|
-
this.type = 'webauthn'; // Set instance property
|
|
428
|
-
}
|
|
429
|
-
|
|
430
|
-
static get type() {
|
|
431
|
-
return 'webauthn';
|
|
432
|
-
}
|
|
433
|
-
|
|
434
|
-
async getId() {
|
|
435
|
-
// Return the proper DID format - this is the identity identifier
|
|
436
|
-
// OrbitDB will internally handle the hashing for log entries
|
|
437
|
-
return await WebAuthnDIDProvider.createDID(this.credential);
|
|
438
|
-
}
|
|
439
|
-
|
|
440
|
-
signIdentity(data) {
|
|
441
|
-
// Return Promise directly to avoid async function issues
|
|
442
|
-
return this.webauthnProvider.sign(data);
|
|
443
|
-
}
|
|
444
|
-
|
|
445
|
-
verifyIdentity(signature, data, publicKey) {
|
|
446
|
-
return this.webauthnProvider.verify(signature, data, publicKey || this.credential.publicKey);
|
|
447
|
-
}
|
|
448
|
-
|
|
449
|
-
/**
|
|
450
|
-
* Create OrbitDB identity using WebAuthn
|
|
451
|
-
*/
|
|
452
|
-
static async createIdentity(options) {
|
|
453
|
-
const { webauthnCredential } = options;
|
|
454
|
-
|
|
455
|
-
const provider = new OrbitDBWebAuthnIdentityProvider({ webauthnCredential });
|
|
456
|
-
const id = await provider.getId();
|
|
457
|
-
|
|
458
|
-
return {
|
|
459
|
-
id,
|
|
460
|
-
publicKey: webauthnCredential.publicKey,
|
|
461
|
-
type: 'webauthn',
|
|
462
|
-
// Make sure sign method is NOT async to avoid Promise serialization
|
|
463
|
-
sign: (identity, data) => {
|
|
464
|
-
// Return the Promise directly, don't await here
|
|
465
|
-
return provider.signIdentity(data);
|
|
466
|
-
},
|
|
467
|
-
// Make sure verify method is NOT async to avoid Promise serialization
|
|
468
|
-
verify: (signature, data) => {
|
|
469
|
-
// Return the Promise directly, don't await here
|
|
470
|
-
return provider.verifyIdentity(signature, data, webauthnCredential.publicKey);
|
|
471
|
-
}
|
|
472
|
-
};
|
|
473
|
-
}
|
|
474
|
-
}
|
|
475
|
-
|
|
476
|
-
/**
|
|
477
|
-
* WebAuthn Identity Provider Function for OrbitDB
|
|
478
|
-
* This follows the same pattern as OrbitDBIdentityProviderDID
|
|
479
|
-
* Returns a function that returns a promise resolving to the provider instance
|
|
480
|
-
*/
|
|
481
|
-
export function OrbitDBWebAuthnIdentityProviderFunction(options = {}) {
|
|
482
|
-
// Return a function that returns a promise (as expected by OrbitDB)
|
|
483
|
-
return async () => {
|
|
484
|
-
return new OrbitDBWebAuthnIdentityProvider(options);
|
|
485
|
-
};
|
|
486
|
-
}
|
|
487
|
-
|
|
488
|
-
// Add static methods and properties that OrbitDB expects
|
|
489
|
-
OrbitDBWebAuthnIdentityProviderFunction.type = 'webauthn';
|
|
490
|
-
OrbitDBWebAuthnIdentityProviderFunction.verifyIdentity = async function(identity) {
|
|
491
|
-
try {
|
|
492
|
-
// For WebAuthn identities, we need to store the credential info in the identity
|
|
493
|
-
// Since WebAuthn verification requires the original credential, not just the public key,
|
|
494
|
-
// we'll create a simplified verification that checks the proof structure
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
// For WebAuthn, the identity should have been created with our provider,
|
|
498
|
-
// so we can trust it if it has the right structure
|
|
499
|
-
// Accept both DID format (did:key:...) and hash format (hex string) for backward compatibility
|
|
500
|
-
const isValidDID = identity.id && identity.id.startsWith('did:key:');
|
|
501
|
-
const isValidHash = identity.id && /^[a-f0-9]{64}$/.test(identity.id); // 64-char hex string (legacy)
|
|
502
|
-
|
|
503
|
-
if (identity.type === 'webauthn' && (isValidDID || isValidHash)) {
|
|
504
|
-
return true;
|
|
505
|
-
}
|
|
506
|
-
|
|
507
|
-
return false;
|
|
508
|
-
|
|
509
|
-
} catch (error) {
|
|
510
|
-
console.error('WebAuthn static identity verification failed:', error);
|
|
511
|
-
return false;
|
|
512
|
-
}
|
|
24
|
+
export {
|
|
25
|
+
WebAuthnDIDProvider,
|
|
26
|
+
OrbitDBWebAuthnIdentityProvider,
|
|
27
|
+
OrbitDBWebAuthnIdentityProviderFunction
|
|
513
28
|
};
|
|
514
29
|
|
|
515
30
|
/**
|
|
516
31
|
* Register WebAuthn identity provider with OrbitDB
|
|
32
|
+
* @returns {boolean} True if registration succeeded.
|
|
517
33
|
*/
|
|
518
34
|
export function registerWebAuthnProvider() {
|
|
519
35
|
try {
|
|
@@ -527,6 +43,7 @@ export function registerWebAuthnProvider() {
|
|
|
527
43
|
|
|
528
44
|
/**
|
|
529
45
|
* Check WebAuthn support and provide user-friendly messages
|
|
46
|
+
* @returns {Promise<Object>} Support status and message.
|
|
530
47
|
*/
|
|
531
48
|
export async function checkWebAuthnSupport() {
|
|
532
49
|
const support = {
|
|
@@ -574,6 +91,7 @@ export function storeWebAuthnCredential(credential, key = 'webauthn-credential')
|
|
|
574
91
|
...credential,
|
|
575
92
|
rawCredentialId: Array.from(credential.rawCredentialId),
|
|
576
93
|
attestationObject: Array.from(credential.attestationObject),
|
|
94
|
+
prfInput: credential.prfInput ? Array.from(credential.prfInput) : undefined,
|
|
577
95
|
publicKey: {
|
|
578
96
|
...credential.publicKey,
|
|
579
97
|
x: Array.from(credential.publicKey.x),
|
|
@@ -601,6 +119,7 @@ export function loadWebAuthnCredential(key = 'webauthn-credential') {
|
|
|
601
119
|
...parsed,
|
|
602
120
|
rawCredentialId: new Uint8Array(parsed.rawCredentialId),
|
|
603
121
|
attestationObject: new Uint8Array(parsed.attestationObject),
|
|
122
|
+
prfInput: parsed.prfInput ? new Uint8Array(parsed.prfInput) : undefined,
|
|
604
123
|
publicKey: {
|
|
605
124
|
...parsed.publicKey,
|
|
606
125
|
x: new Uint8Array(parsed.publicKey.x),
|
|
@@ -633,8 +152,19 @@ import * as VerificationUtils from './verification.js';
|
|
|
633
152
|
export {
|
|
634
153
|
// Verification utilities
|
|
635
154
|
VerificationUtils,
|
|
155
|
+
// Keystore encryption utilities
|
|
156
|
+
KeystoreEncryption,
|
|
636
157
|
};
|
|
637
158
|
|
|
159
|
+
export {
|
|
160
|
+
WebAuthnVarsigProvider,
|
|
161
|
+
createWebAuthnVarsigIdentity,
|
|
162
|
+
createWebAuthnVarsigIdentities,
|
|
163
|
+
storeWebAuthnVarsigCredential,
|
|
164
|
+
loadWebAuthnVarsigCredential,
|
|
165
|
+
clearWebAuthnVarsigCredential
|
|
166
|
+
} from './varsig/index.js';
|
|
167
|
+
|
|
638
168
|
// Re-export individual verification functions for convenience
|
|
639
169
|
export {
|
|
640
170
|
verifyDatabaseUpdate,
|
|
@@ -646,6 +176,25 @@ export {
|
|
|
646
176
|
createVerificationResult
|
|
647
177
|
} from './verification.js';
|
|
648
178
|
|
|
179
|
+
// Re-export individual keystore encryption functions for convenience
|
|
180
|
+
export {
|
|
181
|
+
generateSecretKey,
|
|
182
|
+
encryptWithAESGCM,
|
|
183
|
+
decryptWithAESGCM,
|
|
184
|
+
addLargeBlobToCredentialOptions,
|
|
185
|
+
addPRFToCredentialOptions,
|
|
186
|
+
retrieveSKFromLargeBlob,
|
|
187
|
+
addHmacSecretToCredentialOptions,
|
|
188
|
+
wrapSKWithPRF,
|
|
189
|
+
unwrapSKWithPRF,
|
|
190
|
+
wrapSKWithHmacSecret,
|
|
191
|
+
unwrapSKWithHmacSecret,
|
|
192
|
+
storeEncryptedKeystore,
|
|
193
|
+
loadEncryptedKeystore,
|
|
194
|
+
clearEncryptedKeystore,
|
|
195
|
+
checkExtensionSupport
|
|
196
|
+
} from './keystore/encryption.js';
|
|
197
|
+
|
|
649
198
|
export default {
|
|
650
199
|
WebAuthnDIDProvider,
|
|
651
200
|
OrbitDBWebAuthnIdentityProvider,
|
|
@@ -655,6 +204,12 @@ export default {
|
|
|
655
204
|
storeWebAuthnCredential,
|
|
656
205
|
loadWebAuthnCredential,
|
|
657
206
|
clearWebAuthnCredential,
|
|
207
|
+
WebAuthnVarsigProvider,
|
|
208
|
+
createWebAuthnVarsigIdentity,
|
|
209
|
+
createWebAuthnVarsigIdentities,
|
|
210
|
+
storeWebAuthnVarsigCredential,
|
|
211
|
+
loadWebAuthnVarsigCredential,
|
|
212
|
+
clearWebAuthnVarsigCredential,
|
|
658
213
|
// Include verification utilities in default export
|
|
659
214
|
VerificationUtils
|
|
660
215
|
};
|