11import { base64ToByteArray , byteArrayToBase64 , concatenate } from "./utils" ;
2- import { BACKUP_KEY , BD_ENDPOINT , DEFAULT_BD_ENDPOINT , ivLength , keyExportFormat , PIN , saltLength } from "./constants" ;
2+ import {
3+ BACKUP_KEY ,
4+ BD_ENDPOINT ,
5+ DEFAULT_BD_ENDPOINT , ES256 ,
6+ ivLength ,
7+ keyExportFormat ,
8+ PIN ,
9+ RECOVERY_KEY ,
10+ saltLength
11+ } from "./constants" ;
312import { getLogger } from "./logging" ;
4- import { BackupKey } from "./webauthn_psk" ;
13+ import { BackupKey , RecoveryKey } from "./webauthn_psk" ;
514
615const log = getLogger ( 'auth_storage' ) ;
716
@@ -100,6 +109,81 @@ export class PSKStorage {
100109 } ) ;
101110 } ) ;
102111 } ;
112+
113+ public static async storeRecoveryKeys ( recoveryKeys : RecoveryKey [ ] ) : Promise < void > {
114+ log . debug ( 'Storing recovery keys' ) ;
115+
116+ // Export recoveryKeys
117+ const exportKeys = [ ]
118+ for ( let i = 0 ; i < recoveryKeys . length ; i ++ ) {
119+ const recKey = recoveryKeys [ i ] ;
120+ const expPrvKey = await exportKey ( recKey . privKey ) ;
121+ const expPubKey = await window . crypto . subtle . exportKey ( 'jwk' , recKey . pubKey ) ;
122+
123+ const json = {
124+ credentialId : recKey . credentialId ,
125+ pubKey : expPubKey ,
126+ privKey : expPrvKey ,
127+ delegationSignature : recKey . delegationSignature ,
128+ }
129+
130+ exportKeys . push ( json )
131+ }
132+
133+ let exportJSON = JSON . stringify ( exportKeys ) ;
134+ return new Promise < void > ( async ( res , rej ) => {
135+ chrome . storage . local . set ( { [ RECOVERY_KEY ] : exportJSON } , ( ) => {
136+ if ( ! ! chrome . runtime . lastError ) {
137+ log . error ( 'Could not perform PSKStorage.storeRecoveryKeys' , chrome . runtime . lastError . message ) ;
138+ rej ( chrome . runtime . lastError ) ;
139+ return ;
140+ } else {
141+ res ( ) ;
142+ }
143+ } ) ;
144+ } ) ;
145+ }
146+
147+ public static async loadRecoveryKeys ( rpId ) : Promise < RecoveryKey [ ] > {
148+ log . debug ( `Loading recovery keys` ) ;
149+ return new Promise < RecoveryKey [ ] > ( async ( res , rej ) => {
150+ chrome . storage . local . get ( { [ RECOVERY_KEY ] : null } , async ( resp ) => {
151+ if ( ! ! chrome . runtime . lastError ) {
152+ log . error ( 'Could not perform PSKStorage.loadRecoveryKeys' , chrome . runtime . lastError . message ) ;
153+ rej ( chrome . runtime . lastError ) ;
154+ return ;
155+ }
156+
157+ if ( resp [ RECOVERY_KEY ] == null ) {
158+ log . warn ( `No recovery keys found` ) ;
159+ res ( [ ] ) ;
160+ return ;
161+ }
162+
163+ const exportJSON = await JSON . parse ( resp [ RECOVERY_KEY ] ) ;
164+ const recKeys = new Array < RecoveryKey > ( ) ;
165+ for ( let i = 0 ; i < exportJSON . length ; ++ i ) {
166+ const json = exportJSON [ i ] ;
167+ const prvKey = await importKey ( json . privKey ) ;
168+ const pubKey = await window . crypto . subtle . importKey (
169+ 'jwk' ,
170+ json . pubKey ,
171+ {
172+ name : 'ECDSA' ,
173+ namedCurve : ES256 ,
174+ } ,
175+ true ,
176+ [ 'sign' ] ,
177+ ) ;
178+
179+ const recKey = new RecoveryKey ( json . credId , pubKey , prvKey , json . sign ) ;
180+ recKeys . push ( recKey ) ;
181+ }
182+ log . debug ( 'Loaded recovery keys successfully' ) ;
183+ res ( recKeys ) ;
184+ } ) ;
185+ } ) ;
186+ }
103187}
104188
105189export class CredentialsMap {
@@ -194,35 +278,7 @@ export class PublicKeyCredentialSource {
194278 const _id = json . id ;
195279 const _rpId = json . rpId ;
196280 const _userHandle = json . userHandle ;
197-
198- const keyPayload = base64ToByteArray ( json . privateKey ) ;
199- const saltByteLength = keyPayload [ 0 ] ;
200- const ivByteLength = keyPayload [ 1 ] ;
201- const keyAlgorithmByteLength = keyPayload [ 2 ] ;
202- let offset = 3 ;
203- const salt = keyPayload . subarray ( offset , offset + saltByteLength ) ;
204- offset += saltByteLength ;
205- const iv = keyPayload . subarray ( offset , offset + ivByteLength ) ;
206- offset += ivByteLength ;
207- const keyAlgorithmBytes = keyPayload . subarray ( offset , offset + keyAlgorithmByteLength ) ;
208- offset += keyAlgorithmByteLength ;
209- const keyBytes = keyPayload . subarray ( offset ) ;
210-
211- const wrappingKey = await getWrappingKey ( PIN , salt ) ;
212- const wrapAlgorithm : AesGcmParams = {
213- iv,
214- name : 'AES-GCM' ,
215- } ;
216- const unwrappingKeyAlgorithm = JSON . parse ( new TextDecoder ( ) . decode ( keyAlgorithmBytes ) ) ;
217- const _privateKey = await window . crypto . subtle . unwrapKey (
218- keyExportFormat ,
219- keyBytes ,
220- wrappingKey ,
221- wrapAlgorithm ,
222- unwrappingKeyAlgorithm ,
223- true ,
224- [ 'sign' ] ,
225- ) ;
281+ const _privateKey = await importKey ( json . privateKey ) ;
226282
227283 return new PublicKeyCredentialSource ( _id , _privateKey , _rpId , _userHandle ) ;
228284 }
@@ -246,41 +302,74 @@ export class PublicKeyCredentialSource {
246302 }
247303
248304 public async export ( ) : Promise < any > {
249- const salt = window . crypto . getRandomValues ( new Uint8Array ( saltLength ) ) ;
250- const wrappingKey = await getWrappingKey ( PIN , salt ) ;
251- const iv = window . crypto . getRandomValues ( new Uint8Array ( ivLength ) ) ;
252- const wrapAlgorithm : AesGcmParams = {
253- iv,
254- name : 'AES-GCM' ,
255- } ;
256-
257- const wrappedKeyBuffer = await window . crypto . subtle . wrapKey (
258- keyExportFormat ,
259- this . privateKey ,
260- wrappingKey ,
261- wrapAlgorithm ,
262- ) ;
263- const wrappedKey = new Uint8Array ( wrappedKeyBuffer ) ;
264- const keyAlgorithm = new TextEncoder ( ) . encode ( JSON . stringify ( this . privateKey . algorithm ) ) ;
265- const payload = concatenate (
266- Uint8Array . of ( saltLength , ivLength , keyAlgorithm . length ) ,
267- salt ,
268- iv ,
269- keyAlgorithm ,
270- wrappedKey ) ;
271-
272- const json = {
305+ return {
273306 id : this . id ,
274- privateKey : byteArrayToBase64 ( payload ) ,
307+ privateKey : await exportKey ( this . privateKey ) ,
275308 rpId : this . rpId ,
276309 userHandle : this . userHandle ,
277310 type : this . type
278- }
279-
280- return json ;
311+ } ;
281312 }
282313}
283314
315+ async function exportKey ( key : CryptoKey ) : Promise < string > {
316+ const salt = window . crypto . getRandomValues ( new Uint8Array ( saltLength ) ) ;
317+ const wrappingKey = await getWrappingKey ( PIN , salt ) ;
318+ const iv = window . crypto . getRandomValues ( new Uint8Array ( ivLength ) ) ;
319+ const wrapAlgorithm : AesGcmParams = {
320+ iv,
321+ name : 'AES-GCM' ,
322+ } ;
323+
324+ const wrappedKeyBuffer = await window . crypto . subtle . wrapKey (
325+ keyExportFormat ,
326+ key ,
327+ wrappingKey ,
328+ wrapAlgorithm ,
329+ ) ;
330+ const wrappedKey = new Uint8Array ( wrappedKeyBuffer ) ;
331+ const keyAlgorithm = new TextEncoder ( ) . encode ( JSON . stringify ( key . algorithm ) ) ;
332+ const payload = concatenate (
333+ Uint8Array . of ( saltLength , ivLength , keyAlgorithm . length ) ,
334+ salt ,
335+ iv ,
336+ keyAlgorithm ,
337+ wrappedKey ) ;
338+
339+ return byteArrayToBase64 ( payload )
340+ }
341+
342+ async function importKey ( rawKey : string ) : Promise < CryptoKey > {
343+ const keyPayload = base64ToByteArray ( rawKey ) ;
344+ const saltByteLength = keyPayload [ 0 ] ;
345+ const ivByteLength = keyPayload [ 1 ] ;
346+ const keyAlgorithmByteLength = keyPayload [ 2 ] ;
347+ let offset = 3 ;
348+ const salt = keyPayload . subarray ( offset , offset + saltByteLength ) ;
349+ offset += saltByteLength ;
350+ const iv = keyPayload . subarray ( offset , offset + ivByteLength ) ;
351+ offset += ivByteLength ;
352+ const keyAlgorithmBytes = keyPayload . subarray ( offset , offset + keyAlgorithmByteLength ) ;
353+ offset += keyAlgorithmByteLength ;
354+ const keyBytes = keyPayload . subarray ( offset ) ;
355+
356+ const wrappingKey = await getWrappingKey ( PIN , salt ) ;
357+ const wrapAlgorithm : AesGcmParams = {
358+ iv,
359+ name : 'AES-GCM' ,
360+ } ;
361+ const unwrappingKeyAlgorithm = JSON . parse ( new TextDecoder ( ) . decode ( keyAlgorithmBytes ) ) ;
362+ return await window . crypto . subtle . unwrapKey (
363+ keyExportFormat ,
364+ keyBytes ,
365+ wrappingKey ,
366+ wrapAlgorithm ,
367+ unwrappingKeyAlgorithm ,
368+ true ,
369+ [ 'sign' ] ,
370+ ) ;
371+ }
372+
284373const getWrappingKey = async ( pin : string , salt : Uint8Array ) : Promise < CryptoKey > => {
285374 const enc = new TextEncoder ( ) ;
286375 const derivationKey = await window . crypto . subtle . importKey (
0 commit comments