diff --git a/apps/browser/src/background/main.background.ts b/apps/browser/src/background/main.background.ts index 0fb48425f3..f02546f392 100644 --- a/apps/browser/src/background/main.background.ts +++ b/apps/browser/src/background/main.background.ts @@ -468,7 +468,7 @@ export default class MainBackground { ); this.fido2UserInterfaceService = new BrowserFido2UserInterfaceService(this.popupUtilsService); - this.fido2Service = new Fido2Service(this.fido2UserInterfaceService); + this.fido2Service = new Fido2Service(this.fido2UserInterfaceService, this.cipherService); const systemUtilsServiceReloadCallback = () => { const forceWindowReload = diff --git a/libs/common/src/abstractions/cipher.service.ts b/libs/common/src/abstractions/cipher.service.ts index b551f20991..fca82aefa7 100644 --- a/libs/common/src/abstractions/cipher.service.ts +++ b/libs/common/src/abstractions/cipher.service.ts @@ -33,7 +33,7 @@ export abstract class CipherService { updateLastUsedDate: (id: string) => Promise; updateLastLaunchedDate: (id: string) => Promise; saveNeverDomain: (domain: string) => Promise; - createWithServer: (cipher: Cipher) => Promise; + createWithServer: (cipher: Cipher) => Promise; updateWithServer: (cipher: Cipher) => Promise; shareWithServer: ( cipher: CipherView, diff --git a/libs/common/src/models/request/cipher.request.ts b/libs/common/src/models/request/cipher.request.ts index b7717d9729..6eb9203981 100644 --- a/libs/common/src/models/request/cipher.request.ts +++ b/libs/common/src/models/request/cipher.request.ts @@ -1,6 +1,7 @@ import { CipherRepromptType } from "../../enums/cipherRepromptType"; import { CipherType } from "../../enums/cipherType"; import { CardApi } from "../api/card.api"; +import { Fido2KeyApi } from "../api/fido2-key.api"; import { FieldApi } from "../api/field.api"; import { IdentityApi } from "../api/identity.api"; import { LoginUriApi } from "../api/login-uri.api"; @@ -22,6 +23,7 @@ export class CipherRequest { secureNote: SecureNoteApi; card: CardApi; identity: IdentityApi; + fido2Key: Fido2KeyApi; fields: FieldApi[]; passwordHistory: PasswordHistoryRequest[]; // Deprecated, remove at some point and rename attachments2 to attachments @@ -121,6 +123,17 @@ export class CipherRequest { ? cipher.identity.licenseNumber.encryptedString : null; break; + case CipherType.Fido2Key: + this.fido2Key = new Fido2KeyApi(); + this.fido2Key.key = + cipher.fido2Key.key != null ? cipher.fido2Key.key.encryptedString : null; + this.fido2Key.origin = + cipher.fido2Key.origin != null ? cipher.fido2Key.origin.encryptedString : null; + this.fido2Key.rpId = + cipher.fido2Key.rpId != null ? cipher.fido2Key.rpId.encryptedString : null; + this.fido2Key.userHandle = + cipher.fido2Key.userHandle != null ? cipher.fido2Key.userHandle.encryptedString : null; + break; default: break; } diff --git a/libs/common/src/models/view/fido2-key.view.ts b/libs/common/src/models/view/fido2-key.view.ts index 96ad0b3642..964a893fd1 100644 --- a/libs/common/src/models/view/fido2-key.view.ts +++ b/libs/common/src/models/view/fido2-key.view.ts @@ -1,5 +1,4 @@ export class Fido2KeyView { - id: string; key: string; rpId: string; origin: string; diff --git a/libs/common/src/services/cipher.service.ts b/libs/common/src/services/cipher.service.ts index a7b54942e2..945066e969 100644 --- a/libs/common/src/services/cipher.service.ts +++ b/libs/common/src/services/cipher.service.ts @@ -23,6 +23,7 @@ import { Cipher } from "../models/domain/cipher"; import Domain from "../models/domain/domain-base"; import { EncArrayBuffer } from "../models/domain/enc-array-buffer"; import { EncString } from "../models/domain/enc-string"; +import { Fido2Key } from "../models/domain/fido2-key"; import { Field } from "../models/domain/field"; import { Identity } from "../models/domain/identity"; import { Login } from "../models/domain/login"; @@ -1244,6 +1245,20 @@ export class CipherService implements CipherServiceAbstraction { key ); return; + case CipherType.Fido2Key: + cipher.fido2Key = new Fido2Key(); + await this.encryptObjProperty( + model.fido2Key, + cipher.fido2Key, + { + key: null, + rpId: null, + origin: null, + userHandle: null, + }, + key + ); + break; default: throw new Error("Unknown cipher type."); } diff --git a/libs/common/src/services/fido2/fido2.service.ts b/libs/common/src/services/fido2/fido2.service.ts index d9163b5b3d..a3b014851f 100644 --- a/libs/common/src/services/fido2/fido2.service.ts +++ b/libs/common/src/services/fido2/fido2.service.ts @@ -1,5 +1,6 @@ import { CBOR } from "cbor-redux"; +import { CipherService } from "../../abstractions/cipher.service"; import { Fido2UserInterfaceService } from "../../abstractions/fido2/fido2-user-interface.service.abstraction"; import { Fido2Utils } from "../../abstractions/fido2/fido2-utils"; import { @@ -11,7 +12,11 @@ import { NoCredentialFoundError, OriginMismatchError, } from "../../abstractions/fido2/fido2.service.abstraction"; +import { CipherType } from "../../enums/cipherType"; import { Utils } from "../../misc/utils"; +import { Cipher } from "../../models/domain/cipher"; +import { CipherView } from "../../models/view/cipher.view"; +import { Fido2KeyView } from "../../models/view/fido2-key.view"; import { CredentialId } from "./credential-id"; import { joseToDer } from "./ecdsa-utils"; @@ -20,16 +25,21 @@ const STANDARD_ATTESTATION_FORMAT = "packed"; interface BitCredential { credentialId: CredentialId; - keyPair: CryptoKeyPair; + privateKey: CryptoKey; rpId: string; origin: string; userHandle: Uint8Array; } +const KeyUsages: KeyUsage[] = ["sign"]; + export class Fido2Service implements Fido2ServiceAbstraction { private credentials = new Map(); - constructor(private fido2UserInterfaceService: Fido2UserInterfaceService) {} + constructor( + private fido2UserInterfaceService: Fido2UserInterfaceService, + private cipherService: CipherService + ) {} async createCredential( params: CredentialRegistrationParams @@ -40,7 +50,6 @@ export class Fido2Service implements Fido2ServiceAbstraction { const attestationFormat = STANDARD_ATTESTATION_FORMAT; const encoder = new TextEncoder(); - const credentialId = new CredentialId(Utils.newGuid()); const clientData = encoder.encode( JSON.stringify({ @@ -55,14 +64,21 @@ export class Fido2Service implements Fido2ServiceAbstraction { namedCurve: "P-256", }, true, - ["sign", "verify"] + KeyUsages ); + const credentialId = await this.saveCredential({ + privateKey: keyPair.privateKey, + origin: params.origin, + rpId: params.rp.id, + userHandle: Fido2Utils.stringToBuffer(params.user.id), + }); + const authData = await generateAuthData({ rpId: params.rp.id, credentialId, userPresence: presence, - userVerification: false, + userVerification: true, // TODO: Change to false keyPair, attestationFormat: STANDARD_ATTESTATION_FORMAT, }); @@ -70,7 +86,7 @@ export class Fido2Service implements Fido2ServiceAbstraction { const asn1Der_signature = await generateSignature({ authData, clientData, - keyPair, + privateKey: keyPair.privateKey, }); const attestationObject = new Uint8Array( @@ -84,14 +100,6 @@ export class Fido2Service implements Fido2ServiceAbstraction { }) ); - await this.saveCredential({ - credentialId, - keyPair, - origin: params.origin, - rpId: params.rp.id, - userHandle: Fido2Utils.stringToBuffer(params.user.id), - }); - // eslint-disable-next-line no-console console.log("Fido2Service.createCredential => result", { credentialId: Fido2Utils.bufferToString(credentialId.raw), @@ -111,7 +119,8 @@ export class Fido2Service implements Fido2ServiceAbstraction { if (params.allowedCredentialIds && params.allowedCredentialIds.length > 0) { // We're looking for regular non-resident keys - credential = this.getCredential(params.allowedCredentialIds); + credential = await this.getCredential(params.allowedCredentialIds); + console.log("Found credential: ", credential); } else { // We're looking for a resident key credential = this.getCredentialByRp(params.rpId); @@ -140,13 +149,13 @@ export class Fido2Service implements Fido2ServiceAbstraction { credentialId: credential.credentialId, rpId: params.rpId, userPresence: presence, - userVerification: false, + userVerification: true, // TODO: Change to false! }); const signature = await generateSignature({ authData, clientData, - keyPair: credential.keyPair, + privateKey: credential.privateKey, }); return { @@ -158,20 +167,67 @@ export class Fido2Service implements Fido2ServiceAbstraction { }; } - private getCredential(allowedCredentialIds: string[]): BitCredential | undefined { - let credential: BitCredential | undefined; + private async getCredential(allowedCredentialIds: string[]): Promise { + let cipher: Cipher | undefined; for (const allowedCredential of allowedCredentialIds) { - const id = new CredentialId(allowedCredential); - if (this.credentials.has(id.encoded)) { - credential = this.credentials.get(id.encoded); + cipher = await this.cipherService.get(allowedCredential); + + if (cipher != undefined) { break; } } - return credential; + + if (cipher == null) { + return undefined; + } + + const cipherView = await cipher.decrypt(); + const keyBuffer = Fido2Utils.stringToBuffer(cipherView.fido2Key.key); + let privateKey; + try { + privateKey = await crypto.subtle.importKey( + "pkcs8", + keyBuffer, + { + name: "ECDSA", + namedCurve: "P-256", + }, + true, + KeyUsages + ); + } catch (err) { + console.log("Error importing key", { err }); + throw err; + } + + return { + credentialId: new CredentialId(cipherView.id), + privateKey, + origin: cipherView.fido2Key.origin, + rpId: cipherView.fido2Key.rpId, + userHandle: Fido2Utils.stringToBuffer(cipherView.fido2Key.userHandle), + }; } - private async saveCredential(credential: BitCredential): Promise { - this.credentials.set(credential.credentialId.encoded, credential); + private async saveCredential( + credential: Omit + ): Promise { + const pcks8Key = await crypto.subtle.exportKey("pkcs8", credential.privateKey); + + const view = new CipherView(); + view.type = CipherType.Fido2Key; + view.name = credential.origin; + view.fido2Key = new Fido2KeyView(); + view.fido2Key.key = Fido2Utils.bufferToString(pcks8Key); + view.fido2Key.origin = credential.origin; + view.fido2Key.rpId = credential.rpId; + view.fido2Key.userHandle = Fido2Utils.bufferToString(credential.userHandle); + + const cipher = await this.cipherService.encrypt(view); + await this.cipherService.createWithServer(cipher); + + // TODO: Cipher service modifies supplied object, we might wanna change that. + return new CredentialId(cipher.id); } private getCredentialByRp(rpId: string): BitCredential | undefined { @@ -267,7 +323,7 @@ async function generateAuthData(params: AuthDataParams) { interface SignatureParams { authData: Uint8Array; clientData: Uint8Array; - keyPair: CryptoKeyPair; + privateKey: CryptoKey; } async function generateSignature(params: SignatureParams) { @@ -279,7 +335,7 @@ async function generateSignature(params: SignatureParams) { name: "ECDSA", hash: { name: "SHA-256" }, }, - params.keyPair.privateKey, + params.privateKey, sigBase ) );