| // Copyright 2019 The Chromium OS Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "u2fd/util.h" |
| |
| #include <array> |
| #include <string> |
| #include <utility> |
| #include <vector> |
| |
| #include <base/logging.h> |
| #include <crypto/scoped_openssl_types.h> |
| #include <openssl/bn.h> |
| #include <openssl/ecdsa.h> |
| #include <openssl/sha.h> |
| #include <openssl/x509.h> |
| |
| namespace u2f { |
| namespace util { |
| |
| template <> |
| void AppendToVector(const std::vector<uint8_t>& from, |
| std::vector<uint8_t>* to) { |
| to->insert(to->end(), from.begin(), from.end()); |
| } |
| |
| template <> |
| void AppendToVector(const std::string& from, std::vector<uint8_t>* to) { |
| to->insert(to->end(), from.begin(), from.end()); |
| } |
| |
| void AppendSubstringToVector(const std::string& from, |
| int start, |
| int length, |
| std::vector<uint8_t>* to) { |
| to->insert(to->end(), from.begin() + start, from.begin() + start + length); |
| } |
| |
| base::Optional<std::vector<uint8_t>> SignatureToDerBytes(const uint8_t* r, |
| const uint8_t* s) { |
| crypto::ScopedECDSA_SIG sig(ECDSA_SIG_new()); |
| |
| if (!BN_bin2bn(r, 32, sig->r) || !BN_bin2bn(s, 32, sig->s)) { |
| LOG(ERROR) << "Failed to initialize ECDSA_SIG"; |
| return base::nullopt; |
| } |
| |
| int sig_len = i2d_ECDSA_SIG(sig.get(), nullptr); |
| |
| std::vector<uint8_t> signature(sig_len); |
| uint8_t* sig_ptr = &signature[0]; |
| |
| if (i2d_ECDSA_SIG(sig.get(), &sig_ptr) != sig_len) { |
| LOG(ERROR) << "DER encoding returned unexpected length"; |
| return base::nullopt; |
| } |
| |
| return signature; |
| } |
| |
| std::vector<uint8_t> Sha256(const std::vector<uint8_t>& data) { |
| std::vector<uint8_t> hash(SHA256_DIGEST_LENGTH); |
| SHA256_CTX sha_context; |
| |
| SHA256_Init(&sha_context); |
| SHA256_Update(&sha_context, &data.front(), data.size()); |
| SHA256_Final(&hash.front(), &sha_context); |
| |
| return hash; |
| } |
| |
| crypto::ScopedEC_KEY CreateAttestationKey() { |
| crypto::ScopedEC_KEY key(EC_KEY_new_by_curve_name(NID_X9_62_prime256v1)); |
| EC_KEY_set_asn1_flag(key.get(), OPENSSL_EC_NAMED_CURVE); |
| |
| if (!key || !EC_KEY_generate_key(key.get())) { |
| LOG(ERROR) << "Failed to generate U2F attestation key."; |
| return nullptr; |
| } else { |
| return key; |
| } |
| } |
| |
| base::Optional<std::vector<uint8_t>> AttestToData( |
| const std::vector<uint8_t>& data, EC_KEY* attestation_key) { |
| std::vector<uint8_t> digest = Sha256(data); |
| |
| std::vector<uint8_t> signature(ECDSA_size(attestation_key)); |
| unsigned int signature_length; |
| |
| if (!ECDSA_sign(0 /* type: ignored by OpenSSL */, &digest[0], digest.size(), |
| signature.data(), &signature_length, attestation_key)) { |
| LOG(ERROR) << "Failed to sign data using U2F attestation key"; |
| return base::nullopt; |
| } |
| |
| signature.resize(signature_length); |
| return signature; |
| } |
| |
| namespace { |
| |
| template <typename C> |
| crypto::ScopedOpenSSL<X509, X509_free> ParseX509(const C& container) { |
| const unsigned char* parse_ptr = |
| static_cast<const unsigned char*>(&container[0]); |
| |
| crypto::ScopedOpenSSL<X509, X509_free> cert( |
| d2i_X509(nullptr /* create and return new X509 struct */, &parse_ptr, |
| container.size())); |
| |
| if (!cert) { |
| LOG(ERROR) << "Failed to parse X509 certificate."; |
| } |
| |
| return cert; |
| } |
| |
| base::Optional<std::vector<uint8_t>> DerEncodeCertificate(X509* cert) { |
| int cert_size = i2d_X509(cert, nullptr); |
| if (cert_size < 0) { |
| LOG(ERROR) << "Failed to DER-encode X509 certficate, error: " << cert_size; |
| return base::nullopt; |
| } |
| |
| std::vector<uint8_t> cert_der(cert_size); |
| unsigned char* output_ptr = &cert_der[0]; |
| |
| if (i2d_X509(cert, &output_ptr) != cert_size) { |
| LOG(ERROR) << "X509 DER-encoding returned unexpected size, expected " |
| << cert_size; |
| return base::nullopt; |
| } |
| |
| return cert_der; |
| } |
| |
| } // namespace |
| |
| base::Optional<std::vector<uint8_t>> CreateAttestationCertificate( |
| EC_KEY* attestation_key) { |
| // We use a fixed template for the X509 certificate rather than generating one |
| // using OpenSSL, so that we can ensure that u2fd and cr50 both return |
| // certificates with the same structure. |
| // The array below is generated by the openssl tool from the template in |
| // x509_tmpl.txt. |
| constexpr std::array<unsigned char, 164> cert_template = { |
| 0x30, 0x81, 0xA1, 0x30, 0x81, 0x8E, 0xA0, 0x03, 0x02, 0x01, 0x02, 0x02, |
| 0x01, 0x00, 0x30, 0x0A, 0x06, 0x08, 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x04, |
| 0x03, 0x02, 0x30, 0x0F, 0x31, 0x0D, 0x30, 0x0B, 0x06, 0x03, 0x55, 0x04, |
| 0x03, 0x13, 0x04, 0x63, 0x72, 0x35, 0x30, 0x30, 0x22, 0x18, 0x0F, 0x32, |
| 0x30, 0x30, 0x30, 0x30, 0x31, 0x30, 0x31, 0x30, 0x30, 0x30, 0x30, 0x30, |
| 0x30, 0x5A, 0x18, 0x0F, 0x32, 0x30, 0x39, 0x39, 0x31, 0x32, 0x33, 0x31, |
| 0x32, 0x33, 0x35, 0x39, 0x35, 0x39, 0x5A, 0x30, 0x0F, 0x31, 0x0D, 0x30, |
| 0x0B, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x04, 0x63, 0x72, 0x35, 0x30, |
| 0x30, 0x19, 0x30, 0x13, 0x06, 0x07, 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x02, |
| 0x01, 0x06, 0x08, 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x03, 0x01, 0x07, 0x03, |
| 0x02, 0x00, 0x00, 0xA3, 0x17, 0x30, 0x15, 0x30, 0x13, 0x06, 0x0B, 0x2B, |
| 0x06, 0x01, 0x04, 0x01, 0x82, 0xE5, 0x1C, 0x02, 0x01, 0x01, 0x04, 0x04, |
| 0x03, 0x02, 0x03, 0x08, 0x30, 0x0A, 0x06, 0x08, 0x2A, 0x86, 0x48, 0xCE, |
| 0x3D, 0x04, 0x03, 0x02, 0x03, 0x02, 0x00, 0x00, |
| }; |
| |
| crypto::ScopedOpenSSL<X509, X509_free> cert = ParseX509(cert_template); |
| if (!cert) { |
| return base::nullopt; |
| } |
| |
| crypto::ScopedEVP_PKEY pkey(EVP_PKEY_new()); |
| if (!pkey || !EVP_PKEY_set1_EC_KEY(pkey.get(), attestation_key)) { |
| LOG(ERROR) << "Failed to create EVP_PKEY"; |
| return base::nullopt; |
| } |
| |
| if (!X509_set_pubkey(cert.get(), pkey.get()) || |
| X509_sign(cert.get(), pkey.get(), EVP_sha256()) <= |
| 0 /* returns length on succes */) { |
| LOG(ERROR) << "Failed to update X509 pubkey and signature fields"; |
| return base::nullopt; |
| } |
| |
| return DerEncodeCertificate(cert.get()); |
| } |
| |
| bool RemoveCertificatePadding(std::vector<uint8_t>* cert_in) { |
| const unsigned char* cert_start = &cert_in->front(); |
| const unsigned char* parse_ptr = cert_start; |
| |
| crypto::ScopedOpenSSL<X509, X509_free> cert( |
| d2i_X509(nullptr /* create and return new X509 struct */, &parse_ptr, |
| cert_in->size())); |
| |
| if (!cert) { |
| LOG(ERROR) << "Failed to parse X509 certificate."; |
| return false; |
| } |
| |
| size_t cert_size = parse_ptr - cert_start; |
| |
| if (cert_size > cert_in->size()) { |
| LOG(ERROR) << "Unexpectedly parsed X509 cert larger than input buffer."; |
| return false; |
| } |
| |
| cert_in->resize(cert_size); |
| return true; |
| } |
| |
| } // namespace util |
| } // namespace u2f |