Public Access
docs/crypto.md is now the normative, frozen specification. This had to land before anything else in M1: the server holds ciphertext and no keys, so it can never re-encrypt, and a format change after users hold data is a coordinated client rewrite with no rollback. Specification: - DSH1 envelope layout, canonical 64-byte AAD encoding, SealTo construction, key hierarchy, Argon2id profiles, fingerprints, and the change rules for each version field. - AAD encoding is fixed-width binary rather than delimited string concatenation, so no field value can forge a field boundary. This supersedes the illustrative form sketched in ADR 0001, which now points here. - UUIDs are RFC 4122 big-endian. Guid.ToByteArray() emits the first three groups little-endian and would have made our ciphertext unreadable by any other implementation of this spec, failing only at a cross-implementation boundary. Verified rather than assumed: - PrimitiveAvailabilityTests proves X25519, Ed25519, XChaCha20-Poly1305, Argon2id and HKDF-SHA512 all function on net10.0. NSec 26.4.0 targets net9.0 and is consumed by forward compatibility; this closes one of the two package questions the plan flagged. - Argon2Profile exists because NSec's MemorySize is in KIBIBYTES, not bytes. Passing bytes gives either a 256 GiB allocation or a 256 KiB KDF that cracks instantly. The type takes mebibytes so the unit cannot be got wrong at a call site. Found by benchmarking: the first measurements were ~1000x too slow, which turned out to be 19 GiB of work. - Parameters measured, not guessed: 256 MiB/t=4 is 323 ms on this machine; the table of candidates is in the spec. Implementation and tests (83 total, up from 17): - AadDescriptor, DshEnvelope, DshCrypto (Seal/Open/SealTo/OpenSealed/fingerprints). - Decryption returns null rather than throwing: ciphertext comes from a server that is explicitly not trusted, so a failed tag is an expected outcome. - Envelope readers reject unknown algorithms and any non-zero flag bit, so an envelope that is not fully understood fails closed. - Executable form of the spec's substitution claims: a server cannot move ciphertext between resources, roll back a key generation or item version, repurpose a payload as metadata, or confuse the two constructions. - Golden vectors in tests/fixtures/crypto/vectors.json guard the format. Mutation-checked: a one-byte schema version change trips four tests including the guard. Two build-infrastructure bugs found and fixed along the way: - .editorconfig forced camelCase on const and static readonly fields. PascalCase is the .NET convention for both; the config was wrong, not the code. - The golden fixture was resolved with [CallerFilePath], which ContinuousIntegrationBuild rewrites to /_/... under deterministic source paths. It passed locally and would have failed only in CI. Now copied to the output directory and read from there.
191 lines
7.5 KiB
JSON
191 lines
7.5 KiB
JSON
{
|
|
"_comment": "Generated by DodoSSH.Crypto.Tests.GoldenVectors. Normative source: docs/crypto.md.",
|
|
"_warning": "A failing assertion here is a regression or an intentional versioned format change. Do not regenerate to make it pass.",
|
|
"specVersion": 1,
|
|
"aad": [
|
|
{
|
|
"name": "item-payload",
|
|
"purpose": 4,
|
|
"resourceType": 5,
|
|
"resourceId": "0192f0c8-1a2b-7c3d-8e4f-5a6b7c8d9e0f",
|
|
"keyId": "0192f0c8-9999-7aaa-8bbb-cccccccccccc",
|
|
"keyGeneration": 7,
|
|
"itemVersion": 3,
|
|
"aadVersion": 1,
|
|
"schemaVersion": 1,
|
|
"canonicalEncoding": "647368310a0104050192f0c81a2b7c3d8e4f5a6b7c8d9e0f0192f0c899997aaa8bbbcccccccccccc000000070000000300010000000000000000000000000000",
|
|
"aad": "bb106e753e2fd9ac31142889a4356cbf9fc1f8db3777a1921ecbb5481bd4379e"
|
|
},
|
|
{
|
|
"name": "item-payload-other-resource",
|
|
"purpose": 4,
|
|
"resourceType": 5,
|
|
"resourceId": "0192f0c8-1a2b-7c3d-8e4f-5a6b7c8d9e10",
|
|
"keyId": "0192f0c8-9999-7aaa-8bbb-cccccccccccc",
|
|
"keyGeneration": 7,
|
|
"itemVersion": 3,
|
|
"aadVersion": 1,
|
|
"schemaVersion": 1,
|
|
"canonicalEncoding": "647368310a0104050192f0c81a2b7c3d8e4f5a6b7c8d9e100192f0c899997aaa8bbbcccccccccccc000000070000000300010000000000000000000000000000",
|
|
"aad": "ae87f8da1a55b36286ed103a11fb51145adff18a222557db30422918eba6c29f"
|
|
},
|
|
{
|
|
"name": "item-metadata",
|
|
"purpose": 5,
|
|
"resourceType": 4,
|
|
"resourceId": "0192f0c8-1a2b-7c3d-8e4f-5a6b7c8d9e0f",
|
|
"keyId": "00000000-0000-0000-0000-000000000000",
|
|
"keyGeneration": 1,
|
|
"itemVersion": 1,
|
|
"aadVersion": 1,
|
|
"schemaVersion": 1,
|
|
"canonicalEncoding": "647368310a0105040192f0c81a2b7c3d8e4f5a6b7c8d9e0f00000000000000000000000000000000000000010000000100010000000000000000000000000000",
|
|
"aad": "cfb042451484a4f484b45e812a2c7667d12592bcb8ec47be5b5ef95c38b1d3ad"
|
|
},
|
|
{
|
|
"name": "vault-key-grant",
|
|
"purpose": 2,
|
|
"resourceType": 3,
|
|
"resourceId": "0192f0c8-1a2b-7c3d-8e4f-5a6b7c8d9e0f",
|
|
"keyId": "00000000-0000-0000-0000-000000000000",
|
|
"keyGeneration": 2,
|
|
"itemVersion": 0,
|
|
"aadVersion": 1,
|
|
"schemaVersion": 1,
|
|
"canonicalEncoding": "647368310a0102030192f0c81a2b7c3d8e4f5a6b7c8d9e0f00000000000000000000000000000000000000020000000000010000000000000000000000000000",
|
|
"aad": "5c9444daa7f74193b04abefb57d5a49a8782e8e1a7fd97771865f9e038f8c957"
|
|
},
|
|
{
|
|
"name": "user-secret-bundle",
|
|
"purpose": 1,
|
|
"resourceType": 1,
|
|
"resourceId": "0192f0c8-1a2b-7c3d-8e4f-5a6b7c8d9e0f",
|
|
"keyId": "00000000-0000-0000-0000-000000000000",
|
|
"keyGeneration": 1,
|
|
"itemVersion": 0,
|
|
"aadVersion": 1,
|
|
"schemaVersion": 1,
|
|
"canonicalEncoding": "647368310a0101010192f0c81a2b7c3d8e4f5a6b7c8d9e0f00000000000000000000000000000000000000010000000000010000000000000000000000000000",
|
|
"aad": "9f73034823c49cdfcad4fcc75e67ae22be151a92afed72ab7548097ef5a99f68"
|
|
},
|
|
{
|
|
"name": "all-zero-ids",
|
|
"purpose": 6,
|
|
"resourceType": 0,
|
|
"resourceId": "00000000-0000-0000-0000-000000000000",
|
|
"keyId": "00000000-0000-0000-0000-000000000000",
|
|
"keyGeneration": 0,
|
|
"itemVersion": 0,
|
|
"aadVersion": 1,
|
|
"schemaVersion": 1,
|
|
"canonicalEncoding": "647368310a0106000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000",
|
|
"aad": "cebc8d57709c0ebe47874c85fc39aa538e17c4202b767673c8636ef181cd1664"
|
|
}
|
|
],
|
|
"envelope": [
|
|
{
|
|
"name": "xchacha20poly1305",
|
|
"algId": 1,
|
|
"nonce": "a0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7",
|
|
"ciphertext": "000102030405060708090a0b0c0d0e0f10111213",
|
|
"prefixSize": 30,
|
|
"envelope": "445348310100a0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7000102030405060708090a0b0c0d0e0f10111213"
|
|
},
|
|
{
|
|
"name": "aes256gcm",
|
|
"algId": 2,
|
|
"nonce": "b0b1b2b3b4b5b6b7b8b9babb",
|
|
"ciphertext": "000102030405060708090a0b0c0d0e0f10111213",
|
|
"prefixSize": 18,
|
|
"envelope": "445348310200b0b1b2b3b4b5b6b7b8b9babb000102030405060708090a0b0c0d0e0f10111213"
|
|
},
|
|
{
|
|
"name": "sealto-x25519",
|
|
"algId": 3,
|
|
"nonce": "a0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7",
|
|
"ephemeralPublicKey": "c0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedf",
|
|
"ciphertext": "000102030405060708090a0b0c0d0e0f10111213",
|
|
"prefixSize": 62,
|
|
"envelope": "445348310300c0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7000102030405060708090a0b0c0d0e0f10111213"
|
|
}
|
|
],
|
|
"aead": [
|
|
{
|
|
"name": "xchacha20poly1305-with-canonical-aad",
|
|
"key": "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
|
|
"nonce": "101112131415161718191a1b1c1d1e1f2021222324252627",
|
|
"aad": "bb106e753e2fd9ac31142889a4356cbf9fc1f8db3777a1921ecbb5481bd4379e",
|
|
"plaintext": "636f727265637420686f727365206261747465727920737461706c65",
|
|
"ciphertext": "4793718431eb55f3feed50be98b0416d7bff929d804d53a7873495132465b6b1da6e73e042821964543ecd90",
|
|
"envelope": "445348310100101112131415161718191a1b1c1d1e1f20212223242526274793718431eb55f3feed50be98b0416d7bff929d804d53a7873495132465b6b1da6e73e042821964543ecd90"
|
|
}
|
|
],
|
|
"hkdf": [
|
|
{
|
|
"name": "passphrase-kek",
|
|
"algorithm": "HKDF-SHA512-Expand",
|
|
"prk": "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f",
|
|
"info": "dsh1/kek/passphrase/v1",
|
|
"outputLength": 32,
|
|
"output": "652b3a4a3ce03b235095ad32f1eed2cfdae915b5b0a98cc9f96face30853f4c7"
|
|
},
|
|
{
|
|
"name": "local-cache",
|
|
"algorithm": "HKDF-SHA512-Expand",
|
|
"prk": "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f",
|
|
"info": "dsh1/localcache/v1",
|
|
"outputLength": 32,
|
|
"output": "5b69ed9266ff5f297f11667ca693b0049b805365ee34d54d6e60b843e414b1f5"
|
|
}
|
|
],
|
|
"argon2id": [
|
|
{
|
|
"name": "passphrase-default",
|
|
"memoryMebibytes": 256,
|
|
"memoryKibibytes": 262144,
|
|
"passes": 4,
|
|
"parallelism": 1
|
|
},
|
|
{
|
|
"name": "passphrase-reduced",
|
|
"memoryMebibytes": 128,
|
|
"memoryKibibytes": 131072,
|
|
"passes": 3,
|
|
"parallelism": 1
|
|
},
|
|
{
|
|
"name": "passphrase-high",
|
|
"memoryMebibytes": 512,
|
|
"memoryKibibytes": 524288,
|
|
"passes": 4,
|
|
"parallelism": 1
|
|
},
|
|
{
|
|
"name": "random-secret",
|
|
"memoryMebibytes": 64,
|
|
"memoryKibibytes": 65536,
|
|
"passes": 3,
|
|
"parallelism": 1
|
|
},
|
|
{
|
|
"name": "random-secret-output",
|
|
"memoryMebibytes": 64,
|
|
"memoryKibibytes": 65536,
|
|
"passes": 3,
|
|
"parallelism": 1,
|
|
"passphrase": "correct horse battery staple",
|
|
"salt": "202122232425262728292a2b2c2d2e2f",
|
|
"outputLength": 32,
|
|
"output": "3573a601a50874c6c4222082d040f039ba4f557a0151e0357e8abb66fed7b29e"
|
|
}
|
|
],
|
|
"fingerprint": [
|
|
{
|
|
"name": "identity-fingerprint",
|
|
"x25519PublicKey": "404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f",
|
|
"ed25519PublicKey": "606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f",
|
|
"fingerprint": "fe8d8673f517688bf0d5d9b812327619a303c765af1f47dbd6a777db193c36e5"
|
|
}
|
|
]
|
|
}
|