diff options
author | 2023-02-08 16:57:13 -0600 | |
---|---|---|
committer | 2023-02-08 14:57:13 -0800 | |
commit | c8243d19f0a06c7a04a5d611bd14c5fb52b46c42 (patch) | |
tree | 76ce2650cfc2f4dbd6e08a471fe626b06de0b898 | |
parent | 7db87fe0cbac2d4eeba1c8a3d530897e1f16e78f (diff) | |
download | bun-c8243d19f0a06c7a04a5d611bd14c5fb52b46c42.tar.gz bun-c8243d19f0a06c7a04a5d611bd14c5fb52b46c42.tar.zst bun-c8243d19f0a06c7a04a5d611bd14c5fb52b46c42.zip |
fix: import and export ed25519 (#2004)
* fix(webcrypto): allow import and export ed25519
* fix(webcrypto): copy exportkey
* fix(webcrypto): fix use after stack free
5 files changed, 100 insertions, 59 deletions
diff --git a/src/bun.js/bindings/webcrypto/CryptoAlgorithmEd25519.cpp b/src/bun.js/bindings/webcrypto/CryptoAlgorithmEd25519.cpp index 111f57560..7003222bd 100644 --- a/src/bun.js/bindings/webcrypto/CryptoAlgorithmEd25519.cpp +++ b/src/bun.js/bindings/webcrypto/CryptoAlgorithmEd25519.cpp @@ -39,7 +39,6 @@ namespace WebCore { static ExceptionOr<Vector<uint8_t>> signEd25519(const Vector<uint8_t>& sk, size_t len, const Vector<uint8_t>& data) { - uint8_t pk[32]; uint8_t newSignature[64]; ED25519_sign(newSignature, data.data(), data.size(), sk.data()); diff --git a/src/bun.js/bindings/webcrypto/CryptoKeyOKP.cpp b/src/bun.js/bindings/webcrypto/CryptoKeyOKP.cpp index 58b46cbc4..a4138d8c9 100644 --- a/src/bun.js/bindings/webcrypto/CryptoKeyOKP.cpp +++ b/src/bun.js/bindings/webcrypto/CryptoKeyOKP.cpp @@ -37,28 +37,65 @@ namespace WebCore { static const ASCIILiteral X25519 { "X25519"_s }; static const ASCIILiteral Ed25519 { "Ed25519"_s }; -static constexpr size_t keySizeInBytesFromNamedCurve(CryptoKeyOKP::NamedCurve curve, CryptoKeyType type) +static constexpr size_t internalKeySizeInBytesFromNamedCurve(CryptoKeyOKP::NamedCurve curve, CryptoKeyType type) { switch (curve) { case CryptoKeyOKP::NamedCurve::X25519: return 32; case CryptoKeyOKP::NamedCurve::Ed25519: return type == CryptoKeyType::Private ? 64 : 32; + default: + return -1; + } +} + +static constexpr size_t externalKeySizeInBytesFromNamedCurve(CryptoKeyOKP::NamedCurve curve) +{ + switch (curve) { + case CryptoKeyOKP::NamedCurve::X25519: + case CryptoKeyOKP::NamedCurve::Ed25519: + return 32; + default: + return -1; } - return 32; } RefPtr<CryptoKeyOKP> CryptoKeyOKP::create(CryptoAlgorithmIdentifier identifier, NamedCurve curve, CryptoKeyType type, KeyMaterial&& platformKey, bool extractable, CryptoKeyUsageBitmap usages) { - if (platformKey.size() != keySizeInBytesFromNamedCurve(curve, type)) + auto bytesExpectedInternal = internalKeySizeInBytesFromNamedCurve(curve, type); + if (bytesExpectedInternal == -1) return nullptr; + + if (platformKey.size() != bytesExpectedInternal) { + if (type != CryptoKeyType::Private || curve != NamedCurve::Ed25519) + return nullptr; + + auto bytesExpectedExternal = externalKeySizeInBytesFromNamedCurve(curve); + if (bytesExpectedExternal == -1) + return nullptr; + + // We need to match the internal format when importing a private key + // Import format only consists of 32 bytes of private key + // Internal format is private key + public key suffix + if (platformKey.size() == bytesExpectedExternal) { + auto&& privateKey = ed25519PrivateFromSeed(WTFMove(platformKey)); + if (!privateKey.data()) + return nullptr; + + return adoptRef(*new CryptoKeyOKP(identifier, curve, type, WTFMove(privateKey), extractable, usages)); + } + + return nullptr; + } + return adoptRef(*new CryptoKeyOKP(identifier, curve, type, WTFMove(platformKey), extractable, usages)); } CryptoKeyOKP::CryptoKeyOKP(CryptoAlgorithmIdentifier identifier, NamedCurve curve, CryptoKeyType type, KeyMaterial&& data, bool extractable, CryptoKeyUsageBitmap usages) : CryptoKey(identifier, type, extractable, usages) , m_curve(curve) - , m_data(WTFMove(data)) + , m_data(data) + , m_exportKey(curve == NamedCurve::Ed25519 && type == CryptoKeyType::Private ? std::optional<Vector<uint8_t>>(Vector<uint8_t>(data.data(), 32)) : std::nullopt) { } @@ -102,7 +139,7 @@ RefPtr<CryptoKeyOKP> CryptoKeyOKP::importJwk(CryptoAlgorithmIdentifier identifie return nullptr; if (!keyData.alg.isEmpty() && keyData.alg != "EdDSA"_s) return nullptr; - if (usages && !keyData.use.isEmpty() && keyData.use != "sign"_s) + if (usages && !keyData.use.isEmpty() && keyData.use != "sig"_s) return nullptr; if (keyData.key_ops && ((keyData.usages & usages) != usages)) return nullptr; @@ -138,10 +175,10 @@ ExceptionOr<Vector<uint8_t>> CryptoKeyOKP::exportRaw() const if (type() != CryptoKey::Type::Public) return Exception { InvalidAccessError }; - auto result = platformExportRaw(); + auto&& result = platformExportRaw(); if (result.isEmpty()) return Exception { OperationError }; - return result; + return WTFMove(result); } ExceptionOr<JsonWebKey> CryptoKeyOKP::exportJwk() const diff --git a/src/bun.js/bindings/webcrypto/CryptoKeyOKP.h b/src/bun.js/bindings/webcrypto/CryptoKeyOKP.h index 40642fe68..cc1fe2c73 100644 --- a/src/bun.js/bindings/webcrypto/CryptoKeyOKP.h +++ b/src/bun.js/bindings/webcrypto/CryptoKeyOKP.h @@ -59,13 +59,21 @@ public: NamedCurve namedCurve() const { return m_curve; } String namedCurveString() const; + bool isEd25519PrivateKey() { return namedCurve() == NamedCurve::Ed25519 && type() == CryptoKeyType::Private; }; static bool isValidOKPAlgorithm(CryptoAlgorithmIdentifier); + static KeyMaterial ed25519PublicFromPrivate(const KeyMaterial& privateKey); + static KeyMaterial x25519PublicFromPrivate(const KeyMaterial& privateKey); + static KeyMaterial ed25519PrivateFromSeed(KeyMaterial&& seed); size_t keySizeInBits() const { return platformKey().size() * 8; } size_t keySizeInBytes() const { return platformKey().size(); } const KeyMaterial& platformKey() const { return m_data; } + size_t exportKeySizeInBits() const { return exportKey().size() * 8; } + size_t exportKeySizeInBytes() const { return exportKey().size(); } + const KeyMaterial& exportKey() const { return !m_exportKey ? m_data : *m_exportKey; }; + private: CryptoKeyOKP(CryptoAlgorithmIdentifier, NamedCurve, CryptoKeyType, Vector<uint8_t>&&, bool extractable, CryptoKeyUsageBitmap); @@ -75,9 +83,6 @@ private: String generateJwkD() const; String generateJwkX() const; - String getEd25519PublicFromPrivate() const; - String getX25519PublicFromPrivate() const; - static bool isPlatformSupportedCurve(NamedCurve); static std::optional<CryptoKeyPair> platformGeneratePair(CryptoAlgorithmIdentifier, NamedCurve, bool extractable, CryptoKeyUsageBitmap); Vector<uint8_t> platformExportRaw() const; @@ -86,6 +91,7 @@ private: NamedCurve m_curve; KeyMaterial m_data; + std::optional<KeyMaterial> m_exportKey; }; } // namespace WebCore diff --git a/src/bun.js/bindings/webcrypto/CryptoKeyOKPOpenSSL.cpp b/src/bun.js/bindings/webcrypto/CryptoKeyOKPOpenSSL.cpp index 0372443fc..8c6a25f19 100644 --- a/src/bun.js/bindings/webcrypto/CryptoKeyOKPOpenSSL.cpp +++ b/src/bun.js/bindings/webcrypto/CryptoKeyOKPOpenSSL.cpp @@ -262,7 +262,7 @@ ExceptionOr<Vector<uint8_t>> CryptoKeyOKP::exportPkcs8() const if (type() != CryptoKeyType::Private) return Exception { InvalidAccessError }; - size_t keySize = keySizeInBytes(); + size_t keySize = exportKeySizeInBytes(); // SEQUENCE, length, version SEQUENCE, length, OID, Octet String Octet String size_t totalSize = 1 + 1 + 3 + 1 + 1 + 5 + 1 + 1 + 1 + 1 + keySize; @@ -284,7 +284,7 @@ ExceptionOr<Vector<uint8_t>> CryptoKeyOKP::exportPkcs8() const addEncodedASN1Length(result, keySize + 2); result.append(OctetStringMark); addEncodedASN1Length(result, keySize); - result.append(platformKey().data(), platformKey().size()); + result.append(exportKey().data(), exportKey().size()); ASSERT(result.size() == totalSize); @@ -294,26 +294,40 @@ ExceptionOr<Vector<uint8_t>> CryptoKeyOKP::exportPkcs8() const String CryptoKeyOKP::generateJwkD() const { ASSERT(type() == CryptoKeyType::Private); + if (namedCurve() == NamedCurve::Ed25519) { + ASSERT(m_exportKey); + return base64URLEncodeToString(*m_exportKey); + } return base64URLEncodeToString(m_data); } -String CryptoKeyOKP::getEd25519PublicFromPrivate() const +CryptoKeyOKP::KeyMaterial CryptoKeyOKP::ed25519PublicFromPrivate(const KeyMaterial& seed) { - uint8_t publicKey[ED25519_PUBLIC_KEY_LEN]; + auto publicKey = KeyMaterial(ED25519_PUBLIC_KEY_LEN); uint8_t privateKey[ED25519_PRIVATE_KEY_LEN]; - ED25519_keypair_from_seed(publicKey, privateKey, m_data.data()); + ED25519_keypair_from_seed(publicKey.data(), privateKey, seed.data()); - return base64URLEncodeToString(Span<const uint8_t> { publicKey, sizeof(publicKey) }); + return WTFMove(publicKey); } -String CryptoKeyOKP::getX25519PublicFromPrivate() const +CryptoKeyOKP::KeyMaterial CryptoKeyOKP::x25519PublicFromPrivate(const KeyMaterial& privateKey) { - uint8_t publicKey[X25519_PUBLIC_VALUE_LEN]; + auto publicKey = KeyMaterial(X25519_PUBLIC_VALUE_LEN); - X25519_public_from_private(publicKey, m_data.data()); + X25519_public_from_private(publicKey.data(), privateKey.data()); - return base64URLEncodeToString(Span<const uint8_t> { publicKey, sizeof(publicKey) }); + return WTFMove(publicKey); +} + +CryptoKeyOKP::KeyMaterial CryptoKeyOKP::ed25519PrivateFromSeed(KeyMaterial&& seed) +{ + uint8_t publicKey[ED25519_PUBLIC_KEY_LEN]; + auto privateKey = KeyMaterial(ED25519_PRIVATE_KEY_LEN); + + ED25519_keypair_from_seed(publicKey, privateKey.data(), seed.data()); + + return WTFMove(privateKey); } String CryptoKeyOKP::generateJwkX() const @@ -324,15 +338,20 @@ String CryptoKeyOKP::generateJwkX() const ASSERT(type() == CryptoKeyType::Private); if (namedCurve() == NamedCurve::Ed25519) - return getEd25519PublicFromPrivate(); + return base64URLEncodeToString(WTFMove(ed25519PublicFromPrivate(const_cast<KeyMaterial&>(m_data)))); ASSERT(namedCurve() == NamedCurve::X25519); - return getX25519PublicFromPrivate(); + return base64URLEncodeToString(WTFMove(x25519PublicFromPrivate(const_cast<KeyMaterial&>(m_data)))); } -Vector<uint8_t> CryptoKeyOKP::platformExportRaw() const +CryptoKeyOKP::KeyMaterial CryptoKeyOKP::platformExportRaw() const { - return m_data; + if (namedCurve() == NamedCurve::Ed25519 && type() == CryptoKeyType::Private) { + ASSERT(m_exportKey); + const auto& exportKey = *m_exportKey; + return WTFMove(Vector<uint8_t>(exportKey.data(), exportKey.size())); + } + return WTFMove(KeyMaterial(m_data.data(), m_data.size())); } } // namespace WebCore diff --git a/src/bun.js/builtins/cpp/ProcessObjectInternalsBuiltins.cpp b/src/bun.js/builtins/cpp/ProcessObjectInternalsBuiltins.cpp index c5ffdff09..574d18f20 100644 --- a/src/bun.js/builtins/cpp/ProcessObjectInternalsBuiltins.cpp +++ b/src/bun.js/builtins/cpp/ProcessObjectInternalsBuiltins.cpp @@ -51,12 +51,12 @@ namespace WebCore { const JSC::ConstructAbility s_processObjectInternalsGetStdioWriteStreamCodeConstructAbility = JSC::ConstructAbility::CannotConstruct; const JSC::ConstructorKind s_processObjectInternalsGetStdioWriteStreamCodeConstructorKind = JSC::ConstructorKind::None; const JSC::ImplementationVisibility s_processObjectInternalsGetStdioWriteStreamCodeImplementationVisibility = JSC::ImplementationVisibility::Public; -const int s_processObjectInternalsGetStdioWriteStreamCodeLength = 9968; +const int s_processObjectInternalsGetStdioWriteStreamCodeLength = 9767; static const JSC::Intrinsic s_processObjectInternalsGetStdioWriteStreamCodeIntrinsic = JSC::NoIntrinsic; const char* const s_processObjectInternalsGetStdioWriteStreamCode = "(function (fd_, rawRequire) {\n" \ " var module = { path: \"node:process\", require: rawRequire };\n" \ - " var require = (path) => module.require(path);\n" \ + " var require = path => module.require(path);\n" \ "\n" \ " function createStdioWriteStream(fd_) {\n" \ " var { Duplex, eos, destroy } = require(\"node:stream\");\n" \ @@ -103,20 +103,11 @@ const char* const s_processObjectInternalsGetStdioWriteStreamCode = " _destroy(err, callback) {\n" \ " if (!err && this.#onClose !== null) {\n" \ " var AbortError = class AbortError extends Error {\n" \ - " constructor(\n" \ - " message = \"The operation was aborted\",\n" \ - " options = void 0,\n" \ - " ) {\n" \ + " constructor(message = \"The operation was aborted\", options = void 0) {\n" \ " if (options !== void 0 && typeof options !== \"object\") {\n" \ - " throw new Error(\n" \ - " `Invalid AbortError options:\\n" \ + " throw new Error(`Invalid AbortError options:\\n" \ "\\n" \ - "${JSON.stringify(\n" \ - " options,\n" \ - " null,\n" \ - " 2,\n" \ - " )}`,\n" \ - " );\n" \ + "${JSON.stringify(options, null, 2)}`);\n" \ " }\n" \ " super(message, options);\n" \ " this.code = \"ABORT_ERR\";\n" \ @@ -158,7 +149,7 @@ const char* const s_processObjectInternalsGetStdioWriteStreamCode = " }\n" \ " });\n" \ "\n" \ - " eos(stream, (err) => {\n" \ + " eos(stream, err => {\n" \ " this.#writable = false;\n" \ " if (err) {\n" \ " destroy(stream, err);\n" \ @@ -197,7 +188,7 @@ const char* const s_processObjectInternalsGetStdioWriteStreamCode = " this.push(null);\n" \ " });\n" \ "\n" \ - " eos(readStream, (err) => {\n" \ + " eos(readStream, err => {\n" \ " this.#readable = false;\n" \ " if (err) {\n" \ " destroy(readStream, err);\n" \ @@ -230,12 +221,7 @@ const char* const s_processObjectInternalsGetStdioWriteStreamCode = " if (!encoding) return true;\n" \ "\n" \ " var normalied = encoding.toLowerCase();\n" \ - " return (\n" \ - " normalied === \"utf8\" ||\n" \ - " normalied === \"utf-8\" ||\n" \ - " normalied === \"buffer\" ||\n" \ - " normalied === \"binary\"\n" \ - " );\n" \ + " return normalied === \"utf8\" || normalied === \"utf-8\" || normalied === \"buffer\" || normalied === \"binary\";\n" \ " }\n" \ "\n" \ " var FastStdioWriteStream = class StdioWriteStream extends EventEmitter {\n" \ @@ -389,7 +375,7 @@ const char* const s_processObjectInternalsGetStdioWriteStreamCode = " this.#performCallback(callback);\n" \ " this.emit(\"drain\");\n" \ " },\n" \ - " (err) => this.#performCallback(callback, err),\n" \ + " err => this.#performCallback(callback, err),\n" \ " );\n" \ " return false;\n" \ " }\n" \ @@ -472,12 +458,12 @@ const char* const s_processObjectInternalsGetStdioWriteStreamCode = const JSC::ConstructAbility s_processObjectInternalsGetStdinStreamCodeConstructAbility = JSC::ConstructAbility::CannotConstruct; const JSC::ConstructorKind s_processObjectInternalsGetStdinStreamCodeConstructorKind = JSC::ConstructorKind::None; const JSC::ImplementationVisibility s_processObjectInternalsGetStdinStreamCodeImplementationVisibility = JSC::ImplementationVisibility::Public; -const int s_processObjectInternalsGetStdinStreamCodeLength = 4415; +const int s_processObjectInternalsGetStdinStreamCodeLength = 4305; static const JSC::Intrinsic s_processObjectInternalsGetStdinStreamCodeIntrinsic = JSC::NoIntrinsic; const char* const s_processObjectInternalsGetStdinStreamCode = "(function (fd_, rawRequire, Bun) {\n" \ " var module = { path: \"node:process\", require: rawRequire };\n" \ - " var require = (path) => module.require(path);\n" \ + " var require = path => module.require(path);\n" \ "\n" \ " var { Duplex, eos, destroy } = require(\"node:stream\");\n" \ "\n" \ @@ -526,15 +512,9 @@ const char* const s_processObjectInternalsGetStdinStreamCode = " var AbortError = class AbortError extends Error {\n" \ " constructor(message = \"The operation was aborted\", options = void 0) {\n" \ " if (options !== void 0 && typeof options !== \"object\") {\n" \ - " throw new Error(\n" \ - " `Invalid AbortError options:\\n" \ + " throw new Error(`Invalid AbortError options:\\n" \ "\\n" \ - "${JSON.stringify(\n" \ - " options,\n" \ - " null,\n" \ - " 2,\n" \ - " )}`,\n" \ - " );\n" \ + "${JSON.stringify(options, null, 2)}`);\n" \ " }\n" \ " super(message, options);\n" \ " this.code = \"ABORT_ERR\";\n" \ @@ -652,7 +632,7 @@ const char* const s_processObjectInternalsGetStdinStreamCode = " }\n" \ " });\n" \ "\n" \ - " eos(writeStream, (err) => {\n" \ + " eos(writeStream, err => {\n" \ " this.#writable = false;\n" \ " if (err) {\n" \ " destroy(writeStream, err);\n" \ |