aboutsummaryrefslogtreecommitdiff
path: root/src/bun.js/bindings/webcrypto/CryptoKeyRSAOpenSSL.cpp
diff options
context:
space:
mode:
authorGravatar Jarred Sumner <jarred@jarredsumner.com> 2022-10-23 20:25:18 -0700
committerGravatar GitHub <noreply@github.com> 2022-10-23 20:25:18 -0700
commit76652ac3cad64dbc2fd54e976ce4bad0a37caa03 (patch)
tree179865bc417dc6bf2f224dd310b77b931ee45c73 /src/bun.js/bindings/webcrypto/CryptoKeyRSAOpenSSL.cpp
parent14cec299f5170b8ed35eed28e53b88724b8cc04f (diff)
downloadbun-76652ac3cad64dbc2fd54e976ce4bad0a37caa03.tar.gz
bun-76652ac3cad64dbc2fd54e976ce4bad0a37caa03.tar.zst
bun-76652ac3cad64dbc2fd54e976ce4bad0a37caa03.zip
Add Web Crypto API (#1384)
* Add Web Crypto API * Duplicate symbols * Update c_cpp_properties.json Co-authored-by: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com>
Diffstat (limited to 'src/bun.js/bindings/webcrypto/CryptoKeyRSAOpenSSL.cpp')
-rw-r--r--src/bun.js/bindings/webcrypto/CryptoKeyRSAOpenSSL.cpp408
1 files changed, 408 insertions, 0 deletions
diff --git a/src/bun.js/bindings/webcrypto/CryptoKeyRSAOpenSSL.cpp b/src/bun.js/bindings/webcrypto/CryptoKeyRSAOpenSSL.cpp
new file mode 100644
index 000000000..ef4794e36
--- /dev/null
+++ b/src/bun.js/bindings/webcrypto/CryptoKeyRSAOpenSSL.cpp
@@ -0,0 +1,408 @@
+/*
+ * Copyright (C) 2021 Sony Interactive Entertainment Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "config.h"
+#include "CryptoKeyRSA.h"
+
+#if ENABLE(WEB_CRYPTO)
+
+#include "CryptoAlgorithmRegistry.h"
+#include "CryptoKeyPair.h"
+#include "CryptoKeyRSAComponents.h"
+#include "OpenSSLUtilities.h"
+#include <JavaScriptCore/TypedArrayInlines.h>
+#include <openssl/x509.h>
+#include <openssl/evp.h>
+
+namespace WebCore {
+
+static size_t getRSAModulusLength(RSA* rsa)
+{
+ if (!rsa)
+ return 0;
+ return RSA_size(rsa) * 8;
+}
+
+RefPtr<CryptoKeyRSA> CryptoKeyRSA::create(CryptoAlgorithmIdentifier identifier, CryptoAlgorithmIdentifier hash, bool hasHash, const CryptoKeyRSAComponents& keyData, bool extractable, CryptoKeyUsageBitmap usages)
+{
+ CryptoKeyType keyType;
+ switch (keyData.type()) {
+ case CryptoKeyRSAComponents::Type::Public:
+ keyType = CryptoKeyType::Public;
+ break;
+ case CryptoKeyRSAComponents::Type::Private:
+ keyType = CryptoKeyType::Private;
+ break;
+ default:
+ 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().isEmpty())
+ return nullptr;
+
+ // For both public and private keys, we need the public modulus and exponent.
+ if (keyData.modulus().isEmpty() || keyData.exponent().isEmpty())
+ 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().isEmpty() || keyData.firstPrimeInfo().primeFactor.isEmpty() || keyData.secondPrimeInfo().primeFactor.isEmpty())
+ return nullptr;
+ }
+
+ auto rsa = RSAPtr(RSA_new());
+ 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
+ if (!RSA_set0_key(rsa.get(), n.get(), e.get(), nullptr))
+ return nullptr;
+
+ // Ownership transferred to OpenSSL
+ n.release();
+ e.release();
+
+ 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
+ if (!RSA_set0_key(rsa.get(), nullptr, nullptr, d.get()))
+ return nullptr;
+
+ // Ownership transferred to OpenSSL
+ d.release();
+
+ auto p = convertToBigNumber(keyData.firstPrimeInfo().primeFactor);
+ auto q = convertToBigNumber(keyData.secondPrimeInfo().primeFactor);
+ if (!p || !q)
+ return nullptr;
+
+ if (!RSA_set0_factors(rsa.get(), p.get(), q.get()))
+ return nullptr;
+
+ // Ownership transferred to OpenSSL
+ p.release();
+ q.release();
+
+ // 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.isEmpty()) ? convertToBigNumber(keyData.firstPrimeInfo().factorCRTExponent) : nullptr;
+ // dmq1 -- d mod (q - 1)
+ auto dmq1 = (!keyData.secondPrimeInfo().factorCRTExponent.isEmpty()) ? convertToBigNumber(keyData.secondPrimeInfo().factorCRTExponent) : nullptr;
+ // iqmp -- q^(-1) mod p
+ auto iqmp = (!keyData.secondPrimeInfo().factorCRTCoefficient.isEmpty()) ? convertToBigNumber(keyData.secondPrimeInfo().factorCRTCoefficient) : nullptr;
+
+ if (!RSA_set0_crt_params(rsa.get(), dmp1.get(), dmq1.get(), iqmp.get()))
+ return nullptr;
+
+ // Ownership transferred to OpenSSL
+ dmp1.release();
+ dmq1.release();
+ iqmp.release();
+ }
+
+ auto pkey = EvpPKeyPtr(EVP_PKEY_new());
+ if (!pkey)
+ return nullptr;
+
+ if (EVP_PKEY_set1_RSA(pkey.get(), rsa.get()) != 1)
+ return nullptr;
+
+ return adoptRef(new CryptoKeyRSA(identifier, hash, hasHash, keyType, WTFMove(pkey), extractable, usages));
+}
+
+CryptoKeyRSA::CryptoKeyRSA(CryptoAlgorithmIdentifier identifier, CryptoAlgorithmIdentifier hash, bool hasHash, CryptoKeyType type, PlatformRSAKeyContainer&& platformKey, bool extractable, CryptoKeyUsageBitmap usages)
+ : CryptoKey(identifier, type, extractable, usages)
+ , m_platformKey(WTFMove(platformKey))
+ , m_restrictedToSpecificHash(hasHash)
+ , m_hash(hash)
+{
+}
+
+bool CryptoKeyRSA::isRestrictedToHash(CryptoAlgorithmIdentifier& identifier) const
+{
+ if (!m_restrictedToSpecificHash)
+ return false;
+
+ identifier = m_hash;
+ return true;
+}
+
+size_t CryptoKeyRSA::keySizeInBits() const
+{
+ RSA* rsa = EVP_PKEY_get0_RSA(m_platformKey.get());
+ if (!rsa)
+ return 0;
+
+ return getRSAModulusLength(rsa);
+}
+
+// Convert the exponent vector to a 32-bit value, if possible.
+static std::optional<uint32_t> exponentVectorToUInt32(const Vector<uint8_t>& exponent)
+{
+ if (exponent.size() > 4) {
+ if (std::any_of(exponent.begin(), exponent.end() - 4, [](uint8_t element) { return !!element; }))
+ return std::nullopt;
+ }
+
+ uint32_t result = 0;
+ for (size_t size = exponent.size(), i = std::min<size_t>(4, size); i > 0; --i) {
+ result <<= 8;
+ result += exponent[size - i];
+ }
+
+ return result;
+}
+
+void CryptoKeyRSA::generatePair(CryptoAlgorithmIdentifier algorithm, CryptoAlgorithmIdentifier hash, bool hasHash, unsigned modulusLength, const Vector<uint8_t>& publicExponent, bool extractable, CryptoKeyUsageBitmap usages, KeyPairCallback&& callback, VoidCallback&& failureCallback, ScriptExecutionContext*)
+{
+ // OpenSSL doesn't report an error if the exponent is smaller than three or even.
+ auto e = exponentVectorToUInt32(publicExponent);
+ if (!e || *e < 3 || !(*e & 0x1)) {
+ failureCallback();
+ return;
+ }
+
+ auto exponent = convertToBigNumber(publicExponent);
+ auto privateRSA = RSAPtr(RSA_new());
+ if (!exponent || RSA_generate_key_ex(privateRSA.get(), modulusLength, exponent.get(), nullptr) <= 0) {
+ failureCallback();
+ return;
+ }
+
+ auto publicRSA = RSAPtr(RSAPublicKey_dup(privateRSA.get()));
+ if (!publicRSA) {
+ failureCallback();
+ return;
+ }
+
+ auto privatePKey = EvpPKeyPtr(EVP_PKEY_new());
+ if (EVP_PKEY_set1_RSA(privatePKey.get(), privateRSA.get()) <= 0) {
+ failureCallback();
+ return;
+ }
+
+ auto publicPKey = EvpPKeyPtr(EVP_PKEY_new());
+ if (EVP_PKEY_set1_RSA(publicPKey.get(), publicRSA.get()) <= 0) {
+ failureCallback();
+ return;
+ }
+
+ auto publicKey = CryptoKeyRSA::create(algorithm, hash, hasHash, CryptoKeyType::Public, WTFMove(publicPKey), true, usages);
+ auto privateKey = CryptoKeyRSA::create(algorithm, hash, hasHash, CryptoKeyType::Private, WTFMove(privatePKey), extractable, usages);
+ callback(CryptoKeyPair { WTFMove(publicKey), WTFMove(privateKey) });
+}
+
+RefPtr<CryptoKeyRSA> CryptoKeyRSA::importSpki(CryptoAlgorithmIdentifier identifier, std::optional<CryptoAlgorithmIdentifier> hash, Vector<uint8_t>&& keyData, bool extractable, CryptoKeyUsageBitmap usages)
+{
+ // We need a local pointer variable to pass to d2i (DER to internal) functions().
+ const uint8_t* ptr = keyData.data();
+
+ // We use d2i_PUBKEY() to import a public key.
+ auto pkey = EvpPKeyPtr(d2i_PUBKEY(nullptr, &ptr, keyData.size()));
+ if (!pkey || EVP_PKEY_id(pkey.get()) != EVP_PKEY_RSA)
+ return nullptr;
+
+ return adoptRef(new CryptoKeyRSA(identifier, hash.value_or(CryptoAlgorithmIdentifier::SHA_1), !!hash, CryptoKeyType::Public, WTFMove(pkey), extractable, usages));
+}
+
+RefPtr<CryptoKeyRSA> CryptoKeyRSA::importPkcs8(CryptoAlgorithmIdentifier identifier, std::optional<CryptoAlgorithmIdentifier> hash, Vector<uint8_t>&& keyData, bool extractable, CryptoKeyUsageBitmap usages)
+{
+ // We need a local pointer variable to pass to d2i (DER to internal) functions().
+ const uint8_t* ptr = keyData.data();
+
+ // We use d2i_PKCS8_PRIV_KEY_INFO() to import a private key.
+ auto p8inf = PKCS8PrivKeyInfoPtr(d2i_PKCS8_PRIV_KEY_INFO(nullptr, &ptr, keyData.size()));
+ if (!p8inf)
+ return nullptr;
+
+ auto pkey = EvpPKeyPtr(EVP_PKCS82PKEY(p8inf.get()));
+ if (!pkey || EVP_PKEY_id(pkey.get()) != EVP_PKEY_RSA)
+ return nullptr;
+
+ return adoptRef(new CryptoKeyRSA(identifier, hash.value_or(CryptoAlgorithmIdentifier::SHA_1), !!hash, CryptoKeyType::Private, WTFMove(pkey), extractable, usages));
+}
+
+ExceptionOr<Vector<uint8_t>> CryptoKeyRSA::exportSpki() const
+{
+ if (type() != CryptoKeyType::Public)
+ return Exception { InvalidAccessError };
+
+ int len = i2d_PUBKEY(platformKey(), nullptr);
+ if (len < 0)
+ return Exception { OperationError };
+
+ Vector<uint8_t> keyData(len);
+ auto ptr = keyData.data();
+ if (i2d_PUBKEY(platformKey(), &ptr) < 0)
+ return Exception { OperationError };
+
+ return keyData;
+}
+
+ExceptionOr<Vector<uint8_t>> CryptoKeyRSA::exportPkcs8() const
+{
+ if (type() != CryptoKeyType::Private)
+ return Exception { InvalidAccessError };
+
+ auto p8inf = PKCS8PrivKeyInfoPtr(EVP_PKEY2PKCS8(platformKey()));
+ if (!p8inf)
+ return Exception { OperationError };
+
+ int len = i2d_PKCS8_PRIV_KEY_INFO(p8inf.get(), nullptr);
+ if (len < 0)
+ return Exception { OperationError };
+
+ Vector<uint8_t> keyData(len);
+ auto ptr = keyData.data();
+ if (i2d_PKCS8_PRIV_KEY_INFO(p8inf.get(), &ptr) < 0)
+ return Exception { OperationError };
+
+ return keyData;
+}
+
+auto CryptoKeyRSA::algorithm() const -> KeyAlgorithm
+{
+ RSA* rsa = EVP_PKEY_get0_RSA(platformKey());
+
+ auto modulusLength = getRSAModulusLength(rsa);
+ Vector<uint8_t> publicExponent;
+
+ if (rsa) {
+ const BIGNUM* e;
+ RSA_get0_key(rsa, nullptr, &e, nullptr);
+ publicExponent = convertToBytes(e);
+ }
+
+ if (m_restrictedToSpecificHash) {
+ CryptoRsaHashedKeyAlgorithm result;
+ result.name = CryptoAlgorithmRegistry::singleton().name(algorithmIdentifier());
+ result.modulusLength = modulusLength;
+ result.publicExponent = Uint8Array::tryCreate(publicExponent.data(), publicExponent.size());
+ result.hash.name = CryptoAlgorithmRegistry::singleton().name(m_hash);
+ return result;
+ }
+
+ CryptoRsaKeyAlgorithm result;
+ result.name = CryptoAlgorithmRegistry::singleton().name(algorithmIdentifier());
+ result.modulusLength = modulusLength;
+ result.publicExponent = Uint8Array::tryCreate(publicExponent.data(), publicExponent.size());
+ return result;
+}
+
+std::unique_ptr<CryptoKeyRSAComponents> CryptoKeyRSA::exportData() const
+{
+ RSA* rsa = EVP_PKEY_get0_RSA(platformKey());
+ if (!rsa)
+ return nullptr;
+
+ const BIGNUM* n;
+ const BIGNUM* e;
+ const BIGNUM* d;
+ RSA_get0_key(rsa, &n, &e, &d);
+
+ switch (type()) {
+ case CryptoKeyType::Public:
+ // We need the public modulus and exponent for the public key.
+ if (!n || !e)
+ return nullptr;
+ return CryptoKeyRSAComponents::createPublic(convertToBytes(n), convertToBytes(e));
+ case CryptoKeyType::Private: {
+ // We need the public modulus, exponent, and private exponent, as well as p and q prime information.
+ const BIGNUM* p;
+ const BIGNUM* q;
+ RSA_get0_factors(rsa, &p, &q);
+
+ if (!n || !e || !d || !p || !q)
+ return nullptr;
+
+ CryptoKeyRSAComponents::PrimeInfo firstPrimeInfo;
+ firstPrimeInfo.primeFactor = convertToBytes(p);
+
+ CryptoKeyRSAComponents::PrimeInfo secondPrimeInfo;
+ secondPrimeInfo.primeFactor = convertToBytes(q);
+
+ auto context = BNCtxPtr(BN_CTX_new());
+
+ const BIGNUM* dmp1;
+ const BIGNUM* dmq1;
+ const BIGNUM* iqmp;
+ RSA_get0_crt_params(rsa, &dmp1, &dmq1, &iqmp);
+
+ // dmp1 -- d mod (p - 1)
+ if (dmp1)
+ firstPrimeInfo.factorCRTExponent = convertToBytes(dmp1);
+ else {
+ auto dmp1New = BIGNUMPtr(BN_new());
+ auto pm1 = BIGNUMPtr(BN_dup(p));
+ if (BN_sub_word(pm1.get(), 1) == 1 && BN_mod(dmp1New.get(), d, pm1.get(), context.get()) == 1)
+ firstPrimeInfo.factorCRTExponent = convertToBytes(dmp1New.get());
+ }
+
+ // dmq1 -- d mod (q - 1)
+ if (dmq1)
+ secondPrimeInfo.factorCRTExponent = convertToBytes(dmq1);
+ else {
+ auto dmq1New = BIGNUMPtr(BN_new());
+ auto qm1 = BIGNUMPtr(BN_dup(q));
+ if (BN_sub_word(qm1.get(), 1) == 1 && BN_mod(dmq1New.get(), d, qm1.get(), context.get()) == 1)
+ secondPrimeInfo.factorCRTExponent = convertToBytes(dmq1New.get());
+ }
+
+ // iqmp -- q^(-1) mod p
+ if (iqmp)
+ secondPrimeInfo.factorCRTCoefficient = convertToBytes(iqmp);
+ else {
+ auto iqmpNew = BIGNUMPtr(BN_mod_inverse(nullptr, q, p, context.get()));
+ if (iqmpNew)
+ secondPrimeInfo.factorCRTCoefficient = convertToBytes(iqmpNew.get());
+ }
+
+ return CryptoKeyRSAComponents::createPrivateWithAdditionalData(
+ convertToBytes(n), convertToBytes(e), convertToBytes(d),
+ WTFMove(firstPrimeInfo), WTFMove(secondPrimeInfo), Vector<CryptoKeyRSAComponents::PrimeInfo> { });
+ }
+ default:
+ ASSERT_NOT_REACHED();
+ return nullptr;
+ }
+}
+
+} // namespace WebCore
+
+#endif // ENABLE(WEB_CRYPTO)