@blamejs/pki 0.1.21 → 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 +47 -0
- package/README.md +17 -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 +64 -32
- package/lib/oid.js +114 -58
- package/lib/path-validate.js +302 -135
- package/lib/schema-all.js +54 -40
- 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 +306 -111
- package/lib/schema-smime.js +363 -0
- 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
|
|
|
@@ -43,6 +43,11 @@ var tsp = require("./schema-tsp");
|
|
|
43
43
|
var crmf = require("./schema-crmf");
|
|
44
44
|
var cmp = require("./schema-cmp");
|
|
45
45
|
var attrcert = require("./schema-attrcert");
|
|
46
|
+
// smime is a COMPANION decoder for CMS signed-attribute values, NOT a top-level
|
|
47
|
+
// format -- it has no self-describing DER root (an ESS attribute value is a bare
|
|
48
|
+
// SEQUENCE) so it is deliberately absent from FORMATS / the detect-and-route
|
|
49
|
+
// `parse`; it is reached only by explicit OID dispatch on a CMS attribute.
|
|
50
|
+
var smime = require("./schema-smime");
|
|
46
51
|
var frameworkError = require("./framework-error");
|
|
47
52
|
|
|
48
53
|
var SchemaError = frameworkError.SchemaError;
|
|
@@ -56,14 +61,14 @@ var PemError = frameworkError.PemError;
|
|
|
56
61
|
// fails typed inside that format's own parse.
|
|
57
62
|
var ENTRY = { pemLabel: null, PemError: PemError, ErrorClass: SchemaError, prefix: "schema", what: "input", ber: true };
|
|
58
63
|
|
|
59
|
-
// REGISTRY
|
|
64
|
+
// REGISTRY -- each format is declarative data: a name, a `detect(root)` that
|
|
60
65
|
// recognizes the decoded DER root as this format's outer structure, and the
|
|
61
66
|
// member's own `parse`. Adding a format is a table entry, not new dispatch
|
|
62
67
|
// logic. Order matters: the most specific detector wins, so a new member is
|
|
63
68
|
// inserted ahead of any more-permissive one.
|
|
64
69
|
var FORMATS = [
|
|
65
70
|
{
|
|
66
|
-
// CMS ContentInfo (RFC 5652
|
|
71
|
+
// CMS ContentInfo (RFC 5652 sec. 3) -- the only registered root that leads with an
|
|
67
72
|
// OBJECT IDENTIFIER child (a SEQUENCE of exactly 2: contentType OID + a
|
|
68
73
|
// context [0] EXPLICIT content wrapper). Disjoint from the INTEGER-first pkcs8
|
|
69
74
|
// and the tbs-SEQUENCE-first signed-envelope trio, so it detects unambiguously.
|
|
@@ -75,13 +80,13 @@ var FORMATS = [
|
|
|
75
80
|
parse: function (input) { return cms.parse(input); },
|
|
76
81
|
},
|
|
77
82
|
{
|
|
78
|
-
// TimeStampResp ::= SEQUENCE { status PKIStatusInfo, timeStampToken OPTIONAL }
|
|
83
|
+
// TimeStampResp ::= SEQUENCE { status PKIStatusInfo, timeStampToken OPTIONAL } --
|
|
79
84
|
// a SEQUENCE of 1-2 whose first child is a PKIStatusInfo SEQUENCE whose own first
|
|
80
85
|
// child is an INTEGER status. This detector is a strict REFINEMENT of the
|
|
81
86
|
// ocsp-request one (both are SEQUENCE-of-1-2 with a SEQUENCE first child), so it
|
|
82
87
|
// MUST be checked first: a tokenless TimeStampResp (a bare PKIStatusInfo) would
|
|
83
88
|
// otherwise be shadowed by ocsp-request, whereas an OCSPRequest's tbsRequest never
|
|
84
|
-
// 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
|
|
85
90
|
// bare timestamp token is a CMS ContentInfo and routes to cms; a bare TSTInfo is
|
|
86
91
|
// an internal payload (reached via pki.schema.tsp.parseTstInfo).
|
|
87
92
|
name: "tsp",
|
|
@@ -90,7 +95,7 @@ var FORMATS = [
|
|
|
90
95
|
parse: function (input) { return tsp.parse(input); },
|
|
91
96
|
},
|
|
92
97
|
{
|
|
93
|
-
// CertReqMessages ::= SEQUENCE SIZE(1..MAX) OF CertReqMsg (RFC 4211
|
|
98
|
+
// CertReqMessages ::= SEQUENCE SIZE(1..MAX) OF CertReqMsg (RFC 4211 sec. 3) -- a
|
|
94
99
|
// SEQUENCE whose first CertReqMsg's CertRequest leads with a universal INTEGER
|
|
95
100
|
// (certReqId) then a universal SEQUENCE (certTemplate). Leads with a SEQUENCE
|
|
96
101
|
// like the signed-envelope trio and the ocsp-request, but the INTEGER-then-
|
|
@@ -104,13 +109,13 @@ var FORMATS = [
|
|
|
104
109
|
},
|
|
105
110
|
{
|
|
106
111
|
// PKIMessage ::= SEQUENCE { header PKIHeader, body PKIBody [0..26],
|
|
107
|
-
// protection [0]?, extraCerts [1]? } (RFC 9810
|
|
112
|
+
// protection [0]?, extraCerts [1]? } (RFC 9810 sec. 5.1) -- a SEQUENCE of 2-4
|
|
108
113
|
// whose first child is a >=3-child SEQUENCE leading with a bare INTEGER
|
|
109
114
|
// (pvno) and whose second child is context-constructed [0..26]. ORDER IS
|
|
110
115
|
// LOAD-BEARING here: a 2-child PKIMessage whose body is ir [0] also
|
|
111
116
|
// satisfies the shallow ocsp-request probe below (k[0] SEQUENCE +
|
|
112
117
|
// k[1] context-[0]), while no OCSPRequest satisfies this detector (its
|
|
113
|
-
// tbsRequest never leads with a bare INTEGER)
|
|
118
|
+
// tbsRequest never leads with a bare INTEGER) -- so cmp MUST sit ahead of
|
|
114
119
|
// ocsp-request for the pair to dispatch deterministically.
|
|
115
120
|
name: "cmp",
|
|
116
121
|
module: cmp,
|
|
@@ -119,10 +124,10 @@ var FORMATS = [
|
|
|
119
124
|
},
|
|
120
125
|
{
|
|
121
126
|
// OCSPRequest ::= SEQUENCE { tbsRequest SEQUENCE, optionalSignature [0] EXPLICIT
|
|
122
|
-
// OPTIONAL }
|
|
127
|
+
// OPTIONAL } -- a SEQUENCE of 1-2 whose first child is the tbsRequest SEQUENCE.
|
|
123
128
|
// Leads with a SEQUENCE like the signed-envelope trio, but is excluded by arity
|
|
124
129
|
// (the trio is EXACTLY 3 children; an OCSPRequest is 1-2). Checked AFTER tsp,
|
|
125
|
-
// whose detector is a strict refinement (RFC 6960
|
|
130
|
+
// whose detector is a strict refinement (RFC 6960 sec. 4.1.1), and AFTER cmp,
|
|
126
131
|
// whose 2-child ir-body shape this probe would otherwise shadow.
|
|
127
132
|
name: "ocsp-request",
|
|
128
133
|
module: ocsp,
|
|
@@ -131,8 +136,8 @@ var FORMATS = [
|
|
|
131
136
|
},
|
|
132
137
|
{
|
|
133
138
|
// OCSPResponse ::= SEQUENCE { responseStatus ENUMERATED, responseBytes [0]
|
|
134
|
-
// EXPLICIT OPTIONAL }
|
|
135
|
-
// 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).
|
|
136
141
|
name: "ocsp-response",
|
|
137
142
|
module: ocsp,
|
|
138
143
|
detect: ocsp.matchesResponse,
|
|
@@ -140,12 +145,12 @@ var FORMATS = [
|
|
|
140
145
|
},
|
|
141
146
|
{
|
|
142
147
|
// PKCS#12 PFX ::= SEQUENCE { version INTEGER, authSafe ContentInfo, macData
|
|
143
|
-
// OPTIONAL }
|
|
144
|
-
// 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 --
|
|
145
150
|
// a shape a PrivateKeyInfo's AlgorithmIdentifier never presents) and
|
|
146
151
|
// children[2] is a MacData SEQUENCE or absent (pkcs8 requires an OCTET STRING
|
|
147
152
|
// there). Shape-disjoint from pkcs8, and registered ahead of it as the more
|
|
148
|
-
// specific probe (RFC 7292
|
|
153
|
+
// specific probe (RFC 7292 sec. 4). A BER-encoded PFX detects through the
|
|
149
154
|
// orchestrator's BER decode fallback and routes here.
|
|
150
155
|
name: "pkcs12",
|
|
151
156
|
module: pkcs12,
|
|
@@ -153,11 +158,11 @@ var FORMATS = [
|
|
|
153
158
|
parse: function (input) { return pkcs12.parse(input); },
|
|
154
159
|
},
|
|
155
160
|
{
|
|
156
|
-
// PKCS#8 PrivateKeyInfo / OneAsymmetricKey
|
|
161
|
+
// PKCS#8 PrivateKeyInfo / OneAsymmetricKey -- SEQUENCE whose first child is an
|
|
157
162
|
// INTEGER (version) and third an OCTET STRING (privateKey); disjoint from the
|
|
158
163
|
// signed-envelope trio. (EncryptedPrivateKeyInfo is deliberately NOT
|
|
159
|
-
// auto-routed: its SEQUENCE{SEQUENCE, OCTET STRING} shape is ambiguous
|
|
160
|
-
// 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
|
|
161
166
|
// without a validated encryption-algorithm discriminator, which arrives with
|
|
162
167
|
// the PBES layer. It is reached explicitly via pki.schema.pkcs8.parseEncrypted.)
|
|
163
168
|
name: "pkcs8",
|
|
@@ -167,7 +172,7 @@ var FORMATS = [
|
|
|
167
172
|
},
|
|
168
173
|
{
|
|
169
174
|
// CertificationRequest ::= SEQUENCE { certificationRequestInfo,
|
|
170
|
-
// signatureAlgorithm, signature }
|
|
175
|
+
// signatureAlgorithm, signature } -- the same outer 3-element shape,
|
|
171
176
|
// distinguished by a CertificationRequestInfo of EXACTLY four children
|
|
172
177
|
// ending in the IMPLICIT [0] attributes element. Checked first because that
|
|
173
178
|
// detector is the most specific and mutually exclusive with the others.
|
|
@@ -178,20 +183,28 @@ var FORMATS = [
|
|
|
178
183
|
},
|
|
179
184
|
{
|
|
180
185
|
// AttributeCertificate ::= SEQUENCE { acinfo, signatureAlgorithm, signatureValue }
|
|
181
|
-
//
|
|
182
|
-
//
|
|
183
|
-
//
|
|
184
|
-
//
|
|
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].
|
|
185
192
|
name: "attrcert",
|
|
186
193
|
module: attrcert,
|
|
187
194
|
detect: attrcert.matches,
|
|
188
195
|
parse: function (input) { return attrcert.parse(input); },
|
|
189
196
|
},
|
|
190
197
|
{
|
|
191
|
-
// AttributeCertificateV1 (X.509-1997, RFC 5652
|
|
192
|
-
// whose acInfo LEADS WITH the subject CHOICE (context [0]/[1]).
|
|
193
|
-
// deferred with a precise attrcert/legacy-v1-not-supported rather
|
|
194
|
-
// 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.
|
|
195
208
|
name: "attrcert-v1",
|
|
196
209
|
module: attrcert,
|
|
197
210
|
detect: attrcert.matchesV1,
|
|
@@ -199,7 +212,7 @@ var FORMATS = [
|
|
|
199
212
|
},
|
|
200
213
|
{
|
|
201
214
|
// CertificateList ::= SEQUENCE { tbsCertList, signatureAlgorithm,
|
|
202
|
-
// signatureValue }
|
|
215
|
+
// signatureValue } -- the same outer shape as a certificate, distinguished
|
|
203
216
|
// by its tbsCertList (a bare Time at the certificate's Validity position).
|
|
204
217
|
name: "crl",
|
|
205
218
|
module: crl,
|
|
@@ -209,7 +222,7 @@ var FORMATS = [
|
|
|
209
222
|
{
|
|
210
223
|
name: "x509",
|
|
211
224
|
module: x509,
|
|
212
|
-
// Certificate
|
|
225
|
+
// Certificate -- identified by a Validity (SEQUENCE of two Times) inside the
|
|
213
226
|
// tbs, so a CSR / other 3-element signed envelope is NOT misclassified as a
|
|
214
227
|
// certificate (it falls through to schema/unknown-format).
|
|
215
228
|
detect: x509.matches,
|
|
@@ -228,7 +241,7 @@ var FORMATS = [
|
|
|
228
241
|
* The names of every registered format, in detection order.
|
|
229
242
|
*
|
|
230
243
|
* @example
|
|
231
|
-
* pki.schema.all(); //
|
|
244
|
+
* pki.schema.all(); // -> ["cms", "tsp", "crmf", "cmp", "ocsp-request", "ocsp-response", "pkcs12", "pkcs8", "csr", "attrcert", "attrcert-v1", "crl", "x509"]
|
|
232
245
|
*/
|
|
233
246
|
function all() { return FORMATS.map(function (f) { return f.name; }); }
|
|
234
247
|
|
|
@@ -247,7 +260,7 @@ function all() { return FORMATS.map(function (f) { return f.name; }); }
|
|
|
247
260
|
* errors of the matched format propagate unchanged.
|
|
248
261
|
*
|
|
249
262
|
* @example
|
|
250
|
-
* var parsed = pki.schema.parse(der); // cert
|
|
263
|
+
* var parsed = pki.schema.parse(der); // cert -> the pki.schema.x509 shape
|
|
251
264
|
*/
|
|
252
265
|
function parse(input) {
|
|
253
266
|
// Coerce + decode via the shared parse-entry, then route the COERCED DER (a
|
|
@@ -267,16 +280,17 @@ function parse(input) {
|
|
|
267
280
|
module.exports = {
|
|
268
281
|
engine: engine,
|
|
269
282
|
x509: { parse: x509.parse, pemDecode: x509.pemDecode, pemEncode: x509.pemEncode },
|
|
270
|
-
crl: { parse: crl.parse, pemDecode: crl.pemDecode },
|
|
283
|
+
crl: { parse: crl.parse, pemDecode: crl.pemDecode, pemEncode: crl.pemEncode },
|
|
271
284
|
csr: { parse: csr.parse, pemDecode: csr.pemDecode, pemEncode: csr.pemEncode },
|
|
272
285
|
pkcs8: { parse: pkcs8.parse, parseEncrypted: pkcs8.parseEncrypted, pemDecode: pkcs8.pemDecode, pemEncode: pkcs8.pemEncode },
|
|
273
286
|
pkcs12: { parse: pkcs12.parse, pemDecode: pkcs12.pemDecode, pemEncode: pkcs12.pemEncode },
|
|
274
287
|
cms: { parse: cms.parse, pemDecode: cms.pemDecode, pemEncode: cms.pemEncode },
|
|
275
|
-
ocsp: { parseRequest: ocsp.parseRequest, parseResponse: ocsp.parseResponse, pemDecode: ocsp.pemDecode },
|
|
276
|
-
tsp: { parse: tsp.parse, parseTstInfo: tsp.parseTstInfo, parseToken: tsp.parseToken, pemDecode: tsp.pemDecode },
|
|
277
|
-
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 },
|
|
278
291
|
cmp: { parse: cmp.parse, pemDecode: cmp.pemDecode, pemEncode: cmp.pemEncode },
|
|
279
|
-
attrcert: { parse: attrcert.parse, pemDecode: attrcert.pemDecode },
|
|
292
|
+
attrcert: { parse: attrcert.parse, pemDecode: attrcert.pemDecode, pemEncode: attrcert.pemEncode },
|
|
293
|
+
smime: { parseSigningCertificate: smime.parseSigningCertificate, parseSigningCertificateV2: smime.parseSigningCertificateV2, parseSmimeCapabilities: smime.parseSmimeCapabilities, decodeAttribute: smime.decodeAttribute },
|
|
280
294
|
all: all,
|
|
281
295
|
parse: parse,
|
|
282
296
|
};
|
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
|
};
|