Implement WebAuthn auth for minting PKP and storing encryption condition (#2)
* Implement WebAuthn auth for minting PKP and storing encryption condition * Update copy * Update copy
This commit is contained in:
986
src/App.js
986
src/App.js
File diff suppressed because it is too large
Load Diff
33
src/utils/cbor.ts
Normal file
33
src/utils/cbor.ts
Normal file
@ -0,0 +1,33 @@
|
||||
// copy-🍝 from https://github.com/MasterKale/SimpleWebAuthn/blob/33528afe001d4aca62052dce204c0398c3127ffd/packages/server/src/helpers/decodeCbor.ts
|
||||
|
||||
export function decodeCborFirst(
|
||||
cbor: any,
|
||||
input:
|
||||
| string
|
||||
| Buffer
|
||||
| ArrayBuffer
|
||||
| Uint8Array
|
||||
| Uint8ClampedArray
|
||||
| DataView
|
||||
): any {
|
||||
try {
|
||||
// throws if there are extra bytes
|
||||
return cbor.decodeFirstSync(input);
|
||||
} catch (err) {
|
||||
const _err = err as CborDecoderError;
|
||||
// if the error was due to extra bytes, return the unpacked value
|
||||
if (_err.value) {
|
||||
return _err.value;
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Intuited from a quick scan of `cbor.decodeFirstSync()` here:
|
||||
*
|
||||
* https://github.com/hildjj/node-cbor/blob/v5.1.0/lib/decoder.js#L189
|
||||
*/
|
||||
class CborDecoderError extends Error {
|
||||
value: any;
|
||||
}
|
39
src/utils/decodeAttestationObject.ts
Normal file
39
src/utils/decodeAttestationObject.ts
Normal file
@ -0,0 +1,39 @@
|
||||
// copy-🍝 from https://github.com/MasterKale/SimpleWebAuthn/blob/33528afe001d4aca62052dce204c0398c3127ffd/packages/server/src/helpers/decodeAttestationObject.ts#L8
|
||||
|
||||
/**
|
||||
* Convert an AttestationObject buffer to a proper object
|
||||
*
|
||||
* @param base64AttestationObject Attestation Object buffer
|
||||
*/
|
||||
export function decodeAttestationObject(
|
||||
cbor: any,
|
||||
attestationObject: Buffer
|
||||
): AttestationObject {
|
||||
const toCBOR: AttestationObject = cbor.decodeAllSync(attestationObject)[0];
|
||||
return toCBOR;
|
||||
}
|
||||
|
||||
export type AttestationFormat =
|
||||
| "fido-u2f"
|
||||
| "packed"
|
||||
| "android-safetynet"
|
||||
| "android-key"
|
||||
| "tpm"
|
||||
| "apple"
|
||||
| "none";
|
||||
|
||||
export type AttestationObject = {
|
||||
fmt: AttestationFormat;
|
||||
attStmt: AttestationStatement;
|
||||
authData: Buffer;
|
||||
};
|
||||
|
||||
export type AttestationStatement = {
|
||||
sig?: Buffer;
|
||||
x5c?: Buffer[];
|
||||
response?: Buffer;
|
||||
alg?: number;
|
||||
ver?: string;
|
||||
certInfo?: Buffer;
|
||||
pubArea?: Buffer;
|
||||
};
|
19
src/utils/decodeAuthenticatorExtensions.js
Normal file
19
src/utils/decodeAuthenticatorExtensions.js
Normal file
@ -0,0 +1,19 @@
|
||||
// copy-🍝 from https://github.com/MasterKale/SimpleWebAuthn/blob/33528afe001d4aca62052dce204c0398c3127ffd/packages/server/src/helpers/decodeAuthenticatorExtensions.ts
|
||||
|
||||
/**
|
||||
* Convert authenticator extension data buffer to a proper object
|
||||
*
|
||||
* @param extensionData Authenticator Extension Data buffer
|
||||
*/
|
||||
export function decodeAuthenticatorExtensions(cbor, extensionData) {
|
||||
let toCBOR;
|
||||
try {
|
||||
toCBOR = cbor.decodeAllSync(extensionData)[0];
|
||||
} catch (err) {
|
||||
const _err = err;
|
||||
throw new Error(
|
||||
`Error decoding authenticator extensions: ${_err.message}`
|
||||
);
|
||||
}
|
||||
return toCBOR;
|
||||
}
|
112
src/utils/parseAuthenticatorData.ts
Normal file
112
src/utils/parseAuthenticatorData.ts
Normal file
@ -0,0 +1,112 @@
|
||||
import { decodeCborFirst } from "./cbor";
|
||||
import { decodeAuthenticatorExtensions } from "./decodeAuthenticatorExtensions";
|
||||
|
||||
/**
|
||||
* Make sense of the authData buffer contained in an Attestation
|
||||
*/
|
||||
export function parseAuthenticatorData(
|
||||
cbor: any,
|
||||
authData: Buffer
|
||||
): ParsedAuthenticatorData {
|
||||
if (authData.byteLength < 37) {
|
||||
throw new Error(
|
||||
`Authenticator data was ${authData.byteLength} bytes, expected at least 37 bytes`
|
||||
);
|
||||
}
|
||||
|
||||
let pointer = 0;
|
||||
|
||||
const rpIdHash = authData.slice(pointer, (pointer += 32));
|
||||
|
||||
const flagsBuf = authData.slice(pointer, (pointer += 1));
|
||||
const flagsInt = flagsBuf[0];
|
||||
|
||||
// Bit positions can be referenced here:
|
||||
// https://www.w3.org/TR/webauthn-2/#flags
|
||||
const flags = {
|
||||
up: !!(flagsInt & (1 << 0)), // User Presence
|
||||
uv: !!(flagsInt & (1 << 2)), // User Verified
|
||||
be: !!(flagsInt & (1 << 3)), // Backup Eligibility
|
||||
bs: !!(flagsInt & (1 << 4)), // Backup State
|
||||
at: !!(flagsInt & (1 << 6)), // Attested Credential Data Present
|
||||
ed: !!(flagsInt & (1 << 7)), // Extension Data Present
|
||||
flagsInt,
|
||||
};
|
||||
|
||||
const counterBuf = authData.slice(pointer, (pointer += 4));
|
||||
const counter = counterBuf.readUInt32BE(0);
|
||||
|
||||
let aaguid: Buffer | undefined = undefined;
|
||||
let credentialID: Buffer | undefined = undefined;
|
||||
let credentialPublicKey: Buffer | undefined = undefined;
|
||||
|
||||
if (flags.at) {
|
||||
aaguid = authData.slice(pointer, (pointer += 16));
|
||||
|
||||
const credIDLenBuf = authData.slice(pointer, (pointer += 2));
|
||||
const credIDLen = credIDLenBuf.readUInt16BE(0);
|
||||
|
||||
credentialID = authData.slice(pointer, (pointer += credIDLen));
|
||||
|
||||
// Decode the next CBOR item in the buffer, then re-encode it back to a Buffer
|
||||
const firstDecoded = decodeCborFirst(cbor, authData.slice(pointer));
|
||||
const firstEncoded = Buffer.from(cbor.encode(firstDecoded));
|
||||
credentialPublicKey = firstEncoded;
|
||||
pointer += firstEncoded.byteLength;
|
||||
}
|
||||
|
||||
let extensionsData: any | undefined = undefined;
|
||||
let extensionsDataBuffer: Buffer | undefined = undefined;
|
||||
|
||||
if (flags.ed) {
|
||||
const firstDecoded = decodeCborFirst(cbor, authData.slice(pointer));
|
||||
const firstEncoded = Buffer.from(cbor.encode(firstDecoded));
|
||||
extensionsDataBuffer = firstEncoded;
|
||||
extensionsData = decodeAuthenticatorExtensions(
|
||||
cbor,
|
||||
extensionsDataBuffer
|
||||
);
|
||||
pointer += firstEncoded.byteLength;
|
||||
}
|
||||
|
||||
// Pointer should be at the end of the authenticator data, otherwise too much data was sent
|
||||
if (authData.byteLength > pointer) {
|
||||
throw new Error(
|
||||
"Leftover bytes detected while parsing authenticator data"
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
rpIdHash,
|
||||
flagsBuf,
|
||||
flags,
|
||||
counter,
|
||||
counterBuf,
|
||||
aaguid,
|
||||
credentialID,
|
||||
credentialPublicKey,
|
||||
extensionsData,
|
||||
extensionsDataBuffer,
|
||||
};
|
||||
}
|
||||
|
||||
export type ParsedAuthenticatorData = {
|
||||
rpIdHash: Buffer;
|
||||
flagsBuf: Buffer;
|
||||
flags: {
|
||||
up: boolean;
|
||||
uv: boolean;
|
||||
be: boolean;
|
||||
bs: boolean;
|
||||
at: boolean;
|
||||
ed: boolean;
|
||||
flagsInt: number;
|
||||
};
|
||||
counter: number;
|
||||
counterBuf: Buffer;
|
||||
aaguid?: Buffer;
|
||||
credentialID?: Buffer;
|
||||
credentialPublicKey?: Buffer;
|
||||
extensionsData?: any;
|
||||
extensionsDataBuffer?: Buffer;
|
||||
};
|
Reference in New Issue
Block a user