@blamejs/pki 0.6.13 → 0.6.15
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 +22 -1
- package/README.md +1 -1
- package/index.js +1 -1
- package/lib/acme.js +59 -815
- package/lib/asn1-der.js +7 -245
- package/lib/attrcert-sign.js +12 -260
- package/lib/attrcert-verify.js +1 -7
- package/lib/byte-reader.js +0 -8
- package/lib/byte-writer.js +0 -11
- package/lib/cbor-det.js +0 -54
- package/lib/cmc-build.js +15 -357
- package/lib/cmc-verify.js +6 -239
- package/lib/cmp-build.js +41 -317
- package/lib/cmp-session.js +73 -760
- package/lib/cmp-verify.js +32 -488
- package/lib/cms-compress.js +3 -32
- package/lib/cms-decrypt.js +29 -294
- package/lib/cms-encrypt.js +14 -234
- package/lib/cms-sign.js +29 -324
- package/lib/cms-verify.js +9 -500
- package/lib/composite-kem.js +2 -102
- package/lib/composite-sig.js +1 -77
- package/lib/constants.js +0 -249
- package/lib/crl-sign.js +9 -237
- package/lib/crl-verify.js +1 -10
- package/lib/crmf-sign.js +19 -198
- package/lib/crmf-verify.js +1 -12
- package/lib/csr-sign.js +3 -34
- package/lib/csr-verify.js +1 -12
- package/lib/ct.js +28 -253
- package/lib/edwards-point.js +10 -44
- package/lib/est.js +28 -764
- package/lib/framework-error.js +4 -182
- package/lib/guard-all.js +1 -58
- package/lib/guard-async.js +1 -36
- package/lib/guard-bytes.js +6 -650
- package/lib/guard-compress.js +5 -102
- package/lib/guard-crypto.js +1 -40
- package/lib/guard-der.js +2 -93
- package/lib/guard-encoding.js +2 -60
- package/lib/guard-header.js +1 -36
- package/lib/guard-identifier.js +7 -531
- package/lib/guard-intrinsic.js +1 -150
- package/lib/guard-json.js +10 -73
- package/lib/guard-limits.js +1 -77
- package/lib/guard-list.js +1 -49
- package/lib/guard-name.js +3 -209
- package/lib/guard-parsed.js +7 -335
- package/lib/guard-range.js +1 -71
- package/lib/guard-secret.js +1 -102
- package/lib/guard-text.js +1 -64
- package/lib/guard-time.js +1 -97
- package/lib/hpke.js +3 -105
- package/lib/http-digest.js +14 -142
- package/lib/http-retry-after.js +1 -47
- package/lib/http-transport.js +21 -131
- package/lib/inspect.js +30 -284
- package/lib/ip-utils.js +6 -47
- package/lib/jose.js +7 -142
- package/lib/key.js +2 -67
- package/lib/lint.js +9 -194
- package/lib/merkle.js +1 -27
- package/lib/mime.js +9 -82
- package/lib/ocsp-request-verify.js +1 -14
- package/lib/ocsp-verify.js +1 -82
- package/lib/ocsp.js +11 -169
- package/lib/oid.js +5 -337
- package/lib/path-validate.js +103 -1492
- package/lib/pbes2.js +2 -89
- package/lib/pkcs12-build.js +31 -379
- package/lib/pki-build.js +64 -290
- package/lib/rc2.js +2 -18
- package/lib/rfc3339.js +4 -27
- package/lib/scep.js +59 -233
- package/lib/schema-all.js +0 -122
- package/lib/schema-attrcert.js +4 -146
- package/lib/schema-c509.js +153 -802
- package/lib/schema-cmc.js +5 -220
- package/lib/schema-cmp.js +2 -223
- package/lib/schema-cms.js +13 -427
- package/lib/schema-crl.js +5 -68
- package/lib/schema-crmf.js +6 -167
- package/lib/schema-csr.js +1 -54
- package/lib/schema-csrattrs.js +2 -87
- package/lib/schema-engine.js +0 -152
- package/lib/schema-ocsp.js +0 -117
- package/lib/schema-pkcs12.js +2 -93
- package/lib/schema-pkcs8.js +3 -35
- package/lib/schema-pkix.js +24 -655
- package/lib/schema-smime.js +0 -54
- package/lib/schema-tsp.js +3 -99
- package/lib/schema-x509.js +4 -63
- package/lib/shbs.js +11 -95
- package/lib/sign-scheme.js +2 -97
- package/lib/sigstore.js +9 -169
- package/lib/sleep.js +2 -8
- package/lib/smime.js +15 -408
- package/lib/tls-cert-compress.js +0 -62
- package/lib/trust.js +19 -185
- package/lib/tsp-sign.js +16 -270
- package/lib/validator-all.js +1 -30
- package/lib/validator-attcert.js +2 -46
- package/lib/validator-cose.js +2 -153
- package/lib/validator-keydesc.js +1 -37
- package/lib/validator-sig.js +1 -46
- package/lib/validator-tls.js +1 -88
- package/lib/validator-tpm.js +19 -122
- package/lib/verify-seam.js +1 -24
- package/lib/webauthn-mds.js +3 -400
- package/lib/webauthn.js +12 -833
- package/lib/webcrypto.js +5 -485
- package/lib/x509-sign.js +4 -108
- package/package.json +1 -1
- package/sbom.cdx.json +6 -6
package/lib/attrcert-sign.js
CHANGED
|
@@ -16,16 +16,6 @@
|
|
|
16
16
|
* @spec RFC 5755
|
|
17
17
|
* @card Build and sign an RFC 5755 attribute certificate as an Attribute Authority.
|
|
18
18
|
*/
|
|
19
|
-
//
|
|
20
|
-
// RFC 5755 is DEFINITIONS IMPLICIT TAGS (App. B): a context [n] on a non-CHOICE component replaces the
|
|
21
|
-
// underlying tag (its children are the component's fields: Holder [0]/[1]/[2], AttCertIssuer v2Form [0],
|
|
22
|
-
// RoleSyntax roleAuthority [0], IetfAttrSyntax policyAuthority [0], AAControls [0]/[1]), built with
|
|
23
|
-
// b.contextConstructed(n, ...); a context [n] wrapping a GeneralName CHOICE (roleName [1], Target [0]/[1])
|
|
24
|
-
// is EXPLICIT, built with b.explicit(n, ...). The signature algorithm resolves from the AA public key
|
|
25
|
-
// through the shared sign-scheme registry (RSA / ECDSA / EdDSA / ML-DSA / SLH-DSA / composite). The name /
|
|
26
|
-
// GeneralName / extension / SPKI encoders + the post-sign self-check are the shared lib/pki-build
|
|
27
|
-
// primitives; the AC-specific structures are the byte-exact inverse of the schema-attrcert.js readers,
|
|
28
|
-
// which re-validate every emitted structure on the round trip.
|
|
29
19
|
|
|
30
20
|
var asn1 = require("./asn1-der");
|
|
31
21
|
var oid = require("./oid");
|
|
@@ -34,13 +24,10 @@ var x509 = require("./schema-x509");
|
|
|
34
24
|
var signScheme = require("./sign-scheme");
|
|
35
25
|
var guard = require("./guard-all");
|
|
36
26
|
var intrinsic = require("./guard-intrinsic");
|
|
37
|
-
// Membership is asked through an operation taken at load. Written out, the question reads two
|
|
38
|
-
// replaceable properties to answer one thing, and either answering true admits a name the table
|
|
39
|
-
// never carried -- here, an unrecognized critical extension counted as one this profile supports.
|
|
40
27
|
var _hasOwn = intrinsic.hasOwn;
|
|
41
28
|
var pkix = require("./schema-pkix");
|
|
42
29
|
var schema = require("./schema-engine");
|
|
43
|
-
require("./path-validate");
|
|
30
|
+
require("./path-validate");
|
|
44
31
|
var attrcertVerify = require("./attrcert-verify");
|
|
45
32
|
var pkiBuild = require("./pki-build");
|
|
46
33
|
var frameworkError = require("./framework-error");
|
|
@@ -58,20 +45,12 @@ var _b = pkiBuild.makeBuilder({
|
|
|
58
45
|
NAME_SCHEMA: pkix.name(NS), SPKI_SCHEMA: pkix.spki(NS), EXT_DECODERS: EXT_DECODERS,
|
|
59
46
|
});
|
|
60
47
|
|
|
61
|
-
// The recognized spec keys; an unknown key is a typo and throws at config-time (a custom attribute /
|
|
62
|
-
// extension is passed as pre-encoded DER via the array form).
|
|
63
48
|
var KNOWN_SPEC_KEYS = { holder: 1, notBeforeTime: 1, notAfterTime: 1, serialNumber: 1, attributes: 1, extensions: 1 };
|
|
64
49
|
var KNOWN_HOLDER_KEYS = { entityName: 1, baseCertificateID: 1, fromCertificate: 1, objectDigestInfo: 1 };
|
|
65
|
-
// object key -> the registered OID name of the AC attribute type it emits.
|
|
66
50
|
var ATTR_OID_NAME = {
|
|
67
51
|
role: "role", clearance: "clearance", group: "group", chargingIdentity: "chargingIdentity",
|
|
68
52
|
accessIdentity: "accessIdentity", authenticationInfo: "authenticationInfo",
|
|
69
53
|
};
|
|
70
|
-
// object key -> { oid name, critical } of the AC / AA extension it emits, with the RFC 5755 criticality:
|
|
71
|
-
// auditIdentity MUST be critical (sec. 4.3.1), targetInformation MUST be critical (sec. 4.3.2), proxying
|
|
72
|
-
// MUST be critical (sec. 7.2), noRevAvail MUST be non-critical (sec. 4.3.6), authorityKeyIdentifier MUST
|
|
73
|
-
// be non-critical (sec. 4.3.3); AAControls MAY be critical (sec. 7.4) and is emitted critical as the safer
|
|
74
|
-
// default for a delegation-constraint extension (a verifier that cannot process it MUST reject the AC).
|
|
75
54
|
var EXT_META = {
|
|
76
55
|
auditIdentity: { name: "acAuditIdentity", critical: true },
|
|
77
56
|
targetInformation: { name: "targetInformation", critical: true },
|
|
@@ -80,43 +59,29 @@ var EXT_META = {
|
|
|
80
59
|
acProxying: { name: "acProxying", critical: true },
|
|
81
60
|
authorityKeyIdentifier: { name: "authorityKeyIdentifier", critical: false },
|
|
82
61
|
};
|
|
83
|
-
// The RFC 5755-mandated criticality per extension OID (from EXT_META), enforced on the pre-encoded array
|
|
84
|
-
// hatch so an escape-hatch extension cannot ship with a criticality the profile forbids.
|
|
85
62
|
var REQUIRED_CRITICALITY = {};
|
|
86
63
|
Object.keys(EXT_META).forEach(function (k) { REQUIRED_CRITICALITY[O(EXT_META[k].name)] = EXT_META[k].critical; });
|
|
87
|
-
// ClassList ::= BIT STRING { unmarked(0) .. topSecret(5) } (RFC 5755 sec. 4.4.4), the encode inverse of
|
|
88
|
-
// the schema-attrcert.js _CLASSLIST_NAMES decode array; DEFAULT {unclassified} (bit 1) is omitted.
|
|
89
64
|
var CLASSLIST_BIT = { unmarked: 0, unclassified: 1, restricted: 2, confidential: 3, secret: 4, topSecret: 5 };
|
|
90
65
|
|
|
91
|
-
var _tbsNameBytes = pkiBuild.tbsNameField;
|
|
92
|
-
// Parse a certificate DER/PEM (or accept a parsed certificate), re-typing a raw x509/* parse fault to the
|
|
93
|
-
// attrcert domain so a malformed AA cert / holder cert surfaces attrcert/*, not a foreign CertificateError.
|
|
66
|
+
var _tbsNameBytes = pkiBuild.tbsNameField;
|
|
94
67
|
function _parseCert(cert, what) {
|
|
95
|
-
// Re-derived from the bytes its parser read. Issuer and serial together ARE the Holder's identity,
|
|
96
|
-
// so a certificate assembled from parts could bind an attribute certificate to a holder that no
|
|
97
|
-
// issuer ever named.
|
|
98
68
|
return guard.parsed.acceptDerived(cert, "certificate", function (bytes) {
|
|
99
69
|
try { return x509.parse(bytes); }
|
|
100
70
|
catch (e) { if (e instanceof AttrCertError) throw e; throw _err("attrcert/bad-input", what + " is not a well-formed certificate", e); }
|
|
101
71
|
}, _err, "attrcert/bad-input", what);
|
|
102
72
|
}
|
|
103
|
-
// The raw content octets of an OBJECT IDENTIFIER (past its own tag+len): the body of a [0] IMPLICIT OID.
|
|
104
73
|
function _oidContent(name) {
|
|
105
74
|
var dotted = O(name) || name;
|
|
106
75
|
var enc;
|
|
107
76
|
try { enc = b.oid(dotted); } catch (e) { throw _err("attrcert/bad-input", "not a valid object identifier: " + JSON.stringify(name), e); }
|
|
108
77
|
return asn1.decode(enc).content;
|
|
109
78
|
}
|
|
110
|
-
// A GeneralNames spec (a single GeneralName object or an array of them) -> the member list.
|
|
111
79
|
function _gnList(spec) {
|
|
112
80
|
if (Array.isArray(spec)) return spec;
|
|
113
81
|
return [spec];
|
|
114
82
|
}
|
|
115
83
|
|
|
116
|
-
// ---- structural encoders (byte-exact inverses of the schema-attrcert.js readers) ----
|
|
117
84
|
|
|
118
|
-
// IssuerSerial ::= SEQUENCE { issuer GeneralNames, serial CertificateSerialNumber, issuerUID BIT STRING
|
|
119
|
-
// OPTIONAL }. Returns the content (concatenated field TLVs) so a [n] IMPLICIT caller wraps it directly.
|
|
120
85
|
function _issuerSerialContent(is) {
|
|
121
86
|
if (!is || typeof is !== "object" || Buffer.isBuffer(is)) throw _err("attrcert/bad-input", "baseCertificateID must be an object { issuer, serial, issuerUID? }");
|
|
122
87
|
if (is.issuer == null) throw _err("attrcert/bad-input", "baseCertificateID.issuer (a GeneralNames) is required");
|
|
@@ -127,9 +92,6 @@ function _issuerSerialContent(is) {
|
|
|
127
92
|
}
|
|
128
93
|
return Buffer.concat(parts);
|
|
129
94
|
}
|
|
130
|
-
// ObjectDigestInfo ::= SEQUENCE { digestedObjectType ENUMERATED, otherObjectTypeID OID OPTIONAL,
|
|
131
|
-
// digestAlgorithm AlgorithmIdentifier, objectDigest BIT STRING }. otherObjectTypes(2) is forbidden (sec.
|
|
132
|
-
// 7.3), so otherObjectTypeID is never emitted. Returns the content for a [n] IMPLICIT caller.
|
|
133
95
|
var ODT_CODE = { publicKey: 0, publicKeyCert: 1 };
|
|
134
96
|
function _objectDigestInfoContent(odi) {
|
|
135
97
|
if (!odi || typeof odi !== "object" || Buffer.isBuffer(odi)) throw _err("attrcert/bad-input", "objectDigestInfo must be an object");
|
|
@@ -142,44 +104,31 @@ function _objectDigestInfoContent(odi) {
|
|
|
142
104
|
try { algTlv = b.sequence([b.oid(algOid)]); } catch (e) { throw _err("attrcert/bad-input", "objectDigestInfo.digestAlgorithm is not a valid algorithm identifier", e); }
|
|
143
105
|
return Buffer.concat([b.enumerated(BigInt(code)), algTlv, b.bitString(odi.objectDigest, 0)]);
|
|
144
106
|
}
|
|
145
|
-
// Holder ::= SEQUENCE { baseCertificateID [0] IMPLICIT IssuerSerial, entityName [1] IMPLICIT GeneralNames,
|
|
146
|
-
// objectDigestInfo [2] IMPLICIT ObjectDigestInfo }, exactly one form (the profile binds a real holder).
|
|
147
107
|
function _encodeHolder(holder) {
|
|
148
108
|
if (!holder || typeof holder !== "object" || Buffer.isBuffer(holder)) throw _err("attrcert/bad-input", "holder must be an object with exactly one form");
|
|
149
109
|
guard.identifier.assertKnownKeys(holder, KNOWN_HOLDER_KEYS, _err, "attrcert/bad-input", "unknown holder form ");
|
|
150
110
|
var forms = Object.keys(holder).filter(function (k) { return holder[k] != null; });
|
|
151
111
|
if (forms.length !== 1) throw _err("attrcert/bad-input", "holder must carry exactly one form (entityName, baseCertificateID, fromCertificate, or objectDigestInfo), got " + forms.length);
|
|
152
112
|
if (holder.entityName != null) {
|
|
153
|
-
// entityName [1] IMPLICIT GeneralNames -> the [1] node's children ARE the GeneralName members.
|
|
154
113
|
return b.sequence([_b.encodeGeneralNames(_gnList(holder.entityName), 1)]);
|
|
155
114
|
}
|
|
156
115
|
if (holder.baseCertificateID != null) {
|
|
157
116
|
return b.sequence([b.contextConstructed(0, _issuerSerialContent(holder.baseCertificateID))]);
|
|
158
117
|
}
|
|
159
118
|
if (holder.fromCertificate != null) {
|
|
160
|
-
// Bind to a public-key certificate's identity: baseCertificateID = { issuer = the PKC's issuer DN as a
|
|
161
|
-
// directoryName, serial = the PKC serialNumber } (RFC 5755 sec. 4.1 / 7.3).
|
|
162
|
-
// Both halves come from the signed bytes. issuer and serial together are the identity, so deriving
|
|
163
|
-
// the issuer from tbsBytes while reading the serial off the object let the two name different
|
|
164
|
-
// certificates: a Holder with a genuine issuer DN and whatever serial the caller wrote.
|
|
165
119
|
var pkc = _parseCert(holder.fromCertificate, "holder.fromCertificate");
|
|
166
120
|
var content = _issuerSerialContent({ issuer: [{ directoryName: _tbsNameBytes(pkc, "issuer") }], serial: pkiBuild.tbsSerialNumber(pkc) });
|
|
167
121
|
return b.sequence([b.contextConstructed(0, content)]);
|
|
168
122
|
}
|
|
169
|
-
// objectDigestInfo [2] IMPLICIT ObjectDigestInfo.
|
|
170
123
|
return b.sequence([b.contextConstructed(2, _objectDigestInfoContent(holder.objectDigestInfo))]);
|
|
171
124
|
}
|
|
172
125
|
|
|
173
|
-
// AttCertIssuer v2Form [0] IMPLICIT V2Form { issuerName GeneralNames }, where issuerName is exactly one
|
|
174
|
-
// directoryName [4] over a non-empty DN (RFC 5755 sec. 4.2.3). The [0] replaces the V2Form SEQUENCE tag.
|
|
175
126
|
function _encodeV2FormIssuer(dnSpec) {
|
|
176
127
|
var dnDer = _b.encodeName(dnSpec == null ? [] : dnSpec);
|
|
177
128
|
if (_b.isEmptyName(dnDer)) throw _err("attrcert/bad-issuer-name", "the AA issuerName must be a non-empty distinguished name (RFC 5755 sec. 4.2.3)");
|
|
178
|
-
var issuerNameGns = _b.encodeGeneralNames([{ directoryName: dnDer }]);
|
|
129
|
+
var issuerNameGns = _b.encodeGeneralNames([{ directoryName: dnDer }]);
|
|
179
130
|
return b.contextConstructed(0, issuerNameGns);
|
|
180
131
|
}
|
|
181
|
-
// AttCertValidityPeriod ::= SEQUENCE { notBeforeTime GeneralizedTime, notAfterTime GeneralizedTime }
|
|
182
|
-
// (RFC 5755 sec. 4.2.6): always GeneralizedTime, never UTCTime; reject an inverted window.
|
|
183
132
|
function _encodeValidity(notBefore, notAfter) {
|
|
184
133
|
guard.time.assertValid(notBefore, _err, "attrcert/bad-input", "notBeforeTime");
|
|
185
134
|
guard.time.assertValid(notAfter, _err, "attrcert/bad-input", "notAfterTime");
|
|
@@ -188,10 +137,7 @@ function _encodeValidity(notBefore, notAfter) {
|
|
|
188
137
|
return b.sequence([b.generalizedTime(notBefore), b.generalizedTime(notAfter)]);
|
|
189
138
|
}
|
|
190
139
|
|
|
191
|
-
// ---- attribute-value encoders (sec. 4.4) ----
|
|
192
140
|
|
|
193
|
-
// RoleSyntax ::= SEQUENCE { roleAuthority [0] IMPLICIT GeneralNames OPTIONAL, roleName [1] EXPLICIT
|
|
194
|
-
// GeneralName }. roleName is a GeneralName CHOICE -> EXPLICIT; roleAuthority a GeneralNames -> IMPLICIT [0].
|
|
195
141
|
function _encodeRole(role) {
|
|
196
142
|
if (!role || typeof role !== "object" || role.roleName == null) throw _err("attrcert/bad-input", "role must be an object with a roleName GeneralName");
|
|
197
143
|
var parts = [];
|
|
@@ -199,8 +145,6 @@ function _encodeRole(role) {
|
|
|
199
145
|
parts.push(b.explicit(1, _b.encodeGeneralName(role.roleName)));
|
|
200
146
|
return b.sequence(parts);
|
|
201
147
|
}
|
|
202
|
-
// Clearance ::= SEQUENCE { policyId OID, classList BIT STRING DEFAULT {unclassified}, securityCategories
|
|
203
|
-
// SET OF SecurityCategory OPTIONAL } (RFC 5755 sec. 4.4.4). A classList equal to the DEFAULT is omitted.
|
|
204
148
|
function _encodeClearance(cl) {
|
|
205
149
|
if (!cl || typeof cl !== "object" || cl.policyId == null) throw _err("attrcert/bad-input", "clearance must be an object with a policyId OID");
|
|
206
150
|
var parts;
|
|
@@ -212,7 +156,6 @@ function _encodeClearance(cl) {
|
|
|
212
156
|
if (pos == null) throw _err("attrcert/bad-input", "unknown clearance class " + JSON.stringify(n));
|
|
213
157
|
return pos;
|
|
214
158
|
});
|
|
215
|
-
// A present classList equal to the DEFAULT {unclassified} is non-canonical DER (the parser rejects it).
|
|
216
159
|
var isDefault = positions.length === 1 && positions[0] === CLASSLIST_BIT.unclassified;
|
|
217
160
|
if (!isDefault) parts.push(b.namedBitString(positions));
|
|
218
161
|
}
|
|
@@ -222,9 +165,6 @@ function _encodeClearance(cl) {
|
|
|
222
165
|
}
|
|
223
166
|
return b.sequence(parts);
|
|
224
167
|
}
|
|
225
|
-
// SecurityCategory ::= SEQUENCE { type [0] IMPLICIT OBJECT IDENTIFIER, value [1] EXPLICIT ANY }. The
|
|
226
|
-
// value is one element the parser EXPLICIT [1] wrapper carries verbatim, so it MUST be a single
|
|
227
|
-
// well-formed DER TLV -- validate it (no malformed / multi-element / trailing bytes) before signing.
|
|
228
168
|
function _encodeSecurityCategory(sc) {
|
|
229
169
|
if (!sc || typeof sc !== "object" || sc.type == null || !Buffer.isBuffer(sc.value)) throw _err("attrcert/bad-input", "each securityCategory must be { type: OID, value: DER Buffer }");
|
|
230
170
|
var node;
|
|
@@ -232,8 +172,6 @@ function _encodeSecurityCategory(sc) {
|
|
|
232
172
|
if (node.bytes.length !== sc.value.length) throw _err("attrcert/bad-input", "securityCategory value must be exactly one DER element (no trailing bytes)");
|
|
233
173
|
return b.sequence([b.contextPrimitive(0, _oidContent(sc.type)), b.explicit(1, sc.value)]);
|
|
234
174
|
}
|
|
235
|
-
// IetfAttrSyntax ::= SEQUENCE { policyAuthority [0] IMPLICIT GeneralNames OPTIONAL, values SEQUENCE OF
|
|
236
|
-
// CHOICE { octets OCTET STRING, oid OBJECT IDENTIFIER, string UTF8String } } (group / chargingIdentity).
|
|
237
175
|
function _encodeIetfAttrSyntax(ia) {
|
|
238
176
|
if (!ia || typeof ia !== "object" || !Array.isArray(ia.values) || !ia.values.length) throw _err("attrcert/bad-input", "an IetfAttrSyntax attribute must be an object with a non-empty values array");
|
|
239
177
|
var parts = [];
|
|
@@ -247,8 +185,6 @@ function _encodeIetfAttrSyntax(ia) {
|
|
|
247
185
|
})));
|
|
248
186
|
return b.sequence(parts);
|
|
249
187
|
}
|
|
250
|
-
// SvceAuthInfo ::= SEQUENCE { service GeneralName, ident GeneralName, authInfo OCTET STRING OPTIONAL }.
|
|
251
|
-
// authenticationInfo (sec. 4.4.1) permits authInfo; accessIdentity (sec. 4.4.2) MUST omit it.
|
|
252
188
|
function _encodeSvceAuthInfo(sai, authInfoAllowed, label) {
|
|
253
189
|
if (!sai || typeof sai !== "object" || sai.service == null || sai.ident == null) throw _err("attrcert/bad-input", label + " must be an object with a service and an ident GeneralName");
|
|
254
190
|
var parts = [_b.encodeGeneralName(sai.service), _b.encodeGeneralName(sai.ident)];
|
|
@@ -269,25 +205,17 @@ var ATTR_VALUE_ENCODER = {
|
|
|
269
205
|
accessIdentity: function (v) { return _encodeSvceAuthInfo(v, false, "accessIdentity"); },
|
|
270
206
|
};
|
|
271
207
|
|
|
272
|
-
// ---- extension-value encoders (sec. 4.3 / 7.4) ----
|
|
273
208
|
|
|
274
|
-
// Target ::= CHOICE { targetName [0] EXPLICIT GeneralName, targetGroup [1] EXPLICIT GeneralName }
|
|
275
|
-
// (targetCert [2] is out of v1 scope; use a pre-encoded Extension DER for a targetCert-bearing value).
|
|
276
209
|
function _encodeTarget(t) {
|
|
277
210
|
if (!t || typeof t !== "object") throw _err("attrcert/bad-input", "each target must be { targetName } or { targetGroup }");
|
|
278
211
|
if (t.targetName != null) return b.explicit(0, _b.encodeGeneralName(t.targetName));
|
|
279
212
|
if (t.targetGroup != null) return b.explicit(1, _b.encodeGeneralName(t.targetGroup));
|
|
280
213
|
throw _err("attrcert/bad-input", "each target must be { targetName } or { targetGroup }");
|
|
281
214
|
}
|
|
282
|
-
// targetInformation / acProxying value ::= SEQUENCE OF Targets, Targets ::= SEQUENCE OF Target. A
|
|
283
|
-
// conformant issuer emits exactly one Targets.
|
|
284
215
|
function _encodeSeqOfTargets(targets) {
|
|
285
216
|
if (!Array.isArray(targets) || !targets.length) throw _err("attrcert/bad-input", "a targets value must be a non-empty array of targets");
|
|
286
217
|
return b.sequence([b.sequence(targets.map(_encodeTarget))]);
|
|
287
218
|
}
|
|
288
|
-
// AAControls ::= SEQUENCE { pathLenConstraint INTEGER OPTIONAL, permittedAttrs [0] IMPLICIT AttrSpec
|
|
289
|
-
// OPTIONAL, excludedAttrs [1] IMPLICIT AttrSpec OPTIONAL, permitUnSpecified BOOLEAN DEFAULT TRUE }.
|
|
290
|
-
// AttrSpec ::= SEQUENCE OF OID (the [0]/[1] IMPLICIT tag replaces the SEQUENCE tag).
|
|
291
219
|
function _attrSpec(names, tag, label) {
|
|
292
220
|
if (!Array.isArray(names)) throw _err("attrcert/bad-input", "aaControls." + label + " must be an array of attribute-type OIDs");
|
|
293
221
|
var oids = names.map(function (n) {
|
|
@@ -299,19 +227,15 @@ function _encodeAAControls(aac) {
|
|
|
299
227
|
if (!aac || typeof aac !== "object" || Buffer.isBuffer(aac)) throw _err("attrcert/bad-input", "aaControls must be an object");
|
|
300
228
|
var parts = [];
|
|
301
229
|
if (aac.pathLenConstraint != null) {
|
|
302
|
-
// The parser narrows pathLenConstraint through guard.range.uint31 (0..2^31-1), so a larger value the
|
|
303
|
-
// builder emits would fail the round trip, so bound it to the same range at build time.
|
|
304
230
|
if (typeof aac.pathLenConstraint !== "number" || !Number.isInteger(aac.pathLenConstraint) || aac.pathLenConstraint < 0 || aac.pathLenConstraint > 0x7fffffff) throw _err("attrcert/bad-input", "aaControls.pathLenConstraint must be an integer in 0..2147483647 (RFC 5755 sec. 7.4)");
|
|
305
231
|
parts.push(b.integer(BigInt(aac.pathLenConstraint)));
|
|
306
232
|
}
|
|
307
233
|
if (aac.permittedAttrs != null) parts.push(_attrSpec(aac.permittedAttrs, 0, "permittedAttrs"));
|
|
308
234
|
if (aac.excludedAttrs != null) parts.push(_attrSpec(aac.excludedAttrs, 1, "excludedAttrs"));
|
|
309
|
-
// permitUnSpecified DEFAULT TRUE -- a TRUE value equals the DEFAULT and is omitted (non-canonical DER).
|
|
310
235
|
if (aac.permitUnSpecified === false) parts.push(b.boolean(false));
|
|
311
236
|
else if (aac.permitUnSpecified != null && aac.permitUnSpecified !== true) throw _err("attrcert/bad-input", "aaControls.permitUnSpecified must be a boolean");
|
|
312
237
|
return b.sequence(parts);
|
|
313
238
|
}
|
|
314
|
-
// Emit a single AC / AA extension's value DER from its object-form spec.
|
|
315
239
|
function _extensionValue(key, val, aaSpki) {
|
|
316
240
|
switch (key) {
|
|
317
241
|
case "auditIdentity":
|
|
@@ -326,11 +250,7 @@ function _extensionValue(key, val, aaSpki) {
|
|
|
326
250
|
}
|
|
327
251
|
}
|
|
328
252
|
|
|
329
|
-
// ---- attributes / extensions assembly ----
|
|
330
253
|
|
|
331
|
-
// attributes ::= SEQUENCE OF Attribute (RFC 5755 sec. 4.2.7): non-empty, each AttributeType OID unique;
|
|
332
|
-
// or an array of pre-encoded Attribute DER (validated in shape AND value). Each Attribute value SET is
|
|
333
|
-
// DER-sorted.
|
|
334
254
|
function _buildAttributes(attrSpec) {
|
|
335
255
|
var attrs = [], seen = {};
|
|
336
256
|
function add(type, valueTlv) {
|
|
@@ -349,8 +269,6 @@ function _buildAttributes(attrSpec) {
|
|
|
349
269
|
if (!n.children[1].children || !n.children[1].children.length) throw _err("attrcert/bad-input", "pre-encoded attribute [" + i + "] value SET must contain at least one value");
|
|
350
270
|
if (seen[at]) throw _err("attrcert/duplicate-attribute", "duplicate " + (oid.name(at) || at) + " attribute (RFC 5755 sec. 4.2.7)");
|
|
351
271
|
seen[at] = true;
|
|
352
|
-
// A recognized AC attribute type is validated against its real sec. 4.4 value decoder; an
|
|
353
|
-
// unrecognized type stays opaque (the parser also leaves it opaque).
|
|
354
272
|
n.children[1].children.forEach(function (valNode) {
|
|
355
273
|
try { attrcert.validateAttributeValue(at, valNode.bytes); }
|
|
356
274
|
catch (err) { if (err instanceof AttrCertError) throw err; throw _err("attrcert/bad-input", "pre-encoded " + (oid.name(at) || at) + " attribute value is malformed", err); }
|
|
@@ -371,12 +289,6 @@ function _buildAttributes(attrSpec) {
|
|
|
371
289
|
return b.sequence(attrs);
|
|
372
290
|
}
|
|
373
291
|
|
|
374
|
-
// extensions ::= SEQUENCE OF Extension (RFC 5755 sec. 4.2.9): the recognized object form, or an array
|
|
375
|
-
// of pre-encoded Extension DER. At most one instance of a particular extension OID. Returns a universal
|
|
376
|
-
// RFC 5755 sec. 6 defines two revocation schemes and closes by making them exclusive: "An AC MUST
|
|
377
|
-
// NOT contain both a noRevAvail extension and a 'pointer in AC'." Held over the OIDs an AC ends up
|
|
378
|
-
// carrying, so BOTH extension forms meet it -- the object form and the pre-encoded array form, which
|
|
379
|
-
// is the one that can name a pointer at all. `seen` is the OID-keyed set each branch already builds.
|
|
380
292
|
function _assertRevocationSchemesExclusive(seen) {
|
|
381
293
|
if (!_hasOwn(seen, O("noRevAvail"))) return;
|
|
382
294
|
var names = Object.keys(REVOCATION_POINTERS);
|
|
@@ -388,7 +300,6 @@ function _assertRevocationSchemesExclusive(seen) {
|
|
|
388
300
|
}
|
|
389
301
|
}
|
|
390
302
|
|
|
391
|
-
// SEQUENCE OF Extension, or null when none are requested.
|
|
392
303
|
function _buildExtensions(extSpec, aaSpki) {
|
|
393
304
|
if (extSpec == null) return null;
|
|
394
305
|
if (Array.isArray(extSpec)) {
|
|
@@ -401,14 +312,10 @@ function _buildExtensions(extSpec, aaSpki) {
|
|
|
401
312
|
var extnId = asn1.read.oid(n.children[0]);
|
|
402
313
|
if (seenA[extnId]) throw _err("attrcert/bad-input", "duplicate extension " + extnId + " (RFC 5755 sec. 4.2.9)");
|
|
403
314
|
seenA[extnId] = true;
|
|
404
|
-
// A recognized RFC 5755-profiled extension MUST carry its mandated criticality even via the escape
|
|
405
|
-
// hatch (assertValidExtension already rejected an explicit critical=FALSE, so 3 children == critical).
|
|
406
315
|
var required = REQUIRED_CRITICALITY[extnId];
|
|
407
316
|
if (required != null && (n.children.length === 3) !== required) {
|
|
408
317
|
throw _err("attrcert/bad-input", "pre-encoded " + (oid.name(extnId) || extnId) + " extension must be marked " + (required ? "critical" : "non-critical") + " (RFC 5755)");
|
|
409
318
|
}
|
|
410
|
-
// Validate a recognized value against its real decoder: a cert-style AA extension (AKI / CRLDP /
|
|
411
|
-
// AIA) through the RFC 5280 sec. 4.2.1 table, an AC-specific extension through the sec. 4.3 table.
|
|
412
319
|
var extnValue = asn1.read.octetString(n.children[n.children.length - 1]);
|
|
413
320
|
try {
|
|
414
321
|
if (EXT_DECODERS[extnId]) EXT_DECODERS[extnId](extnValue);
|
|
@@ -425,7 +332,7 @@ function _buildExtensions(extSpec, aaSpki) {
|
|
|
425
332
|
});
|
|
426
333
|
var out = [], seen = {};
|
|
427
334
|
Object.keys(extSpec).forEach(function (k) {
|
|
428
|
-
if (extSpec[k] == null) return;
|
|
335
|
+
if (extSpec[k] == null) return;
|
|
429
336
|
var meta = EXT_META[k], eOid = O(meta.name);
|
|
430
337
|
if (seen[eOid]) throw _err("attrcert/bad-input", "duplicate extension " + JSON.stringify(k) + " (RFC 5755 sec. 4.2.9)");
|
|
431
338
|
seen[eOid] = true;
|
|
@@ -446,18 +353,18 @@ function _buildExtensions(extSpec, aaSpki) {
|
|
|
446
353
|
* @related pki.schema.attrcert.parse, pki.x509.sign
|
|
447
354
|
*
|
|
448
355
|
* Build, sign, and DER-encode an RFC 5755 attribute certificate as an Attribute Authority. `spec`
|
|
449
|
-
* describes the certificate
|
|
356
|
+
* describes the certificate: `holder` (exactly one form: `entityName`, `baseCertificateID`,
|
|
450
357
|
* `fromCertificate` to bind a public-key certificate's identity, or `objectDigestInfo`), `notBeforeTime`
|
|
451
358
|
* / `notAfterTime` (`Date`s -> GeneralizedTime), an optional `serialNumber` (positive, <= 20 octets;
|
|
452
359
|
* a random 20-octet serial is generated when omitted), `attributes` (an object of the sec. 4.4 privilege
|
|
453
|
-
* syntaxes
|
|
360
|
+
* syntaxes (role / clearance / group / chargingIdentity / accessIdentity / authenticationInfo), or an
|
|
454
361
|
* array of pre-encoded Attribute DER), and optional `extensions` (an object of auditIdentity /
|
|
455
362
|
* targetInformation / noRevAvail / aaControls / acProxying / authorityKeyIdentifier, or an array of
|
|
456
363
|
* pre-encoded Extension DER). `issuer` is the signing AA: `{ cert, key }` (the AA certificate DER/PEM
|
|
457
364
|
* and its private key) or `{ name, publicKey, key }` (an explicit issuer DN, AA SPKI DER, and key); an
|
|
458
365
|
* attribute certificate is never self-signed. RFC 5755 section 6 defines two revocation schemes and
|
|
459
|
-
* makes them exclusive
|
|
460
|
-
*
|
|
366
|
+
* makes them exclusive ("An AC MUST NOT contain both a noRevAvail extension and a 'pointer in AC'"),
|
|
367
|
+
* so `noRevAvail` beside a `crlDistributionPoints` or `authorityInfoAccess` extension is refused,
|
|
461
368
|
* in the named form and the pre-encoded form alike. The signature algorithm is resolved from the AA key (RSA
|
|
462
369
|
* PKCS#1 v1.5 or PSS, ECDSA, EdDSA, ML-DSA, SLH-DSA, or a composite arm), and the signature is verified
|
|
463
370
|
* under the AA public key before the certificate is returned. Returns DER, or a PEM `ATTRIBUTE
|
|
@@ -485,8 +392,6 @@ function _buildExtensions(extSpec, aaSpki) {
|
|
|
485
392
|
* pki.schema.attrcert.parse(ac).attributes[0].type; // the role attribute OID
|
|
486
393
|
*/
|
|
487
394
|
function sign(spec, issuer, opts) {
|
|
488
|
-
// Every caller-owned argument copied at entry and released when the call settles -- see the note
|
|
489
|
-
// on the same call in x509-sign.
|
|
490
395
|
return guard.bytes.fixedCall(AttrCertError, "attrcert/bad-input", [
|
|
491
396
|
[spec, "the attribute-certificate spec"], [issuer, "the issuer"], [opts, "pki.attrcert.sign options"],
|
|
492
397
|
], _sign);
|
|
@@ -499,9 +404,6 @@ function _sign(spec, issuer, opts) {
|
|
|
499
404
|
issuer = issuer || {};
|
|
500
405
|
if (issuer.key == null) throw _err("attrcert/bad-input", "a signing key (issuer.key, the AA's WebCrypto CryptoKey or PKCS#8 private key DER/PEM) is required");
|
|
501
406
|
|
|
502
|
-
// Resolve the AA issuer DN + signing-key SPKI. An attribute certificate is NEVER self-signed. No CA
|
|
503
|
-
// gate on issuer.cert -- an AA is NOT a certificate authority (its PKC MUST NOT assert keyCertSign,
|
|
504
|
-
// RFC 5755 sec. 4.5), so requiring cA/keyCertSign would reject every conformant AA certificate.
|
|
505
407
|
var issuerDnSpec, aaSpki;
|
|
506
408
|
if (issuer.cert != null) {
|
|
507
409
|
var aaCert = _parseCert(issuer.cert, "issuer.cert");
|
|
@@ -521,21 +423,16 @@ function _sign(spec, issuer, opts) {
|
|
|
521
423
|
var attributesDer = _buildAttributes(spec.attributes);
|
|
522
424
|
var extsDer = _buildExtensions(spec.extensions, aaSpki);
|
|
523
425
|
|
|
524
|
-
// Resolve the signature scheme from the AA (signing) key's SPKI -- the whole registry, for free.
|
|
525
426
|
var scheme = signScheme.resolveSignScheme(_b.certLikeFromSpki(aaSpki), { combinedRsaSig: true, pss: opts.pss, digestAlgorithm: opts.digestAlgorithm }, true, _signE);
|
|
526
427
|
|
|
527
|
-
// AttributeCertificateInfo ::= SEQUENCE { version(bare INTEGER 1), holder, issuer, signature
|
|
528
|
-
// AlgorithmIdentifier, serialNumber, attrCertValidityPeriod, attributes, [issuerUniqueID], [extensions] }.
|
|
529
|
-
// signature == the outer signatureAlgorithm (RFC 5755 sec. 4.2.4), single source scheme.sigAlgId.
|
|
530
428
|
var acinfoChildren = [
|
|
531
429
|
b.integer(1n), holderDer, issuerDer, scheme.sigAlgId,
|
|
532
430
|
_b.serialInteger(spec.serialNumber), validityDer, attributesDer,
|
|
533
431
|
];
|
|
534
|
-
if (extsDer) acinfoChildren.push(extsDer);
|
|
432
|
+
if (extsDer) acinfoChildren.push(extsDer);
|
|
535
433
|
var acinfoDer = b.sequence(acinfoChildren);
|
|
536
434
|
|
|
537
435
|
return signScheme.signOverTbs(scheme, issuer.key, acinfoDer, _signE).then(function (sig) {
|
|
538
|
-
// The signature MUST verify under the AA public key (the AA proof); composite verifies both arms.
|
|
539
436
|
return Promise.resolve(_b.assertSignatureVerifies(acinfoDer, sig, aaSpki, scheme)).then(function () {
|
|
540
437
|
var acDer = b.sequence([acinfoDer, scheme.sigAlgId, b.bitString(sig, 0)]);
|
|
541
438
|
return opts.pem ? attrcert.pemEncode(acDer, "ATTRIBUTE CERTIFICATE") : acDer;
|
|
@@ -547,15 +444,11 @@ function _sign(spec, issuer, opts) {
|
|
|
547
444
|
}
|
|
548
445
|
|
|
549
446
|
var KNOWN_VERIFY_OPTS = { time: 1, target: 1, revocationStatus: 1 };
|
|
550
|
-
// The two answers a caller can give for RFC 5755 sec. 6, spelled out so a typo is refused instead of
|
|
551
|
-
// read as the safe one. Built without a prototype, so an inherited name cannot answer the lookup.
|
|
552
447
|
var REVOCATION_STATUS = Object.create(null);
|
|
553
448
|
REVOCATION_STATUS.notRevoked = 1;
|
|
554
449
|
REVOCATION_STATUS.revoked = 1;
|
|
555
450
|
var _NAME_SCHEMA = pkix.name(NS);
|
|
556
451
|
|
|
557
|
-
// A Name DER, read through the one Name reader, so every distinguished name this verb compares is
|
|
558
|
-
// in the shape guard.name.dnEqual takes.
|
|
559
452
|
function _readName(der, what) {
|
|
560
453
|
try { return schema.walk(_NAME_SCHEMA, asn1.decode(der), NS).result; }
|
|
561
454
|
catch (e) {
|
|
@@ -564,9 +457,6 @@ function _readName(der, what) {
|
|
|
564
457
|
}
|
|
565
458
|
}
|
|
566
459
|
|
|
567
|
-
// The AC issuer's distinguished name. The parser has already established that issuerName holds
|
|
568
|
-
// exactly one non-empty directoryName [4] (RFC 5755 sec. 4.2.3) and surfaces it as a raw node, so
|
|
569
|
-
// the DN itself is one EXPLICIT unwrap away.
|
|
570
460
|
function _acIssuerName(parsed) {
|
|
571
461
|
var node = asn1.decode(parsed.issuer.v2Form.issuerName.names[0].bytes);
|
|
572
462
|
if (!node.children || node.children.length !== 1) {
|
|
@@ -580,9 +470,6 @@ function _coerceAc(input) {
|
|
|
580
470
|
"attrcert/bad-input", "the attribute certificate");
|
|
581
471
|
}
|
|
582
472
|
|
|
583
|
-
// The AC issuer this caller directly trusts as an AC issuer (RFC 5755 sec. 5, item 4). That is a
|
|
584
|
-
// configuration decision the certificate cannot make for itself, so it is an argument, and an
|
|
585
|
-
// argument this verb refuses to infer.
|
|
586
473
|
function _resolveIssuer(issuer) {
|
|
587
474
|
if (issuer == null || typeof issuer !== "object" || Buffer.isBuffer(issuer)) {
|
|
588
475
|
throw _err("attrcert/bad-input", "an issuer { name, publicKey } is required -- RFC 5755 sec. 5 item 4 makes trusting an AC issuer the verifier's own configuration");
|
|
@@ -593,14 +480,6 @@ function _resolveIssuer(issuer) {
|
|
|
593
480
|
if (issuer.name == null) {
|
|
594
481
|
throw _err("attrcert/bad-input", "issuer.name (the distinguished name this verifier trusts as an AC issuer) is required");
|
|
595
482
|
}
|
|
596
|
-
// Every name form pki.attrcert.sign accepts for the issuing AA is a name this verb has to be able
|
|
597
|
-
// to trust: a string, an array of RDNs, or raw Name DER. The forms come from the one Name encoder
|
|
598
|
-
// both sides use, so an AC issued under a multi-RDN AA name is verifiable under that same name.
|
|
599
|
-
//
|
|
600
|
-
// The trusted name is compared as a distinguished name, never as a string: RFC 5280 sec. 7.1
|
|
601
|
-
// identity is not string equality, and guard.name owns that comparison. Encoding the caller's
|
|
602
|
-
// name and reading it back through the SAME Name reader the parser used puts both sides in the
|
|
603
|
-
// one shape dnEqual takes.
|
|
604
483
|
var trustedDer;
|
|
605
484
|
try { trustedDer = _b.encodeName(issuer.name); }
|
|
606
485
|
catch (e) {
|
|
@@ -608,44 +487,19 @@ function _resolveIssuer(issuer) {
|
|
|
608
487
|
throw _err("attrcert/bad-input", "issuer.name is not a distinguished name", e);
|
|
609
488
|
}
|
|
610
489
|
var rdns = _readName(trustedDer, "issuer.name").rdns;
|
|
611
|
-
// Checked on the decoded name, so every input form is held to it. An empty DN names no issuer, and
|
|
612
|
-
// RFC 5755 sec. 4.2.3 requires the AC's own issuerName to be a non-empty distinguished name, so an
|
|
613
|
-
// empty trusted name could never match a conformant AC and would only mask the caller's mistake.
|
|
614
490
|
if (!rdns.length) {
|
|
615
491
|
throw _err("attrcert/bad-input", "issuer.name is an empty distinguished name, which names no AC issuer (RFC 5755 sec. 4.2.3)");
|
|
616
492
|
}
|
|
617
493
|
return {
|
|
618
494
|
rdns: rdns,
|
|
619
|
-
// Copied, never viewed. Signature verification suspends this call, and the signature engine
|
|
620
|
-
// holds this buffer across that suspension -- a composite arm keeps slices of it and imports
|
|
621
|
-
// them only after its digest settles. A view would leave the trust key writable for that whole
|
|
622
|
-
// window, so a caller could hand in an unrelated key of the same length, overwrite it with the
|
|
623
|
-
// real issuer's while the digest ran, and be told the certificate verified under the key they
|
|
624
|
-
// named. The verdict answers for the key the call was made with.
|
|
625
495
|
spki: guard.bytes.snapshot(issuer.publicKey, AttrCertError, "attrcert/bad-input", "issuer.publicKey"),
|
|
626
496
|
};
|
|
627
497
|
}
|
|
628
498
|
|
|
629
|
-
// RFC 5755 sec. 4.3.2: a targeted AC is valid only at the servers it names, and "servers not named
|
|
630
|
-
// MUST reject the AC". An absent extension means untargeted, so there is nothing to check.
|
|
631
|
-
//
|
|
632
|
-
// Only the decoded targetName and targetGroup alternatives are compared. Searching the extension's
|
|
633
|
-
// raw bytes for the caller's encoded GeneralName would also match a name nested inside a targetCert,
|
|
634
|
-
// where it identifies the TARGET CERTIFICATE'S ISSUER rather than a target, and would then accept an
|
|
635
|
-
// AC at a server it never named. A match against a targetGroup does count: sec. 4.3.2 targets "named
|
|
636
|
-
// targets or groups", so a verifier presenting its group name is naming itself among them.
|
|
637
|
-
//
|
|
638
|
-
// The caller's GeneralName goes through the shared builder, then back through the SAME GeneralName
|
|
639
|
-
// reader the parser used, so both sides of every comparison are the one decoded shape. Reading it
|
|
640
|
-
// back rather than dereferencing a context node's `content` also keeps this off a field that is
|
|
641
|
-
// null whenever the node is constructed.
|
|
642
499
|
function _callerTarget(target) {
|
|
643
500
|
return attrcert.readGeneralName(_b.encodeGeneralName(target), "attrcert/bad-input");
|
|
644
501
|
}
|
|
645
502
|
|
|
646
|
-
// RFC 5280 sec. 7.5: two mailboxes match when the local-part is an exact match AND the host-part
|
|
647
|
-
// matches case-insensitively. The split is on the LAST "@", since a quoted local-part may contain
|
|
648
|
-
// one; a value with no "@" has no host-part to fold and compares exactly.
|
|
649
503
|
function _mailboxEqual(a, bx) {
|
|
650
504
|
var i = a.lastIndexOf("@"), j = bx.lastIndexOf("@");
|
|
651
505
|
if (i < 0 || j < 0) return a === bx;
|
|
@@ -653,37 +507,21 @@ function _mailboxEqual(a, bx) {
|
|
|
653
507
|
a.slice(i + 1).toLowerCase() === bx.slice(j + 1).toLowerCase();
|
|
654
508
|
}
|
|
655
509
|
|
|
656
|
-
// Whether a target the AC names is this verifier, under the matching rule each GeneralName form
|
|
657
|
-
// defines. A form whose rule this build does not implement compares exactly, which can refuse a
|
|
658
|
-
// name a fuller comparison would accept and can never accept one it would refuse.
|
|
659
510
|
function _generalNameMatches(entry, want) {
|
|
660
511
|
if (!entry || entry.tagNumber !== want.tagNumber) return false;
|
|
661
512
|
switch (entry.tagNumber) {
|
|
662
|
-
// RFC 5280 sec. 7.2: "When comparing DNS names for equality, conforming implementations MUST
|
|
663
|
-
// perform a case-insensitive exact match on the entire DNS name."
|
|
664
513
|
case 2: return String(entry.value).toLowerCase() === String(want.value).toLowerCase();
|
|
665
|
-
// sec. 7.5, as above.
|
|
666
514
|
case 1: return _mailboxEqual(String(entry.value), String(want.value));
|
|
667
|
-
// A directoryName is a distinguished name, and sec. 7.1 identity is not string equality, so it
|
|
668
|
-
// goes through the one DN comparator rather than comparing the printed form.
|
|
669
515
|
case 4:
|
|
670
516
|
return !!(entry.value && want.value) &&
|
|
671
517
|
guard.name.dnEqual(entry.value.rdns, want.value.rdns, _err, "attrcert/bad-input", "the target name");
|
|
672
|
-
// sec. 7.4 makes a URI comparison a full RFC 3987 normalization: percent-encoding, path
|
|
673
|
-
// segments and scheme-based rules, not merely lowercasing the scheme and host. A partial
|
|
674
|
-
// normalization would be a guess that could match a URI it should not, so the encoded value is
|
|
675
|
-
// compared as it stands. Re-open condition: an RFC 3987 normalizer in the toolkit.
|
|
676
518
|
default:
|
|
677
519
|
if (Buffer.isBuffer(entry.value) && Buffer.isBuffer(want.value)) return entry.value.equals(want.value);
|
|
678
520
|
if (typeof entry.value === "string" && typeof want.value === "string") return entry.value === want.value;
|
|
679
|
-
// A form whose decoded value is neither (otherName, ediPartyName, x400Address) has no
|
|
680
|
-
// comparison here; the caller is told so rather than given a silent mismatch.
|
|
681
521
|
return null;
|
|
682
522
|
}
|
|
683
523
|
}
|
|
684
524
|
|
|
685
|
-
// `want` is the caller's target already decoded, so this runs against the target the call was made
|
|
686
|
-
// with. See _fixVerifyOptions.
|
|
687
525
|
function _checkTargeting(ac, want) {
|
|
688
526
|
var ext = null;
|
|
689
527
|
for (var i = 0; i < ac.extensions.length; i++) {
|
|
@@ -694,26 +532,7 @@ function _checkTargeting(ac, want) {
|
|
|
694
532
|
return { checked: false, ok: false,
|
|
695
533
|
reason: "this attribute certificate is targeted, and no opts.target names this verifier (RFC 5755 sec. 4.3.2)" };
|
|
696
534
|
}
|
|
697
|
-
// The extension's syntax is a SEQUENCE OF Targets, and its groups are FLATTENED rather than
|
|
698
|
-
// required one by one. RFC 5755 sec. 4.3.2: "If more than one Targets element is found in an AC,
|
|
699
|
-
// the extension MUST be treated as if all Target elements had been found within one Targets
|
|
700
|
-
// element." An issuer conforming to the same paragraph produces only one group anyway, so the
|
|
701
|
-
// nesting carries no meaning to honor; requiring a match in every group would refuse ACs the
|
|
702
|
-
// paragraph says to accept.
|
|
703
535
|
var groups = ext.decoded || [];
|
|
704
|
-
// sec. 4.3.2 defines the check exactly: it "passes if the current server (recipient) is one of the
|
|
705
|
-
// targetName fields in the Targets SEQUENCE, or if the current server is a member of one of the
|
|
706
|
-
// targetGroup fields". The third arm is excluded by the same section -- "The targetCert CHOICE
|
|
707
|
-
// within the Target structure is only present to allow future compatibility with [X.509-2000] and
|
|
708
|
-
// MUST NOT be used" -- and the names inside it identify a certificate, not a target.
|
|
709
|
-
//
|
|
710
|
-
// An AC using it is refused before any match is considered, the same way sec. 6's "An AC MUST NOT
|
|
711
|
-
// contain both a noRevAvail extension and a 'pointer in AC'" is enforced here. Both are MUST NOTs
|
|
712
|
-
// the issuer broke, and a certificate that breaks its own profile is refused rather than partly
|
|
713
|
-
// honored: passing it because some OTHER entry happened to match would resolve the violation in
|
|
714
|
-
// the accepting direction, and would leave the verdict depending on which entries an attacker
|
|
715
|
-
// chose to include alongside the forbidden one. The targets check itself never runs, so its slot
|
|
716
|
-
// says so.
|
|
717
536
|
for (var g0 = 0; g0 < groups.length; g0++) {
|
|
718
537
|
for (var t0 = 0; t0 < groups[g0].length; t0++) {
|
|
719
538
|
if (groups[g0][t0].kind !== "targetCert") continue;
|
|
@@ -732,7 +551,6 @@ function _checkTargeting(ac, want) {
|
|
|
732
551
|
if (m === null) uncomparable = entry.name && entry.name.tagNumber;
|
|
733
552
|
}
|
|
734
553
|
}
|
|
735
|
-
// A target this build cannot compare leaves the check unperformed, which cannot read as a pass.
|
|
736
554
|
if (uncomparable !== null) {
|
|
737
555
|
return { checked: false, ok: false,
|
|
738
556
|
reason: "this attribute certificate names a target in a GeneralName form ([" + uncomparable +
|
|
@@ -742,21 +560,6 @@ function _checkTargeting(ac, want) {
|
|
|
742
560
|
reason: "this attribute certificate does not name this verifier among its targets (RFC 5755 sec. 4.3.2)" };
|
|
743
561
|
}
|
|
744
562
|
|
|
745
|
-
// The critical extensions this verb SUPPORTS, by OID. RFC 5755 sec. 5 defines support in two parts:
|
|
746
|
-
// the verifier must be able to parse the value, AND "where the extension value causes the AC to be
|
|
747
|
-
// rejected, the AC verifier MUST reject the AC". Parsing alone satisfies only the first, so an
|
|
748
|
-
// extension whose constraints this verb never evaluates cannot be called supported however cleanly
|
|
749
|
-
// it decodes.
|
|
750
|
-
//
|
|
751
|
-
// targetInformation -- evaluated, by the sec. 4.3.2 check below.
|
|
752
|
-
// acAuditIdentity -- sec. 4.3.1 is an audit/logging identity and states no rule that rejects an
|
|
753
|
-
// AC, so there is no second part to satisfy. It rides out on the verdict's
|
|
754
|
-
// `extensions` for the logging the section asks for.
|
|
755
|
-
//
|
|
756
|
-
// Everything else critical is refused, including extensions this build parses but does not act on:
|
|
757
|
-
// aaControls (sec. 7.4) constrains what an AA may delegate and acProxying (sec. 7.2) constrains
|
|
758
|
-
// proxy use, and an unevaluated constraint is exactly what item 7 exists to stop. A critical
|
|
759
|
-
// extension defined in future defaults to refused, which is the safe direction.
|
|
760
563
|
var SUPPORTED_CRITICAL = {};
|
|
761
564
|
SUPPORTED_CRITICAL[O("targetInformation")] = 1;
|
|
762
565
|
SUPPORTED_CRITICAL[O("acAuditIdentity")] = 1;
|
|
@@ -765,10 +568,7 @@ function _unsupportedCritical(ac) {
|
|
|
765
568
|
for (var i = 0; i < ac.extensions.length; i++) {
|
|
766
569
|
var e = ac.extensions[i];
|
|
767
570
|
if (e.critical !== true) continue;
|
|
768
|
-
// Own-property membership, so an extension OID that happens to name something on Object.prototype
|
|
769
|
-
// cannot read as supported.
|
|
770
571
|
if (!_hasOwn(SUPPORTED_CRITICAL, e.oid)) return e.oid;
|
|
771
|
-
// A supported OID whose value this build could not interpret is still unusable.
|
|
772
572
|
if (e.decoded === null || e.decoded === undefined || e.decoded.opaque === true) return e.oid;
|
|
773
573
|
}
|
|
774
574
|
return null;
|
|
@@ -873,13 +673,6 @@ function _unsupportedCritical(ac) {
|
|
|
873
673
|
function verify(ac, issuer, opts) {
|
|
874
674
|
return guard.async.deferred(function () { return _verify(ac, issuer, opts); });
|
|
875
675
|
}
|
|
876
|
-
// Every caller-owned value the verdict depends on, read HERE and reduced to a value the caller no
|
|
877
|
-
// longer reaches: the instant rather than the Date (a Date is mutable through setTime even when the
|
|
878
|
-
// property is never reassigned), and the target already encoded and decoded rather than the object.
|
|
879
|
-
// Signature verification suspends this call for at least a turn, and an option re-read after it
|
|
880
|
-
// resumes answers for whatever the caller last wrote, so a caller could hand in an expired instant,
|
|
881
|
-
// replace it while WebCrypto works, and receive `verified: true`. The reduction is returned rather
|
|
882
|
-
// than performed at each use, so reading an option late is not an available mistake.
|
|
883
676
|
function _fixVerifyOptions(opts) {
|
|
884
677
|
var o = guard.identifier.optionsObject(opts, _err, "attrcert/bad-input", "pki.attrcert.verify options");
|
|
885
678
|
guard.identifier.assertKnownKeys(o, KNOWN_VERIFY_OPTS, _err, "attrcert/bad-input",
|
|
@@ -897,24 +690,16 @@ function _fixVerifyOptions(opts) {
|
|
|
897
690
|
}
|
|
898
691
|
revocationStatus = o.revocationStatus;
|
|
899
692
|
}
|
|
900
|
-
// A supplied target is encoded and read back whether or not the AC turns out to be targeted, so a
|
|
901
|
-
// malformed one is refused at the call that supplied it.
|
|
902
693
|
return {
|
|
903
694
|
at: at, revocationStatus: revocationStatus,
|
|
904
695
|
target: o.target === undefined ? undefined : _callerTarget(o.target),
|
|
905
696
|
};
|
|
906
697
|
}
|
|
907
698
|
|
|
908
|
-
// RFC 5755 sec. 6 "pointer in AC": "ACs may 'point' to sources of revocation status information,
|
|
909
|
-
// using either an authorityInfoAccess extension or a crlDistributionPoints extension within the AC."
|
|
910
|
-
// Named by OID, since the OID is what an issuer signs and a display name is not.
|
|
911
699
|
var REVOCATION_POINTERS = Object.create(null);
|
|
912
700
|
REVOCATION_POINTERS[O("cRLDistributionPoints")] = "crlDistributionPoints";
|
|
913
701
|
REVOCATION_POINTERS[O("authorityInfoAccess")] = "authorityInfoAccess";
|
|
914
702
|
|
|
915
|
-
// The two revocation schemes an AC declares, read from its extensions. RFC 5755 sec. 4.3.6 / sec. 6:
|
|
916
|
-
// noRevAvail is the issuer stating that no revocation information will ever be made available for
|
|
917
|
-
// this AC, and it is a bare NULL, so its presence is the whole content.
|
|
918
703
|
function _revocationScheme(ac) {
|
|
919
704
|
var out = { noRevAvail: false, pointer: null };
|
|
920
705
|
for (var i = 0; i < ac.extensions.length; i++) {
|
|
@@ -936,8 +721,6 @@ function _verify(ac, issuer, opts) {
|
|
|
936
721
|
holder: parsed.holder,
|
|
937
722
|
issuer: acIssuer,
|
|
938
723
|
attributes: parsed.attributes,
|
|
939
|
-
// Carried so a caller can act on what rode along: sec. 4.3.1 asks that an audit identity be
|
|
940
|
-
// used for audit and logging, which it cannot be if the verdict drops it.
|
|
941
724
|
extensions: parsed.extensions,
|
|
942
725
|
notBefore: parsed.validity.notBeforeTime,
|
|
943
726
|
notAfter: parsed.validity.notAfterTime,
|
|
@@ -947,71 +730,41 @@ function _verify(ac, issuer, opts) {
|
|
|
947
730
|
targetingChecked: false,
|
|
948
731
|
revocationChecked: false,
|
|
949
732
|
noRevAvail: false,
|
|
950
|
-
holderBindingChecked: false,
|
|
951
|
-
issuerPathChecked: false,
|
|
952
|
-
valid: false,
|
|
733
|
+
holderBindingChecked: false,
|
|
734
|
+
issuerPathChecked: false,
|
|
735
|
+
valid: false,
|
|
953
736
|
verified: false,
|
|
954
737
|
reason: undefined,
|
|
955
738
|
};
|
|
956
739
|
|
|
957
|
-
// The signature runs first so `signatureValid` is answered even when a later gate refuses: an
|
|
958
|
-
// operator reading an out-of-window verdict still learns whether the bytes were authentic.
|
|
959
740
|
return Promise.resolve(attrcertVerify.verifyAcSignature(parsed, trust.spki)).then(function (sigOk) {
|
|
960
741
|
result.signatureValid = sigOk === true;
|
|
961
742
|
var fail = function (reason) { result.reason = reason; return result; };
|
|
962
743
|
if (!result.signatureValid) return fail("the signature does not verify under the issuer public key");
|
|
963
744
|
|
|
964
|
-
// Item 4: the caller trusts a named AC issuer, and this AC must be the one they named.
|
|
965
745
|
if (!guard.name.dnEqual(acIssuer.rdns, trust.rdns, _err,
|
|
966
746
|
"attrcert/bad-input", "the AC issuer")) {
|
|
967
747
|
return fail("this attribute certificate names issuer " + JSON.stringify(acIssuer.dn) +
|
|
968
748
|
", which is not the AC issuer this verifier trusts");
|
|
969
749
|
}
|
|
970
750
|
|
|
971
|
-
// Item 7, before the fields are read: an unsupported critical extension makes the whole AC
|
|
972
|
-
// unusable, so a caller must not act on attributes carried beside one.
|
|
973
751
|
var badExt = _unsupportedCritical(parsed);
|
|
974
752
|
if (badExt) return fail("this attribute certificate carries an unsupported critical extension (" + badExt + "), which RFC 5755 sec. 5 item 7 requires be rejected");
|
|
975
753
|
|
|
976
|
-
// Item 6.
|
|
977
754
|
var targeting = _checkTargeting(parsed, fixed.target);
|
|
978
755
|
result.targetingChecked = targeting.checked;
|
|
979
756
|
if (!targeting.ok) return fail(targeting.reason);
|
|
980
757
|
|
|
981
|
-
// Item 5. An absent instant asks nothing, which cannot read as a pass.
|
|
982
758
|
if (fixed.at === null) {
|
|
983
759
|
return fail("no opts.time was supplied, so RFC 5755 sec. 5 item 5 (the evaluation instant lies within the validity) went unasked");
|
|
984
760
|
}
|
|
985
761
|
result.validityChecked = true;
|
|
986
|
-
// "If the evaluation time is equal to either notBeforeTime or notAfterTime, then the AC is
|
|
987
|
-
// timely and this check succeeds" -- both bounds are inclusive.
|
|
988
762
|
if (fixed.at < guard.time.instantOf(result.notBefore) || fixed.at > guard.time.instantOf(result.notAfter)) {
|
|
989
763
|
return fail("the evaluation instant lies outside this attribute certificate's validity");
|
|
990
764
|
}
|
|
991
765
|
|
|
992
|
-
// RFC 5755 sec. 6. This verb implements the "never revoke" scheme: it holds no revocation
|
|
993
|
-
// evidence of its own and follows no pointer out of the certificate. The section says exactly
|
|
994
|
-
// what that obliges -- "If only the 'never revoke' scheme is supported, then all ACs that do not
|
|
995
|
-
// contain a noRevAvail extension, MUST be rejected" -- because where noRevAvail is absent "the
|
|
996
|
-
// AC issuer is implicitly stating that revocation status checks are supported", so a verdict
|
|
997
|
-
// that skipped one would grant privileges the issuer expected to be able to withdraw. A caller
|
|
998
|
-
// who has established the status passes it as opts.revocationStatus; the AC's own
|
|
999
|
-
// crlDistributionPoints / authorityInfoAccess pointers ride out on the verdict's `extensions`
|
|
1000
|
-
// for them to follow.
|
|
1001
|
-
//
|
|
1002
|
-
// That status is accepted whether or not the AC carries a pointer, because the section closes by
|
|
1003
|
-
// saying where it may come from: "An AC verifier MAY use any source for AC revocation status
|
|
1004
|
-
// information." Requiring a pointer in the certificate would refuse a verifier who obtained the
|
|
1005
|
-
// status from a directory, an out-of-band CRL, or a local record, all of which that sentence
|
|
1006
|
-
// permits. The verdict keeps the two apart: `noRevAvail` says the issuer declared none would
|
|
1007
|
-
// exist, and its absence beside `revocationChecked: true` says the caller supplied one.
|
|
1008
766
|
var scheme = _revocationScheme(parsed);
|
|
1009
767
|
result.noRevAvail = scheme.noRevAvail;
|
|
1010
|
-
// The two schemes are alternatives, and the section closes by saying so: "An AC MUST NOT contain
|
|
1011
|
-
// both a noRevAvail extension and a 'pointer in AC'." An AC carrying both tells the verifier two
|
|
1012
|
-
// different things about its own revocation, and reading the never-revoke claim as settling the
|
|
1013
|
-
// question would resolve that contradiction in the accepting direction. It is refused whichever
|
|
1014
|
-
// way the caller answers, since the certificate itself is the thing that is wrong.
|
|
1015
768
|
if (scheme.noRevAvail && scheme.pointer) {
|
|
1016
769
|
return fail("this attribute certificate carries both noRevAvail and a " + scheme.pointer +
|
|
1017
770
|
" revocation pointer, which RFC 5755 sec. 6 says an AC MUST NOT do");
|
|
@@ -1022,7 +775,6 @@ function _verify(ac, issuer, opts) {
|
|
|
1022
775
|
return fail("this attribute certificate is revoked, as opts.revocationStatus reports");
|
|
1023
776
|
}
|
|
1024
777
|
} else if (result.noRevAvail) {
|
|
1025
|
-
// The issuer has stated no revocation information will exist, so there is no status to obtain.
|
|
1026
778
|
result.revocationChecked = true;
|
|
1027
779
|
} else {
|
|
1028
780
|
return fail("this attribute certificate carries no noRevAvail extension, so its issuer supports revocation status checks and RFC 5755 sec. 6 requires one; establish the status from the certificate's own pointers and pass it as opts.revocationStatus");
|