diff --git a/obj/src/objectstore.ts b/obj/src/objectstore.ts index 95fd11c6..3a4c4d0b 100644 --- a/obj/src/objectstore.ts +++ b/obj/src/objectstore.ts @@ -677,11 +677,13 @@ export class ObjectStoreImpl implements ObjectStore { controller!.enqueue(jm.data); } if (jm.info.pending === 0) { - const digest = Uint8Array.from(sha.digest()); - if (!checkSha256(digest, Uint8Array.from(sha.digest()))) { + const computedDigest = Uint8Array.from(sha.digest()); + if (!checkSha256(digest, computedDigest)) { + const hex = Array.from(computedDigest) + .map((b) => b.toString(16).padStart(2, "0")).join(""); controller!.error( new Error( - `received a corrupt object, digests do not match received: ${info.digest} calculated ${digest}`, + `received a corrupt object, digests do not match\n expected: ${info.digest}\n computed: SHA-256 ${hex}`, ), ); } else { diff --git a/obj/tests/objectstore_test.ts b/obj/tests/objectstore_test.ts index c42cd920..2e8483d9 100644 --- a/obj/tests/objectstore_test.ts +++ b/obj/tests/objectstore_test.ts @@ -49,7 +49,7 @@ import { StorageType, } from "@nats-io/jetstream/internal"; import { equals } from "@std/bytes"; -import { digestType, Objm } from "../src/objectstore.ts"; +import { digestType, type ObjectStoreImpl, Objm } from "../src/objectstore.ts"; import { Base64UrlPaddedCodec } from "../src/base64.ts"; import { sha256 } from "js-sha256"; @@ -1317,6 +1317,43 @@ Deno.test("os - objm creates right number of replicas", async () => { await NatsServer.stopAll(servers, true); }); +Deno.test("objectstore - get detects corrupt digest", async () => { + const { ns, nc } = await setup(jetstreamServerConf({})); + if (await notCompatible(ns, nc, "2.6.3")) { + return; + } + + const objm = new Objm(nc); + const os = await objm.create("test", { storage: StorageType.Memory }); + + const data = new TextEncoder().encode("hello world"); + await os.put( + { name: "corrupt" }, + readableStreamFrom(data), + ); + + // overwrite the metadata entry with a bad digest + const osi = os as ObjectStoreImpl; + const soi = await osi.rawInfo("corrupt"); + assertExists(soi); + soi!.digest = `${digestType}${"A".repeat(43)}=`; + const js = jetstream(nc); + await js.publish( + `$O.test.M.${Base64UrlPaddedCodec.encode("corrupt")}`, + JSON.stringify(soi), + ); + + await assertRejects( + async () => { + await os.getBlob("corrupt"); + }, + Error, + "digests do not match", + ); + + await cleanup(ns, nc); +}); + Deno.test("objectstore - watcherPrefix", async () => { const { ns, nc } = await setup(jetstreamServerConf({})); const js = jetstream(nc, { watcherPrefix: "hello" });