Skip to content

Commit

Permalink
Add crc32 algorithm to DigestStream
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
danlapid committed Jan 20, 2025
1 parent c134deb commit 6b25145
Show file tree
Hide file tree
Showing 3 changed files with 88 additions and 15 deletions.
84 changes: 70 additions & 14 deletions src/workerd/api/crypto/crypto.c++
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,26 @@

#include <openssl/digest.h>
#include <openssl/mem.h>
#include <zlib.h>

#include <algorithm>
#include <array>
#include <limits>
#include <set>
#include <typeinfo>

#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 {
Expand Down Expand Up @@ -676,13 +689,62 @@ 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<kj::byte> buffer) {
value = crc32(value, buffer.begin(), buffer.size());
}

kj::Array<kj::byte> close() {
auto beValue = htonl(value);
static_assert(sizeof(value) == sizeof(beValue), "CRC32 digest is not 32 bits?");
return kj::heapArray<kj::byte>((kj::byte*)&beValue, sizeof(beValue));
}

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_free>(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<kj::byte> buffer) {
auto checkErrorsOnFinish = webCryptoOperationBegin(__func__, algorithm);
OSSLCALL(EVP_DigestUpdate(context.get(), buffer.begin(), buffer.size()));
}

kj::Array<kj::byte> close() {
auto checkErrorsOnFinish = webCryptoOperationBegin(__func__, algorithm);
uint size = 0;
auto digest = kj::heapArray<kj::byte>(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<EVP_MD_CTX> 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_free>(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<CRC32DigestContext>();
} else {
return kj::heap<OpenSSLDigestContext>(algorithm.name);
}
}

DigestStream::DigestStream(kj::Own<WritableStreamController> controller,
Expand Down Expand Up @@ -726,8 +788,7 @@ kj::Maybe<StreamStates::Errored> 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;
}
}
Expand All @@ -743,12 +804,7 @@ kj::Maybe<StreamStates::Errored> 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<kj::byte>(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<StreamStates::Closed>();
return kj::none;
}
Expand Down
8 changes: 7 additions & 1 deletion src/workerd/api/crypto/crypto.h
Original file line number Diff line number Diff line change
Expand Up @@ -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<kj::byte> buffer) = 0;
virtual kj::Array<kj::byte> close() = 0;
};

class DigestStream: public WritableStream {
public:
using DigestContextPtr = kj::Own<EVP_MD_CTX>;
using DigestContextPtr = kj::Own<DigestContext>;
using Algorithm = kj::OneOf<kj::String, SubtleCrypto::HashAlgorithm>;

explicit DigestStream(kj::Own<WritableStreamController> controller,
Expand Down
11 changes: 11 additions & 0 deletions src/workerd/api/tests/crypto-streams-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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'));
Expand Down Expand Up @@ -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();
Expand Down

0 comments on commit 6b25145

Please sign in to comment.