mirror of
https://github.com/bitwarden/browser
synced 2025-12-16 08:13:42 +00:00
[EC-598] feat: implement passwordless vault auth
This commit is contained in:
@@ -34,8 +34,6 @@ interface BitCredential {
|
|||||||
const KeyUsages: KeyUsage[] = ["sign"];
|
const KeyUsages: KeyUsage[] = ["sign"];
|
||||||
|
|
||||||
export class Fido2Service implements Fido2ServiceAbstraction {
|
export class Fido2Service implements Fido2ServiceAbstraction {
|
||||||
private credentials = new Map<string, BitCredential>();
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private fido2UserInterfaceService: Fido2UserInterfaceService,
|
private fido2UserInterfaceService: Fido2UserInterfaceService,
|
||||||
private cipherService: CipherService
|
private cipherService: CipherService
|
||||||
@@ -120,17 +118,19 @@ export class Fido2Service implements Fido2ServiceAbstraction {
|
|||||||
if (params.allowedCredentialIds && params.allowedCredentialIds.length > 0) {
|
if (params.allowedCredentialIds && params.allowedCredentialIds.length > 0) {
|
||||||
// We're looking for regular non-resident keys
|
// We're looking for regular non-resident keys
|
||||||
credential = await this.getCredential(params.allowedCredentialIds);
|
credential = await this.getCredential(params.allowedCredentialIds);
|
||||||
console.log("Found credential: ", credential);
|
|
||||||
} else {
|
} else {
|
||||||
// We're looking for a resident key
|
// We're looking for a resident key
|
||||||
credential = this.getCredentialByRp(params.rpId);
|
credential = await this.getCredentialByRp(params.rpId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log("Found credential: ", credential);
|
||||||
|
|
||||||
if (credential === undefined) {
|
if (credential === undefined) {
|
||||||
throw new NoCredentialFoundError();
|
throw new NoCredentialFoundError();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (credential.origin !== params.origin) {
|
if (credential.origin !== params.origin) {
|
||||||
|
console.error(`${params.origin} tried to use credential created by ${credential.origin}`);
|
||||||
throw new OriginMismatchError();
|
throw new OriginMismatchError();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -177,11 +177,61 @@ export class Fido2Service implements Fido2ServiceAbstraction {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cipher == null) {
|
if (cipher == undefined) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
const cipherView = await cipher.decrypt();
|
const cipherView = await cipher.decrypt();
|
||||||
|
return await mapCipherViewToBitCredential(cipherView);
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
console.log("saving credential", { view, credential });
|
||||||
|
|
||||||
|
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 async getCredentialByRp(rpId: string): Promise<BitCredential | undefined> {
|
||||||
|
const allCipherViews = await this.cipherService.getAllDecrypted();
|
||||||
|
const cipherView = allCipherViews.find(
|
||||||
|
(cv) => cv.type === CipherType.Fido2Key && cv.fido2Key?.rpId === rpId
|
||||||
|
);
|
||||||
|
|
||||||
|
if (cipherView == undefined) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return await mapCipherViewToBitCredential(cipherView);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface AuthDataParams {
|
||||||
|
rpId: string;
|
||||||
|
credentialId: CredentialId;
|
||||||
|
userPresence: boolean;
|
||||||
|
userVerification: boolean;
|
||||||
|
keyPair?: CryptoKeyPair;
|
||||||
|
attestationFormat?: "packed" | "fido-u2f";
|
||||||
|
}
|
||||||
|
|
||||||
|
async function mapCipherViewToBitCredential(cipherView: CipherView): Promise<BitCredential> {
|
||||||
const keyBuffer = Fido2Utils.stringToBuffer(cipherView.fido2Key.key);
|
const keyBuffer = Fido2Utils.stringToBuffer(cipherView.fido2Key.key);
|
||||||
let privateKey;
|
let privateKey;
|
||||||
try {
|
try {
|
||||||
@@ -207,46 +257,6 @@ export class Fido2Service implements Fido2ServiceAbstraction {
|
|||||||
rpId: cipherView.fido2Key.rpId,
|
rpId: cipherView.fido2Key.rpId,
|
||||||
userHandle: Fido2Utils.stringToBuffer(cipherView.fido2Key.userHandle),
|
userHandle: Fido2Utils.stringToBuffer(cipherView.fido2Key.userHandle),
|
||||||
};
|
};
|
||||||
}
|
|
||||||
|
|
||||||
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 {
|
|
||||||
for (const credential of this.credentials.values()) {
|
|
||||||
if (credential.rpId === rpId) {
|
|
||||||
return credential;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
interface AuthDataParams {
|
|
||||||
rpId: string;
|
|
||||||
credentialId: CredentialId;
|
|
||||||
userPresence: boolean;
|
|
||||||
userVerification: boolean;
|
|
||||||
keyPair?: CryptoKeyPair;
|
|
||||||
attestationFormat?: "packed" | "fido-u2f";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function generateAuthData(params: AuthDataParams) {
|
async function generateAuthData(params: AuthDataParams) {
|
||||||
|
|||||||
Reference in New Issue
Block a user