From 0cacc25b1fdda52c64ecb69a30d0e9b0f56291ec Mon Sep 17 00:00:00 2001 From: Francis Dupont Date: Sun, 7 Jun 2015 06:40:19 +0200 Subject: [PATCH] [sedhcpv6] Wrote ECDSA code for OpenSSL, still to debug and do Botan --- src/lib/cryptolink/openssl_ecdsa.cc | 1160 +++++++++++++++++ src/lib/cryptolink/openssl_ecdsa.h | 149 +++ src/lib/cryptolink/tests/ecdsa_unittests.cc | 507 +++++++ src/lib/cryptolink/tests/rsa_unittests.cc | 877 +++++++++++++ src/lib/cryptolink/tests/testdata/eccert.pem | 12 + src/lib/cryptolink/tests/testdata/ecpkcs8.pem | 7 + .../cryptolink/tests/testdata/ecpkcs8ne.pem | 5 + src/lib/cryptolink/tests/testdata/ecpub.pem | 4 + 8 files changed, 2721 insertions(+) create mode 100644 src/lib/cryptolink/openssl_ecdsa.cc create mode 100644 src/lib/cryptolink/openssl_ecdsa.h create mode 100644 src/lib/cryptolink/tests/ecdsa_unittests.cc create mode 100644 src/lib/cryptolink/tests/rsa_unittests.cc create mode 100644 src/lib/cryptolink/tests/testdata/eccert.pem create mode 100644 src/lib/cryptolink/tests/testdata/ecpkcs8.pem create mode 100644 src/lib/cryptolink/tests/testdata/ecpkcs8ne.pem create mode 100644 src/lib/cryptolink/tests/testdata/ecpub.pem diff --git a/src/lib/cryptolink/openssl_ecdsa.cc b/src/lib/cryptolink/openssl_ecdsa.cc new file mode 100644 index 0000000000..2e43febdac --- /dev/null +++ b/src/lib/cryptolink/openssl_ecdsa.cc @@ -0,0 +1,1160 @@ +// Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC") +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH +// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, +// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE +// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +// PERFORMANCE OF THIS SOFTWARE. + +#include +#include + +#include + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + +#ifndef NID_X9_62_prime256v1 +#error "P-256 group is not known (NID_X9_62_prime256v1)" +#endif +#ifndef NID_secp384r1 +#error "P-384 group is not known (NID_secp384r1)" +#endif + +namespace isc { +namespace cryptolink { + +/// @brief Constructor from a key, asym and hash algorithm, +/// key kind and key binary format +EcDsaAsymImpl::EcDsaAsymImpl(const void* key, size_t key_len, + const HashAlgorithm hash_algorithm, + const AsymKeyKind key_kind, + const AsymFormat key_format) { + algo_ = ECDSA_; + hash_ = hash_algorithm; + kind_ = key_kind; + eckey_ = NULL; + x509_ = NULL; + int curve_nid = 0; + switch (hash_) { + case SHA256: + curve_nid = NID_X9_62_prime256v1; + break; + case SHA384: + curve_nid = NID_secp384r1; + break; + default: + isc_throw(UnsupportedAlgorithm, + "Unknown hash algorithm: " << static_cast(hash_)); + } + if (key_len == 0) { + isc_throw(BadKey, "Bad ECDSA " << + (kind_ != CERT ? "key" : "cert") << " length: 0"); + } + + if ((kind_ == PRIVATE) && (key_format == BASIC)) { + // The private key is just a number + const unsigned char* p = reinterpret_cast(key); + BIGNUM* privkey = BN_bin2bn(p, static_cast(key_len), NULL); + if (!privkey) { + throw std::bad_alloc(); + } + eckey_ = EC_KEY_new_by_curve_name(curve_nid); + if (!eckey_) { + BN_clear_free(privkey); + isc_throw(LibraryError, "EC_KEY_new_by_curve_name"); + } + EC_KEY_set_asn1_flag(eckey_, OPENSSL_EC_NAMED_CURVE); + if (!EC_KEY_set_private_key(eckey_, privkey)) { + BN_clear_free(privkey); + EC_KEY_free(eckey_); + eckey_ = NULL; + isc_throw(LibraryError, "EC_KEY_set_private_key"); + } + // Compute the public key + const EC_GROUP* grp = EC_KEY_get0_group(eckey_); + EC_POINT* pubkey = EC_POINT_new(grp); + if (!pubkey) { + BN_clear_free(privkey); + throw std::bad_alloc(); + } + if (!EC_POINT_mul(grp, pubkey, privkey, NULL, NULL, NULL)) { + EC_POINT_free(pubkey); + BN_clear_free(privkey); + EC_KEY_free(eckey_); + eckey_ = NULL; + isc_throw(LibraryError, "EC_POINT_mul"); + } + if (!EC_KEY_set_public_key(eckey_, pubkey)) { + EC_POINT_free(pubkey); + BN_clear_free(privkey); + EC_KEY_free(eckey_); + eckey_ = NULL; + isc_throw(BadKey, "EC_KEY_set_public_key"); + } + EC_POINT_free(pubkey); + BN_clear_free(privkey); + } else if (kind_ == PRIVATE) { + isc_throw(UnsupportedAlgorithm, + "Unknown ECDSA Private Key format: " << + static_cast(key_format)); + } else if ((kind_ == PUBLIC) && (key_format == ASN1)) { + // SubjectPublicKeyInfo + const unsigned char* p = reinterpret_cast(key); + eckey_ = d2i_EC_PUBKEY(NULL, &p, static_cast(key_len)); + if (!eckey_) { + isc_throw(BadKey, "d2i_EC_PUBKEY"); + } + if (EC_KEY_check_key(eckey_) != 1) { + EC_KEY_free(eckey_); + eckey_ = NULL; + isc_throw(BadKey, "EC_KEY_check_key"); + } + EC_GROUP* wanted = EC_GROUP_new_by_curve_name(curve_nid); + if (!wanted) { + EC_KEY_free(eckey_); + eckey_ = NULL; + isc_throw(LibraryError, "EC_GROUP_new_by_curve_name"); + } + const int status = + EC_GROUP_cmp(EC_KEY_get0_group(eckey_), wanted, NULL); + EC_GROUP_free(wanted); + if (status < 0) { + EC_KEY_free(eckey_); + eckey_ = NULL; + isc_throw(LibraryError, "EC_GROUP_cmp"); + } else if (status != 0) { + EC_KEY_free(eckey_); + eckey_ = NULL; + isc_throw(BadKey, "EC_GROUP_cmp"); + } + } else if ((kind_ == PUBLIC) && (key_format == DNS)) { + // RFC 6605 DNS wire format + // key_len == 0 was already checked + size_t len; + std::string algo_name; + if (hash_ == SHA256) { + len = 64; + algo_name = "ECDSA P256"; + } else { + len = 96; + algo_name ="ECDSA P384"; + } + if (key_len != len) { + isc_throw(BadKey, "Bad " << algo_name << + "Public Key length: " << + key_len << ", expected " << len); + } + std::vector pubbin(len + 1); + pubbin[0] = POINT_CONVERSION_UNCOMPRESSED; + std::memcpy(&pubbin[1], key, len); + const uint8_t* p = &pubbin[0]; + eckey_ = EC_KEY_new_by_curve_name(curve_nid); + if (!eckey_) { + isc_throw(LibraryError, "EC_KEY_new_by_curve_name"); + } + EC_KEY_set_asn1_flag(eckey_, OPENSSL_EC_NAMED_CURVE); + if (o2i_ECPublicKey(&eckey_, &p, + static_cast(len + 1)) == NULL) { + EC_KEY_free(eckey_); + eckey_ = NULL; + isc_throw(BadKey, "o2i_ECPublicKey"); + } + if (!EC_KEY_check_key(eckey_)) { + EC_KEY_free(eckey_); + eckey_ = NULL; + isc_throw(BadKey, "EC_KEY_check_key"); + } + } else if (kind_ == PUBLIC) { + isc_throw(UnsupportedAlgorithm, + "Unknown ECDSA Public Key format: " << + static_cast(key_format)); + } else if ((kind_ == CERT) && (key_format == ASN1)) { + // X.509 Public Key Certificate + const unsigned char* p = reinterpret_cast(key); + x509_ = d2i_X509(NULL, &p, static_cast(key_len)); + if (!x509_) { + isc_throw(BadKey, "d2i_X509"); + } + int sig_nid = OBJ_obj2nid(x509_->sig_alg->algorithm); + if (hash_ == SHA256) { + if (sig_nid != OBJ_txt2nid("1.2.840.10045.4.3.2")) { + X509_free(x509_); + x509_ = NULL; + isc_throw(BadKey, "Require an ECDSA P256 certificate"); + } + } else { + if (sig_nid != OBJ_txt2nid("1.2.840.10045.4.3.3")) { + X509_free(x509_); + x509_ = NULL; + isc_throw(BadKey, "Require an ECDSA P256 certificate"); + } + } + EVP_PKEY* pkey = X509_get_pubkey(x509_); + if (!pkey) { + X509_free(x509_); + x509_ = NULL; + isc_throw(BadKey, "X509_get_pubkey"); + } + eckey_ = EVP_PKEY_get1_EC_KEY(pkey); + EVP_PKEY_free(pkey); + if (!eckey_) { + X509_free(x509_); + x509_ = NULL; + isc_throw(BadKey, "EVP_PKEY_get1_EC_KEY"); + } + } else if (kind_ == CERT) { + isc_throw(UnsupportedAlgorithm, + "Unknown Certificate format: " << + static_cast(key_format)); + } else { + isc_throw(UnsupportedAlgorithm, + "Unknown ECDSA Key kind: " << static_cast(kind_)); + } +} + +/// @brief Constructor from a key file with password, +/// asym and hash algorithm, key kind and key binary format +EcDsaAsymImpl::EcDsaAsymImpl(const std::string& filename, + const std::string& password, + const HashAlgorithm hash_algorithm, + const AsymKeyKind key_kind, + const AsymFormat key_format) { + algo_ = ECDSA_; + hash_ = hash_algorithm; + kind_ = key_kind; + eckey_ = NULL; + x509_ = NULL; + int curve_nid = 0; + switch (hash_) { + case SHA256: + curve_nid = NID_X9_62_prime256v1; + break; + case SHA384: + curve_nid = NID_secp384r1; + break; + default: + isc_throw(UnsupportedAlgorithm, + "Unknown hash algorithm: " << static_cast(hash_)); + } + + if ((kind_ == PRIVATE) && (key_format == ASN1)) { + // PKCS#8 Private Key PEM file + FILE* fp = fopen(filename.c_str(), "r"); + if (!fp) { + isc_throw(BadKey, "Can't open file: " << filename); + } + char* pwd = NULL; + if (!password.empty()) { + pwd = const_cast(password.c_str()); + } + eckey_ = PEM_read_ECPrivateKey(fp, NULL, 0, pwd); + fclose(fp); + if (!eckey_) { + isc_throw(BadKey, "PEM_read_ECPrivateKey"); + } + if (!EC_KEY_get0_public_key(eckey_)) { + // Compute the public key as a side effect + const EC_GROUP* grp = EC_KEY_get0_group(eckey_); + const BIGNUM* privkey = EC_KEY_get0_private_key(eckey_); + EC_POINT* pubkey = EC_POINT_new(grp); + if (!pubkey) { + throw std::bad_alloc(); + } + if (!EC_POINT_mul(grp, pubkey, privkey, NULL, NULL, NULL)) { + EC_POINT_free(pubkey); + EC_KEY_free(eckey_); + eckey_ = NULL; + isc_throw(LibraryError, "EC_POINT_mul"); + } + if (!EC_KEY_set_public_key(eckey_, pubkey)) { + EC_POINT_free(pubkey); + EC_KEY_free(eckey_); + eckey_ = NULL; + isc_throw(BadKey, "EC_KEY_set_public_key"); + } + EC_POINT_free(pubkey); + } + if (EC_KEY_check_key(eckey_) != 1) { + EC_KEY_free(eckey_); + eckey_ = NULL; + isc_throw(BadKey, "EC_KEY_check_key"); + } + EC_GROUP* wanted = EC_GROUP_new_by_curve_name(curve_nid); + if (!wanted) { + EC_KEY_free(eckey_); + eckey_ = NULL; + isc_throw(LibraryError, "EC_GROUP_new_by_curve_name"); + } + const int status = + EC_GROUP_cmp(EC_KEY_get0_group(eckey_), wanted, NULL); + EC_GROUP_free(wanted); + if (status < 0) { + EC_KEY_free(eckey_); + eckey_ = NULL; + isc_throw(LibraryError, "EC_GROUP_cmp"); + } else if (status != 0) { + EC_KEY_free(eckey_); + eckey_ = NULL; + isc_throw(BadKey, "EC_GROUP_cmp"); + } + } else if ((kind_ == PRIVATE) && (key_format == DNS)) { + // bind9 .private file + // warn when password not empty + FILE* fp = fopen(filename.c_str(), "r"); + if (!fp) { + isc_throw(BadKey, "Can't open file: " << filename); + } + bool got_algorithm = false; + bool got_privatekey = false; + BIGNUM* privkey = NULL; + char line[4096]; + while (fgets(line, sizeof(line), fp)) { + if (strncmp(line, "Algorithm:", strlen("Algorithm:")) == 0) { + if (got_algorithm) { + fclose(fp); + isc_throw(BadKey, "Two Algorithm entries"); + } + got_algorithm = true; + std::string value(line + strlen("Algorithm:") + 1); + int alg = std::atoi(value.c_str()); + if (alg == 13) { + // ECDSAP256SHA256 + if (hash_ != SHA256) { + fclose(fp); + isc_throw(BadKey, "Require an ECDSA P256 key"); + } + } else if (alg == 14) { + // ECDSAP384SHA384 + if (hash_ != SHA384) { + fclose(fp); + isc_throw(BadKey, "Require an ECDSA SHA384 key"); + } + } else { + fclose(fp); + isc_throw(BadKey, "Bad Algorithm: " << alg); + } + } else if (strncmp(line, "PrivateKey:", + strlen("PrivateKey:")) == 0) { + if (got_privatekey) { + fclose(fp); + isc_throw(BadKey, "Two PrivateKey entries"); + } + got_privatekey = true; + std::string value(line + strlen("PrivateKey:") + 1); + std::vector bin; + try { + isc::util::encode::decodeBase64(value, bin); + } catch (const BadValue& exc) { + fclose(fp); + isc_throw(BadKey, "PrivateKey: " << exc.what()); + } + int len = static_cast(bin.size()); + privkey = BN_bin2bn(&bin[0], len, NULL); + } + } + fclose(fp); + // Check first the existence of the PrivateKey + if (!got_privatekey) { + isc_throw(BadKey, "Missing PrivateExponent entry"); + } + if (!got_algorithm) { + BN_clear_free(privkey); + isc_throw(BadKey, "Missing Algorithm entry"); + } + eckey_ = EC_KEY_new_by_curve_name(curve_nid); + if (!eckey_) { + BN_clear_free(privkey); + isc_throw(LibraryError, "EC_KEY_new_by_curve_name"); + } + EC_KEY_set_asn1_flag(eckey_, OPENSSL_EC_NAMED_CURVE); + if (!EC_KEY_set_private_key(eckey_, privkey)) { + BN_clear_free(privkey); + EC_KEY_free(eckey_); + eckey_ = NULL; + isc_throw(LibraryError, "EC_KEY_set_private_key"); + } + BN_clear_free(privkey); + // Compute the public key as a side effect + if (!EC_KEY_generate_key(eckey_)) { + EC_KEY_free(eckey_); + eckey_ = NULL; + isc_throw(BadKey, "EC_KEY_generate_key"); + } + } else if ((kind_ == PUBLIC) && (key_format == ASN1)) { + // SubjectPublicKeyInfo PEM file + // warn when password not empty + FILE* fp = fopen(filename.c_str(), "r"); + if (!fp) { + isc_throw(BadKey, "Can't open file: " << filename); + } + char* pwd = NULL; + if (!password.empty()) { + pwd = const_cast(password.c_str()); + } + eckey_ = PEM_read_EC_PUBKEY(fp, NULL, 0, pwd); + fclose(fp); + if (!eckey_) { + isc_throw(BadKey, "PEM_read_EC_PUBKEY"); + } + if (EC_KEY_check_key(eckey_) != 1) { + EC_KEY_free(eckey_); + eckey_ = NULL; + isc_throw(BadKey, "EC_KEY_check_key"); + } + EC_GROUP* wanted = EC_GROUP_new_by_curve_name(curve_nid); + if (!wanted) { + EC_KEY_free(eckey_); + eckey_ = NULL; + isc_throw(LibraryError, "EC_GROUP_new_by_curve_name"); + } + const int status = + EC_GROUP_cmp(EC_KEY_get0_group(eckey_), wanted, NULL); + EC_GROUP_free(wanted); + if (status < 0) { + EC_KEY_free(eckey_); + eckey_ = NULL; + isc_throw(LibraryError, "EC_GROUP_cmp"); + } else if (status != 0) { + EC_KEY_free(eckey_); + eckey_ = NULL; + isc_throw(BadKey, "EC_GROUP_cmp"); + } + } else if ((kind_ == PUBLIC) && (key_format == DNS)) { + // bind9 .key file (RDATA) + // warn when password not empty + FILE* fp = fopen(filename.c_str(), "r"); + if (!fp) { + isc_throw(BadKey, "Can't open file: " << filename); + } + char line[4096]; + bool found = false; + while (fgets(line, sizeof(line), fp)) { + if ((line[0] == '\0') || (line[0] == ';')) { + continue; + } + if (strstr(line, "DNSKEY") == NULL) { + continue; + } + found = true; + if (line[strlen(line) - 1] == '\n') { + line[strlen(line) - 1] = 0; + } + break; + } + fclose(fp); + if (!found) { + isc_throw(BadKey, "Can't find a DNSKEY"); + } + const char b64[] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdef" + "ghijklmnopqrstuvwxyz0123456789+/="; + size_t last = strlen(line) - 1; + while (strchr(b64, static_cast(line[last])) != NULL) { + --last; + } + const std::string value(line + last + 1); + std::vector bin; + try { + util::encode::decodeBase64(value, bin); + } catch (const BadValue& exc) { + isc_throw(BadKey, "Can't decode base64: " << exc.what()); + } + size_t len; + std::string algo_name; + if (hash_ == SHA256) { + len = 64; + algo_name = "ECDSA P256"; + } else { + len = 96; + algo_name ="ECDSA P384"; + } + if (bin.size() != len) { + isc_throw(BadKey, "Bad " << algo_name << + "Public Key length: " << + bin.size() << ", expected " << len); + } + bin.insert(bin.begin(), POINT_CONVERSION_UNCOMPRESSED); + const uint8_t* p = &bin[0]; + eckey_ = EC_KEY_new_by_curve_name(curve_nid); + if (!eckey_) { + isc_throw(LibraryError, "EC_KEY_new_by_curve_name"); + } + EC_KEY_set_asn1_flag(eckey_, OPENSSL_EC_NAMED_CURVE); + if (o2i_ECPublicKey(&eckey_, &p, + static_cast(len + 1)) == NULL) { + EC_KEY_free(eckey_); + eckey_ = NULL; + isc_throw(BadKey, "o2i_ECPublicKey"); + } + if (!EC_KEY_check_key(eckey_)) { + EC_KEY_free(eckey_); + eckey_ = NULL; + isc_throw(BadKey, "EC_KEY_check_key"); + } + } else if (kind_ == PUBLIC) { + isc_throw(UnsupportedAlgorithm, + "Unknown ECDSA Public Key format: " << + static_cast(key_format)); + } else if ((kind_ == CERT) && (key_format == ASN1)) { + // Public Key Certificate PEM file + // warn when password not empty + FILE* fp = fopen(filename.c_str(), "r"); + if (!fp) { + isc_throw(BadKey, "Can't open file: " << filename); + } + x509_ = PEM_read_X509(fp, NULL, 0, NULL); + fclose(fp); + if (!x509_) { + isc_throw(BadKey, "PEM_read_X509"); + } + int sig_nid = OBJ_obj2nid(x509_->sig_alg->algorithm); + if (hash_ == SHA256) { + if (sig_nid != OBJ_txt2nid("1.2.840.10045.4.3.2")) { + X509_free(x509_); + x509_ = NULL; + isc_throw(BadKey, "Require an ECDSA P256 certificate"); + } + } else { + if (sig_nid != OBJ_txt2nid("1.2.840.10045.4.3.3")) { + X509_free(x509_); + x509_ = NULL; + isc_throw(BadKey, "Require an ECDSA P256 certificate"); + } + } + EVP_PKEY* pkey = X509_get_pubkey(x509_); + if (!pkey) { + X509_free(x509_); + x509_ = NULL; + isc_throw(BadKey, "X509_get_pubkey"); + } + eckey_ = EVP_PKEY_get1_EC_KEY(pkey); + EVP_PKEY_free(pkey); + if (!eckey_) { + X509_free(x509_); + x509_ = NULL; + isc_throw(BadKey, "EVP_PKEY_get1_EC_KEY"); + } + } else if (kind_ == CERT) { + isc_throw(UnsupportedAlgorithm, + "Unknown Public Key Certificate format: " << + static_cast(key_format)); + } else { + isc_throw(UnsupportedAlgorithm, + "Unknown ECDSA Key kind: " << static_cast(kind_)); + } +} + +/// @brief Destructor +EcDsaAsymImpl::~EcDsaAsymImpl() { + tbs_.clear(); + if (eckey_) { + EC_KEY_free(eckey_); + eckey_ = NULL; + } + if (x509_) { + X509_free(x509_); + x509_ = NULL; + } +} + +/// @brief Returns the AsymAlgorithm of the object +AsymAlgorithm EcDsaAsymImpl::getAsymAlgorithm() const { + return (algo_); +} + +/// @brief Returns the HashAlgorithm of the object +HashAlgorithm EcDsaAsymImpl::getHashAlgorithm() const { + return (hash_); +} + +/// @brief Returns the AsymKeyKind of the object +AsymKeyKind EcDsaAsymImpl::getAsymKeyKind() const { + return (kind_); +} + +/// @brief Returns the key size in bits +size_t EcDsaAsymImpl::getKeySize() const { + return (hash_ == SHA256 ? 256 : 384); +} + +/// @brief Returns the output size of the signature +size_t EcDsaAsymImpl::getSignatureLength(const AsymFormat sig_format) const { + switch (sig_format) { + case BASIC: + case DNS: + // In both cases a pair of big integers + return (hash_ == SHA256 ? 64 : 96); + case ASN1: + // Compute the maximum size of the ASN.1 structure + return (hash_ == SHA256 ? 64 + 8 : 96 + 8); + default: + isc_throw(UnsupportedAlgorithm, + "Unknown ECDSA Signature format: " << + static_cast(sig_format)); + } +} + +/// @brief Add data to digest +void EcDsaAsymImpl::update(const void* data, const size_t len) { + const size_t old = tbs_.size(); + tbs_.resize(old + len); + std::memcpy(&tbs_[old], data, len); +} + +/// @brief Calculate the final signature +void EcDsaAsymImpl::sign(isc::util::OutputBuffer& result, size_t len, + const AsymFormat sig_format) { + if ((sig_format != BASIC) && + (sig_format != ASN1) && + (sig_format != DNS)) { + isc_throw(UnsupportedAlgorithm, + "Unknown Signature format: " << + static_cast(sig_format)); + } + size_t size = getSignatureLength(sig_format); + ossl::SecBuf buf(size); + ECDSA_SIG* sig = ECDSA_do_sign(&tbs_[0], + static_cast(tbs_.size()), + eckey_); + if (!sig) { + isc_throw(LibraryError, "ECDSA_do_sign"); + } + if ((sig_format == BASIC) || (sig_format == DNS)) { + // Store the 2 integers with padding + BN_bn2bin(sig->r, &buf[(size / 2) - BN_num_bytes(sig->r)]); + BN_bn2bin(sig->s, &buf[size - BN_num_bytes(sig->s)]); + ECDSA_SIG_free(sig); + if (len > size) { + len = size; + } + } else { + // Store the ECDSA_SIG structure in DER + int sig_len = i2d_ECDSA_SIG(sig, NULL); + if (sig_len < 0) { + ECDSA_SIG_free(sig); + isc_throw(LibraryError, "i2d_ECDSA_SIG 0"); + } + buf.resize(sig_len); + uint8_t* p = &buf[0]; + sig_len = i2d_ECDSA_SIG(sig, &p); + ECDSA_SIG_free(sig); + if (sig_len != static_cast(buf.size())) { + isc_throw(LibraryError, "i2d_ECDSA_SIG"); + } + size_t sig_size = static_cast(sig_len); + if (len > sig_size) { + len = sig_size; + } + } + result.writeData(&buf[0], len); +} + +/// @brief Calculate the final signature +void EcDsaAsymImpl::sign(void* result, size_t len, + const AsymFormat sig_format) { + if ((sig_format != BASIC) && + (sig_format != ASN1) && + (sig_format != DNS)) { + isc_throw(UnsupportedAlgorithm, + "Unknown Signature format: " << + static_cast(sig_format)); + } + size_t size = getSignatureLength(sig_format); + ossl::SecBuf buf(size); + ECDSA_SIG* sig = ECDSA_do_sign(&tbs_[0], + static_cast(tbs_.size()), + eckey_); + if (!sig) { + isc_throw(LibraryError, "ECDSA_do_sign"); + } + if ((sig_format == BASIC) || (sig_format == DNS)) { + // Store the 2 integers with padding + BN_bn2bin(sig->r, &buf[(size / 2) - BN_num_bytes(sig->r)]); + BN_bn2bin(sig->s, &buf[size - BN_num_bytes(sig->s)]); + ECDSA_SIG_free(sig); + if (len > size) { + len = size; + } + } else { + // Store the ECDSA_SIG structure in DER + int sig_len = i2d_ECDSA_SIG(sig, NULL); + if (sig_len < 0) { + ECDSA_SIG_free(sig); + isc_throw(LibraryError, "i2d_ECDSA_SIG 0"); + } + buf.resize(sig_len); + uint8_t* p = &buf[0]; + sig_len = i2d_ECDSA_SIG(sig, &p); + ECDSA_SIG_free(sig); + if (sig_len != static_cast(buf.size())) { + isc_throw(LibraryError, "i2d_ECDSA_SIG"); + } + size_t sig_size = static_cast(sig_len); + if (len > sig_size) { + len = sig_size; + } + } + std::memcpy(result, &buf[0], len); +} + +/// @brief Calculate the final signature +std::vector EcDsaAsymImpl::sign(size_t len, + const AsymFormat sig_format) { + if ((sig_format != BASIC) && + (sig_format != ASN1) && + (sig_format != DNS)) { + isc_throw(UnsupportedAlgorithm, + "Unknown Signature format: " << + static_cast(sig_format)); + } + size_t size = getSignatureLength(sig_format); + ossl::SecBuf buf(size); + ECDSA_SIG* sig = ECDSA_do_sign(&tbs_[0], + static_cast(tbs_.size()), + eckey_); + if (!sig) { + isc_throw(LibraryError, "ECDSA_do_sign"); + } + if ((sig_format == BASIC) || (sig_format == DNS)) { + // Store the 2 integers with padding + BN_bn2bin(sig->r, &buf[(size / 2) - BN_num_bytes(sig->r)]); + BN_bn2bin(sig->s, &buf[size - BN_num_bytes(sig->s)]); + ECDSA_SIG_free(sig); + // resize to min(len, size) + buf.resize(len < size ? len : size); + } else { + // Store the ECDSA_SIG structure in DER + int sig_len = i2d_ECDSA_SIG(sig, NULL); + if (sig_len < 0) { + ECDSA_SIG_free(sig); + isc_throw(LibraryError, "i2d_ECDSA_SIG 0"); + } + buf.resize(sig_len); + uint8_t* p = &buf[0]; + sig_len = i2d_ECDSA_SIG(sig, &p); + ECDSA_SIG_free(sig); + if (sig_len != static_cast(buf.size())) { + isc_throw(LibraryError, "i2d_ECDSA_SIG"); + } + // resize to min(len, sig_len) + size_t sig_size = static_cast(sig_len); + buf.resize(len < sig_size ? len : sig_size); + } + return (std::vector(buf.begin(), buf.end())); +} + +/// @brief Verify an existing signature +bool EcDsaAsymImpl::verify(const void* sig, size_t len, + const AsymFormat sig_format) { + if ((sig_format != BASIC) && + (sig_format != ASN1) && + (sig_format != DNS)) { + isc_throw(UnsupportedAlgorithm, + "Unknown Signature format: " << + static_cast(sig_format)); + } + ECDSA_SIG* asn_sig; + const uint8_t* sigbuf = + reinterpret_cast(const_cast(sig)); + if ((sig_format == BASIC) || (sig_format == DNS)) { + size_t size = getSignatureLength(sig_format); + if (len != size) { + return false; + } + asn_sig = ECDSA_SIG_new(); + if (!asn_sig) { + throw std::bad_alloc(); + } + if (asn_sig->r) { + BN_clear_free(asn_sig->r); + } + asn_sig->r = BN_bin2bn(sigbuf, size / 2, NULL); + if (!asn_sig->r) { + ECDSA_SIG_free(asn_sig); + throw std::bad_alloc(); + } + if (asn_sig->s) { + BN_clear_free(asn_sig->s); + } + asn_sig->s = BN_bin2bn(sigbuf + size / 2, size / 2, NULL); + if (!asn_sig->s) { + ECDSA_SIG_free(asn_sig); + throw std::bad_alloc(); + } + } else { + asn_sig = d2i_ECDSA_SIG(NULL, &sigbuf, static_cast(len)); + if (!asn_sig) { + // Don't throw: just return false + return false; + } + } + int status = ECDSA_do_verify(&tbs_[0], static_cast(tbs_.size()), + asn_sig, eckey_); + ECDSA_SIG_free(asn_sig); + switch (status) { + case 1: + return true; + case 0: + return false; + case -1: + default: + isc_throw(LibraryError, "ECDSA_do_verify"); + } +} + +/// @brief Clear the crypto state and go back to the initial state +void EcDsaAsymImpl::clear() { + tbs_.clear(); +} + +/// @brief Export the key value (binary) +std::vector +EcDsaAsymImpl::exportkey(const AsymKeyKind key_kind, + const AsymFormat key_format) const { + if ((key_kind == PRIVATE) && (key_format == BASIC)) { + // The private key is as just a number + if (kind_ != PRIVATE) { + isc_throw(UnsupportedAlgorithm, "Have no ECDSA Private Key"); + } + const BIGNUM* privkey = EC_KEY_get0_private_key(eckey_); + if (!privkey) { + isc_throw(LibraryError, "EC_KEY_get0_private_key"); + } + int len = BN_num_bytes(privkey); + std::vector bin(static_cast(len)); + len = BN_bn2bin(privkey, &bin[0]); + if (len != static_cast(bin.size())) { + isc_throw(LibraryError, "BN_bn2bin"); + } + return bin; + } else if (key_kind == PRIVATE) { + isc_throw(UnsupportedAlgorithm, + "Unknown ECDSA Private Key format: " << + static_cast(key_format)); + } else if ((key_kind == PUBLIC) && (key_format == ASN1)) { + // SubjectPublicKeyInfo + int len = i2d_EC_PUBKEY(eckey_, NULL); + if (len < 0) { + isc_throw(LibraryError, "i2d_EC_PUBKEY 0"); + } + std::vector der(static_cast(len)); + unsigned char* p = &der[0]; + len = i2d_EC_PUBKEY(eckey_, &p); + if (len != static_cast(der.size())) { + isc_throw(LibraryError, "i2d_EC_PUBKEY"); + } + return der; + } else if ((key_kind == PUBLIC) && (key_format == DNS)) { + // RFC 6605 DNS wire format + int len = i2o_ECPublicKey(eckey_, NULL); + if (len < 1) { + isc_throw(LibraryError, "i2o_ECPublicKey 0"); + } + std::vector pubkey(static_cast(len)); + uint8_t* p = &pubkey[0]; + len = i2o_ECPublicKey(eckey_, &p); + if (len != static_cast(pubkey.size())) { + isc_throw(LibraryError, "i2o_ECPublicKey"); + } + return std::vector(pubkey.begin() + 1, pubkey.end()); + } else if (key_kind == PUBLIC) { + isc_throw(UnsupportedAlgorithm, + "Unknown ECDSA Public Key format: " << + static_cast(key_format)); + } else if ((key_kind == CERT) && (key_format == ASN1)) { + // X.509 Public Key Certificate + if (kind_ != CERT) { + isc_throw(UnsupportedAlgorithm, "Have no Certificate"); + } + int len = i2d_X509(x509_, NULL); + if (len < 0) { + isc_throw(LibraryError, "i2d_X509 0"); + } + std::vector ber(static_cast(len)); + unsigned char* p = &ber[0]; + len = i2d_X509(x509_, &p); + if (len != static_cast(ber.size())) { + isc_throw(LibraryError, "i2d_X509"); + } + return ber; + } else if (key_kind == CERT) { + isc_throw(UnsupportedAlgorithm, + "Unknown Certificate format: " << + static_cast(key_format)); + } else { + isc_throw(UnsupportedAlgorithm, + "Unknown ECDSA Key kind: " << + static_cast(key_kind)); + } +} + +/// @brief Export the key value (file) +void EcDsaAsymImpl::exportkey(const std::string& filename, + const std::string& password, + const AsymKeyKind key_kind, + const AsymFormat key_format) const { + if ((key_kind == PRIVATE) && (key_format == ASN1)) { + // PKCS#8 Private Key PEM file + if (kind_ != PRIVATE) { + isc_throw(UnsupportedAlgorithm, "Have no ECDSA Private Key"); + } + char* pwd = NULL; + const EVP_CIPHER* enc = NULL; + if (!password.empty()) { + pwd = const_cast(password.c_str()); + enc = EVP_des_ede3_cbc(); + if (!enc) { + isc_throw(LibraryError, "EVP_des_ede3_cbc"); + } + } + FILE* fp = fopen(filename.c_str(), "w"); + if (!fp) { + isc_throw(BadKey, "Can't open file: " << filename); + } + EVP_PKEY* pkey = EVP_PKEY_new(); + if (!pkey) { + fclose(fp); + throw std::bad_alloc(); + } + if (!EVP_PKEY_set1_EC_KEY(pkey, eckey_)) { + EVP_PKEY_free(pkey); + fclose(fp); + isc_throw(LibraryError, "EVP_PKEY_set1_EC_KEY"); + } + if (!PEM_write_PKCS8PrivateKey(fp, pkey, enc, pwd, + static_cast(password.size()), + 0, NULL)) { + EVP_PKEY_free(pkey); + fclose(fp); + isc_throw(LibraryError, "PEM_write_PKCS8PrivateKey"); + } + EVP_PKEY_free(pkey); + fclose(fp); + } else if ((key_kind == PRIVATE) && (key_format == DNS)) { + // bind9 .private file + if (kind_ != PRIVATE) { + isc_throw(UnsupportedAlgorithm, "Have no ECDSA Private Key"); + } + FILE* fp = fopen(filename.c_str(), "w"); + if (!fp) { + isc_throw(BadKey, "Can't open file: " << filename); + } + const BIGNUM* privkey = EC_KEY_get0_private_key(eckey_); + if (!privkey) { + fclose(fp); + isc_throw(LibraryError, "EC_KEY_get0_private_key"); + } + fprintf(fp, "Private-key-format: v1.2\n"); + if (hash_ == SHA256) { + fprintf(fp, "Algorithm: 13 (ECDSAP256SHA256)\n"); + } else if (hash_ == SHA256) { + fprintf(fp, "Algorithm: 1 (ECDSAP384SHA384)\n"); + } + std::vector bin; + bin.resize(BN_num_bytes(privkey)); + BN_bn2bin(privkey, &bin[0]); + fprintf(fp, "PrivateKey: %s\n", + util::encode::encodeBase64(bin).c_str()); + fclose(fp); + } else if (key_kind == PRIVATE) { + if (kind_ != PRIVATE) { + isc_throw(UnsupportedAlgorithm, "Have no ECDSA Private Key"); + } + isc_throw(UnsupportedAlgorithm, + "Unknown ECDSA Private Key format: " << + static_cast(key_format)); + } else if ((key_kind == PUBLIC) && (key_format == ASN1)) { + // SubjectPublicKeyInfo PEM file + // warn when password not empty + FILE* fp = fopen(filename.c_str(), "w"); + if (!fp) { + isc_throw(BadKey, "Can't open file: " << filename); + } + if (!PEM_write_EC_PUBKEY(fp, eckey_)) { + fclose(fp); + isc_throw(LibraryError, "PEM_write_EC_PUBKEY"); + } + fclose(fp); + } else if ((key_kind == PUBLIC) && (key_format == DNS)) { + // bind9 .key file (RDATA) + // warn when password not empty + std::vector bin = exportkey(key_kind, key_format); + FILE* fp = fopen(filename.c_str(), "w"); + if (!fp) { + isc_throw(BadKey, "Can't open file: " << filename); + } + fprintf(fp, "; DNSKEY RDATA: %s\n", + util::encode::encodeBase64(bin).c_str()); + fclose(fp); + } else if (key_kind == PUBLIC) { + isc_throw(UnsupportedAlgorithm, + "Unknown ECDSA Public Key format: " << + static_cast(key_format)); + } else if ((key_kind == CERT) && (key_format == ASN1)) { + // Public Key Certificate PEM file + // warn when password not empty + if (!x509_) { + isc_throw(UnsupportedAlgorithm, "Have no Certificate"); + } + FILE* fp = fopen(filename.c_str(), "w"); + if (!fp) { + isc_throw(BadKey, "Can't open file: " << filename); + } + if (!PEM_write_X509(fp, x509_)) { + fclose(fp); + isc_throw(LibraryError, "PEM_write_X509"); + } + fclose(fp); + } else if (key_kind == CERT) { + if (!x509_) { + isc_throw(UnsupportedAlgorithm, "Have no Certificate"); + } + isc_throw(UnsupportedAlgorithm, + "Unknown Certificate format: " << + static_cast(key_format)); + } else { + isc_throw(UnsupportedAlgorithm, + "Unknown ECDSA Key kind: " << + static_cast(key_kind)); + } +} + +/// @brief Check the validity +bool EcDsaAsymImpl::validate() const { + X509_STORE* store; + X509_STORE_CTX* ctx; + int status; + switch (kind_) { + case PUBLIC: + case PRIVATE: + return (EC_KEY_check_key(eckey_) == 1); + case CERT: + store = X509_STORE_new(); + if (!store) { + return false; + } + if (!X509_STORE_add_cert(store, x509_)) { + X509_STORE_free(store); + store = NULL; + return false; + } + ctx = X509_STORE_CTX_new(); + if (!ctx) { + X509_STORE_free(store); + store = NULL; + return false; + } + if (!X509_STORE_CTX_init(ctx, store, x509_, NULL)) { + X509_STORE_CTX_free(ctx); + ctx = NULL; + X509_STORE_free(store); + store = NULL; + return false; + } + // By default OpenSSL skips self-signatures + X509_STORE_CTX_set_flags(ctx, X509_V_FLAG_CHECK_SS_SIGNATURE); + status = X509_verify_cert(ctx); + // text version of status available by + // X509_verify_cert_error_string(X509_STORE_CTX_get_error(ctx)) + X509_STORE_CTX_free(ctx); + ctx = NULL; + X509_STORE_free(store); + store = NULL; + if (status == 1) { + return true; + } + return false; + default: + return false; + } +} + +/// @brief Compare two keys +bool EcDsaAsymImpl::compare(const EcDsaAsymImpl* other, + const AsymKeyKind key_kind) const { + if (!other || (other->algo_ != ECDSA_)) { + return false; + } + const EC_GROUP* grp = EC_KEY_get0_group(eckey_); + const EC_GROUP* ogrp = EC_KEY_get0_group(other->eckey_); + const EC_POINT* pub = EC_KEY_get0_public_key(eckey_); + const EC_POINT* opub = EC_KEY_get0_public_key(other->eckey_); + int status; + switch (key_kind) { + case CERT: + // Special case for cert - cert + if ((kind_ == CERT) && (other->kind_ == CERT)) { + return (X509_cmp(x509_, other->x509_) == 0); + } + // At least one should be a cert + if ((kind_ != CERT) && (other->kind_ != CERT)) { + return false; + } + goto cmppub; + case PRIVATE: + if ((kind_ != PRIVATE) || (other->kind_ != PRIVATE)) { + return false; + } + // If public keys match so private too + goto cmppub; + case PUBLIC: + // Compare curves and public keys + if ((kind_ != PUBLIC) && + (kind_ != PRIVATE) && + (kind_ != CERT)) { + return false; + } + if ((other->kind_ != PUBLIC) && + (other->kind_ != PRIVATE) && + (other->kind_ != CERT)) { + return false; + } + cmppub: + status = EC_GROUP_cmp(grp, ogrp, NULL); + switch (status) { + case 0: + // match but not finished + break; + case 1: + // don't match + return false; + default: + // errors + return false; + } + status = EC_POINT_cmp(grp, pub, opub, NULL); + switch (status) { + case 0: + // match + return true; + case 1: + // don't match + return false; + default: + // errors + return false; + } + default: + return false; + } +} + +} // namespace cryptolink +} // namespace isc diff --git a/src/lib/cryptolink/openssl_ecdsa.h b/src/lib/cryptolink/openssl_ecdsa.h new file mode 100644 index 0000000000..2779532254 --- /dev/null +++ b/src/lib/cryptolink/openssl_ecdsa.h @@ -0,0 +1,149 @@ +// Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC") +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH +// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, +// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE +// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +// PERFORMANCE OF THIS SOFTWARE. + +namespace isc { +namespace cryptolink { + +/// @brief OpenSSL implementation of asymmetrical cryptography (Asym). +// Each method is the counterpart of the Asym corresponding method. +// ECDSA +class EcDsaAsymImpl : public AsymImpl { +public: + /// @brief Constructor from a key, asym and hash algorithm, + /// key kind and key binary format + /// + /// See constructor of the @ref isc::cryptolink::Asym class for details. + /// + /// @param key The key to sign/verify with + /// @param len The length of the key + /// @param hash_algorithm The hash algorithm + /// @param key_kind The key kind + /// @param key_format The key binary format + EcDsaAsymImpl(const void* key, size_t key_len, + const HashAlgorithm hash_algorithm, + const AsymKeyKind key_kind, + const AsymFormat key_format); + + /// @brief Constructor from a key file with password, + /// asym and hash algorithm, key kind and key binary format + /// + /// See constructor of the @ref isc::cryptolink::Asym class for details. + /// + /// @param filename The key file name/path + /// @param password The PKCS#8 password + /// @param hash_algorithm The hash algorithm + /// @param key_kind The key kind + /// @param key_format The key binary format + EcDsaAsymImpl(const std::string& filename, + const std::string& password, + const HashAlgorithm hash_algorithm, + const AsymKeyKind key_kind, + const AsymFormat key_format); + + /// @brief Destructor + virtual ~EcDsaAsymImpl(); + + /// @brief Returns the AsymAlgorithm of the object + AsymAlgorithm getAsymAlgorithm() const; + + /// @brief Returns the HashAlgorithm of the object + HashAlgorithm getHashAlgorithm() const; + + /// @brief Returns the AsymKeyKind of the object + AsymKeyKind getAsymKeyKind() const; + + /// @brief Returns the key size in bits + /// + size_t getKeySize() const; + + /// @brief Returns the output size of the signature + /// + /// \param sig_format The signature binary format + size_t getSignatureLength(const AsymFormat sig_format) const; + + /// @brief Add data to digest + /// + /// See @ref isc::cryptolink::AsymBase::update() for details. + void update(const void* data, const size_t len); + + /// @brief Calculate the final signature + /// + /// See @ref isc::cryptolink::AsymBase::sign() for details. + void sign(isc::util::OutputBuffer& result, size_t len, + const AsymFormat sig_format); + + /// @brief Calculate the final signature + /// + /// See @ref isc::cryptolink::AsymBase::sign() for details. + void sign(void* result, size_t len, const AsymFormat sig_format); + + /// @brief Calculate the final signature + /// + /// See @ref isc::cryptolink::AsymBase::sign() for details. + std::vector sign(size_t len, const AsymFormat sig_format); + + /// @brief Verify an existing signature + /// + /// See @ref isc::cryptolink::AsymBase::verify() for details. + bool verify(const void* sig, size_t len, const AsymFormat sig_format); + + /// @brief Clear the crypto state and go back to the initial state + /// (must be called before reusing an Asym object) + void clear(); + + /// @brief Export the key value (binary) + /// + /// See @ref isc::cryptolink::AsymBase::exportkey() for details + std::vector exportkey(const AsymKeyKind key_kind, + const AsymFormat key_format) const; + + /// @brief Export the key value (file) + /// + /// See @ref isc::cryptolink::AsymBase::exportkey() for details + void exportkey(const std::string& filename, + const std::string& password, + const AsymKeyKind key_kind, + const AsymFormat key_format) const; + + /// @brief Check the validity + /// + /// See @ref isc::cryptolink::AsymBase::validate() for details + bool validate() const; + + /// @brief Compare two keys + /// + /// See @ref isc::cryptolink::Asym::compare() for details + bool compare(const EcDsaAsymImpl* other, const AsymKeyKind key_kind) const; + +private: + /// @brief The asymmetrical cryptography algorithm + AsymAlgorithm algo_; + /// @brief The hash algorithm + HashAlgorithm hash_; + /// @brief The key kind + AsymKeyKind kind_; + /// @brief The to be signed cache + ossl::SecBuf tbs_; + /// @brief The raw pointer to the OpenSSL EC_KEY structure + /// There is no EC_PKEY_init() or EC_PKEY_cleanup() so + /// a smart pointer cannot be used. + EC_KEY* eckey_; + /// @brief The raw pointer to the OpenSSL X509 structure + /// There is no X509_init() or X509_cleanup() so + /// a smart pointer cannot be used. + X509* x509_; +}; + +} // namespace cryptolink +} // namespace isc diff --git a/src/lib/cryptolink/tests/ecdsa_unittests.cc b/src/lib/cryptolink/tests/ecdsa_unittests.cc new file mode 100644 index 0000000000..b63b140255 --- /dev/null +++ b/src/lib/cryptolink/tests/ecdsa_unittests.cc @@ -0,0 +1,507 @@ +// Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC") +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH +// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, +// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE +// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +// PERFORMANCE OF THIS SOFTWARE. + +#include + +#include +#include +#include +#include + +#include + +#include + +#include + +#include +#include + +#include +#include + +#include +#include + +#include + +using boost::lexical_cast; +using namespace isc::util; +using namespace isc::util::encode; +using namespace isc::cryptolink; + +namespace { + +#define P256_OBJ_ID \ +0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x03, \ +0x01, 0x07 + +#define P384_OBJ_ID \ +0x06, 0x05, 0x2b, 0x81, 0x04, 0x00, 0x22 + +#define PRIVATE_KEY \ +0xdc, 0x51, 0xd3, 0x86, 0x6a, 0x15, 0xba, 0xcd, \ +0xe3, 0x3d, 0x96, 0xf9, 0x92, 0xfc, 0xa9, 0x9d, \ +0xa7, 0xe6, 0xef, 0x09, 0x34, 0xe7, 0x09, 0x75, \ +0x59, 0xc2, 0x7f, 0x16, 0x14, 0xc8, 0x8a, 0x7f + +#define PUBLIC_KEY \ +0x24, 0x42, 0xa5, 0xcc, 0x0e, 0xcd, 0x01, 0x5f, \ +0xa3, 0xca, 0x31, 0xdc, 0x8e, 0x2b, 0xbc, 0x70, \ +0xbf, 0x42, 0xd6, 0x0c, 0xbc, 0xa2, 0x00, 0x85, \ +0xe0, 0x82, 0x2c, 0xb0, 0x42, 0x35, 0xe9, 0x70, \ +0x6f, 0xc9, 0x8b, 0xd7, 0xe5, 0x02, 0x11, 0xa4, \ +0xa2, 0x71, 0x02, 0xfa, 0x35, 0x49, 0xdf, 0x79, \ +0xeb, 0xcb, 0x4b, 0xf2, 0x46, 0xb8, 0x09, 0x45, \ +0xcd, 0xdf, 0xe7, 0xd5, 0x09, 0xbb, 0xfd, 0x7d + +#define TBS_DATA \ +0x61, 0x62, 0x63 + +#define SIGNATURE_R \ +0xcb, 0x28, 0xe0, 0x99, 0x9b, 0x9c, 0x77, 0x15, \ +0xfd, 0x0a, 0x80, 0xd8, 0xe4, 0x7a, 0x77, 0x07, \ +0x97, 0x16, 0xcb, 0xbf, 0x91, 0x7d, 0xd7, 0x2e, \ +0x97, 0x56, 0x6e, 0xa1, 0xc0, 0x66, 0x95, 0x7c + +#define SIGNATURE_S \ +0x86, 0xfa, 0x3b, 0xb4, 0xe2, 0x6c, 0xad, 0x5b, \ +0xf9, 0x0b, 0x7f, 0x81, 0x89, 0x92, 0x56, 0xce, \ +0x75, 0x94, 0xbb, 0x1e, 0xa0, 0xc8, 0x92, 0x12, \ +0x74, 0x8b, 0xff, 0x3b, 0x3d, 0x5b, 0x03, 0x15 + + /// @brief Public Key in SubjectPublicKeyInfo format + const uint8_t pubspki[] = { + 0x30, // tag=SEQUENCE (SubjectPublicKeyInfo) + 0x59, // length=89 + 0x30, // tag=SEQUENCE (algorithm : AlgorithmIdentifier) + 0x13, // length=19 + 0x06, // tag=OBJECTID (algorithm) + 0x07, // length=7 + 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, 0x01, // id-ecPublicKey + 0x06, // tag=OBJECTID (namedCurve) + 0x08, // length=8 + 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07, // X9_62_prime256v1 + 0x03, // tag=BIT STRING + 0x42, // length=66 + 0x00, 0x04, PUBLIC_KEY + }; + size_t pubspkilen = 91; + + /// @brief Public Key in DNSSEC format (length 132) + const uint8_t pubdns[] = { + PUBLIC_KEY + }; + size_t pubdnslen = 64; + + /// @brief ASN.1 ECDSA Signature layout + const uint8_t asn1sig[] = { + 0x30, // tag=SEQUENCE + 0x45, // length=69 + 0x02, // tag=INTEGER + 0x21, // length=33 + 0x00, SIGNATURE_R, + 0x02, // tag=INTEGER + 0x21, // length=33 + 0x00, SIGNATURE_S + }; + size_t asn1siglen = 72; + + /// @brief Public Key file (SubjectPublicKeyInfo in PEM) + const std::string pubfile = TEST_DATA_SRCDIR "/ecpub.pem"; + + /// @brief Private Key file (PKCS#8 in PEM, password '1234') + const std::string privfile = TEST_DATA_SRCDIR "/ecpkcs8.pem"; + + /// @brief Certificate file (X.509 Public Key Certificate in PEM) + const std::string certfile = TEST_DATA_SRCDIR "/ecx509.pem"; + + /// @brief Compare data with expected value + /// @param data Value to compare + /// @param expected Expected value + /// @param len Length of the expected value + void checkData(const uint8_t* data, const uint8_t* expected, + size_t len) { + for (size_t i = 0; i < len; ++i) { + ASSERT_EQ(expected[i], data[i]); + } + } + + /// @brief Compare OutputBuffer with expected value + /// encapsulated checkData() + /// @param buf buffer to compare + /// @param expected Expected value + /// @param len Length of the expected value + void checkBuffer(const OutputBuffer& buf, const uint8_t* expected, + size_t len) + { + ASSERT_EQ(len, buf.getLength()); + checkData(static_cast(buf.getData()), expected, + len); + } + + /// @brief Sign and verify with an instantiation of an asymmetrical + /// cryptography object + /// See @ref doEcDsaTest for parameters + void doEcDsaTestDirect(const std::string& data, + const std::string& privfilename, + const std::string& password, + const std::string& pubfilename, + const AsymAlgorithm asym_algorithm, + const HashAlgorithm hash_algorithm, + const AsymFormat sig_format, + const uint8_t* expected_sig, + size_t sig_len) { + OutputBuffer data_buf(data.size()); + data_buf.writeData(data.c_str(), data.size()); + OutputBuffer sig(1); + CryptoLink& crypto = CryptoLink::getCryptoLink(); + + // Sign it + boost::shared_ptr ecdsa_sign(crypto.createAsym(privfilename, + password, + asym_algorithm, + hash_algorithm, + PRIVATE, + ASN1), + deleteAsym); + ecdsa_sign->update(data_buf.getData(), data_buf.getLength()); + ecdsa_sign->sign(sig, sig_len, sig_format); + + // Check whether we can verify it ourselves + boost::shared_ptr ecdsa_verify(crypto.createAsym(pubfilename, + "", + asym_algorithm, + hash_algorithm, + PUBLIC, + ASN1), + deleteAsym); + ecdsa_verify->update(data_buf.getData(), data_buf.getLength()); + EXPECT_TRUE(ecdsa_verify->verify(sig.getData(), + sig.getLength(), + sig_format)); + + // Check if the signature we expect can verify + // (beware we can't compare signature as they are random) + EXPECT_TRUE(ecdsa_verify->verify(expected_sig, + sig_len, + sig_format)); + + // Change the length and check whether verification fails then + // Relies on the fact the length is checked first + EXPECT_FALSE(ecdsa_verify->verify(sig.getData(), + sig.getLength() - 1, + sig_format)); + EXPECT_FALSE(ecdsa_verify->verify(sig.getData(), + sig.getLength() + 1, + sig_format)); + + // Change the sig by flipping the first octet, and check + // whether verification fails then + sig.writeUint8At(~sig[0], 0); + EXPECT_FALSE(ecdsa_verify->verify(sig.getData(), + sig.getLength(), + sig_format)); + } + + /// @brief Sign and verify with vector representation of signature + /// cryptography object + /// See @ref doEcDsaTest for parameters + void doEcDsaTestVector(const std::string& data, + const std::string& privfilename, + const std::string& password, + const std::string& pubfilename, + const AsymAlgorithm asym_algorithm, + const HashAlgorithm hash_algorithm, + const AsymFormat sig_format, + const uint8_t* expected_sig, + size_t sig_len) { + CryptoLink& crypto = CryptoLink::getCryptoLink(); + boost::shared_ptr ecdsa_sign(crypto.createAsym(privfilename, + password, + asym_algorithm, + hash_algorithm, + PRIVATE, + ASN1), + deleteAsym); + ecdsa_sign->update(data.c_str(), data.size()); + std::vector sig = ecdsa_sign->sign(sig_len, sig_format); + ASSERT_EQ(sig_len, sig.size()); + + boost::shared_ptr ecdsa_verify(crypto.createAsym(pubfilename, + "", + asym_algorithm, + hash_algorithm, + PUBLIC, + ASN1), + deleteAsym); + ecdsa_verify->update(data.c_str(), data.size()); + EXPECT_TRUE(ecdsa_verify->verify(&sig[0], sig.size(), sig_format)); + + EXPECT_TRUE(ecdsa_verify->verify(expected_sig, + sig_len, + sig_format)); + + EXPECT_FALSE(ecdsa_verify->verify(&sig[0], + sig.size() - 1, + sig_format)); + EXPECT_FALSE(ecdsa_verify->verify(&sig[0], + sig.size() + 1, + sig_format)); + + sig[0] = ~sig[0]; + EXPECT_FALSE(ecdsa_verify->verify(&sig[0], sig.size(), sig_format)); + } + + /// @brief Sign and verify with array representation of signature + /// cryptography object + /// See @ref doEcDsaTest for parameters + void doEcDsaTestArray(const std::string& data, + const std::string& privfilename, + const std::string& password, + const std::string& pubfilename, + const AsymAlgorithm asym_algorithm, + const HashAlgorithm hash_algorithm, + const AsymFormat sig_format, + const uint8_t* expected_sig, + size_t sig_len) { + CryptoLink& crypto = CryptoLink::getCryptoLink(); + boost::shared_ptr ecdsa_sign(crypto.createAsym(privfilename, + password, + asym_algorithm, + hash_algorithm, + PRIVATE, + ASN1), + deleteAsym); + ecdsa_sign->update(data.c_str(), data.size()); + + // note: this is not exception-safe, and can leak, but + // if there is an unexpected exception in the code below we + // have more important things to fix. + uint8_t* sig = new uint8_t[sig_len]; + + ecdsa_sign->sign(sig, sig_len, sig_format); + + boost::shared_ptr ecdsa_verify(crypto.createAsym(pubfilename, + "", + asym_algorithm, + hash_algorithm, + PUBLIC, + ASN1), + deleteAsym); + ecdsa_verify->update(data.c_str(), data.size()); + EXPECT_TRUE(ecdsa_verify->verify(sig, sig_len, sig_format)); + + EXPECT_TRUE(ecdsa_verify->verify(expected_sig, sig_len, sig_format)); + + EXPECT_FALSE(ecdsa_verify->verify(sig, sig_len - 1, sig_format)); + EXPECT_FALSE(ecdsa_verify->verify(sig, sig_len + 1, sig_format)); + + sig[0] = ~sig[0]; + EXPECT_FALSE(ecdsa_verify->verify(sig, sig_len, sig_format)); + + delete[] sig; + } + + /// @brief Sign and verify using all variants + /// @param data Input value + /// @param privfilename Private key file name + /// @param password Private key password + /// @param pubfilename Public key file name + /// @param asym_algorithm Asym algorithm enum + /// @param hash_algorithm Hash algorithm enum + /// @param sig_format Signature format enum + /// @param expected_sig Expected value + /// @param sig_len Expected value length + void doEcDsaTest(const std::string& data, + const std::string& privfilename, + const std::string& password, + const std::string& pubfilename, + const AsymAlgorithm asym_algorithm, + const HashAlgorithm hash_algorithm, + const AsymFormat sig_format, + const uint8_t* expected_sig, + size_t sig_len) { + doEcDsaTestDirect(data, privfilename, password, pubfilename, + asym_algorithm, hash_algorithm, sig_format, + expected_sig, sig_len); + doEcDsaTestVector(data, privfilename, password, pubfilename, + asym_algorithm, hash_algorithm, sig_format, + expected_sig, sig_len); + doEcDsaTestArray(data, privfilename, password, pubfilename, + asym_algorithm, hash_algorithm, sig_format, + expected_sig, sig_len); + } +} + +// +// Test values +// +TEST(EcDSATest, SHA256) { + uint8_t privkey[] = { + PRIVATE_KEY + }; + size_t privkeylen = 32; + uint8_t pubkey[] = { + PUBLIC_KEY + }; + size_t pubkeylen = 64; + uint8_t data[] = { + TBS_DATA + }; + size_t datalen = 3; + uint8_t sig[] = { + SIGNATURE_R, + SIGNATURE_S + }; + size_t siglen = 64; + CryptoLink& crypto = CryptoLink::getCryptoLink(); + boost::shared_ptr ecdsa_sign(crypto.createAsym(privkey, + privkeylen, + ECDSA_, + SHA256, + PRIVATE, + BASIC), + deleteAsym); + ASSERT_TRUE(ecdsa_sign); + EXPECT_EQ(ECDSA_, ecdsa_sign->getAsymAlgorithm()); + EXPECT_EQ(SHA256, ecdsa_sign->getHashAlgorithm()); + EXPECT_EQ(PRIVATE, ecdsa_sign->getAsymKeyKind()); + EXPECT_EQ(256, ecdsa_sign->getKeySize()); + EXPECT_EQ(64, ecdsa_sign->getSignatureLength(BASIC)); + EXPECT_EQ(72, ecdsa_sign->getSignatureLength(ASN1)); + EXPECT_EQ(64, ecdsa_sign->getSignatureLength(DNS)); + + std::vector basicprivkey = ecdsa_sign->exportkey(PRIVATE, BASIC); + ASSERT_EQ(32, basicprivkey.size()); + EXPECT_TRUE(std::memcmp(privkey, &basicprivkey[0], privkeylen) == 0); + std::vector ans1pubkey = ecdsa_sign->exportkey(PUBLIC, ASN1); + ASSERT_EQ(pubspkilen, ans1pubkey.size()); + EXPECT_TRUE(std::memcmp(pubspki, &ans1pubkey[0], pubspkilen) == 0); + std::vector dnspubkey = ecdsa_sign->exportkey(PUBLIC, DNS); + ASSERT_EQ(64, dnspubkey.size()); + EXPECT_TRUE(std::memcmp(pubkey, &dnspubkey[0], pubkeylen) == 0); + + ecdsa_sign->update(data, datalen); + OutputBuffer nsig(1); + size_t nsig_len = ecdsa_sign->getSignatureLength(BASIC); + ecdsa_sign->sign(nsig, nsig_len, BASIC); + + boost::shared_ptr ecdsa_verify(crypto.createAsym(pubfile, + "", + ECDSA_, + SHA256, + PUBLIC, + ASN1), + deleteAsym); + ASSERT_TRUE(ecdsa_verify); + + ecdsa_verify->update(data, datalen); + EXPECT_TRUE(ecdsa_verify->verify(nsig.getData(), nsig.getLength(), BASIC)); + + EXPECT_TRUE(ecdsa_verify->verify(sig, siglen, DNS)); + + EXPECT_TRUE(ecdsa_verify->verify(asn1sig, asn1siglen, ASN1)); +} + +// +// Multiple signatures +// +TEST(EcDsaTest, doubleSign) { + std::string data = "Kea provides DHCPv4 and DHCPv6 servers"; + OutputBuffer data_buf(data.size()); + data_buf.writeData(data.c_str(), data.size()); + CryptoLink& crypto = CryptoLink::getCryptoLink(); + + // Sign it + boost::shared_ptr ecdsa_sign(crypto.createAsym(privfile, "1234", + ECDSA_, SHA256, + PRIVATE, ASN1), + deleteAsym); + ASSERT_TRUE(ecdsa_sign); + + OutputBuffer sig1(1); + size_t sig1_len = ecdsa_sign->getSignatureLength(BASIC); + EXPECT_EQ(64, sig1_len); + ecdsa_sign->update(data_buf.getData(), data_buf.getLength()); + ecdsa_sign->sign(sig1, sig1_len, BASIC); + ASSERT_EQ(sig1_len, sig1.getLength()); + + // Clear state + ecdsa_sign->clear(); + + // Sign it again + OutputBuffer sig2(1); + size_t sig2_len = ecdsa_sign->getSignatureLength(BASIC); + EXPECT_EQ(64, sig2_len); + ecdsa_sign->update(data_buf.getData(), data_buf.getLength()); + ecdsa_sign->sign(sig2, sig2_len, BASIC); + EXPECT_EQ(sig2_len, sig2.getLength()); + + // Compare + ASSERT_EQ(sig1_len, sig2_len); + + // Verify + boost::shared_ptr ecdsa_verify(crypto.createAsym(pubfile, "", + ECDSA_, SHA256, + PUBLIC, ASN1), + deleteAsym); + ASSERT_TRUE(ecdsa_verify); + + ecdsa_verify->update(data_buf.getData(), data_buf.getLength()); + EXPECT_TRUE(ecdsa_verify->verify(sig1.getData(), sig1.getLength(), BASIC)); + EXPECT_TRUE(ecdsa_verify->verify(sig2.getData(), sig2.getLength(), BASIC)); + +} + +// +// Multiple verifies +// +TEST(EcdsaTest, doubleVerify) { + std::string data = "Kea provides DHCPv4 and DHCPv6 servers"; + OutputBuffer data_buf(data.size()); + data_buf.writeData(data.c_str(), data.size()); + CryptoLink& crypto = CryptoLink::getCryptoLink(); + + // Sign it + boost::shared_ptr ecdsa_sign(crypto.createAsym(privfile, "1234", + ECDSA_, SHA256, + PRIVATE, ASN1), + deleteAsym); + ASSERT_TRUE(ecdsa_sign); + + OutputBuffer sig(1); + size_t sig_len = ecdsa_sign->getSignatureLength(BASIC); + EXPECT_EQ(64, sig_len); + ecdsa_sign->update(data_buf.getData(), data_buf.getLength()); + ecdsa_sign->sign(sig, sig_len, BASIC); + EXPECT_EQ(sig_len, sig.getLength()); + + // Verify + boost::shared_ptr ecdsa_verify(crypto.createAsym(pubfile, "", + ECDSA_, SHA256, + PUBLIC, ASN1), + deleteAsym); + ASSERT_TRUE(ecdsa_verify); + + ecdsa_verify->update(data_buf.getData(), data_buf.getLength()); + EXPECT_TRUE(ecdsa_verify->verify(sig.getData(), sig.getLength(), BASIC)); + + // Clear state + ecdsa_verify->clear(); + + // Verify again + ecdsa_verify->update(data_buf.getData(), data_buf.getLength()); + EXPECT_TRUE(ecdsa_verify->verify(sig.getData(), sig.getLength(), BASIC)); +} diff --git a/src/lib/cryptolink/tests/rsa_unittests.cc b/src/lib/cryptolink/tests/rsa_unittests.cc new file mode 100644 index 0000000000..f5b0c5468e --- /dev/null +++ b/src/lib/cryptolink/tests/rsa_unittests.cc @@ -0,0 +1,877 @@ +// Copyright (C) 2014, 2015 Internet Systems Consortium, Inc. ("ISC") +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH +// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, +// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE +// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +// PERFORMANCE OF THIS SOFTWARE. + +#include + +#include +#include +#include +#include + +#include + +#include + +#include + +#include +#include + +#include +#include + +#include +#include + +#include + +using boost::lexical_cast; +using namespace isc::util; +using namespace isc::util::encode; +using namespace isc::cryptolink; + +namespace { + +#define MODULUS \ +0xb3, 0x25, 0xc2, 0x01, 0xee, 0x8c, 0x91, 0x5d, \ +0xa3, 0xea, 0x55, 0x90, 0xa2, 0x2c, 0xb3, 0xb1, \ +0x35, 0x18, 0x7b, 0xa1, 0x00, 0x80, 0x4f, 0x21, \ +0x32, 0x94, 0xd1, 0xa2, 0xc3, 0x1a, 0xbc, 0x0a, \ +0xee, 0xcd, 0x7f, 0x23, 0x12, 0x5d, 0xf8, 0x92, \ +0xcb, 0x87, 0x69, 0xfd, 0x85, 0x0f, 0x25, 0x57, \ +0x88, 0x69, 0x4b, 0x5e, 0x93, 0x12, 0x3a, 0x93, \ +0xa9, 0x23, 0x64, 0x0f, 0x7c, 0xaa, 0x20, 0xca, \ +0x56, 0x93, 0x81, 0xe9, 0x64, 0xb2, 0x20, 0xad, \ +0x55, 0x25, 0x87, 0xb1, 0x85, 0xc8, 0xd6, 0xfe, \ +0x5f, 0xa9, 0xcd, 0x53, 0x2e, 0xaf, 0x9e, 0x53, \ +0xa4, 0xfb, 0xba, 0xef, 0x30, 0x53, 0x9d, 0x01, \ +0xee, 0xb7, 0xff, 0xaa, 0xde, 0x7a, 0xfa, 0x57, \ +0xfb, 0xdf, 0x63, 0xf3, 0x9c, 0x77, 0x2e, 0xa4, \ +0x97, 0x10, 0x30, 0xc1, 0x15, 0x48, 0x6b, 0x01, \ +0x1e, 0x57, 0x8d, 0x5d, 0xeb, 0xfc, 0x42, 0xe1 + +#define PUBLIC_EXPONENT 0x01, 0x00, 0x01 + +#define PUBLIC_KEY \ +0x30, 0x81, 0x89, \ +0x02, 0x81, 0x81, 0x00, MODULUS, \ +0x02, 0x03, PUBLIC_EXPONENT + +#define PRIVATE_EXPONENT \ +0xa8, 0xc9, 0x93, 0x5f, 0xe4, 0x94, 0xf6, 0x45, \ +0x26, 0xb2, 0x1b, 0x8a, 0x18, 0xf2, 0x4b, 0x1f, \ +0x54, 0x2a, 0x4c, 0x18, 0xe6, 0x72, 0xfd, 0x9b, \ +0x06, 0xa0, 0x26, 0x5f, 0xd6, 0xb9, 0x32, 0xa0, \ +0x8e, 0x5c, 0x79, 0x43, 0xdf, 0x03, 0x40, 0xb7, \ +0x76, 0x21, 0x90, 0xa0, 0x37, 0x24, 0x8d, 0x07, \ +0x4a, 0xd4, 0x02, 0x1a, 0x0a, 0x31, 0x6b, 0x95, \ +0x42, 0x0f, 0xc1, 0x2f, 0xc2, 0x42, 0x3c, 0x7f, \ +0x33, 0xb3, 0x54, 0x2f, 0x83, 0xf8, 0x5d, 0x7f, \ +0x86, 0xa5, 0xe8, 0xbe, 0xae, 0x40, 0x41, 0x20, \ +0x5d, 0xa2, 0x66, 0x9a, 0x82, 0x66, 0x03, 0xfc, \ +0xf0, 0x62, 0x24, 0x08, 0x25, 0x99, 0xc6, 0x2b, \ +0xef, 0x9d, 0x79, 0x54, 0x21, 0x91, 0x62, 0x76, \ +0xe5, 0x0b, 0x30, 0xe7, 0x96, 0x9c, 0xfa, 0x45, \ +0xf1, 0x6f, 0xa7, 0x6b, 0x94, 0xa2, 0x70, 0x3d, \ +0xdd, 0x74, 0x6d, 0x4c, 0x8e, 0x8e, 0xe7, 0x01 + +#define PRIME1 \ +0xda, 0x36, 0xde, 0xd3, 0x0a, 0x15, 0x20, 0xac, \ +0x79, 0xa7, 0xe0, 0xa6, 0x55, 0x69, 0xea, 0xcd, \ +0xf7, 0xe7, 0xf3, 0xda, 0xf6, 0xeb, 0xdc, 0xdb, \ +0x7c, 0xdd, 0x13, 0x6c, 0xaa, 0xfd, 0x6e, 0x98, \ +0x7b, 0x54, 0x25, 0xc0, 0x3b, 0x7d, 0xf9, 0xb5, \ +0x5b, 0x97, 0x2f, 0xe6, 0xc1, 0x4d, 0x7d, 0x5e, \ +0x3b, 0x51, 0x9c, 0xc6, 0xb3, 0xd4, 0x23, 0x43, \ +0xbf, 0x42, 0x98, 0x50, 0x03, 0xfb, 0x0b, 0x51 + +#define PRIME2 \ +0xd2, 0x2b, 0x1c, 0x29, 0x83, 0x01, 0xfd, 0x41, \ +0x49, 0xfb, 0x95, 0x89, 0x7c, 0xaf, 0x81, 0x81, \ +0xce, 0xd1, 0x77, 0x5f, 0x65, 0x61, 0x61, 0x07, \ +0xd5, 0x45, 0x58, 0xc5, 0xdd, 0x73, 0x6b, 0x63, \ +0x3a, 0x82, 0x92, 0xad, 0xa6, 0x71, 0xa1, 0xcf, \ +0xe3, 0xb5, 0x10, 0x52, 0x42, 0xe5, 0x76, 0x8a, \ +0x1b, 0xca, 0x6a, 0x52, 0x9d, 0x54, 0x78, 0x07, \ +0xcf, 0x1e, 0xed, 0xe4, 0xf2, 0xf2, 0xba, 0x91 + +#define EXPONENT1 \ +0x6e, 0xf6, 0xa4, 0x0c, 0x90, 0xfd, 0xf9, 0x65, \ +0x7b, 0x5f, 0xa0, 0xdf, 0x34, 0x63, 0xed, 0xe0, \ +0xdb, 0x05, 0x7a, 0x7d, 0x88, 0x3e, 0x9c, 0x4a, \ +0x88, 0x8e, 0x2b, 0x08, 0x81, 0x52, 0xea, 0x60, \ +0x63, 0xa6, 0x80, 0xa3, 0xe6, 0x1c, 0xc3, 0x54, \ +0x33, 0xc5, 0x07, 0xb8, 0xc1, 0xe7, 0x53, 0xaf, \ +0x0d, 0x5f, 0x0c, 0xe8, 0x06, 0x1e, 0x03, 0xe8, \ +0xb9, 0x63, 0x75, 0xec, 0x8a, 0x79, 0xa8, 0x61 + +#define EXPONENT2 \ +0x7c, 0x6d, 0x29, 0x6f, 0x2a, 0x30, 0xb3, 0x4f, \ +0x44, 0x0d, 0xbe, 0xaa, 0x77, 0x37, 0x30, 0xe5, \ +0x39, 0x1c, 0xaa, 0x1f, 0xc0, 0x55, 0xb6, 0xac, \ +0x7c, 0x87, 0x61, 0xd7, 0x43, 0x14, 0x62, 0x2f, \ +0x8d, 0x24, 0x60, 0xd2, 0x8e, 0x08, 0x18, 0x54, \ +0x8b, 0xeb, 0x56, 0x8e, 0x5d, 0x2c, 0x9c, 0xd1, \ +0x87, 0x42, 0x7f, 0x50, 0x09, 0xf5, 0x48, 0x5a, \ +0xd7, 0x34, 0xe8, 0x82, 0xf3, 0x92, 0xe1, 0x01 + +#define COEFFICIENT \ +0x13, 0x5f, 0x97, 0x80, 0x10, 0x66, 0x54, 0x05, \ +0x55, 0x05, 0x03, 0x37, 0x96, 0xe0, 0xc0, 0xa7, \ +0x25, 0x7b, 0x70, 0x4a, 0xd2, 0x0e, 0xce, 0x82, \ +0x42, 0x3e, 0xb9, 0x5a, 0x2d, 0x87, 0xca, 0xfa, \ +0xaa, 0x82, 0xe4, 0xed, 0xdb, 0xbf, 0xbf, 0x26, \ +0x9b, 0x50, 0x84, 0x9e, 0xb0, 0xd0, 0x10, 0xb4, \ +0x79, 0x56, 0x1c, 0xd1, 0x49, 0xa1, 0x4f, 0xf8, \ +0x52, 0x6e, 0xb4, 0x1b, 0x1a, 0x5f, 0x48, 0x0c + +#define PRIVATE_KEY \ +0x30, 0x82, 0x02, 0x5c, 0x02, 0x01, 0x00, \ +0x02, 0x81, 0x81, 0x00, MODULUS, \ +0x02, 0x03, PUBLIC_EXPONENT, \ +0x02, 0x81, 0x81, 0x00, PRIVATE_EXPONENT, \ +0x02, 0x41, 0x00, PRIME1, \ +0x02, 0x41, 0x00, PRIME2, \ +0x02, 0x40, EXPONENT1, \ +0x02, 0x40, EXPONENT2, \ +0x02, 0x40, COEFFICIENT + + /// @brief Public Key in PKCS#1 format + const uint8_t pubpkcs1[] = { + 0x30, // tag=SEQUENCE (RSAPublicKey) + 0x81, 0x89, // length=137 + 0x02, // tag=INTEGER (modulus -- n) + 0x81, 0x81, // length=129 + 0x00, MODULUS, + 0x02, // tag=INTEGER (publicExponent -- e) + 0x03, // length=3 + PUBLIC_EXPONENT + }; + size_t pubpkcs1len = 140; + + /// @brief Public Key in SubjectPublicKeyInfo format + const uint8_t pubspki[] = { + 0x30, // tag=SEQUENCE (SubjectPublicKeyInfo) + 0x81, 0x9f, // length=159 + 0x30, // tag=SEQUENCE (algorithm : AlgorithmIdentifier) + 0x0d, // length=13 + 0x06, // tag=OBJECTID (algorithm) + 0x09, // length=9 + 0x2a, 0x86, 0x48, 0x86, + 0xf7, 0x0d, 0x01, 0x01, + 0x01, // value=rsaEncryption + 0x05, // tag=NULL (parameter) + 0x00, // length=00 + 0x03, // tag=BIT STRING (subjectPublicKey) + 0x81, 0x8d, // length=141 + 0x00, PUBLIC_KEY + }; + size_t pubspkilen = 162; + + /// @brief Public Key in DNSSEC format (length 132) + const uint8_t pubdns[] = { + 0x03, // length=3 + PUBLIC_EXPONENT, + MODULUS + }; + size_t pubdnslen = 132; + + /// @brief Private Key in PKCS#1 format + const uint8_t privpkcs1[] = { + 0x30, // tag=SEQUENCE (RSAPrivateKey) + 0x82, 0x02, 0x5c, // length=604 + 0x02, // tag=INTEGER (version : Version) + 0x01, // length=1 + 0x00, // value=0 + 0x02, // tag=INTEGER (modulus -- n) + 0x81, 0x81, // length=129 + 0x00, MODULUS, + 0x02, // tag=INTEGER (publicExponent -- e) + 0x03, // length=3 + PUBLIC_EXPONENT, + 0x02, // tag=INTEGER (privateExponent -- d) + 0x81, 0x81, // length=129 + 0x00, PRIVATE_EXPONENT, + 0x02, // tag=INTEGER (prime1 -- p) + 0x41, // length=65 + 0x00, PRIME1, + 0x02, // tag=INTEGER (prime2 -- q) + 0x41, // length=65 + 0x00, PRIME2, + 0x02, // tag=INTEGER (exponent1 -- d mod (p-1)) + 0x40, // length=64 + EXPONENT1, + 0x02, // tag=INTEGER (exponent2 -- d mod (q-1)) + 0x40, // length=64 + EXPONENT2, + 0x02, // tag=INTEGER (coefficient -- 1/q mod p) + 0x40, // length=64 + COEFFICIENT + }; + size_t privpkcs1len = 608; + + /// @brief Public Key file (SubjectPublicKeyInfo in PEM) + const std::string pubfile = TEST_DATA_SRCDIR "/pub.pem"; + + /// @brief Private Key file (PKCS#8 in PEM, password '1234') + const std::string privfile = TEST_DATA_SRCDIR "/pkcs8.pem"; + + /// @brief Certificate file (X.509 Public Key Certificate in PEM) + const std::string certfile = TEST_DATA_SRCDIR "/x509.pem"; + + /// @brief Compare data with expected value + /// @param data Value to compare + /// @param expected Expected value + /// @param len Length of the expected value + void checkData(const uint8_t* data, const uint8_t* expected, + size_t len) { + for (size_t i = 0; i < len; ++i) { + ASSERT_EQ(expected[i], data[i]); + } + } + + /// @brief Compare OutputBuffer with expected value + /// encapsulated checkData() + /// @param buf buffer to compare + /// @param expected Expected value + /// @param len Length of the expected value + void checkBuffer(const OutputBuffer& buf, const uint8_t* expected, + size_t len) + { + ASSERT_EQ(len, buf.getLength()); + checkData(static_cast(buf.getData()), expected, + len); + } + + /// @brief Sign and verify with an instantiation of an asymmetrical + /// cryptography object + /// See @ref doRsaTest for parameters + void doRsaTestDirect(const std::string& data, + const std::string& privfilename, + const std::string& password, + const std::string& pubfilename, + const AsymAlgorithm asym_algorithm, + const HashAlgorithm hash_algorithm, + const AsymFormat sig_format, + const uint8_t* expected_sig, + size_t sig_len) { + OutputBuffer data_buf(data.size()); + data_buf.writeData(data.c_str(), data.size()); + OutputBuffer sig(1); + CryptoLink& crypto = CryptoLink::getCryptoLink(); + + // Sign it + boost::shared_ptr rsa_sign(crypto.createAsym(privfilename, + password, + asym_algorithm, + hash_algorithm, + PRIVATE, + ASN1), + deleteAsym); + rsa_sign->update(data_buf.getData(), data_buf.getLength()); + rsa_sign->sign(sig, sig_len, sig_format); + + // Check if the signature is what we expect + checkBuffer(sig, expected_sig, sig_len); + + // Check whether we can verify it ourselves + boost::shared_ptr rsa_verify(crypto.createAsym(pubfilename, + "", + asym_algorithm, + hash_algorithm, + PUBLIC, + ASN1), + deleteAsym); + rsa_verify->update(data_buf.getData(), data_buf.getLength()); + EXPECT_TRUE(rsa_verify->verify(sig.getData(), + sig.getLength(), + sig_format)); + + // Change the length and check whether verification fails then + // Relies on the fact the length is checked first + EXPECT_FALSE(rsa_verify->verify(sig.getData(), + sig.getLength() - 1, + sig_format)); + EXPECT_FALSE(rsa_verify->verify(sig.getData(), + sig.getLength() + 1, + sig_format)); + + // Change the sig by flipping the first octet, and check + // whether verification fails then + sig.writeUint8At(~sig[0], 0); + EXPECT_FALSE(rsa_verify->verify(sig.getData(), + sig.getLength(), + sig_format)); + } + + /// @brief Sign and verify with vector representation of signature + /// cryptography object + /// See @ref doRsaTest for parameters + void doRsaTestVector(const std::string& data, + const std::string& privfilename, + const std::string& password, + const std::string& pubfilename, + const AsymAlgorithm asym_algorithm, + const HashAlgorithm hash_algorithm, + const AsymFormat sig_format, + const uint8_t* expected_sig, + size_t sig_len) { + CryptoLink& crypto = CryptoLink::getCryptoLink(); + boost::shared_ptr rsa_sign(crypto.createAsym(privfilename, + password, + asym_algorithm, + hash_algorithm, + PRIVATE, + ASN1), + deleteAsym); + rsa_sign->update(data.c_str(), data.size()); + std::vector sig = rsa_sign->sign(sig_len, sig_format); + ASSERT_EQ(sig_len, sig.size()); + checkData(&sig[0], expected_sig, sig_len); + + boost::shared_ptr rsa_verify(crypto.createAsym(pubfilename, + "", + asym_algorithm, + hash_algorithm, + PUBLIC, + ASN1), + deleteAsym); + rsa_verify->update(data.c_str(), data.size()); + EXPECT_TRUE(rsa_verify->verify(&sig[0], sig.size(), sig_format)); + + EXPECT_FALSE(rsa_verify->verify(&sig[0], sig.size() - 1, sig_format)); + EXPECT_FALSE(rsa_verify->verify(&sig[0], sig.size() + 1, sig_format)); + + sig[0] = ~sig[0]; + EXPECT_FALSE(rsa_verify->verify(&sig[0], sig.size(), sig_format)); + } + + /// @brief Sign and verify with array representation of signature + /// cryptography object + /// See @ref doRsaTest for parameters + void doRsaTestArray(const std::string& data, + const std::string& privfilename, + const std::string& password, + const std::string& pubfilename, + const AsymAlgorithm asym_algorithm, + const HashAlgorithm hash_algorithm, + const AsymFormat sig_format, + const uint8_t* expected_sig, + size_t sig_len) { + CryptoLink& crypto = CryptoLink::getCryptoLink(); + boost::shared_ptr rsa_sign(crypto.createAsym(privfilename, + password, + asym_algorithm, + hash_algorithm, + PRIVATE, + ASN1), + deleteAsym); + rsa_sign->update(data.c_str(), data.size()); + + // note: this is not exception-safe, and can leak, but + // if there is an unexpected exception in the code below we + // have more important things to fix. + uint8_t* sig = new uint8_t[sig_len]; + + rsa_sign->sign(sig, sig_len, sig_format); + checkData(sig, expected_sig, sig_len); + + boost::shared_ptr rsa_verify(crypto.createAsym(pubfilename, + "", + asym_algorithm, + hash_algorithm, + PUBLIC, + ASN1), + deleteAsym); + rsa_verify->update(data.c_str(), data.size()); + EXPECT_TRUE(rsa_verify->verify(sig, sig_len, sig_format)); + + EXPECT_FALSE(rsa_verify->verify(sig, sig_len - 1, sig_format)); + EXPECT_FALSE(rsa_verify->verify(sig, sig_len + 1, sig_format)); + + sig[0] = ~sig[0]; + EXPECT_FALSE(rsa_verify->verify(sig, sig_len, sig_format)); + + delete[] sig; + } + + /// @brief Sign and verify using all variants + /// @param data Input value + /// @param privfilename Private key file name + /// @param password Private key password + /// @param pubfilename Public key file name + /// @param asym_algorithm Asym algorithm enum + /// @param hash_algorithm Hash algorithm enum + /// @param sig_format Signature format enum + /// @param expected_sig Expected value + /// @param sig_len Expected value length + void doRsaTest(const std::string& data, + const std::string& privfilename, + const std::string& password, + const std::string& pubfilename, + const AsymAlgorithm asym_algorithm, + const HashAlgorithm hash_algorithm, + const AsymFormat sig_format, + const uint8_t* expected_sig, + size_t sig_len) { + doRsaTestDirect(data, privfilename, password, pubfilename, + asym_algorithm, hash_algorithm, sig_format, + expected_sig, sig_len); + doRsaTestVector(data, privfilename, password, pubfilename, + asym_algorithm, hash_algorithm, sig_format, + expected_sig, sig_len); + doRsaTestArray(data, privfilename, password, pubfilename, + asym_algorithm, hash_algorithm, sig_format, + expected_sig, sig_len); + } +} + +// +// Test values +// +TEST(RsaTest, RSA_SHA1) { + const uint8_t sig_expected[] = { + 0x16, 0xaa, 0xd3, 0x27, 0x5b, 0x22, 0xff, 0x2b, + 0x74, 0x77, 0x7a, 0x20, 0x6c, 0xdc, 0xa6, 0xb1, + 0x88, 0x1a, 0xb3, 0xc6, 0x5a, 0xae, 0x35, 0x3a, + 0x04, 0x8d, 0x7f, 0x81, 0x5b, 0xef, 0xd6, 0xe2, + 0x07, 0xee, 0xec, 0x1e, 0xf4, 0x89, 0x82, 0x6c, + 0x2c, 0x0c, 0x25, 0x8a, 0xf0, 0x8a, 0xde, 0x6c, + 0xf7, 0x66, 0x9d, 0xa6, 0xd5, 0x69, 0x1e, 0x47, + 0x76, 0xf3, 0xe7, 0x47, 0x12, 0xd5, 0x92, 0x45, + 0xb5, 0xc6, 0x50, 0x32, 0xe1, 0x25, 0xd9, 0xa1, + 0x23, 0xd1, 0x12, 0x6d, 0x1f, 0xa8, 0x9e, 0xc3, + 0xdb, 0x41, 0xb3, 0x13, 0x0b, 0x7a, 0xea, 0x72, + 0xa7, 0x60, 0xc8, 0xfd, 0x89, 0xee, 0x36, 0xe4, + 0x5c, 0xa8, 0xfa, 0x86, 0x4b, 0xcc, 0x15, 0x7d, + 0xcb, 0x79, 0x90, 0x51, 0xa2, 0x62, 0x43, 0xc7, + 0xe1, 0x04, 0x08, 0x13, 0x68, 0x6d, 0x7b, 0x4d, + 0x45, 0xca, 0x3e, 0x6c, 0xba, 0x62, 0x90, 0x0a, + // pad + 0x00 + }; + const size_t sig_len = 128; + doRsaTest("Permission to use, copy, modify, and/or " + "distribute this software\n", + privfile, "1234", pubfile, RSA_, SHA1, BASIC, + sig_expected, sig_len); +} + +TEST(RsaTest, RSA_PUB_PKCS1) { + CryptoLink& crypto = CryptoLink::getCryptoLink(); + boost::shared_ptr pub_key(crypto.createAsym(pubpkcs1, pubpkcs1len, + RSA_, SHA1, + PUBLIC, BASIC), + deleteAsym); + ASSERT_TRUE(pub_key->validate()); + EXPECT_EQ(RSA_, pub_key->getAsymAlgorithm()); + EXPECT_EQ(SHA1, pub_key->getHashAlgorithm()); + EXPECT_EQ(PUBLIC, pub_key->getAsymKeyKind()); + EXPECT_EQ(1024, pub_key->getKeySize()); + EXPECT_EQ(128, pub_key->getSignatureLength(BASIC)); + EXPECT_EQ(128, pub_key->getSignatureLength(ASN1)); + EXPECT_EQ(128, pub_key->getSignatureLength(DNS)); + + EXPECT_THROW(crypto.createAsym(pubpkcs1, pubpkcs1len - 1, + RSA_, SHA1, PUBLIC, BASIC), + BadKey); + + boost::shared_ptr ref_key(crypto.createAsym(pubfile, "", + RSA_, SHA1, + PUBLIC, ASN1), + deleteAsym); + EXPECT_TRUE(pub_key->compare(ref_key.get(), PUBLIC)); + EXPECT_TRUE(ref_key->compare(pub_key.get(), PUBLIC)); + EXPECT_FALSE(pub_key->compare(ref_key.get(), PRIVATE)); + EXPECT_FALSE(pub_key->compare(ref_key.get(), CERT)); + + const std::vector pubbin = ref_key->exportkey(PUBLIC, BASIC); + checkData(&pubbin[0], pubpkcs1, pubpkcs1len); +} + +TEST(RsaTest, RSA_PUB_SPKI) { + CryptoLink& crypto = CryptoLink::getCryptoLink(); + boost::shared_ptr pub_key(crypto.createAsym(pubspki, pubspkilen, + RSA_, SHA1, + PUBLIC, ASN1), + deleteAsym); + ASSERT_TRUE(pub_key->validate()); + EXPECT_EQ(RSA_, pub_key->getAsymAlgorithm()); + EXPECT_EQ(SHA1, pub_key->getHashAlgorithm()); + EXPECT_EQ(PUBLIC, pub_key->getAsymKeyKind()); + EXPECT_EQ(1024, pub_key->getKeySize()); + + EXPECT_THROW(crypto.createAsym(pubspki, pubspkilen - 1, + RSA_, SHA1, PUBLIC, ASN1), + BadKey); + EXPECT_THROW(crypto.createAsym(pubspki, pubspkilen, + RSA_, SHA1, PUBLIC, BASIC), + BadKey); + + boost::shared_ptr ref_key(crypto.createAsym(pubfile, "", + RSA_, SHA1, + PUBLIC, ASN1), + deleteAsym); + EXPECT_TRUE(pub_key->compare(ref_key.get(), PUBLIC)); + EXPECT_TRUE(ref_key->compare(pub_key.get(), PUBLIC)); + EXPECT_FALSE(pub_key->compare(ref_key.get(), PRIVATE)); + EXPECT_FALSE(pub_key->compare(ref_key.get(), CERT)); + + const std::vector pubbin = ref_key->exportkey(PUBLIC, ASN1); + checkData(&pubbin[0], pubspki, pubspkilen); +} + +TEST(RsaTest, RSA_PUB_DNS) { + CryptoLink& crypto = CryptoLink::getCryptoLink(); + boost::shared_ptr pub_key(crypto.createAsym(pubdns, pubdnslen, + RSA_, SHA1, + PUBLIC, DNS), + deleteAsym); + ASSERT_TRUE(pub_key->validate()); + EXPECT_EQ(RSA_, pub_key->getAsymAlgorithm()); + EXPECT_EQ(SHA1, pub_key->getHashAlgorithm()); + EXPECT_EQ(PUBLIC, pub_key->getAsymKeyKind()); + EXPECT_EQ(1024, pub_key->getKeySize()); + + boost::shared_ptr ref_key(crypto.createAsym(pubfile, "", + RSA_, SHA1, + PUBLIC, ASN1), + deleteAsym); + EXPECT_TRUE(pub_key->compare(ref_key.get(), PUBLIC)); + EXPECT_TRUE(ref_key->compare(pub_key.get(), PUBLIC)); + EXPECT_FALSE(pub_key->compare(ref_key.get(), PRIVATE)); + EXPECT_FALSE(pub_key->compare(ref_key.get(), CERT)); + + const std::vector pubbin = ref_key->exportkey(PUBLIC, DNS); + EXPECT_EQ(pubbin.size(), pubdnslen); + checkData(&pubbin[0], pubdns, pubdnslen); + + const std::string keystr = + "AwEAAbMlwgHujJFdo+pVkKIss7E1GHuhAIBPITKU0aLDGrwK7s1/IxJd+JLLh2n" + "9hQ8lV4hpS16TEjqTqSNkD3yqIMpWk4HpZLIgrVUlh7GFyNb+X6nNUy6vnlOk+7r" + "vMFOdAe63/6reevpX+99j85x3LqSXEDDBFUhrAR5XjV3r/ELh"; + std::vector keybin; + decodeBase64(keystr, keybin); + EXPECT_EQ(keybin.size(), pubdnslen); + checkData(&keybin[0], pubdns, pubdnslen); + + const std::string dnsfile = TEST_DATA_SRCDIR "/Kexample.+005+18330.key"; + boost::shared_ptr dns_key(crypto.createAsym(dnsfile, "", + RSA_, SHA1, + PUBLIC, DNS), + deleteAsym); + EXPECT_TRUE(dns_key->compare(ref_key.get(), PUBLIC)); + EXPECT_TRUE(ref_key->compare(dns_key.get(), PUBLIC)); +} + +TEST(RsaTest, RSA_PRIV_PKCS1) { + CryptoLink& crypto = CryptoLink::getCryptoLink(); + boost::shared_ptr priv_key(crypto.createAsym(privpkcs1, privpkcs1len, + RSA_, SHA1, + PRIVATE, BASIC), + deleteAsym); + ASSERT_TRUE(priv_key->validate()); + EXPECT_EQ(RSA_, priv_key->getAsymAlgorithm()); + EXPECT_EQ(SHA1, priv_key->getHashAlgorithm()); + EXPECT_EQ(PRIVATE, priv_key->getAsymKeyKind()); + EXPECT_EQ(1024, priv_key->getKeySize()); + EXPECT_EQ(128, priv_key->getSignatureLength(BASIC)); + EXPECT_EQ(128, priv_key->getSignatureLength(ASN1)); + EXPECT_EQ(128, priv_key->getSignatureLength(DNS)); + + EXPECT_THROW(crypto.createAsym(privpkcs1, privpkcs1len - 1, + RSA_, SHA1, PRIVATE, BASIC), + BadKey); + + boost::shared_ptr ref_key(crypto.createAsym(privfile, "1234", + RSA_, SHA1, + PRIVATE, ASN1), + deleteAsym); + EXPECT_TRUE(priv_key->compare(ref_key.get(), PRIVATE)); + EXPECT_TRUE(ref_key->compare(priv_key.get(), PRIVATE)); + EXPECT_TRUE(priv_key->compare(ref_key.get(), PUBLIC)); + EXPECT_FALSE(priv_key->compare(ref_key.get(), CERT)); + + const std::vector privbin = ref_key->exportkey(PRIVATE, BASIC); + checkData(&privbin[0], privpkcs1, privpkcs1len); + + boost::shared_ptr pub_key(crypto.createAsym(pubfile, "", + RSA_, SHA1, + PUBLIC, ASN1), + deleteAsym); + EXPECT_TRUE(priv_key->compare(pub_key.get(), PUBLIC)); + EXPECT_TRUE(pub_key->compare(priv_key.get(), PUBLIC)); +} + +TEST(RsaTest, RSA_PRIV_PKCS) { + CryptoLink& crypto = CryptoLink::getCryptoLink(); + boost::shared_ptr ref_key(crypto.createAsym(privfile, "1234", + RSA_, SHA1, + PRIVATE, ASN1), + deleteAsym); +#ifndef WITH_BOTAN + const std::string pkcs1file = TEST_DATA_SRCDIR "/priv.pem"; + boost::shared_ptr pkcs1_key(crypto.createAsym(pkcs1file, "", + RSA_, SHA1, + PRIVATE, BASIC), + deleteAsym); + EXPECT_TRUE(pkcs1_key->validate()); + EXPECT_TRUE(pkcs1_key->compare(ref_key.get(), PRIVATE)); + EXPECT_TRUE(ref_key->compare(pkcs1_key.get(), PRIVATE)); +#endif + + // PKCS#8 without encryption + const std::string nefile = TEST_DATA_SRCDIR "/pkcs8ne.pem"; + boost::shared_ptr ne_key(crypto.createAsym(nefile, "", + RSA_, SHA1, + PRIVATE, ASN1), + deleteAsym); + EXPECT_TRUE(ne_key->validate()); + EXPECT_TRUE(ne_key->compare(ref_key.get(), PRIVATE)); + EXPECT_TRUE(ref_key->compare(ne_key.get(), PRIVATE)); +} + +TEST(RsaTest, RSA_PRIV_DNS) { + CryptoLink& crypto = CryptoLink::getCryptoLink(); + const std::string privdnsfile = + TEST_DATA_SRCDIR "/Kexample.+005+18330.private"; + boost::shared_ptr dns_key(crypto.createAsym(privdnsfile, "", + RSA_, SHA1, + PRIVATE, DNS), + deleteAsym); + EXPECT_TRUE(dns_key->validate()); + EXPECT_EQ(RSA_, dns_key->getAsymAlgorithm()); + EXPECT_EQ(SHA1, dns_key->getHashAlgorithm()); + EXPECT_EQ(PRIVATE, dns_key->getAsymKeyKind()); + EXPECT_EQ(1024, dns_key->getKeySize()); + +#ifndef WITH_BOTAN + EXPECT_THROW(crypto.createAsym(privdnsfile, "", + RSA_, SHA1, PRIVATE, BASIC), + BadKey); +#endif + EXPECT_THROW(crypto.createAsym(privdnsfile, "", + RSA_, SHA1, PRIVATE, ASN1), + BadKey); + EXPECT_THROW(crypto.createAsym(privdnsfile, "", + RSA_, MD5, PRIVATE, DNS), + BadKey); + EXPECT_THROW(crypto.createAsym(privdnsfile, "", + RSA_, SHA256, PRIVATE, DNS), + BadKey); + EXPECT_THROW(crypto.createAsym(privdnsfile, "", + RSA_, SHA512, PRIVATE, DNS), + BadKey); + EXPECT_THROW(crypto.createAsym(privdnsfile, "", + RSA_, SHA224, PRIVATE, DNS), + UnsupportedAlgorithm); + EXPECT_THROW(crypto.createAsym(privdnsfile, "", + RSA_, SHA384, PRIVATE, DNS), + UnsupportedAlgorithm); + + boost::shared_ptr ref_key(crypto.createAsym(privfile, "1234", + RSA_, SHA1, + PRIVATE, ASN1), + deleteAsym); + EXPECT_TRUE(dns_key->compare(ref_key.get(), PRIVATE)); + EXPECT_TRUE(ref_key->compare(dns_key.get(), PRIVATE)); + EXPECT_TRUE(dns_key->compare(ref_key.get(), PUBLIC)); + EXPECT_FALSE(dns_key->compare(ref_key.get(), CERT)); + + char tempname[] = "/tmp/privateXXXXXX"; + const std::string testfile = mktemp(tempname); + ref_key->exportkey(testfile, "", PRIVATE, DNS); + FILE* fp; + fp = fopen(testfile.c_str(), "r"); + ASSERT_TRUE(fp != NULL); + std::vector testbuf(1024); + size_t tcc = fread(&testbuf[0], 1024, 1, fp); + testbuf.resize(tcc); + fclose(fp); + fp = fopen(privdnsfile.c_str(), "r"); + ASSERT_TRUE(fp != NULL); + std::vector refbuf(1024); + int rcc = fread(&refbuf[0], 1024, 1, fp); + refbuf.resize(rcc); + fclose(fp); + EXPECT_EQ(rcc, tcc); + EXPECT_TRUE(testbuf == refbuf); + unlink(testfile.c_str()); +} + +TEST(RsaTest, CERTIFICATE) { + CryptoLink& crypto = CryptoLink::getCryptoLink(); + boost::shared_ptr from_file(crypto.createAsym(certfile, "", + RSA_, SHA1, + CERT, ASN1), + deleteAsym); + EXPECT_TRUE(from_file->validate()); + EXPECT_EQ(RSA_, from_file->getAsymAlgorithm()); + EXPECT_EQ(SHA1, from_file->getHashAlgorithm()); + EXPECT_EQ(CERT, from_file->getAsymKeyKind()); + EXPECT_EQ(1024, from_file->getKeySize()); + + EXPECT_THROW(crypto.createAsym(certfile, "", RSA_, SHA1, PUBLIC, ASN1), + BadKey); + EXPECT_THROW(crypto.createAsym(certfile, "", RSA_, SHA1, PRIVATE, ASN1), + BadKey); + EXPECT_THROW(crypto.createAsym(certfile, "", RSA_, SHA1, CERT, BASIC), + UnsupportedAlgorithm); + EXPECT_THROW(crypto.createAsym(certfile, "", RSA_, SHA1, CERT, DNS), + UnsupportedAlgorithm); + EXPECT_THROW(crypto.createAsym(certfile, "", RSA_, MD5, CERT, ASN1), + BadKey); + EXPECT_THROW(crypto.createAsym(certfile, "", RSA_, SHA224, CERT, ASN1), + BadKey); + EXPECT_THROW(crypto.createAsym(certfile, "", RSA_, SHA256, CERT, ASN1), + BadKey); + EXPECT_THROW(crypto.createAsym(certfile, "", RSA_, SHA384, CERT, ASN1), + BadKey); + EXPECT_THROW(crypto.createAsym(certfile, "", RSA_, SHA512, CERT, ASN1), + BadKey); + + boost::shared_ptr pub_key(crypto.createAsym(pubfile, "", + RSA_, SHA1, + PUBLIC, ASN1), + deleteAsym); + EXPECT_TRUE(from_file->compare(pub_key.get(), PUBLIC)); + EXPECT_TRUE(pub_key->compare(from_file.get(), PUBLIC)); + EXPECT_FALSE(from_file->compare(pub_key.get(), PRIVATE)); + EXPECT_FALSE(pub_key->compare(from_file.get(), PRIVATE)); + + std::vector certbin = from_file->exportkey(CERT, ASN1); + boost::shared_ptr from_bin(crypto.createAsym(&certbin[0], + certbin.size(), + RSA_, SHA1, + CERT, ASN1), + deleteAsym); + EXPECT_TRUE(from_bin->validate()); + EXPECT_TRUE(from_file->compare(from_bin.get(), PUBLIC)); + EXPECT_TRUE(from_bin->compare(from_file.get(), PUBLIC)); + EXPECT_TRUE(from_file->compare(from_bin.get(), CERT)); + EXPECT_TRUE(from_bin->compare(from_file.get(), CERT)); + + EXPECT_THROW(crypto.createAsym(&certbin[0], certbin.size() - 1, + RSA_, SHA1, CERT, ASN1), + BadKey); + EXPECT_THROW(crypto.createAsym(&certbin[0], certbin.size(), + RSA_, SHA1, CERT, BASIC), + UnsupportedAlgorithm); + EXPECT_THROW(crypto.createAsym(&certbin[0], certbin.size(), + RSA_, SHA1, CERT, DNS), + UnsupportedAlgorithm); + EXPECT_THROW(crypto.createAsym(&certbin[0], certbin.size(), + RSA_, MD5, CERT, ASN1), + BadKey); + EXPECT_THROW(crypto.createAsym(&certbin[0], certbin.size(), + RSA_, SHA224, CERT, ASN1), + BadKey); + EXPECT_THROW(crypto.createAsym(&certbin[0], certbin.size(), + RSA_, SHA256, CERT, ASN1), + BadKey); + EXPECT_THROW(crypto.createAsym(&certbin[0], certbin.size(), + RSA_, SHA384, CERT, ASN1), + BadKey); + EXPECT_THROW(crypto.createAsym(&certbin[0], certbin.size(), + RSA_, SHA512, CERT, ASN1), + BadKey); + + certbin[certbin.size() - 1] = ~certbin[certbin.size() - 1]; + boost::shared_ptr bad_bin(crypto.createAsym(&certbin[0], + certbin.size(), + RSA_, SHA1, + CERT, ASN1), + deleteAsym); + EXPECT_FALSE(bad_bin->validate()); +} + +// +// Multiple signatures +// +TEST(RsaTest, doubleSign) { + std::string data = "Kea provides DHCPv4 and DHCPv6 servers"; + OutputBuffer data_buf(data.size()); + data_buf.writeData(data.c_str(), data.size()); + CryptoLink& crypto = CryptoLink::getCryptoLink(); + + // Sign it + boost::shared_ptr rsa_sign(crypto.createAsym(privfile, "1234", + RSA_, SHA1, + PRIVATE, ASN1), + deleteAsym); + ASSERT_TRUE(rsa_sign); + + OutputBuffer sig1(1); + size_t sig1_len = rsa_sign->getSignatureLength(BASIC); + EXPECT_EQ(128, sig1_len); + rsa_sign->update(data_buf.getData(), data_buf.getLength()); + rsa_sign->sign(sig1, sig1_len, BASIC); + ASSERT_EQ(sig1_len, sig1.getLength()); + + // Clear state + rsa_sign->clear(); + + // Sign it again + OutputBuffer sig2(1); + size_t sig2_len = rsa_sign->getSignatureLength(BASIC); + EXPECT_EQ(128, sig2_len); + rsa_sign->update(data_buf.getData(), data_buf.getLength()); + rsa_sign->sign(sig2, sig2_len, BASIC); + EXPECT_EQ(sig2_len, sig2.getLength()); + + // Compare + ASSERT_EQ(sig1_len, sig2_len); + EXPECT_TRUE(std::memcmp(sig1.getData(), sig2.getData(), sig1_len) == 0); +} + +// +// Multiple verifies +// +TEST(RsaTest, doubleVerify) { + std::string data = "Kea provides DHCPv4 and DHCPv6 servers"; + OutputBuffer data_buf(data.size()); + data_buf.writeData(data.c_str(), data.size()); + CryptoLink& crypto = CryptoLink::getCryptoLink(); + + // Sign it + boost::shared_ptr rsa_sign(crypto.createAsym(privfile, "1234", + RSA_, SHA1, + PRIVATE, ASN1), + deleteAsym); + ASSERT_TRUE(rsa_sign); + + OutputBuffer sig(1); + size_t sig_len = rsa_sign->getSignatureLength(BASIC); + EXPECT_EQ(128, sig_len); + rsa_sign->update(data_buf.getData(), data_buf.getLength()); + rsa_sign->sign(sig, sig_len, BASIC); + EXPECT_EQ(sig_len, sig.getLength()); + + // Verify + boost::shared_ptr rsa_verify(crypto.createAsym(pubfile, "", + RSA_, SHA1, + PUBLIC, ASN1), + deleteAsym); + ASSERT_TRUE(rsa_verify); + + rsa_verify->update(data_buf.getData(), data_buf.getLength()); + EXPECT_TRUE(rsa_verify->verify(sig.getData(), sig.getLength(), BASIC)); + + // Clear state + rsa_verify->clear(); + + // Verify again + rsa_verify->update(data_buf.getData(), data_buf.getLength()); + EXPECT_TRUE(rsa_verify->verify(sig.getData(), sig.getLength(), BASIC)); +} diff --git a/src/lib/cryptolink/tests/testdata/eccert.pem b/src/lib/cryptolink/tests/testdata/eccert.pem new file mode 100644 index 0000000000..8916ff373f --- /dev/null +++ b/src/lib/cryptolink/tests/testdata/eccert.pem @@ -0,0 +1,12 @@ +-----BEGIN CERTIFICATE----- +MIIB2jCCAX+gAwIBAgIJALJtRCtzuGsCMAoGCCqGSM49BAMCMEkxJDAiBgNVBAoM +G0ludGVybmV0IFN5c3RlbXMgQ29uc29ydGl1bTEhMB8GA1UEAwwYc2VjdXJlIERI +Q1B2NiBLZWEgc2VydmVyMB4XDTE1MDYwNzAzNTU0OVoXDTE2MDYwNjAzNTU0OVow +STEkMCIGA1UECgwbSW50ZXJuZXQgU3lzdGVtcyBDb25zb3J0aXVtMSEwHwYDVQQD +DBhzZWN1cmUgREhDUHY2IEtlYSBzZXJ2ZXIwWTATBgcqhkjOPQIBBggqhkjOPQMB +BwNCAASgAH2PNkhG5DQwiS9St+qtX5sfmav7ETctzPXraS+erMVvZVKj02IIGkEY +5SC9qKKy/VT0ixfDq9Uzr6Y6bUUlo1AwTjAdBgNVHQ4EFgQUPRv03Ug/4eBh0Eya +pXlRvSkGQ+wwHwYDVR0jBBgwFoAUPRv03Ug/4eBh0EyapXlRvSkGQ+wwDAYDVR0T +BAUwAwEB/zAKBggqhkjOPQQDAgNJADBGAiEAtzJGSIZWaqTzwGoSk7FDJ+w2ODuU +nftHstS0k1GLJl0CIQD5/5tRjAIm57R2nEjBEAvWy01jpNF97/1RXGbPt/deoA== +-----END CERTIFICATE----- diff --git a/src/lib/cryptolink/tests/testdata/ecpkcs8.pem b/src/lib/cryptolink/tests/testdata/ecpkcs8.pem new file mode 100644 index 0000000000..d17b0e55c3 --- /dev/null +++ b/src/lib/cryptolink/tests/testdata/ecpkcs8.pem @@ -0,0 +1,7 @@ +-----BEGIN ENCRYPTED PRIVATE KEY----- +MIHVMEAGCSqGSIb3DQEFDTAzMBsGCSqGSIb3DQEFDDAOBAj+ar59eXmOVgICCAAw +FAYIKoZIhvcNAwcECHzRG/4+8LHeBIGQQc0G8p+hrVJgby9CM3F9JUnsdbw75zao +eFPDnXYzqWqZM2p8/OWetFfK2bhSnmzhNuheVFZ8a4ODnk7CvmQMzCFE14TIx5q7 +szaazLlqTkuQNrwvGjhpZMMVtaFIvRyapVECXO1PzbXkgFPOuUpKY407T22fj4cD +OfXsRIkLqRIHrUlpMub8unF8mcjsQmKb +-----END ENCRYPTED PRIVATE KEY----- diff --git a/src/lib/cryptolink/tests/testdata/ecpkcs8ne.pem b/src/lib/cryptolink/tests/testdata/ecpkcs8ne.pem new file mode 100644 index 0000000000..12e5823bfd --- /dev/null +++ b/src/lib/cryptolink/tests/testdata/ecpkcs8ne.pem @@ -0,0 +1,5 @@ +-----BEGIN PRIVATE KEY----- +MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgi4bkYMdLmFN5+PYA +K550QeLhyOtHooherZNmjHUBQK+hRANCAASgAH2PNkhG5DQwiS9St+qtX5sfmav7 +ETctzPXraS+erMVvZVKj02IIGkEY5SC9qKKy/VT0ixfDq9Uzr6Y6bUUl +-----END PRIVATE KEY----- diff --git a/src/lib/cryptolink/tests/testdata/ecpub.pem b/src/lib/cryptolink/tests/testdata/ecpub.pem new file mode 100644 index 0000000000..b9f897eee2 --- /dev/null +++ b/src/lib/cryptolink/tests/testdata/ecpub.pem @@ -0,0 +1,4 @@ +-----BEGIN PUBLIC KEY----- +MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEoAB9jzZIRuQ0MIkvUrfqrV+bH5mr ++xE3Lcz162kvnqzFb2VSo9NiCBpBGOUgvaiisv1U9IsXw6vVM6+mOm1FJQ== +-----END PUBLIC KEY----- -- 2.47.2