mirror of
https://github.com/bitwarden/browser.git
synced 2025-03-09 12:59:20 +01:00
[EC-598] feat: successfully store passkeys in vault
This commit is contained in:
parent
cb07b2121e
commit
f56971aa89
@ -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 =
|
||||
|
@ -33,7 +33,7 @@ export abstract class CipherService {
|
||||
updateLastUsedDate: (id: string) => Promise<void>;
|
||||
updateLastLaunchedDate: (id: string) => Promise<void>;
|
||||
saveNeverDomain: (domain: string) => Promise<void>;
|
||||
createWithServer: (cipher: Cipher) => Promise<any>;
|
||||
createWithServer: (cipher: Cipher) => Promise<Cipher>;
|
||||
updateWithServer: (cipher: Cipher) => Promise<any>;
|
||||
shareWithServer: (
|
||||
cipher: CipherView,
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -1,5 +1,4 @@
|
||||
export class Fido2KeyView {
|
||||
id: string;
|
||||
key: string;
|
||||
rpId: string;
|
||||
origin: string;
|
||||
|
@ -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.");
|
||||
}
|
||||
|
@ -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<string, BitCredential>();
|
||||
|
||||
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<BitCredential | undefined> {
|
||||
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<void> {
|
||||
this.credentials.set(credential.credentialId.encoded, credential);
|
||||
private async saveCredential(
|
||||
credential: Omit<BitCredential, "credentialId">
|
||||
): Promise<CredentialId> {
|
||||
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
|
||||
)
|
||||
);
|
||||
|
Loading…
Reference in New Issue
Block a user