blob: 9cad918be26365ea818915c540b0836bf948f500 [file] [log] [blame]
/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
/**
* Copyright (c) 2013-2017 Regents of the University of California.
*
* This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
*
* ndn-cxx library is free software: you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License as published by the Free Software
* Foundation, either version 3 of the License, or (at your option) any later version.
*
* ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
*
* You should have received copies of the GNU General Public License and GNU Lesser
* General Public License along with ndn-cxx, e.g., in COPYING.md file. If not, see
* <http://www.gnu.org/licenses/>.
*
* See AUTHORS.md for complete list of ndn-cxx authors and contributors.
*/
#include "private-key.hpp"
#include "base64-decode.hpp"
#include "base64-encode.hpp"
#include "buffer-source.hpp"
#include "stream-sink.hpp"
#include "stream-source.hpp"
#include "../detail/openssl-helper.hpp"
#include "../key-params.hpp"
#include "../../encoding/buffer-stream.hpp"
#include <cstring>
#define ENSURE_PRIVATE_KEY_LOADED(key) \
do { \
if (key == nullptr) \
BOOST_THROW_EXCEPTION(Error("Private key has not been loaded yet")); \
} while (false)
namespace ndn {
namespace security {
namespace transform {
class PrivateKey::Impl
{
public:
Impl()
: key(nullptr)
{
}
~Impl()
{
EVP_PKEY_free(key);
}
public:
EVP_PKEY* key;
};
PrivateKey::PrivateKey()
: m_impl(new Impl)
{
}
PrivateKey::~PrivateKey() = default;
void
PrivateKey::loadPkcs1(const uint8_t* buf, size_t size)
{
detail::Bio mem(BIO_s_mem());
BIO_write(mem.get(), buf, size);
d2i_PrivateKey_bio(mem.get(), &m_impl->key);
ENSURE_PRIVATE_KEY_LOADED(m_impl->key);
}
void
PrivateKey::loadPkcs1(std::istream& is)
{
OBufferStream os;
streamSource(is) >> streamSink(os);
this->loadPkcs1(os.buf()->buf(), os.buf()->size());
}
void
PrivateKey::loadPkcs1Base64(const uint8_t* buf, size_t size)
{
OBufferStream os;
bufferSource(buf, size) >> base64Decode() >> streamSink(os);
this->loadPkcs1(os.buf()->buf(), os.buf()->size());
}
void
PrivateKey::loadPkcs1Base64(std::istream& is)
{
OBufferStream os;
streamSource(is) >> base64Decode() >> streamSink(os);
this->loadPkcs1(os.buf()->buf(), os.buf()->size());
}
void
PrivateKey::loadPkcs8(const uint8_t* buf, size_t size, const char* pw, size_t pwLen)
{
BOOST_ASSERT(std::strlen(pw) == pwLen);
detail::Bio mem(BIO_s_mem());
BIO_write(mem.get(), buf, size);
m_impl->key = d2i_PKCS8PrivateKey_bio(mem.get(), &m_impl->key, nullptr, const_cast<char*>(pw));
ENSURE_PRIVATE_KEY_LOADED(m_impl->key);
}
static inline int
passwordCallback(char* buf, int size, int rwflag, void* u)
{
auto cb = reinterpret_cast<PrivateKey::PasswordCallback*>(u);
return (*cb)(buf, size, rwflag);
}
void
PrivateKey::loadPkcs8(const uint8_t* buf, size_t size, PasswordCallback pwCallback)
{
OpenSSL_add_all_algorithms();
detail::Bio mem(BIO_s_mem());
BIO_write(mem.get(), buf, size);
if (pwCallback)
m_impl->key = d2i_PKCS8PrivateKey_bio(mem.get(), &m_impl->key, passwordCallback, &pwCallback);
else
m_impl->key = d2i_PKCS8PrivateKey_bio(mem.get(), &m_impl->key, nullptr, nullptr);
ENSURE_PRIVATE_KEY_LOADED(m_impl->key);
}
void
PrivateKey::loadPkcs8(std::istream& is, const char* pw, size_t pwLen)
{
OBufferStream os;
streamSource(is) >> streamSink(os);
this->loadPkcs8(os.buf()->buf(), os.buf()->size(), pw, pwLen);
}
void
PrivateKey::loadPkcs8(std::istream& is, PasswordCallback pwCallback)
{
OBufferStream os;
streamSource(is) >> streamSink(os);
this->loadPkcs8(os.buf()->buf(), os.buf()->size(), pwCallback);
}
void
PrivateKey::loadPkcs8Base64(const uint8_t* buf, size_t size, const char* pw, size_t pwLen)
{
OBufferStream os;
bufferSource(buf, size) >> base64Decode() >> streamSink(os);
this->loadPkcs8(os.buf()->buf(), os.buf()->size(), pw, pwLen);
}
void
PrivateKey::loadPkcs8Base64(const uint8_t* buf, size_t size, PasswordCallback pwCallback)
{
OBufferStream os;
bufferSource(buf, size) >> base64Decode() >> streamSink(os);
this->loadPkcs8(os.buf()->buf(), os.buf()->size(), pwCallback);
}
void
PrivateKey::loadPkcs8Base64(std::istream& is, const char* pw, size_t pwLen)
{
OBufferStream os;
streamSource(is) >> base64Decode() >> streamSink(os);
this->loadPkcs8(os.buf()->buf(), os.buf()->size(), pw, pwLen);
}
void
PrivateKey::loadPkcs8Base64(std::istream& is, PasswordCallback pwCallback)
{
OBufferStream os;
streamSource(is) >> base64Decode() >> streamSink(os);
this->loadPkcs8(os.buf()->buf(), os.buf()->size(), pwCallback);
}
void
PrivateKey::savePkcs1(std::ostream& os) const
{
bufferSource(*this->toPkcs1()) >> streamSink(os);
}
void
PrivateKey::savePkcs1Base64(std::ostream& os) const
{
bufferSource(*this->toPkcs1()) >> base64Encode() >> streamSink(os);
}
void
PrivateKey::savePkcs8(std::ostream& os, const char* pw, size_t pwLen) const
{
bufferSource(*this->toPkcs8(pw, pwLen)) >> streamSink(os);
}
void
PrivateKey::savePkcs8(std::ostream& os, PasswordCallback pwCallback) const
{
bufferSource(*this->toPkcs8(pwCallback)) >> streamSink(os);
}
void
PrivateKey::savePkcs8Base64(std::ostream& os, const char* pw, size_t pwLen) const
{
bufferSource(*this->toPkcs8(pw, pwLen)) >> base64Encode() >> streamSink(os);
}
void
PrivateKey::savePkcs8Base64(std::ostream& os, PasswordCallback pwCallback) const
{
bufferSource(*this->toPkcs8(pwCallback)) >> base64Encode() >> streamSink(os);
}
ConstBufferPtr
PrivateKey::derivePublicKey() const
{
ENSURE_PRIVATE_KEY_LOADED(m_impl->key);
uint8_t* pkcs8 = nullptr;
int len = i2d_PUBKEY(m_impl->key, &pkcs8);
if (len <= 0)
BOOST_THROW_EXCEPTION(Error("Failed to derive public key"));
auto result = make_shared<Buffer>(pkcs8, len);
OPENSSL_free(pkcs8);
return result;
}
ConstBufferPtr
PrivateKey::decrypt(const uint8_t* cipherText, size_t cipherLen) const
{
ENSURE_PRIVATE_KEY_LOADED(m_impl->key);
#if OPENSSL_VERSION_NUMBER < 0x1010000fL
switch (EVP_PKEY_type(m_impl->key->type)) {
#else
switch (EVP_PKEY_base_id(m_impl->key)) {
#endif // OPENSSL_VERSION_NUMBER < 0x1010000fL
case EVP_PKEY_RSA:
return rsaDecrypt(cipherText, cipherLen);
default:
BOOST_THROW_EXCEPTION(Error("Decryption is not supported for this key type"));
}
}
void*
PrivateKey::getEvpPkey() const
{
return m_impl->key;
}
ConstBufferPtr
PrivateKey::toPkcs1() const
{
ENSURE_PRIVATE_KEY_LOADED(m_impl->key);
OpenSSL_add_all_algorithms();
detail::Bio mem(BIO_s_mem());
int ret = i2d_PrivateKey_bio(mem.get(), m_impl->key);
if (ret != 1)
BOOST_THROW_EXCEPTION(Error("Cannot convert key into PKCS1 format"));
int len8 = BIO_pending(mem.get());
auto buffer = make_shared<Buffer>(len8);
BIO_read(mem.get(), buffer->buf(), len8);
return buffer;
}
ConstBufferPtr
PrivateKey::toPkcs8(const char* pw, size_t pwLen) const
{
ENSURE_PRIVATE_KEY_LOADED(m_impl->key);
BOOST_ASSERT(std::strlen(pw) == pwLen);
OpenSSL_add_all_algorithms();
detail::Bio mem(BIO_s_mem());
int ret = i2d_PKCS8PrivateKey_bio(mem.get(), m_impl->key, EVP_des_cbc(),
const_cast<char*>(pw), pwLen, nullptr, nullptr);
if (ret != 1)
BOOST_THROW_EXCEPTION(Error("Cannot convert key into PKCS8 format"));
int len8 = BIO_pending(mem.get());
auto buffer = make_shared<Buffer>(len8);
BIO_read(mem.get(), buffer->buf(), len8);
return buffer;
}
ConstBufferPtr
PrivateKey::toPkcs8(PasswordCallback pwCallback) const
{
ENSURE_PRIVATE_KEY_LOADED(m_impl->key);
OpenSSL_add_all_algorithms();
detail::Bio mem(BIO_s_mem());
int ret = i2d_PKCS8PrivateKey_bio(mem.get(), m_impl->key, EVP_des_cbc(),
nullptr, 0,
passwordCallback, &pwCallback);
if (ret != 1)
BOOST_THROW_EXCEPTION(Error("Cannot convert key into PKCS8 format"));
int len8 = BIO_pending(mem.get());
auto buffer = make_shared<Buffer>(len8);
BIO_read(mem.get(), buffer->buf(), len8);
return buffer;
}
ConstBufferPtr
PrivateKey::rsaDecrypt(const uint8_t* cipherText, size_t cipherLen) const
{
detail::EvpPkeyCtx ctx(m_impl->key);
if (EVP_PKEY_decrypt_init(ctx.get()) <= 0)
BOOST_THROW_EXCEPTION(Error("Failed to initialize decryption context"));
if (EVP_PKEY_CTX_set_rsa_padding(ctx.get(), RSA_PKCS1_OAEP_PADDING) <= 0)
BOOST_THROW_EXCEPTION(Error("Failed to set padding"));
size_t outlen = 0;
// Determine buffer length
if (EVP_PKEY_decrypt(ctx.get(), nullptr, &outlen, cipherText, cipherLen) <= 0)
BOOST_THROW_EXCEPTION(Error("Failed to estimate output length"));
auto out = make_shared<Buffer>(outlen);
if (EVP_PKEY_decrypt(ctx.get(), out->buf(), &outlen, cipherText, cipherLen) <= 0)
BOOST_THROW_EXCEPTION(Error("Failed to decrypt cipher text"));
out->resize(outlen);
return out;
}
static unique_ptr<PrivateKey>
generateRsaKey(uint32_t keySize)
{
detail::EvpPkeyCtx kctx(EVP_PKEY_RSA);
int ret = EVP_PKEY_keygen_init(kctx.get());
if (ret != 1)
BOOST_THROW_EXCEPTION(PrivateKey::Error("Fail to generate RSA key"));
ret = EVP_PKEY_CTX_set_rsa_keygen_bits(kctx.get(), keySize);
if (ret != 1)
BOOST_THROW_EXCEPTION(PrivateKey::Error("Fail to generate RSA key"));
detail::EvpPkey key;
ret = EVP_PKEY_keygen(kctx.get(), &key);
if (ret != 1)
BOOST_THROW_EXCEPTION(PrivateKey::Error("Fail to generate RSA key"));
detail::Bio mem(BIO_s_mem());
i2d_PrivateKey_bio(mem.get(), key.get());
int len = BIO_pending(mem.get());
Buffer buffer(len);
BIO_read(mem.get(), buffer.buf(), len);
auto privateKey = make_unique<PrivateKey>();
privateKey->loadPkcs1(buffer.buf(), buffer.size());
return privateKey;
}
static unique_ptr<PrivateKey>
generateEcKey(uint32_t keySize)
{
detail::EvpPkeyCtx ctx(EVP_PKEY_EC);
int ret = EVP_PKEY_paramgen_init(ctx.get());
if (ret != 1)
BOOST_THROW_EXCEPTION(PrivateKey::Error("Fail to generate EC key"));
switch (keySize) {
case 256:
ret = EVP_PKEY_CTX_set_ec_paramgen_curve_nid(ctx.get(), NID_X9_62_prime256v1);
break;
case 384:
ret = EVP_PKEY_CTX_set_ec_paramgen_curve_nid(ctx.get(), NID_secp384r1);
break;
default:
BOOST_THROW_EXCEPTION(PrivateKey::Error("Fail to generate EC key"));
}
if (ret != 1)
BOOST_THROW_EXCEPTION(PrivateKey::Error("Fail to generate EC key"));
detail::EvpPkey params;
ret = EVP_PKEY_paramgen(ctx.get(), &params);
if (ret != 1)
BOOST_THROW_EXCEPTION(PrivateKey::Error("Fail to generate EC key"));
detail::EvpPkeyCtx kctx(params.get());
ret = EVP_PKEY_keygen_init(kctx.get());
if (ret != 1)
BOOST_THROW_EXCEPTION(PrivateKey::Error("Fail to generate EC key"));
detail::EvpPkey key;
ret = EVP_PKEY_keygen(kctx.get(), &key);
if (ret != 1)
BOOST_THROW_EXCEPTION(PrivateKey::Error("Fail to generate EC key"));
detail::Bio mem(BIO_s_mem());
i2d_PrivateKey_bio(mem.get(), key.get());
int len = BIO_pending(mem.get());
Buffer buffer(len);
BIO_read(mem.get(), buffer.buf(), len);
auto privateKey = make_unique<PrivateKey>();
privateKey->loadPkcs1(buffer.buf(), buffer.size());
return privateKey;
}
unique_ptr<PrivateKey>
generatePrivateKey(const KeyParams& keyParams)
{
switch (keyParams.getKeyType()) {
case KeyType::RSA: {
const RsaKeyParams& rsaParams = static_cast<const RsaKeyParams&>(keyParams);
return generateRsaKey(rsaParams.getKeySize());
}
case KeyType::EC: {
const EcKeyParams& ecParams = static_cast<const EcKeyParams&>(keyParams);
return generateEcKey(ecParams.getKeySize());
}
default:
BOOST_THROW_EXCEPTION(std::invalid_argument("Unsupported asymmetric key type"));
}
}
} // namespace transform
} // namespace security
} // namespace ndn