diff options
author | 2023-02-15 00:11:48 -0800 | |
---|---|---|
committer | 2023-02-15 02:11:48 -0600 | |
commit | 4320108add7aa43e6e9ff88fff45b620d1292354 (patch) | |
tree | 1069c11a308fe32b15e6ef288353d553e2a537c3 /src/bun.js/bindings/webcrypto/CryptoKeyOKPOpenSSL.cpp | |
parent | d91052516e876d9938a4dd2d9bdd25a0739f8eba (diff) | |
download | bun-4320108add7aa43e6e9ff88fff45b620d1292354.tar.gz bun-4320108add7aa43e6e9ff88fff45b620d1292354.tar.zst bun-4320108add7aa43e6e9ff88fff45b620d1292354.zip |
ED25519 WebCrypto (#1971)
* ed25519
* Register the algorithm
* try this?
* fix(webcrypto): fix ed25519 keypair gen (#1985)
* fix: import and export ed25519 (#2004)
* fix(webcrypto): allow import and export ed25519
* fix(webcrypto): copy exportkey
* fix(webcrypto): fix use after stack free
---------
Co-authored-by: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com>
Co-authored-by: Derrick Farris <mr.dcfarris@gmail.com>
Diffstat (limited to 'src/bun.js/bindings/webcrypto/CryptoKeyOKPOpenSSL.cpp')
-rw-r--r-- | src/bun.js/bindings/webcrypto/CryptoKeyOKPOpenSSL.cpp | 359 |
1 files changed, 359 insertions, 0 deletions
diff --git a/src/bun.js/bindings/webcrypto/CryptoKeyOKPOpenSSL.cpp b/src/bun.js/bindings/webcrypto/CryptoKeyOKPOpenSSL.cpp new file mode 100644 index 000000000..8c6a25f19 --- /dev/null +++ b/src/bun.js/bindings/webcrypto/CryptoKeyOKPOpenSSL.cpp @@ -0,0 +1,359 @@ +/* + * Copyright (C) 2023 Apple Inc. All rights reserved. + * + * 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 "CryptoKeyOKP.h" + +#if ENABLE(WEB_CRYPTO) + +#include "JsonWebKey.h" +// #include "Logging.h" +#include <wtf/text/Base64.h> +#include <openssl/curve25519.h> +#include "CommonCryptoDERUtilities.h" + +namespace WebCore { + +bool CryptoKeyOKP::isPlatformSupportedCurve(NamedCurve namedCurve) +{ + return namedCurve == NamedCurve::Ed25519; +} + +std::optional<CryptoKeyPair> CryptoKeyOKP::platformGeneratePair(CryptoAlgorithmIdentifier identifier, NamedCurve namedCurve, bool extractable, CryptoKeyUsageBitmap usages) +{ + if (namedCurve != NamedCurve::Ed25519) + return {}; + + uint8_t public_key[ED25519_PUBLIC_KEY_LEN], private_key[ED25519_PRIVATE_KEY_LEN]; + + bool isEd25519 = identifier == CryptoAlgorithmIdentifier::Ed25519; + if (isEd25519) { + ED25519_keypair(public_key, private_key); + } else { + X25519_keypair(public_key, private_key); + } + + bool isPublicKeyExtractable = true; + auto publicKey = CryptoKeyOKP::create(identifier, namedCurve, CryptoKeyType::Public, Vector<uint8_t>(public_key), isPublicKeyExtractable, usages); + ASSERT(publicKey); + auto privateKey = CryptoKeyOKP::create(identifier, namedCurve, CryptoKeyType::Private, Vector<uint8_t>(private_key, isEd25519 ? ED25519_PRIVATE_KEY_LEN : X25519_PRIVATE_KEY_LEN), extractable, usages); + ASSERT(privateKey); + return CryptoKeyPair { WTFMove(publicKey), WTFMove(privateKey) }; +} + +// Per https://www.ietf.org/rfc/rfc5280.txt +// SubjectPublicKeyInfo ::= SEQUENCE { algorithm AlgorithmIdentifier, subjectPublicKey BIT STRING } +// AlgorithmIdentifier ::= SEQUENCE { algorithm OBJECT IDENTIFIER, parameters ANY DEFINED BY algorithm OPTIONAL } +// Per https://www.rfc-editor.org/rfc/rfc8410 +// id-X25519 OBJECT IDENTIFIER ::= { 1 3 101 110 } +// id-X448 OBJECT IDENTIFIER ::= { 1 3 101 111 } +// id-Ed25519 OBJECT IDENTIFIER ::= { 1 3 101 112 } +// id-Ed448 OBJECT IDENTIFIER ::= { 1 3 101 113 } +// For all of the OIDs, the parameters MUST be absent. +RefPtr<CryptoKeyOKP> CryptoKeyOKP::importSpki(CryptoAlgorithmIdentifier identifier, NamedCurve namedCurve, Vector<uint8_t>&& keyData, bool extractable, CryptoKeyUsageBitmap usages) +{ + // FIXME: We should use the underlying crypto library to import PKCS8 OKP keys. + + // Read SEQUENCE + size_t index = 1; + if (keyData.size() < index + 1) + return nullptr; + + // Read length and SEQUENCE + // FIXME: Check length is 5 + 1 + 1 + 1 + keyByteSize. + index += bytesUsedToEncodedLength(keyData[index]) + 1; + if (keyData.size() < index + 1) + return nullptr; + + // Read length + // FIXME: Check length is 5. + index += bytesUsedToEncodedLength(keyData[index]); + if (keyData.size() < index + 5) + return nullptr; + + // Read OID + // FIXME: spec says this is 1 3 101 11X but WPT tests expect 6 3 43 101 11X. + if (keyData[index++] != 6 || keyData[index++] != 3 || keyData[index++] != 43 || keyData[index++] != 101) + return nullptr; + + switch (namedCurve) { + case NamedCurve::X25519: + if (keyData[index++] != 110) + return nullptr; + break; + case NamedCurve::Ed25519: + if (keyData[index++] != 112) + return nullptr; + break; + }; + + // Read BIT STRING + if (keyData.size() < index + 1) + return nullptr; + if (keyData[index++] != 3) + return nullptr; + + // Read length + // FIXME: Check length is keyByteSize + 1. + index += bytesUsedToEncodedLength(keyData[index]); + + if (keyData.size() < index + 1) + return nullptr; + + // Initial octet + if (!!keyData[index]) + return nullptr; + ++index; + + return create(identifier, namedCurve, CryptoKeyType::Public, Span<const uint8_t> { keyData.data() + index, keyData.size() - index }, extractable, usages); +} + +constexpr uint8_t OKPOIDFirstByte = 6; +constexpr uint8_t OKPOIDSecondByte = 3; +constexpr uint8_t OKPOIDThirdByte = 43; +constexpr uint8_t OKPOIDFourthByte = 101; +constexpr uint8_t OKPOIDX25519Byte = 110; +constexpr uint8_t OKPOIDEd25519Byte = 112; + +static void writeOID(CryptoKeyOKP::NamedCurve namedCurve, Vector<uint8_t>& result) +{ + result.append(OKPOIDFirstByte); + result.append(OKPOIDSecondByte); + result.append(OKPOIDThirdByte); + result.append(OKPOIDFourthByte); + + switch (namedCurve) { + case CryptoKeyOKP::NamedCurve::X25519: + result.append(OKPOIDX25519Byte); + break; + case CryptoKeyOKP::NamedCurve::Ed25519: + result.append(OKPOIDEd25519Byte); + break; + }; +} + +ExceptionOr<Vector<uint8_t>> CryptoKeyOKP::exportSpki() const +{ + if (type() != CryptoKeyType::Public) + return Exception { InvalidAccessError }; + + size_t keySize = keySizeInBytes(); + + // SEQUENCE, length, SEQUENCE, length, OID, Bit String (Initial octet prepended) + size_t totalSize = 1 + 1 + 1 + 1 + 5 + 1 + 1 + 1 + keySize; + Vector<uint8_t> result; + result.reserveInitialCapacity(totalSize); + result.append(SequenceMark); + addEncodedASN1Length(result, totalSize - 2); + result.append(SequenceMark); + addEncodedASN1Length(result, 5); + + writeOID(namedCurve(), result); + + result.append(BitStringMark); + addEncodedASN1Length(result, keySize + 1); + result.append(InitialOctet); + result.append(platformKey().data(), platformKey().size()); + + ASSERT(result.size() == totalSize); + + return WTFMove(result); +} + +// Per https://www.ietf.org/rfc/rfc5280.txt +// PrivateKeyInfo ::= SEQUENCE { version INTEGER, privateKeyAlgorithm AlgorithmIdentifier, privateKey OCTET STRING } +// AlgorithmIdentifier ::= SEQUENCE { algorithm OBJECT IDENTIFIER, parameters ANY DEFINED BY algorithm OPTIONAL } +// Per https://www.rfc-editor.org/rfc/rfc8410 +// id-X25519 OBJECT IDENTIFIER ::= { 1 3 101 110 } +// id-X448 OBJECT IDENTIFIER ::= { 1 3 101 111 } +// id-Ed25519 OBJECT IDENTIFIER ::= { 1 3 101 112 } +// id-Ed448 OBJECT IDENTIFIER ::= { 1 3 101 113 } +// For all of the OIDs, the parameters MUST be absent. +RefPtr<CryptoKeyOKP> CryptoKeyOKP::importPkcs8(CryptoAlgorithmIdentifier identifier, NamedCurve namedCurve, Vector<uint8_t>&& keyData, bool extractable, CryptoKeyUsageBitmap usages) +{ + // FIXME: We should use the underlying crypto library to import PKCS8 OKP keys. + + // Read SEQUENCE + size_t index = 1; + if (keyData.size() < index + 1) + return nullptr; + + // Read length + index += bytesUsedToEncodedLength(keyData[index]); + if (keyData.size() < index + 1) + return nullptr; + + // Read version + index += 3; + if (keyData.size() < index + 1) + return nullptr; + + // Read SEQUENCE + index += bytesUsedToEncodedLength(keyData[index]); + if (keyData.size() < index + 1) + return nullptr; + + // Read length + index += bytesUsedToEncodedLength(keyData[index]); + if (keyData.size() < index + 1) + return nullptr; + + // Read OID + if (keyData[index++] != OKPOIDFirstByte || keyData[index++] != OKPOIDSecondByte || keyData[index++] != OKPOIDThirdByte || keyData[index++] != OKPOIDFourthByte) + return nullptr; + + switch (namedCurve) { + case NamedCurve::X25519: + if (keyData[index++] != OKPOIDX25519Byte) + return nullptr; + break; + case NamedCurve::Ed25519: + if (keyData[index++] != OKPOIDEd25519Byte) + return nullptr; + break; + }; + + // Read OCTET STRING + if (keyData.size() < index + 1) + return nullptr; + + if (keyData[index++] != 4) + return nullptr; + + index += bytesUsedToEncodedLength(keyData[index]); + if (keyData.size() < index + 1) + return nullptr; + + // Read OCTET STRING + if (keyData[index++] != 4) + return nullptr; + + index += bytesUsedToEncodedLength(keyData[index]); + if (keyData.size() < index + 1) + return nullptr; + + return create(identifier, namedCurve, CryptoKeyType::Private, Span<const uint8_t> { keyData.data() + index, keyData.size() - index }, extractable, usages); +} + +ExceptionOr<Vector<uint8_t>> CryptoKeyOKP::exportPkcs8() const +{ + if (type() != CryptoKeyType::Private) + return Exception { InvalidAccessError }; + + 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; + Vector<uint8_t> result; + result.reserveInitialCapacity(totalSize); + result.append(SequenceMark); + addEncodedASN1Length(result, totalSize - 2); + + result.append(2); + result.append(1); + result.append(0); + + result.append(SequenceMark); + addEncodedASN1Length(result, 5); + + writeOID(namedCurve(), result); + + result.append(OctetStringMark); + addEncodedASN1Length(result, keySize + 2); + result.append(OctetStringMark); + addEncodedASN1Length(result, keySize); + result.append(exportKey().data(), exportKey().size()); + + ASSERT(result.size() == totalSize); + + return WTFMove(result); +} + +String CryptoKeyOKP::generateJwkD() const +{ + ASSERT(type() == CryptoKeyType::Private); + if (namedCurve() == NamedCurve::Ed25519) { + ASSERT(m_exportKey); + return base64URLEncodeToString(*m_exportKey); + } + return base64URLEncodeToString(m_data); +} + +CryptoKeyOKP::KeyMaterial CryptoKeyOKP::ed25519PublicFromPrivate(const KeyMaterial& seed) +{ + auto publicKey = KeyMaterial(ED25519_PUBLIC_KEY_LEN); + uint8_t privateKey[ED25519_PRIVATE_KEY_LEN]; + + ED25519_keypair_from_seed(publicKey.data(), privateKey, seed.data()); + + return WTFMove(publicKey); +} + +CryptoKeyOKP::KeyMaterial CryptoKeyOKP::x25519PublicFromPrivate(const KeyMaterial& privateKey) +{ + auto publicKey = KeyMaterial(X25519_PUBLIC_VALUE_LEN); + + X25519_public_from_private(publicKey.data(), privateKey.data()); + + 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 +{ + if (type() == CryptoKeyType::Public) + return base64URLEncodeToString(m_data); + + ASSERT(type() == CryptoKeyType::Private); + + if (namedCurve() == NamedCurve::Ed25519) + return base64URLEncodeToString(WTFMove(ed25519PublicFromPrivate(const_cast<KeyMaterial&>(m_data)))); + + ASSERT(namedCurve() == NamedCurve::X25519); + return base64URLEncodeToString(WTFMove(x25519PublicFromPrivate(const_cast<KeyMaterial&>(m_data)))); +} + +CryptoKeyOKP::KeyMaterial CryptoKeyOKP::platformExportRaw() const +{ + 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 + +#endif // ENABLE(WEB_CRYPTO) |