Compare commits

...

6 Commits

Author SHA1 Message Date
Derrick Farris
a9eb730ed0 WIP(tests/web-crypto): add some tests for new ed25519 algo 2023-02-10 15:29:22 -06:00
Derrick Farris
c8243d19f0 fix: import and export ed25519 (#2004)
* fix(webcrypto): allow import and export ed25519

* fix(webcrypto): copy exportkey

* fix(webcrypto): fix use after stack free
2023-02-08 14:57:13 -08:00
Derrick Farris
7db87fe0cb fix(webcrypto): fix ed25519 keypair gen (#1985) 2023-02-04 00:24:35 -08:00
Jarred Sumner
76f72517ff try this? 2023-02-02 14:15:40 -08:00
Jarred Sumner
bb851b7e18 Register the algorithm 2023-02-02 03:26:15 -08:00
Jarred Sumner
fe3131cf17 ed25519 2023-02-02 02:42:58 -08:00
14 changed files with 1287 additions and 46 deletions

View File

@@ -0,0 +1,77 @@
/*
* Copyright (C) 2017 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 "CommonCryptoDERUtilities.h"
#if ENABLE(WEB_CRYPTO)
namespace WebCore {
size_t bytesUsedToEncodedLength(uint8_t octet)
{
if (octet < MaxLengthInOneByte)
return 1;
return octet - MaxLengthInOneByte + 1;
}
size_t extraBytesNeededForEncodedLength(size_t length)
{
if (!length)
return 0;
size_t result = 1;
while (result < sizeof(length) && length >= (1 << (result * 8)))
result += 1;
return result;
}
void addEncodedASN1Length(Vector<uint8_t>& in, size_t length)
{
if (length < MaxLengthInOneByte) {
in.append(length);
return;
}
size_t extraBytes = extraBytesNeededForEncodedLength(length);
in.append(128 + extraBytes); // 128 is used to set the first bit of this byte.
size_t lastPosition = in.size() + extraBytes - 1;
in.grow(in.size() + extraBytes);
for (size_t i = 0; i < extraBytes; i++) {
in[lastPosition - i] = length & 0xff;
length = length >> 8;
}
}
size_t bytesNeededForEncodedLength(size_t length)
{
if (length < MaxLengthInOneByte)
return 1;
return 1 + extraBytesNeededForEncodedLength(length);
}
} // namespace WebCore
#endif // ENABLE(WEB_CRYPTO)

View File

@@ -0,0 +1,56 @@
/*
* Copyright (C) 2017 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.
*/
#pragma once
#include <wtf/Vector.h>
#if ENABLE(WEB_CRYPTO)
// FIXME: <rdar://problem/31618371>
// The following constants and functions are for customized DER implementations.
// They are not intended to be used outside Crypto codes, and should be removed
// once the above bug is fixed.
namespace WebCore {
// Per X.690 08/2015: https://www.itu.int/rec/T-REC-X.680-X.693/en
static const unsigned char BitStringMark = 0x03;
static const unsigned char IntegerMark = 0x02;
static const unsigned char OctetStringMark = 0x04;
static const unsigned char SequenceMark = 0x30;
// Version 0. Per https://tools.ietf.org/html/rfc5208#section-5
static const unsigned char Version[] = {0x02, 0x01, 0x00};
static const unsigned char InitialOctet = 0x00;
static const size_t MaxLengthInOneByte = 128;
size_t bytesUsedToEncodedLength(uint8_t);
size_t extraBytesNeededForEncodedLength(size_t);
void addEncodedASN1Length(Vector<uint8_t>&, size_t);
size_t bytesNeededForEncodedLength(size_t);
} // namespace WebCore
#endif // ENABLE(WEB_CRYPTO)

View File

@@ -0,0 +1,210 @@
/*
* 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 "CryptoAlgorithmEd25519.h"
#if ENABLE(WEB_CRYPTO)
#include "CryptoKeyOKP.h"
#include <JavaScriptCore/JSCJSValueInlines.h>
#include <wtf/CrossThreadCopier.h>
// -- BUN --
#include <openssl/curve25519.h>
namespace WebCore {
static ExceptionOr<Vector<uint8_t>> signEd25519(const Vector<uint8_t>& sk, size_t len, const Vector<uint8_t>& data)
{
uint8_t newSignature[64];
ED25519_sign(newSignature, data.data(), data.size(), sk.data());
return Vector<uint8_t>(newSignature, 64);
}
ExceptionOr<Vector<uint8_t>> CryptoAlgorithmEd25519::platformSign(const CryptoKeyOKP& key, const Vector<uint8_t>& data)
{
return signEd25519(key.platformKey(), key.keySizeInBytes(), data);
}
static ExceptionOr<bool> verifyEd25519(const Vector<uint8_t>& key, size_t keyLengthInBytes, const Vector<uint8_t>& signature, const Vector<uint8_t> data)
{
if (signature.size() != keyLengthInBytes * 2)
return false;
return ED25519_verify(data.data(), data.size(), signature.data(), key.data()) == 1;
}
ExceptionOr<bool> CryptoAlgorithmEd25519::platformVerify(const CryptoKeyOKP& key, const Vector<uint8_t>& signature, const Vector<uint8_t>& data)
{
return verifyEd25519(key.platformKey(), key.keySizeInBytes(), signature, data);
}
Ref<CryptoAlgorithm> CryptoAlgorithmEd25519::create()
{
return adoptRef(*new CryptoAlgorithmEd25519);
}
void CryptoAlgorithmEd25519::generateKey(const CryptoAlgorithmParameters&, bool extractable, CryptoKeyUsageBitmap usages, KeyOrKeyPairCallback&& callback, ExceptionCallback&& exceptionCallback, ScriptExecutionContext&)
{
if (usages & (CryptoKeyUsageEncrypt | CryptoKeyUsageDecrypt | CryptoKeyUsageDeriveKey | CryptoKeyUsageDeriveBits | CryptoKeyUsageWrapKey | CryptoKeyUsageUnwrapKey)) {
exceptionCallback(SyntaxError);
return;
}
auto result = CryptoKeyOKP::generatePair(CryptoAlgorithmIdentifier::Ed25519, CryptoKeyOKP::NamedCurve::Ed25519, extractable, usages);
if (result.hasException()) {
exceptionCallback(result.releaseException().code());
return;
}
auto pair = result.releaseReturnValue();
pair.publicKey->setUsagesBitmap(pair.publicKey->usagesBitmap() & CryptoKeyUsageVerify);
pair.privateKey->setUsagesBitmap(pair.privateKey->usagesBitmap() & CryptoKeyUsageSign);
callback(WTFMove(pair));
}
void CryptoAlgorithmEd25519::sign(const CryptoAlgorithmParameters&, Ref<CryptoKey>&& key, Vector<uint8_t>&& data, VectorCallback&& callback, ExceptionCallback&& exceptionCallback, ScriptExecutionContext& context, WorkQueue& workQueue)
{
if (key->type() != CryptoKeyType::Private) {
exceptionCallback(InvalidAccessError);
return;
}
dispatchOperationInWorkQueue(workQueue, context, WTFMove(callback), WTFMove(exceptionCallback),
[key = WTFMove(key), data = WTFMove(data)] {
return platformSign(downcast<CryptoKeyOKP>(key.get()), data);
});
}
void CryptoAlgorithmEd25519::verify(const CryptoAlgorithmParameters&, Ref<CryptoKey>&& key, Vector<uint8_t>&& signature, Vector<uint8_t>&& data, BoolCallback&& callback, ExceptionCallback&& exceptionCallback, ScriptExecutionContext& context, WorkQueue& workQueue)
{
if (key->type() != CryptoKeyType::Public) {
exceptionCallback(InvalidAccessError);
return;
}
dispatchOperationInWorkQueue(workQueue, context, WTFMove(callback), WTFMove(exceptionCallback),
[key = WTFMove(key), signature = WTFMove(signature), data = WTFMove(data)] {
return platformVerify(downcast<CryptoKeyOKP>(key.get()), signature, data);
});
}
void CryptoAlgorithmEd25519::importKey(CryptoKeyFormat format, KeyData&& data, const CryptoAlgorithmParameters&, bool extractable, CryptoKeyUsageBitmap usages, KeyCallback&& callback, ExceptionCallback&& exceptionCallback)
{
RefPtr<CryptoKeyOKP> result;
switch (format) {
case CryptoKeyFormat::Jwk: {
JsonWebKey key = WTFMove(std::get<JsonWebKey>(data));
if (usages && ((!key.d.isNull() && (usages ^ CryptoKeyUsageSign)) || (key.d.isNull() && (usages ^ CryptoKeyUsageVerify)))) {
exceptionCallback(SyntaxError);
return;
}
if (usages && !key.use.isNull() && key.use != "sig"_s) {
exceptionCallback(DataError);
return;
}
result = CryptoKeyOKP::importJwk(CryptoAlgorithmIdentifier::Ed25519, CryptoKeyOKP::NamedCurve::Ed25519, WTFMove(key), extractable, usages);
break;
}
case CryptoKeyFormat::Raw:
if (usages && (usages ^ CryptoKeyUsageVerify)) {
exceptionCallback(SyntaxError);
return;
}
result = CryptoKeyOKP::importRaw(CryptoAlgorithmIdentifier::Ed25519, CryptoKeyOKP::NamedCurve::Ed25519, WTFMove(std::get<Vector<uint8_t>>(data)), extractable, usages);
break;
case CryptoKeyFormat::Spki:
if (usages && (usages ^ CryptoKeyUsageVerify)) {
exceptionCallback(SyntaxError);
return;
}
result = CryptoKeyOKP::importSpki(CryptoAlgorithmIdentifier::Ed25519, CryptoKeyOKP::NamedCurve::Ed25519, WTFMove(std::get<Vector<uint8_t>>(data)), extractable, usages);
break;
case CryptoKeyFormat::Pkcs8:
if (usages && (usages ^ CryptoKeyUsageSign)) {
exceptionCallback(SyntaxError);
return;
}
result = CryptoKeyOKP::importPkcs8(CryptoAlgorithmIdentifier::Ed25519, CryptoKeyOKP::NamedCurve::Ed25519, WTFMove(std::get<Vector<uint8_t>>(data)), extractable, usages);
break;
}
if (!result) {
exceptionCallback(DataError);
return;
}
callback(*result);
}
void CryptoAlgorithmEd25519::exportKey(CryptoKeyFormat format, Ref<CryptoKey>&& key, KeyDataCallback&& callback, ExceptionCallback&& exceptionCallback)
{
const auto& okpKey = downcast<CryptoKeyOKP>(key.get());
if (!okpKey.keySizeInBits()) {
exceptionCallback(OperationError);
return;
}
KeyData result;
switch (format) {
case CryptoKeyFormat::Jwk: {
auto jwk = okpKey.exportJwk();
if (jwk.hasException()) {
exceptionCallback(jwk.releaseException().code());
return;
}
result = jwk.releaseReturnValue();
break;
}
case CryptoKeyFormat::Raw: {
auto raw = okpKey.exportRaw();
if (raw.hasException()) {
exceptionCallback(raw.releaseException().code());
return;
}
result = raw.releaseReturnValue();
break;
}
case CryptoKeyFormat::Spki: {
auto spki = okpKey.exportSpki();
if (spki.hasException()) {
exceptionCallback(spki.releaseException().code());
return;
}
result = spki.releaseReturnValue();
break;
}
case CryptoKeyFormat::Pkcs8: {
auto pkcs8 = okpKey.exportPkcs8();
if (pkcs8.hasException()) {
exceptionCallback(pkcs8.releaseException().code());
return;
}
result = pkcs8.releaseReturnValue();
break;
}
}
callback(format, WTFMove(result));
}
} // namespace WebCore
#endif // ENABLE(WEB_CRYPTO)

View File

@@ -0,0 +1,61 @@
/*
* 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.
*/
#pragma once
#include "CryptoAlgorithm.h"
#if ENABLE(WEB_CRYPTO)
namespace WebCore {
class CryptoKeyOKP;
class CryptoAlgorithmEd25519 final : public CryptoAlgorithm {
public:
static constexpr ASCIILiteral s_name = "Ed25519"_s;
static constexpr CryptoAlgorithmIdentifier s_identifier = CryptoAlgorithmIdentifier::Ed25519;
static Ref<CryptoAlgorithm> create();
private:
CryptoAlgorithmEd25519() = default;
CryptoAlgorithmIdentifier identifier() const final;
void generateKey(const CryptoAlgorithmParameters& , bool extractable, CryptoKeyUsageBitmap usages, KeyOrKeyPairCallback&& , ExceptionCallback&& , ScriptExecutionContext&);
void sign(const CryptoAlgorithmParameters&, Ref<CryptoKey>&&, Vector<uint8_t>&&, VectorCallback&&, ExceptionCallback&&, ScriptExecutionContext&, WorkQueue&) final;
void verify(const CryptoAlgorithmParameters&, Ref<CryptoKey>&&, Vector<uint8_t>&& signature, Vector<uint8_t>&&, BoolCallback&&, ExceptionCallback&&, ScriptExecutionContext&, WorkQueue&) final;
void importKey(CryptoKeyFormat, KeyData&&, const CryptoAlgorithmParameters&, bool extractable, CryptoKeyUsageBitmap, KeyCallback&&, ExceptionCallback&&) final;
void exportKey(CryptoKeyFormat, Ref<CryptoKey>&&, KeyDataCallback&&, ExceptionCallback&&) final;
static ExceptionOr<Vector<uint8_t>> platformSign(const CryptoKeyOKP&, const Vector<uint8_t>&);
static ExceptionOr<bool> platformVerify(const CryptoKeyOKP&, const Vector<uint8_t>&, const Vector<uint8_t>&);
};
inline CryptoAlgorithmIdentifier CryptoAlgorithmEd25519::identifier() const
{
return s_identifier;
}
} // namespace WebCore
#endif // ENABLE(WEB_CRYPTO)

View File

@@ -48,7 +48,8 @@ enum class CryptoAlgorithmIdentifier {
SHA_384,
SHA_512,
HKDF,
PBKDF2
PBKDF2,
Ed25519
};
} // namespace WebCore

View File

@@ -35,6 +35,7 @@
#include "CryptoAlgorithmAES_KW.h"
#include "CryptoAlgorithmECDH.h"
#include "CryptoAlgorithmECDSA.h"
#include "CryptoAlgorithmEd25519.h"
#include "CryptoAlgorithmHKDF.h"
#include "CryptoAlgorithmHMAC.h"
#include "CryptoAlgorithmPBKDF2.h"
@@ -71,6 +72,7 @@ void CryptoAlgorithmRegistry::platformRegisterAlgorithms()
registerAlgorithm<CryptoAlgorithmSHA256>();
registerAlgorithm<CryptoAlgorithmSHA384>();
registerAlgorithm<CryptoAlgorithmSHA512>();
registerAlgorithm<CryptoAlgorithmEd25519>();
}
} // namespace WebCore

