@naylence/advanced-security 0.3.4 → 0.3.5-test.0

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.
Files changed (34) hide show
  1. package/dist/browser/index.js +4368 -6314
  2. package/dist/browser/index.js.map +1 -1
  3. package/dist/cjs/browser.js +30 -2
  4. package/dist/cjs/browser.js.map +1 -1
  5. package/dist/cjs/naylence/fame/security/cert/index.js.map +1 -1
  6. package/dist/cjs/naylence/fame/security/cert/util.js +481 -81
  7. package/dist/cjs/naylence/fame/security/cert/util.js.map +1 -1
  8. package/dist/cjs/naylence/fame/security/encryption/channel/channel-encryption-manager.js +27 -12
  9. package/dist/cjs/naylence/fame/security/encryption/channel/channel-encryption-manager.js.map +1 -1
  10. package/dist/cjs/naylence/fame/security/register-advanced-security-factories.js +28 -1
  11. package/dist/cjs/naylence/fame/security/register-advanced-security-factories.js.map +1 -1
  12. package/dist/cjs/naylence/fame/security/signing/eddsa-envelope-verifier.js +69 -3
  13. package/dist/cjs/naylence/fame/security/signing/eddsa-envelope-verifier.js.map +1 -1
  14. package/dist/esm/browser.js +16 -2
  15. package/dist/esm/browser.js.map +1 -1
  16. package/dist/esm/naylence/fame/security/cert/index.js.map +1 -1
  17. package/dist/esm/naylence/fame/security/cert/util.js +481 -81
  18. package/dist/esm/naylence/fame/security/cert/util.js.map +1 -1
  19. package/dist/esm/naylence/fame/security/encryption/channel/channel-encryption-manager.js +27 -12
  20. package/dist/esm/naylence/fame/security/encryption/channel/channel-encryption-manager.js.map +1 -1
  21. package/dist/esm/naylence/fame/security/register-advanced-security-factories.js +28 -1
  22. package/dist/esm/naylence/fame/security/register-advanced-security-factories.js.map +1 -1
  23. package/dist/esm/naylence/fame/security/signing/eddsa-envelope-verifier.js +36 -3
  24. package/dist/esm/naylence/fame/security/signing/eddsa-envelope-verifier.js.map +1 -1
  25. package/dist/types/browser.d.ts +16 -1
  26. package/dist/types/browser.d.ts.map +1 -1
  27. package/dist/types/naylence/fame/security/cert/index.d.ts +1 -1
  28. package/dist/types/naylence/fame/security/cert/index.d.ts.map +1 -1
  29. package/dist/types/naylence/fame/security/cert/util.d.ts +13 -23
  30. package/dist/types/naylence/fame/security/cert/util.d.ts.map +1 -1
  31. package/dist/types/naylence/fame/security/encryption/channel/channel-encryption-manager.d.ts.map +1 -1
  32. package/dist/types/naylence/fame/security/register-advanced-security-factories.d.ts.map +1 -1
  33. package/dist/types/naylence/fame/security/signing/eddsa-envelope-verifier.d.ts.map +1 -1
  34. package/package.json +2 -2
@@ -1,6 +1,34 @@
1
1
  "use strict";
2
+ /**
3
+ * Browser-friendly entry point that exposes only modules compatible with
4
+ * runtimes lacking Node.js built-ins. Node-specific certificate authority
5
+ * helpers and Fastify bindings are intentionally excluded.
6
+ */
2
7
  Object.defineProperty(exports, "__esModule", { value: true });
8
+ exports.registerAdvancedSecurityFactories = exports.EdDSAEnvelopeVerifier = exports.ADVANCED_EDDSA_ENVELOPE_VERIFIER_FACTORY_META = exports.AdvancedEdDSAEnvelopeVerifierFactory = exports.ADVANCED_EDDSA_ENVELOPE_SIGNER_FACTORY_META = exports.AdvancedEdDSAEnvelopeSignerFactory = exports.formatCertificateInfo = exports.extractCertificateInfo = exports.ENV_VAR_FAME_CA_SERVICE_URL = exports.CAServiceClient = exports.GRANT_PURPOSE_CA_SIGN = exports.publicKeyFromX5c = exports.validateJwkX5cCertificate = void 0;
3
9
  const tslib_1 = require("tslib");
