mirror of
https://github.com/bitwarden/browser
synced 2025-12-17 08:43:33 +00:00
[EC-598] feat: make everything compile again
This commit is contained in:
@@ -82,9 +82,11 @@ import { CipherService } from "@bitwarden/common/vault/services/cipher.service";
|
|||||||
import { FolderApiService } from "@bitwarden/common/vault/services/folder/folder-api.service";
|
import { FolderApiService } from "@bitwarden/common/vault/services/folder/folder-api.service";
|
||||||
import { SyncNotifierService } from "@bitwarden/common/vault/services/sync/sync-notifier.service";
|
import { SyncNotifierService } from "@bitwarden/common/vault/services/sync/sync-notifier.service";
|
||||||
import { SyncService } from "@bitwarden/common/vault/services/sync/sync.service";
|
import { SyncService } from "@bitwarden/common/vault/services/sync/sync.service";
|
||||||
|
import { Fido2AuthenticatorService as Fido2AuthenticatorServiceAbstraction } from "@bitwarden/common/webauthn/abstractions/fido2-authenticator.service.abstraction";
|
||||||
|
import { Fido2ClientService as Fido2ClientServiceAbstraction } from "@bitwarden/common/webauthn/abstractions/fido2-client.service.abstraction";
|
||||||
import { Fido2UserInterfaceService as Fido2UserInterfaceServiceAbstraction } from "@bitwarden/common/webauthn/abstractions/fido2-user-interface.service.abstraction";
|
import { Fido2UserInterfaceService as Fido2UserInterfaceServiceAbstraction } from "@bitwarden/common/webauthn/abstractions/fido2-user-interface.service.abstraction";
|
||||||
import { Fido2Service as Fido2ServiceAbstraction } from "@bitwarden/common/webauthn/abstractions/fido2.service.abstraction";
|
import { Fido2AuthenticatorService } from "@bitwarden/common/webauthn/services/fido2-authenticator.service";
|
||||||
import { Fido2Service } from "@bitwarden/common/webauthn/services/fido2.service";
|
import { Fido2ClientService } from "@bitwarden/common/webauthn/services/fido2-client.service";
|
||||||
|
|
||||||
import ContextMenusBackground from "../autofill/background/context-menus.background";
|
import ContextMenusBackground from "../autofill/background/context-menus.background";
|
||||||
import NotificationBackground from "../autofill/background/notification.background";
|
import NotificationBackground from "../autofill/background/notification.background";
|
||||||
@@ -178,7 +180,8 @@ export default class MainBackground {
|
|||||||
userVerificationApiService: UserVerificationApiServiceAbstraction;
|
userVerificationApiService: UserVerificationApiServiceAbstraction;
|
||||||
syncNotifierService: SyncNotifierServiceAbstraction;
|
syncNotifierService: SyncNotifierServiceAbstraction;
|
||||||
fido2UserInterfaceService: Fido2UserInterfaceServiceAbstraction;
|
fido2UserInterfaceService: Fido2UserInterfaceServiceAbstraction;
|
||||||
fido2Service: Fido2ServiceAbstraction;
|
fido2AuthenticatorService: Fido2AuthenticatorServiceAbstraction;
|
||||||
|
fido2ClientService: Fido2ClientServiceAbstraction;
|
||||||
avatarUpdateService: AvatarUpdateServiceAbstraction;
|
avatarUpdateService: AvatarUpdateServiceAbstraction;
|
||||||
mainContextMenuHandler: MainContextMenuHandler;
|
mainContextMenuHandler: MainContextMenuHandler;
|
||||||
cipherContextMenuHandler: CipherContextMenuHandler;
|
cipherContextMenuHandler: CipherContextMenuHandler;
|
||||||
@@ -481,7 +484,11 @@ export default class MainBackground {
|
|||||||
);
|
);
|
||||||
|
|
||||||
this.fido2UserInterfaceService = new BrowserFido2UserInterfaceService(this.popupUtilsService);
|
this.fido2UserInterfaceService = new BrowserFido2UserInterfaceService(this.popupUtilsService);
|
||||||
this.fido2Service = new Fido2Service(this.fido2UserInterfaceService, this.cipherService);
|
this.fido2AuthenticatorService = new Fido2AuthenticatorService(
|
||||||
|
this.cipherService,
|
||||||
|
this.fido2UserInterfaceService
|
||||||
|
);
|
||||||
|
this.fido2ClientService = new Fido2ClientService(this.fido2AuthenticatorService);
|
||||||
|
|
||||||
const systemUtilsServiceReloadCallback = () => {
|
const systemUtilsServiceReloadCallback = () => {
|
||||||
const forceWindowReload =
|
const forceWindowReload =
|
||||||
|
|||||||
@@ -223,11 +223,11 @@ export default class RuntimeBackground {
|
|||||||
this.abortControllers.get(msg.abortedRequestId)?.abort();
|
this.abortControllers.get(msg.abortedRequestId)?.abort();
|
||||||
break;
|
break;
|
||||||
case "fido2RegisterCredentialRequest":
|
case "fido2RegisterCredentialRequest":
|
||||||
return await this.main.fido2Service
|
return await this.main.fido2ClientService
|
||||||
.createCredential(msg.data, this.createAbortController(msg.requestId))
|
.createCredential(msg.data, this.createAbortController(msg.requestId))
|
||||||
.finally(() => this.abortControllers.delete(msg.requestId));
|
.finally(() => this.abortControllers.delete(msg.requestId));
|
||||||
case "fido2GetCredentialRequest":
|
case "fido2GetCredentialRequest":
|
||||||
return await this.main.fido2Service
|
return await this.main.fido2ClientService
|
||||||
.assertCredential(msg.data, this.createAbortController(msg.requestId))
|
.assertCredential(msg.data, this.createAbortController(msg.requestId))
|
||||||
.finally(() => this.abortControllers.delete(msg.requestId));
|
.finally(() => this.abortControllers.delete(msg.requestId));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
import { Fido2Utils } from "@bitwarden/common/webauthn/abstractions/fido2-utils";
|
|
||||||
import {
|
import {
|
||||||
CredentialAssertParams,
|
CreateCredentialParams,
|
||||||
CredentialAssertResult,
|
CreateCredentialResult,
|
||||||
CredentialRegistrationParams,
|
AssertCredentialParams,
|
||||||
CredentialRegistrationResult,
|
AssertCredentialResult,
|
||||||
} from "@bitwarden/common/webauthn/abstractions/fido2.service.abstraction";
|
} from "@bitwarden/common/webauthn/abstractions/fido2-client.service.abstraction";
|
||||||
|
import { Fido2Utils } from "@bitwarden/common/webauthn/abstractions/fido2-utils";
|
||||||
|
|
||||||
class BitAuthenticatorAttestationResponse implements AuthenticatorAttestationResponse {
|
class BitAuthenticatorAttestationResponse implements AuthenticatorAttestationResponse {
|
||||||
clientDataJSON: ArrayBuffer;
|
clientDataJSON: ArrayBuffer;
|
||||||
attestationObject: ArrayBuffer;
|
attestationObject: ArrayBuffer;
|
||||||
|
|
||||||
constructor(private result: CredentialRegistrationResult) {
|
constructor(private result: CreateCredentialResult) {
|
||||||
this.clientDataJSON = Fido2Utils.stringToBuffer(result.clientDataJSON);
|
this.clientDataJSON = Fido2Utils.stringToBuffer(result.clientDataJSON);
|
||||||
this.attestationObject = Fido2Utils.stringToBuffer(result.attestationObject);
|
this.attestationObject = Fido2Utils.stringToBuffer(result.attestationObject);
|
||||||
}
|
}
|
||||||
@@ -35,8 +35,9 @@ class BitAuthenticatorAttestationResponse implements AuthenticatorAttestationRes
|
|||||||
export class WebauthnUtils {
|
export class WebauthnUtils {
|
||||||
static mapCredentialCreationOptions(
|
static mapCredentialCreationOptions(
|
||||||
options: CredentialCreationOptions,
|
options: CredentialCreationOptions,
|
||||||
origin: string
|
origin: string,
|
||||||
): CredentialRegistrationParams {
|
sameOriginWithAncestors: boolean
|
||||||
|
): CreateCredentialParams {
|
||||||
const keyOptions = options.publicKey;
|
const keyOptions = options.publicKey;
|
||||||
|
|
||||||
if (keyOptions == undefined) {
|
if (keyOptions == undefined) {
|
||||||
@@ -55,15 +56,12 @@ export class WebauthnUtils {
|
|||||||
excludeCredentials: keyOptions.excludeCredentials?.map((credential) => ({
|
excludeCredentials: keyOptions.excludeCredentials?.map((credential) => ({
|
||||||
id: Fido2Utils.bufferToString(credential.id),
|
id: Fido2Utils.bufferToString(credential.id),
|
||||||
transports: credential.transports,
|
transports: credential.transports,
|
||||||
|
type: credential.type,
|
||||||
})),
|
})),
|
||||||
extensions: {
|
extensions: undefined, // extensions not currently supported
|
||||||
appid: keyOptions.extensions?.appid,
|
|
||||||
appidExclude: keyOptions.extensions?.appidExclude,
|
|
||||||
credProps: keyOptions.extensions?.credProps,
|
|
||||||
uvm: keyOptions.extensions?.uvm,
|
|
||||||
},
|
|
||||||
pubKeyCredParams: keyOptions.pubKeyCredParams.map((params) => ({
|
pubKeyCredParams: keyOptions.pubKeyCredParams.map((params) => ({
|
||||||
alg: params.alg,
|
alg: params.alg,
|
||||||
|
type: params.type,
|
||||||
})),
|
})),
|
||||||
rp: {
|
rp: {
|
||||||
id: keyOptions.rp.id,
|
id: keyOptions.rp.id,
|
||||||
@@ -74,12 +72,11 @@ export class WebauthnUtils {
|
|||||||
displayName: keyOptions.user.displayName,
|
displayName: keyOptions.user.displayName,
|
||||||
},
|
},
|
||||||
timeout: keyOptions.timeout,
|
timeout: keyOptions.timeout,
|
||||||
|
sameOriginWithAncestors,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
static mapCredentialRegistrationResult(
|
static mapCredentialRegistrationResult(result: CreateCredentialResult): PublicKeyCredential {
|
||||||
result: CredentialRegistrationResult
|
|
||||||
): PublicKeyCredential {
|
|
||||||
return {
|
return {
|
||||||
id: result.credentialId,
|
id: result.credentialId,
|
||||||
rawId: Fido2Utils.stringToBuffer(result.credentialId),
|
rawId: Fido2Utils.stringToBuffer(result.credentialId),
|
||||||
@@ -92,8 +89,9 @@ export class WebauthnUtils {
|
|||||||
|
|
||||||
static mapCredentialRequestOptions(
|
static mapCredentialRequestOptions(
|
||||||
options: CredentialRequestOptions,
|
options: CredentialRequestOptions,
|
||||||
origin: string
|
origin: string,
|
||||||
): CredentialAssertParams {
|
sameOriginWithAncestors: boolean
|
||||||
|
): AssertCredentialParams {
|
||||||
const keyOptions = options.publicKey;
|
const keyOptions = options.publicKey;
|
||||||
|
|
||||||
if (keyOptions == undefined) {
|
if (keyOptions == undefined) {
|
||||||
@@ -108,10 +106,11 @@ export class WebauthnUtils {
|
|||||||
rpId: keyOptions.rpId,
|
rpId: keyOptions.rpId,
|
||||||
userVerification: keyOptions.userVerification,
|
userVerification: keyOptions.userVerification,
|
||||||
timeout: keyOptions.timeout,
|
timeout: keyOptions.timeout,
|
||||||
|
sameOriginWithAncestors,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
static mapCredentialAssertResult(result: CredentialAssertResult): PublicKeyCredential {
|
static mapCredentialAssertResult(result: AssertCredentialResult): PublicKeyCredential {
|
||||||
return {
|
return {
|
||||||
id: result.credentialId,
|
id: result.credentialId,
|
||||||
rawId: Fido2Utils.stringToBuffer(result.credentialId),
|
rawId: Fido2Utils.stringToBuffer(result.credentialId),
|
||||||
|
|||||||
@@ -5,13 +5,24 @@ import {
|
|||||||
Fido2UserInterfaceService as Fido2UserInterfaceServiceAbstraction,
|
Fido2UserInterfaceService as Fido2UserInterfaceServiceAbstraction,
|
||||||
NewCredentialParams,
|
NewCredentialParams,
|
||||||
} from "@bitwarden/common/webauthn/abstractions/fido2-user-interface.service.abstraction";
|
} from "@bitwarden/common/webauthn/abstractions/fido2-user-interface.service.abstraction";
|
||||||
import { RequestAbortedError } from "@bitwarden/common/webauthn/abstractions/fido2.service.abstraction";
|
|
||||||
|
|
||||||
import { BrowserApi } from "../../browser/browserApi";
|
import { BrowserApi } from "../../browser/browserApi";
|
||||||
import { PopupUtilsService } from "../../popup/services/popup-utils.service";
|
import { PopupUtilsService } from "../../popup/services/popup-utils.service";
|
||||||
|
|
||||||
const BrowserFido2MessageName = "BrowserFido2UserInterfaceServiceMessage";
|
const BrowserFido2MessageName = "BrowserFido2UserInterfaceServiceMessage";
|
||||||
|
|
||||||
|
export class Fido2Error extends Error {
|
||||||
|
constructor(message: string, readonly fallbackRequested = false) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class RequestAbortedError extends Fido2Error {
|
||||||
|
constructor(fallbackRequested = false) {
|
||||||
|
super("Fido2 request was aborted", fallbackRequested);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export type BrowserFido2Message = { requestId: string } & (
|
export type BrowserFido2Message = { requestId: string } & (
|
||||||
| {
|
| {
|
||||||
type: "PickCredentialRequest";
|
type: "PickCredentialRequest";
|
||||||
@@ -198,6 +209,21 @@ export class BrowserFido2UserInterfaceService implements Fido2UserInterfaceServi
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async confirmNewNonDiscoverableCredential(
|
||||||
|
params: NewCredentialParams,
|
||||||
|
abortController?: AbortController
|
||||||
|
): Promise<string> {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
async informExcludedCredential(
|
||||||
|
existingCipherIds: string[],
|
||||||
|
newCredential: NewCredentialParams,
|
||||||
|
abortController?: AbortController
|
||||||
|
): Promise<void> {
|
||||||
|
// Not Implemented
|
||||||
|
}
|
||||||
|
|
||||||
private setAbortTimeout(abortController: AbortController) {
|
private setAbortTimeout(abortController: AbortController) {
|
||||||
return setTimeout(() => abortController.abort());
|
return setTimeout(() => abortController.abort());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import {
|
import {
|
||||||
CredentialAssertParams,
|
CreateCredentialParams,
|
||||||
CredentialAssertResult,
|
CreateCredentialResult,
|
||||||
CredentialRegistrationParams,
|
AssertCredentialParams,
|
||||||
CredentialRegistrationResult,
|
AssertCredentialResult,
|
||||||
} from "@bitwarden/common/webauthn/abstractions/fido2.service.abstraction";
|
} from "@bitwarden/common/webauthn/abstractions/fido2-client.service.abstraction";
|
||||||
|
|
||||||
export enum MessageType {
|
export enum MessageType {
|
||||||
CredentialCreationRequest,
|
CredentialCreationRequest,
|
||||||
@@ -17,22 +17,22 @@ export enum MessageType {
|
|||||||
|
|
||||||
export type CredentialCreationRequest = {
|
export type CredentialCreationRequest = {
|
||||||
type: MessageType.CredentialCreationRequest;
|
type: MessageType.CredentialCreationRequest;
|
||||||
data: CredentialRegistrationParams;
|
data: CreateCredentialParams;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type CredentialCreationResponse = {
|
export type CredentialCreationResponse = {
|
||||||
type: MessageType.CredentialCreationResponse;
|
type: MessageType.CredentialCreationResponse;
|
||||||
result?: CredentialRegistrationResult;
|
result?: CreateCredentialResult;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type CredentialGetRequest = {
|
export type CredentialGetRequest = {
|
||||||
type: MessageType.CredentialGetRequest;
|
type: MessageType.CredentialGetRequest;
|
||||||
data: CredentialAssertParams;
|
data: AssertCredentialParams;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type CredentialGetResponse = {
|
export type CredentialGetResponse = {
|
||||||
type: MessageType.CredentialGetResponse;
|
type: MessageType.CredentialGetResponse;
|
||||||
result?: CredentialAssertResult;
|
result?: AssertCredentialResult;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type AbortRequest = {
|
export type AbortRequest = {
|
||||||
|
|||||||
@@ -22,7 +22,8 @@ navigator.credentials.create = async (
|
|||||||
const response = await messenger.request(
|
const response = await messenger.request(
|
||||||
{
|
{
|
||||||
type: MessageType.CredentialCreationRequest,
|
type: MessageType.CredentialCreationRequest,
|
||||||
data: WebauthnUtils.mapCredentialCreationOptions(options, window.location.origin),
|
// TODO: Fix sameOriginWithAncestors!
|
||||||
|
data: WebauthnUtils.mapCredentialCreationOptions(options, window.location.origin, true),
|
||||||
},
|
},
|
||||||
abortController
|
abortController
|
||||||
);
|
);
|
||||||
@@ -49,7 +50,8 @@ navigator.credentials.get = async (
|
|||||||
const response = await messenger.request(
|
const response = await messenger.request(
|
||||||
{
|
{
|
||||||
type: MessageType.CredentialGetRequest,
|
type: MessageType.CredentialGetRequest,
|
||||||
data: WebauthnUtils.mapCredentialRequestOptions(options, window.location.origin),
|
// TODO: Fix sameOriginWithAncestors!
|
||||||
|
data: WebauthnUtils.mapCredentialRequestOptions(options, window.location.origin, true),
|
||||||
},
|
},
|
||||||
abortController
|
abortController
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -127,7 +127,11 @@ export class CipherRequest {
|
|||||||
this.fido2Key = new Fido2KeyApi();
|
this.fido2Key = new Fido2KeyApi();
|
||||||
this.fido2Key.keyType =
|
this.fido2Key.keyType =
|
||||||
cipher.fido2Key.keyType != null
|
cipher.fido2Key.keyType != null
|
||||||
? (cipher.fido2Key.keyType.encryptedString as "ECDSA")
|
? (cipher.fido2Key.keyType.encryptedString as "public-key")
|
||||||
|
: null;
|
||||||
|
this.fido2Key.keyAlgorithm =
|
||||||
|
cipher.fido2Key.keyAlgorithm != null
|
||||||
|
? (cipher.fido2Key.keyAlgorithm.encryptedString as "ECDSA")
|
||||||
: null;
|
: null;
|
||||||
this.fido2Key.keyCurve =
|
this.fido2Key.keyCurve =
|
||||||
cipher.fido2Key.keyCurve != null
|
cipher.fido2Key.keyCurve != null
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ export class Fido2AutenticatorError extends Error {
|
|||||||
|
|
||||||
export interface PublicKeyCredentialDescriptor {
|
export interface PublicKeyCredentialDescriptor {
|
||||||
id: BufferSource;
|
id: BufferSource;
|
||||||
transports?: ("ble" | "internal" | "nfc" | "usb")[];
|
transports?: ("ble" | "hybrid" | "internal" | "nfc" | "usb")[];
|
||||||
type: "public-key";
|
type: "public-key";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ export interface CreateCredentialParams {
|
|||||||
challenge: string; // b64 encoded
|
challenge: string; // b64 encoded
|
||||||
excludeCredentials?: {
|
excludeCredentials?: {
|
||||||
id: string; // b64 encoded
|
id: string; // b64 encoded
|
||||||
transports?: ("ble" | "internal" | "nfc" | "usb")[];
|
transports?: ("ble" | "hybrid" | "internal" | "nfc" | "usb")[];
|
||||||
type: "public-key";
|
type: "public-key";
|
||||||
}[];
|
}[];
|
||||||
extensions?: {
|
extensions?: {
|
||||||
|
|||||||
@@ -1,101 +1,110 @@
|
|||||||
export type UserVerification = "discouraged" | "preferred" | "required";
|
/**
|
||||||
|
*
|
||||||
|
* REMOVE BEFORE MERGE
|
||||||
|
*
|
||||||
|
* This is the old version of our FIDO2 client which was built according to spec.
|
||||||
|
* It left here for reference purposes until we no longer need it.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
export interface CredentialRegistrationParams {
|
// export type UserVerification = "discouraged" | "preferred" | "required";
|
||||||
origin: string;
|
|
||||||
attestation?: "direct" | "enterprise" | "indirect" | "none";
|
|
||||||
authenticatorSelection?: {
|
|
||||||
// authenticatorAttachment?: AuthenticatorAttachment; // not used
|
|
||||||
requireResidentKey?: boolean;
|
|
||||||
residentKey?: "discouraged" | "preferred" | "required";
|
|
||||||
userVerification?: UserVerification;
|
|
||||||
};
|
|
||||||
challenge: string; // b64 encoded
|
|
||||||
excludeCredentials?: {
|
|
||||||
id: string; // b64 encoded
|
|
||||||
transports?: ("ble" | "internal" | "nfc" | "usb")[];
|
|
||||||
// type: "public-key"; // not used
|
|
||||||
}[];
|
|
||||||
extensions?: {
|
|
||||||
appid?: string;
|
|
||||||
appidExclude?: string;
|
|
||||||
credProps?: boolean;
|
|
||||||
uvm?: boolean;
|
|
||||||
};
|
|
||||||
pubKeyCredParams: {
|
|
||||||
alg: number;
|
|
||||||
// type: "public-key"; // not used
|
|
||||||
}[];
|
|
||||||
rp: {
|
|
||||||
id?: string;
|
|
||||||
name: string;
|
|
||||||
};
|
|
||||||
user: {
|
|
||||||
id: string; // b64 encoded
|
|
||||||
displayName: string;
|
|
||||||
};
|
|
||||||
timeout: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface CredentialRegistrationResult {
|
// export interface CredentialRegistrationParams {
|
||||||
credentialId: string;
|
// origin: string;
|
||||||
clientDataJSON: string;
|
// attestation?: "direct" | "enterprise" | "indirect" | "none";
|
||||||
attestationObject: string;
|
// authenticatorSelection?: {
|
||||||
authData: string;
|
// // authenticatorAttachment?: AuthenticatorAttachment; // not used
|
||||||
publicKeyAlgorithm: number;
|
// requireResidentKey?: boolean;
|
||||||
transports: string[];
|
// residentKey?: "discouraged" | "preferred" | "required";
|
||||||
}
|
// userVerification?: UserVerification;
|
||||||
|
// };
|
||||||
|
// challenge: string; // b64 encoded
|
||||||
|
// excludeCredentials?: {
|
||||||
|
// id: string; // b64 encoded
|
||||||
|
// transports?: ("ble" | "internal" | "nfc" | "usb")[];
|
||||||
|
// // type: "public-key"; // not used
|
||||||
|
// }[];
|
||||||
|
// extensions?: {
|
||||||
|
// appid?: string;
|
||||||
|
// appidExclude?: string;
|
||||||
|
// credProps?: boolean;
|
||||||
|
// uvm?: boolean;
|
||||||
|
// };
|
||||||
|
// pubKeyCredParams: {
|
||||||
|
// alg: number;
|
||||||
|
// // type: "public-key"; // not used
|
||||||
|
// }[];
|
||||||
|
// rp: {
|
||||||
|
// id?: string;
|
||||||
|
// name: string;
|
||||||
|
// };
|
||||||
|
// user: {
|
||||||
|
// id: string; // b64 encoded
|
||||||
|
// displayName: string;
|
||||||
|
// };
|
||||||
|
// timeout: number;
|
||||||
|
// }
|
||||||
|
|
||||||
export interface CredentialAssertParams {
|
// export interface CredentialRegistrationResult {
|
||||||
allowedCredentialIds: string[];
|
// credentialId: string;
|
||||||
rpId: string;
|
// clientDataJSON: string;
|
||||||
origin: string;
|
// attestationObject: string;
|
||||||
challenge: string;
|
// authData: string;
|
||||||
userVerification?: UserVerification;
|
// publicKeyAlgorithm: number;
|
||||||
timeout: number;
|
// transports: string[];
|
||||||
}
|
// }
|
||||||
|
|
||||||
export interface CredentialAssertResult {
|
// export interface CredentialAssertParams {
|
||||||
credentialId: string;
|
// allowedCredentialIds: string[];
|
||||||
clientDataJSON: string;
|
// rpId: string;
|
||||||
authenticatorData: string;
|
// origin: string;
|
||||||
signature: string;
|
// challenge: string;
|
||||||
userHandle: string;
|
// userVerification?: UserVerification;
|
||||||
}
|
// timeout: number;
|
||||||
|
// }
|
||||||
|
|
||||||
export class Fido2Error extends Error {
|
// export interface CredentialAssertResult {
|
||||||
constructor(message: string, readonly fallbackRequested = false) {
|
// credentialId: string;
|
||||||
super(message);
|
// clientDataJSON: string;
|
||||||
}
|
// authenticatorData: string;
|
||||||
}
|
// signature: string;
|
||||||
|
// userHandle: string;
|
||||||
|
// }
|
||||||
|
|
||||||
export class RequestAbortedError extends Fido2Error {
|
// export class Fido2Error extends Error {
|
||||||
constructor(fallbackRequested = false) {
|
// constructor(message: string, readonly fallbackRequested = false) {
|
||||||
super("Fido2 request was aborted", fallbackRequested);
|
// super(message);
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
export class NoCredentialFoundError extends Fido2Error {
|
// export class RequestAbortedError extends Fido2Error {
|
||||||
constructor() {
|
// constructor(fallbackRequested = false) {
|
||||||
super("No valid credential found", true);
|
// super("Fido2 request was aborted", fallbackRequested);
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
export class OriginMismatchError extends Fido2Error {
|
// export class NoCredentialFoundError extends Fido2Error {
|
||||||
constructor() {
|
// constructor() {
|
||||||
super(
|
// super("No valid credential found", true);
|
||||||
"Authentication requests must originate from the same source that created the credential.",
|
// }
|
||||||
false
|
// }
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export abstract class Fido2Service {
|
// export class OriginMismatchError extends Fido2Error {
|
||||||
createCredential: (
|
// constructor() {
|
||||||
params: CredentialRegistrationParams,
|
// super(
|
||||||
abortController?: AbortController
|
// "Authentication requests must originate from the same source that created the credential.",
|
||||||
) => Promise<CredentialRegistrationResult>;
|
// false
|
||||||
assertCredential: (
|
// );
|
||||||
params: CredentialAssertParams,
|
// }
|
||||||
abortController?: AbortController
|
// }
|
||||||
) => Promise<CredentialAssertResult>;
|
|
||||||
}
|
// export abstract class Fido2Service {
|
||||||
|
// createCredential: (
|
||||||
|
// params: CredentialRegistrationParams,
|
||||||
|
// abortController?: AbortController
|
||||||
|
// ) => Promise<CredentialRegistrationResult>;
|
||||||
|
// assertCredential: (
|
||||||
|
// params: CredentialAssertParams,
|
||||||
|
// abortController?: AbortController
|
||||||
|
// ) => Promise<CredentialAssertResult>;
|
||||||
|
// }
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { CBOR } from "cbor-redux";
|
import { CBOR } from "cbor-redux";
|
||||||
|
|
||||||
import { Utils } from "../../misc/utils";
|
import { Utils } from "../../misc/utils";
|
||||||
|
import { CipherService } from "../../vault/abstractions/cipher.service";
|
||||||
import { CipherType } from "../../vault/enums/cipher-type";
|
import { CipherType } from "../../vault/enums/cipher-type";
|
||||||
import { CipherView } from "../../vault/models/view/cipher.view";
|
import { CipherView } from "../../vault/models/view/cipher.view";
|
||||||
import { CipherService } from "../../vault/services/cipher.service";
|
|
||||||
import {
|
import {
|
||||||
Fido2AlgorithmIdentifier,
|
Fido2AlgorithmIdentifier,
|
||||||
Fido2AutenticatorError,
|
Fido2AutenticatorError,
|
||||||
@@ -13,6 +13,7 @@ import {
|
|||||||
Fido2AuthenticatorMakeCredentialResult,
|
Fido2AuthenticatorMakeCredentialResult,
|
||||||
Fido2AuthenticatorMakeCredentialsParams,
|
Fido2AuthenticatorMakeCredentialsParams,
|
||||||
Fido2AuthenticatorService as Fido2AuthenticatorServiceAbstraction,
|
Fido2AuthenticatorService as Fido2AuthenticatorServiceAbstraction,
|
||||||
|
PublicKeyCredentialDescriptor,
|
||||||
} from "../abstractions/fido2-authenticator.service.abstraction";
|
} from "../abstractions/fido2-authenticator.service.abstraction";
|
||||||
import { Fido2UserInterfaceService } from "../abstractions/fido2-user-interface.service.abstraction";
|
import { Fido2UserInterfaceService } from "../abstractions/fido2-user-interface.service.abstraction";
|
||||||
import { Fido2Utils } from "../abstractions/fido2-utils";
|
import { Fido2Utils } from "../abstractions/fido2-utils";
|
||||||
|
|||||||
@@ -1,465 +1,474 @@
|
|||||||
import { CBOR } from "cbor-redux";
|
/**
|
||||||
|
*
|
||||||
|
* REMOVE BEFORE MERGE
|
||||||
|
*
|
||||||
|
* This is the old version of our FIDO2 client which was built according to spec.
|
||||||
|
* It left here for reference purposes until we no longer need it.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
import { Utils } from "../../misc/utils";
|
// import { CBOR } from "cbor-redux";
|
||||||
import { CipherService } from "../../vault/abstractions/cipher.service";
|
|
||||||
import { CipherType } from "../../vault/enums/cipher-type";
|
|
||||||
import { Cipher } from "../../vault/models/domain/cipher";
|
|
||||||
import { CipherView } from "../../vault/models/view/cipher.view";
|
|
||||||
import { Fido2UserInterfaceService } from "../abstractions/fido2-user-interface.service.abstraction";
|
|
||||||
import { Fido2Utils } from "../abstractions/fido2-utils";
|
|
||||||
import {
|
|
||||||
CredentialAssertParams,
|
|
||||||
CredentialAssertResult,
|
|
||||||
CredentialRegistrationParams,
|
|
||||||
CredentialRegistrationResult,
|
|
||||||
Fido2Service as Fido2ServiceAbstraction,
|
|
||||||
NoCredentialFoundError,
|
|
||||||
UserVerification,
|
|
||||||
} from "../abstractions/fido2.service.abstraction";
|
|
||||||
import { Fido2KeyView } from "../models/view/fido2-key.view";
|
|
||||||
|
|
||||||
import { CredentialId } from "./credential-id";
|
// import { Utils } from "../../misc/utils";
|
||||||
import { joseToDer } from "./ecdsa-utils";
|
// import { CipherService } from "../../vault/abstractions/cipher.service";
|
||||||
|
// import { CipherType } from "../../vault/enums/cipher-type";
|
||||||
|
// import { Cipher } from "../../vault/models/domain/cipher";
|
||||||
|
// import { CipherView } from "../../vault/models/view/cipher.view";
|
||||||
|
// import { Fido2UserInterfaceService } from "../abstractions/fido2-user-interface.service.abstraction";
|
||||||
|
// import { Fido2Utils } from "../abstractions/fido2-utils";
|
||||||
|
// import {
|
||||||
|
// CredentialAssertParams,
|
||||||
|
// CredentialAssertResult,
|
||||||
|
// CredentialRegistrationParams,
|
||||||
|
// CredentialRegistrationResult,
|
||||||
|
// Fido2Service as Fido2ServiceAbstraction,
|
||||||
|
// NoCredentialFoundError,
|
||||||
|
// UserVerification,
|
||||||
|
// } from "../abstractions/fido2.service.abstraction";
|
||||||
|
// import { Fido2KeyView } from "../models/view/fido2-key.view";
|
||||||
|
|
||||||
// We support self-signing, but Google won't accept it.
|
// import { CredentialId } from "./credential-id";
|
||||||
// TODO: Look into supporting self-signed packed format.
|
// import { joseToDer } from "./ecdsa-utils";
|
||||||
const STANDARD_ATTESTATION_FORMAT: "none" | "packed" = "none";
|
|
||||||
const TIMEOUTS = {
|
|
||||||
NO_VERIFICATION: {
|
|
||||||
DEFAULT: 120000,
|
|
||||||
MIN: 30000,
|
|
||||||
MAX: 180000,
|
|
||||||
},
|
|
||||||
WITH_VERIFICATION: {
|
|
||||||
DEFAULT: 300000,
|
|
||||||
MIN: 30000,
|
|
||||||
MAX: 600000,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
interface BitCredential {
|
// // We support self-signing, but Google won't accept it.
|
||||||
credentialId: CredentialId;
|
// // TODO: Look into supporting self-signed packed format.
|
||||||
keyType: "ECDSA";
|
// const STANDARD_ATTESTATION_FORMAT: "none" | "packed" = "none";
|
||||||
keyCurve: "P-256";
|
// const TIMEOUTS = {
|
||||||
keyValue: CryptoKey;
|
// NO_VERIFICATION: {
|
||||||
rpId: string;
|
// DEFAULT: 120000,
|
||||||
rpName: string;
|
// MIN: 30000,
|
||||||
userHandle: Uint8Array;
|
// MAX: 180000,
|
||||||
userName: string;
|
// },
|
||||||
origin: string;
|
// WITH_VERIFICATION: {
|
||||||
}
|
// DEFAULT: 300000,
|
||||||
|
// MIN: 30000,
|
||||||
|
// MAX: 600000,
|
||||||
|
// },
|
||||||
|
// };
|
||||||
|
|
||||||
const KeyUsages: KeyUsage[] = ["sign"];
|
// interface BitCredential {
|
||||||
|
// credentialId: CredentialId;
|
||||||
export class Fido2Service implements Fido2ServiceAbstraction {
|
// keyType: "ECDSA";
|
||||||
constructor(
|
// keyCurve: "P-256";
|
||||||
private fido2UserInterfaceService: Fido2UserInterfaceService,
|
// keyValue: CryptoKey;
|
||||||
private cipherService: CipherService
|
// rpId: string;
|
||||||
) {}
|
// rpName: string;
|
||||||
|
// userHandle: Uint8Array;
|
||||||
async createCredential(
|
// userName: string;
|
||||||
params: CredentialRegistrationParams,
|
// origin: string;
|
||||||
abortController = new AbortController()
|
|
||||||
): Promise<CredentialRegistrationResult> {
|
|
||||||
// Comment: Timeouts could potentially be implemented using decorators.
|
|
||||||
// But since I try to use decorators a little as possible and only
|
|
||||||
// for the most generic solutions, I'm gonne leave this as is untill peer review.
|
|
||||||
const timeout = setAbortTimeout(
|
|
||||||
abortController,
|
|
||||||
params.authenticatorSelection.userVerification,
|
|
||||||
params.timeout
|
|
||||||
);
|
|
||||||
|
|
||||||
const presence = await this.fido2UserInterfaceService.confirmNewCredential(
|
|
||||||
{
|
|
||||||
credentialName: params.rp.name,
|
|
||||||
userName: params.user.displayName,
|
|
||||||
},
|
|
||||||
abortController
|
|
||||||
);
|
|
||||||
|
|
||||||
const attestationFormat = STANDARD_ATTESTATION_FORMAT;
|
|
||||||
const encoder = new TextEncoder();
|
|
||||||
|
|
||||||
const clientData = encoder.encode(
|
|
||||||
JSON.stringify({
|
|
||||||
type: "webauthn.create",
|
|
||||||
challenge: params.challenge,
|
|
||||||
origin: params.origin,
|
|
||||||
crossOrigin: false,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
const keyPair = await crypto.subtle.generateKey(
|
|
||||||
{
|
|
||||||
name: "ECDSA",
|
|
||||||
namedCurve: "P-256",
|
|
||||||
},
|
|
||||||
true,
|
|
||||||
KeyUsages
|
|
||||||
);
|
|
||||||
|
|
||||||
const credentialId = await this.saveCredential({
|
|
||||||
keyType: "ECDSA",
|
|
||||||
keyCurve: "P-256",
|
|
||||||
keyValue: keyPair.privateKey,
|
|
||||||
origin: params.origin,
|
|
||||||
rpId: params.rp.id,
|
|
||||||
rpName: params.rp.name,
|
|
||||||
userHandle: Fido2Utils.stringToBuffer(params.user.id),
|
|
||||||
userName: params.user.displayName,
|
|
||||||
});
|
|
||||||
|
|
||||||
const authData = await generateAuthData({
|
|
||||||
rpId: params.rp.id,
|
|
||||||
credentialId,
|
|
||||||
userPresence: presence,
|
|
||||||
userVerification: true, // TODO: Change to false
|
|
||||||
keyPair,
|
|
||||||
});
|
|
||||||
|
|
||||||
const asn1Der_signature = await generateSignature({
|
|
||||||
authData,
|
|
||||||
clientData,
|
|
||||||
privateKey: keyPair.privateKey,
|
|
||||||
});
|
|
||||||
|
|
||||||
const attestationObject = new Uint8Array(
|
|
||||||
CBOR.encode({
|
|
||||||
fmt: attestationFormat,
|
|
||||||
attStmt:
|
|
||||||
attestationFormat === "packed"
|
|
||||||
? {
|
|
||||||
alg: -7,
|
|
||||||
sig: asn1Der_signature,
|
|
||||||
}
|
|
||||||
: {},
|
|
||||||
authData,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
clearTimeout(timeout);
|
|
||||||
|
|
||||||
return {
|
|
||||||
credentialId: Fido2Utils.bufferToString(credentialId.raw),
|
|
||||||
clientDataJSON: Fido2Utils.bufferToString(clientData),
|
|
||||||
attestationObject: Fido2Utils.bufferToString(attestationObject),
|
|
||||||
authData: Fido2Utils.bufferToString(authData),
|
|
||||||
publicKeyAlgorithm: -7,
|
|
||||||
transports: ["nfc", "usb"],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
async assertCredential(
|
|
||||||
params: CredentialAssertParams,
|
|
||||||
abortController = new AbortController()
|
|
||||||
): Promise<CredentialAssertResult> {
|
|
||||||
const timeout = setAbortTimeout(abortController, params.userVerification, params.timeout);
|
|
||||||
let credential: BitCredential | undefined;
|
|
||||||
|
|
||||||
if (params.allowedCredentialIds && params.allowedCredentialIds.length > 0) {
|
|
||||||
// We're looking for regular non-resident keys
|
|
||||||
credential = await this.getCredential(params.allowedCredentialIds);
|
|
||||||
|
|
||||||
if (credential === undefined) {
|
|
||||||
throw new NoCredentialFoundError();
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Google doesn't work with this. Look into how we're supposed to check this
|
|
||||||
// if (credential.origin !== params.origin) {
|
|
||||||
// throw new OriginMismatchError();
|
|
||||||
// }
|
// }
|
||||||
|
|
||||||
await this.fido2UserInterfaceService.confirmCredential(
|
// const KeyUsages: KeyUsage[] = ["sign"];
|
||||||
credential.credentialId.encoded,
|
|
||||||
abortController
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
// We're looking for a resident key
|
|
||||||
const credentials = await this.getCredentialsByRp(params.rpId);
|
|
||||||
|
|
||||||
if (credentials.length === 0) {
|
// export class Fido2Service implements Fido2ServiceAbstraction {
|
||||||
throw new NoCredentialFoundError();
|
// constructor(
|
||||||
}
|
// private fido2UserInterfaceService: Fido2UserInterfaceService,
|
||||||
|
// private cipherService: CipherService
|
||||||
|
// ) {}
|
||||||
|
|
||||||
const pickedId = await this.fido2UserInterfaceService.pickCredential(
|
// async createCredential(
|
||||||
credentials.map((c) => c.credentialId.encoded),
|
// params: CredentialRegistrationParams,
|
||||||
abortController
|
// abortController = new AbortController()
|
||||||
);
|
// ): Promise<CredentialRegistrationResult> {
|
||||||
credential = credentials.find((c) => c.credentialId.encoded === pickedId);
|
// // Comment: Timeouts could potentially be implemented using decorators.
|
||||||
}
|
// // But since I try to use decorators a little as possible and only
|
||||||
|
// // for the most generic solutions, I'm gonne leave this as is untill peer review.
|
||||||
|
// const timeout = setAbortTimeout(
|
||||||
|
// abortController,
|
||||||
|
// params.authenticatorSelection.userVerification,
|
||||||
|
// params.timeout
|
||||||
|
// );
|
||||||
|
|
||||||
const encoder = new TextEncoder();
|
// const presence = await this.fido2UserInterfaceService.confirmNewCredential(
|
||||||
const clientData = encoder.encode(
|
// {
|
||||||
JSON.stringify({
|
// credentialName: params.rp.name,
|
||||||
type: "webauthn.get",
|
// userName: params.user.displayName,
|
||||||
challenge: params.challenge,
|
// },
|
||||||
origin: params.origin,
|
// abortController
|
||||||
})
|
// );
|
||||||
);
|
|
||||||
|
|
||||||
const authData = await generateAuthData({
|
// const attestationFormat = STANDARD_ATTESTATION_FORMAT;
|
||||||
credentialId: credential.credentialId,
|
// const encoder = new TextEncoder();
|
||||||
rpId: params.rpId,
|
|
||||||
userPresence: true,
|
|
||||||
userVerification: true, // TODO: Change to false!
|
|
||||||
});
|
|
||||||
|
|
||||||
const signature = await generateSignature({
|
// const clientData = encoder.encode(
|
||||||
authData,
|
// JSON.stringify({
|
||||||
clientData,
|
// type: "webauthn.create",
|
||||||
privateKey: credential.keyValue,
|
// challenge: params.challenge,
|
||||||
});
|
// origin: params.origin,
|
||||||
|
// crossOrigin: false,
|
||||||
|
// })
|
||||||
|
// );
|
||||||
|
// const keyPair = await crypto.subtle.generateKey(
|
||||||
|
// {
|
||||||
|
// name: "ECDSA",
|
||||||
|
// namedCurve: "P-256",
|
||||||
|
// },
|
||||||
|
// true,
|
||||||
|
// KeyUsages
|
||||||
|
// );
|
||||||
|
|
||||||
clearTimeout(timeout);
|
// const credentialId = await this.saveCredential({
|
||||||
|
// keyType: "ECDSA",
|
||||||
|
// keyCurve: "P-256",
|
||||||
|
// keyValue: keyPair.privateKey,
|
||||||
|
// origin: params.origin,
|
||||||
|
// rpId: params.rp.id,
|
||||||
|
// rpName: params.rp.name,
|
||||||
|
// userHandle: Fido2Utils.stringToBuffer(params.user.id),
|
||||||
|
// userName: params.user.displayName,
|
||||||
|
// });
|
||||||
|
|
||||||
return {
|
// const authData = await generateAuthData({
|
||||||
credentialId: credential.credentialId.encoded,
|
// rpId: params.rp.id,
|
||||||
clientDataJSON: Fido2Utils.bufferToString(clientData),
|
// credentialId,
|
||||||
authenticatorData: Fido2Utils.bufferToString(authData),
|
// userPresence: presence,
|
||||||
signature: Fido2Utils.bufferToString(signature),
|
// userVerification: true, // TODO: Change to false
|
||||||
userHandle: Fido2Utils.bufferToString(credential.userHandle),
|
// keyPair,
|
||||||
};
|
// });
|
||||||
}
|
|
||||||
|
|
||||||
private async getCredential(allowedCredentialIds: string[]): Promise<BitCredential | undefined> {
|
// const asn1Der_signature = await generateSignature({
|
||||||
let cipher: Cipher | undefined;
|
// authData,
|
||||||
for (const allowedCredential of allowedCredentialIds) {
|
// clientData,
|
||||||
cipher = await this.cipherService.get(allowedCredential);
|
// privateKey: keyPair.privateKey,
|
||||||
|
// });
|
||||||
|
|
||||||
if (cipher?.deletedDate != undefined) {
|
// const attestationObject = new Uint8Array(
|
||||||
cipher = undefined;
|
// CBOR.encode({
|
||||||
}
|
// fmt: attestationFormat,
|
||||||
|
// attStmt:
|
||||||
|
// attestationFormat === "packed"
|
||||||
|
// ? {
|
||||||
|
// alg: -7,
|
||||||
|
// sig: asn1Der_signature,
|
||||||
|
// }
|
||||||
|
// : {},
|
||||||
|
// authData,
|
||||||
|
// })
|
||||||
|
// );
|
||||||
|
|
||||||
if (cipher != undefined) {
|
// clearTimeout(timeout);
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cipher == undefined) {
|
// return {
|
||||||
return undefined;
|
// credentialId: Fido2Utils.bufferToString(credentialId.raw),
|
||||||
}
|
// clientDataJSON: Fido2Utils.bufferToString(clientData),
|
||||||
|
// attestationObject: Fido2Utils.bufferToString(attestationObject),
|
||||||
const cipherView = await cipher.decrypt();
|
// authData: Fido2Utils.bufferToString(authData),
|
||||||
return await mapCipherViewToBitCredential(cipherView);
|
// publicKeyAlgorithm: -7,
|
||||||
}
|
// transports: ["nfc", "usb"],
|
||||||
|
|
||||||
private async saveCredential(
|
|
||||||
credential: Omit<BitCredential, "credentialId">
|
|
||||||
): Promise<CredentialId> {
|
|
||||||
const pcks8Key = await crypto.subtle.exportKey("pkcs8", credential.keyValue);
|
|
||||||
|
|
||||||
const view = new CipherView();
|
|
||||||
view.type = CipherType.Fido2Key;
|
|
||||||
view.name = credential.rpName;
|
|
||||||
|
|
||||||
view.fido2Key = new Fido2KeyView();
|
|
||||||
view.fido2Key.origin = credential.origin;
|
|
||||||
view.fido2Key.keyType = credential.keyType;
|
|
||||||
view.fido2Key.keyCurve = credential.keyCurve;
|
|
||||||
view.fido2Key.keyValue = Fido2Utils.bufferToString(pcks8Key);
|
|
||||||
view.fido2Key.rpId = credential.rpId;
|
|
||||||
view.fido2Key.rpName = credential.rpName;
|
|
||||||
view.fido2Key.userHandle = Fido2Utils.bufferToString(credential.userHandle);
|
|
||||||
view.fido2Key.userName = credential.userName;
|
|
||||||
view.fido2Key.origin = credential.origin;
|
|
||||||
|
|
||||||
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 getCredentialsByRp(rpId: string): Promise<BitCredential[]> {
|
|
||||||
const allCipherViews = await this.cipherService.getAllDecrypted();
|
|
||||||
const cipherViews = allCipherViews.filter(
|
|
||||||
(cv) => !cv.isDeleted && cv.type === CipherType.Fido2Key && cv.fido2Key?.rpId === rpId
|
|
||||||
);
|
|
||||||
|
|
||||||
return await Promise.all(cipherViews.map((view) => mapCipherViewToBitCredential(view)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
interface AuthDataParams {
|
|
||||||
rpId: string;
|
|
||||||
credentialId: CredentialId;
|
|
||||||
userPresence: boolean;
|
|
||||||
userVerification: boolean;
|
|
||||||
keyPair?: CryptoKeyPair;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function mapCipherViewToBitCredential(cipherView: CipherView): Promise<BitCredential> {
|
|
||||||
const keyBuffer = Fido2Utils.stringToBuffer(cipherView.fido2Key.keyValue);
|
|
||||||
const privateKey = await crypto.subtle.importKey(
|
|
||||||
"pkcs8",
|
|
||||||
keyBuffer,
|
|
||||||
{
|
|
||||||
name: cipherView.fido2Key.keyType,
|
|
||||||
namedCurve: cipherView.fido2Key.keyCurve,
|
|
||||||
},
|
|
||||||
true,
|
|
||||||
KeyUsages
|
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
|
||||||
credentialId: new CredentialId(cipherView.id),
|
|
||||||
keyType: cipherView.fido2Key.keyType,
|
|
||||||
keyCurve: cipherView.fido2Key.keyCurve,
|
|
||||||
keyValue: privateKey,
|
|
||||||
rpId: cipherView.fido2Key.rpId,
|
|
||||||
rpName: cipherView.fido2Key.rpName,
|
|
||||||
userHandle: Fido2Utils.stringToBuffer(cipherView.fido2Key.userHandle),
|
|
||||||
userName: cipherView.fido2Key.userName,
|
|
||||||
origin: cipherView.fido2Key.origin,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
async function generateAuthData(params: AuthDataParams) {
|
|
||||||
const encoder = new TextEncoder();
|
|
||||||
|
|
||||||
const authData: Array<number> = [];
|
|
||||||
|
|
||||||
const rpIdHash = new Uint8Array(
|
|
||||||
await crypto.subtle.digest({ name: "SHA-256" }, encoder.encode(params.rpId))
|
|
||||||
);
|
|
||||||
authData.push(...rpIdHash);
|
|
||||||
|
|
||||||
const flags = authDataFlags({
|
|
||||||
extensionData: false,
|
|
||||||
attestationData: params.keyPair !== undefined,
|
|
||||||
userVerification: params.userVerification,
|
|
||||||
userPresence: params.userPresence,
|
|
||||||
});
|
|
||||||
authData.push(flags);
|
|
||||||
|
|
||||||
// add 4 bytes of counter - we use time in epoch seconds as monotonic counter
|
|
||||||
// TODO: Consider changing this to a cryptographically safe random number
|
|
||||||
const now = new Date().getTime() / 1000;
|
|
||||||
authData.push(
|
|
||||||
((now & 0xff000000) >> 24) & 0xff,
|
|
||||||
((now & 0x00ff0000) >> 16) & 0xff,
|
|
||||||
((now & 0x0000ff00) >> 8) & 0xff,
|
|
||||||
now & 0x000000ff
|
|
||||||
);
|
|
||||||
|
|
||||||
// attestedCredentialData
|
|
||||||
const attestedCredentialData: Array<number> = [];
|
|
||||||
|
|
||||||
// Use 0 because we're self-signing at the moment
|
|
||||||
const aaguid = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
|
|
||||||
attestedCredentialData.push(...aaguid);
|
|
||||||
|
|
||||||
// credentialIdLength (2 bytes) and credential Id
|
|
||||||
const rawId = params.credentialId.raw;
|
|
||||||
const credentialIdLength = [(rawId.length - (rawId.length & 0xff)) / 256, rawId.length & 0xff];
|
|
||||||
attestedCredentialData.push(...credentialIdLength);
|
|
||||||
attestedCredentialData.push(...rawId);
|
|
||||||
|
|
||||||
if (params.keyPair) {
|
|
||||||
const publicKeyJwk = await crypto.subtle.exportKey("jwk", params.keyPair.publicKey);
|
|
||||||
// COSE format of the EC256 key
|
|
||||||
const keyX = Utils.fromUrlB64ToArray(publicKeyJwk.x);
|
|
||||||
const keyY = Utils.fromUrlB64ToArray(publicKeyJwk.y);
|
|
||||||
|
|
||||||
// const credPublicKeyCOSE = {
|
|
||||||
// "1": 2, // kty
|
|
||||||
// "3": -7, // alg
|
|
||||||
// "-1": 1, // crv
|
|
||||||
// "-2": keyX,
|
|
||||||
// "-3": keyY,
|
|
||||||
// };
|
// };
|
||||||
// const coseBytes = new Uint8Array(cbor.encode(credPublicKeyCOSE));
|
// }
|
||||||
|
|
||||||
// Can't get `cbor-redux` to encode in CTAP2 canonical CBOR. So we do it manually:
|
// async assertCredential(
|
||||||
const coseBytes = new Uint8Array(77);
|
// params: CredentialAssertParams,
|
||||||
coseBytes.set([0xa5, 0x01, 0x02, 0x03, 0x26, 0x20, 0x01, 0x21, 0x58, 0x20], 0);
|
// abortController = new AbortController()
|
||||||
coseBytes.set(keyX, 10);
|
// ): Promise<CredentialAssertResult> {
|
||||||
coseBytes.set([0x22, 0x58, 0x20], 10 + 32);
|
// const timeout = setAbortTimeout(abortController, params.userVerification, params.timeout);
|
||||||
coseBytes.set(keyY, 10 + 32 + 3);
|
// let credential: BitCredential | undefined;
|
||||||
|
|
||||||
// credential public key - convert to array from CBOR encoded COSE key
|
// if (params.allowedCredentialIds && params.allowedCredentialIds.length > 0) {
|
||||||
attestedCredentialData.push(...coseBytes);
|
// // We're looking for regular non-resident keys
|
||||||
|
// credential = await this.getCredential(params.allowedCredentialIds);
|
||||||
|
|
||||||
authData.push(...attestedCredentialData);
|
// if (credential === undefined) {
|
||||||
}
|
// throw new NoCredentialFoundError();
|
||||||
|
// }
|
||||||
|
|
||||||
return new Uint8Array(authData);
|
// // TODO: Google doesn't work with this. Look into how we're supposed to check this
|
||||||
}
|
// // if (credential.origin !== params.origin) {
|
||||||
|
// // throw new OriginMismatchError();
|
||||||
|
// // }
|
||||||
|
|
||||||
interface SignatureParams {
|
// await this.fido2UserInterfaceService.confirmCredential(
|
||||||
authData: Uint8Array;
|
// credential.credentialId.encoded,
|
||||||
clientData: Uint8Array;
|
// abortController
|
||||||
privateKey: CryptoKey;
|
// );
|
||||||
}
|
// } else {
|
||||||
|
// // We're looking for a resident key
|
||||||
|
// const credentials = await this.getCredentialsByRp(params.rpId);
|
||||||
|
|
||||||
async function generateSignature(params: SignatureParams) {
|
// if (credentials.length === 0) {
|
||||||
const clientDataHash = await crypto.subtle.digest({ name: "SHA-256" }, params.clientData);
|
// throw new NoCredentialFoundError();
|
||||||
const sigBase = new Uint8Array([...params.authData, ...new Uint8Array(clientDataHash)]);
|
// }
|
||||||
const p1336_signature = new Uint8Array(
|
|
||||||
await window.crypto.subtle.sign(
|
|
||||||
{
|
|
||||||
name: "ECDSA",
|
|
||||||
hash: { name: "SHA-256" },
|
|
||||||
},
|
|
||||||
params.privateKey,
|
|
||||||
sigBase
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
const asn1Der_signature = joseToDer(p1336_signature, "ES256");
|
// const pickedId = await this.fido2UserInterfaceService.pickCredential(
|
||||||
|
// credentials.map((c) => c.credentialId.encoded),
|
||||||
|
// abortController
|
||||||
|
// );
|
||||||
|
// credential = credentials.find((c) => c.credentialId.encoded === pickedId);
|
||||||
|
// }
|
||||||
|
|
||||||
return asn1Der_signature;
|
// const encoder = new TextEncoder();
|
||||||
}
|
// const clientData = encoder.encode(
|
||||||
|
// JSON.stringify({
|
||||||
|
// type: "webauthn.get",
|
||||||
|
// challenge: params.challenge,
|
||||||
|
// origin: params.origin,
|
||||||
|
// })
|
||||||
|
// );
|
||||||
|
|
||||||
interface Flags {
|
// const authData = await generateAuthData({
|
||||||
extensionData: boolean;
|
// credentialId: credential.credentialId,
|
||||||
attestationData: boolean;
|
// rpId: params.rpId,
|
||||||
userVerification: boolean;
|
// userPresence: true,
|
||||||
userPresence: boolean;
|
// userVerification: true, // TODO: Change to false!
|
||||||
}
|
// });
|
||||||
|
|
||||||
function authDataFlags(options: Flags): number {
|
// const signature = await generateSignature({
|
||||||
let flags = 0;
|
// authData,
|
||||||
|
// clientData,
|
||||||
|
// privateKey: credential.keyValue,
|
||||||
|
// });
|
||||||
|
|
||||||
if (options.extensionData) {
|
// clearTimeout(timeout);
|
||||||
flags |= 0b1000000;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (options.attestationData) {
|
// return {
|
||||||
flags |= 0b01000000;
|
// credentialId: credential.credentialId.encoded,
|
||||||
}
|
// clientDataJSON: Fido2Utils.bufferToString(clientData),
|
||||||
|
// authenticatorData: Fido2Utils.bufferToString(authData),
|
||||||
|
// signature: Fido2Utils.bufferToString(signature),
|
||||||
|
// userHandle: Fido2Utils.bufferToString(credential.userHandle),
|
||||||
|
// };
|
||||||
|
// }
|
||||||
|
|
||||||
if (options.userVerification) {
|
// private async getCredential(allowedCredentialIds: string[]): Promise<BitCredential | undefined> {
|
||||||
flags |= 0b00000100;
|
// let cipher: Cipher | undefined;
|
||||||
}
|
// for (const allowedCredential of allowedCredentialIds) {
|
||||||
|
// cipher = await this.cipherService.get(allowedCredential);
|
||||||
|
|
||||||
if (options.userPresence) {
|
// if (cipher?.deletedDate != undefined) {
|
||||||
flags |= 0b00000001;
|
// cipher = undefined;
|
||||||
}
|
// }
|
||||||
|
|
||||||
return flags;
|
// if (cipher != undefined) {
|
||||||
}
|
// break;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
function setAbortTimeout(
|
// if (cipher == undefined) {
|
||||||
abortController: AbortController,
|
// return undefined;
|
||||||
userVerification: UserVerification,
|
// }
|
||||||
timeout?: number
|
|
||||||
): number {
|
|
||||||
let clampedTimeout: number;
|
|
||||||
|
|
||||||
if (userVerification === "discouraged") {
|
// const cipherView = await cipher.decrypt();
|
||||||
timeout = timeout ?? TIMEOUTS.NO_VERIFICATION.DEFAULT;
|
// return await mapCipherViewToBitCredential(cipherView);
|
||||||
clampedTimeout = Math.max(
|
// }
|
||||||
TIMEOUTS.NO_VERIFICATION.MIN,
|
|
||||||
Math.min(timeout, TIMEOUTS.NO_VERIFICATION.MAX)
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
timeout = timeout ?? TIMEOUTS.WITH_VERIFICATION.DEFAULT;
|
|
||||||
clampedTimeout = Math.max(
|
|
||||||
TIMEOUTS.WITH_VERIFICATION.MIN,
|
|
||||||
Math.min(timeout, TIMEOUTS.WITH_VERIFICATION.MAX)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return window.setTimeout(() => abortController.abort(), clampedTimeout);
|
// private async saveCredential(
|
||||||
}
|
// credential: Omit<BitCredential, "credentialId">
|
||||||
|
// ): Promise<CredentialId> {
|
||||||
|
// const pcks8Key = await crypto.subtle.exportKey("pkcs8", credential.keyValue);
|
||||||
|
|
||||||
|
// const view = new CipherView();
|
||||||
|
// view.type = CipherType.Fido2Key;
|
||||||
|
// view.name = credential.rpName;
|
||||||
|
|
||||||
|
// view.fido2Key = new Fido2KeyView();
|
||||||
|
// view.fido2Key.origin = credential.origin;
|
||||||
|
// view.fido2Key.keyType = credential.keyType;
|
||||||
|
// view.fido2Key.keyCurve = credential.keyCurve;
|
||||||
|
// view.fido2Key.keyValue = Fido2Utils.bufferToString(pcks8Key);
|
||||||
|
// view.fido2Key.rpId = credential.rpId;
|
||||||
|
// view.fido2Key.rpName = credential.rpName;
|
||||||
|
// view.fido2Key.userHandle = Fido2Utils.bufferToString(credential.userHandle);
|
||||||
|
// view.fido2Key.userName = credential.userName;
|
||||||
|
// view.fido2Key.origin = credential.origin;
|
||||||
|
|
||||||
|
// 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 getCredentialsByRp(rpId: string): Promise<BitCredential[]> {
|
||||||
|
// const allCipherViews = await this.cipherService.getAllDecrypted();
|
||||||
|
// const cipherViews = allCipherViews.filter(
|
||||||
|
// (cv) => !cv.isDeleted && cv.type === CipherType.Fido2Key && cv.fido2Key?.rpId === rpId
|
||||||
|
// );
|
||||||
|
|
||||||
|
// return await Promise.all(cipherViews.map((view) => mapCipherViewToBitCredential(view)));
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// interface AuthDataParams {
|
||||||
|
// rpId: string;
|
||||||
|
// credentialId: CredentialId;
|
||||||
|
// userPresence: boolean;
|
||||||
|
// userVerification: boolean;
|
||||||
|
// keyPair?: CryptoKeyPair;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// async function mapCipherViewToBitCredential(cipherView: CipherView): Promise<BitCredential> {
|
||||||
|
// const keyBuffer = Fido2Utils.stringToBuffer(cipherView.fido2Key.keyValue);
|
||||||
|
// const privateKey = await crypto.subtle.importKey(
|
||||||
|
// "pkcs8",
|
||||||
|
// keyBuffer,
|
||||||
|
// {
|
||||||
|
// name: cipherView.fido2Key.keyType,
|
||||||
|
// namedCurve: cipherView.fido2Key.keyCurve,
|
||||||
|
// },
|
||||||
|
// true,
|
||||||
|
// KeyUsages
|
||||||
|
// );
|
||||||
|
|
||||||
|
// return {
|
||||||
|
// credentialId: new CredentialId(cipherView.id),
|
||||||
|
// keyType: cipherView.fido2Key.keyType,
|
||||||
|
// keyCurve: cipherView.fido2Key.keyCurve,
|
||||||
|
// keyValue: privateKey,
|
||||||
|
// rpId: cipherView.fido2Key.rpId,
|
||||||
|
// rpName: cipherView.fido2Key.rpName,
|
||||||
|
// userHandle: Fido2Utils.stringToBuffer(cipherView.fido2Key.userHandle),
|
||||||
|
// userName: cipherView.fido2Key.userName,
|
||||||
|
// origin: cipherView.fido2Key.origin,
|
||||||
|
// };
|
||||||
|
// }
|
||||||
|
|
||||||
|
// async function generateAuthData(params: AuthDataParams) {
|
||||||
|
// const encoder = new TextEncoder();
|
||||||
|
|
||||||
|
// const authData: Array<number> = [];
|
||||||
|
|
||||||
|
// const rpIdHash = new Uint8Array(
|
||||||
|
// await crypto.subtle.digest({ name: "SHA-256" }, encoder.encode(params.rpId))
|
||||||
|
// );
|
||||||
|
// authData.push(...rpIdHash);
|
||||||
|
|
||||||
|
// const flags = authDataFlags({
|
||||||
|
// extensionData: false,
|
||||||
|
// attestationData: params.keyPair !== undefined,
|
||||||
|
// userVerification: params.userVerification,
|
||||||
|
// userPresence: params.userPresence,
|
||||||
|
// });
|
||||||
|
// authData.push(flags);
|
||||||
|
|
||||||
|
// // add 4 bytes of counter - we use time in epoch seconds as monotonic counter
|
||||||
|
// // TODO: Consider changing this to a cryptographically safe random number
|
||||||
|
// const now = new Date().getTime() / 1000;
|
||||||
|
// authData.push(
|
||||||
|
// ((now & 0xff000000) >> 24) & 0xff,
|
||||||
|
// ((now & 0x00ff0000) >> 16) & 0xff,
|
||||||
|
// ((now & 0x0000ff00) >> 8) & 0xff,
|
||||||
|
// now & 0x000000ff
|
||||||
|
// );
|
||||||
|
|
||||||
|
// // attestedCredentialData
|
||||||
|
// const attestedCredentialData: Array<number> = [];
|
||||||
|
|
||||||
|
// // Use 0 because we're self-signing at the moment
|
||||||
|
// const aaguid = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
|
||||||
|
// attestedCredentialData.push(...aaguid);
|
||||||
|
|
||||||
|
// // credentialIdLength (2 bytes) and credential Id
|
||||||
|
// const rawId = params.credentialId.raw;
|
||||||
|
// const credentialIdLength = [(rawId.length - (rawId.length & 0xff)) / 256, rawId.length & 0xff];
|
||||||
|
// attestedCredentialData.push(...credentialIdLength);
|
||||||
|
// attestedCredentialData.push(...rawId);
|
||||||
|
|
||||||
|
// if (params.keyPair) {
|
||||||
|
// const publicKeyJwk = await crypto.subtle.exportKey("jwk", params.keyPair.publicKey);
|
||||||
|
// // COSE format of the EC256 key
|
||||||
|
// const keyX = Utils.fromUrlB64ToArray(publicKeyJwk.x);
|
||||||
|
// const keyY = Utils.fromUrlB64ToArray(publicKeyJwk.y);
|
||||||
|
|
||||||
|
// // const credPublicKeyCOSE = {
|
||||||
|
// // "1": 2, // kty
|
||||||
|
// // "3": -7, // alg
|
||||||
|
// // "-1": 1, // crv
|
||||||
|
// // "-2": keyX,
|
||||||
|
// // "-3": keyY,
|
||||||
|
// // };
|
||||||
|
// // const coseBytes = new Uint8Array(cbor.encode(credPublicKeyCOSE));
|
||||||
|
|
||||||
|
// // Can't get `cbor-redux` to encode in CTAP2 canonical CBOR. So we do it manually:
|
||||||
|
// const coseBytes = new Uint8Array(77);
|
||||||
|
// coseBytes.set([0xa5, 0x01, 0x02, 0x03, 0x26, 0x20, 0x01, 0x21, 0x58, 0x20], 0);
|
||||||
|
// coseBytes.set(keyX, 10);
|
||||||
|
// coseBytes.set([0x22, 0x58, 0x20], 10 + 32);
|
||||||
|
// coseBytes.set(keyY, 10 + 32 + 3);
|
||||||
|
|
||||||
|
// // credential public key - convert to array from CBOR encoded COSE key
|
||||||
|
// attestedCredentialData.push(...coseBytes);
|
||||||
|
|
||||||
|
// authData.push(...attestedCredentialData);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// return new Uint8Array(authData);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// interface SignatureParams {
|
||||||
|
// authData: Uint8Array;
|
||||||
|
// clientData: Uint8Array;
|
||||||
|
// privateKey: CryptoKey;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// async function generateSignature(params: SignatureParams) {
|
||||||
|
// const clientDataHash = await crypto.subtle.digest({ name: "SHA-256" }, params.clientData);
|
||||||
|
// const sigBase = new Uint8Array([...params.authData, ...new Uint8Array(clientDataHash)]);
|
||||||
|
// const p1336_signature = new Uint8Array(
|
||||||
|
// await window.crypto.subtle.sign(
|
||||||
|
// {
|
||||||
|
// name: "ECDSA",
|
||||||
|
// hash: { name: "SHA-256" },
|
||||||
|
// },
|
||||||
|
// params.privateKey,
|
||||||
|
// sigBase
|
||||||
|
// )
|
||||||
|
// );
|
||||||
|
|
||||||
|
// const asn1Der_signature = joseToDer(p1336_signature, "ES256");
|
||||||
|
|
||||||
|
// return asn1Der_signature;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// interface Flags {
|
||||||
|
// extensionData: boolean;
|
||||||
|
// attestationData: boolean;
|
||||||
|
// userVerification: boolean;
|
||||||
|
// userPresence: boolean;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// function authDataFlags(options: Flags): number {
|
||||||
|
// let flags = 0;
|
||||||
|
|
||||||
|
// if (options.extensionData) {
|
||||||
|
// flags |= 0b1000000;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// if (options.attestationData) {
|
||||||
|
// flags |= 0b01000000;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// if (options.userVerification) {
|
||||||
|
// flags |= 0b00000100;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// if (options.userPresence) {
|
||||||
|
// flags |= 0b00000001;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// return flags;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// function setAbortTimeout(
|
||||||
|
// abortController: AbortController,
|
||||||
|
// userVerification: UserVerification,
|
||||||
|
// timeout?: number
|
||||||
|
// ): number {
|
||||||
|
// let clampedTimeout: number;
|
||||||
|
|
||||||
|
// if (userVerification === "discouraged") {
|
||||||
|
// timeout = timeout ?? TIMEOUTS.NO_VERIFICATION.DEFAULT;
|
||||||
|
// clampedTimeout = Math.max(
|
||||||
|
// TIMEOUTS.NO_VERIFICATION.MIN,
|
||||||
|
// Math.min(timeout, TIMEOUTS.NO_VERIFICATION.MAX)
|
||||||
|
// );
|
||||||
|
// } else {
|
||||||
|
// timeout = timeout ?? TIMEOUTS.WITH_VERIFICATION.DEFAULT;
|
||||||
|
// clampedTimeout = Math.max(
|
||||||
|
// TIMEOUTS.WITH_VERIFICATION.MIN,
|
||||||
|
// Math.min(timeout, TIMEOUTS.WITH_VERIFICATION.MAX)
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
|
||||||
|
// return window.setTimeout(() => abortController.abort(), clampedTimeout);
|
||||||
|
// }
|
||||||
|
|||||||
Reference in New Issue
Block a user