Chromium Code Reviews| Index: chrome/common/extensions/api/networking_private/networking_private_crypto_openssl.cc |
| diff --git a/chrome/common/extensions/api/networking_private/networking_private_crypto_openssl.cc b/chrome/common/extensions/api/networking_private/networking_private_crypto_openssl.cc |
| index 95a6bd6c59e2d06cc8023612cda2453f5451c12e..222674ecac7790324ce9d5cb7a0b8f6410eb4eae 100644 |
| --- a/chrome/common/extensions/api/networking_private/networking_private_crypto_openssl.cc |
| +++ b/chrome/common/extensions/api/networking_private/networking_private_crypto_openssl.cc |
| @@ -4,38 +4,216 @@ |
| #include "chrome/common/extensions/api/networking_private/networking_private_crypto.h" |
| +// TODO(davidben): Remove this #ifdef when we switch to BoringSSL. |
| +#ifdef OPENSSL_IS_BORINGSSL |
| +#include <openssl/digest.h> |
| +#endif |
| +#include <openssl/evp.h> |
| +#include <openssl/rsa.h> |
| +#include <openssl/x509.h> |
| + |
| #include "base/logging.h" |
| +#include "base/strings/string_util.h" |
| +#include "crypto/openssl_util.h" |
| +#include "crypto/rsa_private_key.h" |
| +#include "crypto/scoped_openssl_types.h" |
| +#include "net/cert/pem_tokenizer.h" |
| -NetworkingPrivateCrypto::NetworkingPrivateCrypto() { |
| -} |
| +namespace { |
| + |
| +typedef crypto::ScopedOpenSSL<X509, X509_free>::Type ScopedX509; |
| -NetworkingPrivateCrypto::~NetworkingPrivateCrypto() { |
| +// Parses |pem_data| for a PEM block of |pem_type|. |
| +// Returns true if a |pem_type| block is found, storing the decoded result in |
| +// |der_output|. |
| +bool GetDERFromPEM(const std::string& pem_data, |
| + const std::string& pem_type, |
| + std::vector<uint8>* der_output) { |
| + std::vector<std::string> headers; |
| + headers.push_back(pem_type); |
| + net::PEMTokenizer pem_tok(pem_data, headers); |
| + if (!pem_tok.GetNext()) { |
| + return false; |
| + } |
| + |
| + der_output->assign(pem_tok.data().begin(), pem_tok.data().end()); |
| + return true; |
| } |
| +} // namespace |
| + |
| bool NetworkingPrivateCrypto::VerifyCredentials( |
| const std::string& certificate, |
| const std::string& signature, |
| const std::string& data, |
| const std::string& connected_mac) { |
| - // https://crbug.com/393023 |
| - NOTIMPLEMENTED(); |
| - return false; |
| + crypto::EnsureOpenSSLInit(); |
| + crypto::OpenSSLErrStackTracer err_tracer(FROM_HERE); |
| + |
| + std::vector<uint8> cert_data; |
|
Ryan Sleevi
2014/07/17 17:26:29
uint8 (Chromium-specific) is deprecated. Use uint8
davidben
2014/07/17 22:08:22
Done.
|
| + if (!GetDERFromPEM(certificate, "CERTIFICATE", &cert_data)) { |
| + LOG(ERROR) << "Failed to parse certificate."; |
| + return false; |
| + } |
| + |
| + // Parse into an OpenSSL X509. |
| + const uint8* ptr = cert_data.empty() ? NULL : &cert_data[0]; |
| + ScopedX509 cert(d2i_X509(NULL, &ptr, cert_data.size())); |
| + if (!cert || ptr != (&cert_data[0] + cert_data.size())) { |
|
Ryan Sleevi
2014/07/17 17:26:29
still unsafe here.
Just
const uint8* ptr = cert_d
davidben
2014/07/17 22:08:22
Done. (Well, size = 0 => cert = NULL, but sure.)
|
| + LOG(ERROR) << "Failed to parse certificate."; |
| + return false; |
| + } |
| + |
| + // Import the trusted public key. |
| + ptr = kTrustedCAPublicKeyDER; |
| + crypto::ScopedRSA ca_public_key_rsa( |
| + d2i_RSAPublicKey(NULL, &ptr, kTrustedCAPublicKeyDERLength)); |
| + if (!ca_public_key_rsa || |
| + ptr != kTrustedCAPublicKeyDER + kTrustedCAPublicKeyDERLength) { |
| + NOTREACHED(); |
| + LOG(ERROR) << "Failed to import trusted public key."; |
| + return false; |
| + } |
| + crypto::ScopedEVP_PKEY ca_public_key(EVP_PKEY_new()); |
| + if (!ca_public_key || |
| + !EVP_PKEY_set1_RSA(ca_public_key.get(), ca_public_key_rsa.get())) { |
| + LOG(ERROR) << "Failed to initialize EVP_PKEY"; |
| + return false; |
| + } |
| + |
| + // Check that the certificate is signed by the trusted public key. |
| + if (X509_verify(cert.get(), ca_public_key.get()) <= 0) { |
| + LOG(ERROR) << "Certificate is not issued by the trusted CA."; |
| + return false; |
| + } |
| + |
| + // Check that the device listed in the certificate is correct. |
| + // Something like evt_e161 001a11ffacdf |
| + std::string common_name; |
| + int common_name_length = X509_NAME_get_text_by_NID( |
| + cert->cert_info->subject, NID_commonName, NULL, 0); |
| + if (common_name_length < 0) { |
| + LOG(ERROR) << "Certificate does not have common name."; |
| + return false; |
| + } |
| + if (common_name_length > 0) { |
| + common_name_length = X509_NAME_get_text_by_NID( |
| + cert->cert_info->subject, |
| + NID_commonName, |
| + WriteInto(&common_name, common_name_length + 1), |
| + common_name_length + 1); |
|
Ryan Sleevi
2014/07/17 17:26:29
I'm guessing this is only safe because of line 85.
davidben
2014/07/17 22:08:22
For this function, the length is correct the first
|
| + DCHECK_EQ((int)common_name.size(), common_name_length); |
| + if (common_name_length < 0) { |
| + LOG(ERROR) << "Certificate does not have common name."; |
| + return false; |
| + } |
| + } |
| + |
| + std::string translated_mac; |
| + base::RemoveChars(connected_mac, ":", &translated_mac); |
| + if (!EndsWith(common_name, translated_mac, false)) { |
| + LOG(ERROR) << "MAC addresses don't match."; |
| + return false; |
| + } |
| + |
| + // Make sure that the certificate matches the unsigned data presented. |
| + // Verify that the |signature| matches |data|. |
| + crypto::ScopedEVP_PKEY public_key(X509_get_pubkey(cert.get())); |
| + if (!public_key) { |
| + LOG(ERROR) << "Unable to extract public key from certificate."; |
| + return false; |
| + } |
| + |
| +// TODO(davidben): Remove this #ifdef when we switch to |
| +// BoringSSL. OpenSSL's API is not const-correct. |
| +#ifdef OPENSSL_IS_BORINGSSL |
| + const uint8* signature_data = |
| + reinterpret_cast<const uint8*>(signature.data()); |
| +#else // !OPENSSL_IS_BORINGSSL |
| + uint8* signature_data = |
| + const_cast<uint8*>(reinterpret_cast<const uint8*>(signature.data())); |
| +#endif // OPENSSL_IS_BORINGSSL |
| + |
| + EVP_MD_CTX ctx; |
| + EVP_MD_CTX_init(&ctx); |
| + bool ok = EVP_DigestVerifyInit( |
| + &ctx, NULL, EVP_sha1(), NULL, public_key.get()) == 1 && |
| + EVP_DigestVerifyUpdate(&ctx, data.data(), data.size()) == 1 && |
| + EVP_DigestVerifyFinal(&ctx, signature_data, signature.size()) == 1; |
| + EVP_MD_CTX_cleanup(&ctx); |
| + if (!ok) { |
| + LOG(ERROR) << "Signed blobs did not match."; |
| + return false; |
| + } |
| + return true; |
| } |
| bool NetworkingPrivateCrypto::EncryptByteString( |
| const std::vector<uint8>& pub_key_der, |
| const std::string& data, |
| std::vector<uint8>* encrypted_output) { |
| - // https://crbug.com/393023 |
| - NOTIMPLEMENTED(); |
| - return false; |
| + crypto::EnsureOpenSSLInit(); |
| + crypto::OpenSSLErrStackTracer err_tracer(FROM_HERE); |
| + |
| + const uint8* ptr = pub_key_der.empty() ? NULL : &pub_key_der[0]; |
| + crypto::ScopedRSA rsa(d2i_RSAPublicKey(NULL, &ptr, pub_key_der.size())); |
| + if (!rsa || ptr != (&pub_key_der[0] + pub_key_der.size())) { |
|
Ryan Sleevi
2014/07/17 17:26:29
ditto BUG
davidben
2014/07/17 22:08:22
Done.
|
| + LOG(ERROR) << "Failed to parse public key"; |
| + return false; |
| + } |
| + |
| + scoped_ptr<uint8[]> rsa_output(new uint8[RSA_size(rsa.get())]); |
| + int encrypted_length = |
| + RSA_public_encrypt(data.size(), |
| + reinterpret_cast<const uint8*>(data.data()), |
| + rsa_output.get(), |
| + rsa.get(), |
| + RSA_PKCS1_PADDING); |
| + if (encrypted_length < 0) { |
| + LOG(ERROR) << "Error during decryption"; |
| + return false; |
| + } |
| + encrypted_output->assign(rsa_output.get(), |
| + rsa_output.get() + encrypted_length); |
| + return true; |
| } |
| bool NetworkingPrivateCrypto::DecryptByteString( |
| const std::string& private_key_pem, |
| const std::vector<uint8>& encrypted_data, |
| std::string* decrypted_output) { |
| - // https://crbug.com/393023 |
| - NOTIMPLEMENTED(); |
| - return false; |
| + crypto::EnsureOpenSSLInit(); |
| + crypto::OpenSSLErrStackTracer err_tracer(FROM_HERE); |
| + |
| + std::vector<uint8> private_key_data; |
| + if (!GetDERFromPEM(private_key_pem, "PRIVATE KEY", &private_key_data)) { |
| + LOG(ERROR) << "Failed to parse private key PEM."; |
| + return false; |
| + } |
| + scoped_ptr<crypto::RSAPrivateKey> private_key( |
| + crypto::RSAPrivateKey::CreateFromPrivateKeyInfo(private_key_data)); |
| + if (!private_key || !private_key->key()) { |
| + LOG(ERROR) << "Failed to parse private key DER."; |
| + return false; |
| + } |
| + |
| + crypto::ScopedRSA rsa(EVP_PKEY_get1_RSA(private_key->key())); |
| + if (!rsa) { |
| + LOG(ERROR) << "Failed to get RSA key."; |
| + return false; |
| + } |
| + |
| + scoped_ptr<uint8[]> rsa_output(new uint8[RSA_size(rsa.get())]); |
| + int output_length = RSA_private_decrypt(encrypted_data.size(), |
| + &encrypted_data[0], |
| + rsa_output.get(), |
| + rsa.get(), |
| + RSA_PKCS1_PADDING); |
| + if (output_length < 0) { |
| + LOG(ERROR) << "Error during decryption."; |
| + return false; |
| + } |
| + decrypted_output->assign(reinterpret_cast<char*>(rsa_output.get()), |
| + output_length); |
| + return true; |
| } |