node-opcua-pki 6.22.0 → 7.0.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 (137) hide show
  1. package/bin/install_prerequisite.ts +1 -1
  2. package/bin/pki.ts +1 -1
  3. package/dist/bin/install_prerequisite.d.ts +2 -0
  4. package/dist/bin/install_prerequisite.js +6 -0
  5. package/dist/bin/install_prerequisite.js.map +1 -0
  6. package/dist/bin/pki.d.ts +2 -0
  7. package/dist/bin/pki.js +4 -0
  8. package/dist/bin/pki.js.map +1 -0
  9. package/dist/lib/ca/backends/native_ca_backend.d.ts +49 -0
  10. package/dist/lib/ca/backends/native_ca_backend.js +365 -0
  11. package/dist/lib/ca/backends/native_ca_backend.js.map +1 -0
  12. package/dist/lib/ca/backends/openssl_ca_backend.d.ts +47 -0
  13. package/dist/lib/ca/backends/openssl_ca_backend.js +398 -0
  14. package/dist/lib/ca/backends/openssl_ca_backend.js.map +1 -0
  15. package/dist/lib/ca/certificate_authority.d.ts +62 -0
  16. package/dist/lib/ca/certificate_authority.js +91 -0
  17. package/dist/lib/ca/certificate_authority.js.map +1 -0
  18. package/dist/lib/ca/core/ca_backend.d.ts +89 -0
  19. package/dist/lib/ca/core/ca_backend.js +24 -0
  20. package/dist/lib/ca/core/ca_backend.js.map +1 -0
  21. package/dist/lib/ca/core/ca_database.d.ts +91 -0
  22. package/dist/lib/ca/core/ca_database.js +267 -0
  23. package/dist/lib/ca/core/ca_database.js.map +1 -0
  24. package/dist/{core-BwqSJPHc.d.mts → lib/ca/core/certificate_authority_core.d.ts} +45 -394
  25. package/dist/lib/ca/core/certificate_authority_core.js +1300 -0
  26. package/dist/lib/ca/core/certificate_authority_core.js.map +1 -0
  27. package/dist/lib/ca/core/index.d.ts +17 -0
  28. package/dist/lib/ca/core/index.js +25 -0
  29. package/dist/lib/ca/core/index.js.map +1 -0
  30. package/dist/lib/ca/crypto_create_CA.d.ts +1 -0
  31. package/dist/lib/ca/crypto_create_CA.js +821 -0
  32. package/dist/lib/ca/crypto_create_CA.js.map +1 -0
  33. package/dist/lib/ca/index.d.ts +1 -0
  34. package/dist/lib/ca/index.js +2 -0
  35. package/dist/lib/ca/index.js.map +1 -0
  36. package/dist/lib/ca/templates/ca_config_template.cnf.d.ts +2 -0
  37. package/dist/lib/ca/templates/ca_config_template.cnf.js +170 -0
  38. package/dist/lib/ca/templates/ca_config_template.cnf.js.map +1 -0
  39. package/dist/lib/core.d.ts +33 -0
  40. package/dist/lib/core.js +56 -0
  41. package/dist/lib/core.js.map +1 -0
  42. package/dist/lib/index.d.ts +11 -0
  43. package/dist/lib/index.js +37 -0
  44. package/dist/lib/index.js.map +1 -0
  45. package/dist/lib/misc/applicationurn.d.ts +1 -0
  46. package/dist/lib/misc/applicationurn.js +40 -0
  47. package/dist/lib/misc/applicationurn.js.map +1 -0
  48. package/dist/lib/misc/hostname.d.ts +8 -0
  49. package/dist/lib/misc/hostname.js +82 -0
  50. package/dist/lib/misc/hostname.js.map +1 -0
  51. package/dist/lib/misc/subject.d.ts +2 -0
  52. package/dist/lib/misc/subject.js +2 -0
  53. package/dist/lib/misc/subject.js.map +1 -0
  54. package/dist/{index.d.ts → lib/pki/certificate_manager.d.ts} +24 -246
  55. package/dist/lib/pki/certificate_manager.js +2302 -0
  56. package/dist/lib/pki/certificate_manager.js.map +1 -0
  57. package/dist/lib/pki/templates/simple_config_template.cnf.d.ts +2 -0
  58. package/dist/lib/pki/templates/simple_config_template.cnf.js +74 -0
  59. package/dist/lib/pki/templates/simple_config_template.cnf.js.map +1 -0
  60. package/dist/lib/pki/toolbox_pfx.d.ts +127 -0
  61. package/dist/lib/pki/toolbox_pfx.js +248 -0
  62. package/dist/lib/pki/toolbox_pfx.js.map +1 -0
  63. package/dist/lib/toolbox/common.d.ts +150 -0
  64. package/dist/lib/toolbox/common.js +82 -0
  65. package/dist/lib/toolbox/common.js.map +1 -0
  66. package/dist/lib/toolbox/common2.d.ts +25 -0
  67. package/dist/lib/toolbox/common2.js +97 -0
  68. package/dist/lib/toolbox/common2.js.map +1 -0
  69. package/dist/lib/toolbox/config.d.ts +5 -0
  70. package/dist/lib/toolbox/config.js +28 -0
  71. package/dist/lib/toolbox/config.js.map +1 -0
  72. package/dist/lib/toolbox/debug.d.ts +5 -0
  73. package/dist/lib/toolbox/debug.js +35 -0
  74. package/dist/lib/toolbox/debug.js.map +1 -0
  75. package/dist/lib/toolbox/display.d.ts +4 -0
  76. package/dist/lib/toolbox/display.js +56 -0
  77. package/dist/lib/toolbox/display.js.map +1 -0
  78. package/dist/lib/toolbox/index.d.ts +5 -0
  79. package/dist/lib/toolbox/index.js +28 -0
  80. package/dist/lib/toolbox/index.js.map +1 -0
  81. package/dist/lib/toolbox/with_openssl/_create_random_file.d.ts +4 -0
  82. package/dist/lib/toolbox/with_openssl/_create_random_file.js +50 -0
  83. package/dist/lib/toolbox/with_openssl/_create_random_file.js.map +1 -0
  84. package/dist/lib/toolbox/with_openssl/_env.d.ts +58 -0
  85. package/dist/lib/toolbox/with_openssl/_env.js +126 -0
  86. package/dist/lib/toolbox/with_openssl/_env.js.map +1 -0
  87. package/dist/lib/toolbox/with_openssl/create_certificate_signing_request.d.ts +5 -0
  88. package/dist/lib/toolbox/with_openssl/create_certificate_signing_request.js +72 -0
  89. package/dist/lib/toolbox/with_openssl/create_certificate_signing_request.js.map +1 -0
  90. package/dist/lib/toolbox/with_openssl/create_private_key.d.ts +5 -0
  91. package/dist/lib/toolbox/with_openssl/create_private_key.js +92 -0
  92. package/dist/lib/toolbox/with_openssl/create_private_key.js.map +1 -0
  93. package/dist/lib/toolbox/with_openssl/create_self_signed_certificate.d.ts +5 -0
  94. package/dist/lib/toolbox/with_openssl/create_self_signed_certificate.js +135 -0
  95. package/dist/lib/toolbox/with_openssl/create_self_signed_certificate.js.map +1 -0
  96. package/dist/lib/toolbox/with_openssl/execute_openssl.d.ts +67 -0
  97. package/dist/lib/toolbox/with_openssl/execute_openssl.js +246 -0
  98. package/dist/lib/toolbox/with_openssl/execute_openssl.js.map +1 -0
  99. package/dist/lib/toolbox/with_openssl/index.d.ts +5 -0
  100. package/dist/lib/toolbox/with_openssl/index.js +30 -0
  101. package/dist/lib/toolbox/with_openssl/index.js.map +1 -0
  102. package/dist/lib/toolbox/with_openssl/install_prerequisite.d.ts +7 -0
  103. package/dist/lib/toolbox/with_openssl/install_prerequisite.js +358 -0
  104. package/dist/lib/toolbox/with_openssl/install_prerequisite.js.map +1 -0
  105. package/dist/lib/toolbox/with_openssl/toolbox.d.ts +53 -0
  106. package/dist/lib/toolbox/with_openssl/toolbox.js +190 -0
  107. package/dist/lib/toolbox/with_openssl/toolbox.js.map +1 -0
  108. package/dist/lib/toolbox/without_openssl/create_certificate_signing_request.d.ts +5 -0
  109. package/dist/lib/toolbox/without_openssl/create_certificate_signing_request.js +69 -0
  110. package/dist/lib/toolbox/without_openssl/create_certificate_signing_request.js.map +1 -0
  111. package/dist/lib/toolbox/without_openssl/create_self_signed_certificate.d.ts +3 -0
  112. package/dist/lib/toolbox/without_openssl/create_self_signed_certificate.js +78 -0
  113. package/dist/lib/toolbox/without_openssl/create_self_signed_certificate.js.map +1 -0
  114. package/dist/lib/toolbox/without_openssl/index.d.ts +2 -0
  115. package/dist/lib/toolbox/without_openssl/index.js +25 -0
  116. package/dist/lib/toolbox/without_openssl/index.js.map +1 -0
  117. package/package.json +11 -30
  118. package/dist/bin/install_prerequisite.mjs +0 -18
  119. package/dist/bin/install_prerequisite.mjs.map +0 -1
  120. package/dist/bin/pki.mjs +0 -5895
  121. package/dist/bin/pki.mjs.map +0 -1
  122. package/dist/chunk-EQ4DGI4M.mjs +0 -1917
  123. package/dist/chunk-EQ4DGI4M.mjs.map +0 -1
  124. package/dist/chunk-GCHH54PS.mjs +0 -30
  125. package/dist/chunk-GCHH54PS.mjs.map +0 -1
  126. package/dist/core-BwqSJPHc.d.ts +0 -1041
  127. package/dist/core.d.mts +0 -2
  128. package/dist/core.d.ts +0 -2
  129. package/dist/core.js +0 -1902
  130. package/dist/core.js.map +0 -1
  131. package/dist/core.mjs +0 -21
  132. package/dist/core.mjs.map +0 -1
  133. package/dist/index.d.mts +0 -1001
  134. package/dist/index.js +0 -4968
  135. package/dist/index.js.map +0 -1
  136. package/dist/index.mjs +0 -3106
  137. package/dist/index.mjs.map +0 -1
