diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/helpers/convertASN1toPEM.ts | 48 | ||||
-rw-r--r-- | src/helpers/convertCOSEECDHAtoPKCS.ts | 32 | ||||
-rw-r--r-- | src/helpers/parseAttestationAuthData.ts | 63 | ||||
-rw-r--r-- | src/helpers/verifySignature.ts | 18 | ||||
-rw-r--r-- | src/types.ts | 35 |
5 files changed, 192 insertions, 4 deletions
diff --git a/src/helpers/convertASN1toPEM.ts b/src/helpers/convertASN1toPEM.ts new file mode 100644 index 0000000..c282e15 --- /dev/null +++ b/src/helpers/convertASN1toPEM.ts @@ -0,0 +1,48 @@ +/** + * Convert binary certificate or public key to an OpenSSL-compatible PEM text format. + * + * @param buffer - Cert or PubKey buffer + * @return PEM + */ +export default function convertASN1toPEM(pkBuffer: Buffer) { + let buffer = pkBuffer; + + let type; + if (buffer.length === 65 && buffer[0] === 0x04) { + /** + * If needed, we encode rawpublic key to ASN structure, adding metadata: + * + * SEQUENCE { + * SEQUENCE { + * OBJECTIDENTIFIER 1.2.840.10045.2.1 (ecPublicKey) + * OBJECTIDENTIFIER 1.2.840.10045.3.1.7 (P-256) + * } + * BITSTRING <raw public key> + * } + * + * Luckily, to do that, we just need to prefix it with constant 26 bytes (metadata is + * constant). + */ + buffer = Buffer.concat([ + Buffer.from('3059301306072a8648ce3d020106082a8648ce3d030107034200', 'hex'), + buffer, + ]); + + type = 'PUBLIC KEY'; + } else { + type = 'CERTIFICATE'; + } + + const b64cert = buffer.toString('base64'); + + let PEMKey = ''; + for (let i = 0; i < Math.ceil(b64cert.length / 64); i += 1) { + const start = 64 * i; + + PEMKey += `${b64cert.substr(start, 64)}\n`; + } + + PEMKey = `-----BEGIN ${type}-----\n${PEMKey}-----END ${type}-----\n`; + + return PEMKey; +} diff --git a/src/helpers/convertCOSEECDHAtoPKCS.ts b/src/helpers/convertCOSEECDHAtoPKCS.ts new file mode 100644 index 0000000..725a346 --- /dev/null +++ b/src/helpers/convertCOSEECDHAtoPKCS.ts @@ -0,0 +1,32 @@ +import cbor from 'cbor'; + +/** + * Takes COSE encoded public key and converts it to RAW PKCS ECDHA key + * @param COSEPublicKey COSE-encoded public key + * @return RAW PKCS encoded public key + */ +export default function COSEECDHAtoPKCS(COSEPublicKey: Buffer) { + /* + +------+-------+-------+---------+----------------------------------+ + | name | key | label | type | description | + | | type | | | | + +------+-------+-------+---------+----------------------------------+ + | crv | 2 | -1 | int / | EC Curve identifier - Taken from | + | | | | tstr | the COSE Curves registry | + | | | | | | + | x | 2 | -2 | bstr | X Coordinate | + | | | | | | + | y | 2 | -3 | bstr / | Y Coordinate | + | | | | bool | | + | | | | | | + | d | 2 | -4 | bstr | Private key | + +------+-------+-------+---------+----------------------------------+ + */ + + const coseStruct = cbor.decodeAllSync(COSEPublicKey)[0]; + const tag = Buffer.from([0x04]); + const x = coseStruct.get(-2); + const y = coseStruct.get(-3); + + return Buffer.concat([tag, x, y]); +} diff --git a/src/helpers/parseAttestationAuthData.ts b/src/helpers/parseAttestationAuthData.ts new file mode 100644 index 0000000..951c0af --- /dev/null +++ b/src/helpers/parseAttestationAuthData.ts @@ -0,0 +1,63 @@ +import { ParsedAttestationAuthData } from "@lib/types"; + +/** + * Make sense of the authData buffer contained in an Attestation + */ +export default function parseAttestationAuthData(authData: Buffer): ParsedAttestationAuthData { + console.log('parsing attestation auth data'); + + let intBuffer = authData; + + const rpIdHash = intBuffer.slice(0, 32); + intBuffer = intBuffer.slice(32); + + const flagsBuf = intBuffer.slice(0, 1); + intBuffer = intBuffer.slice(1); + + const flagsInt = flagsBuf[0]; + + const flags = { + up: !!(flagsInt & 0x01), + uv: !!(flagsInt & 0x04), + at: !!(flagsInt & 0x40), + ed: !!(flagsInt & 0x80), + flagsInt, + }; + + console.debug('flags:', flags); + + const counterBuf = intBuffer.slice(0, 4); + intBuffer = intBuffer.slice(4); + + const counter = counterBuf.readUInt32BE(0); + + let aaguid: Buffer | undefined = undefined; + let credentialID: Buffer | undefined = undefined; + let COSEPublicKey: Buffer | undefined = undefined; + + if (flags.at) { + aaguid = intBuffer.slice(0, 16); + intBuffer = intBuffer.slice(16); + + const credIDLenBuf = intBuffer.slice(0, 2); + intBuffer = intBuffer.slice(2); + + const credIDLen = credIDLenBuf.readUInt16BE(0); + + credentialID = intBuffer.slice(0, credIDLen); + intBuffer = intBuffer.slice(credIDLen); + + COSEPublicKey = intBuffer; + } + + return { + rpIdHash, + flagsBuf, + flags, + counter, + counterBuf, + aaguid, + credentialID, + COSEPublicKey, + }; +} diff --git a/src/helpers/verifySignature.ts b/src/helpers/verifySignature.ts new file mode 100644 index 0000000..c938a23 --- /dev/null +++ b/src/helpers/verifySignature.ts @@ -0,0 +1,18 @@ +import crypto from 'crypto'; + +/** + * Verify an authenticator's signature + * + * @param signature attStmt.sig + * @param signatureBase Output from Buffer.concat() + * @param publicKey Authenticator's public key as a PEM certificate + */ +export default function verifySignature( + signature: Buffer, + signatureBase: Buffer, + publicKey: string, +): boolean { + return crypto.createVerify('SHA256') + .update(signatureBase) + .verify(publicKey, signature); +} diff --git a/src/types.ts b/src/types.ts index f3993da..927cdf4 100644 --- a/src/types.ts +++ b/src/types.ts @@ -28,13 +28,40 @@ export type AttestationObject = { fmt: ATTESTATION_FORMATS, attStmt: { sig?: Buffer, - x5c?: Buffer, + x5c?: Buffer[], }, authData: Buffer, }; +export type ParsedAttestationAuthData = { + rpIdHash: Buffer, + flagsBuf: Buffer, + flags: { + up: boolean, + uv: boolean, + at: boolean, + ed: boolean, + flagsInt: number, + }, + counter: number, + counterBuf: Buffer, + aaguid?: Buffer, + credentialID?: Buffer, + COSEPublicKey?: Buffer, +}; + export type ClientDataJSON = { - type: string; - challenge: string; - origin: string; + type: string, + challenge: string, + origin: string, +}; + +export type VerifiedAttestation = { + verified: boolean, + authenticatorInfo?: { + fmt: ATTESTATION_FORMATS, + counter: number, + base64PublicKey: string, + base64CredentialID: string, + }, }; |