feat: add encrypted local node enrollment
This commit is contained in:
parent
683e135f5e
commit
d2ec5c6c87
6 changed files with 233 additions and 14 deletions
|
|
@ -0,0 +1,79 @@
|
|||
import assert from 'node:assert/strict';
|
||||
import test from 'node:test';
|
||||
import { createPrivateKey, createPublicKey } from 'node:crypto';
|
||||
import { EncryptedLocalNodeEnrollmentStore, type NodeKeyEnrollmentAdapter } from './node-keystore-enrollment.js';
|
||||
import { nodePublicKeyFingerprint, parseNodeKeyRecord, type NodeKeyRecord } from './node-keystore-bridge.js';
|
||||
|
||||
function adapter(overrides: Partial<NodeKeyEnrollmentAdapter> = {}) {
|
||||
let record: Readonly<NodeKeyRecord> | null = null;
|
||||
let exportedPrivateKey: Buffer | null = null;
|
||||
const value: NodeKeyEnrollmentAdapter = {
|
||||
isAvailable: () => true,
|
||||
readRecord: async () => record,
|
||||
async encryptPrivateKeyPkcs8(privateKeyPkcs8) {
|
||||
exportedPrivateKey = Buffer.from(privateKeyPkcs8);
|
||||
return Buffer.from('sealed-local-node-key').toString('base64');
|
||||
},
|
||||
async writeRecord(next) { record = next; },
|
||||
...overrides,
|
||||
};
|
||||
return { adapter: value, privateKey: () => exportedPrivateKey, record: () => record };
|
||||
}
|
||||
|
||||
test('creates one encrypted local-terminal identity and returns only public enrollment material', async () => {
|
||||
const harness = adapter();
|
||||
const store = new EncryptedLocalNodeEnrollmentStore(harness.adapter, () => 'HL-LOCAL-TEST-001');
|
||||
const enrollment = await store.ensureLocalTerminal();
|
||||
const record = parseNodeKeyRecord(harness.record());
|
||||
assert.ok(record);
|
||||
assert.equal(enrollment.created, true);
|
||||
assert.equal(enrollment.registrationState, 'LOCAL_KEY_READY');
|
||||
assert.equal(enrollment.nodeId, 'HL-LOCAL-TEST-001');
|
||||
assert.equal(enrollment.nodeKeyFingerprint, nodePublicKeyFingerprint(enrollment.publicKeyPem));
|
||||
assert.deepEqual(Object.keys(enrollment).sort(), [
|
||||
'created', 'nodeId', 'nodeKeyFingerprint', 'nodeType', 'publicKeyPem', 'registrationState',
|
||||
]);
|
||||
assert.equal('encryptedPrivateKeyPkcs8' in enrollment, false);
|
||||
|
||||
const privateKey = createPrivateKey({ key: harness.privateKey()!, format: 'der', type: 'pkcs8' });
|
||||
const derivedPublic = createPublicKey(privateKey).export({ format: 'pem', type: 'spki' }).toString();
|
||||
assert.equal(derivedPublic, enrollment.publicKeyPem);
|
||||
});
|
||||
|
||||
test('reuses a valid enrolled identity and never rotates it implicitly', async () => {
|
||||
const harness = adapter();
|
||||
const firstStore = new EncryptedLocalNodeEnrollmentStore(harness.adapter, () => 'HL-LOCAL-TEST-001');
|
||||
const first = await firstStore.ensureLocalTerminal();
|
||||
const secondStore = new EncryptedLocalNodeEnrollmentStore(harness.adapter, () => 'HL-LOCAL-TEST-002');
|
||||
const second = await secondStore.ensureLocalTerminal();
|
||||
assert.equal(second.created, false);
|
||||
assert.equal(second.nodeId, first.nodeId);
|
||||
assert.equal(second.nodeKeyFingerprint, first.nodeKeyFingerprint);
|
||||
});
|
||||
|
||||
test('fails closed when encryption is unavailable or an existing record is malformed', async () => {
|
||||
const unavailable = adapter({ isAvailable: () => false });
|
||||
await assert.rejects(
|
||||
new EncryptedLocalNodeEnrollmentStore(unavailable.adapter).ensureLocalTerminal(),
|
||||
/node_keystore_encryption_unavailable/,
|
||||
);
|
||||
|
||||
const malformed = adapter({ readRecord: async () => ({ schema: 'wrong' }) });
|
||||
await assert.rejects(
|
||||
new EncryptedLocalNodeEnrollmentStore(malformed.adapter).ensureLocalTerminal(),
|
||||
/node_keystore_record_invalid/,
|
||||
);
|
||||
});
|
||||
|
||||
test('does not write a record when encryption output is invalid', async () => {
|
||||
let writes = 0;
|
||||
const harness = adapter({
|
||||
encryptPrivateKeyPkcs8: async () => '***',
|
||||
writeRecord: async () => { writes += 1; },
|
||||
});
|
||||
await assert.rejects(
|
||||
new EncryptedLocalNodeEnrollmentStore(harness.adapter).ensureLocalTerminal(),
|
||||
/node_keystore_enrollment_failed/,
|
||||
);
|
||||
assert.equal(writes, 0);
|
||||
});
|
||||
Loading…
Reference in a new issue