@blamejs/pki 0.8.7 → 0.8.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/schema-all.js CHANGED
@@ -38,6 +38,8 @@ var c509 = require("./schema-c509");
38
38
  var crl = require("./schema-crl");
39
39
  var csr = require("./schema-csr");
40
40
  var pkcs8 = require("./schema-pkcs8");
41
+ var pkcs1 = require("./schema-pkcs1");
42
+ var sec1 = require("./schema-sec1");
41
43
  var pkcs12 = require("./schema-pkcs12");
42
44
  var cms = require("./schema-cms");
43
45
  var ocsp = require("./schema-ocsp");
@@ -230,6 +232,117 @@ function detectFormat(input) {
230
232
  return null;
231
233
  }
232
234
 
235
+ /** @internal What a bundle row's bytes are, given the label the file put on them. An
236
+ * `EncryptedPrivateKeyInfo` is `SEQUENCE { AlgorithmIdentifier, OCTET STRING }`, which a PKCS#1
237
+ * `DigestInfo` also is, so structure alone cannot classify it and `detectFormat` deliberately
238
+ * does not. In a bundle the label is evidence the bare bytes do not carry, and RFC 7468 sec. 11
239
+ * gives the encrypted structure a label of its own, so the label is what makes the question
240
+ * answerable here and nowhere else. */
241
+ var LABEL_ONLY_FORMATS = [
242
+ { label: "ENCRYPTED PRIVATE KEY", name: "pkcs8-encrypted", detect: pkcs8.matchesEncrypted },
243
+ { label: "RSA PRIVATE KEY", name: "pkcs1", detect: pkcs1.matches },
244
+ { label: "RSA PUBLIC KEY", name: "pkcs1-public", detect: pkcs1.matchesPublic },
245
+ { label: "EC PRIVATE KEY", name: "sec1", detect: sec1.matches },
246
+ ];
247
+
248
+ function detectFormatForLabel(input, label) {
249
+ var der = pkix.coerceToDer(input, ENTRY);
250
+ var root = pkix.decodeRoot(der, ENTRY);
251
+ for (var j = 0; j < LABEL_ONLY_FORMATS.length; j++) {
252
+ if (LABEL_ONLY_FORMATS[j].label === label) {
253
+ return LABEL_ONLY_FORMATS[j].detect(root) ? LABEL_ONLY_FORMATS[j].name : null;
254
+ }
255
+ }
256
+ for (var i = 0; i < FORMATS.length; i++) {
257
+ if (FORMATS[i].detect(root)) return FORMATS[i].name;
258
+ }
259
+ return null;
260
+ }
261
+
262
+ /**
263
+ * @primitive pki.schema.pem.decodeBundle
264
+ * @signature pki.schema.pem.decodeBundle(text, opts) -> [{ index, offset, label, der }]
265
+ * @since 0.8.8
266
+ * @status stable
267
+ * @spec RFC 7468
268
+ * @related pki.schema.detectFormat, pki.schema.pem.encodeBundle
269
+ *
270
+ * Read every object in a PEM file: a `fullchain.pem` of a leaf and its intermediates, a
271
+ * `ca-certificates.crt` of hundreds of anchors, a file holding a key beside its certificate.
272
+ * Each row carries the label that named the object, the DER it decoded to, its position in
273
+ * the file and the offset into the text its boundary started at, so a fault in a file of hundreds
274
+ * names the one object it is about. `offset` counts into the text this verb read: a
275
+ * `BufferSource` is read as latin1, so one character is one byte and the offset is the byte
276
+ * offset into the file, while a string a caller decoded itself is counted in that string's own
277
+ * characters.
278
+ *
279
+ * Explanatory text before, between and after the blocks is text, which is what a bundle
280
+ * written by a real tool carries. A label the toolkit has no parser for is a row like any
281
+ * other: the label is data, not a filter.
282
+ *
283
+ * This is the verb for a file of several objects. Every other door reads one: a parse door,
284
+ * a `pemDecode`, and a verb taking a certificate or a message as PEM each refuse a file
285
+ * holding more than one with `pem/multiple-blocks`, naming how many it holds.
286
+ *
287
+ * Fail-closed on the file rather than on the object: a boundary opened and never closed, a
288
+ * block closed under a different label, a body carrying RFC 1421 encryption headers, a body
289
+ * outside the base64 alphabet, more objects than `opts.maxBlocks`, and objects decoding to
290
+ * more than `opts.maxDecodedBytes` are each refused with their own `pem/*` code. Either cap
291
+ * may be tightened by a caller and neither may be raised above the toolkit's own.
292
+ *
293
+ * @opts
294
+ * route check each label's claim against the structure its bytes carry, and add
295
+ * `format` to every row (`null` when the label names no parser). A label
296
+ * whose claim the bytes do not support is `pem/label-structure-mismatch`.
297
+ * maxBlocks how many objects the file may hold. Default `C.LIMITS.PEM_MAX_BLOCKS`.
298
+ * maxDecodedBytes how many bytes those objects may decode to in total, across the file
299
+ * rather than per object. Default `C.LIMITS.PEM_MAX_DECODED_BYTES`.
300
+ *
301
+ * @example
302
+ * var pair = await pki.key.generate("Ed25519");
303
+ * var cert = await pki.x509.sign({ subject: "example.com", subjectPublicKey: await pki.key.export(pair.publicKey),
304
+ * notBefore: new Date("2026-01-01T00:00:00Z"), notAfter: new Date("2036-01-01T00:00:00Z") },
305
+ * { key: await pki.key.export(pair.privateKey) });
306
+ * var text = "Some Root CA\n============\n" + pki.schema.x509.pemEncode(cert, "CERTIFICATE");
307
+ * var rows = pki.schema.pem.decodeBundle(text);
308
+ * rows.length; // 1, the two prose lines being text
309
+ * rows[0].label; // "CERTIFICATE"
310
+ * rows[0].offset; // 26, where its boundary starts
311
+ * pki.schema.x509.parse(rows[0].der);
312
+ */
313
+
314
+ /**
315
+ * @primitive pki.schema.pem.encodeBundle
316
+ * @signature pki.schema.pem.encodeBundle(objects) -> string
317
+ * @since 0.8.8
318
+ * @status stable
319
+ * @spec RFC 7468
320
+ * @related pki.schema.pem.decodeBundle
321
+ *
322
+ * Write an ordered list of `{ label, der }` objects to one PEM text, each block wrapped at 64
323
+ * characters and closed under the label it was opened with. What `decodeBundle` reads back is
324
+ * the list that went in, and writing that list again produces the same text.
325
+ *
326
+ * A label is held to the uppercase form this toolkit writes, so a label carrying a boundary
327
+ * cannot put a block into the file that the caller never named. The reader is wider than the
328
+ * writer: it takes every label RFC 7468 sec. 3 admits, including lowercase and the empty one.
329
+ *
330
+ * @example
331
+ * var pair = await pki.key.generate("Ed25519");
332
+ * var cert = await pki.x509.sign({ subject: "example.com", subjectPublicKey: await pki.key.export(pair.publicKey),
333
+ * notBefore: new Date("2026-01-01T00:00:00Z"), notAfter: new Date("2036-01-01T00:00:00Z") },
334
+ * { key: await pki.key.export(pair.privateKey) });
335
+ * var text = pki.schema.pem.encodeBundle([{ label: "CERTIFICATE", der: cert }, { label: "CERTIFICATE", der: cert }]);
336
+ * pki.schema.pem.decodeBundle(text).length; // 2
337
+ */
338
+
339
+ /** @internal What a block's bytes are, for the label-versus-structure comparison. Bytes the
340
+ * detector cannot place are not a fault here: the label may name something this toolkit has no
341
+ * parser for, and the reader answers that with a row rather than a refusal. */
342
+ function _detectOrNull(der, label) {
343
+ try { return detectFormatForLabel(der, label); } catch (_e) { /* allow:swallow-unverified unreadable bytes are "no structure", which the caller is told */ return null; }
344
+ }
345
+
233
346
  module.exports = {
234
347
  engine: engine,
235
348
  x509: { parse: x509.parse, pemDecode: x509.pemDecode, pemEncode: x509.pemEncode },
@@ -237,6 +350,13 @@ module.exports = {
237
350
  crl: { parse: crl.parse, pemDecode: crl.pemDecode, pemEncode: crl.pemEncode },
238
351
  csr: { parse: csr.parse, pemDecode: csr.pemDecode, pemEncode: csr.pemEncode },
239
352
  pkcs8: { parse: pkcs8.parse, parseEncrypted: pkcs8.parseEncrypted, pemDecode: pkcs8.pemDecode, pemEncode: pkcs8.pemEncode },
353
+ pkcs1: {
354
+ parse: pkcs1.parse, parsePublic: pkcs1.parsePublic,
355
+ encode: pkcs1.encode, encodePublic: pkcs1.encodePublic,
356
+ pemDecode: pkcs1.pemDecode, pemEncode: pkcs1.pemEncode,
357
+ pemDecodePublic: pkcs1.pemDecodePublic, pemEncodePublic: pkcs1.pemEncodePublic,
358
+ },
359
+ sec1: { parse: sec1.parse, encode: sec1.encode, pemDecode: sec1.pemDecode, pemEncode: sec1.pemEncode },
240
360
  pkcs12: { parse: pkcs12.parse, pemDecode: pkcs12.pemDecode, pemEncode: pkcs12.pemEncode },
241
361
  cms: { parse: cms.parse, pemDecode: cms.pemDecode, pemEncode: cms.pemEncode },
242
362
  ocsp: { parseRequest: ocsp.parseRequest, parseResponse: ocsp.parseResponse, pemDecode: ocsp.pemDecode, pemEncode: ocsp.pemEncode },
@@ -247,6 +367,10 @@ module.exports = {
247
367
  attrcert: { parse: attrcert.parse, pemDecode: attrcert.pemDecode, pemEncode: attrcert.pemEncode },
248
368
  smime: { parseSigningCertificate: smime.parseSigningCertificate, parseSigningCertificateV2: smime.parseSigningCertificateV2, parseSmimeCapabilities: smime.parseSmimeCapabilities, decodeAttribute: smime.decodeAttribute },
249
369
  cmc: { parse: cmc.parse, parsePkiData: cmc.parsePkiData, parsePkiResponse: cmc.parsePkiResponse },
370
+ pem: {
371
+ decodeBundle: function (text, opts) { return pkix.pemDecodeBundle(text, opts, PemError, _detectOrNull); },
372
+ encodeBundle: function (objects) { return pkix.pemEncodeBundle(objects, PemError); },
373
+ },
250
374
  all: all,
251
375
  parse: parse,
252
376
  detectFormat: detectFormat,
@@ -318,8 +318,10 @@ function parseV1(input, callerOpts) {
318
318
  * Extract the DER bytes from a PEM block (default label
319
319
  * `ATTRIBUTE CERTIFICATE`, the OpenSSL armor and the canonical-label default
320
320
  * every sibling format applies). Pass a `label` to enforce a different block
321
- * type, or an explicit `null` to take the first block of any type. Throws
322
- * `PemError` on a missing / mismatched envelope or a non-base64 body.
321
+ * type, or an explicit `null` to accept a block of any type. The text holds one
322
+ * object: a file of several is refused with `pem/multiple-blocks` and read with
323
+ * `pki.schema.pem.decodeBundle`. Throws `PemError` on a missing / mismatched
324
+ * envelope or a non-base64 body.
323
325
  *
324
326
  * @example
325
327
  * var pair = await pki.key.generate("Ed25519");
@@ -368,8 +368,10 @@ var parse = pkix.makeRecordingParser({
368
368
  * @related pki.schema.crmf.parse
369
369
  *
370
370
  * Extract the DER bytes from a PEM block (RFC 4211 registers no RFC 7468 label, so
371
- * the first block is taken unless `label` is given; CRMF rides inside CMP / EST as
372
- * DER in practice). Throws `PemError` on a missing envelope or a non-base64 body.
371
+ * a block of any label is accepted unless `label` is given; CRMF rides inside CMP /
372
+ * EST as DER in practice). The text holds one object: a file of several is refused
373
+ * with `pem/multiple-blocks` and read with `pki.schema.pem.decodeBundle`. Throws
374
+ * `PemError` on a missing envelope or a non-base64 body.
373
375
  *
374
376
  * @example
375
377
  * var pair = await pki.key.generate("Ed25519");
@@ -0,0 +1,335 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ // Copyright (c) blamejs contributors
3
+ "use strict";
4
+ /**
5
+ * @module pki.schema.pkcs1
6
+ * @nav Schema
7
+ * @title PKCS#1
8
+ * @fullname PKCS#1 RSA keys, the RSA PRIVATE KEY and RSA PUBLIC KEY encodings
9
+ * @order 145
10
+ * @slug pkcs1
11
+ *
12
+ * @intro
13
+ * The RSA key encodings of RFC 8017 Appendix A.1: `RSAPrivateKey` and
14
+ * `RSAPublicKey`, the `RSA PRIVATE KEY` and `RSA PUBLIC KEY` PEM blocks OpenSSL
15
+ * wrote by default for years and appliances still emit. `parse` and
16
+ * `parsePublic` read them into their components, and `encode` and `encodePublic`
17
+ * write them back.
18
+ *
19
+ * Neither structure carries an algorithm identifier, which is what separates
20
+ * them from PKCS#8 and SubjectPublicKeyInfo. So what the key is used under is
21
+ * the caller's decision, never a default this module supplies, and nothing here
22
+ * routes through `pki.schema.parse`: a SEQUENCE of INTEGERs is a shape other
23
+ * structures take, and detecting by shape alone would be a guess.
24
+ *
25
+ * Every relation the specification states between the components is checked, in
26
+ * the same pass that reads them. Appendix A.1 fixes the field order and the
27
+ * two-or-more-primes rule, sec. 3.1 requires the public exponent to be odd and
28
+ * between 3 and the modulus, and sec. 3.2 places the private exponent, the two
29
+ * CRT exponents and the coefficient below the modulus or their own prime. A
30
+ * component outside those describes no key, and the structure alone cannot say
31
+ * so.
32
+ *
33
+ * @card
34
+ * Read and write the RFC 8017 App. A.1 `RSAPrivateKey` and `RSAPublicKey`, DER
35
+ * or PEM, with every component relation the specification states checked and no
36
+ * algorithm inferred.
37
+ */
38
+
39
+ var asn1 = require("./asn1-der");
40
+ var schema = require("./schema-engine");
41
+ var pkix = require("./schema-pkix");
42
+ var oid = require("./oid");
43
+ var frameworkError = require("./framework-error");
44
+ var intrinsic = require("./guard-intrinsic");
45
+
46
+ var Pkcs1Error = frameworkError.Pkcs1Error;
47
+ var PemError = frameworkError.PemError;
48
+
49
+ var NS = pkix.makeNS("pkcs1", Pkcs1Error, oid);
50
+
51
+ var PRIVATE_LABEL = "RSA PRIVATE KEY";
52
+ var PUBLIC_LABEL = "RSA PUBLIC KEY";
53
+
54
+ /** @internal RFC 8017 sec. 3.1 and sec. 3.2 state every component as a positive integer. A DER
55
+ * INTEGER whose first content octet has its high bit set reads as a negative BigInt, and a
56
+ * negative or zero component describes no key, so it is refused at the leaf rather than carried. */
57
+ function _positive(value, what) {
58
+ if (typeof value !== "bigint" || value <= 0n) {
59
+ throw NS.E("pkcs1/bad-component", what + " must be a positive integer (RFC 8017 sec. 3.1, sec. 3.2)");
60
+ }
61
+ return value;
62
+ }
63
+
64
+ /** @internal sec. 3.2: `d < n`, `dP < p`, `dQ < q` and `qInv < p`. Each is an equality the key's
65
+ * own arithmetic depends on, and a value at or above its bound is not the value the specification
66
+ * names. */
67
+ function _below(value, bound, what, boundName) {
68
+ if (value >= bound) {
69
+ throw NS.E("pkcs1/bad-component", what + " must be smaller than " + boundName + " (RFC 8017 sec. 3.2)");
70
+ }
71
+ }
72
+
73
+ var OTHER_PRIME_INFO = schema.seq([
74
+ schema.field("prime", schema.integerLeaf()),
75
+ schema.field("exponent", schema.integerLeaf()),
76
+ schema.field("coefficient", schema.integerLeaf()),
77
+ ], {
78
+ assert: "sequence", arity: { min: 3, max: 3 }, code: "pkcs1/bad-other-prime-infos", what: "OtherPrimeInfo",
79
+ build: function (m) {
80
+ /** @internal RFC 8017 sec. 3.2 derives an additional prime's exponent modulo `r_i - 1` and its
81
+ * coefficient modulo `r_i`, so both are smaller than that prime, the same relation the first
82
+ * two primes are held to. Checking positivity alone here would read a key the specification
83
+ * does not describe. */
84
+ var prime = _positive(m.fields.prime.value, "an other prime info's prime");
85
+ var exponent = _positive(m.fields.exponent.value, "an other prime info's exponent");
86
+ var coefficient = _positive(m.fields.coefficient.value, "an other prime info's coefficient");
87
+ _below(exponent, prime, "an other prime info's exponent", "its prime");
88
+ _below(coefficient, prime, "an other prime info's coefficient", "its prime");
89
+ return { prime: prime, exponent: exponent, coefficient: coefficient };
90
+ },
91
+ });
92
+
93
+ var RSA_PRIVATE_KEY = schema.seq([
94
+ schema.field("version", schema.integerLeaf()),
95
+ schema.field("modulus", schema.integerLeaf()),
96
+ schema.field("publicExponent", schema.integerLeaf()),
97
+ schema.field("privateExponent", schema.integerLeaf()),
98
+ schema.field("prime1", schema.integerLeaf()),
99
+ schema.field("prime2", schema.integerLeaf()),
100
+ schema.field("exponent1", schema.integerLeaf()),
101
+ schema.field("exponent2", schema.integerLeaf()),
102
+ schema.field("coefficient", schema.integerLeaf()),
103
+ /** @internal App. A.1.2 puts `otherPrimeInfos` last and untagged, so it is recognized by being
104
+ * a universal SEQUENCE rather than by a context tag, and it is a SEQUENCE OF: the order is the
105
+ * key's own and carries no DER SET ordering. */
106
+ schema.optional("otherPrimeInfos", schema.seqOf(OTHER_PRIME_INFO, { min: 0, code: "pkcs1/bad-other-prime-infos", what: "otherPrimeInfos" }),
107
+ { whenUniversal: [asn1.TAGS.SEQUENCE] }),
108
+ ], {
109
+ assert: "sequence", arity: { min: 9, max: 10 }, code: "pkcs1/not-an-rsa-private-key", what: "RSAPrivateKey",
110
+ build: function (m) {
111
+ var version = m.fields.version.value;
112
+ if (version !== 0n && version !== 1n) {
113
+ throw NS.E("pkcs1/bad-version", "an RSAPrivateKey is version 0 or 1 (RFC 8017 App. A.1.2), got " + version);
114
+ }
115
+ var multi = m.fields.otherPrimeInfos.present;
116
+ var infos = multi
117
+ ? intrinsic.map(m.fields.otherPrimeInfos.value.items, function (it) { return it.value.result; })
118
+ : [];
119
+ /** @internal App. A.1.2 ties the version to the prime count: "otherPrimeInfos ... shall be
120
+ * omitted if version is 0 and shall contain at least one instance of OtherPrimeInfo if version
121
+ * is 1". A key stating one and carrying the other states two different keys. */
122
+ if (version === 0n && multi) {
123
+ throw NS.E("pkcs1/bad-other-prime-infos", "a version-0 RSAPrivateKey carries no otherPrimeInfos (RFC 8017 App. A.1.2)");
124
+ }
125
+ if (version === 1n && !multi) {
126
+ throw NS.E("pkcs1/bad-other-prime-infos", "a version-1 RSAPrivateKey carries at least one OtherPrimeInfo (RFC 8017 App. A.1.2)");
127
+ }
128
+ if (version === 1n && infos.length === 0) {
129
+ throw NS.E("pkcs1/bad-other-prime-infos", "otherPrimeInfos is SIZE(1..MAX), so an empty one is refused (RFC 8017 App. A.1.2)");
130
+ }
131
+
132
+ var modulus = _positive(m.fields.modulus.value, "the modulus");
133
+ var publicExponent = _positive(m.fields.publicExponent.value, "the public exponent");
134
+ var privateExponent = _positive(m.fields.privateExponent.value, "the private exponent");
135
+ var prime1 = _positive(m.fields.prime1.value, "prime1");
136
+ var prime2 = _positive(m.fields.prime2.value, "prime2");
137
+ var exponent1 = _positive(m.fields.exponent1.value, "exponent1");
138
+ var exponent2 = _positive(m.fields.exponent2.value, "exponent2");
139
+ var coefficient = _positive(m.fields.coefficient.value, "the coefficient");
140
+ _assertExponent(publicExponent, modulus);
141
+ _below(privateExponent, modulus, "the private exponent", "the modulus");
142
+ _below(exponent1, prime1, "exponent1", "prime1");
143
+ _below(exponent2, prime2, "exponent2", "prime2");
144
+ _below(coefficient, prime1, "the coefficient", "prime1");
145
+ return {
146
+ version: intrinsic.Number(version), modulus: modulus, publicExponent: publicExponent,
147
+ privateExponent: privateExponent, prime1: prime1, prime2: prime2,
148
+ exponent1: exponent1, exponent2: exponent2, coefficient: coefficient,
149
+ otherPrimeInfos: infos,
150
+ };
151
+ },
152
+ });
153
+
154
+ /** @internal sec. 3.1: "the RSA public exponent e ... 3 <= e < n". An even exponent has no
155
+ * multiplicative inverse modulo a product of odd primes, so it names no key either. */
156
+ function _assertExponent(e, n) {
157
+ if (e < 3n || e >= n) {
158
+ throw NS.E("pkcs1/bad-exponent", "the public exponent must satisfy 3 <= e < n (RFC 8017 sec. 3.1)");
159
+ }
160
+ if ((e & 1n) === 0n) {
161
+ throw NS.E("pkcs1/bad-exponent", "the public exponent must be odd (RFC 8017 sec. 3.1)");
162
+ }
163
+ }
164
+
165
+ var RSA_PUBLIC_KEY = schema.seq([
166
+ schema.field("modulus", schema.integerLeaf()),
167
+ schema.field("publicExponent", schema.integerLeaf()),
168
+ ], {
169
+ assert: "sequence", arity: { min: 2, max: 2 }, code: "pkcs1/not-an-rsa-public-key", what: "RSAPublicKey",
170
+ build: function (m) {
171
+ var modulus = _positive(m.fields.modulus.value, "the modulus");
172
+ var publicExponent = _positive(m.fields.publicExponent.value, "the public exponent");
173
+ _assertExponent(publicExponent, modulus);
174
+ return { modulus: modulus, publicExponent: publicExponent };
175
+ },
176
+ });
177
+
178
+ var PRIVATE_OPTS = intrinsic.assign(intrinsic.create(null), {
179
+ pemLabel: PRIVATE_LABEL, PemError: PemError, ErrorClass: Pkcs1Error, prefix: "pkcs1",
180
+ what: "RSA private key", topSchema: RSA_PRIVATE_KEY, ns: NS,
181
+ });
182
+ var PUBLIC_OPTS = intrinsic.assign(intrinsic.create(null), {
183
+ pemLabel: PUBLIC_LABEL, PemError: PemError, ErrorClass: Pkcs1Error, prefix: "pkcs1",
184
+ what: "RSA public key", topSchema: RSA_PUBLIC_KEY, ns: NS,
185
+ });
186
+
187
+ /**
188
+ * @primitive pki.schema.pkcs1.parse
189
+ * @signature pki.schema.pkcs1.parse(input, opts) -> key
190
+ * @since 0.8.8
191
+ * @status stable
192
+ * @spec RFC 8017
193
+ * @related pki.schema.pkcs1.parsePublic, pki.schema.pkcs8.parse
194
+ *
195
+ * Read an RFC 8017 Appendix A.1.2 `RSAPrivateKey`, as DER bytes or an
196
+ * `RSA PRIVATE KEY` PEM block, into `{ version, modulus, publicExponent,
197
+ * privateExponent, prime1, prime2, exponent1, exponent2, coefficient,
198
+ * otherPrimeInfos }`. Every component is a `BigInt`.
199
+ *
200
+ * The structure names no algorithm, so nothing here decides what the key is used
201
+ * under. Read it and hand the components to the verb that does.
202
+ *
203
+ * Fail-closed on the specification's own relations: a version other than 0 or 1, a
204
+ * version disagreeing with whether other prime infos are present, an
205
+ * `OtherPrimeInfo` that is not three integers, a component that is not a positive
206
+ * integer, a public exponent outside `3 <= e < n` or an even one, and a private
207
+ * exponent, CRT exponent or coefficient at or above its bound are each refused
208
+ * with their own `pkcs1/*` code.
209
+ *
210
+ * @example
211
+ * // requires: der -- an RSAPrivateKey, from `openssl genrsa` or an appliance that emits one
212
+ * var key = pki.schema.pkcs1.parse(der);
213
+ * key.version; // 0
214
+ * key.publicExponent; // 65537n
215
+ */
216
+ var parse = pkix.makeParser(PRIVATE_OPTS);
217
+
218
+ /**
219
+ * @primitive pki.schema.pkcs1.parsePublic
220
+ * @signature pki.schema.pkcs1.parsePublic(input, opts) -> key
221
+ * @since 0.8.8
222
+ * @status stable
223
+ * @spec RFC 8017
224
+ * @related pki.schema.pkcs1.parse
225
+ *
226
+ * Read an RFC 8017 Appendix A.1.1 `RSAPublicKey`, as DER bytes or an
227
+ * `RSA PUBLIC KEY` PEM block, into `{ modulus, publicExponent }`. A structure
228
+ * carrying anything else, including a private key, is refused: the two encodings
229
+ * are told apart by their shape and neither door reads the other's.
230
+ *
231
+ * @example
232
+ * // requires: der -- an RSAPrivateKey, from `openssl genrsa` or an appliance that emits one
233
+ * var priv = pki.schema.pkcs1.parse(der);
234
+ * var pubDer = pki.schema.pkcs1.encodePublic({ modulus: priv.modulus, publicExponent: priv.publicExponent });
235
+ * pki.schema.pkcs1.parsePublic(pubDer).publicExponent; // 65537n
236
+ */
237
+ var parsePublic = pkix.makeParser(PUBLIC_OPTS);
238
+
239
+ /**
240
+ * @primitive pki.schema.pkcs1.encode
241
+ * @signature pki.schema.pkcs1.encode(key) -> Buffer
242
+ * @since 0.8.8
243
+ * @status stable
244
+ * @spec RFC 8017
245
+ * @related pki.schema.pkcs1.parse
246
+ *
247
+ * Write an `RSAPrivateKey` back to DER from the shape `parse` returns. What `parse`
248
+ * read is what this writes: a key read and written again produces the bytes it came
249
+ * from.
250
+ *
251
+ * @example
252
+ * // requires: der -- an RSAPrivateKey, from `openssl genrsa` or an appliance that emits one
253
+ * pki.schema.pkcs1.encode(pki.schema.pkcs1.parse(der)).equals(der); // true
254
+ */
255
+ function encode(key) {
256
+ if (key === null || typeof key !== "object") {
257
+ throw NS.E("pkcs1/bad-input", "encode takes the object parse returns");
258
+ }
259
+ var kids = [asn1.build.integer(intrinsic.BigInt(key.version)), asn1.build.integer(key.modulus),
260
+ asn1.build.integer(key.publicExponent), asn1.build.integer(key.privateExponent),
261
+ asn1.build.integer(key.prime1), asn1.build.integer(key.prime2),
262
+ asn1.build.integer(key.exponent1), asn1.build.integer(key.exponent2),
263
+ asn1.build.integer(key.coefficient)];
264
+ var infos = key.otherPrimeInfos;
265
+ if (intrinsic.isArray(infos) && infos.length > 0) {
266
+ var rows = [];
267
+ for (var i = 0; i < infos.length; i++) {
268
+ rows[i] = asn1.build.sequence([asn1.build.integer(infos[i].prime),
269
+ asn1.build.integer(infos[i].exponent), asn1.build.integer(infos[i].coefficient)]);
270
+ }
271
+ kids[9] = asn1.build.sequence(rows);
272
+ }
273
+ return asn1.build.sequence(kids);
274
+ }
275
+
276
+ /**
277
+ * @primitive pki.schema.pkcs1.encodePublic
278
+ * @signature pki.schema.pkcs1.encodePublic(key) -> Buffer
279
+ * @since 0.8.8
280
+ * @status stable
281
+ * @spec RFC 8017
282
+ * @related pki.schema.pkcs1.parsePublic
283
+ *
284
+ * Write an `RSAPublicKey` back to DER from the shape `parsePublic` returns.
285
+ *
286
+ * @example
287
+ * // requires: der -- an RSAPrivateKey, from `openssl genrsa` or an appliance that emits one
288
+ * var priv = pki.schema.pkcs1.parse(der);
289
+ * var pubDer = pki.schema.pkcs1.encodePublic({ modulus: priv.modulus, publicExponent: priv.publicExponent });
290
+ * pki.schema.pkcs1.encodePublic(pki.schema.pkcs1.parsePublic(pubDer)).equals(pubDer); // true
291
+ */
292
+ function encodePublic(key) {
293
+ if (key === null || typeof key !== "object") {
294
+ throw NS.E("pkcs1/bad-input", "encodePublic takes the object parsePublic returns");
295
+ }
296
+ return asn1.build.sequence([asn1.build.integer(key.modulus), asn1.build.integer(key.publicExponent)]);
297
+ }
298
+
299
+ function pemDecode(text) { return pkix.pemDecode(text, PRIVATE_LABEL, PemError); }
300
+ function pemEncode(der) { return pkix.pemEncode(der, PRIVATE_LABEL, PemError); }
301
+ function pemDecodePublic(text) { return pkix.pemDecode(text, PUBLIC_LABEL, PemError); }
302
+ function pemEncodePublic(der) { return pkix.pemEncode(der, PUBLIC_LABEL, PemError); }
303
+
304
+ /** @internal RFC 8017 App. A.1.2: `RSAPrivateKey ::= SEQUENCE { version, modulus, publicExponent,
305
+ * privateExponent, prime1, prime2, exponent1, exponent2, coefficient, otherPrimeInfos OPTIONAL }`,
306
+ * so nine INTEGERs and an optional SEQUENCE. Structure alone does not separate this from every
307
+ * other SEQUENCE of INTEGERs, which is why nothing routes to it from bare bytes; it answers for a
308
+ * caller that already has the armor label saying what the bytes are meant to be. */
309
+ function matches(root) {
310
+ var k = pkix.rootSequenceChildren(root, 9, 10);
311
+ if (!k) return false;
312
+ for (var i = 0; i < 9; i++) {
313
+ if (!schema.isUniversal(k[i], asn1.TAGS.INTEGER)) return false;
314
+ }
315
+ return k.length === 9 || schema.isUniversal(k[9], asn1.TAGS.SEQUENCE);
316
+ }
317
+
318
+ /** @internal RFC 8017 App. A.1.1: `RSAPublicKey ::= SEQUENCE { modulus, publicExponent }`. */
319
+ function matchesPublic(root) {
320
+ var k = pkix.rootSequenceChildren(root, 2, 2);
321
+ return !!k && schema.isUniversal(k[0], asn1.TAGS.INTEGER) && schema.isUniversal(k[1], asn1.TAGS.INTEGER);
322
+ }
323
+
324
+ module.exports = {
325
+ parse: parse,
326
+ parsePublic: parsePublic,
327
+ matches: matches,
328
+ matchesPublic: matchesPublic,
329
+ encode: encode,
330
+ encodePublic: encodePublic,
331
+ pemDecode: pemDecode,
332
+ pemEncode: pemEncode,
333
+ pemDecodePublic: pemDecodePublic,
334
+ pemEncodePublic: pemEncodePublic,
335
+ };
@@ -193,6 +193,20 @@ function matches(root) {
193
193
  }
194
194
 
195
195
 
196
+ /** @internal RFC 5958 sec. 3: `EncryptedPrivateKeyInfo ::= SEQUENCE { encryptionAlgorithm
197
+ * AlgorithmIdentifier, encryptedData OCTET STRING }`. Exactly two children where a
198
+ * `PrivateKeyInfo` has three or more, and an `AlgorithmIdentifier` where that one has an INTEGER
199
+ * version, so the two are told apart by their first child and neither is read as the other. */
200
+ function matchesEncrypted(root) {
201
+ var TAGS = asn1.TAGS;
202
+ var k = pkix.rootSequenceChildren(root, 2, 2);
203
+ if (!k) return false;
204
+ if (!schema.isUniversal(k[0], TAGS.SEQUENCE) || !k[0].children || k[0].children.length < 1) return false;
205
+ if (!schema.isUniversal(k[0].children[0], TAGS.OBJECT_IDENTIFIER)) return false;
206
+ return schema.isUniversal(k[1], TAGS.OCTET_STRING) && !k[1].constructed;
207
+ }
208
+
209
+
196
210
  /** @internal `ctx` is the walk context of the parse that reached this key, when another format
197
211
  * carries it. Only its caps and budget cross over: the key is still read in this namespace, so a
198
212
  * fault in it keeps the PKCS#8 error class and code. */
@@ -209,6 +223,7 @@ module.exports = {
209
223
  pemDecode: pemDecode,
210
224
  pemEncode: pemEncode,
211
225
  matches: matches,
226
+ matchesEncrypted: matchesEncrypted,
212
227
  walkPrivateKeyInfo: walkPrivateKeyInfo,
213
228
  walkEncryptedPrivateKeyInfo: walkEncryptedPrivateKeyInfo,
214
229
  };