Skip to content

Commit a59f971

Browse files
committed
fix(ca): recover from partial CA initialization
construct_CertificateAuthority now checks for both cakey.pem AND cacert.pem before skipping CA creation. If only the private key exists (partial init from a previous failure, e.g. OpenSSL 3.5 authorityKeyIdentifier error), the stale key and CSR are deleted and the CA is rebuilt from scratch. Previously, a failed first init left cakey.pem behind, causing all subsequent initialize() calls to skip CA creation — leaving cacert.pem permanently missing. Add test: "should recover from partial CA init"
1 parent a7e1912 commit a59f971

2 files changed

Lines changed: 51 additions & 3 deletions

File tree

packages/node-opcua-pki/lib/ca/certificate_authority.ts

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -151,11 +151,26 @@ async function construct_CertificateAuthority(certificateAuthority: CertificateA
151151

152152
await construct_default_files();
153153

154-
if (fs.existsSync(path.join(caRootDir, "private/cakey.pem")) && !config.forceCA) {
155-
// certificate already exists => do not overwrite
156-
debugLog("CA private key already exists ... skipping");
154+
const caKeyExists = fs.existsSync(path.join(caRootDir, "private/cakey.pem"));
155+
const caCertExists = fs.existsSync(path.join(caRootDir, "public/cacert.pem"));
156+
if (caKeyExists && caCertExists && !config.forceCA) {
157+
// CA is fully initialized => do not overwrite
158+
debugLog("CA private key and certificate already exist ... skipping");
157159
return;
158160
}
161+
if (caKeyExists && !caCertExists) {
162+
// Partial init: key exists but certificate does not.
163+
// This can happen when a previous CA creation failed
164+
// (e.g. OpenSSL 3.5 authorityKeyIdentifier error).
165+
// Remove the stale key so the CA is rebuilt from scratch.
166+
debugLog("CA private key exists but cacert.pem is missing — rebuilding CA");
167+
fs.unlinkSync(path.join(caRootDir, "private/cakey.pem"));
168+
// Also remove the stale CSR if present
169+
const staleCsr = path.join(caRootDir, "private/cakey.csr");
170+
if (fs.existsSync(staleCsr)) {
171+
fs.unlinkSync(staleCsr);
172+
}
173+
}
159174

160175
// tslint:disable:no-empty
161176
displayTitle("Create Certificate Authority (CA)");

test/test_certificate_authority.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,39 @@ describe("Certificate Authority", function (this: Mocha.Suite) {
5050
const ca = new CertificateAuthority(options as CertificateAuthorityOptions);
5151
await ca.initialize();
5252
});
53+
54+
it("should recover from partial CA init (key exists, cert missing)", async () => {
55+
// Simulate a partial init: create directory with cakey.pem but no cacert.pem
56+
const partialCALocation = path.join(testData.tmpFolder, "CA_partial");
57+
const privateDir = path.join(partialCALocation, "private");
58+
const publicDir = path.join(partialCALocation, "public");
59+
60+
fs.mkdirSync(privateDir, { recursive: true });
61+
fs.mkdirSync(publicDir, { recursive: true });
62+
63+
// Create a dummy private key file to simulate the partial state
64+
fs.writeFileSync(path.join(privateDir, "cakey.pem"), "STALE KEY DATA");
65+
66+
// cacert.pem intentionally NOT created
67+
68+
const ca = new CertificateAuthority({
69+
keySize: 2048,
70+
location: partialCALocation
71+
});
72+
73+
// initialize() should detect the partial state, clean up, and rebuild
74+
await ca.initialize();
75+
76+
// After recovery, both files should exist
77+
fs.existsSync(path.join(privateDir, "cakey.pem")).should.eql(true);
78+
fs.existsSync(path.join(publicDir, "cacert.pem")).should.eql(true);
79+
80+
// The CA should be functional — verify we can get the cert
81+
const der = ca.getCACertificateDER();
82+
Buffer.isBuffer(der).should.eql(true);
83+
der.length.should.be.greaterThan(0);
84+
der[0].should.eql(0x30); // DER SEQUENCE
85+
});
5386
});
5487

5588
describe("Signing Certificate with Certificate Authority", function (this: Mocha.Suite) {

0 commit comments

Comments
 (0)