node-opcua-pki 6.8.2 → 6.9.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/dist/bin/pki.mjs +261 -6
- package/dist/bin/pki.mjs.map +1 -1
- package/dist/index.d.mts +129 -0
- package/dist/index.d.ts +129 -0
- package/dist/index.js +251 -0
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +263 -8
- package/dist/index.mjs.map +1 -1
- package/package.json +4 -4
- package/readme.md +78 -0
package/dist/index.d.mts
CHANGED
|
@@ -148,6 +148,25 @@ interface CertificateAuthorityOptions {
|
|
|
148
148
|
* await ca.initialize();
|
|
149
149
|
* ```
|
|
150
150
|
*/
|
|
151
|
+
/**
|
|
152
|
+
* A record from the OpenSSL CA certificate database
|
|
153
|
+
* (`index.txt`).
|
|
154
|
+
*/
|
|
155
|
+
interface IssuedCertificateRecord {
|
|
156
|
+
/** Hex-encoded serial number (e.g. `"1000"`). */
|
|
157
|
+
serial: string;
|
|
158
|
+
/** Certificate status. */
|
|
159
|
+
status: "valid" | "revoked" | "expired";
|
|
160
|
+
/** X.500 subject string (slash-delimited). */
|
|
161
|
+
subject: string;
|
|
162
|
+
/** Certificate expiry date as ISO-8601 string. */
|
|
163
|
+
expiryDate: string;
|
|
164
|
+
/**
|
|
165
|
+
* Revocation date as ISO-8601 string.
|
|
166
|
+
* Only present when `status === "revoked"`.
|
|
167
|
+
*/
|
|
168
|
+
revocationDate?: string;
|
|
169
|
+
}
|
|
151
170
|
declare class CertificateAuthority {
|
|
152
171
|
/** RSA key size used when generating the CA private key. */
|
|
153
172
|
readonly keySize: KeySize;
|
|
@@ -177,6 +196,116 @@ declare class CertificateAuthority {
|
|
|
177
196
|
* Used by OpenSSL for CRL-based verification.
|
|
178
197
|
*/
|
|
179
198
|
get caCertificateWithCrl(): string;
|
|
199
|
+
/**
|
|
200
|
+
* Return the CA certificate as a DER-encoded buffer.
|
|
201
|
+
*
|
|
202
|
+
* @throws if the CA certificate file does not exist
|
|
203
|
+
* (call {@link initialize} first).
|
|
204
|
+
*/
|
|
205
|
+
getCACertificateDER(): Buffer;
|
|
206
|
+
/**
|
|
207
|
+
* Return the CA certificate as a PEM-encoded string.
|
|
208
|
+
*
|
|
209
|
+
* @throws if the CA certificate file does not exist
|
|
210
|
+
* (call {@link initialize} first).
|
|
211
|
+
*/
|
|
212
|
+
getCACertificatePEM(): string;
|
|
213
|
+
/**
|
|
214
|
+
* Return the current Certificate Revocation List as a
|
|
215
|
+
* DER-encoded buffer.
|
|
216
|
+
*
|
|
217
|
+
* Returns an empty buffer if no CRL has been generated yet.
|
|
218
|
+
*/
|
|
219
|
+
getCRLDER(): Buffer;
|
|
220
|
+
/**
|
|
221
|
+
* Return the current Certificate Revocation List as a
|
|
222
|
+
* PEM-encoded string.
|
|
223
|
+
*
|
|
224
|
+
* Returns an empty string if no CRL has been generated yet.
|
|
225
|
+
*/
|
|
226
|
+
getCRLPEM(): string;
|
|
227
|
+
/**
|
|
228
|
+
* Return a list of all issued certificates recorded in the
|
|
229
|
+
* OpenSSL `index.txt` database.
|
|
230
|
+
*
|
|
231
|
+
* Each entry includes the serial number, subject, status,
|
|
232
|
+
* expiry date, and (for revoked certs) the revocation date.
|
|
233
|
+
*/
|
|
234
|
+
getIssuedCertificates(): IssuedCertificateRecord[];
|
|
235
|
+
/**
|
|
236
|
+
* Return the total number of certificates recorded in
|
|
237
|
+
* `index.txt`.
|
|
238
|
+
*/
|
|
239
|
+
getIssuedCertificateCount(): number;
|
|
240
|
+
/**
|
|
241
|
+
* Return the status of a certificate by its serial number.
|
|
242
|
+
*
|
|
243
|
+
* @param serial - hex-encoded serial number (e.g. `"1000"`)
|
|
244
|
+
* @returns `"valid"`, `"revoked"`, `"expired"`, or
|
|
245
|
+
* `undefined` if not found
|
|
246
|
+
*/
|
|
247
|
+
getCertificateStatus(serial: string): "valid" | "revoked" | "expired" | undefined;
|
|
248
|
+
/**
|
|
249
|
+
* Read a specific issued certificate by serial number and
|
|
250
|
+
* return its content as a DER-encoded buffer.
|
|
251
|
+
*
|
|
252
|
+
* OpenSSL stores signed certificates in the `certs/`
|
|
253
|
+
* directory using the naming convention `<SERIAL>.pem`.
|
|
254
|
+
*
|
|
255
|
+
* @param serial - hex-encoded serial number (e.g. `"1000"`)
|
|
256
|
+
* @returns the DER buffer, or `undefined` if not found
|
|
257
|
+
*/
|
|
258
|
+
getCertificateBySerial(serial: string): Buffer | undefined;
|
|
259
|
+
/**
|
|
260
|
+
* Path to the OpenSSL certificate database file.
|
|
261
|
+
*/
|
|
262
|
+
get indexFile(): string;
|
|
263
|
+
/**
|
|
264
|
+
* Parse the OpenSSL `index.txt` certificate database.
|
|
265
|
+
*
|
|
266
|
+
* Each line has tab-separated fields:
|
|
267
|
+
* ```
|
|
268
|
+
* status expiry [revocationDate] serial unknown subject
|
|
269
|
+
* ```
|
|
270
|
+
*
|
|
271
|
+
* - status: `V` (valid), `R` (revoked), `E` (expired)
|
|
272
|
+
* - expiry: `YYMMDDHHmmssZ`
|
|
273
|
+
* - revocationDate: present only for revoked certs
|
|
274
|
+
* - serial: hex string
|
|
275
|
+
* - unknown: always `"unknown"`
|
|
276
|
+
* - subject: X.500 slash-delimited string
|
|
277
|
+
*/
|
|
278
|
+
private _parseIndexTxt;
|
|
279
|
+
/**
|
|
280
|
+
* Sign a DER-encoded Certificate Signing Request and return
|
|
281
|
+
* the signed certificate as a DER buffer.
|
|
282
|
+
*
|
|
283
|
+
* This method handles temp-file creation and cleanup
|
|
284
|
+
* internally so that callers can work with in-memory
|
|
285
|
+
* buffers only.
|
|
286
|
+
*
|
|
287
|
+
* @param csrDer - the CSR as a DER-encoded buffer
|
|
288
|
+
* @param options - signing options
|
|
289
|
+
* @param options.validity - certificate validity in days
|
|
290
|
+
* (default: 365)
|
|
291
|
+
* @returns the signed certificate as a DER-encoded buffer
|
|
292
|
+
*/
|
|
293
|
+
signCertificateRequestFromDER(csrDer: Buffer, options?: {
|
|
294
|
+
validity?: number;
|
|
295
|
+
}): Promise<Buffer>;
|
|
296
|
+
/**
|
|
297
|
+
* Revoke a DER-encoded certificate and regenerate the CRL.
|
|
298
|
+
*
|
|
299
|
+
* Extracts the serial number from the certificate, then
|
|
300
|
+
* uses the stored cert file at `certs/<serial>.pem` for
|
|
301
|
+
* revocation — avoiding temp-file PEM format mismatches.
|
|
302
|
+
*
|
|
303
|
+
* @param certDer - the certificate as a DER-encoded buffer
|
|
304
|
+
* @param reason - CRL reason code
|
|
305
|
+
* (default: `"keyCompromise"`)
|
|
306
|
+
* @throws if the certificate's serial is not found in the CA
|
|
307
|
+
*/
|
|
308
|
+
revokeCertificateDER(certDer: Buffer, reason?: string): Promise<void>;
|
|
180
309
|
/**
|
|
181
310
|
* Initialize the CA directory structure, generate the CA
|
|
182
311
|
* private key and self-signed certificate if they do not
|
package/dist/index.d.ts
CHANGED
|
@@ -148,6 +148,25 @@ interface CertificateAuthorityOptions {
|
|
|
148
148
|
* await ca.initialize();
|
|
149
149
|
* ```
|
|
150
150
|
*/
|
|
151
|
+
/**
|
|
152
|
+
* A record from the OpenSSL CA certificate database
|
|
153
|
+
* (`index.txt`).
|
|
154
|
+
*/
|
|
155
|
+
interface IssuedCertificateRecord {
|
|
156
|
+
/** Hex-encoded serial number (e.g. `"1000"`). */
|
|
157
|
+
serial: string;
|
|
158
|
+
/** Certificate status. */
|
|
159
|
+
status: "valid" | "revoked" | "expired";
|
|
160
|
+
/** X.500 subject string (slash-delimited). */
|
|
161
|
+
subject: string;
|
|
162
|
+
/** Certificate expiry date as ISO-8601 string. */
|
|
163
|
+
expiryDate: string;
|
|
164
|
+
/**
|
|
165
|
+
* Revocation date as ISO-8601 string.
|
|
166
|
+
* Only present when `status === "revoked"`.
|
|
167
|
+
*/
|
|
168
|
+
revocationDate?: string;
|
|
169
|
+
}
|
|
151
170
|
declare class CertificateAuthority {
|
|
152
171
|
/** RSA key size used when generating the CA private key. */
|
|
153
172
|
readonly keySize: KeySize;
|
|
@@ -177,6 +196,116 @@ declare class CertificateAuthority {
|
|
|
177
196
|
* Used by OpenSSL for CRL-based verification.
|
|
178
197
|
*/
|
|
179
198
|
get caCertificateWithCrl(): string;
|
|
199
|
+
/**
|
|
200
|
+
* Return the CA certificate as a DER-encoded buffer.
|
|
201
|
+
*
|
|
202
|
+
* @throws if the CA certificate file does not exist
|
|
203
|
+
* (call {@link initialize} first).
|
|
204
|
+
*/
|
|
205
|
+
getCACertificateDER(): Buffer;
|
|
206
|
+
/**
|
|
207
|
+
* Return the CA certificate as a PEM-encoded string.
|
|
208
|
+
*
|
|
209
|
+
* @throws if the CA certificate file does not exist
|
|
210
|
+
* (call {@link initialize} first).
|
|
211
|
+
*/
|
|
212
|
+
getCACertificatePEM(): string;
|
|
213
|
+
/**
|
|
214
|
+
* Return the current Certificate Revocation List as a
|
|
215
|
+
* DER-encoded buffer.
|
|
216
|
+
*
|
|
217
|
+
* Returns an empty buffer if no CRL has been generated yet.
|
|
218
|
+
*/
|
|
219
|
+
getCRLDER(): Buffer;
|
|
220
|
+
/**
|
|
221
|
+
* Return the current Certificate Revocation List as a
|
|
222
|
+
* PEM-encoded string.
|
|
223
|
+
*
|
|
224
|
+
* Returns an empty string if no CRL has been generated yet.
|
|
225
|
+
*/
|
|
226
|
+
getCRLPEM(): string;
|
|
227
|
+
/**
|
|
228
|
+
* Return a list of all issued certificates recorded in the
|
|
229
|
+
* OpenSSL `index.txt` database.
|
|
230
|
+
*
|
|
231
|
+
* Each entry includes the serial number, subject, status,
|
|
232
|
+
* expiry date, and (for revoked certs) the revocation date.
|
|
233
|
+
*/
|
|
234
|
+
getIssuedCertificates(): IssuedCertificateRecord[];
|
|
235
|
+
/**
|
|
236
|
+
* Return the total number of certificates recorded in
|
|
237
|
+
* `index.txt`.
|
|
238
|
+
*/
|
|
239
|
+
getIssuedCertificateCount(): number;
|
|
240
|
+
/**
|
|
241
|
+
* Return the status of a certificate by its serial number.
|
|
242
|
+
*
|
|
243
|
+
* @param serial - hex-encoded serial number (e.g. `"1000"`)
|
|
244
|
+
* @returns `"valid"`, `"revoked"`, `"expired"`, or
|
|
245
|
+
* `undefined` if not found
|
|
246
|
+
*/
|
|
247
|
+
getCertificateStatus(serial: string): "valid" | "revoked" | "expired" | undefined;
|
|
248
|
+
/**
|
|
249
|
+
* Read a specific issued certificate by serial number and
|
|
250
|
+
* return its content as a DER-encoded buffer.
|
|
251
|
+
*
|
|
252
|
+
* OpenSSL stores signed certificates in the `certs/`
|
|
253
|
+
* directory using the naming convention `<SERIAL>.pem`.
|
|
254
|
+
*
|
|
255
|
+
* @param serial - hex-encoded serial number (e.g. `"1000"`)
|
|
256
|
+
* @returns the DER buffer, or `undefined` if not found
|
|
257
|
+
*/
|
|
258
|
+
getCertificateBySerial(serial: string): Buffer | undefined;
|
|
259
|
+
/**
|
|
260
|
+
* Path to the OpenSSL certificate database file.
|
|
261
|
+
*/
|
|
262
|
+
get indexFile(): string;
|
|
263
|
+
/**
|
|
264
|
+
* Parse the OpenSSL `index.txt` certificate database.
|
|
265
|
+
*
|
|
266
|
+
* Each line has tab-separated fields:
|
|
267
|
+
* ```
|
|
268
|
+
* status expiry [revocationDate] serial unknown subject
|
|
269
|
+
* ```
|
|
270
|
+
*
|
|
271
|
+
* - status: `V` (valid), `R` (revoked), `E` (expired)
|
|
272
|
+
* - expiry: `YYMMDDHHmmssZ`
|
|
273
|
+
* - revocationDate: present only for revoked certs
|
|
274
|
+
* - serial: hex string
|
|
275
|
+
* - unknown: always `"unknown"`
|
|
276
|
+
* - subject: X.500 slash-delimited string
|
|
277
|
+
*/
|
|
278
|
+
private _parseIndexTxt;
|
|
279
|
+
/**
|
|
280
|
+
* Sign a DER-encoded Certificate Signing Request and return
|
|
281
|
+
* the signed certificate as a DER buffer.
|
|
282
|
+
*
|
|
283
|
+
* This method handles temp-file creation and cleanup
|
|
284
|
+
* internally so that callers can work with in-memory
|
|
285
|
+
* buffers only.
|
|
286
|
+
*
|
|
287
|
+
* @param csrDer - the CSR as a DER-encoded buffer
|
|
288
|
+
* @param options - signing options
|
|
289
|
+
* @param options.validity - certificate validity in days
|
|
290
|
+
* (default: 365)
|
|
291
|
+
* @returns the signed certificate as a DER-encoded buffer
|
|
292
|
+
*/
|
|
293
|
+
signCertificateRequestFromDER(csrDer: Buffer, options?: {
|
|
294
|
+
validity?: number;
|
|
295
|
+
}): Promise<Buffer>;
|
|
296
|
+
/**
|
|
297
|
+
* Revoke a DER-encoded certificate and regenerate the CRL.
|
|
298
|
+
*
|
|
299
|
+
* Extracts the serial number from the certificate, then
|
|
300
|
+
* uses the stored cert file at `certs/<serial>.pem` for
|
|
301
|
+
* revocation — avoiding temp-file PEM format mismatches.
|
|
302
|
+
*
|
|
303
|
+
* @param certDer - the certificate as a DER-encoded buffer
|
|
304
|
+
* @param reason - CRL reason code
|
|
305
|
+
* (default: `"keyCompromise"`)
|
|
306
|
+
* @throws if the certificate's serial is not found in the CA
|
|
307
|
+
*/
|
|
308
|
+
revokeCertificateDER(certDer: Buffer, reason?: string): Promise<void>;
|
|
180
309
|
/**
|
|
181
310
|
* Initialize the CA directory structure, generate the CA
|
|
182
311
|
* private key and self-signed certificate if they do not
|
package/dist/index.js
CHANGED
|
@@ -53,6 +53,7 @@ module.exports = __toCommonJS(lib_exports);
|
|
|
53
53
|
// packages/node-opcua-pki/lib/ca/certificate_authority.ts
|
|
54
54
|
var import_node_assert6 = __toESM(require("assert"));
|
|
55
55
|
var import_node_fs6 = __toESM(require("fs"));
|
|
56
|
+
var import_node_os3 = __toESM(require("os"));
|
|
56
57
|
var import_node_path5 = __toESM(require("path"));
|
|
57
58
|
var import_chalk5 = __toESM(require("chalk"));
|
|
58
59
|
var import_node_opcua_crypto2 = require("node-opcua-crypto");
|
|
@@ -866,6 +867,18 @@ async function regenerateCrl(revocationList, configOption, options) {
|
|
|
866
867
|
displaySubtitle("Display (Certificate Revocation List)");
|
|
867
868
|
await execute_openssl(`crl -in ${q(n2(revocationList))} -text -noout`, options);
|
|
868
869
|
}
|
|
870
|
+
function parseOpenSSLDate(dateStr) {
|
|
871
|
+
const raw = dateStr?.split(",")[0] ?? "";
|
|
872
|
+
if (raw.length < 12) return "";
|
|
873
|
+
const yy = parseInt(raw.substring(0, 2), 10);
|
|
874
|
+
const year = yy >= 70 ? 1900 + yy : 2e3 + yy;
|
|
875
|
+
const month = raw.substring(2, 4);
|
|
876
|
+
const day = raw.substring(4, 6);
|
|
877
|
+
const hour = raw.substring(6, 8);
|
|
878
|
+
const min = raw.substring(8, 10);
|
|
879
|
+
const sec = raw.substring(10, 12);
|
|
880
|
+
return `${year}-${month}-${day}T${hour}:${min}:${sec}Z`;
|
|
881
|
+
}
|
|
869
882
|
var CertificateAuthority = class {
|
|
870
883
|
/** RSA key size used when generating the CA private key. */
|
|
871
884
|
keySize;
|
|
@@ -913,6 +926,244 @@ var CertificateAuthority = class {
|
|
|
913
926
|
get caCertificateWithCrl() {
|
|
914
927
|
return makePath(this.rootDir, "./public/cacertificate_with_crl.pem");
|
|
915
928
|
}
|
|
929
|
+
// ---------------------------------------------------------------
|
|
930
|
+
// Buffer-based accessors (US-059)
|
|
931
|
+
// ---------------------------------------------------------------
|
|
932
|
+
/**
|
|
933
|
+
* Return the CA certificate as a DER-encoded buffer.
|
|
934
|
+
*
|
|
935
|
+
* @throws if the CA certificate file does not exist
|
|
936
|
+
* (call {@link initialize} first).
|
|
937
|
+
*/
|
|
938
|
+
getCACertificateDER() {
|
|
939
|
+
const pem = (0, import_node_opcua_crypto2.readCertificatePEM)(this.caCertificate);
|
|
940
|
+
return (0, import_node_opcua_crypto2.convertPEMtoDER)(pem);
|
|
941
|
+
}
|
|
942
|
+
/**
|
|
943
|
+
* Return the CA certificate as a PEM-encoded string.
|
|
944
|
+
*
|
|
945
|
+
* @throws if the CA certificate file does not exist
|
|
946
|
+
* (call {@link initialize} first).
|
|
947
|
+
*/
|
|
948
|
+
getCACertificatePEM() {
|
|
949
|
+
const raw = (0, import_node_opcua_crypto2.readCertificatePEM)(this.caCertificate);
|
|
950
|
+
const beginMarker = "-----BEGIN CERTIFICATE-----";
|
|
951
|
+
const idx = raw.indexOf(beginMarker);
|
|
952
|
+
if (idx > 0) {
|
|
953
|
+
return raw.substring(idx);
|
|
954
|
+
}
|
|
955
|
+
return raw;
|
|
956
|
+
}
|
|
957
|
+
/**
|
|
958
|
+
* Return the current Certificate Revocation List as a
|
|
959
|
+
* DER-encoded buffer.
|
|
960
|
+
*
|
|
961
|
+
* Returns an empty buffer if no CRL has been generated yet.
|
|
962
|
+
*/
|
|
963
|
+
getCRLDER() {
|
|
964
|
+
const crlPath = this.revocationListDER;
|
|
965
|
+
if (!import_node_fs6.default.existsSync(crlPath)) {
|
|
966
|
+
return Buffer.alloc(0);
|
|
967
|
+
}
|
|
968
|
+
return import_node_fs6.default.readFileSync(crlPath);
|
|
969
|
+
}
|
|
970
|
+
/**
|
|
971
|
+
* Return the current Certificate Revocation List as a
|
|
972
|
+
* PEM-encoded string.
|
|
973
|
+
*
|
|
974
|
+
* Returns an empty string if no CRL has been generated yet.
|
|
975
|
+
*/
|
|
976
|
+
getCRLPEM() {
|
|
977
|
+
const crlPath = this.revocationList;
|
|
978
|
+
if (!import_node_fs6.default.existsSync(crlPath)) {
|
|
979
|
+
return "";
|
|
980
|
+
}
|
|
981
|
+
const raw = import_node_fs6.default.readFileSync(crlPath, "utf-8");
|
|
982
|
+
const beginMarker = "-----BEGIN X509 CRL-----";
|
|
983
|
+
const idx = raw.indexOf(beginMarker);
|
|
984
|
+
if (idx > 0) {
|
|
985
|
+
return raw.substring(idx);
|
|
986
|
+
}
|
|
987
|
+
return raw;
|
|
988
|
+
}
|
|
989
|
+
// ---------------------------------------------------------------
|
|
990
|
+
// Certificate database API (US-057)
|
|
991
|
+
// ---------------------------------------------------------------
|
|
992
|
+
/**
|
|
993
|
+
* Return a list of all issued certificates recorded in the
|
|
994
|
+
* OpenSSL `index.txt` database.
|
|
995
|
+
*
|
|
996
|
+
* Each entry includes the serial number, subject, status,
|
|
997
|
+
* expiry date, and (for revoked certs) the revocation date.
|
|
998
|
+
*/
|
|
999
|
+
getIssuedCertificates() {
|
|
1000
|
+
return this._parseIndexTxt();
|
|
1001
|
+
}
|
|
1002
|
+
/**
|
|
1003
|
+
* Return the total number of certificates recorded in
|
|
1004
|
+
* `index.txt`.
|
|
1005
|
+
*/
|
|
1006
|
+
getIssuedCertificateCount() {
|
|
1007
|
+
return this._parseIndexTxt().length;
|
|
1008
|
+
}
|
|
1009
|
+
/**
|
|
1010
|
+
* Return the status of a certificate by its serial number.
|
|
1011
|
+
*
|
|
1012
|
+
* @param serial - hex-encoded serial number (e.g. `"1000"`)
|
|
1013
|
+
* @returns `"valid"`, `"revoked"`, `"expired"`, or
|
|
1014
|
+
* `undefined` if not found
|
|
1015
|
+
*/
|
|
1016
|
+
getCertificateStatus(serial) {
|
|
1017
|
+
const upper = serial.toUpperCase();
|
|
1018
|
+
const record = this._parseIndexTxt().find((r) => r.serial.toUpperCase() === upper);
|
|
1019
|
+
return record?.status;
|
|
1020
|
+
}
|
|
1021
|
+
/**
|
|
1022
|
+
* Read a specific issued certificate by serial number and
|
|
1023
|
+
* return its content as a DER-encoded buffer.
|
|
1024
|
+
*
|
|
1025
|
+
* OpenSSL stores signed certificates in the `certs/`
|
|
1026
|
+
* directory using the naming convention `<SERIAL>.pem`.
|
|
1027
|
+
*
|
|
1028
|
+
* @param serial - hex-encoded serial number (e.g. `"1000"`)
|
|
1029
|
+
* @returns the DER buffer, or `undefined` if not found
|
|
1030
|
+
*/
|
|
1031
|
+
getCertificateBySerial(serial) {
|
|
1032
|
+
const upper = serial.toUpperCase();
|
|
1033
|
+
const certFile = import_node_path5.default.join(this.rootDir, "certs", `${upper}.pem`);
|
|
1034
|
+
if (!import_node_fs6.default.existsSync(certFile)) {
|
|
1035
|
+
return void 0;
|
|
1036
|
+
}
|
|
1037
|
+
const pem = (0, import_node_opcua_crypto2.readCertificatePEM)(certFile);
|
|
1038
|
+
return (0, import_node_opcua_crypto2.convertPEMtoDER)(pem);
|
|
1039
|
+
}
|
|
1040
|
+
/**
|
|
1041
|
+
* Path to the OpenSSL certificate database file.
|
|
1042
|
+
*/
|
|
1043
|
+
get indexFile() {
|
|
1044
|
+
return import_node_path5.default.join(this.rootDir, "index.txt");
|
|
1045
|
+
}
|
|
1046
|
+
/**
|
|
1047
|
+
* Parse the OpenSSL `index.txt` certificate database.
|
|
1048
|
+
*
|
|
1049
|
+
* Each line has tab-separated fields:
|
|
1050
|
+
* ```
|
|
1051
|
+
* status expiry [revocationDate] serial unknown subject
|
|
1052
|
+
* ```
|
|
1053
|
+
*
|
|
1054
|
+
* - status: `V` (valid), `R` (revoked), `E` (expired)
|
|
1055
|
+
* - expiry: `YYMMDDHHmmssZ`
|
|
1056
|
+
* - revocationDate: present only for revoked certs
|
|
1057
|
+
* - serial: hex string
|
|
1058
|
+
* - unknown: always `"unknown"`
|
|
1059
|
+
* - subject: X.500 slash-delimited string
|
|
1060
|
+
*/
|
|
1061
|
+
_parseIndexTxt() {
|
|
1062
|
+
const indexPath = this.indexFile;
|
|
1063
|
+
if (!import_node_fs6.default.existsSync(indexPath)) {
|
|
1064
|
+
return [];
|
|
1065
|
+
}
|
|
1066
|
+
const content = import_node_fs6.default.readFileSync(indexPath, "utf-8");
|
|
1067
|
+
const lines = content.split("\n").filter((l) => l.trim().length > 0);
|
|
1068
|
+
const records = [];
|
|
1069
|
+
for (const line of lines) {
|
|
1070
|
+
const fields = line.split(" ");
|
|
1071
|
+
if (fields.length < 4) continue;
|
|
1072
|
+
const statusChar = fields[0];
|
|
1073
|
+
const expiryStr = fields[1];
|
|
1074
|
+
let serial;
|
|
1075
|
+
let subject;
|
|
1076
|
+
let revocationDate;
|
|
1077
|
+
if (statusChar === "R") {
|
|
1078
|
+
revocationDate = fields[2];
|
|
1079
|
+
serial = fields[3];
|
|
1080
|
+
subject = fields.length >= 6 ? fields[5] : "";
|
|
1081
|
+
} else {
|
|
1082
|
+
serial = fields[3];
|
|
1083
|
+
subject = fields.length >= 6 ? fields[5] : "";
|
|
1084
|
+
}
|
|
1085
|
+
let status;
|
|
1086
|
+
switch (statusChar) {
|
|
1087
|
+
case "V":
|
|
1088
|
+
status = "valid";
|
|
1089
|
+
break;
|
|
1090
|
+
case "R":
|
|
1091
|
+
status = "revoked";
|
|
1092
|
+
break;
|
|
1093
|
+
case "E":
|
|
1094
|
+
status = "expired";
|
|
1095
|
+
break;
|
|
1096
|
+
default:
|
|
1097
|
+
continue;
|
|
1098
|
+
}
|
|
1099
|
+
records.push({
|
|
1100
|
+
serial,
|
|
1101
|
+
status,
|
|
1102
|
+
subject,
|
|
1103
|
+
expiryDate: parseOpenSSLDate(expiryStr),
|
|
1104
|
+
revocationDate: revocationDate ? parseOpenSSLDate(revocationDate) : void 0
|
|
1105
|
+
});
|
|
1106
|
+
}
|
|
1107
|
+
return records;
|
|
1108
|
+
}
|
|
1109
|
+
// ---------------------------------------------------------------
|
|
1110
|
+
// Buffer-based CA operations (US-058)
|
|
1111
|
+
// ---------------------------------------------------------------
|
|
1112
|
+
/**
|
|
1113
|
+
* Sign a DER-encoded Certificate Signing Request and return
|
|
1114
|
+
* the signed certificate as a DER buffer.
|
|
1115
|
+
*
|
|
1116
|
+
* This method handles temp-file creation and cleanup
|
|
1117
|
+
* internally so that callers can work with in-memory
|
|
1118
|
+
* buffers only.
|
|
1119
|
+
*
|
|
1120
|
+
* @param csrDer - the CSR as a DER-encoded buffer
|
|
1121
|
+
* @param options - signing options
|
|
1122
|
+
* @param options.validity - certificate validity in days
|
|
1123
|
+
* (default: 365)
|
|
1124
|
+
* @returns the signed certificate as a DER-encoded buffer
|
|
1125
|
+
*/
|
|
1126
|
+
async signCertificateRequestFromDER(csrDer, options) {
|
|
1127
|
+
const validity = options?.validity ?? 365;
|
|
1128
|
+
const tmpDir = await import_node_fs6.default.promises.mkdtemp(import_node_path5.default.join(import_node_os3.default.tmpdir(), "pki-sign-"));
|
|
1129
|
+
try {
|
|
1130
|
+
const csrFile = import_node_path5.default.join(tmpDir, "request.csr");
|
|
1131
|
+
const certFile = import_node_path5.default.join(tmpDir, "certificate.pem");
|
|
1132
|
+
const csrPem = (0, import_node_opcua_crypto2.toPem)(csrDer, "CERTIFICATE REQUEST");
|
|
1133
|
+
await import_node_fs6.default.promises.writeFile(csrFile, csrPem, "utf-8");
|
|
1134
|
+
await this.signCertificateRequest(certFile, csrFile, { validity });
|
|
1135
|
+
const certPem = (0, import_node_opcua_crypto2.readCertificatePEM)(certFile);
|
|
1136
|
+
return (0, import_node_opcua_crypto2.convertPEMtoDER)(certPem);
|
|
1137
|
+
} finally {
|
|
1138
|
+
await import_node_fs6.default.promises.rm(tmpDir, {
|
|
1139
|
+
recursive: true,
|
|
1140
|
+
force: true
|
|
1141
|
+
});
|
|
1142
|
+
}
|
|
1143
|
+
}
|
|
1144
|
+
/**
|
|
1145
|
+
* Revoke a DER-encoded certificate and regenerate the CRL.
|
|
1146
|
+
*
|
|
1147
|
+
* Extracts the serial number from the certificate, then
|
|
1148
|
+
* uses the stored cert file at `certs/<serial>.pem` for
|
|
1149
|
+
* revocation — avoiding temp-file PEM format mismatches.
|
|
1150
|
+
*
|
|
1151
|
+
* @param certDer - the certificate as a DER-encoded buffer
|
|
1152
|
+
* @param reason - CRL reason code
|
|
1153
|
+
* (default: `"keyCompromise"`)
|
|
1154
|
+
* @throws if the certificate's serial is not found in the CA
|
|
1155
|
+
*/
|
|
1156
|
+
async revokeCertificateDER(certDer, reason) {
|
|
1157
|
+
const info = (0, import_node_opcua_crypto2.exploreCertificate)(certDer);
|
|
1158
|
+
const serial = info.tbsCertificate.serialNumber.replace(/:/g, "").toUpperCase();
|
|
1159
|
+
const storedCertFile = import_node_path5.default.join(this.rootDir, "certs", `${serial}.pem`);
|
|
1160
|
+
if (!import_node_fs6.default.existsSync(storedCertFile)) {
|
|
1161
|
+
throw new Error(`Cannot revoke: no stored certificate found for serial ${serial} at ${storedCertFile}`);
|
|
1162
|
+
}
|
|
1163
|
+
await this.revokeCertificate(storedCertFile, {
|
|
1164
|
+
reason: reason ?? "keyCompromise"
|
|
1165
|
+
});
|
|
1166
|
+
}
|
|
916
1167
|
/**
|
|
917
1168
|
* Initialize the CA directory structure, generate the CA
|
|
918
1169
|
* private key and self-signed certificate if they do not
|