/**
* @file AWS Nitro Enclaves
* @copyright Multifactor 2021 All Rights Reserved
*
* @description
* Cryptographic operations for AWS Nitro Enclaves
*
* @author Vivek Nair (https://nair.me) <[email protected]>
*/
const cbor = require('cbor')
const cose = require('cose-js')
const caroot = require('./caroot')
const x509 = require('@peculiar/x509')
const { Crypto } = require('@peculiar/webcrypto')
const crypto = (typeof window === 'undefined') ? new Crypto() : window.crypto
x509.cryptoProvider.set(crypto)
/**
* The result of calling a verifyAttestation function.
* @typedef {Object} AttestationResult
* @property {boolean} valid - Indicates whether the attestation document is valid.
* @property {string} [reason] - If the attestation document is not valid, describes why validation failed.
* @property {string} [error] - If the attestation document is not valid and debug is true, includes error causing validation to fail.
* @property {Object} [attr] - If the attestation document is valid, contains the attestation document attributes.
*/
const verify = (childCert, parentCert) => childCert.verify({ signatureOnly: true, publicKey: parentCert })
/**
* Verify a AWS Nitro attestation document. Validates the attestation document signature and certificate.
* NOTE: You still need to verify the PCRs yourself to check if the request is generated by the desired enclave image.
*
* @example
* const trust = require('trust-center');
* await trust.enclaves.nitro.verify(validDocument); // -> {valid: true, attr: {...}}
* await trust.enclaves.nitro.verify(invalidDocument); // -> {valid: false, reason: '...'}
*
* @param {Buffer} document - AWS Nitro attestation document as a Buffer
* @param {boolean} [debug=false] - Include advanced error details in response
* @returns {AttestationResult} The validation result and attestation document attributes or rejection reason
* @author Vivek Nair (https://nair.me) <[email protected]>
* @since 0.1.7
* @async
* @memberOf nitro
*/
async function verifyAttestation (document, debug = false) {
let AttestationDocument
// Parse attestation document
try {
const COSESign1 = cbor.decodeAllSync(document)[0]
AttestationDocument = cbor.decodeAllSync(COSESign1[2])[0]
AttestationDocument.signature = COSESign1[3]
AttestationDocument.certificate = new x509.X509Certificate(AttestationDocument.certificate)
} catch (error) {
if (debug) return { valid: false, reason: 'Failed to verify attestation document signature', error: error }
return { valid: false, reason: 'Failed to validate attestation document' }
}
// Validate attestation document signature
try {
const publicKey = AttestationDocument.certificate.publicKey
const cryptoKey = await crypto.subtle.importKey('spki', publicKey.rawData, { hash: 'SHA-256', ...publicKey.algorithm }, true, ['verify'])
const key = await crypto.subtle.exportKey('jwk', cryptoKey)
const verifier = {
key: {
x: Buffer.from(key.x, 'base64'),
y: Buffer.from(key.y, 'base64')
}
}
await cose.sign.verify(document, verifier, { defaultType: cose.sign.Sign1Tag })
} catch (error) {
if (debug) return { valid: false, reason: 'Failed to verify attestation document signature', error: error }
return { valid: false, reason: 'Failed to verify attestation document signature' }
}
// Validate signing certificate
AttestationDocument.cabundle = AttestationDocument.cabundle.map(certificate => new x509.X509Certificate(certificate))
AttestationDocument.caroot = new x509.X509Certificate(caroot)
if (!(await verify(AttestationDocument.certificate, AttestationDocument.cabundle[AttestationDocument.cabundle.length - 1]))) return { valid: false, reason: 'Failed to verify attestation document signing certificate' }
for (let i = AttestationDocument.cabundle.length - 1; i > 0; i--) {
if (!(await verify(AttestationDocument.cabundle[i], AttestationDocument.cabundle[i - 1]))) return { valid: false, reason: 'Failed to verify attestation document certificate chain' }
}
if (!(await verify(AttestationDocument.cabundle[0], AttestationDocument.caroot))) return { valid: false, reason: 'Failed to verify attestation document certificate chain' }
// Return Results
AttestationDocument.pcrs.forEach((value, key) => {
AttestationDocument['pcr' + key] = value.toString('hex')
})
delete AttestationDocument.pcrs
return { valid: true, attr: AttestationDocument }
}
module.exports.verifyAttestation = verifyAttestation