feat(ql3): establish 3.0 incubation baseline

This commit is contained in:
whyour
2026-08-12 00:25:26 +08:00
parent 4bf92dcfeb
commit c699c32461
2817 changed files with 779642 additions and 653 deletions
@@ -0,0 +1,168 @@
const assert = require('node:assert/strict');
const fs = require('node:fs');
const os = require('node:os');
const path = require('node:path');
const { test } = require('node:test');
const {
establishAuthenticatedLocalCommand,
} = require('@qinglong/local-owner-console/authenticated-command');
const {
provisionLocalOwnerPepperKey,
} = require('@qinglong/local-owner-console/pepper-custody');
const {
apiCredentialSecretDigest,
formatApiCredentialToken,
} = require('@qinglong/runtime-core/api-credential-token');
const CREDENTIAL_ID = 'package-owner';
const PEPPER_KEY_ID = 'package-owner-v1';
const PEPPER = Buffer.alloc(32, 71).toString('base64url');
const SECRET = Buffer.alloc(32, 72).toString('base64url');
const TOKEN = formatApiCredentialToken(CREDENTIAL_ID, SECRET);
function fixture(t) {
const deploymentRoot = fs.mkdtempSync(
path.join(os.tmpdir(), 'ql3-authenticated-command-'),
);
fs.chmodSync(deploymentRoot, 0o700);
t.after(() => fs.rmSync(deploymentRoot, { recursive: true, force: true }));
const ownerPepperKeyringDirectory = path.join(deploymentRoot, 'owner-keys');
fs.mkdirSync(ownerPepperKeyringDirectory, { mode: 0o700 });
const summary = provisionLocalOwnerPepperKey({
keyringDirectory: ownerPepperKeyringDirectory,
pepperKeyId: PEPPER_KEY_ID,
randomBytes: () => Buffer.alloc(32, 71),
});
const databasePath = path.join(deploymentRoot, 'qinglong3.sqlite');
const credentialFilePath = path.join(deploymentRoot, 'credential.json');
fs.writeFileSync(databasePath, 'database', { mode: 0o600 });
fs.writeFileSync(
credentialFilePath,
`${JSON.stringify({
schemaVersion: 1,
kind: 'qinglong3-local-identity-credential-presentation',
token: TOKEN,
})}\n`,
{ mode: 0o600 },
);
let now = 10_000;
let credential = {
credentialId: CREDENTIAL_ID,
version: 1,
pepperKeyId: PEPPER_KEY_ID,
state: 'active',
subject: { type: 'user', id: 'owner-user' },
subjectStatus: 'active',
secretDigest: apiCredentialSecretDigest(
PEPPER,
CREDENTIAL_ID,
SECRET,
),
createdAtMs: 1,
notBeforeAtMs: 1,
expiresAtMs: 1_000_000,
};
const database = {
apiCredentials: {
async resolve(credentialId) {
return credentialId === CREDENTIAL_ID ? credential : null;
},
},
ownerPepper: {
async resolveKey(pepperKeyId) {
return pepperKeyId === PEPPER_KEY_ID
? {
pepperKeyId,
materialDigest: summary.digest,
backupDigest: 'b'.repeat(64),
state: 'active',
version: 2,
registeredAtMs: 1,
activatedAtMs: 2,
}
: null;
},
},
};
return {
database,
options: {
deploymentRoot,
databasePath,
ownerPepperKeyringDirectory,
credentialFilePath,
authenticationNamespace: 'local_package',
now: () => now,
},
credentialFilePath,
setNow(value) {
now = value;
},
revoke() {
credential = { ...credential, state: 'revoked', version: 2 };
},
};
}
test('binds a User credential to a short-lived POSIX local-console principal', async (t) => {
const value = fixture(t);
const authenticated = await establishAuthenticatedLocalCommand(
value.database,
value.options,
);
assert.equal(authenticated.principal.subject.type, 'user');
assert.equal(authenticated.principal.subject.id, 'owner-user');
assert.equal(authenticated.principal.assurance, 'local_console');
assert.match(
authenticated.principal.authenticationId,
/^local_package:[0-9a-f]{64}$/,
);
await authenticated.confirm();
assert.equal(JSON.stringify(authenticated).includes(TOKEN), false);
});
test('fails closed when the credential file identity or credential fence changes', async (t) => {
const value = fixture(t);
const authenticated = await establishAuthenticatedLocalCommand(
value.database,
value.options,
);
const replacement = `${value.credentialFilePath}.replacement`;
fs.writeFileSync(replacement, fs.readFileSync(value.credentialFilePath), {
mode: 0o600,
});
fs.renameSync(replacement, value.credentialFilePath);
await assert.rejects(authenticated.confirm, {
code: 'AUTHENTICATED_LOCAL_COMMAND_AUTHENTICATION_FAILED',
});
const second = fixture(t);
const fenced = await establishAuthenticatedLocalCommand(
second.database,
second.options,
);
second.revoke();
await assert.rejects(fenced.confirm, {
code: 'AUTHENTICATED_LOCAL_COMMAND_AUTHENTICATION_FAILED',
});
});
test('expires without timers and rejects non-private authority files', async (t) => {
const value = fixture(t);
const authenticated = await establishAuthenticatedLocalCommand(
value.database,
value.options,
);
value.setNow(70_000);
await assert.rejects(authenticated.confirm, {
code: 'AUTHENTICATED_LOCAL_COMMAND_AUTHENTICATION_FAILED',
});
const second = fixture(t);
fs.chmodSync(second.credentialFilePath, 0o644);
await assert.rejects(
establishAuthenticatedLocalCommand(second.database, second.options),
{ code: 'AUTHENTICATED_LOCAL_COMMAND_CONFIGURATION_INVALID' },
);
});
@@ -0,0 +1,579 @@
const assert = require('node:assert/strict');
const { createHash } = require('node:crypto');
const fs = require('node:fs');
const os = require('node:os');
const path = require('node:path');
const { DatabaseSync } = require('node:sqlite');
const { test } = require('node:test');
const {
LocalOwnerBootstrapMutationConflictError,
} = require('@qinglong/runtime-core/local-owner-bootstrap');
const {
MIN_LOCAL_OWNER_ACKNOWLEDGEMENT_AUDIT_RETENTION_MS,
MIN_LOCAL_OWNER_ACKNOWLEDGEMENT_REPLAY_RETENTION_MS,
} = require('@qinglong/runtime-core/local-owner-delivery-acknowledgement-gc');
const { migrateLocalSqlitePath } = require('@qinglong/local-sqlite/migration');
const {
openLocalSqliteAcknowledgementGcDatabase,
} = require('@qinglong/local-sqlite/acknowledgement-gc');
const {
openLocalSqliteBootstrapDatabase,
} = require('@qinglong/local-sqlite/bootstrap');
const {
LocalOwnerBootstrapConfigurationError,
LocalOwnerBootstrapRejectedError,
LocalOwnerBootstrapServiceUnavailableError,
createLocalOwnerBootstrapService,
} = require('../dist/bootstrap');
const NOW = 1_760_000_000_000;
const PEPPER = Buffer.alloc(32, 91).toString('base64url');
function fixture(t) {
const directory = fs.mkdtempSync(path.join(os.tmpdir(), 'ql3-owner-'));
t.after(() => fs.rmSync(directory, { recursive: true, force: true }));
return {
databasePath: path.join(directory, 'qinglong3.sqlite'),
profile: 'edge',
};
}
function issuer() {
return {
subject: { type: 'system', id: 'owner-bootstrap' },
authenticationId: 'local-console-test',
authenticatedAtMs: NOW - 1_000,
expiresAtMs: NOW + 60_000,
assurance: 'local_console',
};
}
function restartedIssuer() {
return {
...issuer(),
authenticatedAtMs: NOW + 1_000,
expiresAtMs: NOW + 61_000,
};
}
function entropy(start = 1) {
let value = start;
return (size) => Buffer.alloc(size, value++);
}
async function opened(t, start = 1) {
const options = fixture(t);
await migrateLocalSqlitePath(options);
const database = await openLocalSqliteBootstrapDatabase(options);
const materialDigest = createHash('sha256')
.update('qinglong.local-owner-pepper.summary.v1\0', 'utf8')
.update(PEPPER, 'utf8')
.digest('hex');
await database.ownerPepper.register({
mutationId: '00000000-0000-4000-8000-000000000091',
pepperKeyId: 'legacy-v1',
materialDigest,
backupDigest: 'b'.repeat(64),
registeredAtMs: NOW - 2_000,
});
await database.ownerPepper.activate({
mutationId: '00000000-0000-4000-8000-000000000092',
pepperKeyId: 'legacy-v1',
expectedGeneration: 0,
activatedAtMs: NOW - 1_500,
});
t.after(() => database.close());
return {
options,
database,
service: createLocalOwnerBootstrapService(
database.ownerBootstrap,
database.apiCredentials,
PEPPER,
issuer(),
{ now: () => NOW, randomBytes: entropy(start) },
),
};
}
function provisionRequest(overrides = {}) {
return {
mutationId: '00000000-0000-4000-8000-000000000101',
requestId: 'provision-101',
...overrides,
};
}
function issueRequest(overrides = {}) {
return {
projectId: 'default',
mutationId: '00000000-0000-4000-8000-000000000102',
requestId: 'issue-102',
...overrides,
};
}
function claimRequest(provisioned, challenge, overrides = {}) {
return {
projectId: 'default',
mutationId: '00000000-0000-4000-8000-000000000103',
requestId: 'claim-103',
challengeId: challenge.challengeId,
challengeToken: challenge.challengeToken,
credentialToken: provisioned.credentialToken,
...overrides,
};
}
test('provisions stable identity and claims one Owner without persisting plaintext', async (t) => {
const value = await opened(t);
const provisioned = await value.service.provision(provisionRequest());
const challenge = await value.service.issue(issueRequest());
const claimed = await value.service.claim(
claimRequest(provisioned, challenge),
);
assert.equal(provisioned.status, 'inserted');
assert.match(provisioned.credentialToken, /^ql3c_/);
assert.equal(challenge.status, 'inserted');
assert.equal(challenge.challengeToken.length, 43);
assert.equal(claimed.status, 'inserted');
assert.equal(claimed.binding.role, 'owner');
const client = new DatabaseSync(value.options.databasePath, {
readOnly: true,
});
try {
const credential = client
.prepare('SELECT secret_digest FROM "QingLong3ApiCredentials" LIMIT 1')
.get();
const storedChallenge = client
.prepare(
'SELECT token_digest, consumed_at_ms FROM "QingLong3LocalOwnerBootstrapChallenges" LIMIT 1',
)
.get();
assert.match(credential.secret_digest, /^[0-9a-f]{64}$/);
assert.match(storedChallenge.token_digest, /^[0-9a-f]{64}$/);
assert.equal(storedChallenge.consumed_at_ms, NOW);
const bytes = fs.readFileSync(value.options.databasePath);
assert.equal(
bytes.includes(Buffer.from(provisioned.credentialToken)),
false,
);
assert.equal(bytes.includes(Buffer.from(challenge.challengeToken)), false);
} finally {
client.close();
}
const replayProvision = await value.service.provision(provisionRequest());
const replayIssue = await value.service.issue(issueRequest());
const replayClaim = await value.service.claim(
claimRequest(provisioned, challenge),
);
assert.equal(replayProvision.status, 'existing');
assert.equal(replayProvision.credentialToken, null);
assert.equal(replayIssue.status, 'existing');
assert.equal(replayIssue.challengeToken, null);
assert.equal(replayClaim.status, 'existing');
});
test('replays provisioning and issue across a fresh console authentication', async (t) => {
const value = await opened(t);
const provisioned = await value.service.provision(provisionRequest());
const challenge = await value.service.issue(issueRequest());
const restarted = createLocalOwnerBootstrapService(
value.database.ownerBootstrap,
value.database.apiCredentials,
PEPPER,
restartedIssuer(),
{ now: () => NOW + 2_000, randomBytes: entropy(90) },
);
const replayProvision = await restarted.provision(provisionRequest());
const replayIssue = await restarted.issue(issueRequest());
assert.equal(replayProvision.status, 'existing');
assert.equal(replayProvision.subjectId, provisioned.subjectId);
assert.equal(replayProvision.credentialToken, null);
assert.equal(replayIssue.status, 'existing');
assert.equal(replayIssue.challengeId, challenge.challengeId);
assert.equal(replayIssue.challengeToken, null);
const foreignProof = createLocalOwnerBootstrapService(
value.database.ownerBootstrap,
value.database.apiCredentials,
PEPPER,
{ ...restartedIssuer(), authenticationId: 'different-local-console' },
{ now: () => NOW + 2_000, randomBytes: entropy(100) },
);
await assert.rejects(
foreignProof.provision(provisionRequest()),
LocalOwnerBootstrapMutationConflictError,
);
await assert.rejects(
foreignProof.issue(issueRequest()),
LocalOwnerBootstrapMutationConflictError,
);
});
test('replays a compacted acknowledgement without regenerating entropy', async (t) => {
const value = await opened(t);
const request = provisionRequest();
const provisioned = await value.service.provision(request);
const source = await value.database.ownerBootstrap.resolveProvisioning(
request.mutationId,
);
const acknowledgement = {
kind: 'credential',
mutationId: request.mutationId,
requestId: request.requestId,
subjectId: provisioned.subjectId,
credentialId: provisioned.credentialId,
factDigest: source.credential.secretDigest,
ttlMs: source.credential.expiresAtMs - source.credential.notBeforeAtMs,
deliveryDigest: 'd'.repeat(64),
acknowledgedAtMs: NOW + 1,
};
await value.database.ownerBootstrap.recordDeliveryAcknowledgement(
acknowledgement,
);
const compactedAtMs = Math.max(
source.credential.expiresAtMs,
NOW + MIN_LOCAL_OWNER_ACKNOWLEDGEMENT_AUDIT_RETENTION_MS,
acknowledgement.acknowledgedAtMs +
MIN_LOCAL_OWNER_ACKNOWLEDGEMENT_REPLAY_RETENTION_MS,
);
const gc = await openLocalSqliteAcknowledgementGcDatabase(value.options);
const gcMutationId = '00000000-0000-4000-8000-0000000001f1';
const gcRequestId = 'ack-gc-1f1';
await gc.acknowledgementGc.compact({
mutationId: gcMutationId,
requestId: gcRequestId,
acknowledgementMutationId: request.mutationId,
expectedKind: 'credential',
expectedDeliveryDigest: acknowledgement.deliveryDigest,
bridgeClearEvidence: {
kind: 'credential',
acknowledgementMutationId: request.mutationId,
inspectedAtMs: compactedAtMs,
evidenceDigest: 'e'.repeat(64),
},
retentionPolicy: {
version: 1,
replayRetentionMs: MIN_LOCAL_OWNER_ACKNOWLEDGEMENT_REPLAY_RETENTION_MS,
auditRetentionMs: MIN_LOCAL_OWNER_ACKNOWLEDGEMENT_AUDIT_RETENTION_MS,
},
compactedAtMs,
audit: {
eventId: gcMutationId,
requestId: gcRequestId,
operationId: 'owner.delivery_acknowledgement.gc',
projectId: null,
subject: { type: 'system', id: 'owner-acknowledgement-gc' },
authenticationId: 'local-owner-console',
outcome: 'allowed',
reasons: ['delivery_acknowledgement_gc'],
fence: null,
occurredAtMs: compactedAtMs,
},
});
await gc.close();
const restarted = createLocalOwnerBootstrapService(
value.database.ownerBootstrap,
value.database.apiCredentials,
PEPPER,
restartedIssuer(),
{
now: () => NOW + 2_000,
randomBytes: () => {
throw new Error('entropy must not be requested for a tombstone replay');
},
},
);
assert.deepEqual(await restarted.provision(request), {
status: 'existing',
subjectId: provisioned.subjectId,
credentialId: provisioned.credentialId,
credentialToken: null,
expiresAtMs: provisioned.expiresAtMs,
});
});
test('replays a compacted challenge acknowledgement without regenerating entropy', async (t) => {
const value = await opened(t);
await value.service.provision(provisionRequest());
const request = issueRequest();
const issued = await value.service.issue(request);
const source = await value.database.ownerBootstrap.resolveIssuedChallenge(
request.mutationId,
);
const acknowledgement = {
kind: 'challenge',
mutationId: request.mutationId,
requestId: request.requestId,
projectId: request.projectId,
challengeId: issued.challengeId,
factDigest: source.tokenDigest,
ttlMs: source.expiresAtMs - source.issuedAtMs,
deliveryDigest: 'f'.repeat(64),
acknowledgedAtMs: NOW + 1,
};
await value.database.ownerBootstrap.recordDeliveryAcknowledgement(
acknowledgement,
);
const compactedAtMs = Math.max(
source.expiresAtMs,
NOW + MIN_LOCAL_OWNER_ACKNOWLEDGEMENT_AUDIT_RETENTION_MS,
acknowledgement.acknowledgedAtMs +
MIN_LOCAL_OWNER_ACKNOWLEDGEMENT_REPLAY_RETENTION_MS,
);
const gc = await openLocalSqliteAcknowledgementGcDatabase(value.options);
const gcMutationId = '00000000-0000-4000-8000-0000000001f2';
const gcRequestId = 'ack-gc-1f2';
await gc.acknowledgementGc.compact({
mutationId: gcMutationId,
requestId: gcRequestId,
acknowledgementMutationId: request.mutationId,
expectedKind: 'challenge',
expectedDeliveryDigest: acknowledgement.deliveryDigest,
bridgeClearEvidence: {
kind: 'challenge',
acknowledgementMutationId: request.mutationId,
inspectedAtMs: compactedAtMs,
evidenceDigest: '1'.repeat(64),
},
retentionPolicy: {
version: 1,
replayRetentionMs: MIN_LOCAL_OWNER_ACKNOWLEDGEMENT_REPLAY_RETENTION_MS,
auditRetentionMs: MIN_LOCAL_OWNER_ACKNOWLEDGEMENT_AUDIT_RETENTION_MS,
},
compactedAtMs,
audit: {
eventId: gcMutationId,
requestId: gcRequestId,
operationId: 'owner.delivery_acknowledgement.gc',
projectId: null,
subject: { type: 'system', id: 'owner-acknowledgement-gc' },
authenticationId: 'local-owner-console',
outcome: 'allowed',
reasons: ['delivery_acknowledgement_gc'],
fence: null,
occurredAtMs: compactedAtMs,
},
});
await gc.close();
const restarted = createLocalOwnerBootstrapService(
value.database.ownerBootstrap,
value.database.apiCredentials,
PEPPER,
restartedIssuer(),
{
now: () => NOW + 2_000,
randomBytes: () => {
throw new Error('entropy must not be requested for a tombstone replay');
},
},
);
assert.deepEqual(await restarted.issue(request), {
status: 'existing',
challengeId: issued.challengeId,
challengeToken: null,
expiresAtMs: issued.expiresAtMs,
});
});
test('public requests reject caller-supplied identity fields', async (t) => {
const value = await opened(t);
await assert.rejects(
value.service.provision(
provisionRequest({
issuer: issuer(),
userId: 'chosen-user',
credentialId: 'chosen-key',
}),
),
LocalOwnerBootstrapConfigurationError,
);
await assert.rejects(
value.service.claim({
...claimRequest(
{ credentialToken: 'x' },
{ challengeId: 'A'.repeat(22), challengeToken: 'B'.repeat(43) },
),
principal: issuer(),
}),
LocalOwnerBootstrapConfigurationError,
);
});
test('authentication rejection is audited and consumes the mutation identity', async (t) => {
const value = await opened(t);
const provisioned = await value.service.provision(provisionRequest());
const challenge = await value.service.issue(issueRequest());
const request = claimRequest(provisioned, challenge, {
credentialToken: `${provisioned.credentialToken.slice(0, -1)}A`,
});
await assert.rejects(
value.service.claim(request),
LocalOwnerBootstrapRejectedError,
);
await assert.rejects(
value.service.claim({
...request,
credentialToken: provisioned.credentialToken,
}),
LocalOwnerBootstrapMutationConflictError,
);
const client = new DatabaseSync(value.options.databasePath, {
readOnly: true,
});
try {
const event = client
.prepare(
'SELECT outcome, reasons_json FROM "QingLong3SecurityAuditEvents" WHERE event_id = ?',
)
.get(request.mutationId);
assert.equal(event.outcome, 'authentication_rejected');
assert.equal(event.reasons_json, '["credential_rejected"]');
assert.equal(
client
.prepare('SELECT COUNT(*) AS count FROM "QingLong3ProjectRoleBindings"')
.get().count,
0,
);
} finally {
client.close();
}
});
test('two independent connections have exactly one claim winner', async (t) => {
const value = await opened(t);
const provisioned = await value.service.provision(provisionRequest());
const challenge = await value.service.issue(issueRequest());
const secondDatabase = await openLocalSqliteBootstrapDatabase(value.options);
t.after(() => secondDatabase.close());
const secondService = createLocalOwnerBootstrapService(
secondDatabase.ownerBootstrap,
secondDatabase.apiCredentials,
PEPPER,
issuer(),
{ now: () => NOW, randomBytes: entropy(40) },
);
const results = await Promise.allSettled([
value.service.claim(claimRequest(provisioned, challenge)),
secondService.claim(
claimRequest(provisioned, challenge, {
mutationId: '00000000-0000-4000-8000-000000000104',
requestId: 'claim-104',
}),
),
]);
assert.equal(
results.filter(({ status }) => status === 'fulfilled').length,
1,
);
const client = new DatabaseSync(value.options.databasePath, {
readOnly: true,
});
try {
assert.equal(
client
.prepare('SELECT COUNT(*) AS count FROM "QingLong3ProjectRoleBindings"')
.get().count,
1,
);
} finally {
client.close();
}
});
test('any historical binding permanently closes the bootstrap bypass', async (t) => {
const value = await opened(t);
const provisioned = await value.service.provision(provisionRequest());
const challenge = await value.service.issue(issueRequest());
await value.service.claim(claimRequest(provisioned, challenge));
const client = new DatabaseSync(value.options.databasePath);
try {
client
.prepare(
`INSERT INTO "QingLong3ProjectRoleBindings" (
project_id, subject_type, subject_id, version, state, role,
mutation_id, changed_by_type, changed_by_id, created_at_ms
) VALUES ('default', 'user', ?, 2, 'revoked', NULL,
'binding-revoke-2', 'system', 'owner-bootstrap', ?)`,
)
.run(provisioned.subjectId, NOW + 1);
} finally {
client.close();
}
await assert.rejects(
value.service.issue(
issueRequest({
mutationId: '00000000-0000-4000-8000-000000000105',
requestId: 'issue-105',
}),
),
);
});
test('claim audit failure rolls back binding and challenge consumption', async (t) => {
const value = await opened(t);
const provisioned = await value.service.provision(provisionRequest());
const challenge = await value.service.issue(issueRequest());
const trigger = new DatabaseSync(value.options.databasePath);
trigger.exec(`
CREATE TRIGGER fail_owner_claim_audit
BEFORE INSERT ON "QingLong3SecurityAuditEvents"
WHEN NEW."operation_id" = 'project.owner_bootstrap_claim'
BEGIN
SELECT RAISE(ABORT, 'injected audit failure');
END
`);
trigger.close();
await assert.rejects(
value.service.claim(claimRequest(provisioned, challenge)),
LocalOwnerBootstrapServiceUnavailableError,
);
const client = new DatabaseSync(value.options.databasePath, {
readOnly: true,
});
try {
assert.equal(
client
.prepare('SELECT COUNT(*) AS count FROM "QingLong3ProjectRoleBindings"')
.get().count,
0,
);
assert.equal(
client
.prepare(
'SELECT consumed_at_ms FROM "QingLong3LocalOwnerBootstrapChallenges" LIMIT 1',
)
.get().consumed_at_ms,
null,
);
assert.equal(
client
.prepare(
`SELECT COUNT(*) AS count FROM "QingLong3SecurityAuditEvents"
WHERE event_id = '00000000-0000-4000-8000-000000000103'`,
)
.get().count,
0,
);
} finally {
client.close();
}
});
test('bootstrap authority closes once and rejects later work', async (t) => {
const value = await opened(t);
await Promise.all([value.database.close(), value.database.close()]);
await assert.rejects(
value.database.ownerBootstrap.resolveProjectVersion('default'),
);
await assert.rejects(value.database.apiCredentials.resolve('missing'));
const root = require('@qinglong/local-sqlite');
const runtime = require('@qinglong/local-sqlite/runtime');
assert.equal('openLocalSqliteBootstrapDatabase' in root, false);
assert.equal('openLocalSqliteBootstrapDatabase' in runtime, false);
});
@@ -0,0 +1,28 @@
const assert = require('node:assert/strict');
const { test } = require('node:test');
test('keeps the two reviewed Owner ceremony modules internal to console', () => {
const manifest = require('../package.json');
assert.deepEqual(Object.keys(manifest.exports).sort(), [
'.',
'./authenticated-command',
'./credential-administration-delivery',
'./identity-authentication',
'./pepper-custody',
'./pepper-custody/destructive',
'./secret-delivery',
]);
assert.throws(
() => require('@qinglong/local-owner-console/bootstrap'),
(error) => error?.code === 'ERR_PACKAGE_PATH_NOT_EXPORTED',
);
assert.equal(
typeof require('../dist/bootstrap').createLocalOwnerBootstrapService,
'function',
);
assert.equal(
typeof require('../dist/credential-recovery')
.createLocalOwnerCredentialRecoveryService,
'function',
);
});
@@ -0,0 +1,810 @@
const assert = require('node:assert/strict');
const { createHash, randomUUID } = require('node:crypto');
const fs = require('node:fs');
const os = require('node:os');
const path = require('node:path');
const { DatabaseSync } = require('node:sqlite');
const { test } = require('node:test');
const { migrateLocalSqlitePath } = require('@qinglong/local-sqlite/migration');
const {
openLocalSqliteBootstrapDatabase,
} = require('@qinglong/local-sqlite/bootstrap');
const {
createLocalOwnerBootstrapService,
LOCAL_IDENTITY_BOOTSTRAP_DEFAULT_TTL_MS,
LocalOwnerBootstrapServiceUnavailableError,
} = require('../dist/bootstrap');
const {
formatApiCredentialToken,
} = require('@qinglong/runtime-core/api-credential-token');
const {
FileLocalOwnerBootstrapSecretDelivery,
LocalOwnerConsoleConfigurationError,
LocalOwnerSecretDeliveryError,
openLocalOwnerConsole,
} = require('@qinglong/local-owner-console');
function fixture(t) {
const deploymentRoot = fs.mkdtempSync(
path.join(os.tmpdir(), 'ql3-owner-console-'),
);
fs.chmodSync(deploymentRoot, 0o700);
t.after(() => fs.rmSync(deploymentRoot, { recursive: true, force: true }));
const databasePath = path.join(deploymentRoot, 'qinglong3.sqlite');
const pepperPath = path.join(deploymentRoot, 'owner.pepper');
const secretDeliveryDirectory = path.join(deploymentRoot, 'secrets');
fs.mkdirSync(secretDeliveryDirectory, { mode: 0o700 });
fs.writeFileSync(pepperPath, Buffer.alloc(32, 73).toString('base64url'), {
mode: 0o600,
});
return {
deploymentRoot,
databasePath,
pepperPath,
secretDeliveryDirectory,
profile: 'edge',
};
}
async function ready(t) {
const options = fixture(t);
await migrateLocalSqlitePath({
databasePath: options.databasePath,
profile: options.profile,
});
const material = fs.readFileSync(options.pepperPath);
const materialDigest = createHash('sha256')
.update('qinglong.local-owner-pepper.summary.v1\0', 'utf8')
.update(material)
.digest('hex');
material.fill(0);
const database = await openLocalSqliteBootstrapDatabase({
databasePath: options.databasePath,
profile: options.profile,
});
await database.ownerPepper.register({
mutationId: '00000000-0000-4000-8000-000000000191',
pepperKeyId: 'legacy-v1',
materialDigest,
backupDigest: 'b'.repeat(64),
registeredAtMs: 1,
});
await database.ownerPepper.activate({
mutationId: '00000000-0000-4000-8000-000000000192',
pepperKeyId: 'legacy-v1',
expectedGeneration: 0,
activatedAtMs: 2,
});
await database.close();
return options;
}
function authorityFor(options) {
const root = fs.lstatSync(options.deploymentRoot, { bigint: true });
const uid = process.getuid();
const proofDigest = createHash('sha256')
.update('qinglong.local-owner-console.proof.v1\0', 'utf8')
.update(process.platform, 'utf8')
.update('\0', 'utf8')
.update(String(uid), 'utf8')
.update('\0', 'utf8')
.update(root.dev.toString(), 'utf8')
.update('\0', 'utf8')
.update(root.ino.toString(), 'utf8')
.digest('hex');
const authenticatedAtMs = Date.now();
return {
subject: { type: 'system', id: 'owner-bootstrap' },
authenticationId: `local-console:${proofDigest}`,
authenticatedAtMs,
expiresAtMs: authenticatedAtMs + 60_000,
assurance: 'local_console',
};
}
test('proves one bounded delivery crash bridge is clear', (t) => {
const options = fixture(t);
const delivery = new FileLocalOwnerBootstrapSecretDelivery(
options.secretDeliveryDirectory,
);
const mutationId = '00000000-0000-4000-8000-000000000b01';
const evidence = delivery.inspectBridgeClear('credential', mutationId);
assert.equal(evidence.kind, 'credential');
assert.equal(evidence.acknowledgementMutationId, mutationId);
assert.match(evidence.evidenceDigest, /^[0-9a-f]{64}$/);
fs.writeFileSync(
path.join(
options.secretDeliveryDirectory,
`credential-${mutationId}.pending.json`,
),
'{}',
{ mode: 0o600 },
);
assert.throws(
() => delivery.inspectBridgeClear('credential', mutationId),
/crash bridge is not clear/,
);
});
async function directService(options, secretDelivery) {
const database = await openLocalSqliteBootstrapDatabase({
databasePath: options.databasePath,
profile: options.profile,
});
const service = createLocalOwnerBootstrapService(
database.ownerBootstrap,
database.apiCredentials,
fs.readFileSync(options.pepperPath, 'utf8'),
authorityFor(options),
{ secretDelivery },
);
return { database, service };
}
test('binds POSIX proof at composition time and removes issuer from requests', async (t) => {
const options = await ready(t);
const console = await openLocalOwnerConsole(options);
t.after(() => console.close());
assert.deepEqual(console.recovery, {
inspectedPendingRecords: 0,
publishedRecords: 0,
retainedUncommittedRecords: 0,
orphanTemporaryRecords: 0,
});
const provisioned = await console.service.provision({
mutationId: '00000000-0000-4000-8000-000000000201',
requestId: 'console-provision-201',
});
assert.equal(provisioned.status, 'inserted');
assert.equal(provisioned.credentialToken, null);
const credentialPath = console.credentialDeliveryPath(
'00000000-0000-4000-8000-000000000201',
);
assert.equal(fs.statSync(credentialPath).mode & 0o777, 0o600);
const credential = JSON.parse(fs.readFileSync(credentialPath, 'utf8'));
assert.equal(credential.kind, 'credential');
assert.equal(credential.credentialId, provisioned.credentialId);
const issued = await console.service.issue({
projectId: 'default',
mutationId: '00000000-0000-4000-8000-000000000202',
requestId: 'console-issue-202',
});
assert.equal(issued.status, 'inserted');
assert.equal(issued.challengeToken, null);
const challengePath = console.challengeDeliveryPath(
'00000000-0000-4000-8000-000000000202',
);
assert.equal(fs.statSync(challengePath).mode & 0o777, 0o600);
const challenge = JSON.parse(fs.readFileSync(challengePath, 'utf8'));
assert.equal(challenge.kind, 'challenge');
assert.equal(challenge.challengeId, issued.challengeId);
const claimed = await console.service.claim({
projectId: 'default',
mutationId: '00000000-0000-4000-8000-000000000205',
requestId: 'console-claim-205',
challengeId: challenge.challengeId,
challengeToken: challenge.secret,
credentialToken: formatApiCredentialToken(
credential.credentialId,
credential.secret,
),
});
assert.equal(claimed.status, 'inserted');
const databaseBytes = fs.readFileSync(options.databasePath);
assert.equal(databaseBytes.includes(credential.secret), false);
assert.equal(databaseBytes.includes(challenge.secret), false);
await assert.rejects(
console.service.issue({
projectId: 'default',
mutationId: '00000000-0000-4000-8000-000000000206',
requestId: 'console-issue-206',
issuer: {
subject: { type: 'system', id: 'owner-bootstrap' },
authenticationId: 'forged',
authenticatedAtMs: Date.now(),
expiresAtMs: Date.now() + 60_000,
assurance: 'local_console',
},
}),
);
});
test('claims the first Owner from staged deliveries without crossing the transport with secrets', async (t) => {
const options = await ready(t);
const console = await openLocalOwnerConsole(options);
t.after(() => console.close());
const credentialMutationId = '00000000-0000-4000-8000-000000000711';
const challengeMutationId = '00000000-0000-4000-8000-000000000712';
const claimMutationId = '00000000-0000-4000-8000-000000000713';
await console.service.provision({
mutationId: credentialMutationId,
requestId: 'console-provision-711',
});
await console.service.issue({
projectId: 'default',
mutationId: challengeMutationId,
requestId: 'console-issue-712',
});
const credentialDelivery =
console.inspectCredentialDelivery(credentialMutationId);
const challengeDelivery =
console.inspectChallengeDelivery(challengeMutationId);
const claimed = await console.claimOwnerFromDeliveries({
projectId: 'default',
mutationId: claimMutationId,
requestId: 'console-claim-713',
credentialMutationId,
challengeMutationId,
});
assert.equal(claimed.status, 'inserted');
assert.equal(claimed.binding.role, 'owner');
assert.equal(JSON.stringify(claimed).includes('secret'), false);
await console.acknowledgeCredentialDelivery(
credentialMutationId,
credentialDelivery.deliveryDigest,
);
await console.acknowledgeChallengeDelivery(
challengeMutationId,
challengeDelivery.deliveryDigest,
);
const replay = await console.claimOwnerFromDeliveries({
projectId: 'default',
mutationId: claimMutationId,
requestId: 'console-claim-713',
credentialMutationId,
challengeMutationId,
});
assert.equal(replay.status, 'existing');
await assert.rejects(
console.claimOwnerFromDeliveries({
projectId: 'default',
mutationId: '00000000-0000-4000-8000-000000000714',
requestId: 'console-claim-714',
credentialMutationId,
challengeMutationId,
challengeToken: 'forbidden',
}),
LocalOwnerSecretDeliveryError,
);
});
test('recovers one credential without revoking the old token before delivery acknowledgement', async (t) => {
const options = await ready(t);
const first = await openLocalOwnerConsole(options);
const provisionMutationId = '00000000-0000-4000-8000-000000000701';
const provisioned = await first.service.provision({
mutationId: provisionMutationId,
requestId: 'console-provision-701',
});
const provisionDelivery =
first.inspectCredentialDelivery(provisionMutationId);
await first.acknowledgeCredentialDelivery(
provisionMutationId,
provisionDelivery.deliveryDigest,
);
const issueMutationId = '00000000-0000-4000-8000-000000000702';
const issued = await first.credentialRecovery.issue({
mutationId: issueMutationId,
requestId: 'console-recover-issue-702',
previousCredentialId: provisioned.credentialId,
expectedPreviousVersion: 1,
});
assert.equal(issued.status, 'inserted');
assert.equal(issued.state, 'issued');
assert.equal(issued.replacementCredentialToken, null);
const recoveryDelivery = first.inspectCredentialDelivery(issueMutationId);
await assert.rejects(
first.credentialRecovery.complete({
issueMutationId,
mutationId: '00000000-0000-4000-8000-000000000703',
requestId: 'console-recover-complete-703',
}),
);
const beforeAcknowledgement = await openLocalSqliteBootstrapDatabase({
databasePath: options.databasePath,
profile: options.profile,
});
assert.equal(
(
await beforeAcknowledgement.apiCredentials.resolve(
provisioned.credentialId,
)
).state,
'active',
);
await beforeAcknowledgement.close();
await first.close();
const restarted = await openLocalOwnerConsole(options);
t.after(() => restarted.close());
assert.equal(fs.existsSync(recoveryDelivery.path), true);
await restarted.acknowledgeCredentialRecoveryDelivery(
issueMutationId,
recoveryDelivery.deliveryDigest,
);
const completed = await restarted.credentialRecovery.complete({
issueMutationId,
mutationId: '00000000-0000-4000-8000-000000000703',
requestId: 'console-recover-complete-703',
});
assert.equal(completed.state, 'completed');
assert.equal(fs.existsSync(recoveryDelivery.path), false);
const database = await openLocalSqliteBootstrapDatabase({
databasePath: options.databasePath,
profile: options.profile,
});
assert.equal(
(await database.apiCredentials.resolve(provisioned.credentialId)).state,
'revoked',
);
assert.equal(
(await database.apiCredentials.resolve(issued.replacementCredentialId))
.state,
'active',
);
await database.close();
const replay = await restarted.credentialRecovery.issue({
mutationId: issueMutationId,
requestId: 'console-recover-issue-702',
previousCredentialId: provisioned.credentialId,
expectedPreviousVersion: 1,
});
assert.equal(replay.status, 'existing');
assert.equal(replay.state, 'completed');
assert.equal(replay.replacementCredentialToken, null);
});
test('acknowledges exact ready records and replays without regenerating secrets', async (t) => {
const options = await ready(t);
const console = await openLocalOwnerConsole(options);
const credentialMutationId = '00000000-0000-4000-8000-000000000207';
const provisionRequestId = 'console-provision-207';
const provisioned = await console.service.provision({
mutationId: credentialMutationId,
requestId: provisionRequestId,
});
const credentialSummary =
console.inspectCredentialDelivery(credentialMutationId);
const credentialReady = fs.readFileSync(credentialSummary.path);
await assert.rejects(
console.acknowledgeCredentialDelivery(credentialMutationId, '0'.repeat(64)),
LocalOwnerSecretDeliveryError,
);
assert.equal(fs.existsSync(credentialSummary.path), true);
const acknowledgementDatabaseA = await openLocalSqliteBootstrapDatabase({
databasePath: options.databasePath,
profile: options.profile,
});
const acknowledgementDatabaseB = await openLocalSqliteBootstrapDatabase({
databasePath: options.databasePath,
profile: options.profile,
});
const deliveryA = new FileLocalOwnerBootstrapSecretDelivery(
options.secretDeliveryDirectory,
);
const deliveryB = new FileLocalOwnerBootstrapSecretDelivery(
options.secretDeliveryDirectory,
);
const pepper = fs.readFileSync(options.pepperPath, 'utf8');
const concurrentAcknowledgements = await Promise.all([
deliveryA.acknowledge(
acknowledgementDatabaseA.ownerBootstrap,
pepper,
'credential',
credentialMutationId,
credentialSummary.deliveryDigest,
1,
),
deliveryB.acknowledge(
acknowledgementDatabaseB.ownerBootstrap,
pepper,
'credential',
credentialMutationId,
credentialSummary.deliveryDigest,
2,
),
]);
await Promise.all([
acknowledgementDatabaseA.close(),
acknowledgementDatabaseB.close(),
]);
assert.deepEqual(
concurrentAcknowledgements[0],
concurrentAcknowledgements[1],
);
const credentialAcknowledgement = await console.acknowledgeCredentialDelivery(
credentialMutationId,
credentialSummary.deliveryDigest,
);
assert.deepEqual(credentialAcknowledgement, {
state: 'acknowledged',
kind: 'credential',
mutationId: credentialMutationId,
requestId: provisionRequestId,
ttlMs: LOCAL_IDENTITY_BOOTSTRAP_DEFAULT_TTL_MS,
});
assert.equal(fs.existsSync(credentialSummary.path), false);
const credentialAcknowledgementPath = path.join(
options.secretDeliveryDirectory,
`credential-${credentialMutationId}.acknowledged.json`,
);
assert.equal(fs.existsSync(credentialAcknowledgementPath), false);
const ledgerDatabase = new DatabaseSync(options.databasePath);
const credentialTombstone = ledgerDatabase
.prepare(
`SELECT * FROM "QingLong3LocalOwnerDeliveryAcknowledgements"
WHERE "mutation_id" = ?`,
)
.get(credentialMutationId);
ledgerDatabase.close();
assert.equal(
credentialTombstone.delivery_digest,
credentialSummary.deliveryDigest,
);
assert.equal([1, 2].includes(credentialTombstone.acknowledged_at_ms), true);
assert.equal(Object.keys(credentialTombstone).includes('secret'), false);
const replayedProvision = await console.service.provision({
mutationId: credentialMutationId,
requestId: provisionRequestId,
});
assert.equal(replayedProvision.status, 'existing');
assert.equal(replayedProvision.subjectId, provisioned.subjectId);
assert.equal(replayedProvision.credentialId, provisioned.credentialId);
assert.equal(replayedProvision.credentialToken, null);
const challengeMutationId = '00000000-0000-4000-8000-000000000208';
const issueRequestId = 'console-issue-208';
const issued = await console.service.issue({
projectId: 'default',
mutationId: challengeMutationId,
requestId: issueRequestId,
});
const challengeSummary =
console.inspectChallengeDelivery(challengeMutationId);
const challengeAcknowledgement = await console.acknowledgeChallengeDelivery(
challengeMutationId,
challengeSummary.deliveryDigest,
);
assert.deepEqual(challengeAcknowledgement, {
state: 'acknowledged',
kind: 'challenge',
projectId: 'default',
mutationId: challengeMutationId,
requestId: issueRequestId,
ttlMs: 600_000,
});
const replayedIssue = await console.service.issue({
projectId: 'default',
mutationId: challengeMutationId,
requestId: issueRequestId,
});
assert.equal(replayedIssue.status, 'existing');
assert.equal(replayedIssue.challengeId, issued.challengeId);
assert.equal(replayedIssue.challengeToken, null);
fs.writeFileSync(credentialSummary.path, credentialReady, { mode: 0o600 });
await console.close();
const recovered = await openLocalOwnerConsole(options);
t.after(() => recovered.close());
assert.equal(fs.existsSync(credentialSummary.path), false);
assert.deepEqual(recovered.recovery, {
inspectedPendingRecords: 0,
publishedRecords: 0,
retainedUncommittedRecords: 0,
orphanTemporaryRecords: 0,
});
const recoveredReplay = await recovered.service.provision({
mutationId: credentialMutationId,
requestId: provisionRequestId,
});
assert.equal(recoveredReplay.status, 'existing');
assert.equal(recoveredReplay.credentialToken, null);
});
test('retains a pre-commit secret and publishes it after the matching commit', async (t) => {
const options = await ready(t);
const delivery = new FileLocalOwnerBootstrapSecretDelivery(
options.secretDeliveryDirectory,
);
const mutationId = '00000000-0000-4000-8000-000000000211';
const staged = await delivery.prepare({
kind: 'credential',
mutationId,
requestId: 'console-provision-211',
subjectId: `usr_${Buffer.alloc(16, 11).toString('base64url')}`,
credentialId: `own_${Buffer.alloc(16, 12).toString('base64url')}`,
secret: Buffer.alloc(32, 13).toString('base64url'),
ttlMs: LOCAL_IDENTITY_BOOTSTRAP_DEFAULT_TTL_MS,
});
const pendingPath = delivery
.readyPath('credential', mutationId)
.replace('.ready.json', '.pending.json');
assert.equal(fs.existsSync(pendingPath), true);
const console = await openLocalOwnerConsole(options);
t.after(() => console.close());
assert.deepEqual(console.recovery, {
inspectedPendingRecords: 1,
publishedRecords: 0,
retainedUncommittedRecords: 1,
orphanTemporaryRecords: 0,
});
const provisioned = await console.service.provision({
mutationId,
requestId: staged.requestId,
});
assert.equal(provisioned.status, 'inserted');
assert.equal(provisioned.subjectId, staged.subjectId);
assert.equal(provisioned.credentialId, staged.credentialId);
assert.equal(provisioned.credentialToken, null);
assert.equal(fs.existsSync(pendingPath), false);
assert.equal(fs.existsSync(console.credentialDeliveryPath(mutationId)), true);
});
test('recovers database-committed credential and challenge after publish failure', async (t) => {
const options = await ready(t);
const delivery = new FileLocalOwnerBootstrapSecretDelivery(
options.secretDeliveryDirectory,
);
const failingDelivery = {
prepare(candidate) {
return delivery.prepare(candidate);
},
async publish() {
throw new Error('injected publish failure');
},
};
const provisionMutationId = '00000000-0000-4000-8000-000000000221';
const first = await directService(options, failingDelivery);
await assert.rejects(
first.service.provision({
mutationId: provisionMutationId,
requestId: 'console-provision-221',
}),
LocalOwnerBootstrapServiceUnavailableError,
);
await first.database.close();
const recoveredCredential = await openLocalOwnerConsole(options);
assert.deepEqual(recoveredCredential.recovery, {
inspectedPendingRecords: 1,
publishedRecords: 1,
retainedUncommittedRecords: 0,
orphanTemporaryRecords: 0,
});
const replayedProvision = await recoveredCredential.service.provision({
mutationId: provisionMutationId,
requestId: 'console-provision-221',
});
assert.equal(replayedProvision.status, 'existing');
assert.equal(replayedProvision.credentialToken, null);
await recoveredCredential.close();
const challengeMutationId = '00000000-0000-4000-8000-000000000222';
const second = await directService(options, failingDelivery);
await assert.rejects(
second.service.issue({
projectId: 'default',
mutationId: challengeMutationId,
requestId: 'console-issue-222',
}),
LocalOwnerBootstrapServiceUnavailableError,
);
await second.database.close();
const recoveredChallenge = await openLocalOwnerConsole(options);
t.after(() => recoveredChallenge.close());
assert.deepEqual(recoveredChallenge.recovery, {
inspectedPendingRecords: 1,
publishedRecords: 1,
retainedUncommittedRecords: 0,
orphanTemporaryRecords: 0,
});
const replayedIssue = await recoveredChallenge.service.issue({
projectId: 'default',
mutationId: challengeMutationId,
requestId: 'console-issue-222',
});
assert.equal(replayedIssue.status, 'existing');
assert.equal(replayedIssue.challengeToken, null);
});
test('fails closed on tampered delivery records and bounded-directory overflow', async (t) => {
await t.test('private record mode', async (t) => {
const options = await ready(t);
const console = await openLocalOwnerConsole(options);
const mutationId = '00000000-0000-4000-8000-000000000231';
await console.service.provision({
mutationId,
requestId: 'console-provision-231',
});
const recordPath = console.credentialDeliveryPath(mutationId);
await console.close();
fs.chmodSync(recordPath, 0o644);
await assert.rejects(
openLocalOwnerConsole(options),
LocalOwnerSecretDeliveryError,
);
});
await t.test('database digest mismatch', async (t) => {
const options = await ready(t);
const console = await openLocalOwnerConsole(options);
const mutationId = '00000000-0000-4000-8000-000000000232';
await console.service.provision({
mutationId,
requestId: 'console-provision-232',
});
const recordPath = console.credentialDeliveryPath(mutationId);
const record = JSON.parse(fs.readFileSync(recordPath, 'utf8'));
await console.close();
record.secret = Buffer.alloc(32, 99).toString('base64url');
fs.writeFileSync(recordPath, `${JSON.stringify(record)}\n`, {
mode: 0o600,
});
await assert.rejects(
openLocalOwnerConsole(options),
LocalOwnerSecretDeliveryError,
);
});
await t.test('entry budget', async (t) => {
const options = await ready(t);
for (let index = 0; index < 65; index += 1) {
const name = `.credential-${randomUUID()}.${randomUUID()}.tmp`;
fs.writeFileSync(path.join(options.secretDeliveryDirectory, name), 'x', {
mode: 0o600,
});
}
await assert.rejects(
openLocalOwnerConsole(options),
LocalOwnerSecretDeliveryError,
);
});
await t.test('tampered acknowledgement fact', async (t) => {
const options = await ready(t);
const console = await openLocalOwnerConsole(options);
const mutationId = '00000000-0000-4000-8000-000000000233';
await console.service.provision({
mutationId,
requestId: 'console-provision-233',
});
const summary = console.inspectCredentialDelivery(mutationId);
await console.acknowledgeCredentialDelivery(
mutationId,
summary.deliveryDigest,
);
await console.close();
const database = new DatabaseSync(options.databasePath);
database
.prepare(
`UPDATE "QingLong3LocalOwnerDeliveryAcknowledgements"
SET "fact_digest" = ? WHERE "mutation_id" = ?`,
)
.run('0'.repeat(64), mutationId);
database.close();
const reopened = await openLocalOwnerConsole(options);
t.after(() => reopened.close());
await assert.rejects(
reopened.service.provision({
mutationId,
requestId: 'console-provision-233',
}),
LocalOwnerBootstrapServiceUnavailableError,
);
});
await t.test('acknowledged mutation with pending record', async (t) => {
const options = await ready(t);
const console = await openLocalOwnerConsole(options);
const mutationId = '00000000-0000-4000-8000-000000000234';
await console.service.provision({
mutationId,
requestId: 'console-provision-234',
});
const summary = console.inspectCredentialDelivery(mutationId);
const readyMaterial = fs.readFileSync(summary.path);
await console.acknowledgeCredentialDelivery(
mutationId,
summary.deliveryDigest,
);
await console.close();
fs.writeFileSync(
summary.path.replace('.ready.json', '.pending.json'),
readyMaterial,
{ mode: 0o600 },
);
await assert.rejects(
openLocalOwnerConsole(options),
LocalOwnerSecretDeliveryError,
);
});
});
test('cleans staged files after ENOSPC and read-only delivery failures', async (t) => {
for (const [index, code] of ['ENOSPC', 'EROFS'].entries()) {
await t.test(code, async (t) => {
const options = await ready(t);
const console = await openLocalOwnerConsole(options);
t.after(() => console.close());
const originalWriteFileSync = fs.writeFileSync;
fs.writeFileSync = function injectedWriteFailure(target, ...args) {
if (typeof target === 'number') {
throw Object.assign(new Error(`injected ${code}`), { code });
}
return originalWriteFileSync.call(this, target, ...args);
};
try {
await assert.rejects(
console.service.provision({
mutationId: `00000000-0000-4000-8000-00000000024${index}`,
requestId: `console-provision-24${index}`,
}),
LocalOwnerBootstrapServiceUnavailableError,
);
} finally {
fs.writeFileSync = originalWriteFileSync;
}
assert.deepEqual(fs.readdirSync(options.secretDeliveryDirectory), []);
});
}
});
test('rejects broad deployment permissions and pepper symlinks', async (t) => {
const broad = await ready(t);
fs.chmodSync(broad.deploymentRoot, 0o755);
await assert.rejects(
openLocalOwnerConsole(broad),
LocalOwnerConsoleConfigurationError,
);
const linked = await ready(t);
const actualPepper = path.join(linked.deploymentRoot, 'actual.pepper');
fs.renameSync(linked.pepperPath, actualPepper);
fs.symlinkSync(actualPepper, linked.pepperPath);
await assert.rejects(
openLocalOwnerConsole(linked),
LocalOwnerConsoleConfigurationError,
);
});
test('rechecks database identity before every authority operation', async (t) => {
const options = await ready(t);
const console = await openLocalOwnerConsole(options);
t.after(() => console.close());
const moved = path.join(options.deploymentRoot, 'moved.sqlite');
fs.renameSync(options.databasePath, moved);
fs.copyFileSync(moved, options.databasePath);
fs.chmodSync(options.databasePath, 0o600);
await assert.rejects(
async () =>
console.service.provision({
mutationId: '00000000-0000-4000-8000-000000000203',
requestId: 'console-provision-203',
}),
LocalOwnerConsoleConfigurationError,
);
});
test('close is idempotent and no CLI or default-runtime authority is exported', async (t) => {
const options = await ready(t);
const console = await openLocalOwnerConsole(options);
await Promise.all([console.close(), console.close()]);
await assert.rejects(
console.service.provision({
mutationId: '00000000-0000-4000-8000-000000000204',
requestId: 'console-provision-204',
}),
);
const manifest = require('../package.json');
assert.equal('bin' in manifest, false);
const localRuntime = require('@qinglong/local-sqlite/runtime');
assert.equal('openLocalOwnerConsole' in localRuntime, false);
});
@@ -0,0 +1,106 @@
const assert = require('node:assert/strict');
const fs = require('node:fs');
const os = require('node:os');
const path = require('node:path');
const { test } = require('node:test');
const {
FileLocalCredentialAdministrationDelivery,
LocalCredentialAdministrationDeliveryError,
} = require('@qinglong/local-owner-console/credential-administration-delivery');
const MUTATION_ID = '81000000-0000-4000-8000-000000000001';
const RECOVERY_MUTATION_ID = '81000000-0000-4000-8000-000000000002';
const SECRET = Buffer.alloc(32, 81).toString('base64url');
function fixture(t) {
const root = fs.mkdtempSync(
path.join(fs.realpathSync(os.tmpdir()), 'ql3-managed-credential-'),
);
fs.chmodSync(root, 0o700);
t.after(() => fs.rmSync(root, { recursive: true, force: true }));
return root;
}
function record(overrides = {}) {
return {
schemaVersion: 1,
kind: 'qinglong3-local-managed-credential-delivery',
mutationId: MUTATION_ID,
requestId: 'managed-credential-issue',
projectId: 'default',
subject: { type: 'agent', id: 'agent-planner' },
credentialId: 'agent-planner-primary',
secret: SECRET,
notBeforeAtMs: 1_000,
expiresAtMs: 61_000,
...overrides,
};
}
test('stages, replays, publishes and acknowledges one private credential', (t) => {
const directory = fixture(t);
const delivery = new FileLocalCredentialAdministrationDelivery(directory);
const first = delivery.prepare(record());
const digest = delivery.digest(first);
const replay = delivery.prepare(
record({
secret: Buffer.alloc(32, 82).toString('base64url'),
notBeforeAtMs: 11_000,
expiresAtMs: 71_000,
}),
);
assert.equal(replay.secret, SECRET);
assert.equal(replay.notBeforeAtMs, 1_000);
assert.equal(delivery.digest(replay), digest);
const published = delivery.publish(replay, digest);
assert.equal(
path.basename(published.path),
`managed-credential-${MUTATION_ID}.ready.json`,
);
assert.equal(fs.statSync(published.path).mode & 0o777, 0o600);
assert.equal(delivery.inspect(MUTATION_ID).deliveryDigest, digest);
const presentation = JSON.parse(fs.readFileSync(published.path, 'utf8'));
assert.equal(
presentation.kind,
'qinglong3-local-identity-credential-presentation',
);
assert.match(presentation.token, /^ql3c_agent-planner-primary_/);
assert.equal(delivery.removeAcknowledged(MUTATION_ID, digest), 'removed');
assert.equal(delivery.removeAcknowledged(MUTATION_ID, digest), 'absent');
const recovery = delivery.prepare(
record({
mutationId: RECOVERY_MUTATION_ID,
requestId: 'managed-credential-cleanup-recovery',
}),
);
const recoveryDigest = delivery.digest(recovery);
const recoveryReady = delivery.publish(recovery, recoveryDigest);
fs.unlinkSync(recoveryReady.path);
assert.equal(
delivery.removeAcknowledged(RECOVERY_MUTATION_ID, recoveryDigest),
'removed',
);
});
test('rejects semantic replay drift and a symlinked delivery directory', (t) => {
const directory = fixture(t);
const delivery = new FileLocalCredentialAdministrationDelivery(directory);
delivery.prepare(record());
assert.throws(
() => delivery.prepare(record({ expiresAtMs: 62_000 })),
LocalCredentialAdministrationDeliveryError,
);
const link = `${directory}-link`;
fs.symlinkSync(directory, link);
t.after(() => fs.rmSync(link, { force: true }));
assert.throws(
() => new FileLocalCredentialAdministrationDelivery(link),
LocalCredentialAdministrationDeliveryError,
);
});
@@ -0,0 +1,95 @@
const assert = require('node:assert/strict');
const { test } = require('node:test');
const {
createLocalOwnerCredentialRecoveryService,
} = require('../dist/credential-recovery');
test('issues a distinct credential and replays acknowledged completion', async () => {
const previous = {
credentialId: `own_${'a'.repeat(22)}`,
version: 1,
pepperKeyId: 'owner-key-1',
state: 'active',
subject: { type: 'user', id: `usr_${'b'.repeat(22)}` },
subjectStatus: 'active',
secretDigest: '1'.repeat(64),
createdAtMs: 100,
notBeforeAtMs: 100,
expiresAtMs: 100000000,
};
let recovery = null;
const credentials = {
async resolve(credentialId) {
return credentialId === previous.credentialId ? previous : null;
},
};
const repository = {
async resolve() {
return recovery;
},
async issue(command) {
recovery = {
issueMutationId: command.mutationId,
issueRequestId: command.requestId,
subjectId: command.replacementCredential.subject.id,
previousCredentialId: command.previousCredentialId,
previousCredentialVersion: command.expectedPreviousVersion,
replacementCredential: command.replacementCredential,
state: 'issued',
issuedAtMs: command.replacementCredential.createdAtMs,
};
return { status: 'inserted', recovery };
},
async acknowledge() {
throw new Error('not used');
},
async complete(command) {
assert.equal(recovery.state, 'acknowledged');
recovery = {
...recovery,
state: 'completed',
completeMutationId: command.mutationId,
completeRequestId: command.requestId,
revokedCredentialVersion: command.revokedCredential.version,
completedAtMs: command.revokedCredential.createdAtMs,
};
return { status: 'inserted', recovery };
},
};
let nowMs = 1000;
const service = createLocalOwnerCredentialRecoveryService(
repository,
credentials,
Buffer.alloc(32, 7).toString('base64url'),
{
pepperKeyId: 'owner-key-1',
now: () => nowMs,
randomBytes: (size) => Buffer.alloc(size, size),
},
);
const issueMutationId = '00000000-0000-4000-8000-000000000801';
const issued = await service.issue({
mutationId: issueMutationId,
requestId: 'recover-issue-801',
previousCredentialId: previous.credentialId,
expectedPreviousVersion: 1,
});
assert.equal(issued.status, 'inserted');
assert.notEqual(issued.replacementCredentialId, previous.credentialId);
assert.match(issued.replacementCredentialToken, /^ql3c_/);
recovery = {
...recovery,
state: 'acknowledged',
deliveryDigest: '2'.repeat(64),
acknowledgedAtMs: 1100,
};
nowMs = 1200;
const completion = {
issueMutationId,
mutationId: '00000000-0000-4000-8000-000000000802',
requestId: 'recover-complete-802',
};
assert.equal((await service.complete(completion)).status, 'inserted');
assert.equal((await service.complete(completion)).status, 'existing');
});
@@ -0,0 +1,517 @@
const assert = require('node:assert/strict');
const { createHash } = require('node:crypto');
const fs = require('node:fs');
const os = require('node:os');
const path = require('node:path');
const { DatabaseSync } = require('node:sqlite');
const { test } = require('node:test');
const {
ApiCredentialUnavailableError,
} = require('@qinglong/runtime-core/api-credential');
const {
apiCredentialSecretDigest,
formatApiCredentialToken,
} = require('@qinglong/runtime-core/api-credential-token');
const { migrateLocalSqlitePath } = require('@qinglong/local-sqlite/migration');
const {
openLocalSqliteBootstrapDatabase,
} = require('@qinglong/local-sqlite/bootstrap');
const {
openLocalSqliteRuntimeDatabase,
} = require('@qinglong/local-sqlite/runtime');
const {
LocalOwnerPepperKeyringFileProvider,
provisionLocalOwnerPepperKey,
restoreLocalOwnerPepperKey,
} = require('@qinglong/local-owner-console/pepper-custody');
const {
LocalIdentityAuthenticationConfigurationError,
LocalIdentityAuthenticationUnavailableError,
createLocalIdentityAuthenticator,
createLocalIdentityKeyringAuthenticator,
} = require('@qinglong/local-owner-console/identity-authentication');
const NOW = 1_800_000_000_000;
const PEPPER = Buffer.alloc(32, 7).toString('base64url');
const SECRET = Buffer.alloc(32, 11).toString('base64url');
const CREDENTIAL_ID = 'fresh-owner';
const TOKEN = formatApiCredentialToken(CREDENTIAL_ID, SECRET);
function fixture(t) {
const directory = fs.mkdtempSync(
path.join(os.tmpdir(), 'ql3-local-identity-'),
);
const databasePath = path.join(directory, 'qinglong3.sqlite');
t.after(() => fs.rmSync(directory, { recursive: true, force: true }));
return databasePath;
}
function seed(databasePath, options = {}) {
const client = new DatabaseSync(databasePath);
try {
const materialDigest = createHash('sha256')
.update('qinglong.local-owner-pepper.summary.v1\0', 'utf8')
.update(PEPPER, 'utf8')
.digest('hex');
if (options.recoveryRequired) {
client
.prepare(
`INSERT INTO "QingLong3LocalOwnerPepperKeys" (
"pepper_key_id", "state", "version", "registered_at_ms"
) VALUES ('legacy-v1', 'recovery_required', 1, 0)`,
)
.run();
} else {
client
.prepare(
`INSERT INTO "QingLong3LocalOwnerPepperKeys" (
"pepper_key_id", "material_digest", "backup_digest", "state",
"version", "register_mutation_id", "activate_mutation_id",
"registered_at_ms", "activated_at_ms"
) VALUES (
'legacy-v1', ?, ?, 'active', 2,
'00000000-0000-4000-8000-000000000091',
'00000000-0000-4000-8000-000000000092', ?, ?
)`,
)
.run(materialDigest, 'b'.repeat(64), NOW - 2_000, NOW - 1_500);
client
.prepare(
`INSERT INTO "QingLong3LocalOwnerPepperActivations" (
"generation", "mutation_id", "expected_generation",
"previous_pepper_key_id", "active_pepper_key_id",
"material_digest", "backup_digest", "activated_at_ms"
) VALUES (
1, '00000000-0000-4000-8000-000000000092', 0,
NULL, 'legacy-v1', ?, ?, ?
)`,
)
.run(materialDigest, 'b'.repeat(64), NOW - 1_500);
}
client
.prepare(
`INSERT INTO "QingLong3IdentitySubjects" (
"subject_type", "subject_id", "status", "version",
"created_at_ms", "updated_at_ms"
) VALUES ('user', 'user-01', ?, 1, ?, ?)`,
)
.run(options.subjectStatus ?? 'active', NOW - 1_000, NOW - 1_000);
client
.prepare(
`INSERT INTO "QingLong3ApiCredentials" (
"credential_id", "version", "state", "subject_type",
"subject_id", "secret_digest", "created_at_ms",
"not_before_at_ms", "expires_at_ms"
) VALUES (?, 1, ?, 'user', 'user-01', ?, ?, ?, ?)`,
)
.run(
CREDENTIAL_ID,
options.state ?? 'active',
apiCredentialSecretDigest(PEPPER, CREDENTIAL_ID, SECRET),
NOW - 1_000,
options.notBeforeAtMs ?? NOW - 1_000,
options.expiresAtMs ?? NOW + 600_000,
);
if (!options.omitPepperBinding) {
client
.prepare(
`INSERT INTO "QingLong3ApiCredentialPepperBindings" (
"credential_id", "credential_version", "pepper_key_id"
) VALUES (?, 1, 'legacy-v1')`,
)
.run(CREDENTIAL_ID);
}
} finally {
client.close();
}
}
test('authenticates one stable local User through the shared SQLite authority', async (t) => {
const databasePath = fixture(t);
await migrateLocalSqlitePath({ databasePath, profile: 'edge' });
seed(databasePath);
const runtime = await openLocalSqliteRuntimeDatabase({
databasePath,
profile: 'edge',
});
const authenticator = createLocalIdentityAuthenticator(
runtime.apiCredentials,
PEPPER,
{ now: () => NOW },
);
const principal = await authenticator.authenticate(TOKEN);
assert.deepEqual(principal, {
subject: { type: 'user', id: 'user-01' },
authenticationId: 'local_credential:fresh-owner:1',
authenticatedAtMs: NOW,
expiresAtMs: NOW + 60_000,
assurance: 'single_factor',
});
const authentication = await authenticator.authenticateCredential(TOKEN);
assert.deepEqual(authentication, {
principal,
credentialId: CREDENTIAL_ID,
credentialVersion: 1,
});
await runtime.close();
});
test('authenticates through the runtime catalog and bounded POSIX keyring', async (t) => {
const databasePath = fixture(t);
const keyringDirectory = path.join(path.dirname(databasePath), 'keyring');
fs.mkdirSync(keyringDirectory, { mode: 0o700 });
provisionLocalOwnerPepperKey({
keyringDirectory,
pepperKeyId: 'legacy-v1',
randomBytes: () => Buffer.alloc(32, 7),
});
await migrateLocalSqlitePath({ databasePath, profile: 'edge' });
seed(databasePath);
const runtime = await openLocalSqliteRuntimeDatabase({
databasePath,
profile: 'edge',
});
const authenticator = createLocalIdentityKeyringAuthenticator(
runtime.apiCredentials,
runtime.ownerPepper,
new LocalOwnerPepperKeyringFileProvider(keyringDirectory),
{ now: () => NOW },
);
assert.equal(
(await authenticator.authenticate(TOKEN))?.subject.id,
'user-01',
);
await runtime.close();
await assert.rejects(
authenticator.authenticate(TOKEN),
LocalIdentityAuthenticationUnavailableError,
);
});
test('restores a recovery-required legacy key before explicit activation', async (t) => {
const databasePath = fixture(t);
const keyringDirectory = path.join(path.dirname(databasePath), 'keyring');
const backupDirectory = path.join(path.dirname(databasePath), 'backup');
fs.mkdirSync(keyringDirectory, { mode: 0o700 });
fs.mkdirSync(backupDirectory, { mode: 0o700 });
const backup = provisionLocalOwnerPepperKey({
keyringDirectory: backupDirectory,
pepperKeyId: 'legacy-v1',
randomBytes: () => Buffer.alloc(32, 7),
});
await migrateLocalSqlitePath({ databasePath, profile: 'edge' });
seed(databasePath, { recoveryRequired: true });
assert.deepEqual(
restoreLocalOwnerPepperKey({
keyringDirectory,
backupDirectory,
pepperKeyId: 'legacy-v1',
}),
backup,
);
const bootstrap = await openLocalSqliteBootstrapDatabase({
databasePath,
profile: 'edge',
});
assert.equal(
(await bootstrap.ownerPepper.resolveKey('legacy-v1'))?.state,
'recovery_required',
);
await bootstrap.ownerPepper.register({
mutationId: '00000000-0000-4000-8000-000000000093',
pepperKeyId: 'legacy-v1',
materialDigest: backup.digest,
backupDigest: backup.digest,
registeredAtMs: NOW - 900,
});
await bootstrap.ownerPepper.activate({
mutationId: '00000000-0000-4000-8000-000000000094',
pepperKeyId: 'legacy-v1',
expectedGeneration: 0,
activatedAtMs: NOW - 800,
});
await bootstrap.close();
const runtime = await openLocalSqliteRuntimeDatabase({
databasePath,
profile: 'edge',
});
const authenticator = createLocalIdentityKeyringAuthenticator(
runtime.apiCredentials,
runtime.ownerPepper,
new LocalOwnerPepperKeyringFileProvider(keyringDirectory),
{ now: () => NOW },
);
assert.equal(
(await authenticator.authenticate(TOKEN))?.subject.id,
'user-01',
);
await runtime.close();
});
test('resolves active and retired credential keys through the exact catalog identity', async () => {
const oldPepper = Buffer.alloc(32, 21).toString('base64url');
const newPepper = Buffer.alloc(32, 22).toString('base64url');
const newSecret = Buffer.alloc(32, 23).toString('base64url');
const digest = (pepper) =>
createHash('sha256')
.update('qinglong.local-owner-pepper.summary.v1\0', 'utf8')
.update(pepper, 'utf8')
.digest('hex');
const records = new Map([
[
'owner-old',
{
credentialId: 'owner-old',
version: 1,
pepperKeyId: 'owner-key-old',
state: 'active',
subject: { type: 'user', id: 'user-old' },
subjectStatus: 'active',
secretDigest: apiCredentialSecretDigest(oldPepper, 'owner-old', SECRET),
createdAtMs: NOW - 1_000,
notBeforeAtMs: NOW - 1_000,
expiresAtMs: NOW + 60_000,
},
],
[
'owner-new',
{
credentialId: 'owner-new',
version: 1,
pepperKeyId: 'owner-key-new',
state: 'active',
subject: { type: 'user', id: 'user-new' },
subjectStatus: 'active',
secretDigest: apiCredentialSecretDigest(
newPepper,
'owner-new',
newSecret,
),
createdAtMs: NOW - 500,
notBeforeAtMs: NOW - 500,
expiresAtMs: NOW + 60_000,
},
],
]);
const keys = new Map([
[
'owner-key-old',
{
pepperKeyId: 'owner-key-old',
materialDigest: digest(oldPepper),
backupDigest: 'b'.repeat(64),
state: 'retired',
version: 3,
registeredAtMs: NOW - 2_000,
activatedAtMs: NOW - 1_900,
retiredAtMs: NOW - 100,
},
],
[
'owner-key-new',
{
pepperKeyId: 'owner-key-new',
materialDigest: digest(newPepper),
backupDigest: 'c'.repeat(64),
state: 'active',
version: 2,
registeredAtMs: NOW - 1_000,
activatedAtMs: NOW - 100,
},
],
]);
const materials = new Map([
['owner-key-old', { pepperKeyId: 'owner-key-old', pepper: oldPepper }],
['owner-key-new', { pepperKeyId: 'owner-key-new', pepper: newPepper }],
]);
let materialReads = 0;
const authenticator = createLocalIdentityKeyringAuthenticator(
{ resolve: async (credentialId) => records.get(credentialId) ?? null },
{ resolveKey: async (pepperKeyId) => keys.get(pepperKeyId) ?? null },
{
resolve: async (pepperKeyId) => {
materialReads += 1;
return materials.get(pepperKeyId) ?? null;
},
},
{ now: () => NOW },
);
assert.equal(
(
await authenticator.authenticate(
formatApiCredentialToken('owner-old', SECRET),
)
)?.subject.id,
'user-old',
);
assert.equal(
(
await authenticator.authenticate(
formatApiCredentialToken('owner-new', newSecret),
)
)?.subject.id,
'user-new',
);
keys.get('owner-key-old').state = 'staged';
await assert.rejects(
authenticator.authenticate(formatApiCredentialToken('owner-old', SECRET)),
LocalIdentityAuthenticationUnavailableError,
);
assert.equal(materialReads, 2);
keys.get('owner-key-old').state = 'retired';
materials.set('owner-key-old', {
pepperKeyId: 'owner-key-old',
pepper: Buffer.alloc(32, 24).toString('base64url'),
});
await assert.rejects(
authenticator.authenticate(formatApiCredentialToken('owner-old', SECRET)),
LocalIdentityAuthenticationUnavailableError,
);
});
test('rejects malformed, wrong, inactive and expired credentials', async () => {
const record = {
credentialId: CREDENTIAL_ID,
version: 1,
pepperKeyId: 'legacy-v1',
state: 'active',
subject: { type: 'user', id: 'user-01' },
subjectStatus: 'active',
secretDigest: apiCredentialSecretDigest(PEPPER, CREDENTIAL_ID, SECRET),
createdAtMs: NOW - 1_000,
notBeforeAtMs: NOW - 1_000,
expiresAtMs: NOW + 60_000,
};
const repository = { resolve: async () => record };
const authenticator = createLocalIdentityAuthenticator(repository, PEPPER, {
now: () => NOW,
});
assert.equal(await authenticator.authenticate('not-a-token'), null);
assert.equal(
await authenticator.authenticate(
formatApiCredentialToken(
CREDENTIAL_ID,
Buffer.alloc(32, 12).toString('base64url'),
),
),
null,
);
record.state = 'revoked';
assert.equal(await authenticator.authenticate(TOKEN), null);
record.state = 'active';
record.subjectStatus = 'disabled';
assert.equal(await authenticator.authenticate(TOKEN), null);
record.subjectStatus = 'active';
record.expiresAtMs = NOW;
assert.equal(await authenticator.authenticate(TOKEN), null);
record.expiresAtMs = NOW + 60_000;
record.pepperKeyId = 'other-v1';
await assert.rejects(
authenticator.authenticate(TOKEN),
LocalIdentityAuthenticationUnavailableError,
);
});
test('maps repository and clock failures to unavailable', async () => {
const unavailable = createLocalIdentityAuthenticator(
{
resolve: async () => {
throw new ApiCredentialUnavailableError();
},
},
PEPPER,
);
await assert.rejects(
unavailable.authenticate(TOKEN),
LocalIdentityAuthenticationUnavailableError,
);
const badClock = createLocalIdentityAuthenticator(
{
resolve: async () => ({
credentialId: CREDENTIAL_ID,
version: 1,
pepperKeyId: 'legacy-v1',
state: 'active',
subject: { type: 'user', id: 'user-01' },
subjectStatus: 'active',
secretDigest: apiCredentialSecretDigest(PEPPER, CREDENTIAL_ID, SECRET),
createdAtMs: 0,
notBeforeAtMs: 0,
expiresAtMs: NOW + 60_000,
}),
},
PEPPER,
{ now: () => Number.NaN },
);
await assert.rejects(
badClock.authenticate(TOKEN),
LocalIdentityAuthenticationUnavailableError,
);
});
test('shares the runtime close fence and never opens a second connection', async (t) => {
const databasePath = fixture(t);
await migrateLocalSqlitePath({ databasePath, profile: 'edge' });
seed(databasePath);
const runtime = await openLocalSqliteRuntimeDatabase({
databasePath,
profile: 'edge',
});
const authenticator = createLocalIdentityAuthenticator(
runtime.apiCredentials,
PEPPER,
{ now: () => NOW },
);
await runtime.close();
await assert.rejects(
authenticator.authenticate(TOKEN),
LocalIdentityAuthenticationUnavailableError,
);
});
test('fails closed when credential pepper provenance is missing', async (t) => {
const databasePath = fixture(t);
await migrateLocalSqlitePath({ databasePath, profile: 'edge' });
seed(databasePath, { omitPepperBinding: true });
const runtime = await openLocalSqliteRuntimeDatabase({
databasePath,
profile: 'edge',
});
const authenticator = createLocalIdentityAuthenticator(
runtime.apiCredentials,
PEPPER,
{ now: () => NOW },
);
await assert.rejects(
authenticator.authenticate(TOKEN),
LocalIdentityAuthenticationUnavailableError,
);
await runtime.close();
});
test('rejects weak pepper, widened options and unbounded principal TTL', () => {
const repository = { resolve: async () => null };
assert.throws(
() => createLocalIdentityAuthenticator(repository, 'weak'),
LocalIdentityAuthenticationConfigurationError,
);
assert.throws(
() =>
createLocalIdentityAuthenticator(repository, PEPPER, {
principalTtlMs: 300_001,
}),
LocalIdentityAuthenticationConfigurationError,
);
assert.throws(
() =>
createLocalIdentityAuthenticator(repository, PEPPER, {
extra: true,
}),
LocalIdentityAuthenticationConfigurationError,
);
});
@@ -0,0 +1,71 @@
const assert = require('node:assert/strict');
const fs = require('node:fs');
const os = require('node:os');
const path = require('node:path');
const { test } = require('node:test');
const {
LocalOwnerPepperUnavailableError,
localOwnerPepperKeyPath,
provisionLocalOwnerPepperKey,
} = require('../dist/pepper-custody');
const { destroyLocalOwnerPepperKey } = require(
'../dist/pepper-custody/destructive',
);
function fixture(t) {
const directory = fs.mkdtempSync(path.join(os.tmpdir(), 'ql3-pepper-gc-'));
fs.chmodSync(directory, 0o700);
t.after(() => fs.rmSync(directory, { recursive: true, force: true }));
return directory;
}
test('destroys one exact key durably and replays the same absence proof', (t) => {
const keyringDirectory = fixture(t);
const pepperKeyId = 'owner-key-retired';
const material = provisionLocalOwnerPepperKey({
keyringDirectory,
pepperKeyId,
randomBytes: () => Buffer.alloc(32, 23),
});
const options = {
keyringDirectory,
pepperKeyId,
materialRole: 'runtime',
expectedMaterialDigest: material.digest,
prepareMutationId: '00000000-0000-4000-8000-000000000501',
};
const destroyed = destroyLocalOwnerPepperKey(options);
assert.equal(destroyed.status, 'destroyed');
assert.equal(
fs.existsSync(localOwnerPepperKeyPath(keyringDirectory, pepperKeyId)),
false,
);
const replay = destroyLocalOwnerPepperKey(options);
assert.equal(replay.status, 'absent');
assert.equal(replay.destructionProofDigest, destroyed.destructionProofDigest);
});
test('refuses digest drift without deleting the material', (t) => {
const keyringDirectory = fixture(t);
const pepperKeyId = 'owner-key-retired';
provisionLocalOwnerPepperKey({
keyringDirectory,
pepperKeyId,
randomBytes: () => Buffer.alloc(32, 29),
});
assert.throws(
() =>
destroyLocalOwnerPepperKey({
keyringDirectory,
pepperKeyId,
materialRole: 'runtime',
expectedMaterialDigest: '0'.repeat(64),
prepareMutationId: '00000000-0000-4000-8000-000000000502',
}),
LocalOwnerPepperUnavailableError,
);
assert.equal(
fs.existsSync(localOwnerPepperKeyPath(keyringDirectory, pepperKeyId)),
true,
);
});
@@ -0,0 +1,199 @@
const assert = require('node:assert/strict');
const fs = require('node:fs');
const os = require('node:os');
const path = require('node:path');
const { test } = require('node:test');
const {
LocalOwnerPepperConfigurationError,
LocalOwnerPepperConflictError,
LocalOwnerPepperUnavailableError,
backupLocalOwnerPepper,
inspectLocalOwnerPepper,
provisionLocalOwnerPepper,
restoreLocalOwnerPepper,
} = require('../dist/pepper-custody');
function fixture(t) {
const deploymentRoot = fs.mkdtempSync(
path.join(os.tmpdir(), 'ql3-owner-pepper-'),
);
fs.chmodSync(deploymentRoot, 0o700);
const backupRoot = fs.mkdtempSync(
path.join(os.tmpdir(), 'ql3-owner-pepper-backup-'),
);
fs.chmodSync(backupRoot, 0o700);
t.after(() => {
fs.rmSync(deploymentRoot, { recursive: true, force: true });
fs.rmSync(backupRoot, { recursive: true, force: true });
});
return {
deploymentRoot,
backupRoot,
pepperPath: path.join(deploymentRoot, 'owner.pepper'),
backupPath: path.join(backupRoot, 'owner.pepper.backup'),
};
}
test('provisions one canonical private pepper without replacement', (t) => {
const value = fixture(t);
const entropy = Buffer.alloc(32, 41);
const result = provisionLocalOwnerPepper({
deploymentRoot: value.deploymentRoot,
pepperPath: value.pepperPath,
randomBytes() {
return entropy;
},
});
assert.equal(result.version, 1);
assert.equal(result.byteLength, 43);
assert.match(result.digest, /^[0-9a-f]{64}$/);
assert.equal(fs.statSync(value.pepperPath).mode & 0o777, 0o600);
assert.equal(
fs.readFileSync(value.pepperPath, 'utf8'),
Buffer.alloc(32, 41).toString('base64url'),
);
assert.equal(entropy.equals(Buffer.alloc(32)), true);
assert.deepEqual(
inspectLocalOwnerPepper({
deploymentRoot: value.deploymentRoot,
pepperPath: value.pepperPath,
}),
result,
);
const before = fs.readFileSync(value.pepperPath);
assert.throws(
() =>
provisionLocalOwnerPepper({
deploymentRoot: value.deploymentRoot,
pepperPath: value.pepperPath,
}),
LocalOwnerPepperConflictError,
);
assert.deepEqual(fs.readFileSync(value.pepperPath), before);
});
test('creates an independent no-replace backup and restores only to absence', (t) => {
const value = fixture(t);
const provisioned = provisionLocalOwnerPepper({
deploymentRoot: value.deploymentRoot,
pepperPath: value.pepperPath,
randomBytes: () => Buffer.alloc(32, 42),
});
const backedUp = backupLocalOwnerPepper(value);
assert.deepEqual(backedUp, provisioned);
const pepperStat = fs.statSync(value.pepperPath, { bigint: true });
const backupStat = fs.statSync(value.backupPath, { bigint: true });
assert.equal(backupStat.mode & 0o777n, 0o600n);
assert.notEqual(backupStat.ino, pepperStat.ino);
assert.deepEqual(
fs.readFileSync(value.backupPath),
fs.readFileSync(value.pepperPath),
);
fs.unlinkSync(value.pepperPath);
const restored = restoreLocalOwnerPepper(value);
assert.deepEqual(restored, provisioned);
assert.deepEqual(
fs.readFileSync(value.pepperPath),
fs.readFileSync(value.backupPath),
);
assert.throws(
() => restoreLocalOwnerPepper(value),
LocalOwnerPepperConflictError,
);
});
test('never overwrites a pre-existing backup', (t) => {
const value = fixture(t);
provisionLocalOwnerPepper({
deploymentRoot: value.deploymentRoot,
pepperPath: value.pepperPath,
randomBytes: () => Buffer.alloc(32, 43),
});
fs.writeFileSync(value.backupPath, 'reserved', { mode: 0o600 });
assert.throws(
() => backupLocalOwnerPepper(value),
LocalOwnerPepperConflictError,
);
assert.equal(fs.readFileSync(value.backupPath, 'utf8'), 'reserved');
});
test('fails closed for broad roots, symlink parents and tampered pepper files', (t) => {
const broad = fixture(t);
fs.chmodSync(broad.deploymentRoot, 0o755);
assert.throws(
() =>
provisionLocalOwnerPepper({
deploymentRoot: broad.deploymentRoot,
pepperPath: broad.pepperPath,
}),
LocalOwnerPepperUnavailableError,
);
const linked = fixture(t);
const actual = path.join(linked.deploymentRoot, 'actual');
fs.mkdirSync(actual, { mode: 0o700 });
const alias = path.join(linked.deploymentRoot, 'alias');
fs.symlinkSync(actual, alias);
assert.throws(
() =>
provisionLocalOwnerPepper({
deploymentRoot: linked.deploymentRoot,
pepperPath: path.join(alias, 'owner.pepper'),
}),
LocalOwnerPepperUnavailableError,
);
const tampered = fixture(t);
provisionLocalOwnerPepper({
deploymentRoot: tampered.deploymentRoot,
pepperPath: tampered.pepperPath,
});
fs.chmodSync(tampered.pepperPath, 0o644);
assert.throws(
() =>
inspectLocalOwnerPepper({
deploymentRoot: tampered.deploymentRoot,
pepperPath: tampered.pepperPath,
}),
LocalOwnerPepperUnavailableError,
);
});
test('rejects invalid entropy and widened options before publishing', (t) => {
const value = fixture(t);
assert.throws(
() =>
provisionLocalOwnerPepper({
deploymentRoot: value.deploymentRoot,
pepperPath: value.pepperPath,
randomBytes: () => Buffer.alloc(31),
}),
LocalOwnerPepperConfigurationError,
);
assert.equal(fs.existsSync(value.pepperPath), false);
assert.throws(
() =>
provisionLocalOwnerPepper({
deploymentRoot: value.deploymentRoot,
pepperPath: value.pepperPath,
extra: true,
}),
LocalOwnerPepperConfigurationError,
);
assert.throws(
() =>
provisionLocalOwnerPepper({
deploymentRoot: value.deploymentRoot,
pepperPath: value.pepperPath,
randomBytes() {
throw new Error('sensitive entropy provider detail');
},
}),
(error) =>
error instanceof LocalOwnerPepperUnavailableError &&
error.message === 'Local Owner pepper operation is unavailable',
);
assert.equal(fs.existsSync(value.pepperPath), false);
});
@@ -0,0 +1,117 @@
const assert = require('node:assert/strict');
const fs = require('node:fs');
const os = require('node:os');
const path = require('node:path');
const { test } = require('node:test');
const {
LocalOwnerPepperKeyringFileProvider,
LocalOwnerPepperUnavailableError,
backupLocalOwnerPepperKey,
localOwnerPepperKeyPath,
provisionLocalOwnerPepperKey,
restoreLocalOwnerPepperKey,
} = require('../dist/pepper-custody');
function fixture(t) {
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'ql3-owner-keyring-'));
fs.chmodSync(root, 0o700);
t.after(() => fs.rmSync(root, { recursive: true, force: true }));
return root;
}
test('loads one exact key without exposing an active filesystem pointer', (t) => {
const keyringDirectory = fixture(t);
const summaries = ['owner-key-2', 'owner-key-1'].map((pepperKeyId, index) =>
provisionLocalOwnerPepperKey({
keyringDirectory,
pepperKeyId,
randomBytes: () => Buffer.alloc(32, index + 1),
}),
);
const provider = new LocalOwnerPepperKeyringFileProvider(keyringDirectory);
assert.deepEqual(provider.inspect(), {
version: 1,
keyIds: ['owner-key-1', 'owner-key-2'],
});
assert.equal(provider.resolve('missing-key'), null);
assert.deepEqual(provider.resolve('owner-key-1').summary, summaries[1]);
assert.equal(
path.basename(localOwnerPepperKeyPath(keyringDirectory, 'owner-key-1')),
`${Buffer.from('owner-key-1').toString('base64url')}.pepper`,
);
});
test('backs up and restores one exact key without replacement or inode reuse', (t) => {
const keyringDirectory = fixture(t);
const backupDirectory = fixture(t);
const pepperKeyId = 'owner-key-recovery';
const provisioned = provisionLocalOwnerPepperKey({
keyringDirectory,
pepperKeyId,
randomBytes: () => Buffer.alloc(32, 31),
});
assert.deepEqual(
backupLocalOwnerPepperKey({
keyringDirectory,
backupDirectory,
pepperKeyId,
}),
provisioned,
);
const sourcePath = localOwnerPepperKeyPath(keyringDirectory, pepperKeyId);
const backupPath = localOwnerPepperKeyPath(backupDirectory, pepperKeyId);
assert.notEqual(
fs.statSync(sourcePath, { bigint: true }).ino,
fs.statSync(backupPath, { bigint: true }).ino,
);
fs.unlinkSync(sourcePath);
assert.deepEqual(
restoreLocalOwnerPepperKey({
keyringDirectory,
backupDirectory,
pepperKeyId,
}),
provisioned,
);
assert.deepEqual(
new LocalOwnerPepperKeyringFileProvider(keyringDirectory).resolve(
pepperKeyId,
).summary,
provisioned,
);
});
test('hard-caps the directory and rejects symlinks or unknown entries', (t) => {
const keyringDirectory = fixture(t);
for (let index = 1; index <= 8; index += 1) {
provisionLocalOwnerPepperKey({
keyringDirectory,
pepperKeyId: `owner-key-${index}`,
randomBytes: () => Buffer.alloc(32, index),
});
}
assert.throws(
() =>
provisionLocalOwnerPepperKey({
keyringDirectory,
pepperKeyId: 'owner-key-9',
}),
LocalOwnerPepperUnavailableError,
);
const unsafe = fixture(t);
fs.symlinkSync(
localOwnerPepperKeyPath(keyringDirectory, 'owner-key-1'),
localOwnerPepperKeyPath(unsafe, 'owner-key-1'),
);
assert.throws(
() => new LocalOwnerPepperKeyringFileProvider(unsafe),
LocalOwnerPepperUnavailableError,
);
fs.unlinkSync(localOwnerPepperKeyPath(unsafe, 'owner-key-1'));
fs.writeFileSync(path.join(unsafe, 'active'), 'owner-key-1', { mode: 0o600 });
assert.throws(
() => new LocalOwnerPepperKeyringFileProvider(unsafe),
LocalOwnerPepperUnavailableError,
);
});