Skip to content

Commit 6abab38

Browse files
Merge pull request #10 from davidalmeidac/fix/u-mode-fixture-derivation
fix(vectors): u-mode tokenKey matches SPEC §11.6 and runtime verifier
2 parents ea77938 + f261c21 commit 6abab38

2 files changed

Lines changed: 10 additions & 15 deletions

File tree

node/scripts/gen-credential-modernization-vectors.mjs

Lines changed: 9 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,6 @@ const TOKEN_CHECKSUM_KEY = Buffer.from('sealed-env:token-checksum:v1', 'utf8');
121121
const VAULT_ID_PREFIX = Buffer.from('sealed-env:vault-id:v1', 'utf8');
122122
const DEPLOY_SIG_INFO = Buffer.from('sealed-env:deploy-sig:v1', 'utf8');
123123
const EPOCH_INFO = Buffer.from('epoch-v1', 'utf8');
124-
const UNSEAL_TOKEN_KEY_INFO = Buffer.from('sealed-env:unseal-token-key:v1', 'utf8');
125124

126125
// scrypt params — match sealed-env 0.1.1 default (SEC-002 OWASP 2024 floor).
127126
// Fixtures pin these so any stack can re-derive ek byte-identically.
@@ -218,7 +217,6 @@ function buildTierBToken({ masterKey, salt, exp, nonce, overrideVaultId }) {
218217
// byte string. Reading and writing is lossless (§11.6).
219218
function buildUnsealToken({
220219
masterKey,
221-
signingKey,
222220
totpSecret,
223221
salt,
224222
iss = 'sealed-env-cli',
@@ -229,15 +227,15 @@ function buildUnsealToken({
229227
}) {
230228
// enterprise_epoch is bound to (totpSecret, salt) per SPEC §9.
231229
const enterpriseEpoch = hmacSha256(totpSecret, Buffer.concat([salt, EPOCH_INFO]));
232-
// The legacy JWS unseal-token-key is HKDF(derived_key, ...). We use the
233-
// signingKey as an additional ingredient to bind team/enterprise scope.
234-
const derivedKey = scryptDerive(masterKey, salt);
235-
const tokenKey = hkdfSha256(
236-
Buffer.concat([derivedKey, signingKey]),
237-
salt,
238-
UNSEAL_TOKEN_KEY_INFO,
239-
32,
240-
);
230+
// The legacy JWS unseal-token-key is `derived_key` directly, per SPEC §11.6
231+
// u-mode ("HMAC-SHA256(derived_key, base64url(header)+'.'+base64url(payload))")
232+
// and verified by node/src/totp/unsealToken.ts:98 + :139. A previous draft of
233+
// this generator HKDF'd derivedKey||signingKey into a separate tokenKey; that
234+
// was a fabrication, not the contract. signingKey participates in the wider
235+
// sealed-file HMAC (SPEC §6) but does NOT enter the unseal-token signing
236+
// input. (Use raw derived_key here, not a fresh HMAC — the runtime verifier
237+
// computes `hmacSha256(derivedKey, signingInput)` so this side must match.)
238+
const tokenKey = scryptDerive(masterKey, salt);
241239
// Construct the JWS header + payload that the legacy verifier would
242240
// sign. Sig is HMAC over base64url(header)+"."+base64url(payload).
243241
const header = { alg: 'HS256', typ: 'sealed-env-unseal/v1' };
@@ -330,7 +328,6 @@ function runSelfChecks() {
330328
// 5. u-mode token mint is deterministic with fixed inputs (no Date.now()).
331329
const u1 = buildUnsealToken({
332330
masterKey: MASTER_KEY,
333-
signingKey: SIGNING_KEY,
334331
totpSecret: TOTP_SECRET,
335332
salt: SALT,
336333
iat: 1767225600,
@@ -340,7 +337,6 @@ function runSelfChecks() {
340337
});
341338
const u2 = buildUnsealToken({
342339
masterKey: MASTER_KEY,
343-
signingKey: SIGNING_KEY,
344340
totpSecret: TOTP_SECRET,
345341
salt: SALT,
346342
iat: 1767225600,
@@ -479,7 +475,6 @@ function unsealValid() {
479475
// iat/exp/ops_id are fixed so the token doesn't depend on Date.now().
480476
const token = buildUnsealToken({
481477
masterKey: MASTER_KEY,
482-
signingKey: SIGNING_KEY,
483478
totpSecret: TOTP_SECRET,
484479
salt: SALT,
485480
iat: 1767225600, // 2026-01-01T00:00:00Z

test-vectors/v1/credential-modernization-unseal-valid.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
"signing_key_hex": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
1616
"totp_secret_hex": "cccccccccccccccccccccccccccccccccccccccc"
1717
},
18-
"token": "sealed_env_u_f6d4_p2NleHAa9IZXAGNpYXQaaVW5AGNpc3Nuc2VhbGVkLWVudi1jbGljc2lnWCBA-e-nsFZ9dl1iOkUQ8tbnA5XBlJ__RfuDbPE0xTVvB2VlcG9jaHgsQnNoUWhwcG1UWmwrNmgrR2lvM3VMK3dITC9YdDl2STZyd0ZhbWJsa1k3QT1mb3BzX2lkeBhmaXh0dXJlLXUtbW9kZS1vcHMtaWQtdjFpZGVwbG95X2lk9g",
18+
"token": "sealed_env_u_7b09_p2NleHAa9IZXAGNpYXQaaVW5AGNpc3Nuc2VhbGVkLWVudi1jbGljc2lnWCAVf7DuLyPuhx4BPO9lfRXromrVb-IuT3CzzDY2INAx92VlcG9jaHgsQnNoUWhwcG1UWmwrNmgrR2lvM3VMK3dITC9YdDl2STZyd0ZhbWJsa1k3QT1mb3BzX2lkeBhmaXh0dXJlLXUtbW9kZS1vcHMtaWQtdjFpZGVwbG95X2lk9g",
1919
"config_file_contents": null,
2020
"expected": {
2121
"result": "decrypt_ok",

0 commit comments

Comments
 (0)