4
- // Browser-friendly entry point. Limit exports to APIs that are safe for browser usage.
5
- tslib_1.__exportStar(require("./index.js"), exports);
10
+ var util_js_1 = require("./naylence/fame/security/cert/util.js");
11
+ Object.defineProperty(exports, "validateJwkX5cCertificate", { enumerable: true, get: function () { return util_js_1.validateJwkX5cCertificate; } });
12
+ Object.defineProperty(exports, "publicKeyFromX5c", { enumerable: true, get: function () { return util_js_1.publicKeyFromX5c; } });
13
+ var grants_js_1 = require("./naylence/fame/security/cert/grants.js");
14
+ Object.defineProperty(exports, "GRANT_PURPOSE_CA_SIGN", { enumerable: true, get: function () { return grants_js_1.GRANT_PURPOSE_CA_SIGN; } });
15
+ var ca_service_client_js_1 = require("./naylence/fame/security/cert/ca-service-client.js");
16
+ Object.defineProperty(exports, "CAServiceClient", { enumerable: true, get: function () { return ca_service_client_js_1.CAServiceClient; } });
17
+ Object.defineProperty(exports, "ENV_VAR_FAME_CA_SERVICE_URL", { enumerable: true, get: function () { return ca_service_client_js_1.ENV_VAR_FAME_CA_SERVICE_URL; } });
18
+ Object.defineProperty(exports, "extractCertificateInfo", { enumerable: true, get: function () { return ca_service_client_js_1.extractCertificateInfo; } });
19
+ Object.defineProperty(exports, "formatCertificateInfo", { enumerable: true, get: function () { return ca_service_client_js_1.formatCertificateInfo; } });
20
+ tslib_1.__exportStar(require("./naylence/fame/security/encryption/index.js"), exports);
21
+ var eddsa_envelope_signer_factory_js_1 = require("./naylence/fame/security/signing/eddsa-envelope-signer-factory.js");
22
+ Object.defineProperty(exports, "AdvancedEdDSAEnvelopeSignerFactory", { enumerable: true, get: function () { return eddsa_envelope_signer_factory_js_1.AdvancedEdDSAEnvelopeSignerFactory; } });
23
+ Object.defineProperty(exports, "ADVANCED_EDDSA_ENVELOPE_SIGNER_FACTORY_META", { enumerable: true, get: function () { return eddsa_envelope_signer_factory_js_1.FACTORY_META; } });
24
+ var eddsa_envelope_verifier_factory_js_1 = require("./naylence/fame/security/signing/eddsa-envelope-verifier-factory.js");
25
+ Object.defineProperty(exports, "AdvancedEdDSAEnvelopeVerifierFactory", { enumerable: true, get: function () { return eddsa_envelope_verifier_factory_js_1.AdvancedEdDSAEnvelopeVerifierFactory; } });
26
+ Object.defineProperty(exports, "ADVANCED_EDDSA_ENVELOPE_VERIFIER_FACTORY_META", { enumerable: true, get: function () { return eddsa_envelope_verifier_factory_js_1.FACTORY_META; } });
27
+ var eddsa_envelope_verifier_js_1 = require("./naylence/fame/security/signing/eddsa-envelope-verifier.js");
28
+ Object.defineProperty(exports, "EdDSAEnvelopeVerifier", { enumerable: true, get: function () { return eddsa_envelope_verifier_js_1.EdDSAEnvelopeVerifier; } });
29
+ tslib_1.__exportStar(require("./naylence/fame/security/keys/index.js"), exports);
30
+ var register_advanced_security_factories_js_1 = require("./naylence/fame/security/register-advanced-security-factories.js");
31
+ Object.defineProperty(exports, "registerAdvancedSecurityFactories", { enumerable: true, get: function () { return register_advanced_security_factories_js_1.registerAdvancedSecurityFactories; } });
32
+ tslib_1.__exportStar(require("./naylence/fame/stickiness/index.js"), exports);
33
+ tslib_1.__exportStar(require("./naylence/fame/welcome/index.js"), exports);
6
34
  //# sourceMappingURL=browser.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"browser.js","sourceRoot":"","sources":["../../src/browser.ts"],"names":[],"mappings":";;;AAAA,uFAAuF;AACvF,qDAA2B"}
