@le-space/orbitdb-identity-provider-webauthn-did 0.0.1 → 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/src/index.js CHANGED
@@ -1,466 +1,35 @@
1
1
  /**
2
2
  * WebAuthn DID Provider for OrbitDB
3
3
  *
4
- * Creates hardware-secured DIDs using WebAuthn biometric authentication
4
+ * Creates hardware-secured DIDs using WebAuthn authentication (Passkey, Yubikey, Ledger, etc.)
5
5
  * Integrates with OrbitDB's identity system while keeping private keys in secure hardware
6
6
  */
7
7
 
8
8
  import { useIdentityProvider } from '@orbitdb/core';
9
-
10
- /**
11
- * WebAuthn DID Provider Core Implementation
12
- */
13
- export class WebAuthnDIDProvider {
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
194
- */
195
- static createDID(credentialInfo) {
196
- // Create a deterministic DID based on the public key coordinates
197
- // This ensures the DID is consistent with the actual key used for signing
198
-
199
- const pubKey = credentialInfo.publicKey;
200
- if (!pubKey || !pubKey.x || !pubKey.y) {
201
- throw new Error('Invalid public key: missing x or y coordinates');
202
- }
203
-
204
- const xHex = Array.from(pubKey.x)
205
- .map(b => b.toString(16).padStart(2, '0'))
206
- .join('');
207
- const yHex = Array.from(pubKey.y)
208
- .map(b => b.toString(16).padStart(2, '0'))
209
- .join('');
210
-
211
- if (!xHex || !yHex) {
212
- throw new Error('Failed to generate hex representation of public key coordinates');
213
- }
214
-
215
- const didSuffix = (xHex + yHex).slice(0, 32);
216
- return `did:webauthn:${didSuffix}`;
217
- }
218
-
219
- /**
220
- * Sign data using WebAuthn (requires biometric authentication)
221
- * Creates a persistent signature that can be verified multiple times
222
- */
223
- async sign(data) {
224
- if (!WebAuthnDIDProvider.isSupported()) {
225
- throw new Error('WebAuthn is not supported in this browser');
226
- }
227
-
228
- try {
229
-
230
- // For OrbitDB compatibility, we need to create a signature that can be verified
231
- // against different data. Since WebAuthn private keys are hardware-secured,
232
- // we'll create a deterministic signature based on our credential and the data.
233
-
234
- const dataBytes = typeof data === 'string' ? new TextEncoder().encode(data) : new Uint8Array(data);
235
-
236
- // Create a deterministic challenge based on the credential ID and data
237
- const combined = new Uint8Array(this.rawCredentialId.length + dataBytes.length);
238
- combined.set(this.rawCredentialId, 0);
239
- combined.set(dataBytes, this.rawCredentialId.length);
240
- const challenge = await crypto.subtle.digest('SHA-256', combined);
241
-
242
- // Use WebAuthn to authenticate (this proves the user is present and verified)
243
- const assertion = await navigator.credentials.get({
244
- publicKey: {
245
- challenge,
246
- allowCredentials: [{
247
- id: this.rawCredentialId,
248
- type: 'public-key'
249
- }],
250
- userVerification: 'required',
251
- timeout: 60000
252
- }
253
- });
254
-
255
- if (!assertion) {
256
- throw new Error('WebAuthn authentication failed');
257
- }
258
-
259
-
260
- // Create a signature that includes the original data and credential proof
261
- // This allows verification without requiring WebAuthn again
262
- const webauthnProof = {
263
- credentialId: this.credentialId,
264
- dataHash: WebAuthnDIDProvider.arrayBufferToBase64url(await crypto.subtle.digest('SHA-256', dataBytes)),
265
- authenticatorData: WebAuthnDIDProvider.arrayBufferToBase64url(assertion.response.authenticatorData),
266
- clientDataJSON: new TextDecoder().decode(assertion.response.clientDataJSON),
267
- timestamp: Date.now()
268
- };
269
-
270
-
271
- // Return the proof as a base64url encoded string for OrbitDB
272
- return WebAuthnDIDProvider.arrayBufferToBase64url(new TextEncoder().encode(JSON.stringify(webauthnProof)));
273
-
274
- } catch (error) {
275
- console.error('WebAuthn signing failed:', error);
276
-
277
- if (error.name === 'NotAllowedError') {
278
- throw new Error('Biometric authentication was cancelled');
279
- } else {
280
- throw new Error(`WebAuthn signing error: ${error.message}`);
281
- }
282
- }
283
- }
284
-
285
- /**
286
- * Verify WebAuthn signature/proof for OrbitDB compatibility
287
- */
288
- async verify(signatureData) {
289
- try {
290
- // Decode the WebAuthn proof object
291
- const proofBytes = WebAuthnDIDProvider.base64urlToArrayBuffer(signatureData);
292
- const proofText = new TextDecoder().decode(proofBytes);
293
- const webauthnProof = JSON.parse(proofText);
294
-
295
- // Verify this proof was created by the same credential
296
- if (webauthnProof.credentialId !== this.credentialId) {
297
- console.warn('Credential ID mismatch in WebAuthn proof verification');
298
- return false;
299
- }
300
-
301
- // For OrbitDB, we need flexible verification that works with different data
302
- // The proof contains the original data hash, so we can verify the proof is valid
303
- // without requiring the exact same data to be passed to verify()
304
-
305
- // Verify the client data indicates a successful WebAuthn authentication
306
- try {
307
- const clientData = JSON.parse(webauthnProof.clientDataJSON);
308
- if (clientData.type !== 'webauthn.get') {
309
- console.warn('Invalid WebAuthn proof type');
310
- return false;
311
- }
312
- } catch {
313
- console.warn('Invalid client data in WebAuthn proof');
314
- return false;
315
- }
316
-
317
- // Verify the proof is recent (within 5 minutes)
318
- const proofAge = Date.now() - webauthnProof.timestamp;
319
- if (proofAge > 5 * 60 * 1000) {
320
- console.warn('WebAuthn proof is too old');
321
- return false;
322
- }
323
-
324
- // Verify the authenticator data is present
325
- if (!webauthnProof.authenticatorData) {
326
- console.warn('Missing authenticator data in WebAuthn proof');
327
- return false;
328
- }
329
-
330
- return true;
331
-
332
- } catch (error) {
333
- console.error('WebAuthn proof verification failed:', error);
334
- return false;
335
- }
336
- }
337
-
338
- /**
339
- * Utility: Convert ArrayBuffer to base64url
340
- */
341
- static arrayBufferToBase64url(buffer) {
342
- const bytes = new Uint8Array(buffer);
343
- let binary = '';
344
- for (let i = 0; i < bytes.byteLength; i++) {
345
- binary += String.fromCharCode(bytes[i]);
346
- }
347
- return btoa(binary)
348
- .replace(/\+/g, '-')
349
- .replace(/\//g, '_')
350
- .replace(/=/g, '');
351
- }
352
-
353
- /**
354
- * Utility: Convert base64url to ArrayBuffer
355
- */
356
- static base64urlToArrayBuffer(base64url) {
357
- const base64 = base64url.replace(/-/g, '+').replace(/_/g, '/');
358
- const binary = atob(base64);
359
- const buffer = new ArrayBuffer(binary.length);
360
- const bytes = new Uint8Array(buffer);
361
- for (let i = 0; i < binary.length; i++) {
362
- bytes[i] = binary.charCodeAt(i);
363
- }
364
- return buffer;
365
- }
366
- }
367
- /**
368
- * OrbitDB Identity Provider that uses WebAuthn
369
- */
370
- export class OrbitDBWebAuthnIdentityProvider {
371
- constructor({ webauthnCredential }) {
372
- this.credential = webauthnCredential;
373
- this.webauthnProvider = new WebAuthnDIDProvider(webauthnCredential);
374
- this.type = 'webauthn'; // Set instance property
375
- }
376
-
377
- static get type() {
378
- return 'webauthn';
379
- }
380
-
381
- getId() {
382
- // Return the proper DID format - this is the identity identifier
383
- // OrbitDB will internally handle the hashing for log entries
384
- return WebAuthnDIDProvider.createDID(this.credential);
385
- }
386
-
387
- signIdentity(data) {
388
- // Return Promise directly to avoid async function issues
389
- return this.webauthnProvider.sign(data);
390
- }
391
-
392
- verifyIdentity(signature, data, publicKey) {
393
- return this.webauthnProvider.verify(signature, data, publicKey || this.credential.publicKey);
394
- }
395
-
396
- /**
397
- * Create OrbitDB identity using WebAuthn
398
- */
399
- static async createIdentity(options) {
400
- const { webauthnCredential } = options;
401
-
402
- const provider = new OrbitDBWebAuthnIdentityProvider({ webauthnCredential });
403
- const id = await provider.getId();
404
-
405
- return {
406
- id,
407
- publicKey: webauthnCredential.publicKey,
408
- type: 'webauthn',
409
- // Make sure sign method is NOT async to avoid Promise serialization
410
- sign: (identity, data) => {
411
- // Return the Promise directly, don't await here
412
- return provider.signIdentity(data);
413
- },
414
- // Make sure verify method is NOT async to avoid Promise serialization
415
- verify: (signature, data) => {
416
- // Return the Promise directly, don't await here
417
- return provider.verifyIdentity(signature, data, webauthnCredential.publicKey);
418
- }
419
- };
420
- }
421
- }
422
-
423
- /**
424
- * WebAuthn Identity Provider Function for OrbitDB
425
- * This follows the same pattern as OrbitDBIdentityProviderDID
426
- * Returns a function that returns a promise resolving to the provider instance
427
- */
428
- export function OrbitDBWebAuthnIdentityProviderFunction(options = {}) {
429
- // Return a function that returns a promise (as expected by OrbitDB)
430
- return async () => {
431
- return new OrbitDBWebAuthnIdentityProvider(options);
432
- };
433
- }
434
-
435
- // Add static methods and properties that OrbitDB expects
436
- OrbitDBWebAuthnIdentityProviderFunction.type = 'webauthn';
437
- OrbitDBWebAuthnIdentityProviderFunction.verifyIdentity = async function(identity) {
438
- try {
439
- // For WebAuthn identities, we need to store the credential info in the identity
440
- // Since WebAuthn verification requires the original credential, not just the public key,
441
- // we'll create a simplified verification that checks the proof structure
442
-
443
-
444
- // For WebAuthn, the identity should have been created with our provider,
445
- // so we can trust it if it has the right structure
446
- // Accept both DID format (did:webauthn:...) and hash format (hex string)
447
- const isValidDID = identity.id && identity.id.startsWith('did:webauthn:');
448
- const isValidHash = identity.id && /^[a-f0-9]{64}$/.test(identity.id); // 64-char hex string
449
-
450
- if (identity.type === 'webauthn' && (isValidDID || isValidHash)) {
451
- return true;
452
- }
453
-
454
- return false;
455
-
456
- } catch (error) {
457
- console.error('WebAuthn static identity verification failed:', error);
458
- return false;
459
- }
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';
23
+
24
+ export {
25
+ WebAuthnDIDProvider,
26
+ OrbitDBWebAuthnIdentityProvider,
27
+ OrbitDBWebAuthnIdentityProviderFunction
460
28
  };
461
29
 
462
30
  /**
463
31
  * Register WebAuthn identity provider with OrbitDB
32
+ * @returns {boolean} True if registration succeeded.
464
33
  */
465
34
  export function registerWebAuthnProvider() {
466
35
  try {
@@ -474,6 +43,7 @@ export function registerWebAuthnProvider() {
474
43
 
475
44
  /**
476
45
  * Check WebAuthn support and provide user-friendly messages
46
+ * @returns {Promise<Object>} Support status and message.
477
47
  */
478
48
  export async function checkWebAuthnSupport() {
479
49
  const support = {
@@ -521,6 +91,7 @@ export function storeWebAuthnCredential(credential, key = 'webauthn-credential')
521
91
  ...credential,
522
92
  rawCredentialId: Array.from(credential.rawCredentialId),
523
93
  attestationObject: Array.from(credential.attestationObject),
94
+ prfInput: credential.prfInput ? Array.from(credential.prfInput) : undefined,
524
95
  publicKey: {
525
96
  ...credential.publicKey,
526
97
  x: Array.from(credential.publicKey.x),
@@ -548,6 +119,7 @@ export function loadWebAuthnCredential(key = 'webauthn-credential') {
548
119
  ...parsed,
549
120
  rawCredentialId: new Uint8Array(parsed.rawCredentialId),
550
121
  attestationObject: new Uint8Array(parsed.attestationObject),
122
+ prfInput: parsed.prfInput ? new Uint8Array(parsed.prfInput) : undefined,
551
123
  publicKey: {
552
124
  ...parsed.publicKey,
553
125
  x: new Uint8Array(parsed.publicKey.x),
@@ -574,6 +146,55 @@ export function clearWebAuthnCredential(key = 'webauthn-credential') {
574
146
  }
575
147
  }
576
148
 
149
+ // Import verification utilities
150
+ import * as VerificationUtils from './verification.js';
151
+
152
+ export {
153
+ // Verification utilities
154
+ VerificationUtils,
155
+ // Keystore encryption utilities
156
+ KeystoreEncryption,
157
+ };
158
+
159
+ export {
160
+ WebAuthnVarsigProvider,
161
+ createWebAuthnVarsigIdentity,
162
+ createWebAuthnVarsigIdentities,
163
+ storeWebAuthnVarsigCredential,
164
+ loadWebAuthnVarsigCredential,
165
+ clearWebAuthnVarsigCredential
166
+ } from './varsig/index.js';
167
+
168
+ // Re-export individual verification functions for convenience
169
+ export {
170
+ verifyDatabaseUpdate,
171
+ verifyIdentityStorage,
172
+ verifyDataEntries,
173
+ isValidWebAuthnDID,
174
+ extractWebAuthnDIDSuffix,
175
+ compareWebAuthnDIDs,
176
+ createVerificationResult
177
+ } from './verification.js';
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
+
577
198
  export default {
578
199
  WebAuthnDIDProvider,
579
200
  OrbitDBWebAuthnIdentityProvider,
@@ -582,5 +203,13 @@ export default {
582
203
  checkWebAuthnSupport,
583
204
  storeWebAuthnCredential,
584
205
  loadWebAuthnCredential,
585
- clearWebAuthnCredential
206
+ clearWebAuthnCredential,
207
+ WebAuthnVarsigProvider,
208
+ createWebAuthnVarsigIdentity,
209
+ createWebAuthnVarsigIdentities,
210
+ storeWebAuthnVarsigCredential,
211
+ loadWebAuthnVarsigCredential,
212
+ clearWebAuthnVarsigCredential,
213
+ // Include verification utilities in default export
214
+ VerificationUtils
586
215
  };