View File

@@ -73,9 +73,6 @@ WebCoreOpaqueRoot root(CryptoKey* key)
return WebCoreOpaqueRoot { key };
}
Vector<uint8_t> CryptoKey::randomData(size_t size)
{
Vector<uint8_t> result(size);

View File

@@ -51,6 +51,7 @@ enum class CryptoKeyClass {
AES,
EC,
HMAC,
OKP,
RSA,
Raw,
};
@@ -94,8 +95,11 @@ WebCoreOpaqueRoot root(CryptoKey*);
} // namespace WebCore
#define SPECIALIZE_TYPE_TRAITS_CRYPTO_KEY(ToClassName, KeyClass) \
SPECIALIZE_TYPE_TRAITS_BEGIN(WebCore::ToClassName) \
static bool isType(const WebCore::CryptoKey& key) { return key.keyClass() == WebCore::KeyClass; } \
SPECIALIZE_TYPE_TRAITS_END()
SPECIALIZE_TYPE_TRAITS_BEGIN(WebCore::ToClassName) \
static bool isType(const WebCore::CryptoKey& key) \
{ \
return key.keyClass() == WebCore::KeyClass; \
} \
SPECIALIZE_TYPE_TRAITS_END()
#endif // ENABLE(WEB_CRYPTO)

