diff --git a/runtime/fastly/CMakeLists.txt b/runtime/fastly/CMakeLists.txt index 8946db3dc6..a9e608b3cc 100644 --- a/runtime/fastly/CMakeLists.txt +++ b/runtime/fastly/CMakeLists.txt @@ -61,6 +61,29 @@ add_builtin(fastly::html_rewriter ${CMAKE_CURRENT_SOURCE_DIR}/crates/rust-lol-html/include ) +add_rust_lib(crypto-rasn-wrapper "${CMAKE_CURRENT_SOURCE_DIR}/crates/crypto-rasn-wrapper") + +add_builtin( + fastly::crypto + SRC + builtins/crypto/crypto.cpp + builtins/crypto/crypto-algorithm.cpp + builtins/crypto/crypto-key.cpp + builtins/crypto/crypto-key-ec-components.cpp + builtins/crypto/crypto-key-rsa-components.cpp + builtins/crypto/json-web-key.cpp + builtins/crypto/subtle-crypto.cpp + builtins/crypto/uuid.cpp + DEPENDENCIES + OpenSSL::Crypto + crypto-rasn-wrapper + fmt + INCLUDE_DIRS + runtime + ${CMAKE_CURRENT_SOURCE_DIR}/crates/crypto-rasn-wrapper/target/cxxbridge/crypto-rasn-wrapper/src + ${CMAKE_CURRENT_SOURCE_DIR}/crates/crypto-rasn-wrapper/target/cxxbridge/rust + ) + add_compile_definitions(PUBLIC RUNTIME_VERSION=${RUNTIME_VERSION}) project(FastlyJS) diff --git a/runtime/fastly/builtins/crypto/crypto-algorithm.cpp b/runtime/fastly/builtins/crypto/crypto-algorithm.cpp new file mode 100644 index 0000000000..49f6338f7a --- /dev/null +++ b/runtime/fastly/builtins/crypto/crypto-algorithm.cpp @@ -0,0 +1,1993 @@ +#include "openssl/rsa.h" +#include "openssl/sha.h" +#include +#include +#include +#include +#include +#include + +#include "cxx.h" +#include "lib.rs.h" + +#include "../../../StarlingMonkey/builtins/web/base64.h" +#include "../../../StarlingMonkey/builtins/web/dom-exception.h" +#include "crypto-algorithm.h" + +#include "crypto-key-ec-components.h" +#include "crypto-key-rsa-components.h" +#include "encode.h" + +namespace fastly::crypto { + +using builtins::web::dom_exception::DOMException; + +// namespace { + +int numBitsToBytes(int x) { return (x / 8) + (7 + (x % 8)) / 8; } + +std::pair, size_t> +convertToBytesExpand(JSContext *cx, const BIGNUM *bignum, size_t minimumBufferSize) { + int length = BN_num_bytes(bignum); + + size_t bufferSize = std::max(length, minimumBufferSize); + mozilla::UniquePtr bytes{ + static_cast(JS_malloc(cx, bufferSize))}; + + size_t paddingLength = bufferSize - length; + if (paddingLength > 0) { + uint8_t padding = BN_is_negative(bignum) ? 0xFF : 0x00; + std::fill_n(bytes.get(), paddingLength, padding); + } + BN_bn2bin(bignum, bytes.get() + paddingLength); + return std::pair, size_t>(std::move(bytes), + bufferSize); +} + +const EVP_MD *createDigestAlgorithm(JSContext *cx, CryptoAlgorithmIdentifier hashIdentifier) { + switch (hashIdentifier) { + case CryptoAlgorithmIdentifier::MD5: { + return EVP_md5(); + } + case CryptoAlgorithmIdentifier::SHA_1: { + return EVP_sha1(); + } + case CryptoAlgorithmIdentifier::SHA_256: { + return EVP_sha256(); + } + case CryptoAlgorithmIdentifier::SHA_384: { + return EVP_sha384(); + } + case CryptoAlgorithmIdentifier::SHA_512: { + return EVP_sha512(); + } + default: { + DOMException::raise(cx, "NotSupportedError", "NotSupportedError"); + return nullptr; + } + } +} + +const EVP_MD *createDigestAlgorithm(JSContext *cx, JS::HandleObject key) { + + JS::RootedObject alg(cx, CryptoKey::get_algorithm(key)); + + JS::RootedValue hash_val(cx); + JS_GetProperty(cx, alg, "hash", &hash_val); + if (!hash_val.isObject()) { + DOMException::raise(cx, "NotSupportedError", "NotSupportedError"); + return nullptr; + } + JS::RootedObject hash(cx, &hash_val.toObject()); + JS::RootedValue name_val(cx); + JS_GetProperty(cx, hash, "name", &name_val); + auto name_chars = core::encode(cx, name_val); + if (!name_chars) { + DOMException::raise(cx, "NotSupportedError", "NotSupportedError"); + return nullptr; + } + + std::string_view name = name_chars; + if (name == "MD5") { + return EVP_md5(); + } else if (name == "SHA-1") { + return EVP_sha1(); + } else if (name == "SHA-224") { + return EVP_sha224(); + } else if (name == "SHA-256") { + return EVP_sha256(); + } else if (name == "SHA-384") { + return EVP_sha384(); + } else if (name == "SHA-512") { + return EVP_sha512(); + } else { + DOMException::raise(cx, "NotSupportedError", "NotSupportedError"); + return nullptr; + } +} +// This implements https://w3c.github.io/webcrypto/#sha-operations for all +// the SHA algorithms that we support. +std::optional> rawDigest(JSContext *cx, std::span data, + const EVP_MD *algorithm, size_t buffer_size) { + unsigned int size; + std::vector buf(buffer_size, 0); + if (!EVP_Digest(data.data(), data.size(), buf.data(), &size, algorithm, NULL)) { + // 2. If performing the operation results in an error, then throw an OperationError. + DOMException::raise(cx, "SubtleCrypto.digest: failed to create digest", "OperationError"); + return std::nullopt; + } + return {std::move(buf)}; +}; + +// This implements https://w3c.github.io/webcrypto/#sha-operations for all +// the SHA algorithms that we support. +JSObject *digest(JSContext *cx, std::span data, const EVP_MD *algorithm, + size_t buffer_size) { + unsigned int size; + mozilla::UniquePtr buf{ + static_cast(JS_malloc(cx, buffer_size))}; + if (!buf) { + JS_ReportOutOfMemory(cx); + return nullptr; + } + if (!EVP_Digest(data.data(), data.size(), buf.get(), &size, algorithm, NULL)) { + // 2. If performing the operation results in an error, then throw an OperationError. + DOMException::raise(cx, "SubtleCrypto.digest: failed to create digest", "OperationError"); + return nullptr; + } + // 3. Return a new ArrayBuffer containing result. + JS::RootedObject array_buffer(cx); + array_buffer.set(JS::NewArrayBufferWithContents( + cx, size, buf.get(), JS::NewArrayBufferOutOfMemory::CallerMustFreeMemory)); + if (!array_buffer) { + JS_ReportOutOfMemory(cx); + return nullptr; + } + + // `array_buffer` now owns `buf` + static_cast(buf.release()); + + return array_buffer; +}; + +// https://datatracker.ietf.org/doc/html/rfc7518#section-6.3.1 +// 6.3.1. Parameters for RSA Public Keys +std::unique_ptr createRSAPublicKeyFromJWK(JSContext *cx, JsonWebKey *jwk) { + if (!jwk->n.has_value() || !jwk->e.has_value()) { + DOMException::raise(cx, "Data provided to an operation does not meet requirements", + "DataError"); + return nullptr; + } + auto modulusResult = builtins::web::base64::forgivingBase64Decode( + jwk->n.value(), builtins::web::base64::base64URLDecodeTable); + if (modulusResult.isErr()) { + DOMException::raise( + cx, "The JWK member 'n' could not be base64url decoded or contained padding", "DataError"); + return nullptr; + } + auto modulus = modulusResult.unwrap(); + + // Per RFC 7518 Section 6.3.1.1: https://tools.ietf.org/html/rfc7518#section-6.3.1.1 + if (modulus.starts_with('0')) { + modulus = modulus.erase(0, 1); + } + auto dataResult = builtins::web::base64::stringToJSByteString(cx, jwk->e.value()); + if (dataResult.isErr()) { + DOMException::raise(cx, "Data provided to an operation does not meet requirements", + "DataError"); + return nullptr; + } + auto data = dataResult.unwrap(); + auto exponentResult = builtins::web::base64::forgivingBase64Decode( + data, builtins::web::base64::base64URLDecodeTable); + if (exponentResult.isErr()) { + DOMException::raise( + cx, "The JWK member 'e' could not be base64url decoded or contained padding", "DataError"); + return nullptr; + } + auto exponent = exponentResult.unwrap(); + + // import public key + auto publicKeyComponents = CryptoKeyRSAComponents::createPublic(modulus, exponent); + return publicKeyComponents; +} + +std::unique_ptr createECPublicKeyFromJWK(JSContext *cx, JsonWebKey *jwk) { + if (!jwk->x.has_value() || !jwk->y.has_value()) { + DOMException::raise(cx, "Data provided to an operation does not meet requirements", + "DataError"); + return nullptr; + } + auto xResult = builtins::web::base64::forgivingBase64Decode( + jwk->x.value(), builtins::web::base64::base64URLDecodeTable); + if (xResult.isErr()) { + DOMException::raise( + cx, "The JWK member 'x' could not be base64url decoded or contained padding", "DataError"); + return nullptr; + } + auto x = xResult.unwrap(); + auto yResult = builtins::web::base64::forgivingBase64Decode( + jwk->y.value(), builtins::web::base64::base64URLDecodeTable); + if (yResult.isErr()) { + DOMException::raise( + cx, "The JWK member 'y' could not be base64url decoded or contained padding", "DataError"); + return nullptr; + } + auto y = yResult.unwrap(); + + return CryptoKeyECComponents::createPublic(x, y); +} + +std::unique_ptr createECPrivateKeyFromJWK(JSContext *cx, JsonWebKey *jwk) { + if (!jwk->x.has_value() || !jwk->y.has_value() || !jwk->d.has_value()) { + DOMException::raise(cx, "Data provided to an operation does not meet requirements", + "DataError"); + return nullptr; + } + auto xResult = builtins::web::base64::forgivingBase64Decode( + jwk->x.value(), builtins::web::base64::base64URLDecodeTable); + if (xResult.isErr()) { + DOMException::raise( + cx, "The JWK member 'x' could not be base64url decoded or contained padding", "DataError"); + return nullptr; + } + auto x = xResult.unwrap(); + auto yResult = builtins::web::base64::forgivingBase64Decode( + jwk->y.value(), builtins::web::base64::base64URLDecodeTable); + if (yResult.isErr()) { + DOMException::raise( + cx, "The JWK member 'y' could not be base64url decoded or contained padding", "DataError"); + return nullptr; + } + auto y = yResult.unwrap(); + auto dResult = builtins::web::base64::forgivingBase64Decode( + jwk->d.value(), builtins::web::base64::base64URLDecodeTable); + if (dResult.isErr()) { + DOMException::raise( + cx, "The JWK member 'd' could not be base64url decoded or contained padding", "DataError"); + return nullptr; + } + auto d = dResult.unwrap(); + + return CryptoKeyECComponents::createPrivate(x, y, d); +} +// https://datatracker.ietf.org/doc/html/rfc7518#section-6.3.2 +// 6.3.2. Parameters for RSA Private Keys +std::unique_ptr createRSAPrivateKeyFromJWK(JSContext *cx, JsonWebKey *jwk) { + // 2.10.1 If jwk does not meet the requirements of Section 6.3.2 of JSON Web Algorithms [JWA], + // then throw a DataError. 2.10.2 Let privateKey represents the RSA private key identified by + // interpreting jwk according to Section 6.3.2 of JSON Web Algorithms [JWA]. 2.10.3 If privateKey + // is not a valid RSA private key according to [RFC3447], then throw a DataError. + auto modulusResult = builtins::web::base64::forgivingBase64Decode( + jwk->n.value(), builtins::web::base64::base64URLDecodeTable); + if (modulusResult.isErr()) { + DOMException::raise( + cx, "The JWK member 'n' could not be base64url decoded or contained padding", "DataError"); + return nullptr; + } + auto modulus = modulusResult.unwrap(); + // Per RFC 7518 Section 6.3.1.1: https://tools.ietf.org/html/rfc7518#section-6.3.1.1 + if (modulus.starts_with('0')) { + modulus = modulus.erase(0, 1); + } + auto dataResult = builtins::web::base64::stringToJSByteString(cx, jwk->e.value()); + if (dataResult.isErr()) { + DOMException::raise(cx, "Data provided to an operation does not meet requirements", + "DataError"); + } + auto data = dataResult.unwrap(); + auto exponentResult = builtins::web::base64::forgivingBase64Decode( + data, builtins::web::base64::base64URLDecodeTable); + if (exponentResult.isErr()) { + DOMException::raise( + cx, "The JWK member 'e' could not be base64url decoded or contained padding", "DataError"); + return nullptr; + } + auto exponent = exponentResult.unwrap(); + auto privateExponentResult = builtins::web::base64::forgivingBase64Decode( + jwk->d.value(), builtins::web::base64::base64URLDecodeTable); + if (privateExponentResult.isErr()) { + DOMException::raise( + cx, "The JWK member 'd' could not be base64url decoded or contained padding", "DataError"); + return nullptr; + } + auto privateExponent = privateExponentResult.unwrap(); + if (!jwk->p.has_value() && !jwk->q.has_value() && !jwk->dp.has_value() && !jwk->dp.has_value() && + !jwk->qi.has_value()) { + auto privateKeyComponents = + CryptoKeyRSAComponents::createPrivate(modulus, exponent, privateExponent); + return privateKeyComponents; + } + + if (!jwk->p.has_value() || !jwk->q.has_value() || !jwk->dp.has_value() || !jwk->dq.has_value() || + !jwk->qi.has_value()) { + DOMException::raise(cx, "Data provided to an operation does not meet requirements", + "DataError"); + return nullptr; + } + + auto firstPrimeFactorResult = builtins::web::base64::forgivingBase64Decode( + jwk->p.value(), builtins::web::base64::base64URLDecodeTable); + if (firstPrimeFactorResult.isErr()) { + DOMException::raise( + cx, "The JWK member 'p' could not be base64url decoded or contained padding", "DataError"); + return nullptr; + } + auto firstPrimeFactor = firstPrimeFactorResult.unwrap(); + auto firstFactorCRTExponentResult = builtins::web::base64::forgivingBase64Decode( + jwk->dp.value(), builtins::web::base64::base64URLDecodeTable); + if (firstFactorCRTExponentResult.isErr()) { + DOMException::raise( + cx, "The JWK member 'dp' could not be base64url decoded or contained padding", "DataError"); + return nullptr; + } + auto firstFactorCRTExponent = firstFactorCRTExponentResult.unwrap(); + auto secondPrimeFactorResult = builtins::web::base64::forgivingBase64Decode( + jwk->q.value(), builtins::web::base64::base64URLDecodeTable); + if (secondPrimeFactorResult.isErr()) { + DOMException::raise( + cx, "The JWK member 'q' could not be base64url decoded or contained padding", "DataError"); + return nullptr; + } + auto secondPrimeFactor = secondPrimeFactorResult.unwrap(); + auto secondFactorCRTExponentResult = builtins::web::base64::forgivingBase64Decode( + jwk->dq.value(), builtins::web::base64::base64URLDecodeTable); + if (secondFactorCRTExponentResult.isErr()) { + DOMException::raise( + cx, "The JWK member 'dq' could not be base64url decoded or contained padding", "DataError"); + return nullptr; + } + auto secondFactorCRTExponent = secondFactorCRTExponentResult.unwrap(); + auto secondFactorCRTCoefficientResult = builtins::web::base64::forgivingBase64Decode( + jwk->qi.value(), builtins::web::base64::base64URLDecodeTable); + if (secondFactorCRTCoefficientResult.isErr()) { + DOMException::raise( + cx, "The JWK member 'qi' could not be base64url decoded or contained padding", "DataError"); + return nullptr; + } + auto secondFactorCRTCoefficient = secondFactorCRTCoefficientResult.unwrap(); + + CryptoKeyRSAComponents::PrimeInfo firstPrimeInfo{firstPrimeFactor, firstFactorCRTExponent}; + + CryptoKeyRSAComponents::PrimeInfo secondPrimeInfo{secondPrimeFactor, secondFactorCRTExponent, + secondFactorCRTCoefficient}; + + if (!jwk->oth.size()) { + auto privateKeyComponents = CryptoKeyRSAComponents::createPrivateWithAdditionalData( + modulus, exponent, privateExponent, firstPrimeInfo, secondPrimeInfo, {}); + return privateKeyComponents; + } + + std::vector otherPrimeInfos; + for (const auto &value : jwk->oth) { + auto primeFactorResult = builtins::web::base64::forgivingBase64Decode( + value.r, builtins::web::base64::base64URLDecodeTable); + if (primeFactorResult.isErr()) { + return nullptr; + } + auto primeFactor = primeFactorResult.unwrap(); + auto factorCRTExponentResult = builtins::web::base64::forgivingBase64Decode( + value.d, builtins::web::base64::base64URLDecodeTable); + if (factorCRTExponentResult.isErr()) { + return nullptr; + } + auto factorCRTExponent = factorCRTExponentResult.unwrap(); + auto factorCRTCoefficientResult = builtins::web::base64::forgivingBase64Decode( + value.t, builtins::web::base64::base64URLDecodeTable); + if (factorCRTCoefficientResult.isErr()) { + return nullptr; + } + auto factorCRTCoefficient = factorCRTCoefficientResult.unwrap(); + + otherPrimeInfos.emplace_back(primeFactor, factorCRTExponent, factorCRTCoefficient); + } + + auto privateKeyComponents = CryptoKeyRSAComponents::createPrivateWithAdditionalData( + modulus, exponent, privateExponent, firstPrimeInfo, secondPrimeInfo, otherPrimeInfos); + return privateKeyComponents; +} + +JS::Result toHashIdentifier(JSContext *cx, JS::HandleValue value) { + auto normalizedHashAlgorithm = CryptoAlgorithmDigest::normalize(cx, value); + if (!normalizedHashAlgorithm) { + return JS::Result(JS::Error()); + } + return normalizedHashAlgorithm->identifier(); +} + +std::optional toNamedCurve(std::string_view name) { + if (name == "P-256") { + return NamedCurve::P256; + } else if (name == "P-384") { + return NamedCurve::P384; + } else if (name == "P-521") { + return NamedCurve::P521; + } + + return std::nullopt; +} + +JS::Result toNamedCurve(JSContext *cx, JS::HandleValue value) { + auto nameChars = core::encode(cx, value); + auto name = toNamedCurve(nameChars); + if (name.has_value()) { + return name.value(); + } else { + return JS::Result(JS::Error()); + } +} + +JS::Result curveSize(JSContext *cx, JS::HandleObject key) { + + JS::RootedObject alg(cx, CryptoKey::get_algorithm(key)); + + JS::RootedValue namedCurve_val(cx); + JS_GetProperty(cx, alg, "namedCurve", &namedCurve_val); + auto namedCurve_chars = core::encode(cx, namedCurve_val); + if (!namedCurve_chars) { + return JS::Result(JS::Error()); + } + + std::string_view namedCurve = namedCurve_chars; + if (namedCurve == "P-256") { + return 256; + } else if (namedCurve == "P-384") { + return 384; + } else if (namedCurve == "P-521") { + return 521; + } + + MOZ_ASSERT_UNREACHABLE(); + return 0; +} + +// This implements the first section of +// https://w3c.github.io/webcrypto/#algorithm-normalization-normalize-an-algorithm which is shared +// across all the diffent algorithms, but importantly does not implement the parts to do with the +// chosen `op` (operation) the `op` parts are handled in the specialized `normalize` functions on +// the concrete classes which derive from CryptoAlgorithm such as CryptoAlgorithmDigest. +JS::Result normalizeIdentifier(JSContext *cx, JS::HandleValue value) { + + // The specification states: + // -------- + // If alg is an instance of a DOMString: + // Return the result of running the normalize an algorithm algorithm, + // with the alg set to a new Algorithm dictionary whose name attribute + // is alg, and with the op set to op. + // -------- + // Instead of doing that, we operate on the string and not the dictionary. + // If we see a dictionary (JSObject), we pull the name attribute out + // and coerce it's value to a String. + // The reason we chose this direct is because we only need this one field + // from the provided dictionary, so we store the field on it's own and not + // in a JSObject which would take up more memory. + + // 1. Let registeredAlgorithms be the associative container stored at the op key of + // supportedAlgorithms. + // 2. Let initialAlg be the result of converting the ECMAScript object represented by alg to the + // IDL dictionary type Algorithm, as defined by [WebIDL]. + // 3. If an error occurred, return the error and terminate this algorithm. + // 4. Let algName be the value of the name attribute of initialAlg. + JS::Rooted algName(cx); + if (value.isObject()) { + JS::Rooted params(cx, &value.toObject()); + JS::Rooted name_val(cx); + if (!JS_GetProperty(cx, params, "name", &name_val)) { + return JS::Result(JS::Error()); + } + algName.set(JS::ToString(cx, name_val)); + } else { + algName.set(JS::ToString(cx, value)); + } + // If `algName` is falsey, it means the call to JS::ToString failed. + // In that scenario, we should already have an exception, which is why we are not creating our own + // one. + if (!algName) { + return JS::Result(JS::Error()); + } + + // TODO: We convert from JSString to std::string quite a lot in the codebase, should we pull this + // logic out into a new function? + auto algorithmChars = core::encode(cx, algName); + if (!algorithmChars) { + return JS::Result(JS::Error()); + } + std::string algorithm(algorithmChars.begin(), algorithmChars.len); + + // 5. If registeredAlgorithms contains a key that is a case-insensitive string match for algName: + // 5.1 Set algName to the value of the matching key. + // 5.2 Let desiredType be the IDL dictionary type stored at algName in registeredAlgorithms. + // Note: We do not implement 5.2 here, it is instead implemented in the specialized `normalize` + // functions. + std::transform(algorithm.begin(), algorithm.end(), algorithm.begin(), + [](unsigned char c) { return std::toupper(c); }); + if (algorithm == "RSASSA-PKCS1-V1_5") { + return CryptoAlgorithmIdentifier::RSASSA_PKCS1_v1_5; + } else if (algorithm == "RSA-PSS") { + return CryptoAlgorithmIdentifier::RSA_PSS; + } else if (algorithm == "RSA-OAEP") { + return CryptoAlgorithmIdentifier::RSA_OAEP; + } else if (algorithm == "ECDSA") { + return CryptoAlgorithmIdentifier::ECDSA; + } else if (algorithm == "ECDH") { + return CryptoAlgorithmIdentifier::ECDH; + } else if (algorithm == "AES-CTR") { + return CryptoAlgorithmIdentifier::AES_CTR; + } else if (algorithm == "AES-CBC") { + return CryptoAlgorithmIdentifier::AES_CBC; + } else if (algorithm == "AES-GCM") { + return CryptoAlgorithmIdentifier::AES_GCM; + } else if (algorithm == "AES-KW") { + return CryptoAlgorithmIdentifier::AES_KW; + } else if (algorithm == "HMAC") { + return CryptoAlgorithmIdentifier::HMAC; + } else if (algorithm == "MD5") { + return CryptoAlgorithmIdentifier::MD5; + } else if (algorithm == "SHA-1") { + return CryptoAlgorithmIdentifier::SHA_1; + } else if (algorithm == "SHA-256") { + return CryptoAlgorithmIdentifier::SHA_256; + } else if (algorithm == "SHA-384") { + return CryptoAlgorithmIdentifier::SHA_384; + } else if (algorithm == "SHA-512") { + return CryptoAlgorithmIdentifier::SHA_512; + } else if (algorithm == "HKDF") { + return CryptoAlgorithmIdentifier::HKDF; + } else if (algorithm == "PBKDF2") { + return CryptoAlgorithmIdentifier::PBKDF2; + } else { + // Otherwise: Return a new NotSupportedError and terminate this algorithm. + DOMException::raise(cx, "Algorithm: Unrecognized name", "NotSupportedError"); + return JS::Result(JS::Error()); + } +} +// } // namespace + +const char *curveName(NamedCurve curve) { + switch (curve) { + case NamedCurve::P256: { + return "P-256"; + } + case NamedCurve::P384: { + return "P-384"; + } + case NamedCurve::P521: { + return "P-521"; + } + default: { + MOZ_ASSERT_UNREACHABLE("Unknown `CryptoAlgorithmIdentifier` value"); + } + } +} + +const char *algorithmName(CryptoAlgorithmIdentifier algorithm) { + switch (algorithm) { + case CryptoAlgorithmIdentifier::RSASSA_PKCS1_v1_5: { + return "RSASSA-PKCS1-v1_5"; + } + case CryptoAlgorithmIdentifier::RSA_PSS: { + return "RSA-PSS"; + } + case CryptoAlgorithmIdentifier::RSA_OAEP: { + return "RSA-OAEP"; + } + case CryptoAlgorithmIdentifier::ECDSA: { + return "ECDSA"; + } + case CryptoAlgorithmIdentifier::ECDH: { + return "ECDH"; + } + case CryptoAlgorithmIdentifier::AES_CTR: { + return "AES-CTR"; + } + case CryptoAlgorithmIdentifier::AES_CBC: { + return "AES-CBC"; + } + case CryptoAlgorithmIdentifier::AES_GCM: { + return "AES-GCM"; + } + case CryptoAlgorithmIdentifier::AES_KW: { + return "AES-KW"; + } + case CryptoAlgorithmIdentifier::HMAC: { + return "HMAC"; + } + case CryptoAlgorithmIdentifier::MD5: { + return "MD5"; + } + case CryptoAlgorithmIdentifier::SHA_1: { + return "SHA-1"; + } + case CryptoAlgorithmIdentifier::SHA_256: { + return "SHA-256"; + } + case CryptoAlgorithmIdentifier::SHA_384: { + return "SHA-384"; + } + case CryptoAlgorithmIdentifier::SHA_512: { + return "SHA-512"; + } + case CryptoAlgorithmIdentifier::HKDF: { + return "HKDF"; + } + case CryptoAlgorithmIdentifier::PBKDF2: { + return "PBKDF2"; + } + default: { + MOZ_ASSERT_UNREACHABLE("Unknown `CryptoAlgorithmIdentifier` value"); + } + } +} + +// clang-format off +/// This table is from https://w3c.github.io/webcrypto/#h-note-15 +// | Algorithm | encrypt | decrypt | sign | verify | digest | generateKey | deriveKey | deriveBits | importKey | exportKey | wrapKey | unwrapKey | +// | RSASSA-PKCS1-v1_5 | | | ✔ | ✔ | | ✔ | | | ✔ | ✔ | | | +// | RSA-PSS | | | ✔ | ✔ | | ✔ | | | ✔ | ✔ | | | +// | RSA-OAEP | ✔ | ✔ | | | | ✔ | | | ✔ | ✔ | ✔ | ✔ | +// | ECDSA | | | ✔ | ✔ | | ✔ | | | ✔ | ✔ | | | +// | ECDH | | | | | | ✔ | ✔ | ✔ | ✔ | ✔ | | | +// | AES-CTR | ✔ | ✔ | | | | ✔ | | | ✔ | ✔ | ✔ | ✔ | +// | AES-CBC | ✔ | ✔ | | | | ✔ | | | ✔ | ✔ | ✔ | ✔ | +// | AES-GCM | ✔ | ✔ | | | | ✔ | | | ✔ | ✔ | ✔ | ✔ | +// | AES-KW | | | | | | ✔ | | | ✔ | ✔ | ✔ | ✔ | +// | HMAC | | | ✔ | ✔ | | ✔ | | | ✔ | ✔ | | | +// | SHA-1 | | | | | ✔ | | | | | | | | +// | SHA-256 | | | | | ✔ | | | | | | | | +// | SHA-384 | | | | | ✔ | | | | | | | | +// | SHA-512 | | | | | ✔ | | | | | | | | +// | HKDF | | | | | | | ✔ | ✔ | ✔ | | | | +// | PBKDF2 | | | | | | | ✔ | ✔ | ✔ | | | | +//clang-format on + +std::unique_ptr CryptoAlgorithmDigest::normalize(JSContext *cx, + JS::HandleValue value) { + // Do steps 1 through 5.1 of https://w3c.github.io/webcrypto/#algorithm-normalization-normalize-an-algorithm + auto identifierResult = normalizeIdentifier(cx, value); + if (identifierResult.isErr()) { + // If we are here, this means either the identifier could not be coerced to a String or was not recognized + // In both those scenarios an exception will have already been created, which is why we are not creating one here. + return nullptr; + } + auto identifier = identifierResult.unwrap(); + + // The table listed at https://w3c.github.io/webcrypto/#h-note-15 is what defines which algorithms support which operations + // SHA-1, SHA-256, SHA-384, and SHA-512 are the only algorithms which support the digest operation + // We also support MD5 as an extra implementor defined algorithm + + // Note: The specification states that none of the SHA algorithms take any parameters -- https://w3c.github.io/webcrypto/#sha-registration + switch (identifier) { + case CryptoAlgorithmIdentifier::MD5: { + return std::make_unique(); + } + case CryptoAlgorithmIdentifier::SHA_1: { + return std::make_unique(); + } + case CryptoAlgorithmIdentifier::SHA_256: { + return std::make_unique(); + } + case CryptoAlgorithmIdentifier::SHA_384: { + return std::make_unique(); + } + case CryptoAlgorithmIdentifier::SHA_512: { + return std::make_unique(); + } + default: { + DOMException::raise(cx, "Supplied algorithm does not support the digest operation", "NotSupportedError"); + return nullptr; + } + } +}; + +std::unique_ptr +CryptoAlgorithmSignVerify::normalize(JSContext *cx, JS::HandleValue value) { + // Do steps 1 through 5.1 of https://w3c.github.io/webcrypto/#algorithm-normalization-normalize-an-algorithm + auto identifierResult = normalizeIdentifier(cx, value); + if (identifierResult.isErr()) { + // If we are here, this means either the identifier could not be coerced to a String or was not recognized + // In both those scenarios an exception will have already been created, which is why we are not creating one here. + return nullptr; + } + auto identifier = identifierResult.unwrap(); + JS::Rooted params(cx); + + // The value can either be a JS String or a JS Object with a 'name' property which is the algorithm identifier. + // Other properties within the object will be the parameters for the algorithm to use. + if (value.isString()) { + auto obj = JS_NewPlainObject(cx); + params.set(obj); + if (!JS_SetProperty(cx, params, "name", value)) { + return nullptr; + } + } else if (value.isObject()) { + params.set(&value.toObject()); + } + + // The table listed at https://w3c.github.io/webcrypto/#h-note-15 is what defines which algorithms support which operations + // RSASSA-PKCS1-v1_5, RSA-PSS, ECDSA, HMAC, are the algorithms + // which support the sign operation + switch (identifier) { + case CryptoAlgorithmIdentifier::RSASSA_PKCS1_v1_5: { + return std::make_unique(); + } + case CryptoAlgorithmIdentifier::HMAC: { + return std::make_unique(); + } + case CryptoAlgorithmIdentifier::ECDSA: { + return CryptoAlgorithmECDSA_Sign_Verify::fromParameters(cx, params); + } + case CryptoAlgorithmIdentifier::RSA_PSS: { + MOZ_ASSERT(false); + DOMException::raise(cx, "Supplied algorithm is not yet supported", "NotSupportedError"); + return nullptr; + } + default: { + return nullptr; + } + } +}; + + +namespace { + std::optional, size_t>> hmacSignature(JSContext *cx, + const EVP_MD* algorithm, const std::span& keyData, const std::span data) { + EVP_MD_CTX *ctx = EVP_MD_CTX_new(); + if (!ctx) { + return std::nullopt; + } + + EVP_PKEY * hkey = EVP_PKEY_new_mac_key(EVP_PKEY_HMAC, nullptr, keyData.data(), keyData.size()); + if (!hkey) { + return std::nullopt; + } + + if (1 != EVP_DigestSignInit(ctx, nullptr, algorithm, nullptr, hkey)) { + return std::nullopt; + } + + if (1 != EVP_DigestSignUpdate(ctx, data.data(), data.size())) { + return std::nullopt; + } + + size_t len = 0; + if (1 != EVP_DigestSignFinal(ctx, nullptr, &len)) { + return std::nullopt; + } + + mozilla::UniquePtr cipherText{static_cast(JS_malloc(cx, len))}; + if (!cipherText) { + JS_ReportOutOfMemory(cx); + return std::nullopt; + } + + if (1 != EVP_DigestSignFinal(ctx, cipherText.get(), &len)) { + return std::nullopt; + } + return std::pair, size_t>(std::move(cipherText), len); + } +} + +JSObject *CryptoAlgorithmHMAC_Sign_Verify::sign(JSContext *cx, JS::HandleObject key, std::span data) { + MOZ_ASSERT(CryptoKey::is_instance(key)); + + // 1. Let mac be the result of performing the MAC Generation operation described in Section 4 of [FIPS-198-1] using the key represented by [[handle]] internal slot of key, the hash function identified by the hash attribute of the [[algorithm]] internal slot of key and message as the input data text. + const EVP_MD *algorithm = createDigestAlgorithm(cx, key); + if (!algorithm) { + DOMException::raise(cx, "OperationError", "OperationError"); + return nullptr; + } + + std::span keyData = CryptoKey::hmacKeyData(key); + auto result = hmacSignature(cx, algorithm, keyData, data); + if (!result.has_value()) { + DOMException::raise(cx, "SubtleCrypto.sign: failed to sign", "OperationError"); + return nullptr; + } + auto sig = std::move(result.value().first); + auto size = std::move(result.value().second); + + // 2. Return a new ArrayBuffer object, associated with the relevant global object of this [HTML], and containing the bytes of mac. + JS::RootedObject array_buffer(cx); + array_buffer.set(JS::NewArrayBufferWithContents(cx, size, sig.get(), JS::NewArrayBufferOutOfMemory::CallerMustFreeMemory)); + if (!array_buffer) { + JS_ReportOutOfMemory(cx); + return nullptr; + } + + // `array_buffer` now owns `sig` + static_cast(sig.release()); + + return array_buffer; + +}; +JS::Result CryptoAlgorithmHMAC_Sign_Verify::verify(JSContext *cx, JS::HandleObject key, std::span signature, std::span data) { + MOZ_ASSERT(CryptoKey::is_instance(key)); + // 1. Let mac be the result of performing the MAC Generation operation described in Section 4 of [FIPS-198-1] using the key represented by [[handle]] internal slot of key, the hash function identified by the hash attribute of the [[algorithm]] internal slot of key and message as the input data text. + const EVP_MD *algorithm = createDigestAlgorithm(cx, key); + if (!algorithm) { + DOMException::raise(cx, "OperationError", "OperationError"); + return JS::Result(JS::Error()); + } + + std::span keyData = CryptoKey::hmacKeyData(key); + auto result = hmacSignature(cx, algorithm, keyData, data); + if (!result.has_value()) { + DOMException::raise(cx, "SubtleCrypto.verify: failed to verify", "OperationError"); + return JS::Result(JS::Error()); + } + auto sig = std::move(result.value().first); + auto size = std::move(result.value().second); + + + // 2. Return true if mac is equal to signature and false otherwise. + bool match = size == signature.size() && (CRYPTO_memcmp(sig.get(), signature.data(), size) == 0); + return match; +}; +JSObject *CryptoAlgorithmHMAC_Sign_Verify::toObject(JSContext *cx) { + return nullptr; +}; + +JSObject *CryptoAlgorithmECDSA_Sign_Verify::sign(JSContext *cx, JS::HandleObject key, std::span data) { + MOZ_ASSERT(CryptoKey::is_instance(key)); + + // 1. If the [[type]] internal slot of key is not "private", then throw an InvalidAccessError. + if (CryptoKey::type(key) != CryptoKeyType::Private) { + DOMException::raise(cx, "InvalidAccessError", "InvalidAccessError"); + return nullptr; + } + + // 2. Let hashAlgorithm be the hash member of normalizedAlgorithm. + const EVP_MD* algorithm = createDigestAlgorithm(cx, this->hashIdentifier); + if (!algorithm) { + DOMException::raise(cx, "SubtleCrypto.sign: failed to sign", "OperationError"); + return nullptr; + } + + // 3. Let M be the result of performing the digest operation specified by hashAlgorithm using message. + auto digestOption = rawDigest(cx, data, algorithm, EVP_MD_size(algorithm)); + if (!digestOption.has_value()) { + DOMException::raise(cx, "OperationError", "OperationError"); + return nullptr; + } + + auto digest = digestOption.value(); + + // 4. Let d be the ECDSA private key associated with key. + #pragma clang diagnostic push + #pragma clang diagnostic ignored "-Wdeprecated-declarations" + const EC_KEY * ecKey = EVP_PKEY_get0_EC_KEY(CryptoKey::key(key)); + #pragma clang diagnostic pop + if (!ecKey) { + DOMException::raise(cx, "SubtleCrypto.verify: failed to verify", "OperationError"); + return nullptr; + } + + // 5. Let params be the EC domain parameters associated with key. + #pragma clang diagnostic push + #pragma clang diagnostic ignored "-Wdeprecated-declarations" + auto sig = ECDSA_do_sign(digest.data(), digest.size(), const_cast(ecKey)); + #pragma clang diagnostic pop + if (!sig) { + DOMException::raise(cx, "SubtleCrypto.verify: failed to verify", "OperationError"); + return nullptr; + } + + // 6. If the namedCurve attribute of the [[algorithm]] internal slot of key is "P-256", "P-384" or "P-521": + // Perform the ECDSA signing process, as specified in [RFC6090], Section 5.4, with M as the message, using params as the EC domain parameters, and with d as the private key. + // Let r and s be the pair of integers resulting from performing the ECDSA signing process. + // Let result be an empty byte sequence. + // Let n be the smallest integer such that n * 8 is greater than the logarithm to base 2 of the order of the base point of the elliptic curve identified by params. + // Convert r to an octet string of length n and append this sequence of bytes to result. + // Convert s to an octet string of length n and append this sequence of bytes to result. + // Otherwise, the namedCurve attribute of the [[algorithm]] internal slot of key is a value specified in an applicable specification: + // Perform the ECDSA signature steps specified in that specification, passing in M, params and d and resulting in result. + const BIGNUM* r; + const BIGNUM* s; + ECDSA_SIG_get0(sig, &r, &s); + auto keySize = curveSize(cx, key); + if (keySize.isErr()) { + return nullptr; + } + + size_t keySizeInBytes = numBitsToBytes(keySize.unwrap()); + + auto rBytesAndSize = convertToBytesExpand(cx, r, keySizeInBytes); + auto *rBytes = rBytesAndSize.first.get(); + auto rBytesSize = rBytesAndSize.second; + + auto sBytesAndSize = convertToBytesExpand(cx, s, keySizeInBytes); + auto *sBytes = sBytesAndSize.first.get(); + auto sBytesSize = sBytesAndSize.second; + + auto resultSize = rBytesSize + sBytesSize; + mozilla::UniquePtr result{ + static_cast(JS_malloc(cx, resultSize))}; + + std::memcpy(result.get(), rBytes, rBytesSize); + std::memcpy(result.get() + rBytesSize, sBytes, sBytesSize); + + // 7. Return the result of creating an ArrayBuffer containing result. + JS::RootedObject buffer(cx, JS::NewArrayBufferWithContents(cx, resultSize, result.get(), JS::NewArrayBufferOutOfMemory::CallerMustFreeMemory)); + if (!buffer) { + // We can be here if the array buffer was too large -- if that was the case then a + // JSMSG_BAD_ARRAY_LENGTH will have been created. Otherwise we're probably out of memory. + if (!JS_IsExceptionPending(cx)) { + js::ReportOutOfMemory(cx); + } + return nullptr; + } + + // `signature` is now owned by `buffer` + static_cast(result.release()); + + return buffer; +}; +JS::Result CryptoAlgorithmECDSA_Sign_Verify::verify(JSContext *cx, JS::HandleObject key, std::span signature, std::span data) { + MOZ_ASSERT(CryptoKey::is_instance(key)); + // 1. If the [[type]] internal slot of key is not "public", then throw an InvalidAccessError. + if (CryptoKey::type(key) != CryptoKeyType::Public) { + DOMException::raise(cx, "InvalidAccessError", "InvalidAccessError"); + return JS::Result(JS::Error()); + } + + // 2. Let hashAlgorithm be the hash member of normalizedAlgorithm. + const EVP_MD* algorithm = createDigestAlgorithm(cx, this->hashIdentifier); + if (!algorithm) { + DOMException::raise(cx, "SubtleCrypto.verify: failed to verify", "OperationError"); + return JS::Result(JS::Error()); + } + + // 3. Let M be the result of performing the digest operation specified by hashAlgorithm using message. + auto digestOption = rawDigest(cx, data, algorithm, EVP_MD_size(algorithm)); + if (!digestOption.has_value()) { + DOMException::raise(cx, "OperationError", "OperationError"); + return JS::Result(JS::Error()); + } + + auto digest = digestOption.value(); + + // 4. Let Q be the ECDSA public key associated with key. + #pragma clang diagnostic push + #pragma clang diagnostic ignored "-Wdeprecated-declarations" + const EC_KEY * ecKey = EVP_PKEY_get0_EC_KEY(CryptoKey::key(key)); + #pragma clang diagnostic pop + if (!ecKey) { + DOMException::raise(cx, "SubtleCrypto.verify: failed to verify", "OperationError"); + return JS::Result(JS::Error()); + } + + // 5. Let params be the EC domain parameters associated with key. + // 6. If the namedCurve attribute of the [[algorithm]] internal slot of key is "P-256", "P-384" or "P-521": + // Perform the ECDSA verifying process, as specified in RFC6090, Section 5.3, with M as the received message, signature as the received signature and using params as the EC domain parameters, and Q as the public key. + // Otherwise, the namedCurve attribute of the [[algorithm]] internal slot of key is a value specified in an applicable specification: + // Perform the ECDSA verification steps specified in that specification passing in M, signature, params and Q and resulting in an indication of whether or not the purported signature is valid. + auto keySize = curveSize(cx, key); + if (keySize.isErr()) { + return JS::Result(JS::Error()); + } + + size_t keySizeInBytes = numBitsToBytes(keySize.unwrap()); + + auto sig = ECDSA_SIG_new(); + auto r = BN_bin2bn(signature.data(), keySizeInBytes, nullptr); + auto s = BN_bin2bn(signature.data() + keySizeInBytes, keySizeInBytes, nullptr); + + if (!ECDSA_SIG_set0(sig, r, s)) { + DOMException::raise(cx, "SubtleCrypto.verify: failed to verify", "OperationError"); + return JS::Result(JS::Error()); + } + + // 7. Let result be a boolean with the value true if the signature is valid and the value false otherwise. + #pragma clang diagnostic push + #pragma clang diagnostic ignored "-Wdeprecated-declarations" + bool result = ECDSA_do_verify(digest.data(), digest.size(), sig, const_cast(ecKey)) == 1; + #pragma clang diagnostic pop + + // 8. Return result. + return result; +}; +JSObject *CryptoAlgorithmECDSA_Sign_Verify::toObject(JSContext *cx) { + return nullptr; +}; + +JSObject *CryptoAlgorithmRSASSA_PKCS1_v1_5_Sign_Verify::sign(JSContext *cx, JS::HandleObject key, + std::span data) { + + // 1. If the [[type]] internal slot of key is not "private", then throw an InvalidAccessError. + if (CryptoKey::type(key) != CryptoKeyType::Private) { + DOMException::raise(cx, "InvalidAccessError", "InvalidAccessError"); + return nullptr; + } + + MOZ_ASSERT(CryptoKey::is_instance(key)); + if (CryptoKey::type(key) != CryptoKeyType::Private) { + DOMException::raise(cx, "InvalidAccessError", "InvalidAccessError"); + return nullptr; + } + + const EVP_MD *algorithm = createDigestAlgorithm(cx, key); + if (!algorithm) { + DOMException::raise(cx, "OperationError", "OperationError"); + return nullptr; + } + + auto digest = rawDigest(cx, data, algorithm, EVP_MD_size(algorithm)); + if (!digest.has_value()) { + DOMException::raise(cx, "OperationError", "OperationError"); + return nullptr; + } + + // 2. Perform the signature generation operation defined in Section 8.2 of [RFC3447] with the + // key represented by the [[handle]] internal slot of key as the signer's private key and the + // contents of message as M and using the hash function specified in the hash attribute of the + // [[algorithm]] internal slot of key as the Hash option for the EMSA-PKCS1-v1_5 encoding + // method. + // 3. If performing the operation results in an error, then throw an OperationError. + auto ctx = EVP_PKEY_CTX_new(CryptoKey::key(key), nullptr); + if (!ctx) { + DOMException::raise(cx, "OperationError", "OperationError"); + return nullptr; + } + + if (EVP_PKEY_sign_init(ctx) <= 0) { + DOMException::raise(cx, "OperationError", "OperationError"); + return nullptr; + } + + if (EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_PKCS1_PADDING) <= 0) { + DOMException::raise(cx, "OperationError", "OperationError"); + return nullptr; + } + + if (EVP_PKEY_CTX_set_signature_md(ctx, algorithm) <= 0) { + DOMException::raise(cx, "OperationError", "OperationError"); + return nullptr; + } + + size_t signature_length; + if (EVP_PKEY_sign(ctx, nullptr, &signature_length, digest->data(), digest->size()) <= 0) { + DOMException::raise(cx, "OperationError", "OperationError"); + return nullptr; + } + + // 4. Let signature be the value S that results from performing the operation. + mozilla::UniquePtr signature{static_cast(JS_malloc(cx, signature_length))}; + if (EVP_PKEY_sign(ctx, signature.get(), &signature_length, digest->data(), digest->size()) <= 0) { + DOMException::raise(cx, "OperationError", "OperationError"); + return nullptr; + } + + // 5. Return a new ArrayBuffer associated with the relevant global object of this [HTML], and + // containing the bytes of signature. + JS::RootedObject buffer(cx, JS::NewArrayBufferWithContents(cx, signature_length, signature.get(), JS::NewArrayBufferOutOfMemory::CallerMustFreeMemory)); + if (!buffer) { + // We can be here if the array buffer was too large -- if that was the case then a + // JSMSG_BAD_ARRAY_LENGTH will have been created. Otherwise we're probably out of memory. + if (!JS_IsExceptionPending(cx)) { + js::ReportOutOfMemory(cx); + } + return nullptr; + } + + // `signature` is now owned by `buffer` + static_cast(signature.release()); + + return buffer; +} + +JS::Result CryptoAlgorithmRSASSA_PKCS1_v1_5_Sign_Verify::verify(JSContext *cx, JS::HandleObject key, + std::span signature, + std::span data) { + MOZ_ASSERT(CryptoKey::is_instance(key)); + + if (CryptoKey::type(key) != CryptoKeyType::Public) { + DOMException::raise(cx, "InvalidAccessError", "InvalidAccessError"); + return JS::Result(JS::Error()); + } + const EVP_MD *algorithm = createDigestAlgorithm(cx, key); + + auto digestOption = rawDigest(cx, data, algorithm, EVP_MD_size(algorithm)); + if (!digestOption.has_value()) { + DOMException::raise(cx, "OperationError", "OperationError"); + return JS::Result(JS::Error()); + } + + auto digest = digestOption.value(); + + auto ctx = EVP_PKEY_CTX_new(CryptoKey::key(key), nullptr); + if (!ctx) { + DOMException::raise(cx, "OperationError", "OperationError"); + return JS::Result(JS::Error()); + } + + if (EVP_PKEY_verify_init(ctx) != 1) { + DOMException::raise(cx, "OperationError", "OperationError"); + return JS::Result(JS::Error()); + } + + if (EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_PKCS1_PADDING) != 1) { + DOMException::raise(cx, "OperationError", "OperationError"); + return JS::Result(JS::Error()); + } + + if (EVP_PKEY_CTX_set_signature_md(ctx, algorithm) != 1) { + DOMException::raise(cx, "OperationError", "OperationError"); + return JS::Result(JS::Error()); + } + + return EVP_PKEY_verify(ctx, signature.data(), signature.size(), digest.data(), digest.size()) == + 1; +} + +std::unique_ptr +CryptoAlgorithmImportKey::normalize(JSContext *cx, JS::HandleValue value) { + // Do steps 1 through 5.1 of https://w3c.github.io/webcrypto/#algorithm-normalization-normalize-an-algorithm + auto identifierResult = normalizeIdentifier(cx, value); + if (identifierResult.isErr()) { + // If we are here, this means either the identifier could not be coerced to a String or was not recognized + // In both those scenarios an exception will have already been created, which is why we are not creating one here. + return nullptr; + } + auto identifier = identifierResult.unwrap(); + JS::RootedObject params(cx); + + // The value can either be a JS String or a JS Object with a 'name' property which is the algorithm identifier. + // Other properties within the object will be the parameters for the algorithm to use. + if (value.isString()) { + auto obj = JS_NewPlainObject(cx); + params.set(obj); + JS_SetProperty(cx, params, "name", value); + } else if (value.isObject()) { + params.set(&value.toObject()); + } + + // The table listed at https://w3c.github.io/webcrypto/#h-note-15 is what defines which algorithms support which operations + // RSASSA-PKCS1-v1_5, RSA-PSS, RSA-OAEP, ECDSA, ECDH, AES-CTR, AES-CBC, AES-GCM, AES-KW, HMAC, HKDF, PBKDF2 are the algorithms + // which support the importKey operation + switch (identifier) { + case CryptoAlgorithmIdentifier::RSASSA_PKCS1_v1_5: { + return CryptoAlgorithmRSASSA_PKCS1_v1_5_Import::fromParameters(cx, params); + } + case CryptoAlgorithmIdentifier::HMAC: { + return CryptoAlgorithmHMAC_Import::fromParameters(cx, params); + } + case CryptoAlgorithmIdentifier::ECDSA: { + return CryptoAlgorithmECDSA_Import::fromParameters(cx, params); + } + case CryptoAlgorithmIdentifier::RSA_PSS: + case CryptoAlgorithmIdentifier::RSA_OAEP: + case CryptoAlgorithmIdentifier::AES_CTR: + case CryptoAlgorithmIdentifier::AES_CBC: + case CryptoAlgorithmIdentifier::AES_GCM: + case CryptoAlgorithmIdentifier::AES_KW: + case CryptoAlgorithmIdentifier::ECDH: + case CryptoAlgorithmIdentifier::HKDF: + case CryptoAlgorithmIdentifier::PBKDF2: { + DOMException::raise(cx, "Supplied algorithm is not yet supported", "NotSupportedError"); + return nullptr; + } + default: { + DOMException::raise(cx, "Supplied algorithm does not support the importKey operation", "NotSupportedError"); + return nullptr; + } + } +}; + +std::unique_ptr CryptoAlgorithmHMAC_Import::fromParameters(JSContext *cx, JS::HandleObject parameters) { + JS::Rooted hash_val(cx); + if (!JS_GetProperty(cx, parameters, "hash", &hash_val)) { + return nullptr; + } + auto hashIdentifier = toHashIdentifier(cx, hash_val); + if (hashIdentifier.isErr()) { + return nullptr; + } + bool found; + unsigned long length; + if (!JS_HasProperty(cx, parameters, "length", &found)) { + return nullptr; + } + if (found) { + JS::Rooted length_val(cx); + if (!JS_GetProperty(cx, parameters, "length", &length_val)) { + return nullptr; + } + if (!length_val.isNumber()) { + return nullptr; + } + length = length_val.toNumber(); + return std::make_unique(hashIdentifier.unwrap(), length); + } else { + return std::make_unique(hashIdentifier.unwrap()); + } +} + +// https://w3c.github.io/webcrypto/#hmac-operations +JSObject *CryptoAlgorithmHMAC_Import::importKey(JSContext *cx, CryptoKeyFormat format, + KeyData keyData, bool extractable, + CryptoKeyUsages usages) { + MOZ_ASSERT(cx); + JS::RootedObject result(cx); + + // 2. If usages contains an entry which is not "sign" or "verify", then throw a SyntaxError. + if (!usages.canOnlySignOrVerify()) { + DOMException::raise(cx, "HMAC keys only support 'sign' and 'verify' operations", + "SyntaxError"); + return nullptr; + } + + std::unique_ptr> data; + // 3. Let hash be a new KeyAlgorithm. + // 4. + switch (format) { + // 5. If format is "raw": + case CryptoKeyFormat::Raw: { + // 5.1 Let data be the octet string contained in keyData. + data = std::make_unique>(std::get>(keyData)); + if (!data) { + DOMException::raise(cx, "Supplied keyData must be either an ArrayBuffer, TypedArray, or DataView.", "DataError"); + return nullptr; + } + + // 5.2 Set hash to equal the hash member of normalizedAlgorithm. + // Step 5.2 is done in the call to CryptoKey::createHMAC + break; + } + //6. If format is "jwk": + case CryptoKeyFormat::Jwk: { + // 6.1 If keyData is a JsonWebKey dictionary: + // 6.2 Let jwk equal keyData. + // Otherwise: + // 6.3 Throw a DataError. + auto jwk = std::get(keyData); + if (!jwk) { + DOMException::raise(cx, "Supplied keyData is not a JSONWebKey", "DataError"); + return nullptr; + } + // 6.4 If the kty field of jwk is not "oct", then throw a DataError. + // Step 6.4 has already been done in the other implementation of + // CryptoAlgorithmHMAC_Import::importKey which is called before this one. + // 6.5 If jwk does not meet the requirements of Section 6.4 of JSON Web Algorithms [JWA], then throw a DataError. + + // 6.6 Let data be the octet string obtained by decoding the k field of jwk. + auto dataResult = builtins::web::base64::forgivingBase64Decode( + jwk->k.value(), builtins::web::base64::base64URLDecodeTable); + if (dataResult.isErr()) { + DOMException::raise(cx, + "The JWK member 'k' could not be base64url decoded or contained padding", "DataError"); + return nullptr; + } + auto data_string = dataResult.unwrap(); + auto ddd = std::vector(data_string.begin(), data_string.end()); + data = std::make_unique>( + ddd + ); + // 6.7 Set the hash to equal the hash member of normalizedAlgorithm. + CryptoAlgorithmIdentifier hash = this->hashIdentifier; + switch(hash) { + // 6.8 If the name attribute of hash is "SHA-1": + case CryptoAlgorithmIdentifier::SHA_1: { + // If the alg field of jwk is present and is not "HS1", then throw a DataError. + if (jwk->alg.has_value() && jwk->alg.value() != "HS1") { + DOMException::raise(cx, "Operation not permitted", "DataError"); + return nullptr; + } + break; + } + // 6.9 If the name attribute of hash is "SHA-256": + case CryptoAlgorithmIdentifier::SHA_256: { + // If the alg field of jwk is present and is not "HS256", then throw a DataError. + if (jwk->alg.has_value() && jwk->alg.value() != "HS256") { + DOMException::raise(cx, "Operation not permitted", "DataError"); + return nullptr; + } + break; + } + // 6.10 If the name attribute of hash is "SHA-384": + case CryptoAlgorithmIdentifier::SHA_384: { + // If the alg field of jwk is present and is not "HS384", then throw a DataError. + if (jwk->alg.has_value() && jwk->alg.value() != "HS384") { + DOMException::raise(cx, "Operation not permitted", "DataError"); + return nullptr; + } + break; + } + // 6.11 If the name attribute of hash is "SHA-512": + case CryptoAlgorithmIdentifier::SHA_512: { + // If the alg field of jwk is present and is not "HS512", then throw a DataError. + if (jwk->alg.has_value() && jwk->alg.value() != "HS512") { + DOMException::raise(cx, "Operation not permitted", "DataError"); + return nullptr; + } + break; + } + // 6.12 Otherwise, if the name attribute of hash is defined in another applicable specification: + default: { + // 6.13 Perform any key import steps defined by other applicable specifications, passing format, jwk and hash and obtaining hash. + DOMException::raise(cx, "Operation not permitted", "DataError"); + return nullptr; + } + } + // 6.14 If usages is non-empty and the use field of jwk is present and is not "sign", then throw a DataError. + if (!usages.isEmpty() && jwk->use.has_value() && jwk->use != "sign") { + DOMException::raise(cx, "Operation not permitted", "DataError"); + return nullptr; + } + // 6.15 If the key_ops field of jwk is present, and is invalid according to the requirements of JSON Web Key [JWK] or does not contain all of the specified usages values, then throw a DataError. + if (jwk->key_ops.size() > 0) { + auto ops = CryptoKeyUsages::from(jwk->key_ops); + if (!ops.isSuperSetOf(usages)) { + DOMException::raise(cx, + "The JWK 'key_ops' member was inconsistent with that specified by the " + "Web Crypto call. The JWK usage must be a superset of those requested", "DataError"); + return nullptr; + } + } + // 6.16 If the ext field of jwk is present and has the value false and extractable is true, then throw a DataError. + if (jwk->ext && !jwk->ext.value() && extractable) { + DOMException::raise(cx, "Data provided to an operation does not meet requirements", "DataError"); + return nullptr; + } + break; + } + // 7. Otherwise: throw a NotSupportedError. + default: { + DOMException::raise(cx, "Supplied format is not supported", "NotSupportedError"); + return nullptr; + } + } + + + // 8. Let length be equivalent to the length, in octets, of data, multiplied by 8. + auto length = data->size() * 8; + // 9. If length is zero then throw a DataError. + if (length == 0) { + DOMException::raise(cx, "Supplied keyData can not have a length of 0.", "DataError"); + return nullptr; + } + + // 10. If the length member of normalizedAlgorithm is present: + if (this->length.has_value()) { + // 11. If the length member of normalizedAlgorithm is greater than length: + if (this->length.value() > length) { + // throw a DataError. + DOMException::raise(cx, "The optional HMAC key length must be shorter than the key data, and by no more than 7 bits.", "DataError"); + return nullptr; + // 12. If the length member of normalizedAlgorithm, is less than or equal to length minus eight: + } else if (this->length.value() <= (length - 8)) { + // throw a DataError. + DOMException::raise(cx, "The optional HMAC key length must be shorter than the key data, and by no more than 7 bits.", "DataError"); + return nullptr; + // 13. Otherwise: + } else { + // 14. Set length equal to the length member of normalizedAlgorithm. + length = this->length.value(); + } + } else { + // This is because we need to capture the the length in the algorithm object which is created in CryptoAlgorithmHMAC_Import::toObject + this->length = length; + } + // 15. Let key be a new {{CryptoKey}} object representing an HMAC key with the first length bits of data. + // 16. Let algorithm be a new HmacKeyAlgorithm. + // 17. Set the name attribute of algorithm to "HMAC". + // 18. Set the length attribute of algorithm to length. + // 19. Set the hash attribute of algorithm to hash. + // 20. Set the [[algorithm]] internal slot of key to algorithm. + // 21. Return key. + return CryptoKey::createHMAC(cx, this, std::move(data), length, extractable, usages); +} + +JSObject *CryptoAlgorithmHMAC_Import::importKey(JSContext *cx, CryptoKeyFormat format, JS::HandleValue key_data, bool extractable, + CryptoKeyUsages usages) { + MOZ_ASSERT(cx); + // The only supported formats for HMAC are raw, and jwk. + if (format != CryptoKeyFormat::Raw && format != CryptoKeyFormat::Jwk) { + MOZ_ASSERT_UNREACHABLE("coding error"); + return nullptr; + } + + KeyData data; + if (format == CryptoKeyFormat::Jwk) { + // This handles step 6.4: If the kty field of jwk is not a case-sensitive string match to "oct", then throw a DataError. + auto jwk = JsonWebKey::parse(cx, key_data, "oct"); + if (!jwk) { + return nullptr; + } + data = jwk.release(); + } else { + std::optional> buffer = value_to_buffer(cx, key_data, ""); + if (!buffer.has_value()) { + // value_to_buffer would have already created a JS exception so we don't need to create one + // ourselves. + return nullptr; + } + data = buffer.value(); + } + return this->importKey(cx, format, data, extractable, usages); +} +JSObject *CryptoAlgorithmHMAC_Import::toObject(JSContext *cx) { + // Let algorithm be a new RsaHashedKeyAlgorithm dictionary. + JS::RootedObject algorithm(cx, JS_NewPlainObject(cx)); + + // Set the name attribute of algorithm to "HMAC" + auto alg_name = JS_NewStringCopyZ(cx, this->name()); + if (!alg_name) { + return nullptr; + } + JS::RootedValue name_val(cx, JS::StringValue(alg_name)); + if (!JS_SetProperty(cx, algorithm, "name", name_val)) { + return nullptr; + } + + // Set the hash attribute of algorithm to the hash member of normalizedAlgorithm. + JS::RootedObject hash(cx, JS_NewObject(cx, nullptr)); + + auto hash_name = JS_NewStringCopyZ(cx, algorithmName(this->hashIdentifier)); + if (!hash_name) { + return nullptr; + } + JS::RootedValue hash_name_val(cx, JS::StringValue(hash_name)); + if (!JS_SetProperty(cx, hash, "name", hash_name_val)) { + return nullptr; + } + JS::RootedValue hash_val(cx, JS::ObjectValue(*hash)); + if (!JS_SetProperty(cx, algorithm, "hash", hash_val)) { + return nullptr; + } + + // Set the length attribute of algorithm to the length member of normalizedAlgorithm. + MOZ_ASSERT(this->length.has_value()); + JS::RootedValue length_val(cx, JS::NumberValue(this->length.value())); + if (!JS_SetProperty(cx, algorithm, "length", length_val)) { + return nullptr; + } + return algorithm; +} + +JSObject *CryptoAlgorithmECDSA_Import::importKey(JSContext *cx, CryptoKeyFormat format, + KeyData keyData, bool extractable, + CryptoKeyUsages usages) { + // 1. Let keyData be the key data to be imported. + MOZ_ASSERT(cx); + JS::RootedObject result(cx); + + std::unique_ptr key = nullptr; + switch (format) { + // 2. If format is "jwk": + case CryptoKeyFormat::Jwk: { + // 2.1. If keyData is a JsonWebKey dictionary: + // Let jwk equal keyData. + // Otherwise: + // Throw a DataError. + auto jwk = std::get(keyData); + if (!jwk) { + DOMException::raise(cx, "Supplied keyData is not a JSONWebKey", "DataError"); + return nullptr; + } + // 2.2. If the "d" field is present and usages contains a value which is not "sign", + // or, if the "d" field is not present and usages contains a value which is not + // "verify" then throw a SyntaxError. + if ( + (jwk->d.has_value() && !usages.isEmpty() && !usages.canOnlySign()) || + (!jwk->d.has_value() && !usages.isEmpty() && !usages.canOnlyVerify()) + ) { + DOMException::raise(cx, "EC keys only support 'sign' and 'verify' operations", "SyntaxError"); + return nullptr; + } + + // 2.3. If the "kty" field of jwk is not "EC", then throw a DataError. + // NOTE: This has already been done in the parent function. + + // 2.4. If usages is non-empty and the "use" field of jwk is present and is not "sig", then throw a DataError. + + if (!usages.isEmpty() && jwk->use.has_value() && jwk->use.value() != "sig") { + DOMException::raise(cx, "Operation not permitted", "DataError"); + return nullptr; + } + // 2.5. If the "key_ops" field of jwk is present, and is invalid according to the requirements of JSON Web Key, or it does not contain all of the specified usages values, then throw a DataError. + if (jwk->key_ops.size() > 0) { + auto ops = CryptoKeyUsages::from(jwk->key_ops); + if (!ops.isSuperSetOf(usages)) { + DOMException::raise(cx, + "The JWK 'key_ops' member was inconsistent with that specified by the " + "Web Crypto call. The JWK usage must be a superset of those requested", "DataError"); + return nullptr; + } + } + // 2.6. If the "ext" field of jwk is present and has the value false and extractable is true, then throw a DataError. + if (jwk->ext && !jwk->ext.value() && extractable) { + DOMException::raise(cx, "Data provided to an operation does not meet requirements", "DataError"); + return nullptr; + } + // 2.7. Let namedCurve be a string whose value is equal to the "crv" field of jwk. + auto namedCurve = toNamedCurve(jwk->crv.value()); + // 2.8. If namedCurve is not equal to the namedCurve member of normalizedAlgorithm, throw a DataError. + if (!namedCurve.has_value() || namedCurve.value() != this->namedCurve) { + DOMException::raise(cx, "The JWK's \"crv\" member specifies a different curve than requested", "DataError"); + return nullptr; + } + // 2.9. If namedCurve is equal to "P-256", "P-384" or "P-521": + // NOTE: At this point in time, namedCurve can _only_ be a valid value. + // 2.9.1. Let algNamedCurve be a string whose initial value is undefined. + // 2.9.2. If the "alg" field is not present: + // Let algNamedCurve be undefined. + // If the "alg" field is equal to the string "ES256": + // Let algNamedCurve be the string "P-256". + // If the "alg" field is equal to the string "ES384": + // Let algNamedCurve be the string "P-384". + // If the "alg" field is equal to the string "ES512": + // Let algNamedCurve be the string "P-521". + // otherwise: + // throw a DataError. + // 2.9.3. If algNamedCurve is defined, and is not equal to namedCurve, throw a DataError. + if (jwk->alg.has_value()) { + auto algNamedCurve = toNamedCurve(jwk->crv.value()); + if (!algNamedCurve.has_value() || algNamedCurve.value() != this->namedCurve) { + DOMException::raise(cx, "Oopsie", "DataError"); + return nullptr; + } + } + // 2.9.4. If the "d" field is present: + if (jwk->d.has_value()) { + // 2.9.4.1. If jwk does not meet the requirements of Section 6.2.2 of JSON Web Algorithms, then throw a DataError. + // https://datatracker.ietf.org/doc/html/rfc7518#section-6.2.2.1 + // The "d" (ECC private key) parameter contains the Elliptic Curve + // private key value. It is represented as the base64url encoding of + // the octet string representation of the private key value, as defined + // in Section 2.3.7 of SEC1 [SEC1]. The length of this octet string + // MUST be ceiling(log-base-2(n)/8) octets (where n is the order of the + // curve). + auto dResult = builtins::web::base64::forgivingBase64Decode( + jwk->d.value(), builtins::web::base64::base64URLDecodeTable); + if (dResult.isErr()) { + DOMException::raise( + cx, "The JWK member 'd' could not be base64url decoded or contained padding", "DataError"); + return nullptr; + } + auto d = dResult.unwrap(); + switch (this->namedCurve) { + case NamedCurve::P256: { + if (d.length() != 32) { + auto message = fmt::format("The JWK's \"d\" member defines an octet string of length {} bytes but should be 32", d.length()); + DOMException::raise(cx, message, "SyntaxError"); + return nullptr; + } + break; + } + case NamedCurve::P384: { + if (d.length() != 48) { + auto message = fmt::format("The JWK's \"d\" member defines an octet string of length {} bytes but should be 48", d.length()); + DOMException::raise(cx, message, "SyntaxError"); + return nullptr; + } + break; + } + case NamedCurve::P521: { + if (d.length() != 66) { + auto message = fmt::format("The JWK's \"d\" member defines an octet string of length {} bytes but should be 66", d.length()); + DOMException::raise(cx, message, "SyntaxError"); + return nullptr; + } + break; + } + } + // 2.9.4.2. Let key be a new CryptoKey object that represents the Elliptic Curve private key identified by interpreting jwk according to Section 6.2.2 of JSON Web Algorithms. + // 2.9.4.3. Set the [[type]] internal slot of Key to "private". + key = createECPrivateKeyFromJWK(cx, jwk); + if (!key) { + return nullptr; + } + // Otherwise: + } else { + // 2.9.4.1. If jwk does not meet the requirements of Section 6.2.1 of JSON Web Algorithms, then throw a DataError. + // 2.9.4.2. Let key be a new CryptoKey object that represents the Elliptic Curve public key identified by interpreting jwk according to Section 6.2.1 of JSON Web Algorithms. + // 2.9.4.3. Set the [[type]] internal slot of Key to "public". + key = createECPublicKeyFromJWK(cx, jwk); + if (!key) { + return nullptr; + } + } + // Otherwise: + // 2.9.1. Perform any key import steps defined by other applicable specifications, passing format, jwk and obtaining key. + // 2.9.2. If an error occured or there are no applicable specifications, throw a DataError. + // NOTE: We do not implement the above 2 steps as we only support "P-256", "P-384", and "P-521". + + // 2.10. If the key value is not a valid point on the Elliptic Curve identified by the namedCurve member of normalizedAlgorithm throw a DataError. + // 2.11. Let algorithm be a new instance of an EcKeyAlgorithm object. + // 2.12. Set the name attribute of algorithm to "ECDSA". + // 2.13. Set the namedCurve attribute of algorithm to namedCurve. + // 2.14. Set the [[algorithm]] internal slot of key to algorithm. + return CryptoKey::createECDSA(cx, this, std::move(key), extractable, usages); + break; + } + case CryptoKeyFormat::Pkcs8: + case CryptoKeyFormat::Raw: + case CryptoKeyFormat::Spki: { + MOZ_ASSERT(false); + // TODO finish this + } + } + return nullptr; +} + +JSObject *CryptoAlgorithmECDSA_Import::importKey(JSContext *cx, CryptoKeyFormat format, JS::HandleValue key_data, bool extractable, + CryptoKeyUsages usages) { + MOZ_ASSERT(cx); + + KeyData data; + switch (format) { + case CryptoKeyFormat::Jwk: { + // This handles step 2.3: If the "kty" field of jwk is not "EC", then throw a DataError. + auto jwk = JsonWebKey::parse(cx, key_data, "EC"); + if (!jwk) { + return nullptr; + } + data = jwk.release(); + break; + } + case CryptoKeyFormat::Pkcs8: + case CryptoKeyFormat::Raw: + case CryptoKeyFormat::Spki: { + // TODO finish this + DOMException::raise(cx, "Supplied format is not yet supported", "NotSupportedError"); + return nullptr; + } + } + return this->importKey(cx, format, data, extractable, usages); + +} +JSObject *CryptoAlgorithmECDSA_Import::toObject(JSContext *cx) { + // Let algorithm be a new RsaHashedKeyAlgorithm dictionary. + JS::RootedObject algorithm(cx, JS_NewPlainObject(cx)); + + // Set the name attribute of algorithm to "RSASSA-PKCS1-v1_5" + auto alg_name = JS_NewStringCopyZ(cx, this->name()); + if (!alg_name) { + return nullptr; + } + JS::RootedValue name_val(cx, JS::StringValue(alg_name)); + if (!JS_SetProperty(cx, algorithm, "name", name_val)) { + return nullptr; + } + + // Set the hash attribute of algorithm to the hash member of normalizedAlgorithm. + JS::RootedObject hash(cx, JS_NewObject(cx, nullptr)); + + auto curve_name = JS_NewStringCopyZ(cx, curveName(this->namedCurve)); + if (!curve_name) { + return nullptr; + } + JS::RootedValue curve_name_val(cx, JS::StringValue(curve_name)); + if (!JS_SetProperty(cx, algorithm, "namedCurve", curve_name_val)) { + return nullptr; + } + return algorithm; +} + +std::unique_ptr CryptoAlgorithmECDSA_Sign_Verify::fromParameters(JSContext *cx, JS::HandleObject parameters) { + JS::Rooted hash_val(cx); + if (!JS_GetProperty(cx, parameters, "hash", &hash_val)) { + return nullptr; + } + auto hashIdentifier = toHashIdentifier(cx, hash_val); + if (hashIdentifier.isErr()) { + return nullptr; + } + return std::make_unique(hashIdentifier.unwrap()); +} + +std::unique_ptr CryptoAlgorithmECDSA_Import::fromParameters(JSContext *cx, JS::HandleObject parameters) { + JS::Rooted namedCurve_val(cx); + if (!JS_GetProperty(cx, parameters, "namedCurve", &namedCurve_val)) { + return nullptr; + } + + // P-256 + // P-384 + // P-512 + auto namedCurve = toNamedCurve(cx, namedCurve_val); + if (namedCurve.isErr()) { + return nullptr; + } + return std::make_unique(namedCurve.unwrap()); +} + +std::unique_ptr CryptoAlgorithmRSASSA_PKCS1_v1_5_Import::fromParameters(JSContext *cx, JS::HandleObject parameters) { + JS::Rooted hash_val(cx); + if (!JS_GetProperty(cx, parameters, "hash", &hash_val)) { + return nullptr; + } + auto hashIdentifier = toHashIdentifier(cx, hash_val); + if (hashIdentifier.isErr()) { + return nullptr; + } + return std::make_unique(hashIdentifier.unwrap()); +} + +// https://w3c.github.io/webcrypto/#rsassa-pkcs1-operations +JSObject *CryptoAlgorithmRSASSA_PKCS1_v1_5_Import::importKey(JSContext *cx, CryptoKeyFormat format, + KeyData keyData, bool extractable, + CryptoKeyUsages usages) { + MOZ_ASSERT(cx); + JS::RootedObject result(cx); + switch (format) { + // 2. If format is "jwk": + case CryptoKeyFormat::Jwk: { + // 2.1. If keyData is a JsonWebKey dictionary: + // Let jwk equal keyData. + // Otherwise: + // Throw a DataError. + auto jwk = std::get(keyData); + if (!jwk) { + DOMException::raise(cx, "Supplied keyData is not a JSONWebKey", "DataError"); + return nullptr; + } + + + // 2.2 If the d field of jwk is present and usages contains an entry which + // is not "sign", or, if the d field of jwk is not present and usages + // contains an entry which is not "verify" then throw a SyntaxError. + bool isUsagesAllowed = false; + // public key + if (jwk->d.has_value()) { + isUsagesAllowed = usages.canOnlySign(); + } else { + // private key + isUsagesAllowed = usages.canOnlyVerify(); + } + if (!isUsagesAllowed) { + DOMException::raise(cx, + "The JWK 'key_ops' member was inconsistent with that specified by the " + "Web Crypto call. The JWK usage must be a superset of those requested", "DataError"); + return nullptr; + } + + // 2.3 If the kty field of jwk is not a case-sensitive string match to "RSA", then throw a DataError. + + // Step 2.3 has already been done in the other implementation of + // CryptoAlgorithmRSASSA_PKCS1_v1_5_Import::importKey which is called before this one. + + // 2.4. If usages is non-empty and the use field of jwk is present and is + // not a case-sensitive string match to "sig", then throw a DataError. + if (!usages.isEmpty() && jwk->use.has_value() && jwk->use.value() != "sig") { + DOMException::raise(cx, "Operation not permitted", "DataError"); + return nullptr; + } + + // 2.5. If the key_ops field of jwk is present, and is invalid according to + // the requirements of JSON Web Key [JWK] or does not contain all of the + // specified usages values, then throw a DataError. + if (jwk->key_ops.size() > 0) { + auto ops = CryptoKeyUsages::from(jwk->key_ops); + if (!ops.isSuperSetOf(usages)) { + DOMException::raise(cx, + "The JWK 'key_ops' member was inconsistent with that specified by the " + "Web Crypto call. The JWK usage must be a superset of those requested", "DataError"); + return nullptr; + } + } + + // 2.6 If the ext field of jwk is present and has the value false and + // extractable is true, then throw a DataError. + if (jwk->ext && !jwk->ext.value() && extractable) { + DOMException::raise(cx, "Data provided to an operation does not meet requirements", "DataError"); + return nullptr; + } + + // 2.7 Let hash be a be a string whose initial value is undefined. + // 2.8 If the alg field of jwk is not present: + // Let hash be undefined. + // If the alg field is equal to the string "RS1": + // Let hash be the string "SHA-1". + // If the alg field is equal to the string "RS256": + // Let hash be the string "SHA-256". + // If the alg field is equal to the string "RS384": + // Let hash be the string "SHA-384". + // If the alg field is equal to the string "RS512": + // Let hash be the string "SHA-512". + // Otherwise: + // Perform any key import steps defined by other applicable specifications, passing format, jwk and obtaining hash. + // If an error occurred or there are no applicable specifications, throw a DataError. + // 2.9 If hash is not undefined: + // Let normalizedHash be the result of normalize an algorithm with alg set to hash and op set to digest. + // If normalizedHash is not equal to the hash member of normalizedAlgorithm, throw a DataError. + bool isMatched = false; + switch (this->hashIdentifier) { + case CryptoAlgorithmIdentifier::SHA_1: { + isMatched = !jwk->alg.has_value() || jwk->alg == "RS1"; + break; + } + case CryptoAlgorithmIdentifier::SHA_256: { + isMatched = !jwk->alg.has_value() || jwk->alg == "RS256"; + break; + } + case CryptoAlgorithmIdentifier::SHA_384: { + isMatched = !jwk->alg.has_value() || jwk->alg == "RS384"; + break; + } + case CryptoAlgorithmIdentifier::SHA_512: { + isMatched = !jwk->alg.has_value() || jwk->alg == "RS512"; + break; + } + default: { + break; + } + } + if (!isMatched) { + DOMException::raise(cx, + "The JWK 'alg' member was inconsistent with that specified by the Web Crypto call", + "DataError"); + return nullptr; + } + + std::unique_ptr key = nullptr; + // 2.10 If the d field of jwk is present: + if (jwk->d.has_value()) { + // 2.10.1 If jwk does not meet the requirements of Section 6.3.2 of JSON Web Algorithms [JWA], then throw a DataError. + // 2.10.2 Let privateKey represents the RSA private key identified by interpreting jwk according to Section 6.3.2 of JSON Web Algorithms [JWA]. + // 2.10.3 If privateKey is not a valid RSA private key according to [RFC3447], then throw a DataError. + key = createRSAPrivateKeyFromJWK(cx, jwk); + if (!key) { + return nullptr; + } + // 2.10 Otherwise: + } else { + // 2.10.1 If jwk does not meet the requirements of Section 6.3.1 of JSON Web Algorithms [JWA], then throw a DataError. + // 2.10.2 Let publicKey represent the RSA public key identified by interpreting jwk according to Section 6.3.1 of JSON Web Algorithms [JWA]. + // 2.10.3 If publicKey can be determined to not be a valid RSA public key according to [RFC3447], then throw a DataError. + key = createRSAPublicKeyFromJWK(cx, jwk); + if (!key) { + return nullptr; + } + } + // 2.10.4 Let key be a new CryptoKey object that represents privateKey. + // 2.10.5 Set the [[type]] internal slot of key to "private" + // 3. Let algorithm be a new RsaHashedKeyAlgorithm dictionary. + // 4. Set the name attribute of algorithm to "RSASSA-PKCS1-v1_5" + // 5. Set the modulusLength attribute of algorithm to the length, in bits, of the RSA public modulus. + // 6. Set the publicExponent attribute of algorithm to the BigInteger representation of the RSA public exponent. + // 7. Set the hash attribute of algorithm to the hash member of normalizedAlgorithm. + // 8. Set the [[algorithm]] internal slot of key to algorithm. + // 9. Return key. + return CryptoKey::createRSA(cx, this, std::move(key), extractable, usages); + } + // 2. If format is "spki": + case CryptoKeyFormat::Spki: + // 2.1. If usages contains an entry which is not "verify", then throw a SyntaxError. + if (!usages.canOnlyVerify()) { + DOMException::raise(cx, "spki format usage can only be 'verify'", "SyntaxError"); + } + + std::vector spki_data; + // Uh, this doesn't seem right. + auto _spki_data = std::get>(keyData); + spki_data.assign(_spki_data.begin(), _spki_data.end()); + + fastly::sys::asn::SubjectPublicKeyInfo *spki_raw; + std::string spki_parse_err; + if (!fastly::sys::asn::decode_spki(spki_data, spki_raw, spki_parse_err)) { + DOMException::raise(cx, spki_parse_err, "DataError"); + } + + auto spki(::rust::Box::from_raw(spki_raw)); + + if (!spki->is_rsa()) { + DOMException::raise(cx, "algorithm not RSA", "DataError"); + } + + fastly::sys::asn::RSAPublicKey *public_key_raw; + std::string public_key_parse_err; + if (!spki->decode_public_key(public_key_raw, public_key_parse_err)) { + DOMException::raise(cx, public_key_parse_err, "DataError"); + } + + auto public_key(::rust::Box::from_raw(public_key_raw)); + + std::string modulus; + std::string exponent; + public_key->details(modulus, exponent); + auto publicKeyComponents = CryptoKeyRSAComponents::createPublic(modulus, exponent); + if (!publicKeyComponents) { + return nullptr; + } + return CryptoKey::createRSA(cx, this, std::move(publicKeyComponents), extractable, usages); + + case CryptoKeyFormat::Pkcs8: + // 2.1. If usages contains an entry which is not "sign" then throw a SyntaxError. + // if (!usages.canOnlySign()) { + // DOMException::raise(cx, "pkcs8 format usage can only be 'sign'", "SyntaxError"); + // } + // auto privateKeyComponents = CryptoKeyRSAComponents::createPrivate(modulus, exponent, privateExponent); + // if (!privateKeyComponents) { + // return nullptr; + // } + // return CryptoKey::createRSA(cx, this, std::move(privateKeyComponents), extractable, usages); + [[fallthrough]]; + default: { + DOMException::raise(cx, "Supplied format is not supported", "DataError"); + return nullptr; + } + } +} + +JSObject *CryptoAlgorithmRSASSA_PKCS1_v1_5_Import::importKey(JSContext *cx, + CryptoKeyFormat format, + JS::HandleValue key_data, + bool extractable, + CryptoKeyUsages usages) { + MOZ_ASSERT(cx); + // The only support formats for RSASSA PKCS1 v1-5 are spki, pkcs8, and jwk. + if (format == CryptoKeyFormat::Raw) { + MOZ_ASSERT_UNREACHABLE("coding error"); + return nullptr; + } + + KeyData data; + if (format == CryptoKeyFormat::Jwk) { + // This handles set 2.3: If the kty field of jwk is not a case-sensitive string match to "RSA", then throw a DataError. + auto jwk = JsonWebKey::parse(cx, key_data, "RSA"); + if (!jwk) { + return nullptr; + } + data = jwk.release(); + } else { + std::optional> buffer = value_to_buffer(cx, key_data, ""); + if (!buffer.has_value()) { + // value_to_buffer would have already created a JS exception so we don't need to create one + // ourselves. + return nullptr; + } + data = buffer.value(); + } + return this->importKey(cx, format, data, extractable, usages); +} + +JSObject *CryptoAlgorithmRSASSA_PKCS1_v1_5_Import::toObject(JSContext *cx) { + // Let algorithm be a new RsaHashedKeyAlgorithm dictionary. + JS::RootedObject algorithm(cx, JS_NewPlainObject(cx)); + + // Set the name attribute of algorithm to "RSASSA-PKCS1-v1_5" + auto alg_name = JS_NewStringCopyZ(cx, this->name()); + if (!alg_name) { + return nullptr; + } + JS::RootedValue name_val(cx, JS::StringValue(alg_name)); + if (!JS_SetProperty(cx, algorithm, "name", name_val)) { + return nullptr; + } + + // Set the hash attribute of algorithm to the hash member of normalizedAlgorithm. + JS::RootedObject hash(cx, JS_NewObject(cx, nullptr)); + + auto hash_name = JS_NewStringCopyZ(cx, algorithmName(this->hashIdentifier)); + if (!hash_name) { + return nullptr; + } + JS::RootedValue hash_name_val(cx, JS::StringValue(hash_name)); + if (!JS_SetProperty(cx, hash, "name", hash_name_val)) { + return nullptr; + } + JS::RootedValue hash_val(cx, JS::ObjectValue(*hash)); + if (!JS_SetProperty(cx, algorithm, "hash", hash_val)) { + return nullptr; + } + return algorithm; +} + +JSObject *CryptoAlgorithmMD5::digest(JSContext *cx, std::span data) { + return fastly::crypto::digest(cx, data, EVP_md5(), MD5_DIGEST_LENGTH); +} +JSObject *CryptoAlgorithmSHA1::digest(JSContext *cx, std::span data) { + return fastly::crypto::digest(cx, data, EVP_sha1(), SHA_DIGEST_LENGTH); +} +JSObject *CryptoAlgorithmSHA256::digest(JSContext *cx, std::span data) { + return fastly::crypto::digest(cx, data, EVP_sha256(), SHA256_DIGEST_LENGTH); +} +JSObject *CryptoAlgorithmSHA384::digest(JSContext *cx, std::span data) { + return fastly::crypto::digest(cx, data, EVP_sha384(), SHA384_DIGEST_LENGTH); +} +JSObject *CryptoAlgorithmSHA512::digest(JSContext *cx, std::span data) { + return fastly::crypto::digest(cx, data, EVP_sha512(), SHA512_DIGEST_LENGTH); +} + +} // namespace builtins::web::crypto diff --git a/runtime/fastly/builtins/crypto/crypto-algorithm.h b/runtime/fastly/builtins/crypto/crypto-algorithm.h new file mode 100644 index 0000000000..03f5cd9c33 --- /dev/null +++ b/runtime/fastly/builtins/crypto/crypto-algorithm.h @@ -0,0 +1,241 @@ +#ifndef BUILTINS_WEB_FASTLY_CRYPTO_CRYPTO_ALGORITHM_H +#define BUILTINS_WEB_FASTLY_CRYPTO_CRYPTO_ALGORITHM_H +#include + +#include "builtin.h" +#include "crypto-key.h" +#include "json-web-key.h" +#include "openssl/evp.h" +#include + +namespace fastly::crypto { + +// We are defining all the algorithms from +// https://w3c.github.io/webcrypto/#issue-container-generatedID-15 +enum class CryptoAlgorithmIdentifier : uint8_t { + RSASSA_PKCS1_v1_5, + RSA_PSS, + RSA_OAEP, + ECDSA, + ECDH, + AES_CTR, + AES_CBC, + AES_GCM, + AES_KW, + HMAC, + MD5, + SHA_1, + SHA_256, + SHA_384, + SHA_512, + HKDF, + PBKDF2 +}; + +enum class NamedCurve : uint8_t { + P256, + P384, + P521, +}; + +const char *algorithmName(CryptoAlgorithmIdentifier algorithm); + +/// The base class that all algorithm implementations should derive from. +class CryptoAlgorithm { +public: + virtual ~CryptoAlgorithm() = default; + + virtual const char *name() const noexcept = 0; + virtual CryptoAlgorithmIdentifier identifier() = 0; +}; + +using KeyData = std::variant, JsonWebKey *>; + +class CryptoAlgorithmImportKey : public CryptoAlgorithm { +public: + virtual JSObject *importKey(JSContext *cx, CryptoKeyFormat format, JS::HandleValue key_data, + bool extractable, CryptoKeyUsages usages) = 0; + virtual JSObject *importKey(JSContext *cx, CryptoKeyFormat format, KeyData key_data, + bool extractable, CryptoKeyUsages usages) = 0; + static std::unique_ptr normalize(JSContext *cx, JS::HandleValue value); +}; + +class CryptoAlgorithmSignVerify : public CryptoAlgorithm { +public: + virtual JSObject *sign(JSContext *cx, JS::HandleObject key, std::span data) = 0; + virtual JS::Result verify(JSContext *cx, JS::HandleObject key, std::span signature, + std::span data) = 0; + static std::unique_ptr normalize(JSContext *cx, JS::HandleValue value); +}; + +class CryptoAlgorithmHMAC_Sign_Verify final : public CryptoAlgorithmSignVerify { +public: + const char *name() const noexcept override { return "HMAC"; }; + CryptoAlgorithmHMAC_Sign_Verify() {}; + CryptoAlgorithmIdentifier identifier() final { return CryptoAlgorithmIdentifier::HMAC; }; + + JSObject *sign(JSContext *cx, JS::HandleObject key, std::span data) override; + JS::Result verify(JSContext *cx, JS::HandleObject key, std::span signature, + std::span data) override; + static JSObject *exportKey(JSContext *cx, CryptoKeyFormat format, JS::HandleObject key); + JSObject *toObject(JSContext *cx); +}; + +class CryptoAlgorithmECDSA_Sign_Verify final : public CryptoAlgorithmSignVerify { +public: + // The hash member describes the hash algorithm to use. + CryptoAlgorithmIdentifier hashIdentifier; + + const char *name() const noexcept override { return "ECDSA"; }; + CryptoAlgorithmECDSA_Sign_Verify(CryptoAlgorithmIdentifier hashIdentifier) + : hashIdentifier{hashIdentifier} {}; + + // https://www.w3.org/TR/WebCryptoAPI/#EcdsaParams-dictionary + // 23.3. EcdsaParams dictionary + static std::unique_ptr + fromParameters(JSContext *cx, JS::HandleObject parameters); + CryptoAlgorithmIdentifier identifier() final { return CryptoAlgorithmIdentifier::ECDSA; }; + + JSObject *sign(JSContext *cx, JS::HandleObject key, std::span data) override; + JS::Result verify(JSContext *cx, JS::HandleObject key, std::span signature, + std::span data) override; + JSObject *toObject(JSContext *cx); +}; + +class CryptoAlgorithmECDSA_Import final : public CryptoAlgorithmImportKey { +public: + // A named curve. + NamedCurve namedCurve; + + const char *name() const noexcept override { return "ECDSA"; }; + CryptoAlgorithmECDSA_Import(NamedCurve namedCurve) : namedCurve{namedCurve} {}; + + // https://www.w3.org/TR/WebCryptoAPI/#EcKeyImportParams-dictionary + // 23.6 EcKeyImportParams dictionary + static std::unique_ptr fromParameters(JSContext *cx, + JS::HandleObject parameters); + + CryptoAlgorithmIdentifier identifier() final { return CryptoAlgorithmIdentifier::ECDSA; }; + + JSObject *importKey(JSContext *cx, CryptoKeyFormat format, JS::HandleValue, bool extractable, + CryptoKeyUsages usages) override; + JSObject *importKey(JSContext *cx, CryptoKeyFormat format, KeyData, bool extractable, + CryptoKeyUsages usages) override; + JSObject *toObject(JSContext *cx); +}; + +class CryptoAlgorithmRSASSA_PKCS1_v1_5_Sign_Verify final : public CryptoAlgorithmSignVerify { +public: + const char *name() const noexcept override { return "RSASSA-PKCS1-v1_5"; }; + CryptoAlgorithmRSASSA_PKCS1_v1_5_Sign_Verify() {}; + CryptoAlgorithmIdentifier identifier() final { + return CryptoAlgorithmIdentifier::RSASSA_PKCS1_v1_5; + }; + + JSObject *sign(JSContext *cx, JS::HandleObject key, std::span data) override; + JS::Result verify(JSContext *cx, JS::HandleObject key, std::span signature, + std::span data) override; + JSObject *toObject(JSContext *cx); +}; + +class CryptoAlgorithmRSASSA_PKCS1_v1_5_Import final : public CryptoAlgorithmImportKey { +public: + // The hash member describes the hash algorithm to use. + CryptoAlgorithmIdentifier hashIdentifier; + + const char *name() const noexcept override { return "RSASSA-PKCS1-v1_5"; }; + CryptoAlgorithmRSASSA_PKCS1_v1_5_Import(CryptoAlgorithmIdentifier hashIdentifier) + : hashIdentifier{hashIdentifier} {}; + + // https://w3c.github.io/webcrypto/#RsaHashedImportParams-dictionary + // 20.7 RsaHashedImportParams dictionary + static std::unique_ptr + fromParameters(JSContext *cx, JS::HandleObject parameters); + + CryptoAlgorithmIdentifier identifier() final { + return CryptoAlgorithmIdentifier::RSASSA_PKCS1_v1_5; + }; + + JSObject *importKey(JSContext *cx, CryptoKeyFormat format, JS::HandleValue, bool extractable, + CryptoKeyUsages usages) override; + JSObject *importKey(JSContext *cx, CryptoKeyFormat format, KeyData, bool extractable, + CryptoKeyUsages usages) override; + JSObject *toObject(JSContext *cx); +}; + +class CryptoAlgorithmHMAC_Import final : public CryptoAlgorithmImportKey { +public: + // The hash member describes the hash algorithm to use. + // Valid values are SHA_256, SHA_384, SHA_512. + CryptoAlgorithmIdentifier hashIdentifier; + // A Number representing the length in bits of the key. + // If this is omitted the length of the key is equal to the length of the digest generated by the + // hash algorithm defined in hashIdentifier. + std::optional length; + + const char *name() const noexcept override { return "HMAC"; }; + + CryptoAlgorithmHMAC_Import(CryptoAlgorithmIdentifier hashIdentifier) + : hashIdentifier{hashIdentifier} {}; + + CryptoAlgorithmHMAC_Import(CryptoAlgorithmIdentifier hashIdentifier, size_t length) + : hashIdentifier{hashIdentifier}, length{length} {}; + + // https://w3c.github.io/webcrypto/#hmac-importparams + // 29.3 HmacImportParams dictionary + static std::unique_ptr fromParameters(JSContext *cx, + JS::HandleObject parameters); + + CryptoAlgorithmIdentifier identifier() final { return CryptoAlgorithmIdentifier::HMAC; }; + + JSObject *importKey(JSContext *cx, CryptoKeyFormat format, JS::HandleValue, bool extractable, + CryptoKeyUsages usages) override; + JSObject *importKey(JSContext *cx, CryptoKeyFormat format, KeyData, bool extractable, + CryptoKeyUsages usages) override; + JSObject *toObject(JSContext *cx); +}; + +class CryptoAlgorithmDigest : public CryptoAlgorithm { +public: + virtual JSObject *digest(JSContext *cx, std::span) = 0; + static std::unique_ptr normalize(JSContext *cx, JS::HandleValue value); +}; + +class CryptoAlgorithmMD5 final : public CryptoAlgorithmDigest { +public: + const char *name() const noexcept override { return "MD5"; }; + CryptoAlgorithmIdentifier identifier() override { return CryptoAlgorithmIdentifier::MD5; }; + JSObject *digest(JSContext *cx, std::span) override; +}; + +class CryptoAlgorithmSHA1 final : public CryptoAlgorithmDigest { +public: + const char *name() const noexcept override { return "SHA-1"; }; + CryptoAlgorithmIdentifier identifier() override { return CryptoAlgorithmIdentifier::SHA_1; }; + JSObject *digest(JSContext *cx, std::span) override; +}; + +class CryptoAlgorithmSHA256 final : public CryptoAlgorithmDigest { +public: + const char *name() const noexcept override { return "SHA-256"; }; + CryptoAlgorithmIdentifier identifier() override { return CryptoAlgorithmIdentifier::SHA_256; }; + JSObject *digest(JSContext *cx, std::span) override; +}; + +class CryptoAlgorithmSHA384 final : public CryptoAlgorithmDigest { +public: + const char *name() const noexcept override { return "SHA-384"; }; + CryptoAlgorithmIdentifier identifier() override { return CryptoAlgorithmIdentifier::SHA_384; }; + JSObject *digest(JSContext *cx, std::span) override; +}; + +class CryptoAlgorithmSHA512 final : public CryptoAlgorithmDigest { +public: + const char *name() const noexcept override { return "SHA-512"; }; + CryptoAlgorithmIdentifier identifier() override { return CryptoAlgorithmIdentifier::SHA_512; }; + JSObject *digest(JSContext *cx, std::span) override; +}; + +} // namespace fastly::crypto + +#endif diff --git a/runtime/fastly/builtins/crypto/crypto-key-ec-components.cpp b/runtime/fastly/builtins/crypto/crypto-key-ec-components.cpp new file mode 100644 index 0000000000..d3be1b1459 --- /dev/null +++ b/runtime/fastly/builtins/crypto/crypto-key-ec-components.cpp @@ -0,0 +1,18 @@ +#include "crypto-key-ec-components.h" + +CryptoKeyECComponents::CryptoKeyECComponents(std::string_view x, std::string_view y) + : type(Type::Public), x(x), y(y) {} + +std::unique_ptr CryptoKeyECComponents::createPublic(std::string_view x, + std::string_view y) { + return std::make_unique(x, y); +} + +CryptoKeyECComponents::CryptoKeyECComponents(std::string_view x, std::string_view y, + std::string_view d) + : type(Type::Private), x(x), y(y), d(d) {} + +std::unique_ptr +CryptoKeyECComponents::createPrivate(std::string_view x, std::string_view y, std::string_view d) { + return std::make_unique(x, y, d); +} diff --git a/runtime/fastly/builtins/crypto/crypto-key-ec-components.h b/runtime/fastly/builtins/crypto/crypto-key-ec-components.h new file mode 100644 index 0000000000..af659d9120 --- /dev/null +++ b/runtime/fastly/builtins/crypto/crypto-key-ec-components.h @@ -0,0 +1,27 @@ +#ifndef BUILTINS_WEB_FASTLY_CRYPTO_CRYPTO_KEY_EC_COMPONENTS_H +#define BUILTINS_WEB_FASTLY_CRYPTO_CRYPTO_KEY_EC_COMPONENTS_H +#include +#include + +class CryptoKeyECComponents final { +public: + enum class Type : uint8_t { Public, Private }; + const Type type; + + // Private and public keys. + const std::string x; + const std::string y; + + // Only private keys. + const std::string d; + static std::unique_ptr createPublic(std::string_view x, + std::string_view y); + + static std::unique_ptr + createPrivate(std::string_view x, std::string_view y, std::string_view d); + + CryptoKeyECComponents(std::string_view x, std::string_view y); + + CryptoKeyECComponents(std::string_view x, std::string_view y, std::string_view d); +}; +#endif diff --git a/runtime/fastly/builtins/crypto/crypto-key-rsa-components.cpp b/runtime/fastly/builtins/crypto/crypto-key-rsa-components.cpp new file mode 100644 index 0000000000..b669ba640a --- /dev/null +++ b/runtime/fastly/builtins/crypto/crypto-key-rsa-components.cpp @@ -0,0 +1,37 @@ +#include "crypto-key-rsa-components.h" + +CryptoKeyRSAComponents::CryptoKeyRSAComponents(std::string_view modulus, std::string_view exponent) + : type(Type::Public), modulus(modulus), exponent(exponent) {} + +std::unique_ptr +CryptoKeyRSAComponents::createPublic(std::string_view modulus, std::string_view exponent) { + return std::make_unique(modulus, exponent); +} + +CryptoKeyRSAComponents::CryptoKeyRSAComponents(std::string_view modulus, std::string_view exponent, + std::string_view privateExponent) + : type(Type::Private), modulus(modulus), exponent(exponent), privateExponent(privateExponent), + hasAdditionalPrivateKeyParameters(false) {} + +std::unique_ptr +CryptoKeyRSAComponents::createPrivate(std::string_view modulus, std::string_view exponent, + std::string_view privateExponent) { + return std::make_unique(modulus, exponent, privateExponent); +} + +CryptoKeyRSAComponents::CryptoKeyRSAComponents(std::string_view modulus, std::string_view exponent, + std::string_view privateExponent, + std::optional firstPrimeInfo, + std::optional secondPrimeInfo, + std::vector otherPrimeInfos) + : type(Type::Private), modulus(modulus), exponent(exponent), privateExponent(privateExponent), + hasAdditionalPrivateKeyParameters(true), firstPrimeInfo(firstPrimeInfo), + secondPrimeInfo(secondPrimeInfo), otherPrimeInfos(otherPrimeInfos) {} + +std::unique_ptr CryptoKeyRSAComponents::createPrivateWithAdditionalData( + std::string_view modulus, std::string_view exponent, std::string_view privateExponent, + std::optional firstPrimeInfo, std::optional secondPrimeInfo, + std::vector otherPrimeInfos) { + return std::make_unique(modulus, exponent, privateExponent, + firstPrimeInfo, secondPrimeInfo, otherPrimeInfos); +} diff --git a/runtime/fastly/builtins/crypto/crypto-key-rsa-components.h b/runtime/fastly/builtins/crypto/crypto-key-rsa-components.h new file mode 100644 index 0000000000..f0beea0192 --- /dev/null +++ b/runtime/fastly/builtins/crypto/crypto-key-rsa-components.h @@ -0,0 +1,59 @@ +#ifndef BUILTINS_WEB_FASTLY_CRYPTO_CRYPTO_KEY_RSA_COMPONENTS_H +#define BUILTINS_WEB_FASTLY_CRYPTO_CRYPTO_KEY_RSA_COMPONENTS_H +#include +#include +#include + +class CryptoKeyRSAComponents final { +public: + class PrimeInfo { + public: + std::string primeFactor; + std::string factorCRTExponent; + std::string factorCRTCoefficient; + PrimeInfo(std::string_view primeFactor, std::string_view factorCRTExponent, + std::string_view factorCRTCoefficient) + : primeFactor{primeFactor}, factorCRTExponent{factorCRTExponent}, + factorCRTCoefficient{factorCRTCoefficient} {}; + PrimeInfo(std::string_view primeFactor, std::string_view factorCRTExponent) + : primeFactor{primeFactor}, factorCRTExponent{factorCRTExponent} {}; + PrimeInfo(std::string_view primeFactor) : primeFactor{primeFactor} {}; + }; + enum class Type : uint8_t { Public, Private }; + const Type type; + + // Private and public keys. + const std::string modulus; + const std::string exponent; + + // Only private keys. + const std::string privateExponent; + const bool hasAdditionalPrivateKeyParameters = false; + const std::optional firstPrimeInfo; + const std::optional secondPrimeInfo; + // When three or more primes have been used, the number of array elements + // is be the number of primes used minus two. + const std::vector otherPrimeInfos; + static std::unique_ptr createPublic(std::string_view modulus, + std::string_view exponent); + + static std::unique_ptr createPrivate(std::string_view modulus, + std::string_view exponent, + std::string_view privateExponent); + + static std::unique_ptr createPrivateWithAdditionalData( + std::string_view modulus, std::string_view exponent, std::string_view privateExponent, + std::optional firstPrimeInfo, std::optional secondPrimeInfo, + std::vector otherPrimeInfos); + + CryptoKeyRSAComponents(std::string_view modulus, std::string_view exponent); + + CryptoKeyRSAComponents(std::string_view modulus, std::string_view exponent, + std::string_view privateExponent); + + CryptoKeyRSAComponents(std::string_view modulus, std::string_view exponent, + std::string_view privateExponent, std::optional firstPrimeInfo, + std::optional secondPrimeInfo, + std::vector otherPrimeInfos); +}; +#endif diff --git a/runtime/fastly/builtins/crypto/crypto-key.cpp b/runtime/fastly/builtins/crypto/crypto-key.cpp new file mode 100644 index 0000000000..7f7f78b0f6 --- /dev/null +++ b/runtime/fastly/builtins/crypto/crypto-key.cpp @@ -0,0 +1,725 @@ +#include "crypto-key.h" +#include "crypto-algorithm.h" +#include "encode.h" +#include "openssl/rsa.h" +#include +#include +#include + +namespace fastly::crypto { + +CryptoKeyUsages::CryptoKeyUsages(uint8_t mask) { this->mask = mask; }; +CryptoKeyUsages::CryptoKeyUsages(bool encrypt, bool decrypt, bool sign, bool verify, + bool derive_key, bool derive_bits, bool wrap_key, + bool unwrap_key) { + this->mask = 0; + if (encrypt) { + this->mask |= encrypt_flag; + } + if (decrypt) { + this->mask |= decrypt_flag; + } + if (sign) { + this->mask |= sign_flag; + } + if (verify) { + this->mask |= verify_flag; + } + if (derive_key) { + this->mask |= derive_key_flag; + } + if (derive_bits) { + this->mask |= derive_bits_flag; + } + if (wrap_key) { + this->mask |= wrap_key_flag; + } + if (unwrap_key) { + this->mask |= unwrap_key_flag; + } +}; + +CryptoKeyUsages CryptoKeyUsages::from(std::vector key_usages) { + uint8_t mask = 0; + for (auto usage : key_usages) { + if (usage == "encrypt") { + mask |= encrypt_flag; + } else if (usage == "decrypt") { + mask |= decrypt_flag; + } else if (usage == "sign") { + mask |= sign_flag; + } else if (usage == "verify") { + mask |= verify_flag; + } else if (usage == "deriveKey") { + mask |= derive_key_flag; + } else if (usage == "deriveBits") { + mask |= derive_bits_flag; + } else if (usage == "wrapKey") { + mask |= wrap_key_flag; + } else if (usage == "unwrapKey") { + mask |= unwrap_key_flag; + } + } + return CryptoKeyUsages(mask); +} + +JS::Result CryptoKeyUsages::from(JSContext *cx, JS::HandleValue key_usages) { + bool key_usages_is_array; + if (!JS::IsArrayObject(cx, key_usages, &key_usages_is_array)) { + return JS::Result(JS::Error()); + } + + if (!key_usages_is_array) { + // TODO: This should check if the JS::HandleValue is iterable and if so, should convert it into + // a JS Array + api::throw_error(cx, api::Errors::TypeError, "crypto.subtle.importKey", "keyUsages", + "be a sequence"); + return JS::Result(JS::Error()); + } + + JS::RootedObject array(cx, &key_usages.toObject()); + uint32_t key_usages_length; + if (!JS::GetArrayLength(cx, array, &key_usages_length)) { + return JS::Result(JS::Error()); + } + uint8_t mask = 0; + for (uint32_t index = 0; index < key_usages_length; index++) { + JS::RootedValue val(cx); + if (!JS_GetElement(cx, array, index, &val)) { + return JS::Result(JS::Error()); + } + + auto utf8chars = core::encode(cx, val); + if (!utf8chars) { + return JS::Result(JS::Error()); + } + + std::string_view usage = utf8chars; + + if (usage == "encrypt") { + mask |= encrypt_flag; + } else if (usage == "decrypt") { + mask |= decrypt_flag; + } else if (usage == "sign") { + mask |= sign_flag; + } else if (usage == "verify") { + mask |= verify_flag; + } else if (usage == "deriveKey") { + mask |= derive_key_flag; + } else if (usage == "deriveBits") { + mask |= derive_bits_flag; + } else if (usage == "wrapKey") { + mask |= wrap_key_flag; + } else if (usage == "unwrapKey") { + mask |= unwrap_key_flag; + } else { + api::throw_error( + cx, api::Errors::TypeError, "crypto.subtle.importKey", + "each value in the 'keyUsages' list", + "be one of 'encrypt', 'decrypt', 'sign', 'verify', 'deriveKey', 'deriveBits', " + "'wrapKey', or 'unwrapKey'"); + return JS::Result(JS::Error()); + } + } + return CryptoKeyUsages(mask); +} + +bool CryptoKey::algorithm_get(JSContext *cx, unsigned argc, JS::Value *vp) { + METHOD_HEADER(0); + + // TODO: Change this class so that its prototype isn't an instance of the class + if (self == proto_obj.get()) { + return api::throw_error(cx, api::Errors::WrongReceiver, "algorithm get", "CryptoKey"); + } + + auto algorithm = &JS::GetReservedSlot(self, Slots::Algorithm).toObject(); + JS::RootedObject result(cx, algorithm); + if (!result) { + return false; + } + args.rval().setObject(*result); + + return true; +} + +bool CryptoKey::extractable_get(JSContext *cx, unsigned argc, JS::Value *vp) { + METHOD_HEADER(0); + + // TODO: Change this class so that its prototype isn't an instance of the class + if (self == proto_obj.get()) { + return api::throw_error(cx, api::Errors::WrongReceiver, "extractable get", "CryptoKey"); + } + + auto extractable = JS::GetReservedSlot(self, Slots::Extractable).toBoolean(); + args.rval().setBoolean(extractable); + + return true; +} + +bool CryptoKey::type_get(JSContext *cx, unsigned argc, JS::Value *vp) { + METHOD_HEADER(0) + + // TODO: Change this class so that its prototype isn't an instance of the class + if (self == proto_obj.get()) { + return api::throw_error(cx, api::Errors::WrongReceiver, "type get", "CryptoKey"); + } + + auto type = static_cast(JS::GetReservedSlot(self, Slots::Type).toInt32()); + + // We store the type internally as a CryptoKeyType variant and need to + // convert it into it's JSString representation. + switch (type) { + case CryptoKeyType::Private: { + auto str = JS_AtomizeString(cx, "private"); + if (!str) { + return false; + } + args.rval().setString(str); + return true; + } + case CryptoKeyType::Public: { + auto str = JS_AtomizeString(cx, "public"); + if (!str) { + return false; + } + args.rval().setString(str); + return true; + } + case CryptoKeyType::Secret: { + auto str = JS_AtomizeString(cx, "secret"); + if (!str) { + return false; + } + args.rval().setString(str); + return true; + } + default: { + MOZ_ASSERT_UNREACHABLE("Unknown `CryptoKeyType` value"); + return false; + } + }; +} + +bool CryptoKey::usages_get(JSContext *cx, unsigned argc, JS::Value *vp) { + METHOD_HEADER(0); + + // TODO: Change this class so that its prototype isn't an instance of the class + if (self == proto_obj.get()) { + return api::throw_error(cx, api::Errors::WrongReceiver, "usages get", "CryptoKey"); + } + + // If the JS Array has already been created previously, return it. + auto cached_usage = JS::GetReservedSlot(self, Slots::UsagesArray); + if (cached_usage.isObject()) { + args.rval().setObject(cached_usage.toObject()); + return true; + } + // Else, grab the CryptoKeyUsages value from Slots::Usages and convert + // it into a JS Array and store the result in Slots::UsagesArray. + auto usages = JS::GetReservedSlot(self, Slots::Usages).toInt32(); + MOZ_ASSERT(std::in_range(usages)); + auto usage = CryptoKeyUsages(static_cast(usages)); + // The result is ordered alphabetically. + JS::RootedValueVector result(cx); + JS::RootedString str(cx); + auto append = [&](const char *name) -> bool { + if (!(str = JS_AtomizeString(cx, name))) { + return false; + } + if (!result.append(JS::StringValue(str))) { + js::ReportOutOfMemory(cx); + return false; + } + return true; + }; + if (usage.canDecrypt()) { + if (!append("decrypt")) { + return false; + } + } + if (usage.canDeriveBits()) { + if (!append("deriveBits")) { + return false; + } + } + if (usage.canDeriveKey()) { + if (!append("deriveKey")) { + return false; + } + } + if (usage.canEncrypt()) { + if (!append("encrypt")) { + return false; + } + } + if (usage.canSign()) { + if (!append("sign")) { + return false; + } + } + if (usage.canUnwrapKey()) { + if (!append("unwrapKey")) { + return false; + } + } + if (usage.canVerify()) { + if (!append("verify")) { + return false; + } + } + if (usage.canWrapKey()) { + if (!append("wrapKey")) { + return false; + } + } + + JS::Rooted array(cx, JS::NewArrayObject(cx, result)); + if (!array) { + return false; + } + cached_usage.setObject(*array); + JS::SetReservedSlot(self, Slots::UsagesArray, cached_usage); + + args.rval().setObject(*array); + + return true; +} + +const JSFunctionSpec CryptoKey::static_methods[] = { + JS_FS_END, +}; + +const JSPropertySpec CryptoKey::static_properties[] = { + JS_PS_END, +}; + +const JSFunctionSpec CryptoKey::methods[] = {JS_FS_END}; + +const JSPropertySpec CryptoKey::properties[] = { + JS_PSG("type", CryptoKey::type_get, JSPROP_ENUMERATE), + JS_PSG("extractable", CryptoKey::extractable_get, JSPROP_ENUMERATE), + JS_PSG("algorithm", CryptoKey::algorithm_get, JSPROP_ENUMERATE), + JS_PSG("usages", CryptoKey::usages_get, JSPROP_ENUMERATE), + JS_STRING_SYM_PS(toStringTag, "CryptoKey", JSPROP_READONLY), + JS_PS_END}; + +bool CryptoKey::init_class(JSContext *cx, JS::HandleObject global) { + return BuiltinImpl::init_class_impl(cx, global); +} + +namespace { +int numBitsToBytes(int x) { return (x / 8) + (7 + (x % 8)) / 8; } + +BIGNUM *convertToBigNumber(std::string_view bytes) { + return BN_bin2bn(reinterpret_cast(bytes.data()), bytes.length(), nullptr); +} + +BIGNUM *convertToPaddedBigNumber(std::string_view bytes, size_t expected_length) { + if (bytes.length() != expected_length) { + return nullptr; + } + + return BN_bin2bn(reinterpret_cast(bytes.data()), bytes.length(), nullptr); +} + +int getBigNumberLength(BIGNUM *a) { return BN_num_bytes(a) * 8; } + +int curveIdentifier(NamedCurve curve) { + switch (curve) { + case NamedCurve::P256: { + return NID_X9_62_prime256v1; + } + case NamedCurve::P384: { + return NID_secp384r1; + } + case NamedCurve::P521: { + return NID_secp521r1; + } + } + MOZ_ASSERT_UNREACHABLE(); + return 0; +} +} // namespace + +JSObject *CryptoKey::createHMAC(JSContext *cx, CryptoAlgorithmHMAC_Import *algorithm, + std::unique_ptr> data, unsigned long length, + bool extractable, CryptoKeyUsages usages) { + MOZ_ASSERT(cx); + MOZ_ASSERT(algorithm); + JS::RootedObject instance( + cx, JS_NewObjectWithGivenProto(cx, &CryptoKey::class_, CryptoKey::proto_obj)); + if (!instance) { + return nullptr; + } + + JS::RootedObject alg(cx, algorithm->toObject(cx)); + if (!alg) { + return nullptr; + } + + JS::SetReservedSlot(instance, Slots::Algorithm, JS::ObjectValue(*alg)); + JS::SetReservedSlot(instance, Slots::Type, + JS::Int32Value(static_cast(CryptoKeyType::Secret))); + JS::SetReservedSlot(instance, Slots::Extractable, JS::BooleanValue(extractable)); + JS::SetReservedSlot(instance, Slots::Usages, JS::Int32Value(usages.toInt())); + JS::SetReservedSlot(instance, Slots::KeyDataLength, JS::Int32Value(data->size())); + JS::SetReservedSlot(instance, Slots::KeyData, JS::PrivateValue(data.release()->data())); + return instance; +} + +JSObject *CryptoKey::createECDSA(JSContext *cx, CryptoAlgorithmECDSA_Import *algorithm, + std::unique_ptr keyData, bool extractable, + CryptoKeyUsages usages) { + MOZ_ASSERT(cx); + MOZ_ASSERT(algorithm); + CryptoKeyType keyType; + switch (keyData->type) { + case CryptoKeyECComponents::Type::Public: { + keyType = CryptoKeyType::Public; + break; + } + case CryptoKeyECComponents::Type::Private: { + keyType = CryptoKeyType::Private; + break; + } + default: { + MOZ_ASSERT_UNREACHABLE("Unknown `CryptoKeyECComponents::Type` value"); + return nullptr; + } + } + + // When creating a private key, we require the d information. + if (keyType == CryptoKeyType::Private && keyData->d.empty()) { + return nullptr; + } + + // For both public and private keys, we need the public x and y. + if (keyData->x.empty() || keyData->y.empty()) { + return nullptr; + } + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" + auto curve_nid = curveIdentifier(algorithm->namedCurve); + auto ec = EC_KEY_new_by_curve_name(curve_nid); + if (!ec) { + // TODO: Should we create a JS Exception here? + return nullptr; + } + // JWK requires the length of x, y, d to match the group degree. + const EC_GROUP *group = EC_KEY_get0_group(ec); + int degree_bytes = numBitsToBytes(EC_GROUP_get_degree(group)); + + // Read the public key's uncompressed affine coordinates. + auto x = convertToPaddedBigNumber(keyData->x, degree_bytes); + if (!x) { + return nullptr; + } + + auto y = convertToPaddedBigNumber(keyData->y, degree_bytes); + if (!y) { + return nullptr; + } + + if (!EC_KEY_set_public_key_affine_coordinates(ec, x, y)) { + // TODO: Should we create a JS Exception here? + return nullptr; + } + + // Extract the "d" parameters. + if (!keyData->d.empty()) { + auto d = convertToPaddedBigNumber(keyData->d, degree_bytes); + if (!d) { + return nullptr; + } + + if (!EC_KEY_set_private_key(ec, d)) { + // TODO: Should we create a JS Exception here? + return nullptr; + } + } + + // Verify the key. + if (!EC_KEY_check_key(ec)) { + return nullptr; + } + + // Wrap the EC_KEY into an EVP_PKEY. + EVP_PKEY *pkey = EVP_PKEY_new(); + if (!pkey || !EVP_PKEY_set1_EC_KEY(pkey, ec)) { + return nullptr; + } +#pragma clang diagnostic pop + + ////////////// + JS::RootedObject instance( + cx, JS_NewObjectWithGivenProto(cx, &CryptoKey::class_, CryptoKey::proto_obj)); + if (!instance) { + return nullptr; + } + + JS::RootedObject alg(cx, algorithm->toObject(cx)); + if (!alg) { + return nullptr; + } + + JS::SetReservedSlot(instance, Slots::Algorithm, JS::ObjectValue(*alg)); + JS::SetReservedSlot(instance, Slots::Type, JS::Int32Value(static_cast(keyType))); + JS::SetReservedSlot(instance, Slots::Extractable, JS::BooleanValue(extractable)); + JS::SetReservedSlot(instance, Slots::Usages, JS::Int32Value(usages.toInt())); + JS::SetReservedSlot(instance, Slots::Key, JS::PrivateValue(pkey)); + return instance; +} + +JSObject *CryptoKey::createRSA(JSContext *cx, CryptoAlgorithmRSASSA_PKCS1_v1_5_Import *algorithm, + std::unique_ptr keyData, bool extractable, + CryptoKeyUsages usages) { + MOZ_ASSERT(cx); + MOZ_ASSERT(algorithm); + CryptoKeyType keyType; + switch (keyData->type) { + case CryptoKeyRSAComponents::Type::Public: { + keyType = CryptoKeyType::Public; + break; + } + case CryptoKeyRSAComponents::Type::Private: { + keyType = CryptoKeyType::Private; + break; + } + default: { + MOZ_ASSERT_UNREACHABLE("Unknown `CryptoKeyRSAComponents::Type` value"); + return nullptr; + } + } + + // When creating a private key, we require the p and q prime information. + if (keyType == CryptoKeyType::Private && !keyData->hasAdditionalPrivateKeyParameters) { + return nullptr; + } + + // But we don't currently support creating keys with any additional prime information. + if (!keyData->otherPrimeInfos.empty()) { + return nullptr; + } + + // For both public and private keys, we need the public modulus and exponent. + if (keyData->modulus.empty() || keyData->exponent.empty()) { + return nullptr; + } + + // For private keys, we require the private exponent, as well as p and q prime information. + if (keyType == CryptoKeyType::Private) { + if (keyData->privateExponent.empty() || keyData->firstPrimeInfo->primeFactor.empty() || + keyData->secondPrimeInfo->primeFactor.empty()) { + return nullptr; + } + } + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" + auto rsa = RSA_new(); +#pragma clang diagnostic pop + if (!rsa) { + return nullptr; + } + + auto n = convertToBigNumber(keyData->modulus); + auto e = convertToBigNumber(keyData->exponent); + if (!n || !e) { + return nullptr; + } + +// Calling with d null is fine as long as n and e are not null +// Ownership of n and e transferred to OpenSSL +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" + if (!RSA_set0_key(rsa, n, e, nullptr)) { + return nullptr; + } +#pragma clang diagnostic pop + + if (keyType == CryptoKeyType::Private) { + auto d = convertToBigNumber(keyData->privateExponent); + if (!d) { + return nullptr; + } + +// Calling with n and e null is fine as long as they were set prior +// Ownership of d transferred to OpenSSL +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" + if (!RSA_set0_key(rsa, nullptr, nullptr, d)) { + return nullptr; + } +#pragma clang diagnostic pop + + auto p = convertToBigNumber(keyData->firstPrimeInfo->primeFactor); + auto q = convertToBigNumber(keyData->secondPrimeInfo->primeFactor); + if (!p || !q) { + return nullptr; + } + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" + // Ownership of p and q transferred to OpenSSL + if (!RSA_set0_factors(rsa, p, q)) { + return nullptr; + } +#pragma clang diagnostic pop + + // We set dmp1, dmpq1, and iqmp member of the RSA struct if the keyData has corresponding data. + + // dmp1 -- d mod (p - 1) + auto dmp1 = (!keyData->firstPrimeInfo->factorCRTExponent.empty()) + ? convertToBigNumber(keyData->firstPrimeInfo->factorCRTExponent) + : nullptr; + // dmq1 -- d mod (q - 1) + auto dmq1 = (!keyData->secondPrimeInfo->factorCRTExponent.empty()) + ? convertToBigNumber(keyData->secondPrimeInfo->factorCRTExponent) + : nullptr; + // iqmp -- q^(-1) mod p + auto iqmp = (!keyData->secondPrimeInfo->factorCRTCoefficient.empty()) + ? convertToBigNumber(keyData->secondPrimeInfo->factorCRTCoefficient) + : nullptr; + +// Ownership of dmp1, dmq1 and iqmp transferred to OpenSSL +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" + if (!RSA_set0_crt_params(rsa, dmp1, dmq1, iqmp)) { +#pragma clang diagnostic pop + return nullptr; + } + } + + auto pkey = EVP_PKEY_new(); + if (!pkey) { + return nullptr; + } + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" + if (EVP_PKEY_set1_RSA(pkey, rsa) != 1) { +#pragma clang diagnostic pop + return nullptr; + } + + JS::RootedObject instance( + cx, JS_NewObjectWithGivenProto(cx, &CryptoKey::class_, CryptoKey::proto_obj)); + if (!instance) { + return nullptr; + } + + JS::RootedObject alg(cx, algorithm->toObject(cx)); + if (!alg) { + return nullptr; + } + // Set the modulusLength attribute of algorithm to the length, in bits, of the RSA public modulus. + JS::RootedValue modulusLength(cx, JS::NumberValue(getBigNumberLength(n))); + if (!JS_SetProperty(cx, alg, "modulusLength", modulusLength)) { + return nullptr; + } + + auto p = mozilla::MakeUnique(keyData->exponent.size()); + auto exp = keyData->exponent; + std::copy(exp.begin(), exp.end(), p.get()); + + JS::RootedObject buffer( + cx, JS::NewArrayBufferWithContents(cx, keyData->exponent.size(), p.get(), + JS::NewArrayBufferOutOfMemory::CallerMustFreeMemory)); + // `buffer` takes ownership of `p` if the call to NewArrayBufferWithContents was successful + // if the call was not successful, we need to free `p` before exiting from the function. + if (!buffer) { + // We can be here if the array buffer was too large -- if that was the case then a + // JSMSG_BAD_ARRAY_LENGTH will have been created. Otherwise we're probably out of memory. + if (!JS_IsExceptionPending(cx)) { + js::ReportOutOfMemory(cx); + } + return nullptr; + } + + // At this point, `buffer` owns the memory managed by `p`. + static_cast(p.release()); + + // Set the publicExponent attribute of algorithm to the BigInteger representation of the RSA + // public exponent. + JS::RootedObject byte_array(cx, + JS_NewUint8ArrayWithBuffer(cx, buffer, 0, keyData->exponent.size())); + JS::RootedValue publicExponent(cx, JS::ObjectValue(*byte_array)); + if (!JS_SetProperty(cx, alg, "publicExponent", publicExponent)) { + return nullptr; + } + + JS::SetReservedSlot(instance, Slots::Algorithm, JS::ObjectValue(*alg)); + JS::SetReservedSlot(instance, Slots::Type, JS::Int32Value(static_cast(keyType))); + JS::SetReservedSlot(instance, Slots::Extractable, JS::BooleanValue(extractable)); + JS::SetReservedSlot(instance, Slots::Usages, JS::Int32Value(usages.toInt())); + JS::SetReservedSlot(instance, Slots::Key, JS::PrivateValue(pkey)); + return instance; +} + +CryptoKeyType CryptoKey::type(JSObject *self) { + MOZ_ASSERT(is_instance(self)); + + return static_cast(JS::GetReservedSlot(self, Slots::Type).toInt32()); +} + +JSObject *CryptoKey::get_algorithm(JS::HandleObject self) { + MOZ_ASSERT(is_instance(self)); + auto algorithm = JS::GetReservedSlot(self, Slots::Algorithm).toObjectOrNull(); + return algorithm; +} + +EVP_PKEY *CryptoKey::key(JSObject *self) { + MOZ_ASSERT(is_instance(self)); + return static_cast(JS::GetReservedSlot(self, Slots::Key).toPrivate()); +} + +std::span CryptoKey::hmacKeyData(JSObject *self) { + MOZ_ASSERT(is_instance(self)); + return std::span( + static_cast(JS::GetReservedSlot(self, Slots::KeyData).toPrivate()), + JS::GetReservedSlot(self, Slots::KeyDataLength).toInt32()); +} + +JS::Result CryptoKey::is_algorithm(JSContext *cx, JS::HandleObject self, + CryptoAlgorithmIdentifier algorithm) { + MOZ_ASSERT(CryptoKey::is_instance(self)); + JS::RootedObject self_algorithm(cx, JS::GetReservedSlot(self, Slots::Algorithm).toObjectOrNull()); + MOZ_ASSERT(self_algorithm != nullptr); + JS::Rooted name_val(cx); + if (!JS_GetProperty(cx, self_algorithm, "name", &name_val)) { + return JS::Result(JS::Error()); + } + JS::Rooted str(cx, JS::ToString(cx, name_val)); + if (!str) { + return JS::Result(JS::Error()); + } + // TODO: should chars be used? + auto chars = core::encode(cx, str); + if (!chars) { + return JS::Result(JS::Error()); + } + bool match; + if (!JS_StringEqualsAscii(cx, JS::ToString(cx, name_val), algorithmName(algorithm), &match)) { + return JS::Result(JS::Error()); + } + return match; +} + +bool CryptoKey::canSign(JS::HandleObject self) { + MOZ_ASSERT(is_instance(self)); + auto usages = JS::GetReservedSlot(self, Slots::Usages).toInt32(); + MOZ_ASSERT(std::in_range(usages)); + auto usage = CryptoKeyUsages(static_cast(usages)); + return usage.canSign(); +} + +bool CryptoKey::canVerify(JS::HandleObject self) { + MOZ_ASSERT(is_instance(self)); + auto usages = JS::GetReservedSlot(self, Slots::Usages).toInt32(); + MOZ_ASSERT(std::in_range(usages)); + auto usage = CryptoKeyUsages(static_cast(usages)); + return usage.canVerify(); +} + +} // namespace fastly::crypto diff --git a/runtime/fastly/builtins/crypto/crypto-key.h b/runtime/fastly/builtins/crypto/crypto-key.h new file mode 100644 index 0000000000..61290b0798 --- /dev/null +++ b/runtime/fastly/builtins/crypto/crypto-key.h @@ -0,0 +1,143 @@ +#ifndef BUILTINS_WEB_FASTLY_CRYPTO_CRYPTO_KEY_H +#define BUILTINS_WEB_FASTLY_CRYPTO_CRYPTO_KEY_H + +#include "builtin.h" + +#include "crypto-key-ec-components.h" +#include "crypto-key-rsa-components.h" +#include "openssl/evp.h" + +namespace fastly::crypto { + +enum class CryptoAlgorithmIdentifier : uint8_t; +class CryptoAlgorithmRSASSA_PKCS1_v1_5_Import; +class CryptoAlgorithmHMAC_Import; +class CryptoAlgorithmECDSA_Import; +enum class CryptoKeyType : uint8_t { Public, Private, Secret }; + +enum class CryptoKeyFormat : uint8_t { Raw, Spki, Pkcs8, Jwk }; + +class CryptoKeyUsages { +private: + uint8_t mask = 0; + +public: + static constexpr const uint8_t encrypt_flag = 1 << 0; + static constexpr const uint8_t decrypt_flag = 1 << 1; + static constexpr const uint8_t sign_flag = 1 << 2; + static constexpr const uint8_t verify_flag = 1 << 3; + static constexpr const uint8_t derive_key_flag = 1 << 4; + static constexpr const uint8_t derive_bits_flag = 1 << 5; + static constexpr const uint8_t wrap_key_flag = 1 << 6; + static constexpr const uint8_t unwrap_key_flag = 1 << 7; + + CryptoKeyUsages() = default; + CryptoKeyUsages(uint8_t mask); + CryptoKeyUsages(bool encrypt, bool decrypt, bool sign, bool verify, bool derive_key, + bool derive_bits, bool wrap_key, bool unwrap_key); + static CryptoKeyUsages from(std::vector key_usages); + static JS::Result from(JSContext *cx, JS::HandleValue key_usages); + + uint8_t toInt() { return this->mask; }; + + bool isEmpty() { return this->mask == 0; }; + bool isSuperSetOf(CryptoKeyUsages &other) { return this->mask & other.mask; }; + + bool canEncrypt() { return this->mask & encrypt_flag; }; + bool canDecrypt() { return this->mask & decrypt_flag; }; + bool canSign() { return this->mask & sign_flag; }; + bool canVerify() { return this->mask & verify_flag; }; + bool canDeriveKey() { return this->mask & derive_key_flag; }; + bool canDeriveBits() { return this->mask & derive_bits_flag; }; + bool canWrapKey() { return this->mask & wrap_key_flag; }; + bool canUnwrapKey() { return this->mask & unwrap_key_flag; }; + + bool canOnlyEncrypt() { return this->mask == encrypt_flag; }; + bool canOnlyDecrypt() { return this->mask == decrypt_flag; }; + bool canOnlySign() { return this->mask == sign_flag; }; + bool canOnlyVerify() { return this->mask == verify_flag; }; + bool canOnlySignOrVerify() { return this->mask & (sign_flag | verify_flag); }; + bool canOnlyDeriveKey() { return this->mask == derive_key_flag; }; + bool canOnlyDeriveBits() { return this->mask == derive_bits_flag; }; + bool canOnlyWrapKey() { return this->mask == wrap_key_flag; }; + bool canOnlyUnwrapKey() { return this->mask == unwrap_key_flag; }; +}; + +class CryptoKey : public builtins::BuiltinNoConstructor { +public: + static const int ctor_length = 0; + static constexpr const char *class_name = "CryptoKey"; + + // https://w3c.github.io/webcrypto/#dom-cryptokey-algorithm + // Returns the cached ECMAScript object associated with the [[algorithm]] internal slot. + static bool algorithm_get(JSContext *cx, unsigned argc, JS::Value *vp); + + // https://w3c.github.io/webcrypto/#dom-cryptokey-extractable + // Reflects the [[extractable]] internal slot, which indicates whether or not the raw keying + // material may be exported by the application. + static bool extractable_get(JSContext *cx, unsigned argc, JS::Value *vp); + + // https://w3c.github.io/webcrypto/#dom-cryptokey-type + // Reflects the [[type]] internal slot, which contains the type of the underlying key. + static bool type_get(JSContext *cx, unsigned argc, JS::Value *vp); + + // https://w3c.github.io/webcrypto/#dom-cryptokey-usages + // Returns the cached ECMAScript object associated with the [[usages]] internal slot, which + // indicates which cryptographic operations are permissible to be used with this key. + static bool usages_get(JSContext *cx, unsigned argc, JS::Value *vp); + + enum Slots { + // https://w3c.github.io/webcrypto/#ref-for-dfn-CryptoKey-slot-algorithm-1 + // The contents of the [[algorithm]] internal slot shall be, or be derived from, a KeyAlgorithm. + // We store a JS::ObjectValue within this slot which contains a JS Object representation of the + // algorithm. + Algorithm, + // The type of the underlying key. + // We store a JS::Int32Value representation of the CryptoKeyType variant in this slot + Type, + // Indicates whether or not the raw keying material may be exported by the application + // We store a JS::BooleanValue in this slot + Extractable, + // Indicates which cryptographic operations are permissible to be used with this key + // We store a JS::Int32Value representation of a CryptoKeyUsages. in this slot + Usages, + // Returns the cached ECMAScript object associated with the [[usages]] internal slot, + // which indicates which cryptographic operations are permissible to be used with this key. + UsagesArray, + // We store a JS::PrivateValue in this slot, it will contain either the raw key data. + // It will either be an `EVP_PKEY *` or an `uint8_t *`. + // `uint8_t *` is used only for HMAC keys, `EVP_PKEY *` is used for all the other key types. + Key, + KeyData, + KeyDataLength, + Count + }; + static const JSFunctionSpec static_methods[]; + static const JSPropertySpec static_properties[]; + static const JSFunctionSpec methods[]; + static const JSPropertySpec properties[]; + + static bool init_class(JSContext *cx, JS::HandleObject global); + + static JSObject *createHMAC(JSContext *cx, CryptoAlgorithmHMAC_Import *algorithm, + std::unique_ptr> data, unsigned long length, + bool extractable, CryptoKeyUsages usages); + static JSObject *createRSA(JSContext *cx, CryptoAlgorithmRSASSA_PKCS1_v1_5_Import *algorithm, + std::unique_ptr keyData, bool extractable, + CryptoKeyUsages usages); + static JSObject *createECDSA(JSContext *cx, CryptoAlgorithmECDSA_Import *algorithm, + std::unique_ptr keyData, bool extractable, + CryptoKeyUsages usages); + static CryptoKeyType type(JSObject *self); + static JSObject *get_algorithm(JS::HandleObject self); + static EVP_PKEY *key(JSObject *self); + static std::span hmacKeyData(JSObject *self); + static bool canSign(JS::HandleObject self); + static bool canVerify(JS::HandleObject self); + static JS::Result is_algorithm(JSContext *cx, JS::HandleObject self, + CryptoAlgorithmIdentifier algorithm); +}; + +} // namespace fastly::crypto + +#endif diff --git a/runtime/fastly/builtins/crypto/crypto.cpp b/runtime/fastly/builtins/crypto/crypto.cpp new file mode 100644 index 0000000000..899f9bffd9 --- /dev/null +++ b/runtime/fastly/builtins/crypto/crypto.cpp @@ -0,0 +1,151 @@ +#include "../../../StarlingMonkey/builtins/web/dom-exception.h" +#include "crypto.h" +#include "host_api.h" +#include "subtle-crypto.h" +#include "uuid.h" + + +namespace fastly::crypto { + +bool is_int_typed_array(JSObject *obj) { + return JS_IsInt8Array(obj) || JS_IsUint8Array(obj) || JS_IsInt16Array(obj) || + JS_IsUint16Array(obj) || JS_IsInt32Array(obj) || JS_IsUint32Array(obj) || + JS_IsUint8ClampedArray(obj) || JS_IsBigInt64Array(obj) || JS_IsBigUint64Array(obj); +} + +using builtins::web::dom_exception::DOMException; + +#define MAX_BYTE_LENGTH 65536 +/** + * Implementation of + * https://www.w3.org/TR/WebCryptoAPI/#Crypto-method-getRandomValues + * TODO: investigate ways to automatically wipe the buffer passed in here when + * it is GC'd. Content can roughly approximate that using finalizers for views + * of the buffer, but it's far from ideal. + */ +bool Crypto::get_random_values(JSContext *cx, unsigned argc, JS::Value *vp) { + METHOD_HEADER(1) + + if (!args[0].isObject() || !is_int_typed_array(&args[0].toObject())) { + DOMException::raise(cx, "crypto.getRandomValues: input must be an integer-typed TypedArray", + "TypeMismatchError"); + return false; + } + + JS::RootedObject typed_array(cx, &args[0].toObject()); + size_t byte_length = JS_GetArrayBufferViewByteLength(typed_array); + if (byte_length > MAX_BYTE_LENGTH) { + std::string message = "crypto.getRandomValues: input byteLength must be at most "; + message += std::to_string(MAX_BYTE_LENGTH); + message += ", but is "; + message += std::to_string(byte_length); + DOMException::raise(cx, message, "QuotaExceededError"); + return false; + } + + JS::AutoCheckCannotGC noGC(cx); + bool is_shared; + auto *buffer = static_cast(JS_GetArrayBufferViewData(typed_array, &is_shared, noGC)); + + auto res = host_api::Random::get_bytes(byte_length); + if (auto *err = res.to_err()) { + noGC.reset(); + HANDLE_ERROR(cx, *err); + return false; + } + + auto bytes = std::move(res.unwrap()); + std::copy_n(bytes.begin(), byte_length, buffer); + + args.rval().setObject(*typed_array); + return true; +} + +bool Crypto::random_uuid(JSContext *cx, unsigned argc, JS::Value *vp) { + METHOD_HEADER(0) + + auto maybe_uuid = random_uuid_v4(cx); + if (!maybe_uuid.has_value()) { + return false; + } + + auto uuid = maybe_uuid.value(); + MOZ_ASSERT(uuid.size() == 36); + + JS::RootedString str(cx, JS_NewStringCopyN(cx, uuid.data(), uuid.size())); + if (!str) { + return false; + } + + args.rval().setString(str); + return true; +} +JS::PersistentRooted Crypto::subtle; +JS::PersistentRooted crypto; + +bool Crypto::subtle_get(JSContext *cx, unsigned argc, JS::Value *vp) { + METHOD_HEADER_WITH_NAME(0, "subtle get"); + if (self != crypto.get()) { + return api::throw_error(cx, api::Errors::WrongReceiver, "subtle get", "Crypto"); + } + + args.rval().setObject(*subtle); + return true; +} + +const JSFunctionSpec Crypto::static_methods[] = { + JS_FS_END, +}; + +const JSPropertySpec Crypto::static_properties[] = { + JS_PS_END, +}; + +const JSFunctionSpec Crypto::methods[] = { + JS_FN("getRandomValues", get_random_values, 1, JSPROP_ENUMERATE), + JS_FN("randomUUID", random_uuid, 0, JSPROP_ENUMERATE), JS_FS_END}; + +const JSPropertySpec Crypto::properties[] = { + JS_PSG("subtle", subtle_get, JSPROP_ENUMERATE), + JS_STRING_SYM_PS(toStringTag, "Crypto", JSPROP_READONLY), JS_PS_END}; + +bool crypto_get(JSContext *cx, unsigned argc, JS::Value *vp) { + JS::CallArgs args = CallArgsFromVp(argc, vp); + JS::RootedObject global(cx, JS::CurrentGlobalOrNull(cx)); + auto thisv = args.thisv(); + if (thisv != JS::UndefinedHandleValue && thisv != JS::ObjectValue(*global)) { + return api::throw_error(cx, api::Errors::WrongReceiver, "crypto get", "Window"); + } + args.rval().setObject(*crypto); + return true; +} + +bool Crypto::init_class(JSContext *cx, JS::HandleObject global) { + if (!init_class_impl(cx, global)) { + return false; + } + + JS::RootedObject cryptoInstance( + cx, JS_NewObjectWithGivenProto(cx, &Crypto::class_, Crypto::proto_obj)); + if (!cryptoInstance) { + return false; + } + crypto.init(cx, cryptoInstance); + + JS::RootedObject subtleCrypto( + cx, JS_NewObjectWithGivenProto(cx, &SubtleCrypto::class_, SubtleCrypto::proto_obj)); + subtle.init(cx, subtleCrypto); + return JS_DefineProperty(cx, global, "crypto", crypto_get, nullptr, JSPROP_ENUMERATE); +} + +bool install(api::Engine *engine) { + if (!SubtleCrypto::init_class(engine->cx(), engine->global())) + return false; + if (!Crypto::init_class(engine->cx(), engine->global())) + return false; + if (!CryptoKey::init_class(engine->cx(), engine->global())) + return false; + return true; +} + +} // namespace builtins::web::crypto diff --git a/runtime/fastly/builtins/crypto/crypto.h b/runtime/fastly/builtins/crypto/crypto.h new file mode 100644 index 0000000000..0c5dc1b0be --- /dev/null +++ b/runtime/fastly/builtins/crypto/crypto.h @@ -0,0 +1,33 @@ +#ifndef BUILTINS_WEB_FASTLY_CRYPTO_H +#define BUILTINS_WEB_FASTLY_CRYPTO_H + +#include "builtin.h" + +namespace fastly::crypto { + +class Crypto : public builtins::BuiltinNoConstructor { +private: +public: + static constexpr const char *class_name = "Crypto"; + static const int ctor_length = 0; + + static JS::PersistentRooted subtle; + + enum Slots { Count }; + static const JSFunctionSpec static_methods[]; + static const JSPropertySpec static_properties[]; + static const JSFunctionSpec methods[]; + static const JSPropertySpec properties[]; + + static bool subtle_get(JSContext *cx, unsigned argc, JS::Value *vp); + static bool get_random_values(JSContext *cx, unsigned argc, JS::Value *vp); + static bool random_uuid(JSContext *cx, unsigned argc, JS::Value *vp); + + static bool init_class(JSContext *cx, JS::HandleObject global); +}; + +bool install(api::Engine *engine); + +} // namespace fastly::crypto + +#endif diff --git a/runtime/fastly/builtins/crypto/json-web-key.cpp b/runtime/fastly/builtins/crypto/json-web-key.cpp new file mode 100644 index 0000000000..d86939a0a8 --- /dev/null +++ b/runtime/fastly/builtins/crypto/json-web-key.cpp @@ -0,0 +1,366 @@ +// TODO: remove these once the warnings are fixed +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Winvalid-offsetof" +#pragma clang diagnostic ignored "-Wdeprecated-enum-enum-conversion" +#include "js/ArrayBuffer.h" +#include "js/Conversions.h" +#include "js/ForOfIterator.h" +#include "js/Object.h" +#include "js/Promise.h" +#include "js/experimental/TypedData.h" +#include "jsapi.h" +#include "jsfriendapi.h" +#pragma clang diagnostic pop + +#include "../../../StarlingMonkey/builtins/web/dom-exception.h" +#include "builtin.h" +#include "encode.h" +#include "json-web-key.h" + +#include + +namespace fastly::crypto { + +namespace { +JS::Result> +extractStringPropertyFromObject(JSContext *cx, JS::HandleObject object, std::string_view property) { + bool has_property; + if (!JS_HasProperty(cx, object, property.data(), &has_property)) { + return JS::Result>(JS::Error()); + } + if (!has_property) { + return std::optional(std::nullopt); + } + JS::RootedValue value(cx); + if (!JS_GetProperty(cx, object, property.data(), &value)) { + return JS::Result>(JS::Error()); + } + // Convert into a String following https://tc39.es/ecma262/#sec-tostring + auto chars = core::encode(cx, value); + if (!chars) { + return JS::Result>(JS::Error()); + } + return std::optional(std::string(chars.begin(), chars.len)); +} +} // namespace + +std::unique_ptr JsonWebKey::parse(JSContext *cx, JS::HandleValue value, + std::string_view required_kty_value) { + if (!value.isObject()) { + api::throw_error(cx, api::Errors::TypeError, "crypto.subtle.importKey", "keyData", + "be a JSONWebKey"); + return nullptr; + } + JS::RootedObject object(cx, &value.toObject()); + + // DOMString kty; + auto kty_result = extractStringPropertyFromObject(cx, object, "kty"); + if (kty_result.isErr()) { + return nullptr; + } + auto kty_option = kty_result.unwrap(); + if (!kty_option.has_value()) { + api::throw_error(cx, api::Errors::TypeError, "crypto.subtle.importKey", "keyData", + "be a JSONWebKey"); + return nullptr; + } + auto kty = kty_option.value(); + if (kty != required_kty_value) { + auto message = fmt::format("crypto.subtle.importkey: The JWK member 'kty' was not '{}'", + required_kty_value); + builtins::web::dom_exception::DOMException::raise(cx, message, "DataError"); + return nullptr; + } + + // DOMString use; + auto use_result = extractStringPropertyFromObject(cx, object, "use"); + if (use_result.isErr()) { + return nullptr; + } + auto use = use_result.unwrap(); + + // DOMString alg; + auto alg_result = extractStringPropertyFromObject(cx, object, "alg"); + if (alg_result.isErr()) { + return nullptr; + } + auto alg = alg_result.unwrap(); + + // DOMString crv; + auto crv_result = extractStringPropertyFromObject(cx, object, "crv"); + if (crv_result.isErr()) { + return nullptr; + } + auto crv = crv_result.unwrap(); + + // DOMString x; + auto x_result = extractStringPropertyFromObject(cx, object, "x"); + if (x_result.isErr()) { + return nullptr; + } + auto x = x_result.unwrap(); + + // DOMString y; + auto y_result = extractStringPropertyFromObject(cx, object, "y"); + if (y_result.isErr()) { + return nullptr; + } + auto y = y_result.unwrap(); + + // DOMString d; + auto d_result = extractStringPropertyFromObject(cx, object, "d"); + if (d_result.isErr()) { + return nullptr; + } + auto d = d_result.unwrap(); + + // DOMString n; + auto n_result = extractStringPropertyFromObject(cx, object, "n"); + if (n_result.isErr()) { + return nullptr; + } + auto n = n_result.unwrap(); + + // DOMString e; + auto e_result = extractStringPropertyFromObject(cx, object, "e"); + if (e_result.isErr()) { + return nullptr; + } + auto e = e_result.unwrap(); + + // DOMString p; + auto p_result = extractStringPropertyFromObject(cx, object, "p"); + if (p_result.isErr()) { + return nullptr; + } + auto p = p_result.unwrap(); + + // DOMString q; + auto q_result = extractStringPropertyFromObject(cx, object, "q"); + if (q_result.isErr()) { + return nullptr; + } + auto q = q_result.unwrap(); + + // DOMString dp; + auto dp_result = extractStringPropertyFromObject(cx, object, "dp"); + if (dp_result.isErr()) { + return nullptr; + } + auto dp = dp_result.unwrap(); + + // DOMString dq; + auto dq_result = extractStringPropertyFromObject(cx, object, "dq"); + if (dq_result.isErr()) { + return nullptr; + } + auto dq = dq_result.unwrap(); + + // DOMString qi; + auto qi_result = extractStringPropertyFromObject(cx, object, "qi"); + if (qi_result.isErr()) { + return nullptr; + } + auto qi = qi_result.unwrap(); + + // DOMString k; + auto k_result = extractStringPropertyFromObject(cx, object, "k"); + if (k_result.isErr()) { + return nullptr; + } + auto k = k_result.unwrap(); + + // boolean ext; + std::optional ext = std::nullopt; + { + bool has_ext; + if (!JS_HasProperty(cx, object, "ext", &has_ext)) { + return nullptr; + } + if (has_ext) { + JS::RootedValue ext_val(cx); + if (!JS_GetProperty(cx, object, "ext", &ext_val)) { + return nullptr; + } + ext = JS::ToBoolean(ext_val); + } + } + // sequence key_ops; + std::vector key_ops; + { + bool has_key_ops; + if (!JS_HasProperty(cx, object, "key_ops", &has_key_ops)) { + return nullptr; + } + if (has_key_ops) { + JS::RootedValue key_ops_val(cx); + if (!JS_GetProperty(cx, object, "key_ops", &key_ops_val)) { + return nullptr; + } + bool key_ops_is_array; + if (!JS::IsArrayObject(cx, key_ops_val, &key_ops_is_array)) { + return nullptr; + } + if (!key_ops_is_array) { + // TODO: Check if key_ops_val is iterable via Symbol.iterator and if so, convert to a JS + // Array + builtins::web::dom_exception::DOMException::raise( + cx, "crypto.subtle.importkey: The JWK member 'key_ops' was not a sequence", + "DataError"); + return nullptr; + } + uint32_t length; + JS::RootedObject key_ops_array(cx, &key_ops_val.toObject()); + if (!JS::GetArrayLength(cx, key_ops_array, &length)) { + return nullptr; + } + if (length != 0) { + // Initialize to the provided array's length. + // TODO: move to mozilla::Vector so we can catch if this reserve exceeds our memory limits + key_ops.reserve(length); + JS::RootedValue op_val(cx); + for (uint32_t i = 0; i < length; ++i) { + // We should check for and handle interrupts so we can allow GC to happen whilst we are + // iterating. + // TODO: Go through entire codebase and add JS_CheckForInterrupt into code-paths which + // iterate or are recursive such as the structuredClone function if + // (!JS_CheckForInterrupt(cx)) { + // return nullptr; + // } + + if (!JS_GetElement(cx, key_ops_array, i, &op_val)) { + return nullptr; + } + + auto op_chars = core::encode(cx, op_val); + if (!op_chars) { + return nullptr; + } + + std::string op(op_chars.begin(), op_chars.len); + + if (op != "encrypt" && op != "decrypt" && op != "sign" && op != "verify" && + op != "deriveKey" && op != "deriveBits" && op != "wrapKey" && op != "unwrapKey") { + builtins::web::dom_exception::DOMException::raise( + cx, + "crypto.subtle.importKey parameter 'keyData': " + "each value in the 'key_ops' list must be one of 'encrypt', 'decrypt', " + "'sign', 'verify', 'deriveKey', 'deriveBits', 'wrapKey', or 'unwrapKey'", + "DataError"); + return nullptr; + } + + // No duplicates allowed + if (std::find(key_ops.begin(), key_ops.end(), op) != key_ops.end()) { + builtins::web::dom_exception::DOMException::raise(cx, + "crypto.subtle.importKey parameter 'keyData': " + "'key_ops' list must not contain duplicate entries", + "DataError"); + return nullptr; + } + + key_ops.push_back(std::move(op)); + } + } + } + } + + std::vector oth; + { + bool has_oth; + if (!JS_HasProperty(cx, object, "oth", &has_oth)) { + return nullptr; + } + if (has_oth) { + JS::RootedValue oth_val(cx); + if (!JS_GetProperty(cx, object, "oth", &oth_val)) { + return nullptr; + } + bool oth_is_array; + if (!JS::IsArrayObject(cx, oth_val, &oth_is_array)) { + return nullptr; + } + if (!oth_is_array) { + // TODO: Check if oth_val is iterable via Symbol.iterator and if so, convert to a JS Array + builtins::web::dom_exception::DOMException::raise( + cx, "crypto.subtle.importkey: The JWK member 'oth' was not a sequence", "DataError"); + return nullptr; + } + uint32_t length; + JS::RootedObject oth_array(cx, &oth_val.toObject()); + if (!JS::GetArrayLength(cx, oth_array, &length)) { + return nullptr; + } + if (length != 0) { + // Initialize to the provided array's length. + // TODO: move to mozilla::Vector so we can catch if this reserve exceeds our memory limits + oth.reserve(length); + JS::RootedValue info_val(cx); + for (uint32_t i = 0; i < length; ++i) { + // We should check for and handle interrupts so we can allow GC to happen whilst we are + // iterating. + // TODO: Go through entire codebase and add JS_CheckForInterrupt into code-paths which + // iterate or are recursive such as the structuredClone function if + // (!JS_CheckForInterrupt(cx)) { + // return nullptr; + // } + + if (!JS_GetElement(cx, oth_array, i, &info_val)) { + return nullptr; + } + + if (!info_val.isObject()) { + api::throw_error(cx, api::Errors::TypeError, + "crypto.subtle.importKey parameter 'keyData'", "'oth' list", + "be an RsaOtherPrimesInfo object"); + return nullptr; + } + JS::RootedObject info_obj(cx, &info_val.toObject()); + + JS::RootedValue r_val(cx); + if (!JS_GetProperty(cx, info_obj, "r", &r_val)) { + return nullptr; + } + auto r_chars = core::encode(cx, info_val); + if (!r_chars) { + api::throw_error(cx, api::Errors::TypeError, + "crypto.subtle.importKey parameter 'keyData'", "'oth' list", + "be an RsaOtherPrimesInfo object"); + return nullptr; + } + std::string r(r_chars.begin(), r_chars.len); + JS::RootedValue d_val(cx); + if (!JS_GetProperty(cx, info_obj, "d", &d_val)) { + return nullptr; + } + auto d_chars = core::encode(cx, info_val); + if (!d_chars) { + api::throw_error(cx, api::Errors::TypeError, + "crypto.subtle.importKey parameter 'keyData'", "'oth' list", + "be an RsaOtherPrimesInfo object"); + return nullptr; + } + std::string d(d_chars.begin(), d_chars.len); + + JS::RootedValue t_val(cx); + if (!JS_GetProperty(cx, info_obj, "t", &t_val)) { + return nullptr; + } + auto t_chars = core::encode(cx, info_val); + if (!t_chars) { + api::throw_error(cx, api::Errors::TypeError, + "crypto.subtle.importKey parameter 'keyData'", "'oth' list", + "be an RsaOtherPrimesInfo object"); + return nullptr; + } + std::string t(t_chars.begin(), t_chars.len); + + oth.emplace_back(r, d, t); + } + } + } + } + return std::make_unique(kty, use, key_ops, alg, ext, crv, x, y, n, e, d, p, q, dp, dq, + qi, oth, k); +} +} // namespace fastly::crypto diff --git a/runtime/fastly/builtins/crypto/json-web-key.h b/runtime/fastly/builtins/crypto/json-web-key.h new file mode 100644 index 0000000000..6711d88b27 --- /dev/null +++ b/runtime/fastly/builtins/crypto/json-web-key.h @@ -0,0 +1,200 @@ +#ifndef BUILTINS_WEB_FASTLY_CRYPTO_JSON_WEB_KEY_H +#define BUILTINS_WEB_FASTLY_CRYPTO_JSON_WEB_KEY_H +#include "jsapi.h" +#include +#include +#include +#include +#include + +namespace fastly::crypto { + +// https://datatracker.ietf.org/doc/html/rfc7518#section-6.3.2.7 +// 6.3.2.7. "oth" (Other Primes Info) Parameter +class RsaOtherPrimesInfo { +public: + // https://datatracker.ietf.org/doc/html/rfc7518#section-6.3.2.7.1 + // 6.3.2.7.1. "r" (Prime Factor) + // The "r" (prime factor) parameter within an "oth" array member + // represents the value of a subsequent prime factor. It is represented + // as a Base64urlUInt-encoded value. + std::string r; + + // https://datatracker.ietf.org/doc/html/rfc7518#section-6.3.2.7.2 + // 6.3.2.7.2. "d" (Factor CRT Exponent) + // The "d" (factor CRT exponent) parameter within an "oth" array member + // represents the CRT exponent of the corresponding prime factor. It is + // represented as a Base64urlUInt-encoded value. + std::string d; + + // https://datatracker.ietf.org/doc/html/rfc7518#section-6.3.2.7.3 + // 6.3.2.7.3. "t" (Factor CRT Coefficient) + // The "t" (factor CRT coefficient) parameter within an "oth" array + // member represents the CRT coefficient of the corresponding prime + // factor. It is represented as a Base64urlUInt-encoded value. + std::string t; + RsaOtherPrimesInfo(std::string r, std::string d, std::string t) : r{r}, d{d}, t{t} {} +}; + +// https://datatracker.ietf.org/doc/html/rfc7517#section-4 +class JsonWebKey { +public: + // https://datatracker.ietf.org/doc/html/rfc7517#section-4.1 + // 4.1. "kty" (Key Type) Parameter + std::string kty; + // https://datatracker.ietf.org/doc/html/rfc7517#section-4.2 + // 4.2. "use" (Public Key Use) Parameter + std::optional use; + + // https://datatracker.ietf.org/doc/html/rfc7517#section-4.3 + // 4.3. "key_ops" (Key Operations) Parameter + std::vector key_ops; + // https://datatracker.ietf.org/doc/html/rfc7517#section-4.4 + // 4.4. "alg" (Algorithm) Parameter + std::optional alg; + + // https://w3c.github.io/webcrypto/#iana-section-jwk + std::optional ext; + + // 6.2. Parameters for Elliptic Curve Keys + // https://datatracker.ietf.org/doc/html/rfc7518#section-6.2.1.1 + // 6.2.1.1. "crv" (Curve) Parameter + // The "crv" (curve) parameter identifies the cryptographic curve used + // with the key. + std::optional crv; + // https://datatracker.ietf.org/doc/html/rfc7518#section-6.2.1.2 + // 6.2.1.2. "x" (X Coordinate) Parameter + // The "x" (x coordinate) parameter contains the x coordinate for the + // Elliptic Curve point. It is represented as the base64url encoding of + // the octet string representation of the coordinate, as defined in + // Section 2.3.5 of SEC1 [SEC1] + std::optional x; + // https://datatracker.ietf.org/doc/html/rfc7518#section-6.2.1.3 + // 6.2.1.3. "y" (Y Coordinate) Parameter + // The "y" (y coordinate) parameter contains the y coordinate for the + // Elliptic Curve point. It is represented as the base64url encoding of + // the octet string representation of the coordinate, as defined in + // Section 2.3.5 of SEC1 [SEC1]. + std::optional y; + + // 6.2.2. Parameters for Elliptic Curve Private Keys + // https://datatracker.ietf.org/doc/html/rfc7518#section-6.2.2.1 + // The "d" (ECC private key) parameter contains the Elliptic Curve + // private key value. It is represented as the base64url encoding of + // the octet string representation of the private key value, as defined + // in Section 2.3.7 of SEC1 [SEC1]. The length of this octet string + // MUST be ceiling(log-base-2(n)/8) octets (where n is the order of the + // curve). + // + // 6.3.2. Parameters for RSA Private Keys + // The "d" (private exponent) parameter contains the private exponent + // value for the RSA private key. It is represented as a Base64urlUInt- + // encoded value. + std::optional d; + + // 6.3. Parameters for RSA Keys + // 6.3.1. Parameters for RSA Public Keys + // https://datatracker.ietf.org/doc/html/rfc7518#section-6.3.1.1 + // 6.3.1.1. "n" (Modulus) Parameter + // The "n" (modulus) parameter contains the modulus value for the RSA + // public key. It is represented as a Base64urlUInt-encoded value. + std::optional n; + // https://datatracker.ietf.org/doc/html/rfc7518#section-6.3.1.2 + // 6.3.1.2. "e" (Exponent) Parameter + // The "e" (exponent) parameter contains the exponent value for the RSA + // public key. It is represented as a Base64urlUInt-encoded value. + std::optional e; + + // 6.3.2. Parameters for RSA Private Keys + // https://datatracker.ietf.org/doc/html/rfc7518#section-6.3.2.2 + // The "p" (first prime factor) parameter contains the first prime + // factor. It is represented as a Base64urlUInt-encoded value. + std::optional p; + // https://datatracker.ietf.org/doc/html/rfc7518#section-6.3.2.3 + // The "q" (second prime factor) parameter contains the second prime + // factor. It is represented as a Base64urlUInt-encoded value. + std::optional q; + // https://datatracker.ietf.org/doc/html/rfc7518#section-6.3.2.4 + // The "dp" (first factor CRT exponent) parameter contains the Chinese + // Remainder Theorem (CRT) exponent of the first factor. It is + // represented as a Base64urlUInt-encoded value. + std::optional dp; + // https://datatracker.ietf.org/doc/html/rfc7518#section-6.3.2.5 + // The "dq" (second factor CRT exponent) parameter contains the CRT + // exponent of the second factor. It is represented as a Base64urlUInt- + // encoded value. + std::optional dq; + // https://datatracker.ietf.org/doc/html/rfc7518#section-6.3.2.6 + // The "qi" (first CRT coefficient) parameter contains the CRT + // coefficient of the second factor. It is represented as a + // Base64urlUInt-encoded value. + std::optional qi; + // https://datatracker.ietf.org/doc/html/rfc7518#section-6.3.2.7 + // The "oth" (other primes info) parameter contains an array of + // information about any third and subsequent primes, should they exist. + // When only two primes have been used (the normal case), this parameter + // MUST be omitted. When three or more primes have been used, the + // number of array elements MUST be the number of primes used minus two. + std::vector oth; + + // 6.4. Parameters for Symmetric Keys + // https://datatracker.ietf.org/doc/html/rfc7518#section-6.4.1 + // 6.4.1. "k" (Key Value) Parameter + // The "k" (key value) parameter contains the value of the symmetric (or + // other single-valued) key. It is represented as the base64url + // encoding of the octet sequence containing the key value. + std::optional k; + + JsonWebKey(std::string kty, std::vector key_ops, std::optional ext, + std::optional n, std::optional e) + : kty{kty}, key_ops{key_ops}, ext{ext}, n{n}, e{e} {} + + JsonWebKey RSAPublicKey(std::string kty, std::vector key_ops, + std::optional ext, std::optional n, + std::optional e) { + return JsonWebKey(kty, key_ops, ext, n, e); + } + + JsonWebKey(std::string kty, std::vector key_ops, std::optional ext, + std::optional n, std::optional e, + std::optional d) + : kty{kty}, key_ops{key_ops}, ext{ext}, d{d}, n{n}, e{e} {} + + JsonWebKey RSAPrivateKey(std::string kty, std::vector key_ops, + std::optional ext, std::optional n, + std::optional e, std::optional d) { + return JsonWebKey(kty, key_ops, ext, n, e, d); + } + JsonWebKey(std::string kty, std::vector key_ops, std::optional ext, + std::optional n, std::optional e, + std::optional d, std::optional p, + std::optional q, std::optional dp, + std::optional dq, std::optional qi) + : kty{kty}, key_ops{key_ops}, ext{ext}, d{d}, n{n}, e{e}, p{p}, q{q}, dp{dp}, dq{dq}, qi{qi} { + } + + JsonWebKey RSAPrivateKeyWithAdditionalPrimes( + std::string kty, std::vector key_ops, std::optional ext, + std::optional n, std::optional e, std::optional d, + std::optional p, std::optional q, std::optional dp, + std::optional dq, std::optional qi) { + return JsonWebKey(kty, key_ops, ext, n, e, d, p, q, dp, dq, qi); + } + + JsonWebKey(std::string kty, std::optional use, std::vector key_ops, + std::optional alg, std::optional ext, + std::optional crv, std::optional x, + std::optional y, std::optional n, + std::optional e, std::optional d, + std::optional p, std::optional q, + std::optional dp, std::optional dq, + std::optional qi, std::vector oth, + std::optional k) + : kty{kty}, use{use}, key_ops{key_ops}, alg{alg}, ext{ext}, crv{crv}, x{x}, y{y}, d{d}, n{n}, + e{e}, p{p}, q{q}, dp{dp}, dq{dq}, qi{qi}, oth{oth}, k{k} {} + + static std::unique_ptr parse(JSContext *cx, JS::HandleValue value, + std::string_view required_kty_value); +}; +} // namespace fastly::crypto +#endif diff --git a/runtime/fastly/builtins/crypto/subtle-crypto.cpp b/runtime/fastly/builtins/crypto/subtle-crypto.cpp new file mode 100644 index 0000000000..808293d82a --- /dev/null +++ b/runtime/fastly/builtins/crypto/subtle-crypto.cpp @@ -0,0 +1,370 @@ +#include "subtle-crypto.h" +#include "../../../StarlingMonkey/builtins/web/dom-exception.h" +#include "builtin.h" +#include "encode.h" + +namespace fastly::crypto { + +using builtins::web::dom_exception::DOMException; + +// digest(algorithm, data) +// https://w3c.github.io/webcrypto/#SubtleCrypto-method-digest +bool SubtleCrypto::digest(JSContext *cx, unsigned argc, JS::Value *vp) { + JS::CallArgs args = CallArgsFromVp(argc, vp); + if (!check_receiver(cx, args.thisv(), "SubtleCrypto.digest")) { + return ReturnPromiseRejectedWithPendingError(cx, args); + } + if (!args.requireAtLeast(cx, "SubtleCrypto.digest", 2)) { + return ReturnPromiseRejectedWithPendingError(cx, args); + } + + // 1. Let algorithm be the algorithm parameter passed to the digest() method. + auto algorithm = args.get(0); + + // 2 . Let data be the result of getting a copy of the bytes held by the data parameter + // passed to the digest() method. + auto data = value_to_buffer(cx, args.get(1), "SubtleCrypto.digest: data"); + if (!data.has_value()) { + // value_to_buffer would have already created a JS exception so we don't need to create one + // ourselves. + return ReturnPromiseRejectedWithPendingError(cx, args); + } + + // 3. Let normalizedAlgorithm be the result of normalizing an algorithm, with alg set to + // algorithm and op set to "digest". + // 4. If an error occurred, return a Promise rejected with normalizedAlgorithm. + auto normalizedAlgorithm = CryptoAlgorithmDigest::normalize(cx, algorithm); + if (!normalizedAlgorithm) { + // TODO Rename error to NotSupportedError + return ReturnPromiseRejectedWithPendingError(cx, args); + } + + // 5. Let promise be a new Promise. + JS::RootedObject promise(cx, JS::NewPromiseObject(cx, nullptr)); + if (!promise) { + return ReturnPromiseRejectedWithPendingError(cx, args); + } + + // 6. Return promise and perform the remaining steps in parallel. + args.rval().setObject(*promise); + + // 7. If the following steps or referenced procedures say to throw an error, reject promise with + // the returned error and then terminate the algorithm. + // 8. Let result be the result of performing the digest operation specified by normalizedAlgorithm + // using algorithm, with data as message. + auto array_buffer = normalizedAlgorithm->digest(cx, data.value()); + if (!array_buffer) { + return RejectPromiseWithPendingError(cx, promise); + } + + // 9. Resolve promise with result. + JS::RootedValue result(cx); + result.setObject(*array_buffer); + JS::ResolvePromise(cx, promise, result); + + return true; +} + +// Promise importKey(KeyFormat format, +// (BufferSource or JsonWebKey) keyData, +// AlgorithmIdentifier algorithm, +// boolean extractable, +// sequence keyUsages ); +// https://w3c.github.io/webcrypto/#dfn-SubtleCrypto-method-importKey +bool SubtleCrypto::importKey(JSContext *cx, unsigned argc, JS::Value *vp) { + MOZ_ASSERT(cx); + JS::CallArgs args = CallArgsFromVp(argc, vp); + + if (!args.requireAtLeast(cx, "SubtleCrypto.importKey", 5)) { + return ReturnPromiseRejectedWithPendingError(cx, args); + } + if (!check_receiver(cx, args.thisv(), "SubtleCrypto.importKey")) { + return ReturnPromiseRejectedWithPendingError(cx, args); + } + + // 1. Let format, algorithm, extractable and usages, be the format, algorithm, + // extractable and keyUsages parameters passed to the importKey() method, respectively. + CryptoKeyFormat format; + { + auto format_arg = args.get(0); + // Convert into a String following https://tc39.es/ecma262/#sec-tostring + auto format_chars = core::encode(cx, format_arg); + if (!format_chars.ptr) { + return ReturnPromiseRejectedWithPendingError(cx, args); + } + std::string_view format_string = format_chars; + if (format_string == "spki") { + format = CryptoKeyFormat::Spki; + } else if (format_string == "pkcs8") { + format = CryptoKeyFormat::Pkcs8; + } else if (format_string == "jwk") { + format = CryptoKeyFormat::Jwk; + } else if (format_string == "raw") { + format = CryptoKeyFormat::Raw; + } else { + DOMException::raise(cx, + "crypto.subtle.importkey: Provided format parameter is not supported. " + "Supported formats are: 'spki', 'pkcs8', 'jwk', and 'raw'", + "NotSupportedError"); + return ReturnPromiseRejectedWithPendingError(cx, args); + } + } + auto key_data = args.get(1); + auto algorithm = args.get(2); + bool extractable = JS::ToBoolean(args.get(3)); + CryptoKeyUsages usages; + { + auto usages_arg = args.get(4); + + auto keyUsageMaskResult = CryptoKeyUsages::from(cx, usages_arg); + if (keyUsageMaskResult.isErr()) { + return ReturnPromiseRejectedWithPendingError(cx, args); + } + usages = keyUsageMaskResult.unwrap(); + } + + // 3. Let normalizedAlgorithm be the result of normalizing an algorithm, with alg set to algorithm + // and op set to "importKey". + // 4. If an error occurred, return a Promise rejected with normalizedAlgorithm. + auto normalizedAlgorithm = CryptoAlgorithmImportKey::normalize(cx, algorithm); + if (!normalizedAlgorithm) { + return ReturnPromiseRejectedWithPendingError(cx, args); + } + + // 5. Let promise be a new Promise. + JS::RootedObject promise(cx, JS::NewPromiseObject(cx, nullptr)); + if (!promise) { + return ReturnPromiseRejectedWithPendingError(cx, args); + } + + // 6. Return promise and perform the remaining steps in parallel. + args.rval().setObject(*promise); + + // 7. If the following steps or referenced procedures say to throw an error, + // reject promise with the returned error and then terminate the algorithm. + + // Steps 8 - 11 are all done in the `importKey` call + // 8. Let result be the CryptoKey object that results from performing the + // import key operation specified by normalizedAlgorithm using keyData, + // algorithm, format, extractable and usages. + // 9. If the [[type]] internal slot of result is "secret" or "private" and + // usages is empty, then throw a SyntaxError. + // 10. Set the [[extractable]] internal slot of result to extractable. + // 11. Set the [[usages]] internal slot of result to the normalized value of + // usages. + JS::RootedObject result(cx); + JSObject *key = normalizedAlgorithm->importKey(cx, format, key_data, extractable, usages); + if (!key) { + return RejectPromiseWithPendingError(cx, promise); + } + + // 12. Resolve promise with result. + JS::RootedValue result_val(cx); + result_val.setObject(*key); + JS::ResolvePromise(cx, promise, result_val); + + return true; +} + +// https://w3c.github.io/webcrypto/#SubtleCrypto-method-sign +bool SubtleCrypto::sign(JSContext *cx, unsigned argc, JS::Value *vp) { + MOZ_ASSERT(cx); + MOZ_ASSERT(vp); + JS::CallArgs args = CallArgsFromVp(argc, vp); + + if (!args.requireAtLeast(cx, "SubtleCrypto.sign", 3)) { + return ReturnPromiseRejectedWithPendingError(cx, args); + } + if (!check_receiver(cx, args.thisv(), "SubtleCrypto.sign")) { + return ReturnPromiseRejectedWithPendingError(cx, args); + } + + // 1. Let algorithm and key be the algorithm and key parameters passed to the sign() method, + // respectively. + auto algorithm = args.get(0); + auto key_arg = args.get(1); + if (!CryptoKey::is_instance(key_arg)) { + api::throw_error(cx, api::Errors::TypeError, "crypto.subtle.sign", "key", + "be a CryptoKey object"); + return ReturnPromiseRejectedWithPendingError(cx, args); + } + JS::RootedObject key(cx, &key_arg.toObject()); + + // 2. Let data be the result of getting a copy of the bytes held by the data parameter passed to + // the sign() method. + std::optional> dataOptional = + value_to_buffer(cx, args.get(2), "crypto.subtle.sign: data"); + if (!dataOptional.has_value()) { + // value_to_buffer would have already created a JS exception so we don't need to create one + // ourselves. + return ReturnPromiseRejectedWithPendingError(cx, args); + } + std::span data = dataOptional.value(); + + // 3. Let normalizedAlgorithm be the result of normalizing an algorithm, with alg set to algorithm + // and op set to "sign". + auto normalizedAlgorithm = CryptoAlgorithmSignVerify::normalize(cx, algorithm); + // 4. If an error occurred, return a Promise rejected with normalizedAlgorithm. + if (!normalizedAlgorithm) { + // TODO Rename error to NotSupportedError + return ReturnPromiseRejectedWithPendingError(cx, args); + } + + // 5. Let promise be a new Promise. + JS::RootedObject promise(cx, JS::NewPromiseObject(cx, nullptr)); + if (!promise) { + return ReturnPromiseRejectedWithPendingError(cx, args); + } + // 6. Return promise and perform the remaining steps in parallel. + args.rval().setObject(*promise); + + // 7. If the following steps or referenced procedures say to throw an error, reject promise with + // the returned error and then terminate the algorithm. + + // 8. If the name member of normalizedAlgorithm is not equal to the name attribute of the + // [[algorithm]] internal slot of key then throw an InvalidAccessError. + auto identifier = normalizedAlgorithm->identifier(); + auto match_result = CryptoKey::is_algorithm(cx, key, identifier); + if (match_result.isErr()) { + DOMException::raise(cx, "CryptoKey doesn't match AlgorithmIdentifier", "InvalidAccessError"); + return RejectPromiseWithPendingError(cx, promise); + } + + if (match_result.unwrap() == false) { + DOMException::raise(cx, "CryptoKey doesn't match AlgorithmIdentifier", "InvalidAccessError"); + return RejectPromiseWithPendingError(cx, promise); + } + + // 9. If the [[usages]] internal slot of key does not contain an entry that is "sign", then throw + // an InvalidAccessError. + if (!CryptoKey::canSign(key)) { + DOMException::raise(cx, "CryptoKey doesn't support signing", "InvalidAccessError"); + return RejectPromiseWithPendingError(cx, promise); + } + + // 10. Let result be the result of performing the sign operation specified by normalizedAlgorithm + // using key and algorithm and with data as message. + auto signature = normalizedAlgorithm->sign(cx, key, data); + if (!signature) { + return RejectPromiseWithPendingError(cx, promise); + } + JS::RootedValue result(cx, JS::ObjectValue(*signature)); + + // 11. Resolve promise with result. + JS::ResolvePromise(cx, promise, result); + return true; +} + +// Promise verify(AlgorithmIdentifier algorithm, +// CryptoKey key, +// BufferSource signature, +// BufferSource data); +// https://w3c.github.io/webcrypto/#SubtleCrypto-method-verify +bool SubtleCrypto::verify(JSContext *cx, unsigned argc, JS::Value *vp) { + MOZ_ASSERT(cx); + MOZ_ASSERT(vp); + JS::CallArgs args = CallArgsFromVp(argc, vp); + + if (!args.requireAtLeast(cx, "SubtleCrypto.verify", 4)) { + return ReturnPromiseRejectedWithPendingError(cx, args); + } + if (!check_receiver(cx, args.thisv(), "SubtleCrypto.verify")) { + return ReturnPromiseRejectedWithPendingError(cx, args); + } + + // 1. Let algorithm and key be the algorithm and key parameters passed to the verify() method, + // respectively. + auto algorithm = args.get(0); + auto key_arg = args.get(1); + if (!CryptoKey::is_instance(key_arg)) { + api::throw_error(cx, api::Errors::TypeError, "crypto.subtle.verify", "key", + "be a CryptoKey object"); + return ReturnPromiseRejectedWithPendingError(cx, args); + } + JS::RootedObject key(cx, &key_arg.toObject()); + + // 2. Let signature be the result of getting a copy of the bytes held by the signature + // parameter passed to the verify() method. + std::optional> signature = + value_to_buffer(cx, args.get(2), "SubtleCrypto.verify: signature (argument 3)"); + if (!signature) { + return ReturnPromiseRejectedWithPendingError(cx, args); + } + + // 3. Let data be the result of getting a copy of the bytes held by the data parameter passed + // to the verify() method. + std::optional> data = + value_to_buffer(cx, args.get(3), "SubtleCrypto.verify: data (argument 4)"); + if (!data) { + return ReturnPromiseRejectedWithPendingError(cx, args); + } + + // 4. Let normalizedAlgorithm be the result of normalizing an algorithm, with alg set to + // algorithm and op set to "verify". + // 5. If an error occurred, return a Promise rejected with normalizedAlgorithm. + auto normalizedAlgorithm = CryptoAlgorithmSignVerify::normalize(cx, algorithm); + if (!normalizedAlgorithm) { + // TODO Rename error to NotSupportedError + return ReturnPromiseRejectedWithPendingError(cx, args); + } + + // 6. Let promise be a new Promise. + JS::RootedObject promise(cx, JS::NewPromiseObject(cx, nullptr)); + if (!promise) { + return ReturnPromiseRejectedWithPendingError(cx, args); + } + + // 7. Return promise and perform the remaining steps in parallel. + args.rval().setObject(*promise); + + // 8. If the following steps or referenced procedures say to throw an error, reject promise + // with the returned error and then terminate the algorithm. + // 9. If the name member of normalizedAlgorithm is not equal to the name attribute of the + // [[algorithm]] internal slot of key then throw an InvalidAccessError. + auto identifier = normalizedAlgorithm->identifier(); + auto match_result = CryptoKey::is_algorithm(cx, key, identifier); + if (match_result.isErr() || match_result.unwrap() == false) { + DOMException::raise(cx, "CryptoKey doesn't match AlgorithmIdentifier", "InvalidAccessError"); + return RejectPromiseWithPendingError(cx, promise); + } + // 10. If the [[usages]] internal slot of key does not contain an entry that is "verify", then + // throw an InvalidAccessError. + if (!CryptoKey::canVerify(key)) { + DOMException::raise(cx, "CryptoKey doesn't support verification", "InvalidAccessError"); + return RejectPromiseWithPendingError(cx, promise); + } + // 11. Let result be the result of performing the verify operation specified by + // normalizedAlgorithm using key, algorithm and signature and with data as message. + + auto matchResult = normalizedAlgorithm->verify(cx, key, signature.value(), data.value()); + if (matchResult.isErr()) { + return RejectPromiseWithPendingError(cx, promise); + } + // 12. Resolve promise with result. + JS::RootedValue result(cx); + result.setBoolean(matchResult.unwrap()); + JS::ResolvePromise(cx, promise, result); + + return true; +} + +const JSFunctionSpec SubtleCrypto::static_methods[] = { + JS_FS_END, +}; + +const JSPropertySpec SubtleCrypto::static_properties[] = { + JS_PS_END, +}; + +const JSFunctionSpec SubtleCrypto::methods[] = { + JS_FN("digest", digest, 2, JSPROP_ENUMERATE), + JS_FN("importKey", importKey, 5, JSPROP_ENUMERATE), JS_FN("sign", sign, 3, JSPROP_ENUMERATE), + JS_FN("verify", verify, 4, JSPROP_ENUMERATE), JS_FS_END}; + +const JSPropertySpec SubtleCrypto::properties[] = { + JS_STRING_SYM_PS(toStringTag, "SubtleCrypto", JSPROP_READONLY), JS_PS_END}; + +bool SubtleCrypto::init_class(JSContext *cx, JS::HandleObject global) { + return init_class_impl(cx, global); +} +} // namespace fastly::crypto diff --git a/runtime/fastly/builtins/crypto/subtle-crypto.h b/runtime/fastly/builtins/crypto/subtle-crypto.h new file mode 100644 index 0000000000..926a6bd882 --- /dev/null +++ b/runtime/fastly/builtins/crypto/subtle-crypto.h @@ -0,0 +1,43 @@ +#ifndef BUILTINS_WEB_FASTLY_CRYPTO_SUBTLE_CRYPTO_H +#define BUILTINS_WEB_FASTLY_CRYPTO_SUBTLE_CRYPTO_H + +#include "builtin.h" +#include "crypto-algorithm.h" + +namespace fastly::crypto { + +enum class Operations : uint8_t { + Encrypt, + Decrypt, + Sign, + Verify, + Digest, + GenerateKey, + DeriveBits, + ImportKey, + WrapKey, + UnwrapKey, + GetKeyLength +}; + +class SubtleCrypto : public builtins::BuiltinNoConstructor { +private: +public: + static constexpr const char *class_name = "SubtleCrypto"; + static const int ctor_length = 0; + + enum Slots { Count }; + static const JSFunctionSpec static_methods[]; + static const JSPropertySpec static_properties[]; + static const JSFunctionSpec methods[]; + static const JSPropertySpec properties[]; + static bool digest(JSContext *cx, unsigned argc, JS::Value *vp); + static bool importKey(JSContext *cx, unsigned argc, JS::Value *vp); + static bool sign(JSContext *cx, unsigned argc, JS::Value *vp); + static bool verify(JSContext *cx, unsigned argc, JS::Value *vp); + + static bool init_class(JSContext *cx, JS::HandleObject global); +}; + +} // namespace fastly::crypto +#endif diff --git a/runtime/fastly/builtins/crypto/uuid.cpp b/runtime/fastly/builtins/crypto/uuid.cpp new file mode 100644 index 0000000000..626b495bed --- /dev/null +++ b/runtime/fastly/builtins/crypto/uuid.cpp @@ -0,0 +1,93 @@ +#include "uuid.h" +#include "host_api.h" + +#include + +namespace fastly::crypto { + +// FROM RFC 4122 +// The formal definition of the UUID string representation is +// provided by the following ABNF [7]: +// +// UUID = time-low "-" time-mid "-" +// time-high-and-version "-" +// clock-seq-and-reserved +// clock-seq-low "-" node +// time-low = 4hexOctet +// time-mid = 2hexOctet +// time-high-and-version = 2hexOctet +// clock-seq-and-reserved = hexOctet +// clock-seq-low = hexOctet +// node = 6hexOctet +// hexOctet = hexDigit hexDigit +// hexDigit = +// "0" / "1" / "2" / "3" / "4" / "5" / "6" / "7" / "8" / "9" / +// "a" / "b" / "c" / "d" / "e" / "f" / +// "A" / "B" / "C" / "D" / "E" / "F" +struct UUID { + uint32_t time_low; + uint16_t time_mid; + uint16_t time_high_and_version; + uint8_t clock_seq_and_reserved; + uint8_t clock_seq_low; + uint8_t node[6]; +}; + +// FROM RFC 4122 +// 4.1.3. Version +// +// The version number is in the most significant 4 bits of the time +// stamp (bits 4 through 7 of the time_hi_and_version field). +// +// The following table lists the currently-defined versions for this +// UUID variant. +// +// Msb0 Msb1 Msb2 Msb3 Version Description +// +// 0 0 0 1 1 The time-based version +// specified in this document. +// +// 0 0 1 0 2 DCE Security version, with +// embedded POSIX UIDs. +// +// 0 0 1 1 3 The name-based version +// specified in this document +// that uses MD5 hashing. +// +// 0 1 0 0 4 The randomly or pseudo- +// randomly generated version +// specified in this document. +// +// 0 1 0 1 5 The name-based version +// specified in this document +// that uses SHA-1 hashing. +std::optional random_uuid_v4(JSContext *cx) { + UUID id; + + { + auto res = host_api::Random::get_bytes(sizeof(id)); + if (auto *err = res.to_err()) { + HANDLE_ERROR(cx, *err); + return std::nullopt; + } + + auto bytes = std::move(res.unwrap()); + std::copy_n(bytes.begin(), sizeof(id), reinterpret_cast(&id)); + } + + // Set the two most significant bits (bits 6 and 7) of the clock_seq_hi_and_reserved to zero and + // one, respectively. + id.clock_seq_and_reserved &= 0x3f; + id.clock_seq_and_reserved |= 0x80; + // Set the four most significant bits (bits 12 through 15) of the time_hi_and_version field to the + // 4-bit version number from Section 4.1.3. + id.time_high_and_version &= 0x0fff; + id.time_high_and_version |= 0x4000; + + return fmt::format("{:08x}-{:04x}-{:04x}-{:02x}{:02x}-{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}", + id.time_low, id.time_mid, id.time_high_and_version, id.clock_seq_and_reserved, + id.clock_seq_low, id.node[0], id.node[1], id.node[2], id.node[3], id.node[4], + id.node[5]); +} + +} // namespace fastly::crypto diff --git a/runtime/fastly/builtins/crypto/uuid.h b/runtime/fastly/builtins/crypto/uuid.h new file mode 100644 index 0000000000..2d59f8f54d --- /dev/null +++ b/runtime/fastly/builtins/crypto/uuid.h @@ -0,0 +1,25 @@ +#ifndef BUILTINS_WEB_CRYPTO_UUID_H +#define BUILTINS_WEB_CRYPTO_UUID_H + +#include "builtin.h" + +namespace fastly::crypto { + +// FROM RFC 4122 +// The version 4 UUID is meant for generating UUIDs from truly-random or +// pseudo-random numbers. +// +// The algorithm is as follows: +// +// Set the two most significant bits (bits 6 and 7) of the clock_seq_hi_and_reserved to zero and +// one, respectively. +// +// Set the four most significant bits (bits 12 through 15) of the time_hi_and_version field to the +// 4-bit version number from Section 4.1.3. +// +// Set all the other bits to randomly (or pseudo-randomly) chosen values. +std::optional random_uuid_v4(JSContext *cx); + +} // namespace fastly::crypto + +#endif // BUILTINS_WEB_CRYPTO_UUID_H diff --git a/runtime/fastly/crates/crypto-rasn-wrapper/.gitignore b/runtime/fastly/crates/crypto-rasn-wrapper/.gitignore new file mode 100644 index 0000000000..c41cc9e35e --- /dev/null +++ b/runtime/fastly/crates/crypto-rasn-wrapper/.gitignore @@ -0,0 +1 @@ +/target \ No newline at end of file diff --git a/runtime/fastly/crates/crypto-rasn-wrapper/Cargo.lock b/runtime/fastly/crates/crypto-rasn-wrapper/Cargo.lock new file mode 100644 index 0000000000..a362eec944 --- /dev/null +++ b/runtime/fastly/crates/crypto-rasn-wrapper/Cargo.lock @@ -0,0 +1,788 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "anstyle" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" + +[[package]] +name = "anyhow" +version = "1.0.101" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f0e0fee31ef5ed1ba1316088939cea399010ed7731dba877ed44aeb407a75ea" + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "bitflags" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" + +[[package]] +name = "bitvec" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + +[[package]] +name = "bitvec-nom2" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d988fcc40055ceaa85edc55875a08f8abd29018582647fd82ad6128dba14a5f0" +dependencies = [ + "bitvec", + "nom", +] + +[[package]] +name = "bytes" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" + +[[package]] +name = "cc" +version = "1.2.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aebf35691d1bfb0ac386a69bac2fde4dd276fb618cf8bf4f5318fe285e821bb2" +dependencies = [ + "find-msvc-tools", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "chrono" +version = "0.4.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fac4744fb15ae8337dc853fee7fb3f4e48c0fbaa23d0afe49c447b4fab126118" +dependencies = [ + "num-traits", +] + +[[package]] +name = "clap" +version = "4.5.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2797f34da339ce31042b27d23607e051786132987f595b02ba4f6a6dffb7030a" +dependencies = [ + "clap_builder", +] + +[[package]] +name = "clap_builder" +version = "4.5.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24a241312cea5059b13574bb9b3861cabf758b879c15190b37b6d6fd63ab6876" +dependencies = [ + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_lex" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a822ea5bc7590f9d40f1ba12c0dc3c2760f3482c6984db1573ad11031420831" + +[[package]] +name = "codespan-reporting" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af491d569909a7e4dee0ad7db7f5341fef5c614d5b8ec8cf765732aba3cff681" +dependencies = [ + "serde", + "termcolor", + "unicode-width", +] + +[[package]] +name = "crypto-rasn-wrapper" +version = "0.1.0" +dependencies = [ + "cxx", + "cxx-build", + "rasn", +] + +[[package]] +name = "cxx" +version = "1.0.194" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "747d8437319e3a2f43d93b341c137927ca70c0f5dabeea7a005a73665e247c7e" +dependencies = [ + "cc", + "cxx-build", + "cxxbridge-cmd", + "cxxbridge-flags", + "cxxbridge-macro", + "foldhash 0.2.0", + "link-cplusplus", +] + +[[package]] +name = "cxx-build" +version = "1.0.194" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0f4697d190a142477b16aef7da8a99bfdc41e7e8b1687583c0d23a79c7afc1e" +dependencies = [ + "cc", + "codespan-reporting", + "indexmap", + "proc-macro2", + "quote", + "scratch", + "syn", +] + +[[package]] +name = "cxxbridge-cmd" +version = "1.0.194" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0956799fa8678d4c50eed028f2de1c0552ae183c76e976cf7ca8c4e36a7c328" +dependencies = [ + "clap", + "codespan-reporting", + "indexmap", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "cxxbridge-flags" +version = "1.0.194" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23384a836ab4f0ad98ace7e3955ad2de39de42378ab487dc28d3990392cb283a" + +[[package]] +name = "cxxbridge-macro" +version = "1.0.194" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6acc6b5822b9526adfb4fc377b67128fdd60aac757cc4a741a6278603f763cf" +dependencies = [ + "indexmap", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "find-msvc-tools" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "foldhash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" + +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + +[[package]] +name = "getrandom" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "139ef39800118c7683f2fd3c98c1b23c09ae076556b435f8e9064ae108aaeeec" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasip2", + "wasip3", +] + +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "foldhash 0.1.5", +] + +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "id-arena" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" + +[[package]] +name = "indexmap" +version = "2.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" +dependencies = [ + "equivalent", + "hashbrown 0.16.1", + "serde", + "serde_core", +] + +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" + +[[package]] +name = "leb128fmt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" + +[[package]] +name = "libc" +version = "0.2.182" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6800badb6cb2082ffd7b6a67e6125bb39f18782f793520caee8cb8846be06112" + +[[package]] +name = "link-cplusplus" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f78c730aaa7d0b9336a299029ea49f9ee53b0ed06e9202e8cb7db9bae7b8c82" +dependencies = [ + "cc", +] + +[[package]] +name = "log" +version = "0.4.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" + +[[package]] +name = "memchr" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn", +] + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + +[[package]] +name = "rasn" +version = "0.28.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe40c63064fb2b97594092d3ba4766778c3569424b2758c152df5492d4162aa1" +dependencies = [ + "bitvec", + "bitvec-nom2", + "bytes", + "cfg-if", + "chrono", + "either", + "nom", + "num-bigint", + "num-integer", + "num-traits", + "once_cell", + "rasn-derive", + "serde_json", + "snafu", + "xml-no-std", +] + +[[package]] +name = "rasn-derive" +version = "0.28.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90c1c5eb230cb591677030f8a610d10f21e8c3f84274c69e2b4840c74bef94f9" +dependencies = [ + "proc-macro2", + "rasn-derive-impl", + "syn", +] + +[[package]] +name = "rasn-derive-impl" +version = "0.28.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e5dad703153553d4c2fbd86a74e8d02943aa2d32b15699e2073de8f16bf4674" +dependencies = [ + "either", + "itertools", + "proc-macro2", + "quote", + "syn", + "uuid", +] + +[[package]] +name = "scratch" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d68f2ec51b097e4c1a75b681a8bec621909b5e91f15bb7b840c4f2f7b01148b2" + +[[package]] +name = "semver" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.149" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "snafu" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e84b3f4eacbf3a1ce05eac6763b4d629d60cbc94d632e4092c54ade71f1e1a2" +dependencies = [ + "snafu-derive", +] + +[[package]] +name = "snafu-derive" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1c97747dbf44bb1ca44a561ece23508e99cb592e862f22222dcf42f51d1e451" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "syn" +version = "2.0.116" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3df424c70518695237746f84cede799c9c58fcb37450d7b23716568cc8bc69cb" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + +[[package]] +name = "termcolor" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "unicode-width" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "uuid" +version = "1.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b672338555252d43fd2240c714dc444b8c6fb0a5c5335e65a07bba7742735ddb" +dependencies = [ + "getrandom", +] + +[[package]] +name = "wasip2" +version = "1.0.2+wasi-0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasip3" +version = "0.4.0+wasi-0.3.0-rc-2026-01-06" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasm-encoder" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" +dependencies = [ + "leb128fmt", + "wasmparser", +] + +[[package]] +name = "wasm-metadata" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" +dependencies = [ + "anyhow", + "indexmap", + "wasm-encoder", + "wasmparser", +] + +[[package]] +name = "wasmparser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" +dependencies = [ + "bitflags", + "hashbrown 0.15.5", + "indexmap", + "semver", +] + +[[package]] +name = "winapi-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "wit-bindgen" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" +dependencies = [ + "wit-bindgen-rust-macro", +] + +[[package]] +name = "wit-bindgen-core" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" +dependencies = [ + "anyhow", + "heck", + "wit-parser", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" +dependencies = [ + "anyhow", + "heck", + "indexmap", + "prettyplease", + "syn", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" +dependencies = [ + "anyhow", + "prettyplease", + "proc-macro2", + "quote", + "syn", + "wit-bindgen-core", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-component" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" +dependencies = [ + "anyhow", + "bitflags", + "indexmap", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder", + "wasm-metadata", + "wasmparser", + "wit-parser", +] + +[[package]] +name = "wit-parser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" +dependencies = [ + "anyhow", + "id-arena", + "indexmap", + "log", + "semver", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser", +] + +[[package]] +name = "wyz" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" +dependencies = [ + "tap", +] + +[[package]] +name = "xml-no-std" +version = "0.8.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd223bc94c615fc02bf2f4bbc22a4a9bfe489c2add3ec10b1038df3aca44cac7" + +[[package]] +name = "zmij" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" diff --git a/runtime/fastly/crates/crypto-rasn-wrapper/Cargo.toml b/runtime/fastly/crates/crypto-rasn-wrapper/Cargo.toml new file mode 100644 index 0000000000..802181b88c --- /dev/null +++ b/runtime/fastly/crates/crypto-rasn-wrapper/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "crypto-rasn-wrapper" +version = "0.1.0" +edition = "2024" + +[dependencies] +cxx = "1.0" +rasn = "=0.28.7" + +[build-dependencies] +cxx-build = "1.0" diff --git a/runtime/fastly/crates/crypto-rasn-wrapper/build.rs b/runtime/fastly/crates/crypto-rasn-wrapper/build.rs new file mode 100644 index 0000000000..7b904c51a4 --- /dev/null +++ b/runtime/fastly/crates/crypto-rasn-wrapper/build.rs @@ -0,0 +1,5 @@ +fn main() { + cxx_build::bridge("src/lib.rs") + .std("c++20") + .compile("crypto-rasn-wrapper"); +} diff --git a/runtime/fastly/crates/crypto-rasn-wrapper/src/generated.rs b/runtime/fastly/crates/crypto-rasn-wrapper/src/generated.rs new file mode 100644 index 0000000000..9e92031899 --- /dev/null +++ b/runtime/fastly/crates/crypto-rasn-wrapper/src/generated.rs @@ -0,0 +1,202 @@ +#[allow( + non_camel_case_types, + non_snake_case, + non_upper_case_globals, + unused, + clippy::too_many_arguments +)] +pub mod x509 { + extern crate alloc; + use core::borrow::Borrow; + use rasn::prelude::*; + use std::sync::LazyLock; + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + pub struct AlgorithmIdentifier { + pub algorithm: ObjectIdentifier, + pub parameters: Option, + } + impl AlgorithmIdentifier { + pub fn new(algorithm: ObjectIdentifier, parameters: Option) -> Self { + Self { + algorithm, + parameters, + } + } + } + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + pub struct Attribute { + #[rasn(identifier = "type")] + pub r_type: AttributeType, + pub values: SetOf, + } + impl Attribute { + pub fn new(r_type: AttributeType, values: SetOf) -> Self { + Self { r_type, values } + } + } + #[doc = " at least one value is required"] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(delegate)] + pub struct AttributeType(pub ObjectIdentifier); + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(delegate)] + pub struct AttributeValue(pub Any); + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(delegate)] + pub struct Attributes(pub SetOf); + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + pub struct Extension { + #[rasn(identifier = "extnID")] + pub extn_id: ObjectIdentifier, + #[rasn(default = "extension_critical_default")] + pub critical: bool, + #[rasn(identifier = "extnValue")] + pub extn_value: OctetString, + } + impl Extension { + pub fn new(extn_id: ObjectIdentifier, critical: bool, extn_value: OctetString) -> Self { + Self { + extn_id, + critical, + extn_value, + } + } + } + fn extension_critical_default() -> bool { + false + } + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(delegate, size("1.."))] + pub struct Extensions(pub SequenceOf); + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + pub struct OtherPrimeInfo { + pub prime: Integer, + pub exponent: Integer, + pub coefficient: Integer, + } + impl OtherPrimeInfo { + pub fn new(prime: Integer, exponent: Integer, coefficient: Integer) -> Self { + Self { + prime, + exponent, + coefficient, + } + } + } + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(delegate, size("1.."))] + pub struct OtherPrimeInfos(pub SequenceOf); + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(delegate)] + pub struct PrivateKey(pub OctetString); + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(delegate)] + pub struct PrivateKeyAlgorithmIdentifier(pub AlgorithmIdentifier); + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + pub struct PrivateKeyInfo { + pub version: Version, + #[rasn(identifier = "privateKeyAlgorithm")] + pub private_key_algorithm: PrivateKeyAlgorithmIdentifier, + #[rasn(identifier = "privateKey")] + pub private_key: PrivateKey, + #[rasn(tag(context, 0))] + pub attributes: Option, + } + impl PrivateKeyInfo { + pub fn new( + version: Version, + private_key_algorithm: PrivateKeyAlgorithmIdentifier, + private_key: PrivateKey, + attributes: Option, + ) -> Self { + Self { + version, + private_key_algorithm, + private_key, + attributes, + } + } + } + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + pub struct RSAPrivateKey { + pub version: Version, + pub modulus: Integer, + #[rasn(identifier = "publicExponent")] + pub public_exponent: Integer, + #[rasn(identifier = "privateExponent")] + pub private_exponent: Integer, + pub prime1: Integer, + pub prime2: Integer, + pub exponent1: Integer, + pub exponent2: Integer, + pub coefficient: Integer, + #[rasn(identifier = "otherPrimeInfos")] + pub other_prime_infos: Option, + } + impl RSAPrivateKey { + pub fn new( + version: Version, + modulus: Integer, + public_exponent: Integer, + private_exponent: Integer, + prime1: Integer, + prime2: Integer, + exponent1: Integer, + exponent2: Integer, + coefficient: Integer, + other_prime_infos: Option, + ) -> Self { + Self { + version, + modulus, + public_exponent, + private_exponent, + prime1, + prime2, + exponent1, + exponent2, + coefficient, + other_prime_infos, + } + } + } + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + pub struct RSAPublicKey { + pub modulus: Integer, + #[rasn(identifier = "publicExponent")] + pub public_exponent: Integer, + } + impl RSAPublicKey { + pub fn new(modulus: Integer, public_exponent: Integer) -> Self { + Self { + modulus, + public_exponent, + } + } + } + #[doc = " DEFINED BY AttributeType"] + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + pub struct SubjectPublicKeyInfo { + pub algorithm: AlgorithmIdentifier, + #[rasn(identifier = "subjectPublicKey")] + pub subject_public_key: BitString, + } + impl SubjectPublicKeyInfo { + pub fn new(algorithm: AlgorithmIdentifier, subject_public_key: BitString) -> Self { + Self { + algorithm, + subject_public_key, + } + } + } + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)] + #[rasn(delegate)] + pub struct Version(pub Integer); + pub static PKCS_1: LazyLock = + LazyLock::new(|| Oid::const_new(&[1u32, 2u32, 840u32, 113549u32, 1u32, 1u32]).to_owned()); + pub static RSA_ENCRYPTION: LazyLock = LazyLock::new(|| { + Oid::new(&[&***PKCS_1, &[1u32]].concat()) + .unwrap() + .to_owned() + }); +} diff --git a/runtime/fastly/crates/crypto-rasn-wrapper/src/lib.rs b/runtime/fastly/crates/crypto-rasn-wrapper/src/lib.rs new file mode 100644 index 0000000000..518e680d37 --- /dev/null +++ b/runtime/fastly/crates/crypto-rasn-wrapper/src/lib.rs @@ -0,0 +1,113 @@ +use std::pin::Pin; + +use cxx::{CxxString, CxxVector}; + +mod generated; + +use generated::x509::*; + +pub fn decode_spki( + data: &CxxVector, + mut out: Pin<&mut *mut SubjectPublicKeyInfo>, + err: Pin<&mut CxxString>, +) -> bool { + match rasn::der::decode(data.as_slice()) { + Ok(spki) => { + out.set(Box::into_raw(Box::new(spki))); + true + } + Err(e) => { + err.push_bytes(format!("{e}").as_ref()); + false + } + } +} + +pub fn decode_pkcs8( + data: &CxxVector, + mut out: Pin<&mut *mut PrivateKeyInfo>, + err: Pin<&mut CxxString>, +) -> bool { + match rasn::der::decode(data.as_slice()) { + Ok(pkcs8) => { + out.set(Box::into_raw(Box::new(pkcs8))); + true + } + Err(e) => { + err.push_bytes(format!("{e}").as_ref()); + false + } + } +} + +impl SubjectPublicKeyInfo { + pub fn is_rsa(&self) -> bool { + self.algorithm.algorithm == *RSA_ENCRYPTION + } + + pub fn decode_public_key( + &self, + mut out: Pin<&mut *mut RSAPublicKey>, + err: Pin<&mut CxxString>, + ) -> bool { + match rasn::der::decode(self.subject_public_key.as_raw_slice()) { + Ok(pubkey) => { + out.set(Box::into_raw(Box::new(pubkey))); + true + } + Err(e) => { + err.push_bytes(format!("{e}").as_ref()); + false + } + } + } +} + +impl PrivateKeyInfo { + pub fn is_rsa(&self) -> bool { + self.private_key_algorithm.0.algorithm == *RSA_ENCRYPTION + } +} + +impl RSAPublicKey { + pub fn details(&self, modulus: Pin<&mut CxxString>, exponent: Pin<&mut CxxString>) { + modulus.push_bytes(format!("{}", self.modulus).as_ref()); + exponent.push_bytes(format!("{}", self.public_exponent).as_ref()); + } +} + +#[cxx::bridge] +mod ffi { + #[namespace = "fastly::sys::asn"] + extern "Rust" { + type SubjectPublicKeyInfo; + fn is_rsa(&self) -> bool; + fn decode_public_key( + &self, + mut out: Pin<&mut *mut RSAPublicKey>, + err: Pin<&mut CxxString>, + ) -> bool; + fn decode_spki( + data: &CxxVector, + out: Pin<&mut *mut SubjectPublicKeyInfo>, + err: Pin<&mut CxxString>, + ) -> bool; + } + + #[namespace = "fastly::sys::asn"] + extern "Rust" { + type PrivateKeyInfo; + fn is_rsa(&self) -> bool; + fn decode_pkcs8( + data: &CxxVector, + out: Pin<&mut *mut PrivateKeyInfo>, + err: Pin<&mut CxxString>, + ) -> bool; + } + + #[namespace = "fastly::sys::asn"] + extern "Rust" { + type RSAPublicKey; + fn details(&self, modulus: Pin<&mut CxxString>, exponent: Pin<&mut CxxString>); + } +} diff --git a/runtime/fastly/crates/crypto-rasn-wrapper/src/subtle-crypto.asn1 b/runtime/fastly/crates/crypto-rasn-wrapper/src/subtle-crypto.asn1 new file mode 100644 index 0000000000..4225dc5d09 --- /dev/null +++ b/runtime/fastly/crates/crypto-rasn-wrapper/src/subtle-crypto.asn1 @@ -0,0 +1,75 @@ +X509 DEFINITIONS ::= + +BEGIN + +Attribute ::= SEQUENCE { + type AttributeType, + values SET OF AttributeValue } + -- at least one value is required +AttributeType ::= OBJECT IDENTIFIER + +AttributeValue ::= ANY -- DEFINED BY AttributeType + SubjectPublicKeyInfo ::= SEQUENCE { + algorithm AlgorithmIdentifier, + subjectPublicKey BIT STRING } + + Extensions ::= SEQUENCE SIZE (1..MAX) OF Extension + + Extension ::= SEQUENCE { + extnID OBJECT IDENTIFIER, + critical BOOLEAN DEFAULT FALSE, + extnValue OCTET STRING + -- contains the DER encoding of an ASN.1 value + -- corresponding to the extension type identified + -- by extnID + } + + AlgorithmIdentifier ::= SEQUENCE { + algorithm OBJECT IDENTIFIER, + parameters ANY DEFINED BY algorithm OPTIONAL } + + RSAPublicKey ::= SEQUENCE { + modulus INTEGER, -- n + publicExponent INTEGER -- e + } + + RSAPrivateKey ::= SEQUENCE { + version Version, + modulus INTEGER, -- n + publicExponent INTEGER, -- e + privateExponent INTEGER, -- d + prime1 INTEGER, -- p + prime2 INTEGER, -- q + exponent1 INTEGER, -- d mod (p-1) + exponent2 INTEGER, -- d mod (q-1) + coefficient INTEGER, -- (inverse of q) mod p + otherPrimeInfos OtherPrimeInfos OPTIONAL + } + OtherPrimeInfos ::= SEQUENCE SIZE(1..MAX) OF OtherPrimeInfo + + OtherPrimeInfo ::= SEQUENCE { + prime INTEGER, -- ri + exponent INTEGER, -- di + coefficient INTEGER -- ti + } + + PrivateKeyInfo ::= SEQUENCE { + version Version, + privateKeyAlgorithm PrivateKeyAlgorithmIdentifier, + privateKey PrivateKey, + attributes [0] IMPLICIT Attributes OPTIONAL } + + Version ::= INTEGER + + PrivateKeyAlgorithmIdentifier ::= AlgorithmIdentifier + + PrivateKey ::= OCTET STRING + + Attributes ::= SET OF Attribute + + pkcs-1 OBJECT IDENTIFIER ::= { + iso(1) member-body(2) us(840) rsadsi(113549) pkcs(1) 1 +} + + rsaEncryption OBJECT IDENTIFIER ::= { pkcs-1 1 } +END diff --git a/runtime/rust-toolchain.toml b/runtime/rust-toolchain.toml index 013ea233a5..ffbda9ca9a 100644 --- a/runtime/rust-toolchain.toml +++ b/runtime/rust-toolchain.toml @@ -1,4 +1,4 @@ [toolchain] -channel = "1.81.0" +channel = "1.93.1" targets = ["wasm32-wasip1"] profile = "minimal"