blob: 01a76975da6a73f036c3120dadcd18a1228a0cb7 [file] [log] [blame]
/*
*
* Copyright (c) 2021 Project CHIP Authors
* Copyright (c) 2013-2017 Nest Labs, Inc.
* All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @file
* This file implements utility functions for reading, parsing,
* encoding, and decoding CHIP key material.
*
*/
#include "chip-cert.h"
#include <lib/support/BufferWriter.h>
#include <protocols/Protocols.h>
using namespace chip;
using namespace chip::Protocols;
using namespace chip::ASN1;
using namespace chip::TLV;
using namespace chip::Crypto;
namespace {
KeyFormat DetectKeyFormat(const uint8_t * key, uint32_t keyLen)
{
static uint32_t p256SerializedKeypairLen = kP256_PublicKey_Length + kP256_PrivateKey_Length;
static const char * ecPEMMarker = "-----BEGIN EC PRIVATE KEY-----";
static const char * pkcs8PEMMarker = "-----BEGIN PRIVATE KEY-----";
static const char * ecPUBPEMMarker = "-----BEGIN PUBLIC KEY-----";
if (keyLen == p256SerializedKeypairLen)
{
return kKeyFormat_Chip_Raw;
}
if (keyLen == BASE64_ENCODED_LEN(p256SerializedKeypairLen))
{
return kKeyFormat_Chip_Base64;
}
if (ContainsPEMMarker(ecPEMMarker, key, keyLen) || ContainsPEMMarker(pkcs8PEMMarker, key, keyLen))
{
return kKeyFormat_X509_PEM;
}
if (ContainsPEMMarker(ecPUBPEMMarker, key, keyLen))
{
return kKeyFormat_X509_PUBKEY_PEM;
}
return kKeyFormat_X509_DER;
}
bool SerializeKeyPair(EVP_PKEY * key, uint8_t * chipKey, uint32_t chipKeyBufSize, uint32_t & chipKeyLen)
{
bool res = true;
const BIGNUM * privKeyBN = nullptr;
const EC_GROUP * group = nullptr;
const EC_KEY * ecKey = nullptr;
const EC_POINT * ecPoint = nullptr;
uint8_t * pubKey = chipKey;
uint8_t * privKey = chipKey + kP256_PublicKey_Length;
size_t pubKeyLen = 0;
int privKeyLen = 0;
VerifyOrExit(chipKeyBufSize >= kP256_PublicKey_Length + kP256_PrivateKey_Length, res = false);
ecKey = EVP_PKEY_get1_EC_KEY(key);
VerifyOrExit(ecKey != nullptr, res = false);
privKeyBN = EC_KEY_get0_private_key(ecKey);
VerifyOrExit(privKeyBN != nullptr, res = false);
group = EC_KEY_get0_group(ecKey);
VerifyOrExit(group != nullptr, res = false);
ecPoint = EC_KEY_get0_public_key(ecKey);
VerifyOrExit(ecPoint != nullptr, res = false);
pubKeyLen =
EC_POINT_point2oct(group, ecPoint, POINT_CONVERSION_UNCOMPRESSED, Uint8::to_uchar(pubKey), kP256_PublicKey_Length, nullptr);
VerifyOrExit(pubKeyLen == kP256_PublicKey_Length, res = false);
privKeyLen = BN_bn2binpad(privKeyBN, privKey, kP256_PrivateKey_Length);
VerifyOrExit(privKeyLen == kP256_PrivateKey_Length, res = false);
chipKeyLen = kP256_PublicKey_Length + kP256_PrivateKey_Length;
exit:
return res;
}
bool DeserializeKeyPair(const uint8_t * keyPair, uint32_t keyPairLen, EVP_PKEY * key)
{
bool res = true;
int result = 0;
const uint8_t * pubKey = keyPair;
uint32_t pubKeyLen = kP256_PublicKey_Length;
const uint8_t * privKey = keyPair + pubKeyLen;
uint32_t privKeyLen = kP256_PrivateKey_Length;
std::unique_ptr<EC_GROUP, void (*)(EC_GROUP *)> group(EC_GROUP_new_by_curve_name(gNIDChipCurveP256), &EC_GROUP_free);
std::unique_ptr<EC_POINT, void (*)(EC_POINT *)> ecPoint(EC_POINT_new(group.get()), &EC_POINT_free);
std::unique_ptr<EC_KEY, void (*)(EC_KEY *)> ecKey(EC_KEY_new_by_curve_name(gNIDChipCurveP256), &EC_KEY_free);
std::unique_ptr<BIGNUM, void (*)(BIGNUM *)> privKeyBN(BN_new(), &BN_clear_free);
ERR_clear_error();
VerifyOrExit(key != nullptr, res = false);
VerifyOrExit(keyPair != nullptr, res = false);
VerifyOrExit(keyPairLen == privKeyLen + pubKeyLen, res = false);
result = EC_POINT_oct2point(group.get(), ecPoint.get(), pubKey, pubKeyLen, nullptr);
VerifyOrExit(result == 1, res = false);
result = EC_KEY_set_public_key(ecKey.get(), ecPoint.get());
VerifyOrExit(result == 1, res = false);
VerifyOrExit(BN_bin2bn(privKey, static_cast<int>(privKeyLen), privKeyBN.get()) != nullptr, res = false);
result = EC_KEY_set_private_key(ecKey.get(), privKeyBN.get());
VerifyOrExit(result == 1, res = false);
result = EVP_PKEY_set1_EC_KEY(key, ecKey.get());
VerifyOrExit(result == 1, res = false);
exit:
return res;
}
} // namespace
bool ReadKey(const char * fileName, EVP_PKEY * key)
{
bool res = true;
uint32_t keyDataLen = 0;
KeyFormat keyFormat = kKeyFormat_Unknown;
std::unique_ptr<uint8_t[]> keyData;
res = ReadFileIntoMem(fileName, nullptr, keyDataLen);
VerifyTrueOrExit(res);
keyData = std::unique_ptr<uint8_t[]>(new uint8_t[keyDataLen]);
res = ReadFileIntoMem(fileName, keyData.get(), keyDataLen);
VerifyTrueOrExit(res);
keyFormat = DetectKeyFormat(keyData.get(), keyDataLen);
if (keyFormat == kKeyFormat_Chip_Base64)
{
res = Base64Decode(keyData.get(), keyDataLen, keyData.get(), keyDataLen, keyDataLen);
VerifyTrueOrExit(res);
keyFormat = kKeyFormat_Chip_Raw;
}
if (keyFormat == kKeyFormat_Chip_Raw)
{
res = DeserializeKeyPair(keyData.get(), keyDataLen, key);
VerifyTrueOrExit(res);
}
else
{
std::unique_ptr<BIO, void (*)(BIO *)> keyBIO(
BIO_new_mem_buf(static_cast<const void *>(keyData.get()), static_cast<int>(keyDataLen)), &BIO_free_all);
if (keyFormat == kKeyFormat_X509_PUBKEY_PEM)
{
EC_KEY * ecKey = EC_KEY_new();
if (PEM_read_bio_EC_PUBKEY(keyBIO.get(), &ecKey, nullptr, nullptr) == nullptr)
{
ReportOpenSSLErrorAndExit("PEM_read_bio_EC_PUBKEY", res = false);
}
if (EVP_PKEY_set1_EC_KEY(key, ecKey) != 1)
{
ReportOpenSSLErrorAndExit("EVP_PKEY_set1_EC_KEY", res = false);
}
}
else if (keyFormat == kKeyFormat_X509_PEM)
{
if (PEM_read_bio_PrivateKey(keyBIO.get(), &key, nullptr, nullptr) == nullptr)
{
ReportOpenSSLErrorAndExit("PEM_read_bio_PrivateKey", res = false);
}
}
else
{
if (d2i_PrivateKey_bio(keyBIO.get(), &key) == nullptr)
{
ReportOpenSSLErrorAndExit("d2i_PrivateKey_bio", res = false);
}
}
}
if (EC_GROUP_get_curve_name(EC_KEY_get0_group(EVP_PKEY_get1_EC_KEY(key))) != gNIDChipCurveP256)
{
fprintf(stderr, "Specified key uses unsupported Elliptic Curve\n");
ExitNow(res = false);
}
exit:
return res;
}
bool GenerateKeyPair(EVP_PKEY * key)
{
bool res = true;
std::unique_ptr<EC_KEY, void (*)(EC_KEY *)> ecKey(EC_KEY_new_by_curve_name(gNIDChipCurveP256), &EC_KEY_free);
VerifyOrExit(key != nullptr, res = false);
if (!EC_KEY_generate_key(ecKey.get()))
{
ReportOpenSSLErrorAndExit("EC_KEY_generate_key", res = false);
}
if (!EVP_PKEY_set1_EC_KEY(key, ecKey.get()))
{
ReportOpenSSLErrorAndExit("EVP_PKEY_set1_EC_KEY", res = false);
}
exit:
return res;
}
bool WritePrivateKey(const char * fileName, EVP_PKEY * key, KeyFormat keyFmt)
{
bool res = true;
FILE * file = nullptr;
uint8_t * keyToWrite = nullptr;
uint32_t keyToWriteLen = 0;
uint32_t chipKeyLen = kP256_PublicKey_Length + kP256_PrivateKey_Length;
uint32_t chipKeyBase64Len = BASE64_ENCODED_LEN(chipKeyLen);
std::unique_ptr<uint8_t[]> chipKey(new uint8_t[chipKeyLen]);
std::unique_ptr<uint8_t[]> chipKeyBase64(new uint8_t[chipKeyBase64Len]);
VerifyOrExit(key != nullptr, res = false);
res = OpenFile(fileName, file, true);
VerifyTrueOrExit(res);
if (EVP_PKEY_type(EVP_PKEY_id(key)) != EVP_PKEY_EC)
{
fprintf(stderr, "Unsupported private key type\n");
ExitNow(res = false);
}
switch (keyFmt)
{
case kKeyFormat_X509_PEM:
if (PEM_write_ECPrivateKey(file, EVP_PKEY_get1_EC_KEY(key), nullptr, nullptr, 0, nullptr, nullptr) == 0)
{
ReportOpenSSLErrorAndExit("PEM_write_PrivateKey", res = false);
}
break;
case kKeyFormat_X509_DER:
if (i2d_ECPrivateKey_fp(file, EVP_PKEY_get1_EC_KEY(key)) == 0)
{
ReportOpenSSLErrorAndExit("i2d_PrivateKey_fp", res = false);
}
break;
case kKeyFormat_Chip_Raw:
case kKeyFormat_Chip_Base64:
res = SerializeKeyPair(key, chipKey.get(), chipKeyLen, chipKeyLen);
VerifyTrueOrExit(res);
if (keyFmt == kKeyFormat_Chip_Base64)
{
res = Base64Encode(chipKey.get(), chipKeyLen, chipKeyBase64.get(), chipKeyBase64Len, chipKeyBase64Len);
VerifyTrueOrExit(res);
keyToWrite = chipKeyBase64.get();
keyToWriteLen = chipKeyBase64Len;
}
else
{
keyToWrite = chipKey.get();
keyToWriteLen = chipKeyLen;
}
if (fwrite(keyToWrite, 1, keyToWriteLen, file) != keyToWriteLen)
{
fprintf(stderr, "Unable to write to %s\n%s\n", fileName, strerror(ferror(file) ? errno : ENOSPC));
ExitNow(res = false);
}
break;
default:
fprintf(stderr, "Unsupported private key format");
ExitNow(res = false);
}
exit:
CloseFile(file);
return res;
}