@blamejs/pki 0.1.22 → 0.1.23
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/CHANGELOG.md +35 -0
- package/README.md +16 -6
- package/index.js +16 -12
- package/lib/asn1-der.js +126 -68
- package/lib/constants.js +46 -21
- package/lib/ct.js +54 -39
- package/lib/framework-error.js +57 -34
- package/lib/oid.js +110 -59
- package/lib/path-validate.js +302 -135
- package/lib/schema-all.js +49 -41
- package/lib/schema-attrcert.js +101 -64
- package/lib/schema-cmp.js +152 -131
- package/lib/schema-cms.js +621 -163
- package/lib/schema-crl.js +43 -21
- package/lib/schema-crmf.js +136 -79
- package/lib/schema-csr.js +63 -14
- package/lib/schema-engine.js +92 -43
- package/lib/schema-ocsp.js +101 -55
- package/lib/schema-pkcs12.js +80 -64
- package/lib/schema-pkcs8.js +12 -12
- package/lib/schema-pkix.js +283 -100
- package/lib/schema-smime.js +39 -39
- package/lib/schema-tsp.js +108 -59
- package/lib/schema-x509.js +36 -25
- package/lib/webcrypto.js +156 -22
- package/package.json +3 -2
- package/sbom.cdx.json +6 -6
package/lib/schema-all.js
CHANGED
|
@@ -11,13 +11,13 @@
|
|
|
11
11
|
*
|
|
12
12
|
* @intro
|
|
13
13
|
* The schema family: a declarative ASN.1 structure-schema engine and the
|
|
14
|
-
* per-format parsers built on it. Every format
|
|
14
|
+
* per-format parsers built on it. Every format -- from X.509 certificates
|
|
15
15
|
* and CRLs through CMS, OCSP, timestamps, and PKCS#12 stores; `all()`
|
|
16
|
-
* enumerates the registered set
|
|
16
|
+
* enumerates the registered set -- is a member that COMPOSES the
|
|
17
17
|
* shared engine and the shared PKIX sub-schemas (AlgorithmIdentifier, Name,
|
|
18
|
-
* Extension), so a structural rule
|
|
18
|
+
* Extension), so a structural rule -- bounds-checked positional reads,
|
|
19
19
|
* optional / tagged field ordering, SET-OF uniqueness, fail-closed typed
|
|
20
|
-
* errors
|
|
20
|
+
* errors -- is defined once in the engine and no format can reintroduce the
|
|
21
21
|
* class of bug it prevents.
|
|
22
22
|
*
|
|
23
23
|
* `parse` is the orchestrator: hand it DER (or PEM) and it detects which
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
* and `all()` enumerates the registered formats.
|
|
27
27
|
*
|
|
28
28
|
* @card
|
|
29
|
-
* One declarative schema engine; every PKI format (X.509, CRL,
|
|
29
|
+
* One declarative schema engine; every PKI format (X.509, CRL, ...) is a
|
|
30
30
|
* member composed on it. Detect-and-parse DER, or call a format directly.
|
|
31
31
|
*/
|
|
32
32
|
|
|
@@ -44,7 +44,7 @@ var crmf = require("./schema-crmf");
|
|
|
44
44
|
var cmp = require("./schema-cmp");
|
|
45
45
|
var attrcert = require("./schema-attrcert");
|
|
46
46
|
// smime is a COMPANION decoder for CMS signed-attribute values, NOT a top-level
|
|
47
|
-
// format
|
|
47
|
+
// format -- it has no self-describing DER root (an ESS attribute value is a bare
|
|
48
48
|
// SEQUENCE) so it is deliberately absent from FORMATS / the detect-and-route
|
|
49
49
|
// `parse`; it is reached only by explicit OID dispatch on a CMS attribute.
|
|
50
50
|
var smime = require("./schema-smime");
|
|
@@ -61,14 +61,14 @@ var PemError = frameworkError.PemError;
|
|
|
61
61
|
// fails typed inside that format's own parse.
|
|
62
62
|
var ENTRY = { pemLabel: null, PemError: PemError, ErrorClass: SchemaError, prefix: "schema", what: "input", ber: true };
|
|
63
63
|
|
|
64
|
-
// REGISTRY
|
|
64
|
+
// REGISTRY -- each format is declarative data: a name, a `detect(root)` that
|
|
65
65
|
// recognizes the decoded DER root as this format's outer structure, and the
|
|
66
66
|
// member's own `parse`. Adding a format is a table entry, not new dispatch
|
|
67
67
|
// logic. Order matters: the most specific detector wins, so a new member is
|
|
68
68
|
// inserted ahead of any more-permissive one.
|
|
69
69
|
var FORMATS = [
|
|
70
70
|
{
|
|
71
|
-
// CMS ContentInfo (RFC 5652
|
|
71
|
+
// CMS ContentInfo (RFC 5652 sec. 3) -- the only registered root that leads with an
|
|
72
72
|
// OBJECT IDENTIFIER child (a SEQUENCE of exactly 2: contentType OID + a
|
|
73
73
|
// context [0] EXPLICIT content wrapper). Disjoint from the INTEGER-first pkcs8
|
|
74
74
|
// and the tbs-SEQUENCE-first signed-envelope trio, so it detects unambiguously.
|
|
@@ -80,13 +80,13 @@ var FORMATS = [
|
|
|
80
80
|
parse: function (input) { return cms.parse(input); },
|
|
81
81
|
},
|
|
82
82
|
{
|
|
83
|
-
// TimeStampResp ::= SEQUENCE { status PKIStatusInfo, timeStampToken OPTIONAL }
|
|
83
|
+
// TimeStampResp ::= SEQUENCE { status PKIStatusInfo, timeStampToken OPTIONAL } --
|
|
84
84
|
// a SEQUENCE of 1-2 whose first child is a PKIStatusInfo SEQUENCE whose own first
|
|
85
85
|
// child is an INTEGER status. This detector is a strict REFINEMENT of the
|
|
86
86
|
// ocsp-request one (both are SEQUENCE-of-1-2 with a SEQUENCE first child), so it
|
|
87
87
|
// MUST be checked first: a tokenless TimeStampResp (a bare PKIStatusInfo) would
|
|
88
88
|
// otherwise be shadowed by ocsp-request, whereas an OCSPRequest's tbsRequest never
|
|
89
|
-
// leads with a universal INTEGER and so never matches here (RFC 3161
|
|
89
|
+
// leads with a universal INTEGER and so never matches here (RFC 3161 sec. 2.4.2). A
|
|
90
90
|
// bare timestamp token is a CMS ContentInfo and routes to cms; a bare TSTInfo is
|
|
91
91
|
// an internal payload (reached via pki.schema.tsp.parseTstInfo).
|
|
92
92
|
name: "tsp",
|
|
@@ -95,7 +95,7 @@ var FORMATS = [
|
|
|
95
95
|
parse: function (input) { return tsp.parse(input); },
|
|
96
96
|
},
|
|
97
97
|
{
|
|
98
|
-
// CertReqMessages ::= SEQUENCE SIZE(1..MAX) OF CertReqMsg (RFC 4211
|
|
98
|
+
// CertReqMessages ::= SEQUENCE SIZE(1..MAX) OF CertReqMsg (RFC 4211 sec. 3) -- a
|
|
99
99
|
// SEQUENCE whose first CertReqMsg's CertRequest leads with a universal INTEGER
|
|
100
100
|
// (certReqId) then a universal SEQUENCE (certTemplate). Leads with a SEQUENCE
|
|
101
101
|
// like the signed-envelope trio and the ocsp-request, but the INTEGER-then-
|
|
@@ -109,13 +109,13 @@ var FORMATS = [
|
|
|
109
109
|
},
|
|
110
110
|
{
|
|
111
111
|
// PKIMessage ::= SEQUENCE { header PKIHeader, body PKIBody [0..26],
|
|
112
|
-
// protection [0]?, extraCerts [1]? } (RFC 9810
|
|
112
|
+
// protection [0]?, extraCerts [1]? } (RFC 9810 sec. 5.1) -- a SEQUENCE of 2-4
|
|
113
113
|
// whose first child is a >=3-child SEQUENCE leading with a bare INTEGER
|
|
114
114
|
// (pvno) and whose second child is context-constructed [0..26]. ORDER IS
|
|
115
115
|
// LOAD-BEARING here: a 2-child PKIMessage whose body is ir [0] also
|
|
116
116
|
// satisfies the shallow ocsp-request probe below (k[0] SEQUENCE +
|
|
117
117
|
// k[1] context-[0]), while no OCSPRequest satisfies this detector (its
|
|
118
|
-
// tbsRequest never leads with a bare INTEGER)
|
|
118
|
+
// tbsRequest never leads with a bare INTEGER) -- so cmp MUST sit ahead of
|
|
119
119
|
// ocsp-request for the pair to dispatch deterministically.
|
|
120
120
|
name: "cmp",
|
|
121
121
|
module: cmp,
|
|
@@ -124,10 +124,10 @@ var FORMATS = [
|
|
|
124
124
|
},
|
|
125
125
|
{
|
|
126
126
|
// OCSPRequest ::= SEQUENCE { tbsRequest SEQUENCE, optionalSignature [0] EXPLICIT
|
|
127
|
-
// OPTIONAL }
|
|
127
|
+
// OPTIONAL } -- a SEQUENCE of 1-2 whose first child is the tbsRequest SEQUENCE.
|
|
128
128
|
// Leads with a SEQUENCE like the signed-envelope trio, but is excluded by arity
|
|
129
129
|
// (the trio is EXACTLY 3 children; an OCSPRequest is 1-2). Checked AFTER tsp,
|
|
130
|
-
// whose detector is a strict refinement (RFC 6960
|
|
130
|
+
// whose detector is a strict refinement (RFC 6960 sec. 4.1.1), and AFTER cmp,
|
|
131
131
|
// whose 2-child ir-body shape this probe would otherwise shadow.
|
|
132
132
|
name: "ocsp-request",
|
|
133
133
|
module: ocsp,
|
|
@@ -136,8 +136,8 @@ var FORMATS = [
|
|
|
136
136
|
},
|
|
137
137
|
{
|
|
138
138
|
// OCSPResponse ::= SEQUENCE { responseStatus ENUMERATED, responseBytes [0]
|
|
139
|
-
// EXPLICIT OPTIONAL }
|
|
140
|
-
// child, so it is disjoint from every other format (RFC 6960
|
|
139
|
+
// EXPLICIT OPTIONAL } -- the only registered root that leads with an ENUMERATED
|
|
140
|
+
// child, so it is disjoint from every other format (RFC 6960 sec. 4.2.1).
|
|
141
141
|
name: "ocsp-response",
|
|
142
142
|
module: ocsp,
|
|
143
143
|
detect: ocsp.matchesResponse,
|
|
@@ -145,12 +145,12 @@ var FORMATS = [
|
|
|
145
145
|
},
|
|
146
146
|
{
|
|
147
147
|
// PKCS#12 PFX ::= SEQUENCE { version INTEGER, authSafe ContentInfo, macData
|
|
148
|
-
// OPTIONAL }
|
|
149
|
-
// children[1] is a ContentInfo (SEQUENCE of exactly 2: OID + [0] constructed
|
|
148
|
+
// OPTIONAL } -- INTEGER-first like pkcs8, so the discriminators are deeper:
|
|
149
|
+
// children[1] is a ContentInfo (SEQUENCE of exactly 2: OID + [0] constructed --
|
|
150
150
|
// a shape a PrivateKeyInfo's AlgorithmIdentifier never presents) and
|
|
151
151
|
// children[2] is a MacData SEQUENCE or absent (pkcs8 requires an OCTET STRING
|
|
152
152
|
// there). Shape-disjoint from pkcs8, and registered ahead of it as the more
|
|
153
|
-
// specific probe (RFC 7292
|
|
153
|
+
// specific probe (RFC 7292 sec. 4). A BER-encoded PFX detects through the
|
|
154
154
|
// orchestrator's BER decode fallback and routes here.
|
|
155
155
|
name: "pkcs12",
|
|
156
156
|
module: pkcs12,
|
|
@@ -158,11 +158,11 @@ var FORMATS = [
|
|
|
158
158
|
parse: function (input) { return pkcs12.parse(input); },
|
|
159
159
|
},
|
|
160
160
|
{
|
|
161
|
-
// PKCS#8 PrivateKeyInfo / OneAsymmetricKey
|
|
161
|
+
// PKCS#8 PrivateKeyInfo / OneAsymmetricKey -- SEQUENCE whose first child is an
|
|
162
162
|
// INTEGER (version) and third an OCTET STRING (privateKey); disjoint from the
|
|
163
163
|
// signed-envelope trio. (EncryptedPrivateKeyInfo is deliberately NOT
|
|
164
|
-
// auto-routed: its SEQUENCE{SEQUENCE, OCTET STRING} shape is ambiguous
|
|
165
|
-
// PKCS#1 DigestInfo is identical
|
|
164
|
+
// auto-routed: its SEQUENCE{SEQUENCE, OCTET STRING} shape is ambiguous -- a
|
|
165
|
+
// PKCS#1 DigestInfo is identical -- so structural detection cannot classify it
|
|
166
166
|
// without a validated encryption-algorithm discriminator, which arrives with
|
|
167
167
|
// the PBES layer. It is reached explicitly via pki.schema.pkcs8.parseEncrypted.)
|
|
168
168
|
name: "pkcs8",
|
|
@@ -172,7 +172,7 @@ var FORMATS = [
|
|
|
172
172
|
},
|
|
173
173
|
{
|
|
174
174
|
// CertificationRequest ::= SEQUENCE { certificationRequestInfo,
|
|
175
|
-
// signatureAlgorithm, signature }
|
|
175
|
+
// signatureAlgorithm, signature } -- the same outer 3-element shape,
|
|
176
176
|
// distinguished by a CertificationRequestInfo of EXACTLY four children
|
|
177
177
|
// ending in the IMPLICIT [0] attributes element. Checked first because that
|
|
178
178
|
// detector is the most specific and mutually exclusive with the others.
|
|
@@ -183,20 +183,28 @@ var FORMATS = [
|
|
|
183
183
|
},
|
|
184
184
|
{
|
|
185
185
|
// AttributeCertificate ::= SEQUENCE { acinfo, signatureAlgorithm, signatureValue }
|
|
186
|
-
//
|
|
187
|
-
//
|
|
188
|
-
//
|
|
189
|
-
//
|
|
186
|
+
// -- the signed-envelope trio, recognized by an acinfo that LEADS WITH a bare
|
|
187
|
+
// INTEGER version and carries a 2-GeneralizedTime attrCertValidityPeriod at
|
|
188
|
+
// children[5] (RFC 5755, section 4.1). Order-independent versus crl / x509 / csr:
|
|
189
|
+
// a v2 acinfo puts the AlgorithmIdentifier at the position where their probes
|
|
190
|
+
// require a bare Time / a 2-Time Validity / a 4-child CRI, and none of their tbs
|
|
191
|
+
// shapes carry the 2-GeneralizedTime marker at children[5].
|
|
190
192
|
name: "attrcert",
|
|
191
193
|
module: attrcert,
|
|
192
194
|
detect: attrcert.matches,
|
|
193
195
|
parse: function (input) { return attrcert.parse(input); },
|
|
194
196
|
},
|
|
195
197
|
{
|
|
196
|
-
// AttributeCertificateV1 (X.509-1997, RFC 5652
|
|
197
|
-
// whose acInfo LEADS WITH the subject CHOICE (context [0]/[1]).
|
|
198
|
-
// deferred with a precise attrcert/legacy-v1-not-supported rather
|
|
199
|
-
// wrong format.
|
|
198
|
+
// AttributeCertificateV1 (X.509-1997, RFC 5652, section 10.2) -- the obsolete
|
|
199
|
+
// predecessor whose acInfo LEADS WITH the subject CHOICE (context [0]/[1]).
|
|
200
|
+
// Recognized and deferred with a precise attrcert/legacy-v1-not-supported rather
|
|
201
|
+
// than routed to a wrong format. ORDER IS LOAD-BEARING against x509: a [0]-subject
|
|
202
|
+
// v1 AC ALSO satisfies x509.matches (the [0] child reads as the certificate's
|
|
203
|
+
// EXPLICIT version marker, putting the 2-GeneralizedTime attrCertValidityPeriod at
|
|
204
|
+
// the Validity probe offset), so this entry MUST sit ahead of x509 for first-match
|
|
205
|
+
// dispatch to defer it here. children[1] == SEQUENCE keeps a v3 certificate (whose
|
|
206
|
+
// [0] version is followed by an INTEGER serialNumber) from matching in the other
|
|
207
|
+
// direction.
|
|
200
208
|
name: "attrcert-v1",
|
|
201
209
|
module: attrcert,
|
|
202
210
|
detect: attrcert.matchesV1,
|
|
@@ -204,7 +212,7 @@ var FORMATS = [
|
|
|
204
212
|
},
|
|
205
213
|
{
|
|
206
214
|
// CertificateList ::= SEQUENCE { tbsCertList, signatureAlgorithm,
|
|
207
|
-
// signatureValue }
|
|
215
|
+
// signatureValue } -- the same outer shape as a certificate, distinguished
|
|
208
216
|
// by its tbsCertList (a bare Time at the certificate's Validity position).
|
|
209
217
|
name: "crl",
|
|
210
218
|
module: crl,
|
|
@@ -214,7 +222,7 @@ var FORMATS = [
|
|
|
214
222
|
{
|
|
215
223
|
name: "x509",
|
|
216
224
|
module: x509,
|
|
217
|
-
// Certificate
|
|
225
|
+
// Certificate -- identified by a Validity (SEQUENCE of two Times) inside the
|
|
218
226
|
// tbs, so a CSR / other 3-element signed envelope is NOT misclassified as a
|
|
219
227
|
// certificate (it falls through to schema/unknown-format).
|
|
220
228
|
detect: x509.matches,
|
|
@@ -233,7 +241,7 @@ var FORMATS = [
|
|
|
233
241
|
* The names of every registered format, in detection order.
|
|
234
242
|
*
|
|
235
243
|
* @example
|
|
236
|
-
* pki.schema.all(); //
|
|
244
|
+
* pki.schema.all(); // -> ["cms", "tsp", "crmf", "cmp", "ocsp-request", "ocsp-response", "pkcs12", "pkcs8", "csr", "attrcert", "attrcert-v1", "crl", "x509"]
|
|
237
245
|
*/
|
|
238
246
|
function all() { return FORMATS.map(function (f) { return f.name; }); }
|
|
239
247
|
|
|
@@ -252,7 +260,7 @@ function all() { return FORMATS.map(function (f) { return f.name; }); }
|
|
|
252
260
|
* errors of the matched format propagate unchanged.
|
|
253
261
|
*
|
|
254
262
|
* @example
|
|
255
|
-
* var parsed = pki.schema.parse(der); // cert
|
|
263
|
+
* var parsed = pki.schema.parse(der); // cert -> the pki.schema.x509 shape
|
|
256
264
|
*/
|
|
257
265
|
function parse(input) {
|
|
258
266
|
// Coerce + decode via the shared parse-entry, then route the COERCED DER (a
|
|
@@ -272,16 +280,16 @@ function parse(input) {
|
|
|
272
280
|
module.exports = {
|
|
273
281
|
engine: engine,
|
|
274
282
|
x509: { parse: x509.parse, pemDecode: x509.pemDecode, pemEncode: x509.pemEncode },
|
|
275
|
-
crl: { parse: crl.parse, pemDecode: crl.pemDecode },
|
|
283
|
+
crl: { parse: crl.parse, pemDecode: crl.pemDecode, pemEncode: crl.pemEncode },
|
|
276
284
|
csr: { parse: csr.parse, pemDecode: csr.pemDecode, pemEncode: csr.pemEncode },
|
|
277
285
|
pkcs8: { parse: pkcs8.parse, parseEncrypted: pkcs8.parseEncrypted, pemDecode: pkcs8.pemDecode, pemEncode: pkcs8.pemEncode },
|
|
278
286
|
pkcs12: { parse: pkcs12.parse, pemDecode: pkcs12.pemDecode, pemEncode: pkcs12.pemEncode },
|
|
279
287
|
cms: { parse: cms.parse, pemDecode: cms.pemDecode, pemEncode: cms.pemEncode },
|
|
280
|
-
ocsp: { parseRequest: ocsp.parseRequest, parseResponse: ocsp.parseResponse, pemDecode: ocsp.pemDecode },
|
|
281
|
-
tsp: { parse: tsp.parse, parseTstInfo: tsp.parseTstInfo, parseToken: tsp.parseToken, pemDecode: tsp.pemDecode },
|
|
282
|
-
crmf: { parse: crmf.parse, pemDecode: crmf.pemDecode },
|
|
288
|
+
ocsp: { parseRequest: ocsp.parseRequest, parseResponse: ocsp.parseResponse, pemDecode: ocsp.pemDecode, pemEncode: ocsp.pemEncode },
|
|
289
|
+
tsp: { parse: tsp.parse, parseTstInfo: tsp.parseTstInfo, parseToken: tsp.parseToken, pemDecode: tsp.pemDecode, pemEncode: tsp.pemEncode },
|
|
290
|
+
crmf: { parse: crmf.parse, pemDecode: crmf.pemDecode, pemEncode: crmf.pemEncode },
|
|
283
291
|
cmp: { parse: cmp.parse, pemDecode: cmp.pemDecode, pemEncode: cmp.pemEncode },
|
|
284
|
-
attrcert: { parse: attrcert.parse, pemDecode: attrcert.pemDecode },
|
|
292
|
+
attrcert: { parse: attrcert.parse, pemDecode: attrcert.pemDecode, pemEncode: attrcert.pemEncode },
|
|
285
293
|
smime: { parseSigningCertificate: smime.parseSigningCertificate, parseSigningCertificateV2: smime.parseSigningCertificateV2, parseSmimeCapabilities: smime.parseSmimeCapabilities, decodeAttribute: smime.decodeAttribute },
|
|
286
294
|
all: all,
|
|
287
295
|
parse: parse,
|
package/lib/schema-attrcert.js
CHANGED
|
@@ -11,23 +11,25 @@
|
|
|
11
11
|
* @intro
|
|
12
12
|
* X.509 Attribute Certificate handling per RFC 5755. An attribute certificate
|
|
13
13
|
* binds a holder to a set of privilege attributes (role, group, clearance)
|
|
14
|
-
* without carrying a public key
|
|
14
|
+
* without carrying a public key -- the authorization counterpart to an identity
|
|
15
15
|
* certificate. `parse` decodes a v2 `AttributeCertificate` into its holder,
|
|
16
16
|
* issuer, validity window (real `Date`s), attributes, and extensions, reusing
|
|
17
17
|
* the shared signed-envelope so the raw `tbsBytes` (the exact signed region) and
|
|
18
18
|
* the `signatureValue` are surfaced for a downstream verifier.
|
|
19
19
|
*
|
|
20
20
|
* The holder and issuer identities are `GeneralName`s, validated on the way in
|
|
21
|
-
* (each alternative's form and content per RFC 5280
|
|
22
|
-
* their raw bytes
|
|
23
|
-
*
|
|
24
|
-
*
|
|
21
|
+
* (each alternative's form and content per RFC 5280 sec. 4.2.1.6) and surfaced with
|
|
22
|
+
* their raw bytes. The RFC 5755 sec. 4.2.3 issuer profile is enforced at parse: the
|
|
23
|
+
* issuer is `v2Form` (never `v1Form`) carrying exactly one non-empty
|
|
24
|
+
* `directoryName` in `issuerName`, with `baseCertificateID` / `objectDigestInfo`
|
|
25
|
+
* absent. The MUSTs above parse altitude (the holder-to-PKC binding, targeting
|
|
26
|
+
* and revocation) remain verification-layer concerns. The obsolete X.509-1997
|
|
25
27
|
* `AttributeCertificateV1` is recognized and deferred, not parsed. DER-only,
|
|
26
28
|
* fail-closed.
|
|
27
29
|
*
|
|
28
30
|
* @card
|
|
29
31
|
* Parse DER / PEM RFC 5755 attribute certificates into holder, issuer,
|
|
30
|
-
* validity, attributes, and extensions
|
|
32
|
+
* validity, attributes, and extensions -- validated GeneralNames, raw verifier
|
|
31
33
|
* inputs, legacy v1 recognize-and-defer, fail-closed.
|
|
32
34
|
*/
|
|
33
35
|
|
|
@@ -47,36 +49,31 @@ var TAGS = asn1.TAGS;
|
|
|
47
49
|
var ALGORITHM_IDENTIFIER = pkix.algorithmIdentifier(NS);
|
|
48
50
|
var EXTENSIONS = pkix.extensions(NS);
|
|
49
51
|
|
|
50
|
-
// AttCertVersion ::= INTEGER { v2(1) }
|
|
52
|
+
// AttCertVersion ::= INTEGER { v2(1) } -- a bare, mandatory INTEGER (not the x509
|
|
51
53
|
// [0] EXPLICIT DEFAULT shape). The only legal value is v2, surfaced as 2.
|
|
52
54
|
var VERSION = pkix.versionReader(NS, { "1": 2 });
|
|
53
55
|
|
|
54
56
|
// ---- shared leaves ---------------------------------------------------
|
|
55
57
|
|
|
56
|
-
// AttCertValidityPeriod times are GeneralizedTime (RFC 5755
|
|
58
|
+
// AttCertValidityPeriod times are GeneralizedTime (RFC 5755 sec. 4.2.6) -- a UTCTime is
|
|
57
59
|
// rejected. Non-fractional YYYYMMDDHHMMSSZ is enforced by the codec.
|
|
58
|
-
var GENERALIZED_TIME =
|
|
59
|
-
if (n.tagClass !== "universal" || n.tagNumber !== TAGS.GENERALIZED_TIME) {
|
|
60
|
-
throw ctx.E("attrcert/bad-time", "AttCertValidityPeriod times must be GeneralizedTime (RFC 5755 §4.2.6)");
|
|
61
|
-
}
|
|
62
|
-
return asn1.read.time(n);
|
|
63
|
-
});
|
|
60
|
+
var GENERALIZED_TIME = pkix.generalizedTime(NS, { code: "attrcert/bad-time", message: "AttCertValidityPeriod times must be GeneralizedTime (RFC 5755 sec. 4.2.6)" });
|
|
64
61
|
|
|
65
62
|
// digestedObjectType ::= ENUMERATED { publicKey(0), publicKeyCert(1),
|
|
66
|
-
// otherObjectTypes(2) } (RFC 5755
|
|
63
|
+
// otherObjectTypes(2) } (RFC 5755 sec. 4.1) -- an undefined value is rejected; a
|
|
67
64
|
// non-ENUMERATED tag fails at the codec (asn1/*).
|
|
68
65
|
var ODT_NAMES = { "0": "publicKey", "1": "publicKeyCert", "2": "otherObjectTypes" };
|
|
69
66
|
var DIGESTED_OBJECT_TYPE = schema.decode(function (n, ctx) {
|
|
70
67
|
var v = asn1.read.enumerated(n);
|
|
71
68
|
var k = v.toString();
|
|
72
69
|
if (!Object.prototype.hasOwnProperty.call(ODT_NAMES, k)) {
|
|
73
|
-
throw ctx.E("attrcert/bad-digested-object-type", "digestedObjectType " + k + " is not a defined value (RFC 5755
|
|
70
|
+
throw ctx.E("attrcert/bad-digested-object-type", "digestedObjectType " + k + " is not a defined value (RFC 5755 sec. 4.1)");
|
|
74
71
|
}
|
|
75
|
-
// RFC 5755
|
|
72
|
+
// RFC 5755 sec. 7.3 -- a conformant AC MUST NOT use otherObjectTypes(2); the digested
|
|
76
73
|
// object is limited to a public key or a public-key certificate, so reject it
|
|
77
74
|
// fail-closed rather than parse a digest whose object type is unidentifiable.
|
|
78
75
|
if (k === "2") {
|
|
79
|
-
throw ctx.E("attrcert/bad-digested-object-type", "otherObjectTypes(2) MUST NOT be used (RFC 5755
|
|
76
|
+
throw ctx.E("attrcert/bad-digested-object-type", "otherObjectTypes(2) MUST NOT be used (RFC 5755 sec. 7.3)");
|
|
80
77
|
}
|
|
81
78
|
return { code: Number(v), name: ODT_NAMES[k] };
|
|
82
79
|
});
|
|
@@ -84,7 +81,7 @@ var DIGESTED_OBJECT_TYPE = schema.decode(function (n, ctx) {
|
|
|
84
81
|
// ---- IssuerSerial / ObjectDigestInfo ---------------------------------
|
|
85
82
|
|
|
86
83
|
// IssuerSerial ::= SEQUENCE { issuer GeneralNames, serial CertificateSerialNumber,
|
|
87
|
-
// issuerUID UniqueIdentifier OPTIONAL } (RFC 5755
|
|
84
|
+
// issuerUID UniqueIdentifier OPTIONAL } (RFC 5755 sec. 4.1). Reached as an IMPLICIT
|
|
88
85
|
// [n] node (Holder [0] / V2Form [0]) whose tag replaces the SEQUENCE tag, so the
|
|
89
86
|
// shape assertion is "constructed" (the tag is pinned by the enclosing trailing);
|
|
90
87
|
// a context [n] PRIMITIVE node has no children and fails closed here.
|
|
@@ -106,7 +103,7 @@ var ISSUER_SERIAL = schema.seq([
|
|
|
106
103
|
|
|
107
104
|
// ObjectDigestInfo ::= SEQUENCE { digestedObjectType ENUMERATED, otherObjectTypeID
|
|
108
105
|
// OBJECT IDENTIFIER OPTIONAL, digestAlgorithm AlgorithmIdentifier, objectDigest BIT
|
|
109
|
-
// STRING } (RFC 5755
|
|
106
|
+
// STRING } (RFC 5755 sec. 4.1). otherObjectTypeID is present only for otherObjectTypes(2).
|
|
110
107
|
var OBJECT_DIGEST_INFO = schema.seq([
|
|
111
108
|
schema.field("digestedObjectType", DIGESTED_OBJECT_TYPE),
|
|
112
109
|
schema.optional("otherObjectTypeID", schema.oidLeaf(), { whenUniversal: [TAGS.OBJECT_IDENTIFIER] }),
|
|
@@ -117,10 +114,10 @@ var OBJECT_DIGEST_INFO = schema.seq([
|
|
|
117
114
|
build: function (m, ctx) {
|
|
118
115
|
var t = m.fields.digestedObjectType.value;
|
|
119
116
|
// otherObjectTypeID identifies a non-certificate object type, meaningful only with
|
|
120
|
-
// otherObjectTypes(2)
|
|
117
|
+
// otherObjectTypes(2) -- which RFC 5755 sec. 7.3 forbids (rejected at the leaf above) --
|
|
121
118
|
// so a present otherObjectTypeID is never valid here and is rejected fail-closed.
|
|
122
119
|
if (m.fields.otherObjectTypeID.present) {
|
|
123
|
-
throw ctx.E("attrcert/bad-object-digest-info", "otherObjectTypeID is only valid with otherObjectTypes(2), which RFC 5755
|
|
120
|
+
throw ctx.E("attrcert/bad-object-digest-info", "otherObjectTypeID is only valid with otherObjectTypes(2), which RFC 5755 sec. 7.3 forbids");
|
|
124
121
|
}
|
|
125
122
|
return {
|
|
126
123
|
digestedObjectType: t,
|
|
@@ -135,7 +132,7 @@ var OBJECT_DIGEST_INFO = schema.seq([
|
|
|
135
132
|
|
|
136
133
|
// Holder ::= SEQUENCE { baseCertificateID [0] IMPLICIT IssuerSerial OPTIONAL,
|
|
137
134
|
// entityName [1] IMPLICIT GeneralNames OPTIONAL, objectDigestInfo [2] IMPLICIT
|
|
138
|
-
// ObjectDigestInfo OPTIONAL } (RFC 5755
|
|
135
|
+
// ObjectDigestInfo OPTIONAL } (RFC 5755 sec. 4.1). All three are IMPLICIT + OPTIONAL;
|
|
139
136
|
// the profile RECOMMENDS exactly one, but the parser surfaces all three (null when
|
|
140
137
|
// absent) and does not enforce the cardinality.
|
|
141
138
|
var HOLDER = schema.seq([
|
|
@@ -157,8 +154,11 @@ var HOLDER = schema.seq([
|
|
|
157
154
|
|
|
158
155
|
// V2Form ::= SEQUENCE { issuerName GeneralNames OPTIONAL, baseCertificateID [0]
|
|
159
156
|
// IMPLICIT IssuerSerial OPTIONAL, objectDigestInfo [1] IMPLICIT ObjectDigestInfo
|
|
160
|
-
// OPTIONAL } (RFC 5755
|
|
161
|
-
// node, so the shape is "constructed".
|
|
157
|
+
// OPTIONAL } (RFC 5755 sec. 4.1). Reached via the AttCertIssuer CHOICE on a context [0]
|
|
158
|
+
// node, so the shape is "constructed". The build enforces the sec. 4.2.3 issuer
|
|
159
|
+
// profile MUSTs: every field is syntactically OPTIONAL, but a conformant AC's
|
|
160
|
+
// issuerName is present with one and only one GeneralName -- a non-empty
|
|
161
|
+
// directoryName -- and baseCertificateID / objectDigestInfo are absent.
|
|
162
162
|
var V2_FORM = schema.seq([
|
|
163
163
|
schema.optional("issuerName", pkix.generalNames(NS, { code: "attrcert/bad-issuer-name" }), { whenUniversal: [TAGS.SEQUENCE] }),
|
|
164
164
|
schema.trailing([
|
|
@@ -167,27 +167,47 @@ var V2_FORM = schema.seq([
|
|
|
167
167
|
], { minTag: 0, maxTag: 1, unexpectedCode: "attrcert/bad-v2form", orderCode: "attrcert/bad-v2form" }),
|
|
168
168
|
], {
|
|
169
169
|
assert: "constructed", code: "attrcert/bad-v2form", what: "V2Form",
|
|
170
|
-
build: function (m) {
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
170
|
+
build: function (m, ctx) {
|
|
171
|
+
if (!m.fields.issuerName.present) {
|
|
172
|
+
throw ctx.E("attrcert/bad-issuer-name", "v2Form must contain issuerName (RFC 5755 sec. 4.2.3)");
|
|
173
|
+
}
|
|
174
|
+
var issuerName = m.fields.issuerName.value.result;
|
|
175
|
+
if (issuerName.names.length !== 1) {
|
|
176
|
+
throw ctx.E("attrcert/bad-issuer-name", "issuerName must contain one and only one GeneralName (RFC 5755 sec. 4.2.3)");
|
|
177
|
+
}
|
|
178
|
+
if (issuerName.names[0].tagNumber !== 4) {
|
|
179
|
+
throw ctx.E("attrcert/bad-issuer-name", "the issuerName GeneralName must be a directoryName [4] (RFC 5755 sec. 4.2.3)");
|
|
180
|
+
}
|
|
181
|
+
// directoryName [4] EXPLICIT wraps exactly one Name (the shared GeneralName
|
|
182
|
+
// grammar validated that); a zero-RDN Name is an empty DN identifying no
|
|
183
|
+
// issuer, which sec. 4.2.3 forbids.
|
|
184
|
+
var dirName = asn1.decode(issuerName.names[0].bytes).children[0];
|
|
185
|
+
if (!dirName.children || dirName.children.length < 1) {
|
|
186
|
+
throw ctx.E("attrcert/bad-issuer-name", "the issuerName directoryName must be a non-empty distinguished name (RFC 5755 sec. 4.2.3)");
|
|
187
|
+
}
|
|
188
|
+
if (m.fields.baseCertificateID.present || m.fields.objectDigestInfo.present) {
|
|
189
|
+
throw ctx.E("attrcert/bad-v2form", "baseCertificateID and objectDigestInfo must not be present in v2Form (RFC 5755 sec. 4.2.3)");
|
|
190
|
+
}
|
|
191
|
+
return { issuerName: issuerName, baseCertificateID: null, objectDigestInfo: null };
|
|
176
192
|
},
|
|
177
193
|
});
|
|
178
194
|
|
|
179
195
|
// AttCertIssuer ::= CHOICE { v1Form GeneralNames, v2Form [0] IMPLICIT V2Form } (RFC
|
|
180
|
-
// 5755
|
|
181
|
-
// a
|
|
196
|
+
// 5755 sec. 4.1). A universal SEQUENCE is the (obsolete but structurally recognizable)
|
|
197
|
+
// v1Form, which a conformant AC MUST NOT use (sec. 4.2.3) -- the arm is recognized and
|
|
198
|
+
// rejected with a precise verdict rather than mis-dispatched; a context [0] is v2Form.
|
|
182
199
|
var ATT_CERT_ISSUER = schema.choice([
|
|
183
|
-
{ when: { tagClass: "universal", tagNumber: TAGS.SEQUENCE },
|
|
200
|
+
{ when: { tagClass: "universal", tagNumber: TAGS.SEQUENCE },
|
|
201
|
+
schema: schema.decode(function (n, ctx) {
|
|
202
|
+
throw ctx.E("attrcert/bad-issuer", "AttCertIssuer must use v2Form -- v1Form MUST NOT be used (RFC 5755 sec. 4.2.3)");
|
|
203
|
+
}) },
|
|
184
204
|
{ when: { tagClass: "context", tagNumber: 0 }, schema: V2_FORM },
|
|
185
205
|
], { code: "attrcert/bad-issuer" });
|
|
186
206
|
|
|
187
207
|
// ---- validity + attributes -------------------------------------------
|
|
188
208
|
|
|
189
209
|
// AttCertValidityPeriod ::= SEQUENCE { notBeforeTime GeneralizedTime, notAfterTime
|
|
190
|
-
// GeneralizedTime } (RFC 5755
|
|
210
|
+
// GeneralizedTime } (RFC 5755 sec. 4.2.6).
|
|
191
211
|
var VALIDITY = schema.seq([
|
|
192
212
|
schema.field("notBeforeTime", GENERALIZED_TIME),
|
|
193
213
|
schema.field("notAfterTime", GENERALIZED_TIME),
|
|
@@ -196,7 +216,7 @@ var VALIDITY = schema.seq([
|
|
|
196
216
|
build: function (m) { return { notBeforeTime: m.fields.notBeforeTime.value, notAfterTime: m.fields.notAfterTime.value }; },
|
|
197
217
|
});
|
|
198
218
|
|
|
199
|
-
// attributes ::= SEQUENCE OF Attribute (RFC 5755
|
|
219
|
+
// attributes ::= SEQUENCE OF Attribute (RFC 5755 sec. 4.2.7) -- MUST be non-empty and
|
|
200
220
|
// each AttributeType OID unique. Order-preserving (SEQUENCE OF, not SET OF).
|
|
201
221
|
var ATTRIBUTES = schema.seqOf(pkix.attribute(NS), {
|
|
202
222
|
assert: "sequence", min: 1, code: "attrcert/bad-attributes", what: "attributes",
|
|
@@ -208,7 +228,7 @@ var ATTRIBUTES = schema.seqOf(pkix.attribute(NS), {
|
|
|
208
228
|
|
|
209
229
|
// AttributeCertificateInfo ::= SEQUENCE { version, holder, issuer, signature
|
|
210
230
|
// AlgorithmIdentifier, serialNumber, attrCertValidityPeriod, attributes,
|
|
211
|
-
// issuerUniqueID OPTIONAL, extensions OPTIONAL } (RFC 5755
|
|
231
|
+
// issuerUniqueID OPTIONAL, extensions OPTIONAL } (RFC 5755 sec. 4.1). The tbs body the
|
|
212
232
|
// signed envelope wraps; the cross-field invariants live in the envelope build.
|
|
213
233
|
var ACINFO = schema.seq([
|
|
214
234
|
schema.field("version", VERSION),
|
|
@@ -223,39 +243,36 @@ var ACINFO = schema.seq([
|
|
|
223
243
|
], { assert: "sequence", code: "attrcert/bad-acinfo", what: "AttributeCertificateInfo" });
|
|
224
244
|
|
|
225
245
|
// AttributeCertificate ::= SEQUENCE { acinfo, signatureAlgorithm, signatureValue
|
|
226
|
-
// BIT STRING }
|
|
227
|
-
// invariants (
|
|
246
|
+
// BIT STRING } -- the shared SIGNED envelope (RFC 5755 sec. 4.1). The AC-specific
|
|
247
|
+
// invariants (sec. 4.2.4 sig-alg agreement, sec. 4.2.5 positive-and-<=20-octet serialNumber)
|
|
228
248
|
// run in the build; the envelope owns the SEQUENCE-of-3 shape + signature extraction.
|
|
229
249
|
var ATTRIBUTE_CERTIFICATE = pkix.signedEnvelope(NS, ACINFO, {
|
|
230
250
|
code: "attrcert/not-an-attribute-certificate", what: "AttributeCertificate",
|
|
231
251
|
build: function (e, ctx) {
|
|
232
252
|
var acinfo = e.tbsMatch;
|
|
233
253
|
|
|
234
|
-
// RFC 5755
|
|
254
|
+
// RFC 5755 sec. 4.2.4 -- the outer signatureAlgorithm MUST equal acinfo.signature.
|
|
235
255
|
if (!e.outerSignatureAlgorithmBytes.equals(acinfo.fields.signature.node.bytes)) {
|
|
236
|
-
throw ctx.E("attrcert/bad-signature-algorithm", "signatureAlgorithm must match AttributeCertificateInfo.signature (RFC 5755
|
|
256
|
+
throw ctx.E("attrcert/bad-signature-algorithm", "signatureAlgorithm must match AttributeCertificateInfo.signature (RFC 5755 sec. 4.2.4)");
|
|
237
257
|
}
|
|
238
258
|
|
|
239
|
-
// RFC 5755
|
|
259
|
+
// RFC 5755 sec. 4.2.5 -- serialNumber MUST be a positive INTEGER of at most 20 content
|
|
240
260
|
// octets. Positive excludes both a negative (DER sign bit set) and zero (a minimal
|
|
241
|
-
// INTEGER 0 is a single 0x00 octet
|
|
261
|
+
// INTEGER 0 is a single 0x00 octet -- the codec already rejected any non-minimal
|
|
242
262
|
// zero). Surface it lossless (BigInt) + as hex.
|
|
243
263
|
var serialNode = acinfo.fields.serialNumber.node;
|
|
244
264
|
var sc = serialNode.content;
|
|
245
265
|
if (sc.length === 0 || (sc[0] & 0x80) !== 0 || (sc.length === 1 && sc[0] === 0)) {
|
|
246
|
-
throw ctx.E("attrcert/bad-serial-number", "serialNumber must be a positive INTEGER (RFC 5755
|
|
266
|
+
throw ctx.E("attrcert/bad-serial-number", "serialNumber must be a positive INTEGER (RFC 5755 sec. 4.2.5)");
|
|
247
267
|
}
|
|
248
268
|
if (sc.length > 20) {
|
|
249
|
-
throw ctx.E("attrcert/bad-serial-number", "serialNumber must not exceed 20 content octets (RFC 5755
|
|
269
|
+
throw ctx.E("attrcert/bad-serial-number", "serialNumber must not exceed 20 content octets (RFC 5755 sec. 4.2.5)");
|
|
250
270
|
}
|
|
251
271
|
|
|
252
|
-
// AttCertIssuer is a CHOICE
|
|
253
|
-
//
|
|
254
|
-
|
|
255
|
-
var
|
|
256
|
-
var issuer = issuerNode.tagClass === "context"
|
|
257
|
-
? { form: "v2Form", v2Form: issuerVal.result, v1Form: null }
|
|
258
|
-
: { form: "v1Form", v1Form: issuerVal.result, v2Form: null };
|
|
272
|
+
// AttCertIssuer is a CHOICE whose v1Form arm rejects at the leaf (sec. 4.2.3), so
|
|
273
|
+
// the surviving arm is always v2Form. The { form, v2Form, v1Form } shape is
|
|
274
|
+
// kept stable for consumers keying off `form`.
|
|
275
|
+
var issuer = { form: "v2Form", v2Form: acinfo.fields.issuer.value.result, v1Form: null };
|
|
259
276
|
|
|
260
277
|
var extField = acinfo.fields.extensions;
|
|
261
278
|
return {
|
|
@@ -286,7 +303,8 @@ var ATTRIBUTE_CERTIFICATE = pkix.signedEnvelope(NS, ACINFO, {
|
|
|
286
303
|
* @spec RFC 5755, X.509
|
|
287
304
|
* @related pki.schema.parse, pki.schema.x509.parse
|
|
288
305
|
*
|
|
289
|
-
* Parse a DER `Buffer` or a PEM string/Buffer
|
|
306
|
+
* Parse a DER `Buffer` or a PEM string/Buffer (label `ATTRIBUTE CERTIFICATE`,
|
|
307
|
+
* the OpenSSL armor) into a structured v2 attribute
|
|
290
308
|
* certificate: `{ version, holder, issuer, signatureAlgorithm, serialNumber,
|
|
291
309
|
* serialNumberHex, validity, attributes, issuerUniqueID, extensions, tbsBytes,
|
|
292
310
|
* signatureValue }`. The holder / issuer identities come back as validated
|
|
@@ -302,23 +320,23 @@ var ATTRIBUTE_CERTIFICATE = pkix.signedEnvelope(NS, ACINFO, {
|
|
|
302
320
|
* ac.attributes[0].name; // "role"
|
|
303
321
|
* ac.validity.notAfterTime;// Date
|
|
304
322
|
*/
|
|
305
|
-
var PARSE_OPTS = { pemLabel:
|
|
323
|
+
var PARSE_OPTS = { pemLabel: "ATTRIBUTE CERTIFICATE", PemError: PemError, ErrorClass: AttrCertError, prefix: "attrcert", what: "attribute certificate" };
|
|
306
324
|
|
|
307
325
|
function _legacyV1Error() {
|
|
308
|
-
return new AttrCertError("attrcert/legacy-v1-not-supported", "AttributeCertificateV1 (X.509-1997) is obsolete and not parsed by this build (RFC 5755
|
|
326
|
+
return new AttrCertError("attrcert/legacy-v1-not-supported", "AttributeCertificateV1 (X.509-1997) is obsolete and not parsed by this build (RFC 5755 sec. 1)");
|
|
309
327
|
}
|
|
310
328
|
|
|
311
329
|
function parse(input) {
|
|
312
330
|
var root = pkix.decodeRoot(pkix.coerceToDer(input, PARSE_OPTS), PARSE_OPTS);
|
|
313
331
|
// A well-formed legacy AttributeCertificateV1 gets the advertised, stable
|
|
314
|
-
// attrcert/legacy-v1-not-supported on the DIRECT path too
|
|
315
|
-
// tag error from attempting the v2 walk
|
|
332
|
+
// attrcert/legacy-v1-not-supported on the DIRECT path too -- not a low-level asn1/*
|
|
333
|
+
// tag error from attempting the v2 walk -- so a direct caller of this entry sees the
|
|
316
334
|
// same error family for the recognized-but-deferred form as the orchestrator does.
|
|
317
335
|
if (matchesV1(root)) throw _legacyV1Error();
|
|
318
336
|
return schema.walk(ATTRIBUTE_CERTIFICATE, root, NS).result;
|
|
319
337
|
}
|
|
320
338
|
|
|
321
|
-
// The obsolete X.509-1997 AttributeCertificateV1 (RFC 5652
|
|
339
|
+
// The obsolete X.509-1997 AttributeCertificateV1 (RFC 5652 sec. 10.2) is recognized and
|
|
322
340
|
// deferred: decode the envelope (so a malformed input still fails as bad DER), then
|
|
323
341
|
// throw the precise diagnostic rather than routing it to a wrong format.
|
|
324
342
|
function parseV1(input) {
|
|
@@ -334,19 +352,37 @@ function parseV1(input) {
|
|
|
334
352
|
* @spec RFC 7468, RFC 5755
|
|
335
353
|
* @related pki.schema.attrcert.parse
|
|
336
354
|
*
|
|
337
|
-
* Extract the DER bytes from a PEM block (
|
|
338
|
-
*
|
|
339
|
-
*
|
|
355
|
+
* Extract the DER bytes from a PEM block (default label
|
|
356
|
+
* `ATTRIBUTE CERTIFICATE`, the OpenSSL armor -- the canonical-label default
|
|
357
|
+
* every sibling format applies). Pass a `label` to enforce a different block
|
|
358
|
+
* type, or an explicit `null` to take the first block of any type. Throws
|
|
359
|
+
* `PemError` on a missing / mismatched envelope or a non-base64 body.
|
|
340
360
|
*
|
|
341
361
|
* @example
|
|
342
362
|
* var der = pki.schema.attrcert.pemDecode(pemText);
|
|
343
363
|
*/
|
|
344
|
-
function pemDecode(text, label) { return pkix.pemDecode(text, label ||
|
|
364
|
+
function pemDecode(text, label) { return pkix.pemDecode(text, label === null ? null : (label || "ATTRIBUTE CERTIFICATE"), PemError); }
|
|
365
|
+
|
|
366
|
+
/**
|
|
367
|
+
* @primitive pki.schema.attrcert.pemEncode
|
|
368
|
+
* @signature pki.schema.attrcert.pemEncode(der, label?) -> string
|
|
369
|
+
* @since 0.1.23
|
|
370
|
+
* @status experimental
|
|
371
|
+
* @spec RFC 7468, RFC 5755
|
|
372
|
+
* @related pki.schema.attrcert.pemDecode
|
|
373
|
+
*
|
|
374
|
+
* Wrap DER bytes in a PEM envelope (default label `ATTRIBUTE CERTIFICATE`, the
|
|
375
|
+
* same default `pemDecode` reads). Throws `PemError` on a malformed label.
|
|
376
|
+
*
|
|
377
|
+
* @example
|
|
378
|
+
* var pem = pki.schema.attrcert.pemEncode(der);
|
|
379
|
+
*/
|
|
380
|
+
function pemEncode(der, label) { return pkix.pemEncode(der, label || "ATTRIBUTE CERTIFICATE", PemError); }
|
|
345
381
|
|
|
346
382
|
// matches(root): a v2 AttributeCertificate is the signed-envelope trio whose acinfo
|
|
347
383
|
// LEADS WITH a bare universal INTEGER version (== v2 = 1, NOT the x509 [0] EXPLICIT
|
|
348
384
|
// wrapper) and whose attrCertValidityPeriod at children[5] is a SEQUENCE of exactly
|
|
349
|
-
// two GeneralizedTime (RFC 5755
|
|
385
|
+
// two GeneralizedTime (RFC 5755 sec. 4.2.6 forbids UTCTime) -- a marker no cert / CRL /
|
|
350
386
|
// CSR presents at that position. Disjoint from matchesV1 by the children[0] tag class.
|
|
351
387
|
function matches(root) {
|
|
352
388
|
var acinfo = pkix.signedEnvelopeTbs(root);
|
|
@@ -360,7 +396,7 @@ function matches(root) {
|
|
|
360
396
|
return v.children.every(function (t) { return schema.isUniversal(t, TAGS.GENERALIZED_TIME); });
|
|
361
397
|
}
|
|
362
398
|
|
|
363
|
-
// matchesV1(root): the obsolete AttributeCertificateV1
|
|
399
|
+
// matchesV1(root): the obsolete AttributeCertificateV1 -- its acInfo (version DEFAULT
|
|
364
400
|
// v1 OMITTED) LEADS WITH the subject CHOICE (a context [0]/[1]), and its children[1]
|
|
365
401
|
// is the issuer GeneralNames (a universal SEQUENCE). The children[1] == SEQUENCE test
|
|
366
402
|
// keeps it disjoint from a v3 certificate (whose [0] EXPLICIT version is followed by
|
|
@@ -378,6 +414,7 @@ module.exports = {
|
|
|
378
414
|
parse: parse,
|
|
379
415
|
parseV1: parseV1,
|
|
380
416
|
pemDecode: pemDecode,
|
|
417
|
+
pemEncode: pemEncode,
|
|
381
418
|
matches: matches,
|
|
382
419
|
matchesV1: matchesV1,
|
|
383
420
|
};
|