@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
|
@@ -0,0 +1,555 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OrbitDB Identity Provider for WebAuthn + Keystore
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { logger } from '@libp2p/logger';
|
|
6
|
+
import { generateKeyPair, privateKeyFromRaw } from '@libp2p/crypto/keys';
|
|
7
|
+
import * as KeystoreEncryption from './encryption.js';
|
|
8
|
+
import { WebAuthnDIDProvider } from '../webauthn/provider.js';
|
|
9
|
+
|
|
10
|
+
const identityLog = logger('orbitdb-identity-provider-webauthn-did:identity');
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* OrbitDB Identity Provider that uses WebAuthn
|
|
14
|
+
*/
|
|
15
|
+
export class OrbitDBWebAuthnIdentityProvider {
|
|
16
|
+
/**
|
|
17
|
+
* @param {Object} options - Provider configuration.
|
|
18
|
+
* @param {Object} options.webauthnCredential - WebAuthn credential info.
|
|
19
|
+
* @param {boolean} [options.useKeystoreDID=false] - Use keystore DID instead of WebAuthn DID.
|
|
20
|
+
* @param {Object|null} [options.keystore=null] - OrbitDB keystore instance.
|
|
21
|
+
* @param {string} [options.keystoreKeyType='secp256k1'] - Keystore key type.
|
|
22
|
+
* @param {boolean} [options.encryptKeystore=false] - Encrypt keystore at rest.
|
|
23
|
+
* @param {string} [options.keystoreEncryptionMethod='prf'] - Encryption method.
|
|
24
|
+
*/
|
|
25
|
+
constructor({
|
|
26
|
+
webauthnCredential,
|
|
27
|
+
useKeystoreDID = false,
|
|
28
|
+
keystore = null,
|
|
29
|
+
keystoreKeyType = 'secp256k1',
|
|
30
|
+
encryptKeystore = false,
|
|
31
|
+
keystoreEncryptionMethod = 'prf'
|
|
32
|
+
}) {
|
|
33
|
+
this.credential = webauthnCredential;
|
|
34
|
+
this.webauthnProvider = new WebAuthnDIDProvider(webauthnCredential);
|
|
35
|
+
this.type = 'webauthn'; // Set instance property
|
|
36
|
+
this.useKeystoreDID = useKeystoreDID; // Flag to use Ed25519 DID from keystore
|
|
37
|
+
this.keystore = keystore; // OrbitDB keystore instance
|
|
38
|
+
this.keystoreKeyType = keystoreKeyType; // Key type: 'secp256k1' or 'Ed25519'
|
|
39
|
+
this.encryptKeystore = encryptKeystore; // Flag to encrypt keystore
|
|
40
|
+
this.keystoreEncryptionMethod = keystoreEncryptionMethod; // Encryption method
|
|
41
|
+
this.unlockedKeypair = null; // Store unlocked keypair during session
|
|
42
|
+
this.unlockedPrivateKey = null;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
static get type() {
|
|
46
|
+
return 'webauthn';
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Resolve the identity DID.
|
|
51
|
+
* @returns {Promise<string>} DID string.
|
|
52
|
+
*/
|
|
53
|
+
async getId() {
|
|
54
|
+
identityLog('getId() called');
|
|
55
|
+
|
|
56
|
+
// If useKeystoreDID flag is set, create Ed25519 DID from keystore
|
|
57
|
+
if (this.useKeystoreDID && this.keystore) {
|
|
58
|
+
if (this.encryptKeystore) {
|
|
59
|
+
await this.ensureEncryptedKeystore();
|
|
60
|
+
}
|
|
61
|
+
identityLog('Using Ed25519 DID from keystore');
|
|
62
|
+
const did = await this.createEd25519DIDFromKeystore();
|
|
63
|
+
identityLog('getId() returning Ed25519 DID: %s', did.substring(0, 32) + '...');
|
|
64
|
+
return did;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Default: Return P-256 DID from WebAuthn credential
|
|
68
|
+
const did = await WebAuthnDIDProvider.createDID(this.credential);
|
|
69
|
+
identityLog('getId() returning P-256 DID: %s', did.substring(0, 32) + '...');
|
|
70
|
+
return did;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
async ensureEncryptedKeystore() {
|
|
74
|
+
if (this.unlockedKeypair) {
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
try {
|
|
79
|
+
await KeystoreEncryption.loadEncryptedKeystore(this.credential.credentialId);
|
|
80
|
+
} catch (error) {
|
|
81
|
+
identityLog('Encrypted keystore missing, creating a new one: %s', error.message);
|
|
82
|
+
await this.createEncryptedKeystore();
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
await this.unlockEncryptedKeystore();
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Create Ed25519 DID from OrbitDB keystore
|
|
90
|
+
* This uses the keystore's Ed25519 key to create a did:key DID
|
|
91
|
+
*/
|
|
92
|
+
async createEd25519DIDFromKeystore() {
|
|
93
|
+
if (!this.keystore) {
|
|
94
|
+
throw new Error('Keystore is required to create Ed25519 DID');
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
try {
|
|
98
|
+
// Import multiformats modules
|
|
99
|
+
const multiformats = await import('multiformats');
|
|
100
|
+
const varint = multiformats.varint;
|
|
101
|
+
const { base58btc } = await import('multiformats/bases/base58');
|
|
102
|
+
|
|
103
|
+
if (this.encryptKeystore) {
|
|
104
|
+
const encryptedData = this.unlockedKeypair
|
|
105
|
+
? { publicKey: this.unlockedKeypair.publicKey, keyType: this.unlockedKeypair.keyType }
|
|
106
|
+
: await KeystoreEncryption.loadEncryptedKeystore(this.credential.credentialId);
|
|
107
|
+
const publicKeyBytes = encryptedData.publicKey instanceof Uint8Array
|
|
108
|
+
? encryptedData.publicKey
|
|
109
|
+
: new Uint8Array(encryptedData.publicKey);
|
|
110
|
+
const keyType = encryptedData.keyType || this.keystoreKeyType;
|
|
111
|
+
|
|
112
|
+
return this.createDIDFromKeystorePublicKey(publicKeyBytes, keyType, varint, base58btc);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Get the keystore's identity ID (this will be used to retrieve the key)
|
|
116
|
+
// We'll use the WebAuthn DID as the identity ID to get/create the keystore key
|
|
117
|
+
const identityId = await WebAuthnDIDProvider.createDID(this.credential);
|
|
118
|
+
|
|
119
|
+
// Get or create the Ed25519 key from keystore
|
|
120
|
+
// Try getKey first, if it doesn't exist, createKey will create it
|
|
121
|
+
let keystoreKey = await this.keystore.getKey(identityId);
|
|
122
|
+
if (!keystoreKey) {
|
|
123
|
+
identityLog('Key not found, creating new key for: %s with type: %s', identityId.substring(0, 32) + '...', this.keystoreKeyType);
|
|
124
|
+
keystoreKey = await this.keystore.createKey(identityId, this.keystoreKeyType);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
identityLog('Keystore key obtained, type: %s, keys: %o', typeof keystoreKey, Object.keys(keystoreKey || {}));
|
|
128
|
+
|
|
129
|
+
// The keystore key should have a public property with marshal method
|
|
130
|
+
// But it seems this isn't available immediately after creation
|
|
131
|
+
// Let's try to extract the public key bytes directly
|
|
132
|
+
let publicKeyBytes;
|
|
133
|
+
|
|
134
|
+
// Extract public key bytes from the keystore key
|
|
135
|
+
// OrbitDB uses @libp2p/crypto keys which have different structures
|
|
136
|
+
if (keystoreKey && keystoreKey.publicKey) {
|
|
137
|
+
// Modern libp2p-crypto format - has publicKey property
|
|
138
|
+
const pubKey = keystoreKey.publicKey;
|
|
139
|
+
identityLog('Found publicKey property, type: %s', pubKey.constructor.name);
|
|
140
|
+
|
|
141
|
+
// Try to get raw bytes from the public key
|
|
142
|
+
if (pubKey.raw) {
|
|
143
|
+
publicKeyBytes = pubKey.raw;
|
|
144
|
+
identityLog('Got public key from publicKey.raw: %d bytes', publicKeyBytes.length);
|
|
145
|
+
} else if (pubKey.bytes) {
|
|
146
|
+
publicKeyBytes = pubKey.bytes;
|
|
147
|
+
identityLog('Got public key from publicKey.bytes: %d bytes', publicKeyBytes.length);
|
|
148
|
+
} else if (typeof pubKey.marshal === 'function') {
|
|
149
|
+
publicKeyBytes = pubKey.marshal();
|
|
150
|
+
identityLog('Got public key from publicKey.marshal(): %d bytes', publicKeyBytes.length);
|
|
151
|
+
} else {
|
|
152
|
+
identityLog.error('Cannot extract bytes from publicKey: %o', pubKey);
|
|
153
|
+
throw new Error('Unable to extract bytes from publicKey');
|
|
154
|
+
}
|
|
155
|
+
} else if (keystoreKey && keystoreKey.public && keystoreKey.public.bytes) {
|
|
156
|
+
// Older libp2p-crypto format
|
|
157
|
+
publicKeyBytes = keystoreKey.public.bytes;
|
|
158
|
+
identityLog('Got public key from keystoreKey.public.bytes: %d bytes', publicKeyBytes.length);
|
|
159
|
+
} else if (keystoreKey && keystoreKey.bytes) {
|
|
160
|
+
// Direct bytes
|
|
161
|
+
publicKeyBytes = keystoreKey.bytes;
|
|
162
|
+
identityLog('Got public key from keystoreKey.bytes: %d bytes', publicKeyBytes.length);
|
|
163
|
+
} else {
|
|
164
|
+
identityLog.error('Cannot extract public key from keystoreKey: %o', keystoreKey);
|
|
165
|
+
throw new Error('Unable to extract public key from keystore key');
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Note: secp256k1 public keys are 33 or 65 bytes (compressed/uncompressed)
|
|
169
|
+
// Ed25519 public keys are 32 bytes
|
|
170
|
+
// We need to handle both
|
|
171
|
+
identityLog('Public key extracted: %d bytes, key type: %s', publicKeyBytes.length, keystoreKey.type);
|
|
172
|
+
|
|
173
|
+
if (!publicKeyBytes || publicKeyBytes.length < 32) {
|
|
174
|
+
throw new Error(`Invalid public key length: ${publicKeyBytes ? publicKeyBytes.length : 0} bytes`);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
identityLog('Successfully extracted public key: %d bytes, type: %s', publicKeyBytes.length, keystoreKey.type);
|
|
178
|
+
|
|
179
|
+
return this.createDIDFromKeystorePublicKey(publicKeyBytes, keystoreKey.type, varint, base58btc);
|
|
180
|
+
|
|
181
|
+
} catch (error) {
|
|
182
|
+
identityLog.error('Failed to create Ed25519 DID from keystore: %s', error.message);
|
|
183
|
+
throw new Error(`Failed to create Ed25519 DID from keystore: ${error.message}`);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
createDIDFromKeystorePublicKey(publicKeyBytes, keyType, varint, base58btc) {
|
|
188
|
+
// Determine the correct multicodec based on key type
|
|
189
|
+
// secp256k1 multicodec code (0xe7) or Ed25519 (0xed)
|
|
190
|
+
let multicodec;
|
|
191
|
+
if (keyType === 'secp256k1') {
|
|
192
|
+
multicodec = 0xe7; // secp256k1-pub
|
|
193
|
+
identityLog('Using secp256k1 multicodec (0xe7)');
|
|
194
|
+
} else if (keyType === 'Ed25519' || keyType === 'ed25519') {
|
|
195
|
+
multicodec = 0xed; // ed25519-pub
|
|
196
|
+
identityLog('Using Ed25519 multicodec (0xed)');
|
|
197
|
+
} else {
|
|
198
|
+
throw new Error(`Unsupported key type: ${keyType}`);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const codecLength = varint.encodingLength(multicodec);
|
|
202
|
+
const codecBytes = new Uint8Array(codecLength);
|
|
203
|
+
varint.encodeTo(multicodec, codecBytes, 0);
|
|
204
|
+
|
|
205
|
+
if (codecBytes.length === 0) {
|
|
206
|
+
throw new Error('Failed to encode ED25519_MULTICODEC with varint');
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// Combine multicodec prefix + public key bytes
|
|
210
|
+
const multikey = new Uint8Array(codecBytes.length + publicKeyBytes.length);
|
|
211
|
+
multikey.set(codecBytes, 0);
|
|
212
|
+
multikey.set(publicKeyBytes, codecBytes.length);
|
|
213
|
+
|
|
214
|
+
// Encode as base58btc and create did:key
|
|
215
|
+
const multikeyEncoded = base58btc.encode(multikey);
|
|
216
|
+
return `did:key:${multikeyEncoded}`;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Create and encrypt OrbitDB keystore
|
|
221
|
+
* @returns {Promise<void>}
|
|
222
|
+
*/
|
|
223
|
+
async createEncryptedKeystore() {
|
|
224
|
+
if (!this.encryptKeystore) {
|
|
225
|
+
console.log('ℹ️ Keystore encryption not enabled, skipping');
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
console.log('🔐 Creating encrypted keystore with method:', this.keystoreEncryptionMethod);
|
|
230
|
+
identityLog('Creating encrypted keystore with method: %s', this.keystoreEncryptionMethod);
|
|
231
|
+
|
|
232
|
+
try {
|
|
233
|
+
// Generate secret key
|
|
234
|
+
const sk = KeystoreEncryption.generateSecretKey();
|
|
235
|
+
|
|
236
|
+
const keyType = this.keystoreKeyType === 'secp256k1' ? 'secp256k1' : 'Ed25519';
|
|
237
|
+
const keyPair = await generateKeyPair(keyType);
|
|
238
|
+
const privateKeyBytes = keyPair.marshal ? keyPair.marshal() : keyPair.raw;
|
|
239
|
+
const publicKeyBytes = keyPair.publicKey?.marshal ? keyPair.publicKey.marshal() : keyPair.publicKey?.raw;
|
|
240
|
+
|
|
241
|
+
if (!privateKeyBytes || !publicKeyBytes) {
|
|
242
|
+
throw new Error('Failed to serialize keystore keypair');
|
|
243
|
+
}
|
|
244
|
+
const { ciphertext, iv } = await KeystoreEncryption.encryptWithAESGCM(privateKeyBytes, sk);
|
|
245
|
+
|
|
246
|
+
// Store SK in WebAuthn or wrap it
|
|
247
|
+
let encryptedData;
|
|
248
|
+
|
|
249
|
+
if (this.keystoreEncryptionMethod === 'prf') {
|
|
250
|
+
// Wrap SK with PRF (WebAuthn Level 3 - preferred method)
|
|
251
|
+
const wrapped = await KeystoreEncryption.wrapSKWithPRF(
|
|
252
|
+
this.credential.rawCredentialId,
|
|
253
|
+
sk,
|
|
254
|
+
window.location.hostname
|
|
255
|
+
);
|
|
256
|
+
|
|
257
|
+
encryptedData = {
|
|
258
|
+
ciphertext,
|
|
259
|
+
iv,
|
|
260
|
+
credentialId: this.credential.credentialId,
|
|
261
|
+
publicKey: publicKeyBytes,
|
|
262
|
+
keyType,
|
|
263
|
+
wrappedSK: wrapped.wrappedSK,
|
|
264
|
+
wrappingIV: wrapped.wrappingIV,
|
|
265
|
+
salt: wrapped.salt,
|
|
266
|
+
encryptionMethod: 'prf'
|
|
267
|
+
};
|
|
268
|
+
} else if (this.keystoreEncryptionMethod === 'largeBlob') {
|
|
269
|
+
// For largeBlob, we need to store SK during next authentication
|
|
270
|
+
// Store it temporarily for wrapping
|
|
271
|
+
encryptedData = {
|
|
272
|
+
ciphertext,
|
|
273
|
+
iv,
|
|
274
|
+
credentialId: this.credential.credentialId,
|
|
275
|
+
publicKey: publicKeyBytes,
|
|
276
|
+
keyType,
|
|
277
|
+
secretKey: sk, // Will be moved to largeBlob
|
|
278
|
+
encryptionMethod: 'largeBlob'
|
|
279
|
+
};
|
|
280
|
+
} else if (this.keystoreEncryptionMethod === 'hmac-secret') {
|
|
281
|
+
// Wrap SK with hmac-secret
|
|
282
|
+
const wrapped = await KeystoreEncryption.wrapSKWithHmacSecret(
|
|
283
|
+
this.credential.rawCredentialId,
|
|
284
|
+
sk,
|
|
285
|
+
window.location.hostname
|
|
286
|
+
);
|
|
287
|
+
|
|
288
|
+
encryptedData = {
|
|
289
|
+
ciphertext,
|
|
290
|
+
iv,
|
|
291
|
+
credentialId: this.credential.credentialId,
|
|
292
|
+
publicKey: publicKeyBytes,
|
|
293
|
+
keyType,
|
|
294
|
+
wrappedSK: wrapped.wrappedSK,
|
|
295
|
+
wrappingIV: wrapped.wrappingIV,
|
|
296
|
+
salt: wrapped.salt,
|
|
297
|
+
encryptionMethod: 'hmac-secret'
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// Store encrypted keystore
|
|
302
|
+
console.log('💾 Storing encrypted keystore with credentialId:', this.credential.credentialId?.substring(0, 16) + '...');
|
|
303
|
+
await KeystoreEncryption.storeEncryptedKeystore(encryptedData, this.credential.credentialId);
|
|
304
|
+
this.unlockedKeypair = {
|
|
305
|
+
privateKey: privateKeyBytes,
|
|
306
|
+
publicKey: publicKeyBytes,
|
|
307
|
+
keyType
|
|
308
|
+
};
|
|
309
|
+
this.unlockedPrivateKey = keyPair;
|
|
310
|
+
console.log('✅ Encrypted keystore stored successfully');
|
|
311
|
+
|
|
312
|
+
identityLog('Encrypted keystore created and stored successfully');
|
|
313
|
+
|
|
314
|
+
} catch (error) {
|
|
315
|
+
identityLog.error('Failed to create encrypted keystore: %s', error.message);
|
|
316
|
+
throw new Error(`Failed to create encrypted keystore: ${error.message}`);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* Unlock encrypted keystore
|
|
322
|
+
* @returns {Promise<Object>} Decrypted keypair
|
|
323
|
+
*/
|
|
324
|
+
async unlockEncryptedKeystore() {
|
|
325
|
+
if (!this.encryptKeystore) {
|
|
326
|
+
return null;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
identityLog('Unlocking encrypted keystore with method: %s', this.keystoreEncryptionMethod);
|
|
330
|
+
|
|
331
|
+
try {
|
|
332
|
+
// Load encrypted keystore
|
|
333
|
+
const encryptedData = await KeystoreEncryption.loadEncryptedKeystore(this.credential.credentialId);
|
|
334
|
+
|
|
335
|
+
let sk;
|
|
336
|
+
|
|
337
|
+
if (encryptedData.encryptionMethod === 'prf') {
|
|
338
|
+
// Unwrap SK with PRF
|
|
339
|
+
sk = await KeystoreEncryption.unwrapSKWithPRF(
|
|
340
|
+
this.credential.rawCredentialId,
|
|
341
|
+
encryptedData.wrappedSK,
|
|
342
|
+
encryptedData.wrappingIV,
|
|
343
|
+
encryptedData.salt,
|
|
344
|
+
window.location.hostname
|
|
345
|
+
);
|
|
346
|
+
} else if (encryptedData.encryptionMethod === 'largeBlob') {
|
|
347
|
+
// Retrieve SK from largeBlob
|
|
348
|
+
sk = await KeystoreEncryption.retrieveSKFromLargeBlob(
|
|
349
|
+
this.credential.rawCredentialId,
|
|
350
|
+
window.location.hostname
|
|
351
|
+
);
|
|
352
|
+
} else if (encryptedData.encryptionMethod === 'hmac-secret') {
|
|
353
|
+
// Unwrap SK with hmac-secret
|
|
354
|
+
sk = await KeystoreEncryption.unwrapSKWithHmacSecret(
|
|
355
|
+
this.credential.rawCredentialId,
|
|
356
|
+
encryptedData.wrappedSK,
|
|
357
|
+
encryptedData.wrappingIV,
|
|
358
|
+
encryptedData.salt,
|
|
359
|
+
window.location.hostname
|
|
360
|
+
);
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
// Decrypt keystore private key
|
|
364
|
+
const privateKeyBytes = await KeystoreEncryption.decryptWithAESGCM(
|
|
365
|
+
encryptedData.ciphertext,
|
|
366
|
+
sk,
|
|
367
|
+
encryptedData.iv
|
|
368
|
+
);
|
|
369
|
+
|
|
370
|
+
const publicKeyBytes = encryptedData.publicKey instanceof Uint8Array
|
|
371
|
+
? encryptedData.publicKey
|
|
372
|
+
: new Uint8Array(encryptedData.publicKey);
|
|
373
|
+
// Store unlocked keypair in memory for session
|
|
374
|
+
this.unlockedKeypair = {
|
|
375
|
+
privateKey: privateKeyBytes,
|
|
376
|
+
publicKey: publicKeyBytes,
|
|
377
|
+
keyType: encryptedData.keyType || this.keystoreKeyType
|
|
378
|
+
};
|
|
379
|
+
try {
|
|
380
|
+
this.unlockedPrivateKey = privateKeyFromRaw(privateKeyBytes);
|
|
381
|
+
} catch (error) {
|
|
382
|
+
identityLog.error('Failed to unmarshal encrypted keystore private key: %s', error.message);
|
|
383
|
+
this.unlockedPrivateKey = null;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
identityLog('Encrypted keystore unlocked successfully');
|
|
387
|
+
|
|
388
|
+
return this.unlockedKeypair;
|
|
389
|
+
|
|
390
|
+
} catch (error) {
|
|
391
|
+
identityLog.error('Failed to unlock encrypted keystore: %s', error.message);
|
|
392
|
+
throw new Error(`Failed to unlock encrypted keystore: ${error.message}`);
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
/**
|
|
397
|
+
* Sign data for OrbitDB identity operations.
|
|
398
|
+
* @param {string|Uint8Array} data - Payload to sign.
|
|
399
|
+
* @returns {Promise<string>} Signature envelope.
|
|
400
|
+
*/
|
|
401
|
+
signIdentity(data) {
|
|
402
|
+
const dataLength = typeof data === 'string' ? data.length : data.byteLength;
|
|
403
|
+
identityLog('signIdentity() called with data length: %d', dataLength);
|
|
404
|
+
identityLog('Signer context: %o', {
|
|
405
|
+
useKeystoreDID: this.useKeystoreDID,
|
|
406
|
+
encryptKeystore: this.encryptKeystore,
|
|
407
|
+
keystoreEncryptionMethod: this.keystoreEncryptionMethod,
|
|
408
|
+
hasKeystore: Boolean(this.keystore),
|
|
409
|
+
hasUnlockedKeypair: Boolean(this.unlockedKeypair)
|
|
410
|
+
});
|
|
411
|
+
|
|
412
|
+
const signerSelection =
|
|
413
|
+
this.encryptKeystore && this.unlockedKeypair ? 'encrypted-keystore' : 'webauthn';
|
|
414
|
+
identityLog('Signer selection: %s', signerSelection);
|
|
415
|
+
|
|
416
|
+
// If using encrypted keystore and it's unlocked, use unlocked key
|
|
417
|
+
if (this.encryptKeystore && this.unlockedKeypair) {
|
|
418
|
+
identityLog('Using unlocked encrypted keystore for signing');
|
|
419
|
+
// TODO: Implement signing with unlocked keypair
|
|
420
|
+
// For now, fall back to WebAuthn
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
return this.webauthnProvider.sign(data);
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
/**
|
|
427
|
+
* Verify identity signature.
|
|
428
|
+
* @param {string} signature - Signature envelope.
|
|
429
|
+
* @param {string|Uint8Array} data - Payload that was signed.
|
|
430
|
+
* @param {Object} [publicKey] - Optional public key override.
|
|
431
|
+
* @returns {Promise<boolean>} True if valid.
|
|
432
|
+
*/
|
|
433
|
+
verifyIdentity(signature, data, publicKey) {
|
|
434
|
+
identityLog('verifyIdentity() called');
|
|
435
|
+
return this.webauthnProvider.verify(signature, data, publicKey || this.credential.publicKey);
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
/**
|
|
439
|
+
* Create OrbitDB identity using WebAuthn
|
|
440
|
+
* @param {Object} options - Provider options.
|
|
441
|
+
* @returns {Promise<Object>} OrbitDB identity object.
|
|
442
|
+
*/
|
|
443
|
+
static async createIdentity(options) {
|
|
444
|
+
const {
|
|
445
|
+
webauthnCredential,
|
|
446
|
+
useKeystoreDID = false,
|
|
447
|
+
keystore = null,
|
|
448
|
+
keystoreKeyType = 'secp256k1',
|
|
449
|
+
encryptKeystore = false,
|
|
450
|
+
keystoreEncryptionMethod = 'prf'
|
|
451
|
+
} = options;
|
|
452
|
+
|
|
453
|
+
identityLog('createIdentity() called with useKeystoreDID: %s, keystoreKeyType: %s, encryptKeystore: %s',
|
|
454
|
+
useKeystoreDID, keystoreKeyType, encryptKeystore);
|
|
455
|
+
|
|
456
|
+
const provider = new OrbitDBWebAuthnIdentityProvider({
|
|
457
|
+
webauthnCredential,
|
|
458
|
+
useKeystoreDID,
|
|
459
|
+
keystore,
|
|
460
|
+
keystoreKeyType,
|
|
461
|
+
encryptKeystore,
|
|
462
|
+
keystoreEncryptionMethod
|
|
463
|
+
});
|
|
464
|
+
|
|
465
|
+
// If encryption is enabled, create and unlock encrypted keystore
|
|
466
|
+
if (encryptKeystore && keystore) {
|
|
467
|
+
try {
|
|
468
|
+
console.log('🔐 Creating encrypted keystore with', keystoreEncryptionMethod, '...');
|
|
469
|
+
await provider.createEncryptedKeystore();
|
|
470
|
+
console.log('🔓 Unlocking encrypted keystore...');
|
|
471
|
+
await provider.unlockEncryptedKeystore();
|
|
472
|
+
console.log('✅ Encrypted keystore created and unlocked successfully');
|
|
473
|
+
identityLog('Encrypted keystore created and unlocked');
|
|
474
|
+
} catch (error) {
|
|
475
|
+
// Log error visibly so users know encryption failed
|
|
476
|
+
console.error('❌ Failed to setup encrypted keystore:', error.message);
|
|
477
|
+
console.error(' Full error:', error);
|
|
478
|
+
identityLog.error('Failed to setup encrypted keystore: %s', error.message);
|
|
479
|
+
// Continue anyway - encryption is optional but user should know it failed
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
const id = await provider.getId();
|
|
484
|
+
|
|
485
|
+
identityLog('Identity created successfully: %o', {
|
|
486
|
+
id: id.substring(0, 32) + '...',
|
|
487
|
+
type: 'webauthn',
|
|
488
|
+
didType: useKeystoreDID ? 'Ed25519 (from keystore)' : 'P-256 (from WebAuthn)',
|
|
489
|
+
encrypted: encryptKeystore,
|
|
490
|
+
hasPublicKey: !!webauthnCredential.publicKey
|
|
491
|
+
});
|
|
492
|
+
|
|
493
|
+
return {
|
|
494
|
+
id,
|
|
495
|
+
publicKey: webauthnCredential.publicKey,
|
|
496
|
+
type: 'webauthn',
|
|
497
|
+
sign: (identity, data) => {
|
|
498
|
+
identityLog('identity.sign() called from OrbitDB');
|
|
499
|
+
return provider.signIdentity(data);
|
|
500
|
+
},
|
|
501
|
+
verify: (signature, data) => {
|
|
502
|
+
identityLog('identity.verify() called from OrbitDB');
|
|
503
|
+
return provider.verifyIdentity(signature, data, webauthnCredential.publicKey);
|
|
504
|
+
}
|
|
505
|
+
};
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
/**
|
|
510
|
+
* WebAuthn Identity Provider Function for OrbitDB
|
|
511
|
+
* This follows the same pattern as OrbitDBIdentityProviderDID
|
|
512
|
+
* Returns a function that returns a promise resolving to the provider instance
|
|
513
|
+
*
|
|
514
|
+
* @param {Object} options - Configuration options
|
|
515
|
+
* @param {Object} options.webauthnCredential - WebAuthn credential for authentication
|
|
516
|
+
* @param {boolean} options.useKeystoreDID - If true, creates DID from keystore instead of P-256 DID from WebAuthn
|
|
517
|
+
* @param {Object} options.keystore - OrbitDB keystore instance (required if useKeystoreDID is true)
|
|
518
|
+
* @param {string} options.keystoreKeyType - Key type for keystore: 'secp256k1' (default) or 'Ed25519'
|
|
519
|
+
* @param {boolean} options.encryptKeystore - If true, encrypts the keystore with WebAuthn-protected secret
|
|
520
|
+
* @param {string} options.keystoreEncryptionMethod - Encryption method: 'largeBlob' or 'hmac-secret'
|
|
521
|
+
* @returns {Function} Provider factory for OrbitDB.
|
|
522
|
+
*/
|
|
523
|
+
export function OrbitDBWebAuthnIdentityProviderFunction(options = {}) {
|
|
524
|
+
// Return a function that returns a promise (as expected by OrbitDB)
|
|
525
|
+
return async () => {
|
|
526
|
+
return new OrbitDBWebAuthnIdentityProvider(options);
|
|
527
|
+
};
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
// Add static methods and properties that OrbitDB expects
|
|
531
|
+
OrbitDBWebAuthnIdentityProviderFunction.type = 'webauthn';
|
|
532
|
+
OrbitDBWebAuthnIdentityProviderFunction.verifyIdentity = async function(identity) {
|
|
533
|
+
try {
|
|
534
|
+
// For WebAuthn identities, we need to store the credential info in the identity
|
|
535
|
+
// Since WebAuthn verification requires the original credential, not just the public key,
|
|
536
|
+
// we'll create a simplified verification that checks the proof structure
|
|
537
|
+
|
|
538
|
+
|
|
539
|
+
// For WebAuthn, the identity should have been created with our provider,
|
|
540
|
+
// so we can trust it if it has the right structure
|
|
541
|
+
// Accept both DID format (did:key:...) and hash format (hex string) for backward compatibility
|
|
542
|
+
const isValidDID = identity.id && identity.id.startsWith('did:key:');
|
|
543
|
+
const isValidHash = identity.id && /^[a-f0-9]{64}$/.test(identity.id); // 64-char hex string (legacy)
|
|
544
|
+
|
|
545
|
+
if (identity.type === 'webauthn' && (isValidDID || isValidHash)) {
|
|
546
|
+
return true;
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
return false;
|
|
550
|
+
|
|
551
|
+
} catch (error) {
|
|
552
|
+
console.error('WebAuthn static identity verification failed:', error);
|
|
553
|
+
return false;
|
|
554
|
+
}
|
|
555
|
+
};
|