From e99deb59db44d3d1ba6932df4f64e8bbcfaccc35 Mon Sep 17 00:00:00 2001 From: Mikkel Ricafrente Date: Wed, 7 Aug 2024 17:54:16 +0100 Subject: [PATCH] R2-2317 add support for SSE-C via `workerd` bindings --- src/workerd/api/r2-api.capnp | 20 +++++++++++++++ src/workerd/api/r2-bucket.c++ | 47 ++++++++++++++++++++++++++++++++++- src/workerd/api/r2-bucket.h | 14 +++++++++-- src/workerd/api/r2.h | 1 + 4 files changed, 79 insertions(+), 3 deletions(-) diff --git a/src/workerd/api/r2-api.capnp b/src/workerd/api/r2-api.capnp index af42c90e0d1..eb863f2700f 100644 --- a/src/workerd/api/r2-api.capnp +++ b/src/workerd/api/r2-api.capnp @@ -60,6 +60,10 @@ struct R2Conditional { # timestamp. } +struct R2SSECOptions { + key @0 :Text; +} + struct R2Checksums { # The JSON name of these fields must comform to the representation of the ChecksumAlgorithm in # the R2 gateway worker. @@ -93,6 +97,7 @@ struct R2GetRequest { range @1 :R2Range; rangeHeader @3 :Text; onlyIf @2 :R2Conditional; + ssec @4 :R2SSECOptions; } struct R2PutRequest { @@ -106,6 +111,7 @@ struct R2PutRequest { sha384 @7 :Data $Json.hex; sha512 @8 :Data $Json.hex; storageClass @9 :Text; + ssec @10 :R2SSECOptions; } struct R2CreateMultipartUploadRequest { @@ -113,12 +119,14 @@ struct R2CreateMultipartUploadRequest { customFields @1 :List(Record); httpFields @2 :R2HttpFields; storageClass @3 :Text; + ssec @4 :R2SSECOptions; } struct R2UploadPartRequest { object @0 :Text; uploadId @1 :Text; partNumber @2 :UInt32; + ssec @3 :R2SSECOptions; } struct R2CompleteMultipartUploadRequest { @@ -187,6 +195,11 @@ struct R2ErrorResponse { message @2 :Text; } +struct R2SSECResponse { + algorithm @0 :Text; + keyMd5 @1 :Text; +} + struct R2HeadResponse { name @0 :Text; # The name of the object. @@ -220,6 +233,9 @@ struct R2HeadResponse { storageClass @9 :Text; # The storage class of the object. Standard or Infrequent Access. # Provided on object creation to specify which storage tier R2 should use for this object. + + ssec @10 :R2SSECResponse; + # The algorithm/key hash used for encryption(if the user used SSE-C) } using R2GetResponse = R2HeadResponse; @@ -230,12 +246,16 @@ struct R2CreateMultipartUploadResponse { uploadId @0 :Text; # The unique identifier of this object, required for subsequent operations on # this multipart upload. + ssec @1 :R2SSECResponse; + # The algorithm/key hash used for encryption(if the user used SSE-C) } struct R2UploadPartResponse { etag @0 :Text; # The ETag the of the uploaded part. # This ETag is required in order to complete the multipart upload. + ssec @1 :R2SSECResponse; + # The algorithm/key hash used for encryption(if the user used SSE-C) } using R2CompleteMultipartUploadResponse = R2PutResponse; diff --git a/src/workerd/api/r2-bucket.c++ b/src/workerd/api/r2-bucket.c++ index 2bb38e76f6c..aeeb4adf3fd 100644 --- a/src/workerd/api/r2-bucket.c++ +++ b/src/workerd/api/r2-bucket.c++ @@ -8,6 +8,7 @@ #include "util.h" #include #include +#include #include #include #include @@ -17,7 +18,9 @@ #include #include #include - +#include "kj/encoding.h" +#include "workerd/jsg/exception.h" +#include "workerd/jsg/jsg.h" namespace workerd::api::public_beta { static bool isWholeNumber(double x) { double intpart; @@ -298,6 +301,20 @@ void initGetOptions(jsg::Lock& js, Builder& builder, Options& o) { } } } + KJ_IF_SOME(ssec, o.ssec) { + auto ssecBuilder = builder.initSsec(); + KJ_SWITCH_ONEOF(ssec.key) { + KJ_CASE_ONEOF(keyString, kj::String) { + JSG_REQUIRE(std::regex_match(keyString.begin(), keyString.end(), std::regex("[0-9a-f]")), Error, "SSE-C Key has invalid format"); + JSG_REQUIRE(keyString.size() == 64, Error, "SSE-C Key must be 32 bytes in length"); + ssecBuilder.setKey(kj::str(keyString)); + } + KJ_CASE_ONEOF(keyBuff, kj::Array) { + JSG_REQUIRE(keyBuff.size() == 32, Error, "SSE-C Key must be 32 bytes in length"); + ssecBuilder.setKey(kj::encodeHex(keyBuff)); + } + } + } } static bool isQuotedEtag(kj::StringPtr etag) { @@ -551,6 +568,20 @@ R2Bucket::put(jsg::Lock& js, kj::String name, kj::Maybe value, KJ_IF_SOME(s, o.storageClass) { putBuilder.setStorageClass(s); } + KJ_IF_SOME(ssec, o.ssec) { + auto ssecBuilder = putBuilder.initSsec(); + KJ_SWITCH_ONEOF(ssec.key) { + KJ_CASE_ONEOF(keyString, kj::String) { + JSG_REQUIRE(std::regex_match(keyString.begin(), keyString.end(), std::regex("[0-9a-f]")), Error, "SSE-C Key has invalid format"); + JSG_REQUIRE(keyString.size() == 64, Error, "SSE-C Key must be 32 bytes in length"); + ssecBuilder.setKey(kj::str(keyString)); + } + KJ_CASE_ONEOF(keyBuff, kj::Array) { + JSG_REQUIRE(keyBuff.size() == 32, Error, "SSE-C Key must be 32 bytes in length"); + ssecBuilder.setKey(kj::encodeHex(keyBuff)); + } + } + } } auto requestJson = json.encode(requestBuilder); @@ -642,6 +673,20 @@ jsg::Promise> R2Bucket::createMultipartUpload(jsg::L KJ_IF_SOME(s, o.storageClass) { createMultipartUploadBuilder.setStorageClass(s); } + KJ_IF_SOME(ssec, o.ssec) { + auto ssecBuilder = createMultipartUploadBuilder.initSsec(); + KJ_SWITCH_ONEOF(ssec.key) { + KJ_CASE_ONEOF(keyString, kj::String) { + JSG_REQUIRE(std::regex_match(keyString.begin(), keyString.end(), std::regex("[0-9a-f]")), Error, "SSE-C Key has invalid format"); + JSG_REQUIRE(keyString.size() == 64, Error, "SSE-C Key must be 32 bytes in length"); + ssecBuilder.setKey(kj::str(keyString)); + } + KJ_CASE_ONEOF(keyBuff, kj::Array) { + JSG_REQUIRE(keyBuff.size() == 32, Error, "SSE-C Key must be 32 bytes in length"); + ssecBuilder.setKey(kj::encodeHex(keyBuff)); + } + } + } } auto requestJson = json.encode(requestBuilder); diff --git a/src/workerd/api/r2-bucket.h b/src/workerd/api/r2-bucket.h index 757be54d5b1..3250655cbbe 100644 --- a/src/workerd/api/r2-bucket.h +++ b/src/workerd/api/r2-bucket.h @@ -8,6 +8,7 @@ #include #include "streams.h" +#include "workerd/jsg/value.h" #include #include #include @@ -70,11 +71,18 @@ class R2Bucket: public jsg::Object { JSG_STRUCT_TS_OVERRIDE(R2Conditional); }; + struct SSECOptions { + kj::OneOf, kj::String> key; + JSG_STRUCT(key); + JSG_STRUCT_TS_OVERRIDE(R2SSECOptions); + }; + struct GetOptions { jsg::Optional>> onlyIf; jsg::Optional>> range; + jsg::Optional ssec; - JSG_STRUCT(onlyIf, range); + JSG_STRUCT(onlyIf, range, ssec); JSG_STRUCT_TS_OVERRIDE(R2GetOptions); }; @@ -172,8 +180,9 @@ class R2Bucket: public jsg::Object { jsg::Optional, jsg::NonCoercible>> sha384; jsg::Optional, jsg::NonCoercible>> sha512; jsg::Optional storageClass; + jsg::Optional ssec; - JSG_STRUCT(onlyIf, httpMetadata, customMetadata, md5, sha1, sha256, sha384, sha512, storageClass); + JSG_STRUCT(onlyIf, httpMetadata, customMetadata, md5, sha1, sha256, sha384, sha512, storageClass, ssec); JSG_STRUCT_TS_OVERRIDE(R2PutOptions); }; @@ -181,6 +190,7 @@ class R2Bucket: public jsg::Object { jsg::Optional>> httpMetadata; jsg::Optional> customMetadata; jsg::Optional storageClass; + jsg::Optional ssec; JSG_STRUCT(httpMetadata, customMetadata, storageClass); JSG_STRUCT_TS_OVERRIDE(R2MultipartOptions); diff --git a/src/workerd/api/r2.h b/src/workerd/api/r2.h index 23dad20800b..b68479bb708 100644 --- a/src/workerd/api/r2.h +++ b/src/workerd/api/r2.h @@ -18,6 +18,7 @@ namespace workerd::api::public_beta { api::public_beta::R2Bucket::GetResult, \ api::public_beta::R2Bucket::Range, \ api::public_beta::R2Bucket::Conditional, \ + api::public_beta::R2Bucket::SSECOptions, \ api::public_beta::R2Bucket::GetOptions, \ api::public_beta::R2Bucket::PutOptions, \ api::public_beta::R2Bucket::MultipartOptions, \