UNPKG

appattest-checker-node

Version:

Node.JS library to check/verify iOS App Attest attestations & assertions

112 lines 4.56 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.parseAssertion = exports.verifySignaturePerStep1To3 = exports.verifyRPIdPerStep4 = exports.verifyAssertion = void 0; const buffer_1 = require("buffer"); const crypto_1 = require("crypto"); const cbor_1 = __importDefault(require("cbor")); const utils_1 = require("./utils"); const STEPS = [ verifySignaturePerStep1To3, verifyRPIdPerStep4, ]; /** * Verify an Assertion generated on an iOS device using DCAppAttestService per steps 1-4 * {@link https://developer.apple.com/documentation/devicecheck/validating_apps_that_connect_to_your_server#3576644 | here}. * * @remark This code does not verify that any challenge inluded in clientDataHash is valid. Calling * code should do that. Also, on successful verification, the signCount from the Assertion is * returned. Calling code should check that it exceeds any previous persisted signCount and persist * the returned value. These two points are mentioned in Steps 5 & 6 from steps above. * * @remark Ensure that clientDataHash is computed from the same request that was used by the client * for assertion. Any formatting changes could result in issues. * * @param clientDataHash SHA256 of the client data (request). * @param publicKeyPem Public Key of the key pair from the device. * @param appId App Id that generated the assertion. * @param assertion Assertion bytes sent up from the device; derived on device by signing * clientDataHash with private key on the device. * @returns Result object containing signCount if assertion was verified or error if it was not * verified. */ async function verifyAssertion(clientDataHash, publicKeyPem, appId, assertion) { const parseResult = await parseAssertion(assertion); if (typeof parseResult === 'string') { return { verifyError: 'fail_parsing_assertion', errorMessage: parseResult }; } const inputs = { clientDataHash, publicKeyPem, appId, parsedAssertion: parseResult, }; for (const step of STEPS) { const error = await step(inputs); if (error != null) { return { verifyError: error, }; } } return { signCount: (0, utils_1.getSignCount)(inputs.parsedAssertion.authData), }; } exports.verifyAssertion = verifyAssertion; /** @internal */ async function verifyRPIdPerStep4(inputs) { const rpIdHash = (0, utils_1.getRPIdHash)(inputs.parsedAssertion.authData); const appIdHash = await (0, utils_1.getSHA256)(buffer_1.Buffer.from(inputs.appId)); return rpIdHash.equals(appIdHash) ? null : 'fail_rpId_mismatch'; } exports.verifyRPIdPerStep4 = verifyRPIdPerStep4; /** @internal */ async function verifySignaturePerStep1To3(inputs) { const noncePrep = buffer_1.Buffer.concat([ inputs.parsedAssertion.authData, inputs.clientDataHash, ]); const nonce = await (0, utils_1.getSHA256)(noncePrep); let publicKey; try { publicKey = (0, crypto_1.createPublicKey)(inputs.publicKeyPem); } catch (e) { return 'fail_invalid_publicKey'; } const verifier = (0, crypto_1.createVerify)('RSA-SHA256'); verifier.update(nonce); const verified = verifier.verify(publicKey, inputs.parsedAssertion.signature); return verified ? null : 'fail_signature_verification'; } exports.verifySignaturePerStep1To3 = verifySignaturePerStep1To3; // Need to read upto signCount field at offset 33. const MIN_REQUIRED_ASSERTION_BYTES = 34; /** @internal */ async function parseAssertion(assertion) { try { const assertionObj = await cbor_1.default.decodeFirst(assertion); const { signature, authenticatorData } = assertionObj; if (!(signature instanceof buffer_1.Buffer)) { return 'Invalid `signature` field in Assertion'; } if (!(authenticatorData instanceof buffer_1.Buffer)) { return 'Invalid `authenticatorData` field in Assertion'; } if (authenticatorData.length < MIN_REQUIRED_ASSERTION_BYTES) { return 'authenticatorData has < 34 bytes'; } return { signature, authData: authenticatorData, }; } catch (e) { return 'Unable to parse CBOR contents from Assertion'; } } exports.parseAssertion = parseAssertion; //# sourceMappingURL=assertion.js.map