View File

@@ -0,0 +1,252 @@
/*
* 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 "CryptoAlgorithmRegistry.h"
#include "JsonWebKey.h"
#include <wtf/text/Base64.h>
namespace WebCore {
static const ASCIILiteral X25519 { "X25519"_s };
static const ASCIILiteral Ed25519 { "Ed25519"_s };
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;
}
}
RefPtr<CryptoKeyOKP> CryptoKeyOKP::create(CryptoAlgorithmIdentifier identifier, NamedCurve curve, CryptoKeyType type, KeyMaterial&& platformKey, bool extractable, CryptoKeyUsageBitmap usages)
{
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(data)
, m_exportKey(curve == NamedCurve::Ed25519 && type == CryptoKeyType::Private ? std::optional<Vector<uint8_t>>(Vector<uint8_t>(data.data(), 32)) : std::nullopt)
{
}
ExceptionOr<CryptoKeyPair> CryptoKeyOKP::generatePair(CryptoAlgorithmIdentifier identifier, NamedCurve namedCurve, bool extractable, CryptoKeyUsageBitmap usages)
{
if (!isPlatformSupportedCurve(namedCurve))
return Exception { NotSupportedError };
auto result = platformGeneratePair(identifier, namedCurve, extractable, usages);
if (!result)
return Exception { OperationError };
return WTFMove(*result);
}
RefPtr<CryptoKeyOKP> CryptoKeyOKP::importRaw(CryptoAlgorithmIdentifier identifier, NamedCurve namedCurve, Vector<uint8_t>&& keyData, bool extractable, CryptoKeyUsageBitmap usages)
{
if (!isPlatformSupportedCurve(namedCurve))
return nullptr;
return create(identifier, namedCurve, usages & CryptoKeyUsageSign ? CryptoKeyType::Private : CryptoKeyType::Public, WTFMove(keyData), extractable, usages);
}
RefPtr<CryptoKeyOKP> CryptoKeyOKP::importJwk(CryptoAlgorithmIdentifier identifier, NamedCurve namedCurve, JsonWebKey&& keyData, bool extractable, CryptoKeyUsageBitmap usages)
{
if (!isPlatformSupportedCurve(namedCurve))
return nullptr;
switch (namedCurve) {
case NamedCurve::Ed25519:
if (!keyData.d.isEmpty()) {
if (usages & (CryptoKeyUsageEncrypt | CryptoKeyUsageDecrypt | CryptoKeyUsageVerify | CryptoKeyUsageDeriveKey | CryptoKeyUsageDeriveBits | CryptoKeyUsageWrapKey | CryptoKeyUsageUnwrapKey))
return nullptr;
} else {
if (usages & (CryptoKeyUsageEncrypt | CryptoKeyUsageDecrypt | CryptoKeyUsageSign | CryptoKeyUsageDeriveKey | CryptoKeyUsageDeriveBits | CryptoKeyUsageWrapKey | CryptoKeyUsageUnwrapKey))
return nullptr;
}
if (keyData.kty != "OKP"_s)
return nullptr;
if (keyData.crv != "Ed25519"_s)
return nullptr;
if (!keyData.alg.isEmpty() && keyData.alg != "EdDSA"_s)
return nullptr;
if (usages && !keyData.use.isEmpty() && keyData.use != "sig"_s)
return nullptr;
if (keyData.key_ops && ((keyData.usages & usages) != usages))
return nullptr;
if (keyData.ext && !keyData.ext.value() && extractable)
return nullptr;
break;
case NamedCurve::X25519:
if (keyData.crv != "X25519"_s)
return nullptr;
// FIXME: Add further checks.
break;
}
if (!keyData.d.isNull()) {
// FIXME: Validate keyData.x is paired with keyData.d
auto d = base64URLDecode(keyData.d);
if (!d)
return nullptr;
return create(identifier, namedCurve, CryptoKeyType::Private, WTFMove(*d), extractable, usages);
}
if (keyData.x.isNull())
return nullptr;
auto x = base64URLDecode(keyData.x);
if (!x)
return nullptr;
return create(identifier, namedCurve, CryptoKeyType::Public, WTFMove(*x), extractable, usages);
}
ExceptionOr<Vector<uint8_t>> CryptoKeyOKP::exportRaw() const
{
if (type() != CryptoKey::Type::Public)
return Exception { InvalidAccessError };
auto&& result = platformExportRaw();
if (result.isEmpty())
return Exception { OperationError };
return WTFMove(result);
}
ExceptionOr<JsonWebKey> CryptoKeyOKP::exportJwk() const
{
JsonWebKey result;
result.kty = "OKP"_s;
switch (m_curve) {
case NamedCurve::X25519:
result.crv = X25519;
break;
case NamedCurve::Ed25519:
result.crv = Ed25519;
break;
}
result.key_ops = usages();
result.ext = extractable();
switch (type()) {
case CryptoKeyType::Private:
result.d = generateJwkD();
result.x = generateJwkX();
break;
case CryptoKeyType::Public:
result.x = generateJwkX();
break;
case CryptoKeyType::Secret:
return Exception { OperationError };
}
return result;
}
String CryptoKeyOKP::namedCurveString() const
{
switch (m_curve) {
case NamedCurve::X25519:
return X25519;
case NamedCurve::Ed25519:
return Ed25519;
}
ASSERT_NOT_REACHED();
return emptyString();
}
bool CryptoKeyOKP::isValidOKPAlgorithm(CryptoAlgorithmIdentifier algorithm)
{
return algorithm == CryptoAlgorithmIdentifier::Ed25519;
}
auto CryptoKeyOKP::algorithm() const -> KeyAlgorithm
{
CryptoEcKeyAlgorithm result;
result.name = CryptoAlgorithmRegistry::singleton().name(algorithmIdentifier());
switch (m_curve) {
case NamedCurve::X25519:
result.namedCurve = X25519;
break;
case NamedCurve::Ed25519:
result.namedCurve = Ed25519;
break;
}
return result;
}
} // namespace WebCore
#endif // ENABLE(WEB_CRYPTO)

View File

@@ -0,0 +1,101 @@
/*
* 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.
*/
#pragma once
#include "CryptoKey.h"
#include "CryptoKeyPair.h"
#include "ExceptionOr.h"
#if ENABLE(WEB_CRYPTO)
namespace WebCore {
struct JsonWebKey;
class CryptoKeyOKP final : public CryptoKey {
public:
using KeyMaterial = Vector<uint8_t>;
enum class NamedCurve {
X25519,
Ed25519,
};
static RefPtr<CryptoKeyOKP> create(CryptoAlgorithmIdentifier, NamedCurve, CryptoKeyType, KeyMaterial&&, bool extractable, CryptoKeyUsageBitmap);
WEBCORE_EXPORT static ExceptionOr<CryptoKeyPair> generatePair(CryptoAlgorithmIdentifier, NamedCurve, bool extractable, CryptoKeyUsageBitmap);
WEBCORE_EXPORT static RefPtr<CryptoKeyOKP> importRaw(CryptoAlgorithmIdentifier, NamedCurve, Vector<uint8_t>&& keyData, bool extractable, CryptoKeyUsageBitmap);
static RefPtr<CryptoKeyOKP> importJwk(CryptoAlgorithmIdentifier, NamedCurve, JsonWebKey&&, bool extractable, CryptoKeyUsageBitmap);
static RefPtr<CryptoKeyOKP> importSpki(CryptoAlgorithmIdentifier, NamedCurve, Vector<uint8_t>&& keyData, bool extractable, CryptoKeyUsageBitmap);
static RefPtr<CryptoKeyOKP> importPkcs8(CryptoAlgorithmIdentifier, NamedCurve, Vector<uint8_t>&& keyData, bool extractable, CryptoKeyUsageBitmap);
WEBCORE_EXPORT ExceptionOr<Vector<uint8_t>> exportRaw() const;
ExceptionOr<JsonWebKey> exportJwk() const;
ExceptionOr<Vector<uint8_t>> exportSpki() const;
ExceptionOr<Vector<uint8_t>> exportPkcs8() const;
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);
CryptoKeyClass keyClass() const final { return CryptoKeyClass::OKP; }
KeyAlgorithm algorithm() const final;
String generateJwkD() const;
String generateJwkX() const;
static bool isPlatformSupportedCurve(NamedCurve);
static std::optional<CryptoKeyPair> platformGeneratePair(CryptoAlgorithmIdentifier, NamedCurve, bool extractable, CryptoKeyUsageBitmap);
Vector<uint8_t> platformExportRaw() const;
Vector<uint8_t> platformExportSpki() const;
Vector<uint8_t> platformExportPkcs8() const;
NamedCurve m_curve;
KeyMaterial m_data;
std::optional<KeyMaterial> m_exportKey;
};
} // namespace WebCore
SPECIALIZE_TYPE_TRAITS_CRYPTO_KEY(CryptoKeyOKP, CryptoKeyClass::OKP)
#endif // ENABLE(WEB_CRYPTO)

