File
Blob: src/workerd/server/actor-id-impl.c++
| 1 | #include <workerd/jsg/exception.h> |
| 2 | #include <workerd/server/actor-id-impl.h> |
| 3 | #include <workerd/util/entropy.h> |
| 4 | #include <workerd/util/own-util.h> |
| 5 | #include <workerd/util/thread-scopes.h> |
| 6 | |
| 7 | #include <openssl/hmac.h> |
| 8 | |
| 9 | #include <kj/encoding.h> |
| 10 | #include <kj/memory.h> |
| 11 | |
| 12 | namespace workerd::server { |
| 13 | |
| 14 | ActorIdFactoryImpl::ActorIdImpl::ActorIdImpl( |
| 15 | const kj::byte idParam[SHA256_DIGEST_LENGTH], kj::Maybe<kj::String> name) |
| 16 | : name(kj::mv(name)) { |
| 17 | memcpy(id, idParam, sizeof(id)); |
| 18 | } |
| 19 | |
| 20 | kj::String ActorIdFactoryImpl::ActorIdImpl::toString() const { |
| 21 | return kj::encodeHex(kj::ArrayPtr<const kj::byte>(id)); |
| 22 | } |
| 23 | |
| 24 | kj::Maybe<kj::StringPtr> ActorIdFactoryImpl::ActorIdImpl::getName() const { |
| 25 | return name; |
| 26 | } |
| 27 | |
| 28 | kj::Maybe<kj::StringPtr> ActorIdFactoryImpl::ActorIdImpl::getJurisdiction() const { |
| 29 | return kj::none; |
| 30 | } |
| 31 | |
| 32 | bool ActorIdFactoryImpl::ActorIdImpl::equals(const ActorId& other) const { |
| 33 | return kj::arrayPtr(id) == kj::arrayPtr(kj::downcast<const ActorIdImpl>(other).id); |
| 34 | } |
| 35 | |
| 36 | kj::Own<ActorIdFactory::ActorId> ActorIdFactoryImpl::ActorIdImpl::clone() const { |
| 37 | return kj::heap<ActorIdImpl>(id, mapCopyString(name)); |
| 38 | } |
| 39 | |
| 40 | ActorIdFactoryImpl::ActorIdFactoryImpl(kj::StringPtr uniqueKey) { |
| 41 | KJ_ASSERT(SHA256(uniqueKey.asBytes().begin(), uniqueKey.size(), key) == key); |
| 42 | } |
| 43 | |
| 44 | ActorIdFactoryImpl::ActorIdFactoryImpl(const kj::byte keyParam[SHA256_DIGEST_LENGTH]) { |
| 45 | memcpy(key, keyParam, sizeof(key)); |
| 46 | } |
| 47 | |
| 48 | kj::Own<ActorIdFactory::ActorId> ActorIdFactoryImpl::newUniqueId( |
| 49 | kj::Maybe<kj::StringPtr> jurisdiction) { |
| 50 | JSG_REQUIRE( |
| 51 | jurisdiction == kj::none, Error, "Jurisdiction restrictions are not implemented in workerd."); |
| 52 | |
| 53 | // We want to randomly-generate the first 16 bytes, then HMAC those to produce the latter |
| 54 | // 16 bytes. But the HMAC will produce 32 bytes, so we're only taking a prefix of it. We'll |
| 55 | // allocate a single array big enough to output the HMAC as a suffix, which will then get |
| 56 | // truncated. |
| 57 | kj::byte id[BASE_LENGTH + SHA256_DIGEST_LENGTH]{}; |
| 58 | |
| 59 | if (isPredictableModeForTest()) { |
| 60 | memcpy(id, &counter, sizeof(counter)); |
| 61 | kj::arrayPtr(id).slice(counter).fill(0); |
| 62 | ++counter; |
| 63 | } else { |
| 64 | getEntropy(kj::arrayPtr(id, BASE_LENGTH)); |
| 65 | } |
| 66 | |
| 67 | computeMac(id); |
| 68 | return kj::heap<ActorIdImpl>(id, kj::none); |
| 69 | } |
| 70 | |
| 71 | kj::Own<ActorIdFactory::ActorId> ActorIdFactoryImpl::idFromName(kj::String name) { |
| 72 | kj::byte id[BASE_LENGTH + SHA256_DIGEST_LENGTH]{}; |
| 73 | |
| 74 | // Compute the first half of the ID by HMACing the name itself. We're using HMAC as a keyed |
| 75 | // hash here, not actually for authentication, but it works. |
| 76 | unsigned int len = SHA256_DIGEST_LENGTH; |
| 77 | KJ_ASSERT( |
| 78 | HMAC(EVP_sha256(), key, sizeof(key), name.asBytes().begin(), name.size(), id, &len) == id); |
| 79 | KJ_ASSERT(len == SHA256_DIGEST_LENGTH); |
| 80 | |
| 81 | computeMac(id); |
| 82 | return kj::heap<ActorIdImpl>(id, kj::mv(name)); |
| 83 | } |
| 84 | |
| 85 | kj::Own<ActorIdFactory::ActorId> ActorIdFactoryImpl::idFromString(kj::String str) { |
| 86 | auto decoded = kj::decodeHex(str); |
| 87 | JSG_REQUIRE(str.size() == SHA256_DIGEST_LENGTH * 2 && !decoded.hadErrors && |
| 88 | decoded.size() == SHA256_DIGEST_LENGTH, |
| 89 | TypeError, "Invalid Durable Object ID: must be 64 hex digits"); |
| 90 | |
| 91 | kj::byte id[BASE_LENGTH + SHA256_DIGEST_LENGTH]{}; |
| 92 | memcpy(id, decoded.begin(), BASE_LENGTH); |
| 93 | computeMac(id); |
| 94 | |
| 95 | // Verify that the computed mac matches the input. |
| 96 | JSG_REQUIRE(kj::arrayPtr(id).slice(BASE_LENGTH).startsWith(decoded.asPtr().slice(BASE_LENGTH)), |
| 97 | TypeError, "Durable Object ID is not valid for this namespace."); |
| 98 | |
| 99 | return kj::heap<ActorIdImpl>(id, kj::none); |
| 100 | } |
| 101 | |
| 102 | kj::Own<ActorIdFactory> ActorIdFactoryImpl::cloneWithJurisdiction( |
| 103 | kj::Maybe<kj::StringPtr> maybeJurisdiction) { |
| 104 | if (maybeJurisdiction == kj::none) { |
| 105 | return kj::heap<ActorIdFactoryImpl>(key); |
| 106 | } |
| 107 | |
| 108 | JSG_FAIL_REQUIRE(Error, "Jurisdiction restrictions are not implemented in workerd."); |
| 109 | } |
| 110 | |
| 111 | bool ActorIdFactoryImpl::matchesJurisdiction(const ActorId& id) { |
| 112 | return true; |
| 113 | } |
| 114 | |
| 115 | void ActorIdFactoryImpl::computeMac(kj::byte id[BASE_LENGTH + SHA256_DIGEST_LENGTH]) { |
| 116 | // Given that the first `BASE_LENGTH` bytes of `id` are filled in, compute the second half |
| 117 | // of the ID by HMACing the first half. The id must be in a buffer large enough to store the |
| 118 | // first half of the ID plus a full HMAC, even though only a prefix of the HMAC becomes part |
| 119 | // of the final ID. |
| 120 | |
| 121 | kj::byte* hmacOut = id + BASE_LENGTH; |
| 122 | unsigned int len = SHA256_DIGEST_LENGTH; |
| 123 | KJ_ASSERT(HMAC(EVP_sha256(), key, sizeof(key), id, BASE_LENGTH, hmacOut, &len) == hmacOut); |
| 124 | KJ_ASSERT(len == SHA256_DIGEST_LENGTH); |
| 125 | } |
| 126 | |
| 127 | } //namespace workerd::server |