1
+ {"version":3,"file":"browser.js","sourceRoot":"","sources":["../../src/browser.ts"],"names":[],"mappings":";AAAA;;;;GAIG;;;;AAEH,iEAM+C;AAL9C,oHAAA,yBAAyB,OAAA;AAGzB,2GAAA,gBAAgB,OAAA;AAGjB,qEAAgF;AAAvE,kHAAA,qBAAqB,OAAA;AAC9B,2FAO4D;AAN3D,uHAAA,eAAe,OAAA;AAGf,mIAAA,2BAA2B,OAAA;AAC3B,8HAAA,sBAAsB,OAAA;AACtB,6HAAA,qBAAqB,OAAA;AAGtB,uFAA6D;AAE7D,sHAI2E;AAH1E,sJAAA,kCAAkC,OAAA;AAClC,+JAAA,YAAY,OAA+C;AAG5D,0HAI6E;AAH5E,0JAAA,oCAAoC,OAAA;AACpC,mKAAA,YAAY,OAAiD;AAG9D,0GAIqE;AAHpE,mIAAA,qBAAqB,OAAA;AAKtB,iFAAuD;AAEvD,4HAG0E;AAFzE,4JAAA,iCAAiC,OAAA;AAIlC,8EAAoD;AACpD,2EAAiD"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../../src/naylence/fame/security/cert/index.ts"],"names":[],"mappings":";;;AAAA,qCAKmB;AAJjB,oHAAA,yBAAyB,OAAA;AAGzB,2GAAA,gBAAgB,OAAA;AAElB,yCAAoD;AAA3C,kHAAA,qBAAqB,OAAA;AAC9B,mFAI0C;AAHxC,2IAAA,yBAAyB,OAAA;AAI3B,mGAIkD;AAHhD,0JAAA,gCAAgC,OAAA;AAChC,kKAAA,YAAY,OAA4C;AAI1D,gDAAgD;AAChD,6CAOuB;AAHrB,wGAAA,SAAS,OAAA;AACT,sHAAA,uBAAuB,OAAA;AAGzB,+DAOgC;AAN9B,uHAAA,eAAe,OAAA;AACf,8HAAA,sBAAsB,OAAA;AACtB,6HAAA,qBAAqB,OAAA;AAGrB,mIAAA,2BAA2B,OAAA;AAE7B,mEAakC;AAZhC,0HAAA,gBAAgB,OAAA;AAEhB,iHAAA,OAAO,OAAA;AACP,sHAAA,YAAY,OAAA;AACZ,qHAAA,WAAW,OAAA;AACX,sHAAA,YAAY,OAAA;AACZ,iIAAA,uBAAuB,OAAA;AACvB,4HAAA,kBAAkB,OAAA;AAClB,+HAAA,qBAAqB,OAAA;AACrB,qIAAA,2BAA2B,OAAA;AAC3B,gIAAA,sBAAsB,OAAA;AACtB,gIAAA,sBAAsB,OAAA;AAExB,iEAaiC;AAZ/B,yHAAA,gBAAgB,OAAA;AAEhB,8HAAA,qBAAqB,OAAA;AACrB,6HAAA,oBAAoB,OAAA;AACpB,6HAAA,oBAAoB,OAAA;AACpB,4HAAA,mBAAmB,OAAA;AACnB,yIAAA,gCAAgC,OAAA;AAChC,wIAAA,+BAA+B,OAAA;AAC/B,mIAAA,0BAA0B,OAAA;AAC1B,kIAAA,yBAAyB,OAAA;AACzB,kIAAA,yBAAyB,OAAA;AACzB,iIAAA,wBAAwB,OAAA;AAE1B,iEAIiC;AAH/B,yHAAA,gBAAgB,OAAA;AAEhB,qIAAA,4BAA4B,OAAA;AAE9B,iFAGyC;AAFvC,wIAAA,uBAAuB,OAAA"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../../src/naylence/fame/security/cert/index.ts"],"names":[],"mappings":";;;AAAA,qCAMmB;AALjB,oHAAA,yBAAyB,OAAA;AAGzB,2GAAA,gBAAgB,OAAA;AAGlB,yCAAoD;AAA3C,kHAAA,qBAAqB,OAAA;AAC9B,mFAI0C;AAHxC,2IAAA,yBAAyB,OAAA;AAI3B,mGAIkD;AAHhD,0JAAA,gCAAgC,OAAA;AAChC,kKAAA,YAAY,OAA4C;AAI1D,gDAAgD;AAChD,6CAOuB;AAHrB,wGAAA,SAAS,OAAA;AACT,sHAAA,uBAAuB,OAAA;AAGzB,+DAOgC;AAN9B,uHAAA,eAAe,OAAA;AACf,8HAAA,sBAAsB,OAAA;AACtB,6HAAA,qBAAqB,OAAA;AAGrB,mIAAA,2BAA2B,OAAA;AAE7B,mEAakC;AAZhC,0HAAA,gBAAgB,OAAA;AAEhB,iHAAA,OAAO,OAAA;AACP,sHAAA,YAAY,OAAA;AACZ,qHAAA,WAAW,OAAA;AACX,sHAAA,YAAY,OAAA;AACZ,iIAAA,uBAAuB,OAAA;AACvB,4HAAA,kBAAkB,OAAA;AAClB,+HAAA,qBAAqB,OAAA;AACrB,qIAAA,2BAA2B,OAAA;AAC3B,gIAAA,sBAAsB,OAAA;AACtB,gIAAA,sBAAsB,OAAA;AAExB,iEAaiC;AAZ/B,yHAAA,gBAAgB,OAAA;AAEhB,8HAAA,qBAAqB,OAAA;AACrB,6HAAA,oBAAoB,OAAA;AACpB,6HAAA,oBAAoB,OAAA;AACpB,4HAAA,mBAAmB,OAAA;AACnB,yIAAA,gCAAgC,OAAA;AAChC,wIAAA,+BAA+B,OAAA;AAC/B,mIAAA,0BAA0B,OAAA;AAC1B,kIAAA,yBAAyB,OAAA;AACzB,kIAAA,yBAAyB,OAAA;AACzB,iIAAA,wBAAwB,OAAA;AAE1B,iEAIiC;AAH/B,yHAAA,gBAAgB,OAAA;AAEhB,qIAAA,4BAA4B,OAAA;AAE9B,iFAGyC;AAFvC,wIAAA,uBAAuB,OAAA"}
@@ -1,19 +1,62 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.validateJwkX5cCertificate = validateJwkX5cCertificate;
4
3
  exports.publicKeyFromX5c = publicKeyFromX5c;
4
+ exports.validateJwkX5cCertificate = validateJwkX5cCertificate;
5
5
  const asn1_schema_1 = require("@peculiar/asn1-schema");
6
6
  const asn1_x509_1 = require("@peculiar/asn1-x509");
7
+ const sha256_1 = require("@noble/hashes/sha256");
8
+ const sha2_js_1 = require("@noble/hashes/sha2.js");
9
+ const ed25519_1 = require("@noble/ed25519");
7
10
  const runtime_1 = require("@naylence/runtime");
8
11
  const logger = (0, runtime_1.getLogger)("naylence.fame.security.cert.util");
