diff --git a/src/bun.js/bindings/KeyObject.cpp b/src/bun.js/bindings/KeyObject.cpp index 10c4052a79..8fdeedbff2 100644 --- a/src/bun.js/bindings/KeyObject.cpp +++ b/src/bun.js/bindings/KeyObject.cpp @@ -128,15 +128,49 @@ struct AsymmetricKeyValueWithDER { long der_len; }; -struct PrivateKeyPassphrase { +class KeyPassphrase { +private: + WTF::CString pass_holder; + +public: char* passphrase; size_t passphrase_len; + bool hasPassphrase; + KeyPassphrase(JSValue passphraseJSValue, JSC::JSGlobalObject* globalObject, JSC::ThrowScope& scope) + { + this->passphrase = nullptr; + this->passphrase_len = 0; + if (passphraseJSValue.isUndefinedOrNull() || passphraseJSValue.isEmpty()) { + this->hasPassphrase = false; + return; + } + this->hasPassphrase = true; + if (passphraseJSValue.isString()) { + auto passphrase_wtfstr = passphraseJSValue.toWTFString(globalObject); + RETURN_IF_EXCEPTION(scope, ); + if (!passphrase_wtfstr.isNull()) { + if (auto pass = passphrase_wtfstr.tryGetUTF8()) { + if (pass.has_value()) { + auto value = pass.value(); + this->passphrase = const_cast(value.data()); + this->passphrase_len = value.length(); + pass_holder = value; + } + } + } + } else if (auto* passphraseBuffer = jsDynamicCast(passphraseJSValue)) { + this->passphrase = (char*)passphraseBuffer->vector(); + this->passphrase_len = passphraseBuffer->byteLength(); + } else { + JSC::throwTypeError(globalObject, scope, "passphrase must be a Buffer or String"_s); + } + } }; int PasswordCallback(char* buf, int size, int rwflag, void* u) { - auto result = static_cast(u); - if (result != nullptr && size > 0 && result->passphrase != nullptr) { + auto result = static_cast(u); + if (result->hasPassphrase && result != nullptr && size > 0 && result->passphrase != nullptr) { size_t buflen = static_cast(size); size_t len = result->passphrase_len; if (buflen < len) @@ -367,31 +401,8 @@ JSC::EncodedJSValue KeyObject__createPrivateKey(JSC::JSGlobalObject* globalObjec } JSValue passphraseJSValue = options->getIfPropertyExists(globalObject, PropertyName(Identifier::fromString(vm, "passphrase"_s))); - PrivateKeyPassphrase passphrase = { nullptr, 0 }; - - auto hasPassphrase = !passphraseJSValue.isUndefinedOrNull() && !passphraseJSValue.isEmpty(); - - if (hasPassphrase) { - if (passphraseJSValue.isString()) { - auto passphrase_wtfstr = passphraseJSValue.toWTFString(globalObject); - RETURN_IF_EXCEPTION(scope, encodedJSValue()); - if (!passphrase_wtfstr.isNull()) { - if (auto pass = passphrase_wtfstr.tryGetUTF8()) { - if (pass.has_value()) { - auto value = pass.value(); - passphrase.passphrase = const_cast(value.data()); - passphrase.passphrase_len = value.length(); - } - } - } - } else if (auto* passphraseBuffer = jsDynamicCast(passphraseJSValue)) { - passphrase.passphrase = (char*)passphraseBuffer->vector(); - passphrase.passphrase_len = passphraseBuffer->byteLength(); - } else { - JSC::throwTypeError(globalObject, scope, "passphrase must be a Buffer or String"_s); - return JSC::JSValue::encode(JSC::JSValue {}); - } - } + KeyPassphrase passphrase(passphraseJSValue, globalObject, scope); + RETURN_IF_EXCEPTION(scope, encodedJSValue()); if (format == "pem"_s) { auto bio = BIOPtr(BIO_new_mem_buf(const_cast((char*)data), byteLength)); @@ -952,32 +963,8 @@ JSC::EncodedJSValue KeyObject__createPublicKey(JSC::JSGlobalObject* globalObject // maybe is a private pem auto bio = BIOPtr(BIO_new_mem_buf(const_cast((char*)data), byteLength)); JSValue passphraseJSValue = options->getIfPropertyExists(globalObject, PropertyName(Identifier::fromString(vm, "passphrase"_s))); - PrivateKeyPassphrase passphrase = { nullptr, 0 }; - - auto hasPassphrase = !passphraseJSValue.isUndefinedOrNull() && !passphraseJSValue.isEmpty(); - - if (hasPassphrase) { - if (passphraseJSValue.isString()) { - auto passphrase_wtfstr = passphraseJSValue.toWTFString(globalObject); - RETURN_IF_EXCEPTION(scope, encodedJSValue()); - if (!passphrase_wtfstr.isNull()) { - if (auto pass = passphrase_wtfstr.tryGetUTF8()) { - if (pass.has_value()) { - auto value = pass.value(); - passphrase.passphrase = const_cast(value.data()); - passphrase.passphrase_len = value.length(); - } - } - } - } else if (auto* passphraseBuffer = jsDynamicCast(passphraseJSValue)) { - passphrase.passphrase = (char*)passphraseBuffer->vector(); - passphrase.passphrase_len = passphraseBuffer->byteLength(); - } else { - JSC::throwTypeError(globalObject, scope, "passphrase must be a Buffer or String"_s); - return JSC::JSValue::encode(JSC::JSValue {}); - } - } - + KeyPassphrase passphrase(passphraseJSValue, globalObject, scope); + RETURN_IF_EXCEPTION(scope, encodedJSValue()); auto pkey = EvpPKeyPtr(PEM_read_bio_PrivateKey(bio.get(), nullptr, PasswordCallback, &passphrase)); if (!pkey) { throwException(globalObject, scope, createTypeError(globalObject, "Invalid PEM data"_s)); @@ -1740,7 +1727,8 @@ JSC::EncodedJSValue KeyObject__Exports(JSC::JSGlobalObject* globalObject, JSC::C JSValue formatJSValue = options->getIfPropertyExists(globalObject, PropertyName(Identifier::fromString(vm, "format"_s))); JSValue typeJSValue = options->getIfPropertyExists(globalObject, PropertyName(Identifier::fromString(vm, "type"_s))); JSValue passphraseJSValue = options->getIfPropertyExists(globalObject, PropertyName(Identifier::fromString(vm, "passphrase"_s))); - auto hasPassphrase = !passphraseJSValue.isUndefinedOrNull() && !passphraseJSValue.isEmpty(); + KeyPassphrase passphrase(passphraseJSValue, globalObject, scope); + RETURN_IF_EXCEPTION(scope, encodedJSValue()); if (formatJSValue.isUndefinedOrNull() || formatJSValue.isEmpty()) { JSC::throwTypeError(globalObject, scope, "format is expected to be a string"_s); return JSC::JSValue::encode(JSC::JSValue {}); @@ -1748,7 +1736,7 @@ JSC::EncodedJSValue KeyObject__Exports(JSC::JSGlobalObject* globalObject, JSC::C auto string = formatJSValue.toWTFString(globalObject); RETURN_IF_EXCEPTION(scope, encodedJSValue()); - if (string == "jwk"_s && hasPassphrase) { + if (string == "jwk"_s && passphrase.hasPassphrase) { JSC::throwTypeError(globalObject, scope, "encryption is not supported for jwk format"_s); return JSC::JSValue::encode(JSC::JSValue {}); } @@ -1879,45 +1867,23 @@ JSC::EncodedJSValue KeyObject__Exports(JSC::JSGlobalObject* globalObject, JSC::C } } } - void* passphrase = nullptr; - size_t passphrase_len = 0; - if (hasPassphrase) { + if (passphrase.hasPassphrase) { if (!cipher) { JSC::throwTypeError(globalObject, scope, "cipher is required when passphrase is specified"_s); BIO_free(bio); return JSC::JSValue::encode(JSC::JSValue {}); } - if (passphraseJSValue.isString()) { - auto passphrase_wtfstr = passphraseJSValue.toWTFString(globalObject); - RETURN_IF_EXCEPTION(scope, encodedJSValue()); - if (!passphrase_wtfstr.isNull()) { - if (auto pass = passphrase_wtfstr.tryGetUTF8()) { - if (pass.has_value()) { - auto value = pass.value(); - passphrase = const_cast(value.data()); - passphrase_len = value.length(); - } - } - } - } else if (auto* passphraseBuffer = jsDynamicCast(passphraseJSValue)) { - passphrase = passphraseBuffer->vector(); - passphrase_len = passphraseBuffer->byteLength(); - } else { - JSC::throwTypeError(globalObject, scope, "passphrase must be a Buffer or String"_s); - BIO_free(bio); - return JSC::JSValue::encode(JSC::JSValue {}); - } } if (string == "pem"_s) { if (type == "pkcs1"_s) { - if (PEM_write_bio_RSAPrivateKey(bio, rsa_ptr, cipher, (unsigned char*)passphrase, passphrase_len, nullptr, nullptr) != 1) { + if (PEM_write_bio_RSAPrivateKey(bio, rsa_ptr, cipher, (unsigned char*)passphrase.passphrase, passphrase.passphrase_len, nullptr, nullptr) != 1) { JSC::throwTypeError(globalObject, scope, "Failed to write private key"_s); BIO_free(bio); return JSC::JSValue::encode(JSC::JSValue {}); } } else if (type == "pkcs8"_s) { - if (PEM_write_bio_PKCS8PrivateKey(bio, rsaKey, cipher, (char*)passphrase, passphrase_len, nullptr, nullptr) != 1) { + if (PEM_write_bio_PKCS8PrivateKey(bio, rsaKey, cipher, passphrase.passphrase, passphrase.passphrase_len, nullptr, nullptr) != 1) { JSC::throwTypeError(globalObject, scope, "Failed to write private key"_s); BIO_free(bio); return JSC::JSValue::encode(JSC::JSValue {}); @@ -1935,7 +1901,7 @@ JSC::EncodedJSValue KeyObject__Exports(JSC::JSGlobalObject* globalObject, JSC::C return JSC::JSValue::encode(JSC::JSValue {}); } } else if (type == "pkcs8"_s) { - if (i2d_PKCS8PrivateKey_bio(bio, rsaKey, cipher, (char*)passphrase, passphrase_len, nullptr, nullptr) != 1) { + if (i2d_PKCS8PrivateKey_bio(bio, rsaKey, cipher, passphrase.passphrase, passphrase.passphrase_len, nullptr, nullptr) != 1) { JSC::throwTypeError(globalObject, scope, "Failed to write private key"_s); BIO_free(bio); return JSC::JSValue::encode(JSC::JSValue {}); @@ -2024,7 +1990,6 @@ JSC::EncodedJSValue KeyObject__Exports(JSC::JSGlobalObject* globalObject, JSC::C return JSC::JSValue::encode(JSC::JSValue {}); } } else { - JSValue passphraseJSValue = options->getIfPropertyExists(globalObject, PropertyName(Identifier::fromString(vm, "passphrase"_s))); JSValue cipherJSValue = options->getIfPropertyExists(globalObject, PropertyName(Identifier::fromString(vm, "cipher"_s))); const EVP_CIPHER* cipher = nullptr; @@ -2046,47 +2011,25 @@ JSC::EncodedJSValue KeyObject__Exports(JSC::JSGlobalObject* globalObject, JSC::C } } } - void* passphrase = nullptr; - size_t passphrase_len = 0; - auto hasPassphrase = !passphraseJSValue.isUndefinedOrNull() && !passphraseJSValue.isEmpty(); - if (hasPassphrase) { + if (passphrase.hasPassphrase) { + if (!cipher) { JSC::throwTypeError(globalObject, scope, "cipher is required when passphrase is specified"_s); BIO_free(bio); return JSC::JSValue::encode(JSC::JSValue {}); } - if (passphraseJSValue.isString()) { - auto passphrase_wtfstr = passphraseJSValue.toWTFString(globalObject); - RETURN_IF_EXCEPTION(scope, encodedJSValue()); - if (!passphrase_wtfstr.isNull()) { - if (auto pass = passphrase_wtfstr.tryGetUTF8()) { - if (pass.has_value()) { - auto value = pass.value(); - passphrase = const_cast(value.data()); - passphrase_len = value.length(); - } - } - } - } else if (auto* passphraseBuffer = jsDynamicCast(passphraseJSValue)) { - passphrase = passphraseBuffer->vector(); - passphrase_len = passphraseBuffer->byteLength(); - } else { - JSC::throwTypeError(globalObject, scope, "passphrase must be a Buffer or String"_s); - BIO_free(bio); - return JSC::JSValue::encode(JSC::JSValue {}); - } } if (string == "pem"_s) { if (type == "sec1"_s) { - if (PEM_write_bio_ECPrivateKey(bio, ec_ptr, cipher, (unsigned char*)passphrase, passphrase_len, nullptr, nullptr) != 1) { + if (PEM_write_bio_ECPrivateKey(bio, ec_ptr, cipher, (unsigned char*)passphrase.passphrase, passphrase.passphrase_len, nullptr, nullptr) != 1) { JSC::throwTypeError(globalObject, scope, "Failed to write private key"_s); BIO_free(bio); return JSC::JSValue::encode(JSC::JSValue {}); } } else if (type == "pkcs8"_s) { - if (PEM_write_bio_PKCS8PrivateKey(bio, ecKey, cipher, (char*)passphrase, passphrase_len, nullptr, nullptr) != 1) { + if (PEM_write_bio_PKCS8PrivateKey(bio, ecKey, cipher, passphrase.passphrase, passphrase.passphrase_len, nullptr, nullptr) != 1) { JSC::throwTypeError(globalObject, scope, "Failed to write private key"_s); BIO_free(bio); return JSC::JSValue::encode(JSC::JSValue {}); @@ -2104,7 +2047,7 @@ JSC::EncodedJSValue KeyObject__Exports(JSC::JSGlobalObject* globalObject, JSC::C return JSC::JSValue::encode(JSC::JSValue {}); } } else if (type == "pkcs8"_s) { - if (i2d_PKCS8PrivateKey_bio(bio, ecKey, cipher, (char*)passphrase, passphrase_len, nullptr, nullptr) != 1) { + if (i2d_PKCS8PrivateKey_bio(bio, ecKey, cipher, passphrase.passphrase, passphrase.passphrase_len, nullptr, nullptr) != 1) { JSC::throwTypeError(globalObject, scope, "Failed to write private key"_s); BIO_free(bio); return JSC::JSValue::encode(JSC::JSValue {}); @@ -2164,7 +2107,6 @@ JSC::EncodedJSValue KeyObject__Exports(JSC::JSGlobalObject* globalObject, JSC::C // TODO: CHECK THIS WHEN X488 AND ED448 ARE ADDED if (okpKey.type() == CryptoKeyType::Private) { evpKey = EVP_PKEY_new_raw_private_key(okpKey.namedCurve() == CryptoKeyOKP::NamedCurve::X25519 ? EVP_PKEY_X25519 : EVP_PKEY_ED25519, nullptr, keyData.data(), keyData.size()); - JSValue passphraseJSValue = options->getIfPropertyExists(globalObject, PropertyName(Identifier::fromString(vm, "passphrase"_s))); JSValue cipherJSValue = options->getIfPropertyExists(globalObject, PropertyName(Identifier::fromString(vm, "cipher"_s))); const EVP_CIPHER* cipher = nullptr; @@ -2187,41 +2129,18 @@ JSC::EncodedJSValue KeyObject__Exports(JSC::JSGlobalObject* globalObject, JSC::C } } } - void* passphrase = nullptr; - size_t passphrase_len = 0; - auto hasPassphrase = !passphraseJSValue.isUndefinedOrNull() && !passphraseJSValue.isEmpty(); - if (hasPassphrase) { + if (passphrase.hasPassphrase) { if (!cipher) { JSC::throwTypeError(globalObject, scope, "cipher is required when passphrase is specified"_s); BIO_free(bio); return JSC::JSValue::encode(JSC::JSValue {}); } - if (passphraseJSValue.isString()) { - auto passphrase_wtfstr = passphraseJSValue.toWTFString(globalObject); - RETURN_IF_EXCEPTION(scope, encodedJSValue()); - if (!passphrase_wtfstr.isNull()) { - if (auto pass = passphrase_wtfstr.tryGetUTF8()) { - if (pass.has_value()) { - auto value = pass.value(); - passphrase = const_cast(value.data()); - passphrase_len = value.length(); - } - } - } - } else if (auto* passphraseBuffer = jsDynamicCast(passphraseJSValue)) { - passphrase = passphraseBuffer->vector(); - passphrase_len = passphraseBuffer->byteLength(); - } else { - JSC::throwTypeError(globalObject, scope, "passphrase must be a Buffer or String"_s); - BIO_free(bio); - return JSC::JSValue::encode(JSC::JSValue {}); - } } if (string == "pem"_s) { if (type == "pkcs8"_s) { - if (PEM_write_bio_PKCS8PrivateKey(bio, evpKey, cipher, (char*)passphrase, passphrase_len, nullptr, nullptr) != 1) { + if (PEM_write_bio_PKCS8PrivateKey(bio, evpKey, cipher, passphrase.passphrase, passphrase.passphrase_len, nullptr, nullptr) != 1) { JSC::throwTypeError(globalObject, scope, "Failed to write private key"_s); BIO_free(bio); EVP_PKEY_free(evpKey); @@ -2235,7 +2154,7 @@ JSC::EncodedJSValue KeyObject__Exports(JSC::JSGlobalObject* globalObject, JSC::C } } else if (string == "der"_s) { if (type == "pkcs8"_s) { - if (i2d_PKCS8PrivateKey_bio(bio, evpKey, cipher, (char*)passphrase, passphrase_len, nullptr, nullptr) != 1) { + if (i2d_PKCS8PrivateKey_bio(bio, evpKey, cipher, passphrase.passphrase, passphrase.passphrase_len, nullptr, nullptr) != 1) { JSC::throwTypeError(globalObject, scope, "Failed to write private key"_s); BIO_free(bio); EVP_PKEY_free(evpKey);