UNPKG

11.5 kBJavaScriptView Raw
1"use strict";
2
3Object.defineProperty(exports, "__esModule", {
4 value: true
5});
6exports.Signature = void 0;
7var _plistDom = require("@shockpkg/plist-dom");
8var _timestamper = require("./security/timestamper.js");
9var _sha = require("./hasher/sha1.js");
10var _sha2 = require("./hasher/sha256.js");
11const 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="http://www.w3.org/TR/xmldsig-core#rsa-sha1"/>', ' <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>{0}</DigestValue>', ' </Reference>', ' </SignedInfo>',
12// eslint-disable-next-line max-len
13' <SignatureValue Id="PackageSignatureValue">{1}</SignatureValue>', ' <KeyInfo>', ' <X509Data>', ' {2}', ' </X509Data>', ' </KeyInfo>', ' <Object>', ' <Manifest Id="PackageContents">', ' {3}', ' </Manifest>', ' </Object>', ' {4}', ' </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="http://www.w3.org/TR/xmldsig-core#rsa-sha1"></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>{0}</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>']];
14
15/**
16 * Signature object.
17 */
18class Signature {
19 /**
20 * Certificate.
21 */
22 certificate = null;
23
24 /**
25 * Private key.
26 */
27 privateKey = null;
28
29 /**
30 * Timestamp URL.
31 */
32 timestampUrl = null;
33
34 /**
35 * Timestamp URI for SignatureValue.
36 */
37 timestampUriSignature = false;
38
39 /**
40 * Timestamp URI for #PackageSignatureValue.
41 */
42 timestampUriPackage = true;
43
44 /**
45 * Template strings for signatures.
46 */
47 _templates = new Map(templates);
48
49 /**
50 * File references.
51 */
52 _packageManifest = [];
53
54 /**
55 * Manifest digest.
56 */
57 _manifestDigest = null;
58
59 /**
60 * Signed data.
61 */
62 _signedInfo = null;
63
64 /**
65 * Signature digest.
66 */
67 _signature = null;
68
69 /**
70 * Key info.
71 */
72 _keyInfo = null;
73
74 /**
75 * Timestamp info.
76 */
77 _timestamp = null;
78
79 /**
80 * Signature constructor.
81 */
82 constructor() {}
83
84 /**
85 * Reset options to defaults.
86 */
87 defaults() {
88 this.certificate = null;
89 this.privateKey = null;
90 this.timestampUrl = null;
91 this.timestampUriSignature = false;
92 this.timestampUriPackage = true;
93 }
94
95 /**
96 * Reset the internal state.
97 */
98 reset() {
99 this._packageManifest = [];
100 this._manifestDigest = null;
101 this._signedInfo = null;
102 this._signature = null;
103 this._keyInfo = null;
104 this._timestamp = null;
105 }
106
107 /**
108 * Add file to signature.
109 *
110 * @param uri File URI.
111 * @param data File data.
112 */
113 addFile(uri, data) {
114 if (this._signedInfo || this._manifestDigest) {
115 throw new Error('Cannot call after: digest');
116 }
117 const digestB64 = this._base64Encode(this._hashSha256(data));
118
119 // Not perfect, but matches official packager.
120 const uriEncoded = uri.replace(/&/g, '&amp;');
121 this._packageManifest.push(this._templated('fileReference', [uriEncoded, digestB64]));
122 }
123
124 /**
125 * Digest contents.
126 */
127 digest() {
128 if (this._signedInfo || this._manifestDigest) {
129 throw new Error('Already called');
130 }
131 const manifest = this._templated('packageManifest', [this._packageManifest.join('')]);
132 const digest = this._hashSha256(new TextEncoder().encode(manifest));
133 const signed = this._templated('SignedInfo', [this._base64Encode(digest)]);
134 this._manifestDigest = digest;
135 this._signedInfo = signed;
136 }
137
138 /**
139 * Sign signature.
140 */
141 sign() {
142 if (this._signature || this._keyInfo !== null) {
143 throw new Error('Already called');
144 }
145 const signedInfo = this._signedInfo;
146 if (!signedInfo) {
147 throw new Error('Must call after: digest');
148 }
149 const {
150 privateKey: keyPrivate
151 } = this;
152 if (!keyPrivate) {
153 throw new Error('Private key not set');
154 }
155 const keyInfo = this._buildKeyInfo();
156 const signature = keyPrivate.sign(new TextEncoder().encode(signedInfo), 'sha1');
157 this._signature = signature;
158 this._keyInfo = keyInfo;
159 }
160
161 /**
162 * Add timestamp to signature.
163 */
164 async timestamp() {
165 if (this._timestamp) {
166 throw new Error('Already called');
167 }
168 const signature = this._signature;
169 if (!signature) {
170 throw new Error('Must call after: sign');
171 }
172 const {
173 timestampUrl
174 } = this;
175 if (!timestampUrl) {
176 throw new Error('Timestamp URL not set');
177 }
178 const message = this._templated('SignatureValue', [this._base64Encode(signature)]);
179 const timestamper = this._createSecurityTimestamper(timestampUrl);
180 const timestamp = await timestamper.timestamp(this._hashSha1(new TextEncoder().encode(message)), 'sha1');
181 this._timestamp = timestamp;
182 }
183
184 /**
185 * Encode signature.
186 *
187 * @returns Encoded signature.
188 */
189 encode() {
190 const signature = this._signature;
191 if (!signature) {
192 throw new Error('Must call after: sign');
193 }
194 const manifestDigest = this._manifestDigest;
195 const keyInfo = this._keyInfo;
196 if (!manifestDigest || keyInfo === null) {
197 throw new Error('Internal error');
198 }
199 const timestamp = this._timestamp ? this._createTimestampXml() : '';
200 return new TextEncoder().encode(this._templated('PackageSignature', [this._base64Encode(manifestDigest), this._base64Encode(signature), keyInfo, this._packageManifest.join(''), timestamp]));
201 }
202
203 /**
204 * Get list of timestamp data references for URI attribute.
205 *
206 * @returns List of references.
207 */
208 _getTimestampDataReferenceUris() {
209 const r = [];
210 if (this.timestampUriSignature) {
211 r.push('SignatureValue');
212 }
213 if (this.timestampUriPackage) {
214 r.push('#PackageSignatureValue');
215 }
216 return r;
217 }
218
219 /**
220 * Create string from a template string.
221 *
222 * @param name Template name.
223 * @param values Indexed values.
224 * @returns Complete string.
225 */
226 _templated(name, values) {
227 const template = this._templates.get(name);
228 if (!template) {
229 throw new Error(`Unknown template name: ${name}`);
230 }
231 return template.replace(/\{(\d+)\}/g, (str, index) => {
232 const i = +index;
233 if (i >= values.length) {
234 throw new Error(`Index out of range: ${i} > ${values.length}`);
235 }
236 return values[i];
237 });
238 }
239
240 /**
241 * Create timestamper.
242 *
243 * @param url Server URL.
244 * @returns Timestamper instance.
245 */
246 _createSecurityTimestamper(url) {
247 return new _timestamper.SecurityTimestamper(url);
248 }
249
250 /**
251 * Create SHA1 hasher instance.
252 *
253 * @returns Hasher instance.
254 */
255 _createHasherSha1() {
256 return new _sha.HasherSha1();
257 }
258
259 /**
260 * Create SHA256 hasher instance.
261 *
262 * @returns Hasher instance.
263 */
264 _createHasherSha256() {
265 return new _sha2.HasherSha256();
266 }
267
268 /**
269 * Hash data using SHA1.
270 *
271 * @param data Data to be hashed.
272 * @returns Hash digest.
273 */
274 _hashSha1(data) {
275 const hasher = this._createHasherSha1();
276 hasher.update(data);
277 return hasher.digest();
278 }
279
280 /**
281 * Hash data using SHA256.
282 *
283 * @param data Data to be hashed.
284 * @returns Hash digest.
285 */
286 _hashSha256(data) {
287 const hasher = this._createHasherSha256();
288 hasher.update(data);
289 return hasher.digest();
290 }
291
292 /**
293 * Base64 encode with some defaults to match official pacakger.
294 *
295 * @param data Data to be encoded.
296 * @param chunk Chunk size.
297 * @param delimit Chunk delimiter.
298 * @returns Encoded data.
299 */
300 _base64Encode(data, chunk = 76, delimit = '\n') {
301 const chunks = [];
302 for (let b64 = (0, _plistDom.base64Encode)(data); b64; b64 = b64.substring(chunk)) {
303 chunks.push(b64.substring(0, chunk));
304 }
305 return chunks.join(delimit);
306 }
307
308 /**
309 * Create the timestamp XML.
310 *
311 * @returns Timestamp XML.
312 */
313 _createTimestampXml() {
314 const timestamp = this._timestamp;
315 if (!timestamp) {
316 throw new Error('Internal error');
317 }
318 const timestampBase64 = this._base64Encode(timestamp);
319 const result = [];
320 for (const uri of this._getTimestampDataReferenceUris()) {
321 result.push(this._templated('timestamp', [uri, timestampBase64]));
322 }
323 return result.join('\n');
324 }
325
326 /**
327 * Build the key info.
328 *
329 * @returns Key info.
330 */
331 _buildKeyInfo() {
332 const {
333 certchain,
334 crlValidationCerts,
335 crls
336 } = this._buildAndVerifyCertChain();
337 const out = [];
338 for (const data of certchain) {
339 out.push(this._templated('certificate', [this._base64Encode(data)]));
340 }
341 if (crls.length) {
342 for (const data of crlValidationCerts) {
343 out.push(this._templated('certificate', [this._base64Encode(data)]));
344 }
345 }
346 for (const data of crls) {
347 out.push(this._templated('crl', [this._base64Encode(data)]));
348 }
349 return out.join('');
350 }
351
352 /**
353 * Build the certchain data.
354 *
355 * @returns Certchain data.
356 */
357 _buildAndVerifyCertChain() {
358 const {
359 certificate
360 } = this;
361 if (!certificate) {
362 throw new Error('Certificate not set');
363 }
364
365 // Not exactly complete, but enough for self-signed anyway.
366 const certchain = [];
367 const crlValidationCerts = [];
368 const crls = [];
369
370 // Add the certificate data.
371 certchain.push(certificate.encodeCertchain());
372 return {
373 certchain,
374 crlValidationCerts,
375 crls
376 };
377 }
378}
379exports.Signature = Signature;
380//# sourceMappingURL=signature.js.map
\No newline at end of file