9
- /**
10
- * Temporary TypeScript port of validate_jwk_x5c_certificate.
11
- *
12
- * NOTE: The full certificate chain validation logic from the Python runtime
13
- * is still being ported. This implementation performs lightweight structure
14
- * checks and defers deep X.509 validation until the remaining modules are
15
- * available.
16
- */
12
+ const CACHE_LIMIT = 512;
13
+ const OID_ED25519 = "1.3.101.112";
14
+ const textEncoder = new TextEncoder();
15
+ const trustCache = new Map();
16
+ function publicKeyFromX5c(x5c, options = {}) {
17
+ if (!Array.isArray(x5c) || x5c.length === 0) {
18
+ throw new Error("Empty certificate chain");
19
+ }
20
+ const callId = generateCallId();
21
+ const enforceNameConstraints = options.enforceNameConstraints ?? true;
22
+ const trustStorePem = normalizeTrustStoreOption(options.trustStorePem ?? null);
23
+ const returnCertificate = options.returnCertificate ?? false;
24
+ const { parsed, chainBytes } = parseCertificateChain(x5c);
25
+ logger.debug("public_key_from_x5c_called", {
26
+ call_id: callId,
27
+ x5c_count: parsed.length,
28
+ enforce_name_constraints: enforceNameConstraints,
29
+ has_trust_store: Boolean(trustStorePem),
30
+ return_cert: returnCertificate,
31
+ });
32
+ let cacheKey = null;
33
+ if (!returnCertificate) {
34
+ cacheKey = buildCacheKey(chainBytes, trustStorePem, enforceNameConstraints);
35
+ const cached = getCachedPublicKey(cacheKey);
36
+ if (cached) {
37
+ logger.debug("certificate_cache_hit", {
38
+ call_id: callId,
39
+ cache_key: cacheKey,
40
+ });
41
+ return cached;
42
+ }
43
+ logger.debug("certificate_cache_miss", {
44
+ call_id: callId,
45
+ cache_key: cacheKey,
46
+ });
47
+ }
48
+ const validation = validateCertificateChain(parsed, enforceNameConstraints, trustStorePem);
49
+ if (cacheKey) {
50
+ setCachedPublicKey(cacheKey, validation.publicKey, validation.notAfter);
51
+ }
52
+ if (returnCertificate) {
53
+ return {
54
+ publicKey: validation.publicKey.slice(),
55
+ certificate: validation.certificate,
56
+ };
57
+ }
58
+ return validation.publicKey.slice();
59
+ }
17
60
  function validateJwkX5cCertificate(options) {
18
61
  const { jwk, trustStorePem = null, enforceNameConstraints = true, strict = true, } = options;
19
62
  if (!jwk || typeof jwk !== "object") {
@@ -36,85 +79,442 @@ function validateJwkX5cCertificate(options) {
36
79
  }
37
80
  return { isValid: false, error };
38
81
  }
39
- // Until full validation is available we only log that certificate validation
40
- // was requested. This preserves the call sites and allows adding the full
41
- // chain validation later without changing behaviour.
42
- logger.debug("validate_jwk_x5c_certificate_placeholder", {
43
- enforce_name_constraints: enforceNameConstraints,
44
- has_trust_store: Boolean(trustStorePem),
45
- chain_length: x5c.length,
82
+ try {
83
+ publicKeyFromX5c(x5c, {
84
+ trustStorePem,
85
+ enforceNameConstraints,
86
+ });
87
+ return { isValid: true };
88
+ }
89
+ catch (error) {
90
+ const message = error instanceof Error ? error.message : String(error ?? "unknown");
91
+ const normalized = `Certificate validation failed: ${message}`;
92
+ if (strict) {
93
+ throw new Error(normalized);
94
+ }
95
+ return { isValid: false, error: normalized };
96
+ }
97
+ }
98
+ function validateCertificateChain(parsed, enforceNameConstraints, trustStorePem) {
99
+ const leaf = parsed[0];
100
+ const nowMs = Date.now();
101
+ const notBefore = leaf.certificate.tbsCertificate.validity.notBefore.getTime();
102
+ const notAfter = leaf.certificate.tbsCertificate.validity.notAfter.getTime();
103
+ const notBeforeMs = notBefore.getTime();
104
+ const notAfterMs = notAfter.getTime();
105
+ if (nowMs < notBeforeMs || nowMs > notAfterMs) {
106
+ throw new Error(`Certificate is not currently valid (notBefore: ${notBefore.toISOString()}, notAfter: ${notAfter.toISOString()}, now: ${new Date(nowMs).toISOString()})`);
107
+ }
108
+ const issuers = parsed.slice(1);
109
+ if (enforceNameConstraints && issuers.length > 0) {
110
+ const leafUris = extractUrisFromCert(leaf.certificate);
111
+ validateNameConstraints(issuers, leafUris);
112
+ }
113
+ if (trustStorePem) {
114
+ validateTrustAnchor(parsed, trustStorePem);
115
+ }
116
+ validateChainContinuity(parsed);
117
+ const publicKey = leaf.subjectPublicKey.slice();
118
+ return {
119
+ publicKey,
120
+ certificate: leaf.certificate,
121
+ notAfter,
122
+ };
123
+ }
124
+ function parseCertificateChain(x5c) {
125
+ const parsed = [];
126
+ const derChunks = [];
127
+ for (let index = 0; index < x5c.length; index += 1) {
128
+ const entry = x5c[index];
129
+ if (typeof entry !== "string" || entry.trim().length === 0) {
130
+ throw new Error(`Invalid certificate at index ${index}`);
131
+ }
132
+ let der;
133
+ try {
134
+ der = decodeBase64(entry);
135
+ }
136
+ catch (error) {
137
+ const reason = error instanceof Error ? error.message : String(error);
138
+ throw new Error(`Failed to decode certificate at index ${index}: ${reason}`);
139
+ }
140
+ let certificate;
141
+ try {
142
+ certificate = asn1_schema_1.AsnConvert.parse(der, asn1_x509_1.Certificate);
143
+ }
144
+ catch (error) {
145
+ const reason = error instanceof Error ? error.message : String(error);
146
+ throw new Error(`Failed to parse certificate at index ${index}: ${reason}`);
147
+ }
148
+ parsed.push(createParsedCertificate(certificate, der));
149
+ derChunks.push(der);
150
+ }
151
+ return { parsed, chainBytes: concatBytes(derChunks) };
152
+ }
153
+ function createParsedCertificate(certificate, raw) {
154
+ return {
155
+ raw,
156
+ certificate,
157
+ serialNumber: toHex(new Uint8Array(certificate.tbsCertificate.serialNumber)).toUpperCase(),
158
+ subjectName: serializeName(certificate.tbsCertificate.subject),
159
+ issuerName: serializeName(certificate.tbsCertificate.issuer),
160
+ subjectPublicKey: new Uint8Array(certificate.tbsCertificate.subjectPublicKeyInfo.subjectPublicKey).slice(),
161
+ };
162
+ }
163
+ function extractUrisFromCert(cert) {
164
+ const extension = findExtension(cert, asn1_x509_1.id_ce_subjectAltName);
165
+ if (!extension) {
166
+ return [];
167
+ }
168
+ const subjectAlternativeName = asn1_schema_1.AsnConvert.parse(extension.extnValue.buffer, asn1_x509_1.SubjectAlternativeName);
169
+ const uris = [];
170
+ for (const generalName of subjectAlternativeName) {
171
+ if (generalName.uniformResourceIdentifier) {
172
+ uris.push(generalName.uniformResourceIdentifier);
173
+ }
174
+ }
175
+ return uris;
176
+ }
177
+ function validateNameConstraints(issuers, leafUris) {
178
+ for (const issuer of issuers) {
179
+ const extension = findExtension(issuer.certificate, asn1_x509_1.id_ce_nameConstraints);
180
+ if (!extension) {
181
+ continue;
182
+ }
183
+ const constraints = asn1_schema_1.AsnConvert.parse(extension.extnValue.buffer, asn1_x509_1.NameConstraints);
184
+ if (!constraints.permittedSubtrees) {
185
+ continue;
186
+ }
187
+ const permittedUris = collectPermittedUris(Array.from(constraints.permittedSubtrees));
188
+ if (permittedUris.length === 0) {
189
+ continue;
190
+ }
191
+ for (const uri of leafUris) {
192
+ const allowed = permittedUris.some((prefix) => uri.startsWith(prefix));
193
+ if (!allowed) {
194
+ throw new Error(`URI '${uri}' violates name constraints - not in permitted subtrees: ${permittedUris.join(", ")}`);
195
+ }
196
+ }
197
+ }
198
+ }
199
+ function collectPermittedUris(subtrees) {
200
+ const uris = [];
201
+ for (const subtree of subtrees) {
202
+ if (subtree.base.uniformResourceIdentifier &&
203
+ subtree.base.uniformResourceIdentifier.length > 0) {
204
+ uris.push(subtree.base.uniformResourceIdentifier);
205
+ }
206
+ }
207
+ return uris;
208
+ }
209
+ function validateTrustAnchor(chain, trustStorePem) {
210
+ const trustedCerts = parseTrustStore(trustStorePem);
211
+ if (trustedCerts.length === 0) {
212
+ throw new Error("No valid certificates found in trust store");
213
+ }
214
+ logger.debug("trust_anchor_validation_start", {
215
+ chain_length: chain.length,
216
+ trust_store_cert_count: trustedCerts.length,
46
217
  });
47
- return { isValid: true };
48
- }
49
- /**
50
- * Extract public key from X.509 certificate chain.
51
- *
52
- * Parses the leaf certificate from an x5c array and extracts the raw public key bytes.
53
- * For Ed25519 certificates, this returns the 32-byte public key.
54
- *
55
- * @param x5c - Array of base64-encoded DER certificates (leaf first)
56
- * @param options - Validation options
57
- * @returns The raw public key bytes from the leaf certificate
58
- * @throws Error if certificate parsing or validation fails
59
- */
60
- function publicKeyFromX5c(x5c, options = {}) {
61
- if (!x5c || x5c.length === 0) {
62
- throw new Error("Empty certificate chain");
218
+ const chainInfo = chain.map((cert, index) => `[${index}] ${cert.subjectName} (Serial: ${cert.serialNumber})`);
219
+ const trustedInfo = trustedCerts.map((cert, index) => `[${index}] ${cert.subjectName} (Serial: ${cert.serialNumber})`);
220
+ logger.debug("certificate_chain_validation", {
221
+ chain_certificates: chainInfo,
222
+ trust_store_certificates: trustedInfo,
223
+ });
224
+ // Strategy 1: direct trust (exact certificate match)
225
+ for (let i = 0; i < chain.length; i += 1) {
226
+ const cert = chain[i];
227
+ const match = trustedCerts.find((trusted) => trusted.serialNumber === cert.serialNumber &&
228
+ namesEqual(trusted.certificate.tbsCertificate.subject, cert.certificate.tbsCertificate.subject));
229
+ if (match) {
230
+ logger.debug("certificate_chain_trust_validation_passed", {
231
+ matching_serial: match.serialNumber,
232
+ validation_strategy: `direct_trust_cert_${i}`,
233
+ });
234
+ return;
235
+ }
63
236
  }
64
- // Decode leaf certificate
65
- const certB64 = x5c[0];
66
- if (typeof certB64 !== "string") {
67
- throw new Error("Invalid certificate in x5c array - must be base64 string");
237
+ const leaf = chain[0];
238
+ // Strategy 2: leaf issuer in trust store
239
+ for (const trusted of trustedCerts) {
240
+ if (namesEqual(trusted.certificate.tbsCertificate.subject, leaf.certificate.tbsCertificate.issuer) &&
241
+ trusted.serialNumber !== leaf.serialNumber) {
242
+ verifyCertificateSignature(leaf.certificate, trusted.certificate);
243
+ logger.debug("certificate_chain_trust_validation_passed", {
244
+ matching_serial: trusted.serialNumber,
245
+ validation_strategy: "leaf_issuer_trust",
246
+ });
247
+ return;
248
+ }
68
249
  }
69
- let derBytes;
70
- try {
71
- derBytes = Buffer.from(certB64, "base64");
250
+ // Strategy 3: any intermediate issuer in trust store
251
+ for (let index = 1; index < chain.length; index += 1) {
252
+ const intermediate = chain[index];
253
+ for (const trusted of trustedCerts) {
254
+ if (namesEqual(trusted.certificate.tbsCertificate.subject, intermediate.certificate.tbsCertificate.issuer) &&
255
+ trusted.serialNumber !== intermediate.serialNumber) {
256
+ verifyCertificateSignature(intermediate.certificate, trusted.certificate);
257
+ logger.debug("certificate_chain_trust_validation_passed", {
258
+ matching_serial: trusted.serialNumber,
259
+ validation_strategy: `intermediate_issuer_trust_cert_${index}`,
260
+ });
261
+ return;
262
+ }
263
+ }
72
264
  }
73
- catch (error) {
74
- throw new Error(`Failed to decode base64 certificate: ${error instanceof Error ? error.message : String(error)}`);
265
+ logger.warning("certificate_chain_trust_validation_failed", {
266
+ leaf_subject: leaf.subjectName,
267
+ leaf_issuer: leaf.issuerName,
268
+ leaf_serial: leaf.serialNumber,
269
+ trusted_certificates: trustedInfo,
270
+ chain_certificates: chainInfo,
271
+ reason: "no_matching_trust_anchor",
272
+ });
273
+ throw new Error("Certificate chain is not rooted in a trusted anchor");
274
+ }
275
+ function parseTrustStore(trustStorePem) {
276
+ const normalized = normalizePem(trustStorePem);
277
+ const blocks = extractPemBlocks(normalized);
278
+ const parsed = [];
279
+ for (const block of blocks) {
280
+ try {
281
+ const der = decodeBase64(block);
282
+ const certificate = asn1_schema_1.AsnConvert.parse(der, asn1_x509_1.Certificate);
283
+ parsed.push(createParsedCertificate(certificate, der));
284
+ }
285
+ catch (error) {
286
+ const reason = error instanceof Error ? error.message : String(error);
287
+ logger.debug("trust_store_certificate_parse_failed", { reason });
288
+ }
75
289
  }
76
- // Parse DER-encoded certificate
77
- let cert;
78
- try {
79
- cert = asn1_schema_1.AsnConvert.parse(derBytes, asn1_x509_1.Certificate);
290
+ return parsed;
291
+ }
292
+ function extractPemBlocks(pem) {
293
+ const blocks = [];
294
+ const regex = /-----BEGIN CERTIFICATE-----([\s\S]*?)-----END CERTIFICATE-----/gu;
295
+ let match;
296
+ // eslint-disable-next-line no-cond-assign
297
+ while ((match = regex.exec(pem)) !== null) {
298
+ const body = match[1] ?? "";
299
+ blocks.push(body.replace(/\s+/gu, ""));
80
300
  }
81
- catch (error) {
82
- throw new Error(`Failed to parse X.509 certificate: ${error instanceof Error ? error.message : String(error)}`);
83
- }
84
- // Basic temporal validity check
85
- const now = Date.now(); // milliseconds since epoch
86
- const notBefore = cert.tbsCertificate.validity.notBefore;
87
- const notAfter = cert.tbsCertificate.validity.notAfter;
88
- // Time from ASN.1 has getTime() method returning Date object
89
- const notBeforeDate = notBefore.getTime();
90
- const notAfterDate = notAfter.getTime();
91
- const notBeforeMs = notBeforeDate instanceof Date
92
- ? notBeforeDate.getTime()
93
- : Number(notBeforeDate);
94
- const notAfterMs = notAfterDate instanceof Date
95
- ? notAfterDate.getTime()
96
- : Number(notAfterDate);
97
- if (now < notBeforeMs || now > notAfterMs) {
98
- throw new Error(`Certificate is not currently valid (notBefore: ${new Date(notBeforeMs).toISOString()}, notAfter: ${new Date(notAfterMs).toISOString()}, now: ${new Date(now).toISOString()})`);
99
- }
100
- // TODO: Implement name constraints validation when enforceNameConstraints is true
101
- if (options.enforceNameConstraints) {
102
- logger.debug("name_constraints_validation_not_implemented", {
103
- enforcement_requested: true,
104
- });
301
+ return blocks;
302
+ }
303
+ function validateChainContinuity(chain) {
304
+ if (chain.length <= 1) {
305
+ return;
105
306
  }
106
- // TODO: Implement trust store validation when trustStorePem is provided
107
- if (options.trustStorePem) {
108
- logger.debug("trust_store_validation_not_implemented", {
109
- has_trust_store: true,
110
- });
307
+ logger.debug("validating_chain_continuity", { chain_length: chain.length });
308
+ for (let index = 0; index < chain.length - 1; index += 1) {
309
+ const cert = chain[index];
310
+ const issuer = chain[index + 1];
311
+ if (!namesEqual(cert.certificate.tbsCertificate.issuer, issuer.certificate.tbsCertificate.subject)) {
312
+ logger.warning("certificate_chain_continuity_failed", {
313
+ cert_index: index,
314
+ cert_subject: cert.subjectName,
315
+ cert_issuer: cert.issuerName,
316
+ expected_issuer_subject: issuer.subjectName,
317
+ reason: "issuer_name_mismatch",
318
+ });
319
+ throw new Error(`Certificate chain continuity broken: certificate at index ${index} issuer does not match next certificate subject`);
320
+ }
321
+ try {
322
+ verifyCertificateSignature(cert.certificate, issuer.certificate);
323
+ logger.debug("chain_continuity_verification_success", {
324
+ cert_index: index,
325
+ cert_serial: cert.serialNumber,
326
+ issuer_serial: issuer.serialNumber,
327
+ });
328
+ }
329
+ catch (error) {
330
+ const reason = error instanceof Error ? error.message : String(error);
331
+ logger.warning("certificate_chain_continuity_failed", {
332
+ cert_index: index,
333
+ cert_subject: cert.subjectName,
334
+ issuer_subject: issuer.subjectName,
335
+ cert_serial: cert.serialNumber,
336
+ issuer_serial: issuer.serialNumber,
337
+ error: reason,
338
+ reason: "signature_verification_failed",
339
+ });
340
+ throw new Error(`Certificate chain continuity broken: certificate at index ${index} was not signed by certificate at index ${index + 1}: ${reason}`);
341
+ }
342
+ }
343
+ logger.debug("chain_continuity_validation_passed", {
344
+ chain_length: chain.length,
345
+ });
346
+ }
347
+ function verifyCertificateSignature(certificate, issuer) {
348
+ ensureEd25519Support();
349
+ const signatureAlgorithm = certificate.signatureAlgorithm.algorithm;
350
+ const issuerAlgorithm = issuer.tbsCertificate.subjectPublicKeyInfo.algorithm.algorithm;
351
+ if (signatureAlgorithm !== OID_ED25519 || issuerAlgorithm !== OID_ED25519) {
352
+ throw new Error(`Unsupported signature algorithm (certificate: ${signatureAlgorithm}, issuer: ${issuerAlgorithm})`);
353
+ }
354
+ const signatureBytes = new Uint8Array(certificate.signatureValue);
355
+ const tbsBytes = new Uint8Array(asn1_schema_1.AsnConvert.serialize(certificate.tbsCertificate));
356
+ const issuerKey = new Uint8Array(issuer.tbsCertificate.subjectPublicKeyInfo.subjectPublicKey);
357
+ if (issuerKey.length !== 32) {
358
+ throw new Error("Issuer Ed25519 public key must be 32 bytes");
359
+ }
360
+ const valid = (0, ed25519_1.verify)(signatureBytes, tbsBytes, issuerKey);
361
+ if (!valid) {
362
+ throw new Error("Certificate signature verification failed");
363
+ }
364
+ }
365
+ function ensureEd25519Support() {
366
+ const etcPatch = ed25519_1.etc;
367
+ if (!etcPatch.sha512) {
368
+ etcPatch.sha512 = (message) => (0, sha2_js_1.sha512)(message);
369
+ }
370
+ if (!etcPatch.sha512Sync) {
371
+ etcPatch.sha512Sync = (...messages) => {
372
+ if (messages.length === 1) {
373
+ return (0, sha2_js_1.sha512)(messages[0]);
374
+ }
375
+ const combined = ed25519_1.etc.concatBytes(...messages);
376
+ return (0, sha2_js_1.sha512)(combined);
377
+ };
378
+ }
379
+ }
380
+ function findExtension(certificate, oid) {
381
+ const extensions = certificate.tbsCertificate.extensions;
382
+ if (!extensions) {
383
+ return null;
384
+ }
385
+ for (const extension of extensions) {
386
+ if (extension.extnID === oid) {
387
+ return extension;
388
+ }
389
+ }
390
+ return null;
391
+ }
392
+ function namesEqual(a, b) {
393
+ const left = new Uint8Array(asn1_schema_1.AsnConvert.serialize(a));
394
+ const right = new Uint8Array(asn1_schema_1.AsnConvert.serialize(b));
395
+ if (left.length !== right.length) {
396
+ return false;
397
+ }
398
+ for (let i = 0; i < left.length; i += 1) {
399
+ if (left[i] !== right[i]) {
400
+ return false;
401
+ }
402
+ }
403
+ return true;
404
+ }
405
+ function serializeName(name) {
406
+ const rdns = Array.from(name);
407
+ return rdns
408
+ .map((rdn) => Array.from(rdn)
409
+ .map((attr) => `${oidToLabel(attr.type)}=${attr.value.toString()}`)
410
+ .join("+"))
411
+ .join(",");
412
+ }
413
+ function oidToLabel(oid) {
414
+ switch (oid) {
415
+ case "2.5.4.3":
416
+ return "CN";
417
+ case "2.5.4.6":
418
+ return "C";
419
+ case "2.5.4.7":
420
+ return "L";
421
+ case "2.5.4.8":
422
+ return "ST";
423
+ case "2.5.4.10":
424
+ return "O";
425
+ case "2.5.4.11":
426
+ return "OU";
427
+ default:
428
+ return oid;
429
+ }
430
+ }
431
+ function concatBytes(chunks) {
432
+ const totalLength = chunks.reduce((sum, chunk) => sum + chunk.length, 0);
433
+ const result = new Uint8Array(totalLength);
434
+ let offset = 0;
435
+ for (const chunk of chunks) {
436
+ result.set(chunk, offset);
437
+ offset += chunk.length;
438
+ }
439
+ return result;
440
+ }
441
+ function decodeBase64(input) {
442
+ if (typeof Buffer !== "undefined") {
443
+ const normalized = input.replace(/\s+/gu, "");
444
+ return new Uint8Array(Buffer.from(normalized, "base64"));
445
+ }
446
+ if (typeof atob === "function") {
447
+ const normalized = input.replace(/\s+/gu, "");
448
+ const binary = atob(normalized);
449
+ const bytes = new Uint8Array(binary.length);
450
+ for (let i = 0; i < binary.length; i += 1) {
451
+ bytes[i] = binary.charCodeAt(i);
452
+ }
453
+ return bytes;
454
+ }
455
+ throw new Error("No base64 decoder available in this environment");
456
+ }
457
+ function toHex(data) {
458
+ return Array.from(data)
459
+ .map((byte) => byte.toString(16).padStart(2, "0"))
460
+ .join("");
461
+ }
462
+ function buildCacheKey(chainBytes, trustStorePem, enforceNameConstraints) {
463
+ const chainHash = toHex((0, sha256_1.sha256)(chainBytes));
464
+ const trustHash = trustStorePem
465
+ ? toHex((0, sha256_1.sha256)(textEncoder.encode(trustStorePem)))
466
+ : "no-trust";
467
+ const constraintFlag = enforceNameConstraints ? "nc1" : "nc0";
468
+ return `${chainHash}|${trustHash}|${constraintFlag}`;
469
+ }
470
+ function getCachedPublicKey(cacheKey) {
471
+ const entry = trustCache.get(cacheKey);
472
+ if (!entry) {
473
+ return null;
474
+ }
475
+ if (Date.now() > entry.expiresAt) {
476
+ trustCache.delete(cacheKey);
477
+ logger.debug("certificate_cache_expired", { cache_key: cacheKey });
478
+ return null;
479
+ }
480
+ return entry.value.slice();
481
+ }
482
+ function setCachedPublicKey(cacheKey, value, notAfter) {
483
+ while (trustCache.size >= CACHE_LIMIT) {
484
+ const firstKey = trustCache.keys().next().value;
485
+ if (firstKey === undefined) {
486
+ break;
487
+ }
488
+ trustCache.delete(firstKey);
489
+ logger.debug("certificate_cache_evicted", { cache_key: firstKey });
490
+ }
491
+ trustCache.set(cacheKey, {
492
+ value: value.slice(),
493
+ expiresAt: notAfter.getTime(),
494
+ });
495
+ logger.debug("certificate_cache_stored", {
496
+ cache_key: cacheKey,
497
+ expires_at: notAfter.toISOString(),
498
+ cache_size: trustCache.size,
499
+ });
500
+ }
501
+ function normalizeTrustStoreOption(value) {
502
+ if (!value) {
503
+ return null;
504
+ }
505
+ const trimmed = value.trim();
506
+ if (trimmed.length === 0) {
507
+ return null;
111
508
  }
112
- // Extract public key from leaf certificate
113
- const publicKeyInfo = cert.tbsCertificate.subjectPublicKeyInfo;
114
- // For Ed25519, the subjectPublicKey is a BIT STRING containing the raw 32-byte public key
115
- // The BIT STRING is stored as an ArrayBuffer
116
- const publicKeyBitString = publicKeyInfo.subjectPublicKey;
117
- // Convert ArrayBuffer to Uint8Array
118
- return new Uint8Array(publicKeyBitString);
509
+ if (!trimmed.includes("-----BEGIN CERTIFICATE-----")) {
510
+ throw new Error("trustStorePem must contain PEM-encoded certificates when provided");
511
+ }
512
+ return normalizePem(trimmed);
513
+ }
514
+ function normalizePem(pem) {
515
+ return pem.replace(/\r/gu, "").trim();
516
+ }
517
+ function generateCallId() {
518
+ return Math.random().toString(36).slice(2, 10);
119
519
  }
120
520
  //# sourceMappingURL=util.js.map