mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-08 12:32:43 +00:00
fix(backup): read nothing shorter than a ciphertext as needing a key
The shortest value either codec writes is 40 base64 characters. A shorter string under an ...Encrypted member is not ciphertext, and reading it as a legacy value would have made a restore demand the v1 key for a field that holds none.
This commit is contained in:
1 parent
7da778760e
commit
94aa7c6bd4
2 files changed
+13
-4
No files matched your search
@@ -77,9 +77,12 @@ describe("innerCiphertextKeyId", () => {
|
||||
expect(innerCiphertextKeyId(legacy)?.keyId).toBe("v1");
|
||||
});
|
||||
|
||||
it("ignores an empty value and one that is not base64", () => {
|
||||
it("ignores an empty value, a short one, and one that is not base64", () => {
|
||||
expect(innerCiphertextKeyId("")).toBeNull();
|
||||
expect(innerCiphertextKeyId("not ciphertext at all!")).toBeNull();
|
||||
expect(innerCiphertextKeyId("yes")).toBeNull();
|
||||
expect(
|
||||
innerCiphertextKeyId("not ciphertext at all, and long enough to count!"),
|
||||
).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -122,7 +125,9 @@ describe("assessBackupKeys", () => {
|
||||
useKeys({ old: K1, cur: K2 }, "cur");
|
||||
const file = {
|
||||
moodEntries: [{ noteEncrypted: bytesString("fine") }],
|
||||
appSettings: { adminAiKeyEncrypted: "gone.QUJDRA==" },
|
||||
appSettings: {
|
||||
adminAiKeyEncrypted: `gone.${Buffer.alloc(40).toString("base64")}`,
|
||||
},
|
||||
};
|
||||
const collector = new BackupKeyIdCollector();
|
||||
collector.visit(file);
|
||||
|
||||
@@ -40,6 +40,7 @@ const VERSIONED = /^([A-Za-z0-9_-]{1,32})\.[A-Za-z0-9+/=]+$/;
|
||||
const BASE64 = /^[A-Za-z0-9+/]+={0,2}$/;
|
||||
const BASE64_TAIL = /^[A-Za-z0-9+/=]*$/;
|
||||
const BYTES_CODEC_VERSION = 0x02;
|
||||
const MIN_CIPHERTEXT_CHARS = 40;
|
||||
|
||||
/** How an inner value is laid out, which decides how a sample is opened. */
|
||||
export type InnerCiphertextForm = "string" | "bytes-string" | "binary";
|
||||
@@ -55,7 +56,10 @@ export interface InnerCiphertext {
|
||||
* multi-megabyte document costs the same as a note.
|
||||
*/
|
||||
export function innerCiphertextKeyId(value: string): InnerCiphertext | null {
|
||||
if (value.length === 0) return null;
|
||||
// The shortest ciphertext either codec writes is 28 bytes (nonce and tag),
|
||||
// 40 characters as base64: anything shorter is not ciphertext, and reading
|
||||
// it as a legacy value would demand a key for a field that holds none.
|
||||
if (value.length < MIN_CIPHERTEXT_CHARS) return null;
|
||||
const versioned = VERSIONED.exec(value.slice(0, 64));
|
||||
if (versioned && value.indexOf(".") === versioned[1].length) {
|
||||
return { keyId: versioned[1], form: "string" };
|
||||
|
||||
Reference in new issue
Block a user