diff --git a/src/workerd/api/crypto/crypto.c++ b/src/workerd/api/crypto/crypto.c++ index 04c4eacf8c8..94b124e4c9b 100644 --- a/src/workerd/api/crypto/crypto.c++ +++ b/src/workerd/api/crypto/crypto.c++ @@ -14,6 +14,7 @@ #include #include +#include #include #include @@ -21,6 +22,18 @@ #include #include +#ifndef htonl +uint32_t htonl(uint32_t x) { +#if BYTE_ORDER == BIG_ENDIAN + return x; +#elif BYTE_ORDER == LITTLE_ENDIAN + return __bswap_32(x); +#else +#error "What kind of system is this?" +#endif +} +#endif + namespace workerd::api { kj::StringPtr CryptoKeyUsageSet::name() const { @@ -676,13 +689,61 @@ kj::String Crypto::randomUUID() { // ======================================================================================= // Crypto Streams implementation +class CRC32DigestContext: 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 = htonl(value); + return kj::heapArray((kj::byte*)&beValue, sizeof(value)); + } + + private: + uint32_t value; +}; + +class OpenSSLDigestContext: public DigestContext { + public: + OpenSSLDigestContext(kj::StringPtr algorithm): algorithm(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::StringPtr 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 +787,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 +803,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..c052b2fb5e9 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;//, 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..81d1fbb58f6 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(); @@ -118,7 +129,7 @@ export const digeststream = { strictEqual( err.message, 'DigestStream is a byte stream but received an object ' + - 'of non-ArrayBuffer/ArrayBufferView/string type on its writable side.' + 'of non-ArrayBuffer/ArrayBufferView/string type on its writable side.' ); } }