@shockpkg/ria-packager
Version:
Package for creating Adobe AIR packages
403 lines (366 loc) • 12.3 kB
JavaScript
"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, '&');
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