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.
- package/bin/install_prerequisite.ts +1 -1
- package/bin/pki.ts +1 -1
- package/dist/bin/install_prerequisite.d.ts +2 -0
- package/dist/bin/install_prerequisite.js +6 -0
- package/dist/bin/install_prerequisite.js.map +1 -0
- package/dist/bin/pki.d.ts +2 -0
- package/dist/bin/pki.js +4 -0
- package/dist/bin/pki.js.map +1 -0
- package/dist/lib/ca/backends/native_ca_backend.d.ts +49 -0
- package/dist/lib/ca/backends/native_ca_backend.js +365 -0
- package/dist/lib/ca/backends/native_ca_backend.js.map +1 -0
- package/dist/lib/ca/backends/openssl_ca_backend.d.ts +47 -0
- package/dist/lib/ca/backends/openssl_ca_backend.js +398 -0
- package/dist/lib/ca/backends/openssl_ca_backend.js.map +1 -0
- package/dist/lib/ca/certificate_authority.d.ts +62 -0
- package/dist/lib/ca/certificate_authority.js +91 -0
- package/dist/lib/ca/certificate_authority.js.map +1 -0
- package/dist/lib/ca/core/ca_backend.d.ts +89 -0
- package/dist/lib/ca/core/ca_backend.js +24 -0
- package/dist/lib/ca/core/ca_backend.js.map +1 -0
- package/dist/lib/ca/core/ca_database.d.ts +91 -0
- package/dist/lib/ca/core/ca_database.js +267 -0
- package/dist/lib/ca/core/ca_database.js.map +1 -0
- package/dist/{core-BwqSJPHc.d.mts → lib/ca/core/certificate_authority_core.d.ts} +45 -394
- package/dist/lib/ca/core/certificate_authority_core.js +1300 -0
- package/dist/lib/ca/core/certificate_authority_core.js.map +1 -0
- package/dist/lib/ca/core/index.d.ts +17 -0
- package/dist/lib/ca/core/index.js +25 -0
- package/dist/lib/ca/core/index.js.map +1 -0
- package/dist/lib/ca/crypto_create_CA.d.ts +1 -0
- package/dist/lib/ca/crypto_create_CA.js +821 -0
- package/dist/lib/ca/crypto_create_CA.js.map +1 -0
- package/dist/lib/ca/index.d.ts +1 -0
- package/dist/lib/ca/index.js +2 -0
- package/dist/lib/ca/index.js.map +1 -0
- package/dist/lib/ca/templates/ca_config_template.cnf.d.ts +2 -0
- package/dist/lib/ca/templates/ca_config_template.cnf.js +170 -0
- package/dist/lib/ca/templates/ca_config_template.cnf.js.map +1 -0
- package/dist/lib/core.d.ts +33 -0
- package/dist/lib/core.js +56 -0
- package/dist/lib/core.js.map +1 -0
- package/dist/lib/index.d.ts +11 -0
- package/dist/lib/index.js +37 -0
- package/dist/lib/index.js.map +1 -0
- package/dist/lib/misc/applicationurn.d.ts +1 -0
- package/dist/lib/misc/applicationurn.js +40 -0
- package/dist/lib/misc/applicationurn.js.map +1 -0
- package/dist/lib/misc/hostname.d.ts +8 -0
- package/dist/lib/misc/hostname.js +82 -0
- package/dist/lib/misc/hostname.js.map +1 -0
- package/dist/lib/misc/subject.d.ts +2 -0
- package/dist/lib/misc/subject.js +2 -0
- package/dist/lib/misc/subject.js.map +1 -0
- package/dist/{index.d.ts → lib/pki/certificate_manager.d.ts} +24 -246
- package/dist/lib/pki/certificate_manager.js +2302 -0
- package/dist/lib/pki/certificate_manager.js.map +1 -0
- package/dist/lib/pki/templates/simple_config_template.cnf.d.ts +2 -0
- package/dist/lib/pki/templates/simple_config_template.cnf.js +74 -0
- package/dist/lib/pki/templates/simple_config_template.cnf.js.map +1 -0
- package/dist/lib/pki/toolbox_pfx.d.ts +127 -0
- package/dist/lib/pki/toolbox_pfx.js +248 -0
- package/dist/lib/pki/toolbox_pfx.js.map +1 -0
- package/dist/lib/toolbox/common.d.ts +150 -0
- package/dist/lib/toolbox/common.js +82 -0
- package/dist/lib/toolbox/common.js.map +1 -0
- package/dist/lib/toolbox/common2.d.ts +25 -0
- package/dist/lib/toolbox/common2.js +97 -0
- package/dist/lib/toolbox/common2.js.map +1 -0
- package/dist/lib/toolbox/config.d.ts +5 -0
- package/dist/lib/toolbox/config.js +28 -0
- package/dist/lib/toolbox/config.js.map +1 -0
- package/dist/lib/toolbox/debug.d.ts +5 -0
- package/dist/lib/toolbox/debug.js +35 -0
- package/dist/lib/toolbox/debug.js.map +1 -0
- package/dist/lib/toolbox/display.d.ts +4 -0
- package/dist/lib/toolbox/display.js +56 -0
- package/dist/lib/toolbox/display.js.map +1 -0
- package/dist/lib/toolbox/index.d.ts +5 -0
- package/dist/lib/toolbox/index.js +28 -0
- package/dist/lib/toolbox/index.js.map +1 -0
- package/dist/lib/toolbox/with_openssl/_create_random_file.d.ts +4 -0
- package/dist/lib/toolbox/with_openssl/_create_random_file.js +50 -0
- package/dist/lib/toolbox/with_openssl/_create_random_file.js.map +1 -0
- package/dist/lib/toolbox/with_openssl/_env.d.ts +58 -0
- package/dist/lib/toolbox/with_openssl/_env.js +126 -0
- package/dist/lib/toolbox/with_openssl/_env.js.map +1 -0
- package/dist/lib/toolbox/with_openssl/create_certificate_signing_request.d.ts +5 -0
- package/dist/lib/toolbox/with_openssl/create_certificate_signing_request.js +72 -0
- package/dist/lib/toolbox/with_openssl/create_certificate_signing_request.js.map +1 -0
- package/dist/lib/toolbox/with_openssl/create_private_key.d.ts +5 -0
- package/dist/lib/toolbox/with_openssl/create_private_key.js +92 -0
- package/dist/lib/toolbox/with_openssl/create_private_key.js.map +1 -0
- package/dist/lib/toolbox/with_openssl/create_self_signed_certificate.d.ts +5 -0
- package/dist/lib/toolbox/with_openssl/create_self_signed_certificate.js +135 -0
- package/dist/lib/toolbox/with_openssl/create_self_signed_certificate.js.map +1 -0
- package/dist/lib/toolbox/with_openssl/execute_openssl.d.ts +67 -0
- package/dist/lib/toolbox/with_openssl/execute_openssl.js +246 -0
- package/dist/lib/toolbox/with_openssl/execute_openssl.js.map +1 -0
- package/dist/lib/toolbox/with_openssl/index.d.ts +5 -0
- package/dist/lib/toolbox/with_openssl/index.js +30 -0
- package/dist/lib/toolbox/with_openssl/index.js.map +1 -0
- package/dist/lib/toolbox/with_openssl/install_prerequisite.d.ts +7 -0
- package/dist/lib/toolbox/with_openssl/install_prerequisite.js +358 -0
- package/dist/lib/toolbox/with_openssl/install_prerequisite.js.map +1 -0
- package/dist/lib/toolbox/with_openssl/toolbox.d.ts +53 -0
- package/dist/lib/toolbox/with_openssl/toolbox.js +190 -0
- package/dist/lib/toolbox/with_openssl/toolbox.js.map +1 -0
- package/dist/lib/toolbox/without_openssl/create_certificate_signing_request.d.ts +5 -0
- package/dist/lib/toolbox/without_openssl/create_certificate_signing_request.js +69 -0
- package/dist/lib/toolbox/without_openssl/create_certificate_signing_request.js.map +1 -0
- package/dist/lib/toolbox/without_openssl/create_self_signed_certificate.d.ts +3 -0
- package/dist/lib/toolbox/without_openssl/create_self_signed_certificate.js +78 -0
- package/dist/lib/toolbox/without_openssl/create_self_signed_certificate.js.map +1 -0
- package/dist/lib/toolbox/without_openssl/index.d.ts +2 -0
- package/dist/lib/toolbox/without_openssl/index.js +25 -0
- package/dist/lib/toolbox/without_openssl/index.js.map +1 -0
- package/package.json +11 -30
- package/dist/bin/install_prerequisite.mjs +0 -18
- package/dist/bin/install_prerequisite.mjs.map +0 -1
- package/dist/bin/pki.mjs +0 -5895
- package/dist/bin/pki.mjs.map +0 -1
- package/dist/chunk-EQ4DGI4M.mjs +0 -1917
- package/dist/chunk-EQ4DGI4M.mjs.map +0 -1
- package/dist/chunk-GCHH54PS.mjs +0 -30
- package/dist/chunk-GCHH54PS.mjs.map +0 -1
- package/dist/core-BwqSJPHc.d.ts +0 -1041
- package/dist/core.d.mts +0 -2
- package/dist/core.d.ts +0 -2
- package/dist/core.js +0 -1902
- package/dist/core.js.map +0 -1
- package/dist/core.mjs +0 -21
- package/dist/core.mjs.map +0 -1
- package/dist/index.d.mts +0 -1001
- package/dist/index.js +0 -4968
- package/dist/index.js.map +0 -1
- package/dist/index.mjs +0 -3106
- 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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"}
|