UNPKG

@shockpkg/ria-packager

Version:

Package for creating Adobe AIR packages

403 lines (366 loc) 12.3 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Signature = void 0; var _plistDom = require("@shockpkg/plist-dom"); var _timestamper = require("./security/timestamper.js"); var _sha = require("./hasher/sha1.js"); var _sha2 = require("./hasher/sha256.js"); const defaultSignDigest = 'sha256'; const defaultTimestampDigest = 'sha256'; const templates = [['certificate', '<X509Certificate>{0}</X509Certificate>'], ['crl', '<X509CRL>{0}</X509CRL>'], ['fileReference', ['<Reference URI="{0}">', '<DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"></DigestMethod>', '<DigestValue>{1}</DigestValue>', '</Reference>'].join('')], ['packageManifest', '<Manifest xmlns="http://www.w3.org/2000/09/xmldsig#" Id="PackageContents">{0}</Manifest>'], ['PackageSignature', ['<signatures>', ' <Signature xmlns="http://www.w3.org/2000/09/xmldsig#" Id="PackageSignature">', ' <SignedInfo>', ' <CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/>', ' <SignatureMethod Algorithm="{0}"/>', ' <Reference URI="#PackageContents">', ' <Transforms>', ' <Transform Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/>', ' </Transforms>', ' <DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>', ' <DigestValue>{1}</DigestValue>', ' </Reference>', ' </SignedInfo>', // eslint-disable-next-line max-len ' <SignatureValue Id="PackageSignatureValue">{2}</SignatureValue>', ' <KeyInfo>', ' <X509Data>', ' {3}', ' </X509Data>', ' </KeyInfo>', ' <Object>', ' <Manifest Id="PackageContents">', ' {4}', ' </Manifest>', ' </Object>', ' {5}', ' </Signature>', '</signatures>', ''].join('\n')], ['SignedInfo', ['<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#">', '<CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"></CanonicalizationMethod>', '<SignatureMethod Algorithm="{0}"></SignatureMethod>', '<Reference URI="#PackageContents">', '<Transforms>', '<Transform Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"></Transform>', '</Transforms>', '<DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"></DigestMethod>', '<DigestValue>{1}</DigestValue>', '</Reference>', '</SignedInfo>'].join('')], ['timestamp', ['<Object xmlns:xades="http://uri.etsi.org/01903/v1.1.1#" > ', ' <xades:QualifyingProperties>', ' <xades:UnsignedProperties > ', ' <xades:UnsignedSignatureProperties>', ' <xades:SignatureTimeStamp>', ' \t <xades:HashDataInfo uri="{0}">', ' \t <Transforms>', ' \t <Transform Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/>', ' </Transforms>', ' <xades:EncapsulatedTimeStamp>', ' {1}', ' </xades:EncapsulatedTimeStamp> \t', ' \t </xades:HashDataInfo> \t', ' </xades:SignatureTimeStamp>', ' </xades:UnsignedSignatureProperties> ', ' </xades:UnsignedProperties>', ' </xades:QualifyingProperties>', '</Object>'].join('\n')], ['SignatureValue', '<SignatureValue xmlns="http://www.w3.org/2000/09/xmldsig#" Id="PackageSignatureValue">{0}</SignatureValue>']]; /** * Signature object. */ class Signature { /** * Certificate. */ certificate = null; /** * Private key. */ privateKey = null; /** * Signature digest algorithm. */ signDigest = defaultSignDigest; /** * Timestamp digest algorithm. */ timestampDigest = defaultTimestampDigest; /** * Timestamp URL. */ timestampUrl = null; /** * Timestamp URI for SignatureValue. */ timestampUriSignature = false; /** * Timestamp URI for #PackageSignatureValue. */ timestampUriPackage = true; /** * Template strings for signatures. */ _templates = new Map(templates); /** * File references. */ _packageManifest = []; /** * Manifest digest. */ _manifestDigest = null; /** * Signed data. */ _signedInfo = null; /** * Signature digest. */ _signature = null; /** * Key info. */ _keyInfo = null; /** * Timestamp info. */ _timestamp = null; /** * Signature constructor. */ constructor() {} /** * Reset options to defaults. */ defaults() { this.certificate = null; this.privateKey = null; this.signDigest = defaultSignDigest; this.timestampDigest = defaultTimestampDigest; this.timestampUrl = null; this.timestampUriSignature = false; this.timestampUriPackage = true; } /** * Reset the internal state. */ reset() { this._packageManifest = []; this._manifestDigest = null; this._signedInfo = null; this._signature = null; this._keyInfo = null; this._timestamp = null; } /** * Add file to signature. * * @param uri File URI. * @param data File data. */ addFile(uri, data) { if (this._signedInfo || this._manifestDigest) { throw new Error('Cannot call after: digest'); } const digestB64 = this._base64Encode(this._hashSha256(data)); // Not perfect, but matches official packager. const uriEncoded = uri.replace(/&/g, '&amp;'); this._packageManifest.push(this._templated('fileReference', [uriEncoded, digestB64])); } /** * Digest contents. */ digest() { if (this._signedInfo || this._manifestDigest) { throw new Error('Already called'); } const manifest = this._templated('packageManifest', [this._packageManifest.join('')]); const signatureAlgorithm = this.signDigest === 'sha256' ? 'http://www.w3.org/TR/xmldsig-core#rsa-sha256' : 'http://www.w3.org/TR/xmldsig-core#rsa-sha1'; const digest = this._hashSha256(new TextEncoder().encode(manifest)); const signed = this._templated('SignedInfo', [signatureAlgorithm, this._base64Encode(digest)]); this._manifestDigest = digest; this._signedInfo = signed; } /** * Sign signature. */ sign() { if (this._signature || this._keyInfo !== null) { throw new Error('Already called'); } const signedInfo = this._signedInfo; if (!signedInfo) { throw new Error('Must call after: digest'); } const { privateKey: keyPrivate } = this; if (!keyPrivate) { throw new Error('Private key not set'); } const { signDigest } = this; const keyInfo = this._buildKeyInfo(); const signature = keyPrivate.sign(new TextEncoder().encode(signedInfo), signDigest); this._signature = signature; this._keyInfo = keyInfo; } /** * Add timestamp to signature. */ async timestamp() { if (this._timestamp) { throw new Error('Already called'); } const signature = this._signature; if (!signature) { throw new Error('Must call after: sign'); } const { timestampUrl } = this; if (!timestampUrl) { throw new Error('Timestamp URL not set'); } const message = this._templated('SignatureValue', [this._base64Encode(signature)]); const messageData = new TextEncoder().encode(message); const { timestampDigest } = this; const timestamper = this._createSecurityTimestamper(timestampUrl); const timestamp = await timestamper.timestamp(timestampDigest === 'sha256' ? this._hashSha256(messageData) : this._hashSha1(messageData), timestampDigest); this._timestamp = timestamp; } /** * Encode signature. * * @returns Encoded signature. */ encode() { const signature = this._signature; if (!signature) { throw new Error('Must call after: sign'); } const manifestDigest = this._manifestDigest; const keyInfo = this._keyInfo; if (!manifestDigest || keyInfo === null) { throw new Error('Internal error'); } const timestamp = this._timestamp ? this._createTimestampXml() : ''; const signatureAlgorithm = this.signDigest === 'sha256' ? 'http://www.w3.org/TR/xmldsig-core#rsa-sha256' : 'http://www.w3.org/TR/xmldsig-core#rsa-sha1'; return new TextEncoder().encode(this._templated('PackageSignature', [signatureAlgorithm, this._base64Encode(manifestDigest), this._base64Encode(signature), keyInfo, this._packageManifest.join(''), timestamp])); } /** * Get list of timestamp data references for URI attribute. * * @returns List of references. */ _getTimestampDataReferenceUris() { const r = []; if (this.timestampUriSignature) { r.push('SignatureValue'); } if (this.timestampUriPackage) { r.push('#PackageSignatureValue'); } return r; } /** * Create string from a template string. * * @param name Template name. * @param values Indexed values. * @returns Complete string. */ _templated(name, values) { const template = this._templates.get(name); if (!template) { throw new Error(`Unknown template name: ${name}`); } return template.replace(/{(\d+)}/g, (str, index) => { const i = +index; if (i >= values.length) { throw new Error(`Index out of range: ${i} > ${values.length}`); } return values[i]; }); } /** * Create timestamper. * * @param url Server URL. * @returns Timestamper instance. */ _createSecurityTimestamper(url) { return new _timestamper.SecurityTimestamper(url); } /** * Create SHA1 hasher instance. * * @returns Hasher instance. */ _createHasherSha1() { return new _sha.HasherSha1(); } /** * Create SHA256 hasher instance. * * @returns Hasher instance. */ _createHasherSha256() { return new _sha2.HasherSha256(); } /** * Hash data using SHA1. * * @param data Data to be hashed. * @returns Hash digest. */ _hashSha1(data) { const hasher = this._createHasherSha1(); hasher.update(data); return hasher.digest(); } /** * Hash data using SHA256. * * @param data Data to be hashed. * @returns Hash digest. */ _hashSha256(data) { const hasher = this._createHasherSha256(); hasher.update(data); return hasher.digest(); } /** * Base64 encode with some defaults to match official pacakger. * * @param data Data to be encoded. * @param chunk Chunk size. * @param delimit Chunk delimiter. * @returns Encoded data. */ _base64Encode(data, chunk = 76, delimit = '\n') { const chunks = []; for (let b64 = (0, _plistDom.base64Encode)(data); b64; b64 = b64.slice(chunk)) { chunks.push(b64.slice(0, chunk)); } return chunks.join(delimit); } /** * Create the timestamp XML. * * @returns Timestamp XML. */ _createTimestampXml() { const timestamp = this._timestamp; if (!timestamp) { throw new Error('Internal error'); } const timestampBase64 = this._base64Encode(timestamp); const result = []; for (const uri of this._getTimestampDataReferenceUris()) { result.push(this._templated('timestamp', [uri, timestampBase64])); } return result.join('\n'); } /** * Build the key info. * * @returns Key info. */ _buildKeyInfo() { const { certchain, crlValidationCerts, crls } = this._buildAndVerifyCertChain(); const out = []; for (const data of certchain) { out.push(this._templated('certificate', [this._base64Encode(data)])); } if (crls.length) { for (const data of crlValidationCerts) { out.push(this._templated('certificate', [this._base64Encode(data)])); } } for (const data of crls) { out.push(this._templated('crl', [this._base64Encode(data)])); } return out.join(''); } /** * Build the certchain data. * * @returns Certchain data. */ _buildAndVerifyCertChain() { const { certificate } = this; if (!certificate) { throw new Error('Certificate not set'); } // Not exactly complete, but enough for self-signed anyway. const certchain = []; const crlValidationCerts = []; const crls = []; // Add the certificate data. certchain.push(certificate.encodeCertchain()); return { certchain, crlValidationCerts, crls }; } } exports.Signature = Signature; //# sourceMappingURL=signature.js.map