Index: chrome/browser/extensions/api/networking_private/networking_private_crypto.cc |
diff --git a/chrome/browser/extensions/api/networking_private/networking_private_crypto.cc b/chrome/browser/extensions/api/networking_private/networking_private_crypto.cc |
new file mode 100644 |
index 0000000000000000000000000000000000000000..93a37ffb9ce8853effeb3936a1dfaf20c4a9d7d1 |
--- /dev/null |
+++ b/chrome/browser/extensions/api/networking_private/networking_private_crypto.cc |
@@ -0,0 +1,278 @@ |
+// Copyright 2013 The Chromium 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 "chrome/browser/extensions/api/networking_private/networking_private_crypto.h" |
+ |
+#include <cert.h> |
+#include <keyhi.h> |
+#include <keythi.h> |
+#include <pk11pub.h> |
+#include <sechash.h> |
+#include <secport.h> |
+ |
+#include "base/base64.h" |
+#include "base/memory/scoped_ptr.h" |
+#include "base/strings/string_number_conversions.h" |
+#include "base/strings/string_util.h" |
+#include "base/strings/stringprintf.h" |
+#include "crypto/nss_util.h" |
+#include "crypto/rsa_private_key.h" |
+#include "net/cert/pem_tokenizer.h" |
+#include "net/cert/x509_certificate.h" |
+ |
+const char kTrustedCAExponent[] = "00010001"; |
+ |
+const char kTrustedCAModulus[] = |
+ "BC2280BD80F63A21003BAE765E357F3DC3645C559486342F058728CDF7698C17B350A7B8" |
+ "82FADFC7432DD67EABA06FB7137280A44715C1209950CDEC1462095BA498CDD241B6364E" |
+ "FFE82E32304A81A842A36C9B336ECAB2F55366E02753861A851EA7393F4A778EFB546666" |
+ "FB5854C05E39C7F550060BE08AD4CEE16A551F8B1700E669A327E60825693C129D8D052C" |
+ "D62EA231DEB45250D62049DE71A0F9AD204012F1DD25EBD5E6B836F4D68F7FCA43DCD710" |
+ "5BE63F518A85B3F3FFF6032DCB234F9CAD18E793058CAC529AF74CE9997ABE6E7E4D0AE3" |
+ "C61CA993FA3AA5915D1CBD66EBCC60DC8674CACFF8921C987D57FA61479EAB80B7E44880" |
+ "2A92C51B"; |
+ |
+namespace { |
+bool GetDERFromPEM(const std::string& pem_data, |
+ const std::string& pem_type, |
+ std::string* 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 = pem_tok.data(); |
+ return true; |
+} |
+} // namespace |
+ |
+NetworkingPrivateCrypto::NetworkingPrivateCrypto() |
+ : ca_public_key_(NULL), |
+ cert_public_key_(NULL), |
+ cert_(NULL), |
+ enc_public_key_(NULL) {} |
+ |
+NetworkingPrivateCrypto::~NetworkingPrivateCrypto() { |
+ if (ca_public_key_) |
+ SECKEY_DestroyPublicKey(ca_public_key_); |
+ if (cert_public_key_) |
+ SECKEY_DestroyPublicKey(cert_public_key_); |
+ if (cert_) |
+ CERT_DestroyCertificate(cert_); |
+ if (enc_public_key_) |
+ SECKEY_DestroyPublicKey(enc_public_key_); |
+} |
+ |
+bool NetworkingPrivateCrypto::VerifyCredentials( |
+ const std::string& certificate, |
+ const std::string& signed_data, |
+ const std::string& unsigned_data, |
+ const std::string& connected_mac) { |
+ crypto::EnsureNSSInit(); |
+ |
+ net::CertificateList cl = |
+ net::X509Certificate::CreateCertificateListFromBytes( |
+ const_cast<char*>(certificate.c_str()), |
+ certificate.size(), |
+ net::X509Certificate::FORMAT_AUTO); |
+ if (!cl.size()) { |
+ LOG(ERROR) << "Failed to parse certificate."; |
+ return false; |
+ } |
+ |
+ // Check that the device listed in the certificate is correct. |
+ std::string subject_name = cl[0]->subject().GetDisplayName(); |
mef
2013/08/28 18:05:59
Alternatively I can get subject name directly from
Ryan Sleevi
2013/08/28 18:31:26
The above is really only intended for display purp
mef
2013/08/28 21:28:58
Done.
|
+ // Something like evt_e161 001a11ffacdf |
+ size_t subject_mac_pos = subject_name.rfind(' '); |
+ if (subject_mac_pos == std::string::npos) { |
+ LOG(ERROR) << "Badly formatted subject."; |
+ return false; |
+ } |
+ |
+ std::string translated_mac; |
+ RemoveChars(connected_mac, ":", &translated_mac); |
+ if (base::strcasecmp(translated_mac.c_str(), |
+ subject_name.substr(subject_mac_pos + 1).c_str())) { |
Ryan Sleevi
2013/08/28 19:02:37
why not just use std::string::compare?
mef
2013/08/28 21:28:58
Done.
|
+ LOG(ERROR) << "MAC addresses don't match."; |
+ return false; |
+ } |
+ |
+ std::string cert_data; |
+ if (!GetDERFromPEM(certificate, "CERTIFICATE", &cert_data)) { |
Ryan Sleevi
2013/08/28 19:02:37
Why are you parsing the certificate twice like thi
mef
2013/08/28 21:28:58
Done.
|
+ LOG(ERROR) << "Failed to parse certificate."; |
+ return false; |
+ } |
+ |
+ SECItem der_cert; |
+ der_cert.data = |
+ reinterpret_cast<unsigned char*>(const_cast<char*>(cert_data.c_str())); |
+ der_cert.len = cert_data.length(); |
+ der_cert.type = siDERCertBuffer; |
+ |
+ // Parse into a certificate structure. |
+ cert_ = CERT_NewTempCertificate( |
+ CERT_GetDefaultCertDB(), &der_cert, NULL, PR_FALSE, PR_TRUE); |
+ |
+ if (!cert_) { |
+ LOG(ERROR) << "Failed to parse certificate."; |
+ return false; |
+ } |
+ |
+ SECStatus verified = SECSuccess; |
+ |
+// TODO(mef): Add exports to third_party/nss/nss/exports_win.def and roll DEPS. |
+#if !defined(OS_WIN) |
+ crypto::PrivateKeyInfoCodec trusted_ca_key_codec(true); |
mef
2013/08/28 18:05:59
crypto::PrivateKeyInfoCodec is causing linking err
Ryan Sleevi
2013/08/28 18:31:26
Correct, it's not exported, because it's not part
mef
2013/08/28 21:28:58
Done.
|
+ base::HexStringToBytes(kTrustedCAModulus, trusted_ca_key_codec.modulus()); |
+ base::HexStringToBytes(kTrustedCAExponent, |
+ trusted_ca_key_codec.public_exponent()); |
+ std::vector<uint8> trusted_ca_key_der; |
+ if (!trusted_ca_key_codec.ExportPublicKey(&trusted_ca_key_der)) { |
+ LOG(ERROR) << "Failed CA ExportPublicKey"; |
+ return false; |
+ } |
+ |
+ SECItem trusted_ca_key_der_item; |
+ trusted_ca_key_der_item.data = |
+ const_cast<unsigned char*>(&trusted_ca_key_der.front()); |
+ trusted_ca_key_der_item.len = trusted_ca_key_der.size(); |
+ |
+ ca_public_key_ = SECKEY_ImportDERPublicKey(&trusted_ca_key_der_item, CKK_RSA); |
+ verified = CERT_VerifySignedDataWithPublicKey( |
mef
2013/08/28 18:05:59
Is it ok to add these to third_party/nss/nss/expor
Ryan Sleevi
2013/08/28 18:31:26
Use http://mxr.mozilla.org/nss/source/lib/nss/nss.
mef
2013/08/28 21:28:58
Done. I did go with CERT_VerifySignedDataWithPubli
|
+ &cert_->signatureWrap, ca_public_key_, NULL); |
+ if (verified != SECSuccess) { |
+ LOG(ERROR) << "Certificate is not issued by trusted CA."; |
+ return false; |
+ } |
+#endif |
+ |
+ // Excellent, the certificate checks out, now make sure that the certificate |
+ // matches the unsigned data presented. |
+ // We're going to verify that hash(unsigned_data) == public(signed_data) |
+ cert_public_key_ = CERT_ExtractPublicKey(cert_); |
Ryan Sleevi
2013/08/28 18:31:26
Please go through the comments for "we" and look t
mef
2013/08/28 21:28:58
Done.
|
+ |
+ if (!cert_public_key_) { |
+ LOG(ERROR) << "Unable to extract public key from certificate."; |
+ return false; |
+ } |
+ |
+ struct rsa_hash { |
+ unsigned char asn1[15]; |
+ unsigned char data[SHA1_LENGTH]; |
+ } rsa_hash = {{0x30, 0x21, 0x30, 0x09, 0x06, 0x05, 0x2b, 0x0e, 0x03, 0x02, |
+ 0x1a, 0x05, 0x00, 0x04, 0x14}, }; |
mef
2013/08/28 18:05:59
Is there a better way to add ASN1 prefix in front
Ryan Sleevi
2013/08/28 18:31:26
Yeah, don't use this API - you're totally using th
mef
2013/08/28 21:28:58
Done.
|
+ |
+ SECStatus hashed = HASH_HashBuf(HASH_AlgSHA1, |
+ rsa_hash.data, |
+ reinterpret_cast<unsigned char*>( |
+ const_cast<char*>(unsigned_data.c_str())), |
+ unsigned_data.size()); |
+ DCHECK(hashed == SECSuccess); |
+ SECItem signature_item = { |
+ siBuffer, |
+ reinterpret_cast<unsigned char*>(const_cast<char*>(signed_data.c_str())), |
+ static_cast<unsigned int>(signed_data.size())}; |
+ SECItem hash_item = {siBuffer, reinterpret_cast<unsigned char*>(&rsa_hash), |
+ sizeof(rsa_hash)}; |
+ |
+ verified = PK11_Verify(cert_public_key_, &signature_item, &hash_item, NULL); |
+ if (verified != SECSuccess) { |
+ LOG(ERROR) << "Signed blobs did not match."; |
+ return false; |
+ } |
+ |
+ return true; |
+} |
+ |
+bool NetworkingPrivateCrypto::EncryptByteString(const std::string& public_key, |
+ const std::string& data, |
+ std::string* encrypted_output) { |
+ crypto::EnsureNSSInit(); |
+ |
+ SECItem pub_key_der_item; |
+ pub_key_der_item.data = |
+ reinterpret_cast<unsigned char*>(const_cast<char*>(public_key.c_str())); |
+ pub_key_der_item.len = public_key.size(); |
+ |
+ enc_public_key_ = SECKEY_ImportDERPublicKey(&pub_key_der_item, CKK_RSA); |
+ if (!enc_public_key_) { |
+ LOG(ERROR) << "Failed to parse public key."; |
+ return false; |
+ } |
+ |
+ size_t encrypted_length = SECKEY_SignatureLen(enc_public_key_); |
+ scoped_ptr<unsigned char[]> rsa_output(new unsigned char[encrypted_length]); |
+ |
+ LOG(INFO) << "Encrypting data with public key."; |
+ SECStatus encrypted = PK11_PubEncryptPKCS1( |
mef
2013/08/28 18:05:59
Is it ok to add this to third_party/nss/nss/export
Ryan Sleevi
2013/08/28 18:31:26
Yes, this is part of nss.def.
|
+ enc_public_key_, |
+ rsa_output.get(), |
+ // The API helpfully tells us that this operation will treat this buffer |
+ // as read only, but fails to mark the parameter const. |
+ reinterpret_cast<unsigned char*>(const_cast<char*>(data.data())), |
+ data.length(), |
+ NULL); |
+ if (encrypted != SECSuccess) { |
+ LOG(ERROR) << "Error during encryption."; |
+ return false; |
+ } |
+ |
+ encrypted_output->assign(reinterpret_cast<char*>(rsa_output.get()), |
+ encrypted_length); |
+ |
+ return true; |
+} |
+ |
+bool NetworkingPrivateCrypto::DecryptByteString( |
+ const std::string& private_key_pem, |
+ const std::string& encrypted_data, |
+ std::string* decrypted_output) { |
+ crypto::EnsureNSSInit(); |
+ std::string private_key_der; |
+ if (!GetDERFromPEM(private_key_pem, "RSA PRIVATE KEY", &private_key_der)) { |
+ LOG(ERROR) << "Failed to parse private key PEM."; |
+ return false; |
+ } |
+ std::vector<uint8> private_key_data(private_key_der.begin(), |
+ private_key_der.end()); |
+ |
+ LOG(ERROR) << "Private Key Len:" << private_key_data.size(); |
+ |
+ // TODO(mef): Figure out correct way to import DER private key. |
+ scoped_ptr<crypto::RSAPrivateKey> private_key( |
+ crypto::RSAPrivateKey::CreateFromPrivateKeyInfo(private_key_data)); |
mef
2013/08/28 18:05:59
This returns a failure from ASN.1 parser, any sugg
|
+ if (!private_key) { |
+ LOG(ERROR) << "Failed to parse private key DER."; |
+ return false; |
+ } |
+ |
+ size_t encrypted_length = SECKEY_SignatureLen(private_key->public_key()); |
+ scoped_ptr<unsigned char[]> rsa_output(new unsigned char[encrypted_length]); |
+ |
+ unsigned output_length = 0; |
Ryan Sleevi
2013/08/28 19:02:37
Blergh, poor API spec if we're calling it 'unsigne
mef
2013/08/28 21:28:58
Done.
|
+ |
+ SECStatus decrypted = PK11_PrivDecryptPKCS1( |
+ private_key->key(), |
+ rsa_output.get(), |
+ &output_length, |
+ encrypted_length, |
+ |
+ // The API helpfully tells us that this operation will treat this buffer |
+ // as read only, but fails to mark the parameter const. |
+ reinterpret_cast<unsigned char*>( |
+ const_cast<char*>(encrypted_data.data())), |
+ encrypted_data.length()); |
+ |
+ if (decrypted != SECSuccess) { |
+ LOG(ERROR) << "Error during encryption."; |
+ return false; |
+ } |
+ |
+ decrypted_output->assign(reinterpret_cast<char*>(rsa_output.get()), |
+ output_length); |
+ |
+ return true; |
+} |