node-opcua-server-configuration 2.168.0 → 2.169.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.
@@ -118,20 +118,30 @@ export async function executeCreateSigningRequest(
118
118
  });
119
119
  }
120
120
 
121
- const options = {
122
- applicationUri: serverImpl.applicationUri,
123
- subject: subjectName
124
- };
125
-
126
- const activeCertificateManager = serverImpl.tmpCertificateManager || certificateManager;
127
-
128
- await activeCertificateManager.initialize();
129
- const csrFile = await activeCertificateManager.createCertificateRequest(options);
130
- const csrPEM = await fs.promises.readFile(csrFile, "utf8");
131
- const certificateSigningRequest = convertPEMtoDER(csrPEM);
132
-
133
- return {
134
- certificateSigningRequest,
135
- statusCode: StatusCodes.Good
136
- };
121
+ try {
122
+ const options = {
123
+ applicationUri: serverImpl.applicationUri,
124
+ subject: subjectName
125
+ };
126
+
127
+ const activeCertificateManager = serverImpl.tmpCertificateManager || certificateManager;
128
+
129
+ await activeCertificateManager.initialize();
130
+ const csrFile = await activeCertificateManager.createCertificateRequest(options);
131
+ const csrPEM = await fs.promises.readFile(csrFile, "utf8");
132
+ const certificateSigningRequest = convertPEMtoDER(csrPEM);
133
+
134
+ return {
135
+ certificateSigningRequest,
136
+ statusCode: StatusCodes.Good
137
+ };
138
+ } catch (err) {
139
+ errorLog(
140
+ "CreateSigningRequest failed during CSR generation:",
141
+ (err as Error).message,
142
+ "subject=", subjectName,
143
+ "applicationUri=", serverImpl.applicationUri
144
+ );
145
+ return { statusCode: StatusCodes.BadInternalError };
146
+ }
137
147
  }
@@ -6,8 +6,10 @@ import type { CertificateManager, OPCUACertificateManager } from "node-opcua-cer
6
6
  import { exploreCertificate, readPrivateKey } from "node-opcua-crypto";
7
7
  import {
8
8
  certificateMatchesPrivateKey,
9
+ type Certificate,
9
10
  coercePEMorDerToPrivateKey,
10
11
  coercePrivateKeyPem,
12
+ combine_der,
11
13
  makeSHA1Thumbprint,
12
14
  type PrivateKey,
13
15
  toPem
@@ -52,18 +54,33 @@ async function preInstallIssuerCertificates(
52
54
  }
53
55
 
54
56
  // Helper: Stage main certificate to temporary files
57
+ //
58
+ // The PEM file contains the full chain (leaf + issuer CAs) so that
59
+ // getCertificateChain() returns the complete chain. This allows
60
+ // OpenSecureChannel and CreateSession to present the full chain to
61
+ // connecting clients, enabling them to build and verify the trust
62
+ // path without needing all CA certificates pre-installed.
55
63
  async function preInstallCertificate(
56
64
  serverImpl: PushCertificateManagerInternalContext,
57
65
  certificateManager: CertificateManager,
58
- certificate: Buffer
66
+ certificate: Certificate,
67
+ issuerCertificates?: Certificate[]
59
68
  ): Promise<void> {
60
69
  const certFolder = certificateManager.ownCertFolder;
61
- const destDER = path.join(certFolder, "certificate.der");
62
70
  const destPEM = path.join(certFolder, "certificate.pem");
63
- const certificatePEM = toPem(certificate, "CERTIFICATE");
64
71
 
65
- await serverImpl.fileTransactionManager.stageFile(destDER, certificate);
72
+ // Build the full chain: leaf first, then issuer CAs
73
+ const certificateChain: Certificate[] = [certificate, ...(issuerCertificates ?? [])];
74
+ const certificatePEM = toPem(combine_der(certificateChain), "CERTIFICATE");
75
+
66
76
  await serverImpl.fileTransactionManager.stageFile(destPEM, certificatePEM, "utf-8");
77
+
78
+ // Clean up any leftover certificate.der from previous installations.
79
+ // Since DER is no longer written, a stale file would be misleading.
80
+ const legacyDER = path.join(certFolder, "certificate.der");
81
+ if (fs.existsSync(legacyDER)) {
82
+ serverImpl.fileTransactionManager.stageFileRemoval(legacyDER);
83
+ }
67
84
  }
68
85
 
69
86
  // Helper: Stage private key to temporary file
@@ -164,7 +181,7 @@ export async function executeUpdateCertificate(
164
181
  }
165
182
 
166
183
  await preInstallIssuerCertificates(serverImpl, certificateManager, issuerCertificates);
167
- await preInstallCertificate(serverImpl, certificateManager, certificate);
184
+ await preInstallCertificate(serverImpl, certificateManager, certificate, issuerCertificates);
168
185
  serverImpl.emit("certificateUpdated", certificateGroupId, certificate);
169
186
  return { statusCode: StatusCodes.Good, applyChangesRequired: true };
170
187
  } else {
@@ -203,7 +220,7 @@ export async function executeUpdateCertificate(
203
220
 
204
221
  await preInstallPrivateKey(serverImpl, certificateManager, privateKeyFormat, tempPrivateKey);
205
222
  await preInstallIssuerCertificates(serverImpl, certificateManager, issuerCertificates);
206
- await preInstallCertificate(serverImpl, certificateManager, certificate);
223
+ await preInstallCertificate(serverImpl, certificateManager, certificate, issuerCertificates);
207
224
 
208
225
  serverImpl.emit("certificateUpdated", certificateGroupId, certificate);
209
226
  return { statusCode: StatusCodes.Good, applyChangesRequired: true };
@@ -22,6 +22,11 @@ async function readAll(folder: string): Promise<Buffer[]> {
22
22
  const ext = path.extname(file);
23
23
  if (ext === ".der" || ext === ".pem") {
24
24
  const chain = await readCertificateChainAsync(file);
25
+ // Return the full chain as a single ByteString.
26
+ // When addTrustedCertificateFromChain stores a chain-on-disk
27
+ // (leaf + issuer CAs in a single PEM), we preserve that
28
+ // chain so clients reading the TrustList can use it for
29
+ // chain-building.
25
30
  const concatenated = Buffer.concat(chain);
26
31
  results.push(concatenated);
27
32
  } else if (ext === ".crl") {