summaryrefslogtreecommitdiffhomepage
path: root/src
diff options
context:
space:
mode:
authorMatthew Miller <matthew@millerti.me>2020-05-18 17:52:06 -0700
committerMatthew Miller <matthew@millerti.me>2020-05-18 17:52:06 -0700
commit6d086ecb8bbe6e382bf3960a3b0757489a38e78a (patch)
treec7502fa004b8dd78d16bd19cbb0a77d29a3af464 /src
parent3cd5df6f64f070c55190ecae4ba978ac4746dd10 (diff)
Add more helpers and types
Diffstat (limited to 'src')
-rw-r--r--src/helpers/convertASN1toPEM.ts48
-rw-r--r--src/helpers/convertCOSEECDHAtoPKCS.ts32
-rw-r--r--src/helpers/parseAttestationAuthData.ts63
-rw-r--r--src/helpers/verifySignature.ts18
-rw-r--r--src/types.ts35
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,
+ },
};