From 4d89ed4a14d5482dee84ba397c376ef82c300fcf Mon Sep 17 00:00:00 2001 From: Dan Lapid Date: Sat, 18 Jan 2025 19:04:11 +0000 Subject: [PATCH] Add crc32 algorithm to DigestStream Since openssl does not support crc32 as a digest algorithm I extended our support through a new self implemented class and took the initial implementation out to it's own class. --- src/workerd/api/crypto/crypto.c++ | 75 ++++++++++++++++---- src/workerd/api/crypto/crypto.h | 8 ++- src/workerd/api/tests/crypto-streams-test.js | 11 +++ 3 files changed, 79 insertions(+), 15 deletions(-) diff --git a/src/workerd/api/crypto/crypto.c++ b/src/workerd/api/crypto/crypto.c++ index 04c4eacf8c8..9e84185f8ec 100644 --- a/src/workerd/api/crypto/crypto.c++ +++ b/src/workerd/api/crypto/crypto.c++ @@ -14,6 +14,7 @@ #include #include +#include #include #include @@ -676,13 +677,65 @@ kj::String Crypto::randomUUID() { // ======================================================================================= // Crypto Streams implementation +class CRC32DigestContext final: public DigestContext { + public: + CRC32DigestContext(): value(crc32(0, Z_NULL, 0)) {} + virtual ~CRC32DigestContext() = default; + + void write(kj::ArrayPtr buffer) { + value = crc32(value, buffer.begin(), buffer.size()); + } + + kj::Array close() { + auto beValue = htobe32(value); + static_assert(sizeof(value) == sizeof(beValue), "CRC32 digest is not 32 bits?"); + auto digest = kj::heapArray(sizeof(beValue)); + KJ_DASSERT(digest.size() == sizeof(beValue)); + memcpy(digest.begin(), &beValue, sizeof(beValue)); + return digest; + } + + private: + uint32_t value; +}; + +class OpenSSLDigestContext final: public DigestContext { + public: + OpenSSLDigestContext(kj::StringPtr algorithm): algorithm(kj::str(algorithm)) { + auto checkErrorsOnFinish = webCryptoOperationBegin(__func__, algorithm); + auto type = lookupDigestAlgorithm(algorithm).second; + auto opensslContext = kj::disposeWith(EVP_MD_CTX_new()); + KJ_ASSERT(opensslContext.get() != nullptr); + OSSLCALL(EVP_DigestInit_ex(opensslContext.get(), type, nullptr)); + context = kj::mv(opensslContext); + } + virtual ~OpenSSLDigestContext() = default; + + void write(kj::ArrayPtr buffer) { + auto checkErrorsOnFinish = webCryptoOperationBegin(__func__, algorithm); + OSSLCALL(EVP_DigestUpdate(context.get(), buffer.begin(), buffer.size())); + } + + kj::Array close() { + auto checkErrorsOnFinish = webCryptoOperationBegin(__func__, algorithm); + uint size = 0; + auto digest = kj::heapArray(EVP_MD_CTX_size(context.get())); + OSSLCALL(EVP_DigestFinal_ex(context.get(), digest.begin(), &size)); + KJ_ASSERT(size, digest.size()); + return kj::mv(digest); + } + + private: + kj::String algorithm; + kj::Own context; +}; + DigestStream::DigestContextPtr DigestStream::initContext(SubtleCrypto::HashAlgorithm& algorithm) { - auto checkErrorsOnFinish = webCryptoOperationBegin(__func__, algorithm.name); - auto type = lookupDigestAlgorithm(algorithm.name).second; - auto context = kj::disposeWith(EVP_MD_CTX_new()); - KJ_ASSERT(context.get() != nullptr); - OSSLCALL(EVP_DigestInit_ex(context.get(), type, nullptr)); - return kj::mv(context); + if (algorithm.name == "crc32") { + return kj::heap(); + } else { + return kj::heap(algorithm.name); + } } DigestStream::DigestStream(kj::Own controller, @@ -726,8 +779,7 @@ kj::Maybe DigestStream::write(jsg::Lock& js, kj::ArrayPtr return errored.addRef(js); } KJ_CASE_ONEOF(ready, Ready) { - auto checkErrorsOnFinish = webCryptoOperationBegin(__func__, ready.algorithm.name); - OSSLCALL(EVP_DigestUpdate(ready.context.get(), buffer.begin(), buffer.size())); + ready.context->write(buffer); return kj::none; } } @@ -743,12 +795,7 @@ kj::Maybe DigestStream::close(jsg::Lock& js) { return errored.addRef(js); } KJ_CASE_ONEOF(ready, Ready) { - auto checkErrorsOnFinish = webCryptoOperationBegin(__func__, ready.algorithm.name); - uint size = 0; - auto digest = kj::heapArray(EVP_MD_CTX_size(ready.context.get())); - OSSLCALL(EVP_DigestFinal_ex(ready.context.get(), digest.begin(), &size)); - KJ_ASSERT(size, digest.size()); - ready.resolver.resolve(js, kj::mv(digest)); + ready.resolver.resolve(js, ready.context->close()); state.init(); return kj::none; } diff --git a/src/workerd/api/crypto/crypto.h b/src/workerd/api/crypto/crypto.h index f183a34d20a..75b38b8b4cc 100644 --- a/src/workerd/api/crypto/crypto.h +++ b/src/workerd/api/crypto/crypto.h @@ -666,9 +666,15 @@ class SubtleCrypto: public jsg::Object { // DigestStream is a non-standard extension that provides a way of generating // a hash digest from streaming data. It combines Web Crypto concepts into a // WritableStream and is compatible with both APIs. +class DigestContext { + public: + virtual void write(kj::ArrayPtr buffer) = 0; + virtual kj::Array close() = 0; +}; + class DigestStream: public WritableStream { public: - using DigestContextPtr = kj::Own; + using DigestContextPtr = kj::Own; using Algorithm = kj::OneOf; explicit DigestStream(kj::Own controller, diff --git a/src/workerd/api/tests/crypto-streams-test.js b/src/workerd/api/tests/crypto-streams-test.js index 1ce4171abdd..2134f2a5eaa 100644 --- a/src/workerd/api/tests/crypto-streams-test.js +++ b/src/workerd/api/tests/crypto-streams-test.js @@ -38,6 +38,7 @@ export const digeststream = { new crypto.DigestStream('SHA-256'); new crypto.DigestStream('SHA-384'); new crypto.DigestStream('SHA-512'); + new crypto.DigestStream('crc32'); // But fails for unknown digest names... throws(() => new crypto.DigestStream('foo')); @@ -107,6 +108,16 @@ export const digeststream = { deepStrictEqual(digest, check); } + { + const check = new Uint8Array([176, 224, 34, 147]); + const digestStream = new crypto.DigestStream('crc32'); + const writer = digestStream.getWriter(); + await writer.write(new Uint32Array([1, 2, 3])); + await writer.close(); + const digest = new Uint8Array(await digestStream.digest); + deepStrictEqual(digest, check); + } + { const digestStream = new crypto.DigestStream('md5'); const writer = digestStream.getWriter();