CryptoKeyECMac.cpp [plain text]
#include "config.h"
#include "CryptoKeyEC.h"
#if ENABLE(SUBTLE_CRYPTO)
#include "CommonCryptoDERUtilities.h"
#include "CommonCryptoUtilities.h"
#include "JsonWebKey.h"
#include <wtf/text/Base64.h>
namespace WebCore {
static const unsigned char InitialOctetEC = 0x04; static const unsigned char IdEcPublicKey[] = {0x06, 0x07, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, 0x01};
static constexpr unsigned char Secp256r1[] = {0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07};
static constexpr unsigned char Secp384r1[] = {0x06, 0x05, 0x2b, 0x81, 0x04, 0x00, 0x22};
static const unsigned char PrivateKeyVersion[] = {0x02, 0x01, 0x01};
static const unsigned char TaggedType1 = 0xa1;
static bool doesUncompressedPointMatchNamedCurve(CryptoKeyEC::NamedCurve curve, size_t size)
{
switch (curve) {
case CryptoKeyEC::NamedCurve::P256:
return size == 65;
case CryptoKeyEC::NamedCurve::P384:
return size == 97;
case CryptoKeyEC::NamedCurve::P521:
break;
}
ASSERT_NOT_REACHED();
return false;
}
static bool doesFieldElementMatchNamedCurve(CryptoKeyEC::NamedCurve curve, size_t size)
{
switch (curve) {
case CryptoKeyEC::NamedCurve::P256:
return size == 32;
case CryptoKeyEC::NamedCurve::P384:
return size == 48;
case CryptoKeyEC::NamedCurve::P521:
break;
}
ASSERT_NOT_REACHED();
return false;
}
static size_t getKeySizeFromNamedCurve(CryptoKeyEC::NamedCurve curve)
{
switch (curve) {
case CryptoKeyEC::NamedCurve::P256:
return 256;
case CryptoKeyEC::NamedCurve::P384:
return 384;
case CryptoKeyEC::NamedCurve::P521:
break;
}
ASSERT_NOT_REACHED();
return 0;
}
CryptoKeyEC::~CryptoKeyEC()
{
CCECCryptorRelease(m_platformKey);
}
size_t CryptoKeyEC::keySizeInBits() const
{
int result = CCECGetKeySize(m_platformKey);
return result ? result : 0;
}
bool CryptoKeyEC::platformSupportedCurve(NamedCurve curve)
{
return curve == NamedCurve::P256 || curve == NamedCurve::P384;
}
std::optional<CryptoKeyPair> CryptoKeyEC::platformGeneratePair(CryptoAlgorithmIdentifier identifier, NamedCurve curve, bool extractable, CryptoKeyUsageBitmap usages)
{
size_t size = getKeySizeFromNamedCurve(curve);
CCECCryptorRef ccPublicKey;
CCECCryptorRef ccPrivateKey;
if (CCECCryptorGeneratePair(size, &ccPublicKey, &ccPrivateKey))
return std::nullopt;
auto publicKey = CryptoKeyEC::create(identifier, curve, CryptoKeyType::Public, ccPublicKey, true, usages);
auto privateKey = CryptoKeyEC::create(identifier, curve, CryptoKeyType::Private, ccPrivateKey, extractable, usages);
return CryptoKeyPair { WTFMove(publicKey), WTFMove(privateKey) };
}
RefPtr<CryptoKeyEC> CryptoKeyEC::platformImportRaw(CryptoAlgorithmIdentifier identifier, NamedCurve curve, Vector<uint8_t>&& keyData, bool extractable, CryptoKeyUsageBitmap usages)
{
if (!doesUncompressedPointMatchNamedCurve(curve, keyData.size()))
return nullptr;
CCECCryptorRef ccPublicKey;
if (CCECCryptorImportKey(kCCImportKeyBinary, keyData.data(), keyData.size(), ccECKeyPublic, &ccPublicKey))
return nullptr;
return create(identifier, curve, CryptoKeyType::Public, ccPublicKey, extractable, usages);
}
Vector<uint8_t> CryptoKeyEC::platformExportRaw() const
{
size_t expectedSize = keySizeInBits() / 4 + 1; Vector<uint8_t> result(expectedSize);
size_t size = result.size();
if (UNLIKELY(CCECCryptorExportKey(kCCImportKeyBinary, result.data(), &size, ccECKeyPublic, m_platformKey) || size != expectedSize))
return { };
return result;
}
RefPtr<CryptoKeyEC> CryptoKeyEC::platformImportJWKPublic(CryptoAlgorithmIdentifier identifier, NamedCurve curve, Vector<uint8_t>&& x, Vector<uint8_t>&& y, bool extractable, CryptoKeyUsageBitmap usages)
{
if (!doesFieldElementMatchNamedCurve(curve, x.size()) || !doesFieldElementMatchNamedCurve(curve, y.size()))
return nullptr;
size_t size = getKeySizeFromNamedCurve(curve);
CCECCryptorRef ccPublicKey;
if (CCECCryptorCreateFromData(size, x.data(), x.size(), y.data(), y.size(), &ccPublicKey))
return nullptr;
return create(identifier, curve, CryptoKeyType::Public, ccPublicKey, extractable, usages);
}
RefPtr<CryptoKeyEC> CryptoKeyEC::platformImportJWKPrivate(CryptoAlgorithmIdentifier identifier, NamedCurve curve, Vector<uint8_t>&& x, Vector<uint8_t>&& y, Vector<uint8_t>&& d, bool extractable, CryptoKeyUsageBitmap usages)
{
if (!doesFieldElementMatchNamedCurve(curve, x.size()) || !doesFieldElementMatchNamedCurve(curve, y.size()) || !doesFieldElementMatchNamedCurve(curve, d.size()))
return nullptr;
Vector<uint8_t> binaryInput;
binaryInput.append(InitialOctetEC);
binaryInput.appendVector(x);
binaryInput.appendVector(y);
binaryInput.appendVector(d);
CCECCryptorRef ccPrivateKey;
if (CCECCryptorImportKey(kCCImportKeyBinary, binaryInput.data(), binaryInput.size(), ccECKeyPrivate, &ccPrivateKey))
return nullptr;
return create(identifier, curve, CryptoKeyType::Private, ccPrivateKey, extractable, usages);
}
bool CryptoKeyEC::platformAddFieldElements(JsonWebKey& jwk) const
{
size_t keySizeInBytes = keySizeInBits() / 8;
size_t publicKeySize = keySizeInBytes * 2 + 1; size_t privateKeySize = keySizeInBytes * 3 + 1;
Vector<uint8_t> result(privateKeySize);
size_t size = result.size();
switch (type()) {
case CryptoKeyType::Public:
if (UNLIKELY(CCECCryptorExportKey(kCCImportKeyBinary, result.data(), &size, ccECKeyPublic, m_platformKey)))
return false;
break;
case CryptoKeyType::Private:
if (UNLIKELY(CCECCryptorExportKey(kCCImportKeyBinary, result.data(), &size, ccECKeyPrivate, m_platformKey)))
return false;
break;
default:
ASSERT_NOT_REACHED();
return false;
}
if (UNLIKELY((size != publicKeySize) && (size != privateKeySize)))
return false;
jwk.x = WTF::base64URLEncode(result.data() + 1, keySizeInBytes);
jwk.y = WTF::base64URLEncode(result.data() + keySizeInBytes + 1, keySizeInBytes);
if (size > publicKeySize)
jwk.d = WTF::base64URLEncode(result.data() + publicKeySize, keySizeInBytes);
return true;
}
static size_t getOID(CryptoKeyEC::NamedCurve curve, const uint8_t*& oid)
{
size_t oidSize;
switch (curve) {
case CryptoKeyEC::NamedCurve::P256:
oid = Secp256r1;
oidSize = sizeof(Secp256r1);
break;
case CryptoKeyEC::NamedCurve::P384:
oid = Secp384r1;
oidSize = sizeof(Secp384r1);
break;
case CryptoKeyEC::NamedCurve::P521:
ASSERT_NOT_REACHED();
oid = nullptr;
oidSize = 0;
break;
}
return oidSize;
}
RefPtr<CryptoKeyEC> CryptoKeyEC::platformImportSpki(CryptoAlgorithmIdentifier identifier, NamedCurve curve, Vector<uint8_t>&& keyData, bool extractable, CryptoKeyUsageBitmap usages)
{
size_t index = 1; if (keyData.size() < index + 1)
return nullptr;
index += bytesUsedToEncodedLength(keyData[index]) + 1; if (keyData.size() < index + 1)
return nullptr;
index += bytesUsedToEncodedLength(keyData[index]); if (keyData.size() < index + sizeof(IdEcPublicKey))
return nullptr;
if (memcmp(keyData.data() + index, IdEcPublicKey, sizeof(IdEcPublicKey)))
return nullptr;
index += sizeof(IdEcPublicKey); const uint8_t* oid;
size_t oidSize = getOID(curve, oid);
if (keyData.size() < index + oidSize)
return nullptr;
if (memcmp(keyData.data() + index, oid, oidSize))
return nullptr;
index += oidSize + 1; if (keyData.size() < index + 1)
return nullptr;
index += bytesUsedToEncodedLength(keyData[index]) + 1;
if (!doesUncompressedPointMatchNamedCurve(curve, keyData.size() - index))
return nullptr;
CCECCryptorRef ccPublicKey;
if (CCECCryptorImportKey(kCCImportKeyBinary, keyData.data() + index, keyData.size() - index, ccECKeyPublic, &ccPublicKey))
return nullptr;
return create(identifier, curve, CryptoKeyType::Public, ccPublicKey, extractable, usages);
}
Vector<uint8_t> CryptoKeyEC::platformExportSpki() const
{
size_t expectedKeySize = keySizeInBits() / 4 + 1; Vector<uint8_t> keyBytes(expectedKeySize);
size_t keySize = keyBytes.size();
if (UNLIKELY(CCECCryptorExportKey(kCCImportKeyBinary, keyBytes.data(), &keySize, ccECKeyPublic, m_platformKey) || keySize != expectedKeySize))
return { };
const uint8_t* oid;
size_t oidSize = getOID(namedCurve(), oid);
size_t totalSize = sizeof(IdEcPublicKey) + oidSize + bytesNeededForEncodedLength(keySize + 1) + keySize + 4;
Vector<uint8_t> result;
result.reserveCapacity(totalSize + bytesNeededForEncodedLength(totalSize) + 1);
result.append(SequenceMark);
addEncodedASN1Length(result, totalSize);
result.append(SequenceMark);
addEncodedASN1Length(result, sizeof(IdEcPublicKey) + oidSize);
result.append(IdEcPublicKey, sizeof(IdEcPublicKey));
result.append(oid, oidSize);
result.append(BitStringMark);
addEncodedASN1Length(result, keySize + 1);
result.append(InitialOctet);
result.append(keyBytes.data(), keyBytes.size());
return result;
}
RefPtr<CryptoKeyEC> CryptoKeyEC::platformImportPkcs8(CryptoAlgorithmIdentifier identifier, NamedCurve curve, Vector<uint8_t>&& keyData, bool extractable, CryptoKeyUsageBitmap usages)
{
size_t index = 1; if (keyData.size() < index + 1)
return nullptr;
index += bytesUsedToEncodedLength(keyData[index]) + 4; if (keyData.size() < index + 1)
return nullptr;
index += bytesUsedToEncodedLength(keyData[index]); if (keyData.size() < index + sizeof(IdEcPublicKey))
return nullptr;
if (memcmp(keyData.data() + index, IdEcPublicKey, sizeof(IdEcPublicKey)))
return nullptr;
index += sizeof(IdEcPublicKey); const uint8_t* oid;
size_t oidSize = getOID(curve, oid);
if (keyData.size() < index + oidSize)
return nullptr;
if (memcmp(keyData.data() + index, oid, oidSize))
return nullptr;
index += oidSize + 1; if (keyData.size() < index + 1)
return nullptr;
index += bytesUsedToEncodedLength(keyData[index]) + 1; if (keyData.size() < index + 1)
return nullptr;
index += bytesUsedToEncodedLength(keyData[index]) + 4; if (keyData.size() < index + 1)
return nullptr;
index += bytesUsedToEncodedLength(keyData[index]);
if (keyData.size() < index + getKeySizeFromNamedCurve(curve) / 8)
return nullptr;
size_t privateKeyPos = index;
index += getKeySizeFromNamedCurve(curve) / 8 + 1; if (keyData.size() < index + 1)
return nullptr;
index += bytesUsedToEncodedLength(keyData[index]) + 1; if (keyData.size() < index + 1)
return nullptr;
index += bytesUsedToEncodedLength(keyData[index]) + 1;
Vector<uint8_t> keyBinary;
keyBinary.append(keyData.data() + index, keyData.size() - index);
if (!doesUncompressedPointMatchNamedCurve(curve, keyBinary.size()))
return nullptr;
keyBinary.append(keyData.data() + privateKeyPos, getKeySizeFromNamedCurve(curve) / 8);
CCECCryptorRef ccPrivateKey;
if (CCECCryptorImportKey(kCCImportKeyBinary, keyBinary.data(), keyBinary.size(), ccECKeyPrivate, &ccPrivateKey))
return nullptr;
return create(identifier, curve, CryptoKeyType::Private, ccPrivateKey, extractable, usages);
}
Vector<uint8_t> CryptoKeyEC::platformExportPkcs8() const
{
size_t keySizeInBytes = keySizeInBits() / 8;
size_t expectedKeySize = keySizeInBytes * 3 + 1; Vector<uint8_t> keyBytes(expectedKeySize);
size_t keySize = keyBytes.size();
if (UNLIKELY(CCECCryptorExportKey(kCCImportKeyBinary, keyBytes.data(), &keySize, ccECKeyPrivate, m_platformKey) || keySize != expectedKeySize))
return { };
const uint8_t* oid;
size_t oidSize = getOID(namedCurve(), oid);
size_t publicKeySize = keySizeInBytes * 2 + 2;
size_t taggedTypeSize = bytesNeededForEncodedLength(publicKeySize) + publicKeySize + 1;
size_t ecPrivateKeySize = sizeof(Version) + keySizeInBytes + bytesNeededForEncodedLength(taggedTypeSize) + bytesNeededForEncodedLength(publicKeySize) + publicKeySize + 4;
size_t privateKeySize = bytesNeededForEncodedLength(ecPrivateKeySize) + ecPrivateKeySize + 1;
size_t totalSize = sizeof(Version) + sizeof(IdEcPublicKey) + oidSize + bytesNeededForEncodedLength(privateKeySize) + privateKeySize + 3;
Vector<uint8_t> result;
result.reserveCapacity(totalSize + bytesNeededForEncodedLength(totalSize) + 1);
result.append(SequenceMark);
addEncodedASN1Length(result, totalSize);
result.append(Version, sizeof(Version));
result.append(SequenceMark);
addEncodedASN1Length(result, sizeof(IdEcPublicKey) + oidSize);
result.append(IdEcPublicKey, sizeof(IdEcPublicKey));
result.append(oid, oidSize);
result.append(OctetStringMark);
addEncodedASN1Length(result, privateKeySize);
result.append(SequenceMark);
addEncodedASN1Length(result, ecPrivateKeySize);
result.append(PrivateKeyVersion, sizeof(PrivateKeyVersion));
result.append(OctetStringMark);
addEncodedASN1Length(result, keySizeInBytes);
result.append(keyBytes.data() + publicKeySize - 1, keySizeInBytes);
result.append(TaggedType1);
addEncodedASN1Length(result, taggedTypeSize);
result.append(BitStringMark);
addEncodedASN1Length(result, publicKeySize);
result.append(InitialOctet);
result.append(keyBytes.data(), publicKeySize - 1);
return result;
}
}
#endif // ENABLE(SUBTLE_CRYPTO)