View File

@@ -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)

View File

@@ -88,6 +88,22 @@ static ExceptionOr<CryptoAlgorithmIdentifier> toHashIdentifier(JSGlobalObject& s
return digestParams.returnValue()->identifier;
}
static bool isRSAESPKCSWebCryptoDeprecated(JSGlobalObject& state)
{
return true;
// auto& globalObject = *JSC::jsCast<JSDOMGlobalObject*>(&state);
// auto* context = globalObject.scriptExecutionContext();
// return context && context->settingsValues().deprecateRSAESPKCSWebCryptoEnabled;
}
static bool isSafeCurvesEnabled(JSGlobalObject& state)
{
return true;
// auto& globalObject = *JSC::jsCast<JSDOMGlobalObject*>(&state);
// auto* context = globalObject.scriptExecutionContext();
// return context && context->settingsValues().webCryptoSafeCurvesEnabled;
}
static ExceptionOr<std::unique_ptr<CryptoAlgorithmParameters>> normalizeCryptoAlgorithmParameters(JSGlobalObject& state, SubtleCrypto::AlgorithmIdentifier algorithmIdentifier, Operations operation)
{
VM& vm = state.vm();
@@ -109,12 +125,17 @@ static ExceptionOr<std::unique_ptr<CryptoAlgorithmParameters>> normalizeCryptoAl
if (UNLIKELY(!identifier))
return Exception { NotSupportedError };
if (*identifier == CryptoAlgorithmIdentifier::Ed25519 && !isSafeCurvesEnabled(state))
return Exception { NotSupportedError };
std::unique_ptr<CryptoAlgorithmParameters> result;
switch (operation) {
case Operations::Encrypt:
case Operations::Decrypt:
switch (*identifier) {
case CryptoAlgorithmIdentifier::RSAES_PKCS1_v1_5:
if (isRSAESPKCSWebCryptoDeprecated(state))
return Exception { NotSupportedError, "RSAES-PKCS1-v1_5 support is deprecated"_s };
result = makeUnique<CryptoAlgorithmParameters>(params);
break;
case CryptoAlgorithmIdentifier::RSA_OAEP: {
@@ -151,6 +172,7 @@ static ExceptionOr<std::unique_ptr<CryptoAlgorithmParameters>> normalizeCryptoAl
switch (*identifier) {
case CryptoAlgorithmIdentifier::RSASSA_PKCS1_v1_5:
case CryptoAlgorithmIdentifier::HMAC:
case CryptoAlgorithmIdentifier::Ed25519:
result = makeUnique<CryptoAlgorithmParameters>(params);
break;
case CryptoAlgorithmIdentifier::ECDSA: {
@@ -189,6 +211,8 @@ static ExceptionOr<std::unique_ptr<CryptoAlgorithmParameters>> normalizeCryptoAl
case Operations::GenerateKey:
switch (*identifier) {
case CryptoAlgorithmIdentifier::RSAES_PKCS1_v1_5: {
if (isRSAESPKCSWebCryptoDeprecated(state))
return Exception { NotSupportedError, "RSAES-PKCS1-v1_5 support is deprecated"_s };
auto params = convertDictionary<CryptoAlgorithmRsaKeyGenParams>(state, value.get());
RETURN_IF_EXCEPTION(scope, Exception { ExistingExceptionError });
result = makeUnique<CryptoAlgorithmRsaKeyGenParams>(params);
@@ -233,6 +257,9 @@ static ExceptionOr<std::unique_ptr<CryptoAlgorithmParameters>> normalizeCryptoAl
result = makeUnique<CryptoAlgorithmEcKeyParams>(params);
break;
}
case CryptoAlgorithmIdentifier::Ed25519:
result = makeUnique<CryptoAlgorithmParameters>(params);
break;
default:
return Exception { NotSupportedError };
}
@@ -279,6 +306,8 @@ static ExceptionOr<std::unique_ptr<CryptoAlgorithmParameters>> normalizeCryptoAl
case Operations::ImportKey:
switch (*identifier) {
case CryptoAlgorithmIdentifier::RSAES_PKCS1_v1_5:
if (isRSAESPKCSWebCryptoDeprecated(state))
return Exception { NotSupportedError, "RSAES-PKCS1-v1_5 support is deprecated"_s };
result = makeUnique<CryptoAlgorithmParameters>(params);
break;
case CryptoAlgorithmIdentifier::RSASSA_PKCS1_v1_5:
@@ -298,6 +327,7 @@ static ExceptionOr<std::unique_ptr<CryptoAlgorithmParameters>> normalizeCryptoAl
case CryptoAlgorithmIdentifier::AES_GCM:
case CryptoAlgorithmIdentifier::AES_CFB:
case CryptoAlgorithmIdentifier::AES_KW:
case CryptoAlgorithmIdentifier::Ed25519:
result = makeUnique<CryptoAlgorithmParameters>(params);
break;
case CryptoAlgorithmIdentifier::HMAC: {
@@ -321,7 +351,11 @@ static ExceptionOr<std::unique_ptr<CryptoAlgorithmParameters>> normalizeCryptoAl
case CryptoAlgorithmIdentifier::PBKDF2:
result = makeUnique<CryptoAlgorithmParameters>(params);
break;
default:
case CryptoAlgorithmIdentifier::SHA_1:
case CryptoAlgorithmIdentifier::SHA_224:
case CryptoAlgorithmIdentifier::SHA_256:
case CryptoAlgorithmIdentifier::SHA_384:
case CryptoAlgorithmIdentifier::SHA_512:
return Exception { NotSupportedError };
}
break;
@@ -482,10 +516,11 @@ static Vector<uint8_t> copyToVector(BufferSource&& data)
return { data.data(), data.length() };
}
static bool isSupportedExportKey(CryptoAlgorithmIdentifier identifier)
static bool isSupportedExportKey(JSGlobalObject& state, CryptoAlgorithmIdentifier identifier)
{
switch (identifier) {
case CryptoAlgorithmIdentifier::RSAES_PKCS1_v1_5:
return !isRSAESPKCSWebCryptoDeprecated(state);
case CryptoAlgorithmIdentifier::RSASSA_PKCS1_v1_5:
case CryptoAlgorithmIdentifier::RSA_PSS:
case CryptoAlgorithmIdentifier::RSA_OAEP:
@@ -497,6 +532,7 @@ static bool isSupportedExportKey(CryptoAlgorithmIdentifier identifier)
case CryptoAlgorithmIdentifier::HMAC:
case CryptoAlgorithmIdentifier::ECDSA:
case CryptoAlgorithmIdentifier::ECDH:
case CryptoAlgorithmIdentifier::Ed25519:
return true;
default:
return false;
@@ -932,7 +968,7 @@ void SubtleCrypto::importKey(JSC::JSGlobalObject& state, KeyFormat format, KeyDa
void SubtleCrypto::exportKey(KeyFormat format, CryptoKey& key, Ref<DeferredPromise>&& promise)
{
if (!isSupportedExportKey(key.algorithmIdentifier())) {
if (!isSupportedExportKey(*promise->globalObject(), key.algorithmIdentifier())) {
promise->reject(Exception { NotSupportedError });
return;
}
@@ -1003,7 +1039,7 @@ void SubtleCrypto::wrapKey(JSC::JSGlobalObject& state, KeyFormat format, CryptoK
return;
}
if (!isSupportedExportKey(key.algorithmIdentifier())) {
if (!isSupportedExportKey(state, key.algorithmIdentifier())) {
promise->reject(Exception { NotSupportedError });
return;
}

View File

@@ -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" \

View File

@@ -1,4 +1,9 @@
import { describe, expect, it } from "bun:test";
import { describe, expect, it, beforeAll } from "bun:test";
type Ed25519KeyPair = {
privateKey: CryptoKey;
publicKey: CryptoKey;
};
describe("Web Crypto", () => {
it("has globals", () => {
@@ -71,4 +76,104 @@ describe("Web Crypto", () => {
const isSigValid = await verifySignature(msg, signature, SECRET);
expect(isSigValid).toBe(true);
});
describe("Ed25519", () => {
let importedKeypair: Ed25519KeyPair;
describe("generateKey", () => {
it("should generate key pair", async () => {
const keyPair = (await crypto.subtle.generateKey("Ed25519", true, ["sign", "verify"])) as Ed25519KeyPair;
expect(keyPair.privateKey).toBeDefined();
expect(keyPair.privateKey instanceof CryptoKey).toBe(true);
expect(keyPair.publicKey).toBeDefined();
expect(keyPair.publicKey instanceof CryptoKey).toBe(true);
});
it("should generate an extractable key pair", async () => {
const keyPair = (await crypto.subtle.generateKey("Ed25519", true, ["sign", "verify"])) as Ed25519KeyPair;
const privateKey = await crypto.subtle.exportKey("jwk", keyPair.privateKey);
const publicKey = await crypto.subtle.exportKey("jwk", keyPair.publicKey);
expect(privateKey).toBeDefined();
expect(privateKey.d).toBeDefined();
expect(privateKey.x).toBeDefined();
expect(privateKey.x!.length).toBe(43);
expect(privateKey.d!.length).toBe(43);
expect(privateKey.kty).toEqual("OKP");
expect(privateKey.crv).toEqual("Ed25519");
expect(privateKey.ext).toBe(true);
expect(privateKey.key_ops).toStrictEqual(["sign"]);
expect(publicKey).toBeDefined();
expect(publicKey.x).toBeDefined();
expect(publicKey.x!.length).toBe(43);
expect(publicKey.kty).toEqual("OKP");
expect(publicKey.crv).toEqual("Ed25519");
expect(publicKey.ext).toBe(true);
expect(publicKey.key_ops).toStrictEqual(["verify"]);
});
it("should generate an nonextractable private key", async done => {
const keyPair = (await crypto.subtle.generateKey("Ed25519", false, ["sign", "verify"])) as Ed25519KeyPair;
expect(keyPair.privateKey).toBeDefined();
expect(keyPair.publicKey).toBeDefined();
try {
await crypto.subtle.exportKey("jwk", keyPair.privateKey);
done(new Error("Should not be able to export private key"));
} catch (e) {
if (!(e instanceof Error)) {
process.exit(1);
} else {
expect(e.message).toBe("The CryptoKey is nonextractable");
done();
}
}
});
it("should generate keys with correct usages", async () => {
const keyPair1 = (await crypto.subtle.generateKey("Ed25519", false, ["sign"])) as Ed25519KeyPair;
const keyPair2 = (await crypto.subtle.generateKey("Ed25519", false, ["sign", "verify"])) as Ed25519KeyPair;
expect(keyPair1.privateKey?.usages).toBeDefined();
expect(keyPair1.publicKey?.usages).toBeDefined();
expect(keyPair1.privateKey?.usages).toStrictEqual(["sign"]);
expect(keyPair1.publicKey?.usages).toStrictEqual([]);
expect(keyPair2.privateKey?.usages).toBeDefined();
expect(keyPair2.publicKey?.usages).toBeDefined();
expect(keyPair2.privateKey?.usages).toEqual(["sign"]);
expect(keyPair2.publicKey?.usages).toEqual(["verify"]);
});
});
describe("importKey", () => {
it("should do raw import", async () => {
const privateKey = "whvdISVptebNycNBnzsGltGsSWhThuD-mP2tcsBbNt8";
const buf = Buffer.from(privateKey, "base64url");
console.log(buf);
const imported = await crypto.subtle.importKey("raw", buf, "Ed25519", false, ["sign", "verify"]);
});
it("should do JWK import", async () => {
const kp = await crypto.subtle.importKey(
"jwk",
{
kty: "OKP",
d: "whvdISVptebNycNBnzsGltGsSWhThuD-mP2tcsBbNt8",
use: "sig",
crv: "Ed25519",
// kid: "sig-1675587884",
x: "jZJN1eHyhwujYgS9btOxrSGJuVrWVmJMmkovz6vmmJQ",
alg: "EdDSA",
ext: true,
},
"Ed25519",
true,
["sign"],
);
});
it("should do PKCS8 import", () => {});
});
});
});