@@ -0,0 +1,89 @@
1
+ import type { Params, ProcessAltNamesParam } from "../../toolbox/index.js";
2
+ import type { CertificateAuthorityCore } from "./certificate_authority_core.js";
3
+ /**
4
+ * Everything a {@link CertificateAuthorityCore} delegates to a signing backend:
5
+ * the operations that need the CA's private key, or that mutate the CA's
6
+ * certificate database (`index.txt`, `serial`, `crlnumber`,
7
+ * `certs/<SERIAL>.pem`, `crl/revocation_list.*`). Everything else — directory
8
+ * layout, PEM/DER file concatenation, `index.txt` *reading*, URL validation,
9
+ * passphrase handling, `initializeCSR`'s state machine — stays in
10
+ * {@link CertificateAuthorityCore} and is shared by every backend.
11
+ *
12
+ * Two implementations exist. `OpenSslCaBackend` shells out to the `openssl`
13
+ * CLI exactly as `CertificateAuthority` always has, and remains the default.
14
+ * `NativeCaBackend` does the same work in pure JS, which is what allows a
15
+ * CA key held by an HSM or KMS - one that only signs and never exposes key
16
+ * material - to be used at all: the openssl CLI can only load a key from a
17
+ * file. Both write the same on-disk database format, so an install can move
18
+ * between them and external `openssl` tooling keeps working either way.
19
+ */
20
+ export interface CaBackend {
21
+ /**
22
+ * Whether this backend can sign with an external {@link CaSigner} - an
23
+ * HSM or KMS key that only signs and never exposes key material.
24
+ * `false` for a backend that can only load a key from a file, so the
25
+ * CA can refuse the combination up front instead of failing later.
26
+ */
27
+ readonly supportsExternalSigner: boolean;
28
+ /**
29
+ * Check whatever this backend needs before it can do any work, and
30
+ * throw if it is missing. `CertificateAuthority` calls it before
31
+ * delegating, so a missing prerequisite is reported once, up front,
32
+ * rather than as a failure part-way through an operation.
33
+ *
34
+ * This exists because the requirement is the backend's, not the CA's:
35
+ * the openssl backend needs the `openssl` executable on PATH, and the
36
+ * native one needs nothing at all. Asking the CA to know that is what
37
+ * made a `backend: "native"` CA refuse to issue a certificate on a
38
+ * machine without openssl installed.
39
+ */
40
+ preflight(): Promise<void>;
41
+ /**
42
+ * Generate the CA's private key (if missing) and its certificate:
43
+ * self-signed for a root CA, signed by `ca._issuerCA` for a subordinate.
44
+ * Also produces the initial CRL. Called once, from
45
+ * {@link CertificateAuthority.initialize}.
46
+ */
47
+ bootstrap(ca: CertificateAuthorityCore): Promise<void>;
48
+ /**
49
+ * Generate a CSR for the CA's own key (`[v3_ca_req]` profile) — used by
50
+ * `initializeCSR`/`renewCSR` when the CA certificate is signed
51
+ * externally.
52
+ */
53
+ generateCaCsr(ca: CertificateAuthorityCore, caRootDir: string, privateKeyFile: string, csrFile: string): Promise<void>;
54
+ /**
55
+ * Sign a subordinate CA's CSR with this CA's key (`[v3_ca]` profile).
56
+ * Used by {@link CertificateAuthority.signCACertificateRequest}.
57
+ */
58
+ signSubordinateCsr(ca: CertificateAuthorityCore, csrFile: string, certFile: string, validityDays: number): Promise<void>;
59
+ /**
60
+ * Sign an end-entity CSR with this CA's key (`[usr_cert]` profile via
61
+ * `openssl ca`) — the certificate database (`index.txt`, `serial`,
62
+ * `certs/<SERIAL>.pem`) is updated as a side effect. Used by
63
+ * {@link CertificateAuthority.signCertificateRequest}.
64
+ *
65
+ * `sanOverride` is the applicationUri/dns/ip the caller already
66
+ * re-derived from the CSR itself (SANs are not copied by `openssl ca`
67
+ * — see the comment at the call site), so the backend can render the
68
+ * `subjectAltName` extension without re-parsing the CSR.
69
+ */
70
+ signEndEntityCsr(ca: CertificateAuthorityCore, certificate: string, csr: string, params: Params, sanOverride: Required<ProcessAltNamesParam>): Promise<void>;
71
+ /**
72
+ * Mark a certificate revoked in the database and regenerate the CRL.
73
+ * Used by {@link CertificateAuthority.revokeCertificate}.
74
+ */
75
+ revoke(ca: CertificateAuthorityCore, certificate: string, reason: string): Promise<void>;
76
+ /**
77
+ * Regenerate `crl/revocation_list.{crl,der}` from the current database
78
+ * state, without changing any certificate's status. Used after
79
+ * {@link CertificateAuthority.installCACertificate} installs an
80
+ * externally-signed CA certificate.
81
+ */
82
+ regenerateCrl(ca: CertificateAuthorityCore): Promise<void>;
83
+ /**
84
+ * Legacy CLI self-signed certificate creation (`openssl req` +
85
+ * `openssl ca -selfsign`). Used by
86
+ * {@link CertificateAuthority.createSelfSignedCertificate}.
87
+ */
88
+ createSelfSignedCertificate(ca: CertificateAuthorityCore, certificateFile: string, privateKeyFile: string, params: Params): Promise<void>;
89
+ }
@@ -0,0 +1,24 @@
1
+ // ---------------------------------------------------------------------------------------------------------------------
2
+ // node-opcua-pki
3
+ // ---------------------------------------------------------------------------------------------------------------------
4
+ // Copyright (c) 2014-2026 - Etienne Rossignon - etienne.rossignon (at) gadz.org
5
+ // Copyright (c) 2022-2026 - Sterfive.com
6
+ // ---------------------------------------------------------------------------------------------------------------------
7
+ //
8
+ // This project is licensed under the terms of the MIT license.
9
+ //
10
+ // Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
11
+ // documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
12
+ // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
13
+ // permit persons to whom the Software is furnished to do so, subject to the following conditions:
14
+ //
15
+ // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
16
+ // Software.
17
+ //
18
+ // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
19
+ // WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
20
+ // COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
21
+ // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
+ // ---------------------------------------------------------------------------------------------------------------------
23
+ export {};
24
+ //# sourceMappingURL=ca_backend.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ca_backend.js","sourceRoot":"","sources":["../../../../lib/ca/core/ca_backend.ts"],"names":[],"mappings":"AAAA,wHAAwH;AACxH,iBAAiB;AACjB,wHAAwH;AACxH,gFAAgF;AAChF,yCAAyC;AACzC,wHAAwH;AACxH,EAAE;AACF,gEAAgE;AAChE,EAAE;AACF,+GAA+G;AAC/G,sHAAsH;AACtH,kHAAkH;AAClH,mGAAmG;AACnG,EAAE;AACF,uHAAuH;AACvH,YAAY;AACZ,EAAE;AACF,uHAAuH;AACvH,wHAAwH;AACxH,mHAAmH;AACnH,mHAAmH;AACnH,wHAAwH"}
@@ -0,0 +1,91 @@
1
+ /**
2
+ * A record from the OpenSSL CA certificate database (`index.txt`).
3
+ */
4
+ export interface IssuedCertificateRecord {
5
+ /** Hex-encoded serial number (e.g. `"1000"`). */
6
+ serial: string;
7
+ /** Certificate status. */
8
+ status: "valid" | "revoked" | "expired";
9
+ /** X.500 subject string (slash-delimited). */
10
+ subject: string;
11
+ /** Certificate expiry date as ISO-8601 string. */
12
+ expiryDate: string;
13
+ /**
14
+ * Revocation date as ISO-8601 string.
15
+ * Only present when `status === "revoked"`.
16
+ */
17
+ revocationDate?: string;
18
+ /**
19
+ * CRL revocation reason (e.g. `"keyCompromise"`), as stored after the
20
+ * comma in the revocation-date field. Only present when
21
+ * `status === "revoked"` and a reason was recorded.
22
+ */
23
+ reason?: string;
24
+ }
25
+ /**
26
+ * Read and write access to a CA's OpenSSL-format certificate database
27
+ * (`index.txt`, `serial`, `crlnumber`, `certs/<SERIAL>.pem`).
28
+ *
29
+ * This is the format `openssl ca` itself writes and reads; every mutating
30
+ * method here (`nextSerial`, `nextCrlNumber`, `appendIssued`, `markRevoked`,
31
+ * `storeCertificate`) exists so a native (non-openssl) signing backend can
32
+ * write the exact same on-disk format `openssl ca` does, so an install can
33
+ * move between backends and external `openssl` tooling keeps working on the
34
+ * same directory. Callers are responsible for serializing access — every
35
+ * mutating call must happen under the owning CA's directory lock
36
+ * (`CertificateAuthority._withCaDirectoryLock`), the same guarantee
37
+ * `OpenSslCaBackend` gets from the lock around each `openssl ca` /
38
+ * `openssl x509 -CAserial` invocation.
39
+ */
40
+ export declare class CaDatabase {
41
+ #private;
42
+ constructor(rootDir: string);
43
+ /** Path to the OpenSSL certificate database file (`index.txt`). */
44
+ get indexFile(): string;
45
+ /**
46
+ * Parse the OpenSSL `index.txt` certificate database.
47
+ *
48
+ * Each line has tab-separated fields:
49
+ * ```
50
+ * status expiry [revocationDate] serial unknown subject
51
+ * ```
52
+ *
53
+ * - status: `V` (valid), `R` (revoked), `E` (expired)
54
+ * - expiry: `YYMMDDHHmmssZ`
55
+ * - revocationDate: present only for revoked certs
56
+ * - serial: hex string
57
+ * - unknown: always `"unknown"`
58
+ * - subject: X.500 slash-delimited string
59
+ */
60
+ readIndex(): IssuedCertificateRecord[];
61
+ /** Look up one record by serial number (case-insensitive). */
62
+ findBySerial(serial: string): IssuedCertificateRecord | undefined;
63
+ /**
64
+ * Read a specific issued certificate by serial number.
65
+ *
66
+ * OpenSSL stores signed certificates in the `certs/` directory using
67
+ * the naming convention `<SERIAL>.pem`.
68
+ *
69
+ * @param serial - hex-encoded serial number (e.g. `"1000"`)
70
+ * @returns the DER buffer, or `undefined` if not found
71
+ */
72
+ getCertificateBySerial(serial: string): Buffer | undefined;
73
+ /** Hand out the next certificate serial number, bumping the `serial` file. */
74
+ nextSerial(): string;
75
+ /** Hand out the next CRL number, bumping the `crlnumber` file. */
76
+ nextCrlNumber(): string;
77
+ /** Append a newly-issued certificate's `V` (valid) row to `index.txt`. */
78
+ appendIssued(record: {
79
+ serial: string;
80
+ expiryDate: Date;
81
+ subject: string;
82
+ }): void;
83
+ /**
84
+ * Rewrite a certificate's `index.txt` row from `V` to `R` (revoked).
85
+ * Throws if the serial is not found, or is already revoked — the same
86
+ * "already revoked" case `openssl ca -revoke` itself rejects.
87
+ */
88
+ markRevoked(serial: string, revocationDate: Date, reason: string): void;
89
+ /** Store a signed certificate's PEM under `certs/<SERIAL>.pem`, as `openssl ca` does. */
90
+ storeCertificate(serial: string, pem: string): void;
91
+ }
@@ -0,0 +1,267 @@
1
+ // ---------------------------------------------------------------------------------------------------------------------
2
+ // node-opcua-pki
3
+ // ---------------------------------------------------------------------------------------------------------------------
4
+ // Copyright (c) 2014-2026 - Etienne Rossignon - etienne.rossignon (at) gadz.org
5
+ // Copyright (c) 2022-2026 - Sterfive.com
6
+ // ---------------------------------------------------------------------------------------------------------------------
7
+ //
8
+ // This project is licensed under the terms of the MIT license.
9
+ //
10
+ // Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
11
+ // documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
12
+ // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
13
+ // permit persons to whom the Software is furnished to do so, subject to the following conditions:
14
+ //
15
+ // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
16
+ // Software.
17
+ //
18
+ // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
19
+ // WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
20
+ // COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
21
+ // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
+ // ---------------------------------------------------------------------------------------------------------------------
23
+ import fs from "node:fs";
24
+ import path from "node:path";
25
+ import { convertPEMtoDER, readCertificatePEM } from "node-opcua-crypto";
26
+ /**
27
+ * Parse an OpenSSL date string (`YYMMDDHHmmssZ`) into an ISO-8601 string.
28
+ */
29
+ function parseOpenSSLDate(dateStr) {
30
+ // Revocation dates may have a reason suffix: "YYMMDDHHmmssZ,reason"
31
+ // Strip anything after the first comma.
32
+ const raw = dateStr?.split(",")[0] ?? "";
33
+ if (raw.length < 12)
34
+ return "";
35
+ // OpenSSL uses 2-digit year; 70+ is 19xx, <70 is 20xx
36
+ const yy = parseInt(raw.substring(0, 2), 10);
37
+ const year = yy >= 70 ? 1900 + yy : 2000 + yy;
38
+ const month = raw.substring(2, 4);
39
+ const day = raw.substring(4, 6);
40
+ const hour = raw.substring(6, 8);
41
+ const min = raw.substring(8, 10);
42
+ const sec = raw.substring(10, 12);
43
+ return `${year}-${month}-${day}T${hour}:${min}:${sec}Z`;
44
+ }
45
+ /** The inverse of {@link parseOpenSSLDate}: format a `Date` as `YYMMDDHHmmssZ` (UTC). */
46
+ function formatOpenSSLDate(date) {
47
+ const yy = String(date.getUTCFullYear() % 100).padStart(2, "0");
48
+ const mm = String(date.getUTCMonth() + 1).padStart(2, "0");
49
+ const dd = String(date.getUTCDate()).padStart(2, "0");
50
+ const hh = String(date.getUTCHours()).padStart(2, "0");
51
+ const mi = String(date.getUTCMinutes()).padStart(2, "0");
52
+ const ss = String(date.getUTCSeconds()).padStart(2, "0");
53
+ return `${yy}${mm}${dd}${hh}${mi}${ss}Z`;
54
+ }
55
+ /** Extract the reason suffix (after the first comma) from a revocation-date field, if any. */
56
+ function parseRevocationReason(dateStr) {
57
+ const parts = dateStr?.split(",");
58
+ return parts && parts.length > 1 ? parts[1] : undefined;
59
+ }
60
+ /** Pad a hex string to an even number of digits, matching OpenSSL's `serial`/`crlnumber` file convention. */
61
+ function evenLengthHex(value) {
62
+ const hex = value.toString(16).toUpperCase();
63
+ return hex.length % 2 === 0 ? hex : `0${hex}`;
64
+ }
65
+ /**
66
+ * Escape every control character (and backslash) as `\xHH`, the way
67
+ * OpenSSL's `X509_NAME_oneline` does before writing a subject to
68
+ * `index.txt`. `index.txt` is a tab/newline-delimited table: a subject
69
+ * (which comes verbatim from the requester's CSR, so is attacker
70
+ * controlled) containing a raw `\n` or `\t` would otherwise inject a
71
+ * forged row — e.g. an `R` entry naming a victim's serial, which the next
72
+ * CRL regeneration would then faithfully publish as revoked.
73
+ */
74
+ function escapeIndexField(value) {
75
+ // biome-ignore lint/suspicious/noControlCharactersInRegex: escaping control characters is the point
76
+ return value.replace(/[\x00-\x1f\x7f\\]/g, (c) => `\\x${c.charCodeAt(0).toString(16).toUpperCase().padStart(2, "0")}`);
77
+ }
78
+ /** A hex serial handed to a writer must be exactly that — never something that could carry a field or row separator. */
79
+ function assertHexSerial(serial) {
80
+ if (!/^[0-9A-Fa-f]+$/.test(serial)) {
81
+ throw new Error(`Invalid certificate serial number: ${JSON.stringify(serial)}`);
82
+ }
83
+ return serial.toUpperCase();
84
+ }
85
+ /**
86
+ * Read and write access to a CA's OpenSSL-format certificate database
87
+ * (`index.txt`, `serial`, `crlnumber`, `certs/<SERIAL>.pem`).
88
+ *
89
+ * This is the format `openssl ca` itself writes and reads; every mutating
90
+ * method here (`nextSerial`, `nextCrlNumber`, `appendIssued`, `markRevoked`,
91
+ * `storeCertificate`) exists so a native (non-openssl) signing backend can
92
+ * write the exact same on-disk format `openssl ca` does, so an install can
93
+ * move between backends and external `openssl` tooling keeps working on the
94
+ * same directory. Callers are responsible for serializing access — every
95
+ * mutating call must happen under the owning CA's directory lock
96
+ * (`CertificateAuthority._withCaDirectoryLock`), the same guarantee
97
+ * `OpenSslCaBackend` gets from the lock around each `openssl ca` /
98
+ * `openssl x509 -CAserial` invocation.
99
+ */
100
+ export class CaDatabase {
101
+ #rootDir;
102
+ constructor(rootDir) {
103
+ this.#rootDir = rootDir;
104
+ }
105
+ /** Path to the OpenSSL certificate database file (`index.txt`). */
106
+ get indexFile() {
107
+ return path.join(this.#rootDir, "index.txt");
108
+ }
109
+ /**
110
+ * Parse the OpenSSL `index.txt` certificate database.
111
+ *
112
+ * Each line has tab-separated fields:
113
+ * ```
114
+ * status expiry [revocationDate] serial unknown subject
115
+ * ```
116
+ *
117
+ * - status: `V` (valid), `R` (revoked), `E` (expired)
118
+ * - expiry: `YYMMDDHHmmssZ`
119
+ * - revocationDate: present only for revoked certs
120
+ * - serial: hex string
121
+ * - unknown: always `"unknown"`
122
+ * - subject: X.500 slash-delimited string
123
+ */
124
+ readIndex() {
125
+ const indexPath = this.indexFile;
126
+ if (!fs.existsSync(indexPath)) {
127
+ return [];
128
+ }
129
+ const content = fs.readFileSync(indexPath, "utf-8");
130
+ const lines = content.split("\n").filter((l) => l.trim().length > 0);
131
+ const records = [];
132
+ for (const line of lines) {
133
+ const fields = line.split("\t");
134
+ if (fields.length < 4)
135
+ continue;
136
+ const statusChar = fields[0];
137
+ const expiryStr = fields[1];
138
+ let serial;
139
+ let subject;
140
+ let revocationDate;
141
+ if (statusChar === "R") {
142
+ // Revoked: status expiry revocationDate serial unknown subject
143
+ revocationDate = fields[2];
144
+ serial = fields[3];
145
+ subject = fields.length >= 6 ? fields[5] : "";
146
+ }
147
+ else {
148
+ // Valid/Expired: status expiry (empty) serial unknown subject
149
+ serial = fields[3];
150
+ subject = fields.length >= 6 ? fields[5] : "";
151
+ }
152
+ let status;
153
+ switch (statusChar) {
154
+ case "V":
155
+ status = "valid";
156
+ break;
157
+ case "R":
158
+ status = "revoked";
159
+ break;
160
+ case "E":
161
+ status = "expired";
162
+ break;
163
+ default:
164
+ continue; // skip unknown status
165
+ }
166
+ records.push({
167
+ serial,
168
+ status,
169
+ subject,
170
+ expiryDate: parseOpenSSLDate(expiryStr),
171
+ revocationDate: revocationDate ? parseOpenSSLDate(revocationDate) : undefined,
172
+ reason: revocationDate ? parseRevocationReason(revocationDate) : undefined
173
+ });
174
+ }
175
+ return records;
176
+ }
177
+ /** Look up one record by serial number (case-insensitive). */
178
+ findBySerial(serial) {
179
+ const upper = serial.toUpperCase();
180
+ return this.readIndex().find((r) => r.serial.toUpperCase() === upper);
181
+ }
182
+ /**
183
+ * Read a specific issued certificate by serial number.
184
+ *
185
+ * OpenSSL stores signed certificates in the `certs/` directory using
186
+ * the naming convention `<SERIAL>.pem`.
187
+ *
188
+ * @param serial - hex-encoded serial number (e.g. `"1000"`)
189
+ * @returns the DER buffer, or `undefined` if not found
190
+ */
191
+ getCertificateBySerial(serial) {
192
+ const upper = serial.toUpperCase();
193
+ const certFile = path.join(this.#rootDir, "certs", `${upper}.pem`);
194
+ if (!fs.existsSync(certFile)) {
195
+ return undefined;
196
+ }
197
+ const pem = readCertificatePEM(certFile);
198
+ return convertPEMtoDER(pem);
199
+ }
200
+ /**
201
+ * Read-increment-write a hex counter file (`serial`/`crlnumber`),
202
+ * returning the value to hand out — matching `openssl ca`'s own
203
+ * behavior: the file holds the *next* value to assign, and is bumped
204
+ * to the following one immediately after being read. A `.old` backup
205
+ * of the pre-bump content is kept, as `openssl` itself does.
206
+ */
207
+ #nextHexCounter(fileName) {
208
+ const counterFile = path.join(this.#rootDir, fileName);
209
+ const current = BigInt(`0x${fs.readFileSync(counterFile, "utf-8").trim()}`);
210
+ fs.copyFileSync(counterFile, `${counterFile}.old`);
211
+ fs.writeFileSync(counterFile, evenLengthHex(current + 1n));
212
+ return evenLengthHex(current);
213
+ }
214
+ /** Hand out the next certificate serial number, bumping the `serial` file. */
215
+ nextSerial() {
216
+ return this.#nextHexCounter("serial");
217
+ }
218
+ /** Hand out the next CRL number, bumping the `crlnumber` file. */
219
+ nextCrlNumber() {
220
+ return this.#nextHexCounter("crlnumber");
221
+ }
222
+ /** Append a newly-issued certificate's `V` (valid) row to `index.txt`. */
223
+ appendIssued(record) {
224
+ const serial = assertHexSerial(record.serial);
225
+ const subject = escapeIndexField(record.subject);
226
+ const line = `V\t${formatOpenSSLDate(record.expiryDate)}\t\t${serial}\tunknown\t${subject}\n`;
227
+ fs.appendFileSync(this.indexFile, line);
228
+ }
229
+ /**
230
+ * Rewrite a certificate's `index.txt` row from `V` to `R` (revoked).
231
+ * Throws if the serial is not found, or is already revoked — the same
232
+ * "already revoked" case `openssl ca -revoke` itself rejects.
233
+ */
234
+ markRevoked(serial, revocationDate, reason) {
235
+ const upper = assertHexSerial(serial);
236
+ if (!/^[A-Za-z]+$/.test(reason)) {
237
+ throw new Error(`Invalid CRL reason: ${JSON.stringify(reason)}`);
238
+ }
239
+ const lines = fs
240
+ .readFileSync(this.indexFile, "utf-8")
241
+ .split("\n")
242
+ .filter((l) => l.trim().length > 0);
243
+ let found = false;
244
+ const rewritten = lines.map((line) => {
245
+ const fields = line.split("\t");
246
+ if (fields.length < 4 || fields[3]?.toUpperCase() !== upper) {
247
+ return line;
248
+ }
249
+ if (fields[0] === "R") {
250
+ throw new Error(`Certificate ${upper} is already revoked`);
251
+ }
252
+ found = true;
253
+ const subject = fields.length >= 6 ? fields[5] : "";
254
+ return `R\t${fields[1]}\t${formatOpenSSLDate(revocationDate)},${reason}\t${upper}\tunknown\t${subject}`;
255
+ });
256
+ if (!found) {
257
+ throw new Error(`Certificate ${upper} not found in the certificate database`);
258
+ }
259
+ fs.writeFileSync(this.indexFile, `${rewritten.join("\n")}\n`);
260
+ }
261
+ /** Store a signed certificate's PEM under `certs/<SERIAL>.pem`, as `openssl ca` does. */
262
+ storeCertificate(serial, pem) {
263
+ const certFile = path.join(this.#rootDir, "certs", `${assertHexSerial(serial)}.pem`);
264
+ fs.writeFileSync(certFile, pem);
265
+ }
266
+ }
267
+ //# sourceMappingURL=ca_database.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ca_database.js","sourceRoot":"","sources":["../../../../lib/ca/core/ca_database.ts"],"names":[],"mappings":"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"}