diff options
Diffstat (limited to 'net/cert')
87 files changed, 20033 insertions, 0 deletions
diff --git a/net/cert/asn1_util.cc b/net/cert/asn1_util.cc new file mode 100644 index 0000000..6dcff52 --- /dev/null +++ b/net/cert/asn1_util.cc @@ -0,0 +1,331 @@ +// Copyright (c) 2012 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 "net/cert/asn1_util.h" + +namespace net { + +namespace asn1 { + +bool ParseElement(base::StringPiece* in, + unsigned tag_value, + base::StringPiece* out, + unsigned *out_header_len) { + const uint8* data = reinterpret_cast<const uint8*>(in->data()); + + // We don't support kAny and kOptional at the same time. + if ((tag_value & kAny) && (tag_value & kOptional)) + return false; + + if (in->empty() && (tag_value & kOptional)) { + if (out_header_len) + *out_header_len = 0; + if (out) + *out = base::StringPiece(); + return true; + } + + if (in->size() < 2) + return false; + + if (tag_value != kAny && + static_cast<unsigned char>(data[0]) != (tag_value & 0xff)) { + if (tag_value & kOptional) { + if (out_header_len) + *out_header_len = 0; + if (out) + *out = base::StringPiece(); + return true; + } + return false; + } + + size_t len = 0; + if ((data[1] & 0x80) == 0) { + // short form length + if (out_header_len) + *out_header_len = 2; + len = static_cast<size_t>(data[1]) + 2; + } else { + // long form length + const unsigned num_bytes = data[1] & 0x7f; + if (num_bytes == 0 || num_bytes > 2) + return false; + if (in->size() < 2 + num_bytes) + return false; + len = data[2]; + if (num_bytes == 2) { + if (len == 0) { + // the length encoding must be minimal. + return false; + } + len <<= 8; + len += data[3]; + } + if (len < 128) { + // the length should have been encoded in short form. This distinguishes + // DER from BER encoding. + return false; + } + if (out_header_len) + *out_header_len = 2 + num_bytes; + len += 2 + num_bytes; + } + + if (in->size() < len) + return false; + if (out) + *out = base::StringPiece(in->data(), len); + in->remove_prefix(len); + return true; +} + +bool GetElement(base::StringPiece* in, + unsigned tag_value, + base::StringPiece* out) { + unsigned header_len; + if (!ParseElement(in, tag_value, out, &header_len)) + return false; + if (out) + out->remove_prefix(header_len); + return true; +} + +// SeekToSPKI changes |cert| so that it points to a suffix of the +// TBSCertificate where the suffix begins at the start of the ASN.1 +// SubjectPublicKeyInfo value. +static bool SeekToSPKI(base::StringPiece* cert) { + // From RFC 5280, section 4.1 + // Certificate ::= SEQUENCE { + // tbsCertificate TBSCertificate, + // signatureAlgorithm AlgorithmIdentifier, + // signatureValue BIT STRING } + + // TBSCertificate ::= SEQUENCE { + // version [0] EXPLICIT Version DEFAULT v1, + // serialNumber CertificateSerialNumber, + // signature AlgorithmIdentifier, + // issuer Name, + // validity Validity, + // subject Name, + // subjectPublicKeyInfo SubjectPublicKeyInfo, + + base::StringPiece certificate; + if (!GetElement(cert, kSEQUENCE, &certificate)) + return false; + + // We don't allow junk after the certificate. + if (!cert->empty()) + return false; + + base::StringPiece tbs_certificate; + if (!GetElement(&certificate, kSEQUENCE, &tbs_certificate)) + return false; + + if (!GetElement(&tbs_certificate, + kOptional | kConstructed | kContextSpecific | 0, + NULL)) { + return false; + } + + // serialNumber + if (!GetElement(&tbs_certificate, kINTEGER, NULL)) + return false; + // signature + if (!GetElement(&tbs_certificate, kSEQUENCE, NULL)) + return false; + // issuer + if (!GetElement(&tbs_certificate, kSEQUENCE, NULL)) + return false; + // validity + if (!GetElement(&tbs_certificate, kSEQUENCE, NULL)) + return false; + // subject + if (!GetElement(&tbs_certificate, kSEQUENCE, NULL)) + return false; + *cert = tbs_certificate; + return true; +} + +bool ExtractSPKIFromDERCert(base::StringPiece cert, + base::StringPiece* spki_out) { + if (!SeekToSPKI(&cert)) + return false; + if (!ParseElement(&cert, kSEQUENCE, spki_out, NULL)) + return false; + return true; +} + +bool ExtractSubjectPublicKeyFromSPKI(base::StringPiece spki, + base::StringPiece* spk_out) { + // From RFC 5280, Section 4.1 + // SubjectPublicKeyInfo ::= SEQUENCE { + // algorithm AlgorithmIdentifier, + // subjectPublicKey BIT STRING } + // + // AlgorithmIdentifier ::= SEQUENCE { + // algorithm OBJECT IDENTIFIER, + // parameters ANY DEFINED BY algorithm OPTIONAL } + + // Step into SubjectPublicKeyInfo sequence. + base::StringPiece spki_contents; + if (!asn1::GetElement(&spki, asn1::kSEQUENCE, &spki_contents)) + return false; + + // Step over algorithm field (a SEQUENCE). + base::StringPiece algorithm; + if (!asn1::GetElement(&spki_contents, asn1::kSEQUENCE, &algorithm)) + return false; + + // Extract the subjectPublicKey field. + if (!asn1::GetElement(&spki_contents, asn1::kBITSTRING, spk_out)) + return false; + return true; +} + + +bool ExtractCRLURLsFromDERCert(base::StringPiece cert, + std::vector<base::StringPiece>* urls_out) { + urls_out->clear(); + std::vector<base::StringPiece> tmp_urls_out; + + if (!SeekToSPKI(&cert)) + return false; + + // From RFC 5280, section 4.1 + // TBSCertificate ::= SEQUENCE { + // ... + // subjectPublicKeyInfo SubjectPublicKeyInfo, + // issuerUniqueID [1] IMPLICIT UniqueIdentifier OPTIONAL, + // subjectUniqueID [2] IMPLICIT UniqueIdentifier OPTIONAL, + // extensions [3] EXPLICIT Extensions OPTIONAL + + // subjectPublicKeyInfo + if (!GetElement(&cert, kSEQUENCE, NULL)) + return false; + // issuerUniqueID + if (!GetElement(&cert, kOptional | kConstructed | kContextSpecific | 1, NULL)) + return false; + // subjectUniqueID + if (!GetElement(&cert, kOptional | kConstructed | kContextSpecific | 2, NULL)) + return false; + + base::StringPiece extensions_seq; + if (!GetElement(&cert, kOptional | kConstructed | kContextSpecific | 3, + &extensions_seq)) { + return false; + } + + if (extensions_seq.empty()) + return true; + + // Extensions ::= SEQUENCE SIZE (1..MAX) OF Extension + // Extension ::= SEQUENCE { + // extnID OBJECT IDENTIFIER, + // critical BOOLEAN DEFAULT FALSE, + // extnValue OCTET STRING + + // |extensions_seq| was EXPLICITly tagged, so we still need to remove the + // ASN.1 SEQUENCE header. + base::StringPiece extensions; + if (!GetElement(&extensions_seq, kSEQUENCE, &extensions)) + return false; + + while (extensions.size() > 0) { + base::StringPiece extension; + if (!GetElement(&extensions, kSEQUENCE, &extension)) + return false; + + base::StringPiece oid; + if (!GetElement(&extension, kOID, &oid)) + return false; + + // kCRLDistributionPointsOID is the DER encoding of the OID for the X.509 + // CRL Distribution Points extension. + static const uint8 kCRLDistributionPointsOID[] = {0x55, 0x1d, 0x1f}; + + if (oid.size() != sizeof(kCRLDistributionPointsOID) || + memcmp(oid.data(), kCRLDistributionPointsOID, oid.size()) != 0) { + continue; + } + + // critical + GetElement(&extension, kBOOLEAN, NULL); + + // extnValue + base::StringPiece extension_value; + if (!GetElement(&extension, kOCTETSTRING, &extension_value)) + return false; + + // RFC 5280, section 4.2.1.13. + // + // CRLDistributionPoints ::= SEQUENCE SIZE (1..MAX) OF DistributionPoint + // + // DistributionPoint ::= SEQUENCE { + // distributionPoint [0] DistributionPointName OPTIONAL, + // reasons [1] ReasonFlags OPTIONAL, + // cRLIssuer [2] GeneralNames OPTIONAL } + + base::StringPiece distribution_points; + if (!GetElement(&extension_value, kSEQUENCE, &distribution_points)) + return false; + + while (distribution_points.size() > 0) { + base::StringPiece distrib_point; + if (!GetElement(&distribution_points, kSEQUENCE, &distrib_point)) + return false; + + base::StringPiece name; + if (!GetElement(&distrib_point, kContextSpecific | kConstructed | 0, + &name)) { + // If it doesn't contain a name then we skip it. + continue; + } + + if (GetElement(&distrib_point, kContextSpecific | 1, NULL)) { + // If it contains a subset of reasons then we skip it. We aren't + // interested in subsets of CRLs and the RFC states that there MUST be + // a CRL that covers all reasons. + continue; + } + + if (GetElement(&distrib_point, + kContextSpecific | kConstructed | 2, NULL)) { + // If it contains a alternative issuer, then we skip it. + continue; + } + + // DistributionPointName ::= CHOICE { + // fullName [0] GeneralNames, + // nameRelativeToCRLIssuer [1] RelativeDistinguishedName } + base::StringPiece general_names; + if (!GetElement(&name, + kContextSpecific | kConstructed | 0, &general_names)) { + continue; + } + + // GeneralNames ::= SEQUENCE SIZE (1..MAX) OF GeneralName + // GeneralName ::= CHOICE { + // ... + // uniformResourceIdentifier [6] IA5String, + // ... + while (general_names.size() > 0) { + base::StringPiece url; + if (GetElement(&general_names, kContextSpecific | 6, &url)) { + tmp_urls_out.push_back(url); + } else { + if (!GetElement(&general_names, kAny, NULL)) + return false; + } + } + } + } + + urls_out->swap(tmp_urls_out); + return true; +} + +} // namespace asn1 + +} // namespace net diff --git a/net/cert/asn1_util.h b/net/cert/asn1_util.h new file mode 100644 index 0000000..9a10bee --- /dev/null +++ b/net/cert/asn1_util.h @@ -0,0 +1,93 @@ +// Copyright (c) 2012 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. + +#ifndef NET_CERT_ASN1_UTIL_H_ +#define NET_CERT_ASN1_UTIL_H_ + +#include <vector> + +#include "base/string_piece.h" +#include "net/base/net_export.h" + +namespace net { + +namespace asn1 { + +// These are the DER encodings of the tag byte for ASN.1 objects. +static const unsigned kBOOLEAN = 0x01; +static const unsigned kINTEGER = 0x02; +static const unsigned kBITSTRING = 0x03; +static const unsigned kOCTETSTRING = 0x04; +static const unsigned kOID = 0x06; +static const unsigned kSEQUENCE = 0x30; + +// These are flags that can be ORed with the above tag numbers. +static const unsigned kContextSpecific = 0x80; +static const unsigned kConstructed = 0x20; + +// kAny matches any tag value; +static const unsigned kAny = 0x10000; +// kOptional denotes an optional element. +static const unsigned kOptional = 0x20000; + +// ParseElement parses a DER encoded ASN1 element from |in|, requiring that +// it have the given |tag_value|. It returns true on success. The following +// limitations are imposed: +// 1) tag numbers > 31 are not permitted. +// 2) lengths > 65535 are not permitted. +// On successful return: +// |in| is advanced over the element +// |out| contains the element, including the tag and length bytes. +// |out_header_len| contains the length of the tag and length bytes in |out|. +// +// If |tag_value & kOptional| is true then *out_header_len can be zero after a +// true return value if the element was not found. +bool ParseElement(base::StringPiece* in, + unsigned tag_value, + base::StringPiece* out, + unsigned *out_header_len); + +// GetElement performs the same actions as ParseElement, except that the header +// bytes are not included in the output. +// +// If |tag_value & kOptional| is true then this function cannot distinguish +// between a missing optional element and an empty one. +bool GetElement(base::StringPiece* in, + unsigned tag_value, + base::StringPiece* out); + +// ExtractSPKIFromDERCert parses the DER encoded certificate in |cert| and +// extracts the bytes of the SubjectPublicKeyInfo. On successful return, +// |spki_out| is set to contain the SPKI, pointing into |cert|. +NET_EXPORT_PRIVATE bool ExtractSPKIFromDERCert(base::StringPiece cert, + base::StringPiece* spki_out); + +// ExtractSubjectPublicKeyFromSPKI parses the DER encoded SubjectPublicKeyInfo +// in |spki| and extracts the bytes of the SubjectPublicKey. On successful +// return, |spk_out| is set to contain the public key, pointing into |spki|. +NET_EXPORT_PRIVATE bool ExtractSubjectPublicKeyFromSPKI( + base::StringPiece spki, + base::StringPiece* spk_out); + +// ExtractCRLURLsFromDERCert parses the DER encoded certificate in |cert| and +// extracts the URL of each CRL. On successful return, the elements of +// |urls_out| point into |cert|. +// +// CRLs that only cover a subset of the reasons are omitted as the spec +// requires that at least one CRL be included that covers all reasons. +// +// CRLs that use an alternative issuer are also omitted. +// +// The nested set of GeneralNames is flattened into a single list because +// having several CRLs with one location is equivalent to having one CRL with +// several locations as far as a CRL filter is concerned. +NET_EXPORT_PRIVATE bool ExtractCRLURLsFromDERCert( + base::StringPiece cert, + std::vector<base::StringPiece>* urls_out); + +} // namespace asn1 + +} // namespace net + +#endif // NET_CERT_ASN1_UTIL_H_ diff --git a/net/cert/cert_database.cc b/net/cert/cert_database.cc new file mode 100644 index 0000000..db54172 --- /dev/null +++ b/net/cert/cert_database.cc @@ -0,0 +1,39 @@ +// Copyright (c) 2012 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 "net/cert/cert_database.h" + +#include "base/memory/singleton.h" +#include "base/observer_list_threadsafe.h" + +namespace net { + +// static +CertDatabase* CertDatabase::GetInstance() { + return Singleton<CertDatabase>::get(); +} + +void CertDatabase::AddObserver(Observer* observer) { + observer_list_->AddObserver(observer); +} + +void CertDatabase::RemoveObserver(Observer* observer) { + observer_list_->RemoveObserver(observer); +} + +void CertDatabase::NotifyObserversOfCertAdded(const X509Certificate* cert) { + observer_list_->Notify(&Observer::OnCertAdded, make_scoped_refptr(cert)); +} + +void CertDatabase::NotifyObserversOfCertRemoved(const X509Certificate* cert) { + observer_list_->Notify(&Observer::OnCertRemoved, make_scoped_refptr(cert)); +} + +void CertDatabase::NotifyObserversOfCertTrustChanged( + const X509Certificate* cert) { + observer_list_->Notify( + &Observer::OnCertTrustChanged, make_scoped_refptr(cert)); +} + +} // namespace net diff --git a/net/cert/cert_database.h b/net/cert/cert_database.h new file mode 100644 index 0000000..c4ead81 --- /dev/null +++ b/net/cert/cert_database.h @@ -0,0 +1,105 @@ +// Copyright (c) 2012 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. + +#ifndef NET_CERT_CERT_DATABASE_H_ +#define NET_CERT_CERT_DATABASE_H_ + +#include "base/basictypes.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "net/base/net_export.h" +#include "net/cert/x509_certificate.h" + +template <typename T> struct DefaultSingletonTraits; +template <class ObserverType> class ObserverListThreadSafe; + +namespace net { + +// This class provides cross-platform functions to verify and add user +// certificates, and to observe changes to the underlying certificate stores. + +// TODO(gauravsh): This class could be augmented with methods +// for all operations that manipulate the underlying system +// certificate store. + +class NET_EXPORT CertDatabase { + public: + // A CertDatabase::Observer will be notified on certificate database changes. + // The change could be either a new user certificate is added or trust on + // a certificate is changed. Observers can register themselves + // via CertDatabase::AddObserver, and can un-register with + // CertDatabase::RemoveObserver. + class NET_EXPORT Observer { + public: + virtual ~Observer() {} + + // Will be called when a new certificate is added. + virtual void OnCertAdded(const X509Certificate* cert) {} + + // Will be called when a certificate is removed. + virtual void OnCertRemoved(const X509Certificate* cert) {} + + // Will be called when a certificate's trust is changed. + virtual void OnCertTrustChanged(const X509Certificate* cert) {} + + protected: + Observer() {} + + private: + DISALLOW_COPY_AND_ASSIGN(Observer); + }; + + // Returns the CertDatabase singleton. + static CertDatabase* GetInstance(); + + // Check whether this is a valid user cert that we have the private key for. + // Returns OK or a network error code such as ERR_CERT_CONTAINS_ERRORS. + int CheckUserCert(X509Certificate* cert); + + // Store user (client) certificate. Assumes CheckUserCert has already passed. + // Returns OK, or ERR_ADD_USER_CERT_FAILED if there was a problem saving to + // the platform cert database, or possibly other network error codes. + int AddUserCert(X509Certificate* cert); + + // Registers |observer| to receive notifications of certificate changes. The + // thread on which this is called is the thread on which |observer| will be + // called back with notifications. + void AddObserver(Observer* observer); + + // Unregisters |observer| from receiving notifications. This must be called + // on the same thread on which AddObserver() was called. + void RemoveObserver(Observer* observer); + +#if defined(OS_MACOSX) && !defined(OS_IOS) + // Configures the current message loop to observe and forward events from + // Keychain services. The MessageLoop must have an associated CFRunLoop, + // which means that this must be called from a MessageLoop of TYPE_UI. + void SetMessageLoopForKeychainEvents(); +#endif + + private: + friend struct DefaultSingletonTraits<CertDatabase>; + + CertDatabase(); + ~CertDatabase(); + + // Broadcasts notifications to all registered observers. + void NotifyObserversOfCertAdded(const X509Certificate* cert); + void NotifyObserversOfCertRemoved(const X509Certificate* cert); + void NotifyObserversOfCertTrustChanged(const X509Certificate* cert); + + const scoped_refptr<ObserverListThreadSafe<Observer> > observer_list_; + +#if defined(USE_NSS) || (defined(OS_MACOSX) && !defined(OS_IOS)) + class Notifier; + friend class Notifier; + scoped_ptr<Notifier> notifier_; +#endif + + DISALLOW_COPY_AND_ASSIGN(CertDatabase); +}; + +} // namespace net + +#endif // NET_CERT_CERT_DATABASE_H_ diff --git a/net/cert/cert_database_android.cc b/net/cert/cert_database_android.cc new file mode 100644 index 0000000..9755805 --- /dev/null +++ b/net/cert/cert_database_android.cc @@ -0,0 +1,39 @@ +// Copyright (c) 2012 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 "net/cert/cert_database.h" + +#include "base/logging.h" +#include "base/observer_list_threadsafe.h" +#include "net/base/net_errors.h" + +namespace net { + +CertDatabase::CertDatabase() + : observer_list_(new ObserverListThreadSafe<Observer>) { +} + +CertDatabase::~CertDatabase() {} + +int CertDatabase::CheckUserCert(X509Certificate* cert) { + // NOTE: This method shall never be called on Android. + // + // On other platforms, it is only used by the SSLAddCertHandler class + // to handle veritication and installation of downloaded certificates. + // + // On Android, the certificate data is passed directly to the system's + // CertInstaller activity, which handles verification, naming, + // installation and UI (for success/failure). + NOTIMPLEMENTED(); + return ERR_NOT_IMPLEMENTED; +} + +int CertDatabase::AddUserCert(X509Certificate* cert) { + // This method is only used by the content SSLAddCertHandler which is + // never used on Android. + NOTIMPLEMENTED(); + return ERR_NOT_IMPLEMENTED; +} + +} // namespace net diff --git a/net/cert/cert_database_ios.cc b/net/cert/cert_database_ios.cc new file mode 100644 index 0000000..f96f22a --- /dev/null +++ b/net/cert/cert_database_ios.cc @@ -0,0 +1,30 @@ +// Copyright (c) 2012 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 "net/cert/cert_database.h" + +#include "base/logging.h" +#include "base/observer_list_threadsafe.h" +#include "net/base/net_errors.h" + +namespace net { + +CertDatabase::CertDatabase() + : observer_list_(new ObserverListThreadSafe<Observer>) {} + +CertDatabase::~CertDatabase() {} + +int CertDatabase::CheckUserCert(X509Certificate* cert_obj) { + // iOS doesn't handle user certificates. + NOTREACHED(); + return OK; +} + +int CertDatabase::AddUserCert(X509Certificate* cert_obj) { + // iOS doesn't handle user certificates. + NOTREACHED(); + return OK; +} + +} // namespace net diff --git a/net/cert/cert_database_mac.cc b/net/cert/cert_database_mac.cc new file mode 100644 index 0000000..c8fcf47 --- /dev/null +++ b/net/cert/cert_database_mac.cc @@ -0,0 +1,172 @@ +// Copyright (c) 2012 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 "net/cert/cert_database.h" + +#include <Security/Security.h> + +#include "base/logging.h" +#include "base/mac/mac_logging.h" +#include "base/message_loop.h" +#include "base/observer_list_threadsafe.h" +#include "base/process_util.h" +#include "base/single_thread_task_runner.h" +#include "base/synchronization/lock.h" +#include "crypto/mac_security_services_lock.h" +#include "net/base/net_errors.h" +#include "net/cert/x509_certificate.h" + +namespace net { + +// Helper that observes events from the Keychain and forwards them to the +// given CertDatabase. +class CertDatabase::Notifier { + public: + // Creates a new Notifier that will forward Keychain events to |cert_db|. + // |message_loop| must refer to a thread with an associated CFRunLoop - a + // TYPE_UI thread. Events will be dispatched from this message loop. + Notifier(CertDatabase* cert_db, MessageLoop* message_loop) + : cert_db_(cert_db), + registered_(false), + called_shutdown_(false) { + // Ensure an associated CFRunLoop. + DCHECK(message_loop->IsType(MessageLoop::TYPE_UI)); + task_runner_ = message_loop->message_loop_proxy(); + task_runner_->PostTask(FROM_HERE, + base::Bind(&Notifier::Init, + base::Unretained(this))); + } + + // Should be called from the |task_runner_|'s thread. Use Shutdown() + // to shutdown on arbitrary threads. + ~Notifier() { + DCHECK(called_shutdown_); + // Only unregister from the same thread where registration was performed. + if (registered_ && task_runner_->RunsTasksOnCurrentThread()) + SecKeychainRemoveCallback(&Notifier::KeychainCallback); + } + + void Shutdown() { + called_shutdown_ = true; + if (!task_runner_->DeleteSoon(FROM_HERE, this)) { + // If the task runner is no longer running, it's safe to just delete + // the object, since no further events will or can be delivered by + // Keychain Services. + delete this; + } + } + + private: + void Init() { + SecKeychainEventMask event_mask = + kSecKeychainListChangedMask | kSecTrustSettingsChangedEventMask; + OSStatus status = SecKeychainAddCallback(&Notifier::KeychainCallback, + event_mask, this); + if (status == noErr) + registered_ = true; + } + + // SecKeychainCallback function that receives notifications from securityd + // and forwards them to the |cert_db_|. + static OSStatus KeychainCallback(SecKeychainEvent keychain_event, + SecKeychainCallbackInfo* info, + void* context); + + CertDatabase* const cert_db_; + scoped_refptr<base::SingleThreadTaskRunner> task_runner_; + bool registered_; + bool called_shutdown_; +}; + +// static +OSStatus CertDatabase::Notifier::KeychainCallback( + SecKeychainEvent keychain_event, + SecKeychainCallbackInfo* info, + void* context) { + Notifier* that = reinterpret_cast<Notifier*>(context); + + if (info->version > SEC_KEYCHAIN_SETTINGS_VERS1) { + NOTREACHED(); + return errSecWrongSecVersion; + } + + if (info->pid == base::GetCurrentProcId()) { + // Ignore events generated by the current process, as the assumption is + // that they have already been handled. This may miss events that + // originated as a result of spawning native dialogs that allow the user + // to modify Keychain settings. However, err on the side of missing + // events rather than sending too many events. + return errSecSuccess; + } + + switch (keychain_event) { + case kSecKeychainListChangedEvent: + case kSecTrustSettingsChangedEvent: + that->cert_db_->NotifyObserversOfCertTrustChanged(NULL); + break; + } + + return errSecSuccess; +} + +void CertDatabase::SetMessageLoopForKeychainEvents() { + // Shutdown will take care to delete the notifier on the right thread. + if (notifier_.get()) + notifier_.release()->Shutdown(); + + notifier_.reset(new Notifier(this, MessageLoopForUI::current())); +} + +CertDatabase::CertDatabase() + : observer_list_(new ObserverListThreadSafe<Observer>) { +} + +CertDatabase::~CertDatabase() { + // Shutdown will take care to delete the notifier on the right thread. + if (notifier_.get()) + notifier_.release()->Shutdown(); +} + +int CertDatabase::CheckUserCert(X509Certificate* cert) { + if (!cert) + return ERR_CERT_INVALID; + if (cert->HasExpired()) + return ERR_CERT_DATE_INVALID; + + // Verify the Keychain already has the corresponding private key: + SecIdentityRef identity = NULL; + OSStatus err = SecIdentityCreateWithCertificate(NULL, cert->os_cert_handle(), + &identity); + if (err == errSecItemNotFound) + return ERR_NO_PRIVATE_KEY_FOR_CERT; + + if (err != noErr || !identity) { + // TODO(snej): Map the error code more intelligently. + return ERR_CERT_INVALID; + } + + CFRelease(identity); + return OK; +} + +int CertDatabase::AddUserCert(X509Certificate* cert) { + OSStatus err; + { + base::AutoLock locked(crypto::GetMacSecurityServicesLock()); + err = SecCertificateAddToKeychain(cert->os_cert_handle(), NULL); + } + switch (err) { + case noErr: + CertDatabase::NotifyObserversOfCertAdded(cert); + // Fall through. + case errSecDuplicateItem: + return OK; + default: + OSSTATUS_LOG(ERROR, err) << "CertDatabase failed to add cert to keychain"; + // TODO(snej): Map the error code more intelligently. + return ERR_ADD_USER_CERT_FAILED; + } +} + +} // namespace net diff --git a/net/cert/cert_database_nss.cc b/net/cert/cert_database_nss.cc new file mode 100644 index 0000000..3ac1407 --- /dev/null +++ b/net/cert/cert_database_nss.cc @@ -0,0 +1,104 @@ +// Copyright (c) 2012 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 "net/cert/cert_database.h" + +#include <cert.h> +#include <pk11pub.h> +#include <secmod.h> + +#include "base/logging.h" +#include "base/observer_list_threadsafe.h" +#include "crypto/nss_util.h" +#include "net/base/net_errors.h" +#include "net/cert/nss_cert_database.h" +#include "net/cert/x509_certificate.h" + +namespace net { + +// Helper that observes events from the NSSCertDatabase and forwards them to +// the given CertDatabase. +class CertDatabase::Notifier : public NSSCertDatabase::Observer { + public: + explicit Notifier(CertDatabase* cert_db) : cert_db_(cert_db) { + NSSCertDatabase::GetInstance()->AddObserver(this); + } + + virtual ~Notifier() { + NSSCertDatabase::GetInstance()->RemoveObserver(this); + } + + // NSSCertDatabase::Observer implementation: + virtual void OnCertAdded(const X509Certificate* cert) OVERRIDE { + cert_db_->NotifyObserversOfCertAdded(cert); + } + + virtual void OnCertRemoved(const X509Certificate* cert) OVERRIDE { + cert_db_->NotifyObserversOfCertRemoved(cert); + } + + virtual void OnCertTrustChanged(const X509Certificate* cert) OVERRIDE { + cert_db_->NotifyObserversOfCertTrustChanged(cert); + } + + private: + CertDatabase* cert_db_; + + DISALLOW_COPY_AND_ASSIGN(Notifier); +}; + +CertDatabase::CertDatabase() + : observer_list_(new ObserverListThreadSafe<Observer>) { + // Observe NSSCertDatabase events and forward them to observers of + // CertDatabase. This also makes sure that NSS has been initialized. + notifier_.reset(new Notifier(this)); +} + +CertDatabase::~CertDatabase() {} + +int CertDatabase::CheckUserCert(X509Certificate* cert_obj) { + if (!cert_obj) + return ERR_CERT_INVALID; + if (cert_obj->HasExpired()) + return ERR_CERT_DATE_INVALID; + + // Check if the private key corresponding to the certificate exist + // We shouldn't accept any random client certificate sent by a CA. + + // Note: The NSS source documentation wrongly suggests that this + // also imports the certificate if the private key exists. This + // doesn't seem to be the case. + + CERTCertificate* cert = cert_obj->os_cert_handle(); + PK11SlotInfo* slot = PK11_KeyForCertExists(cert, NULL, NULL); + if (!slot) + return ERR_NO_PRIVATE_KEY_FOR_CERT; + + PK11_FreeSlot(slot); + + return OK; +} + +int CertDatabase::AddUserCert(X509Certificate* cert_obj) { + CERTCertificate* cert = cert_obj->os_cert_handle(); + PK11SlotInfo* slot = NULL; + + { + crypto::AutoNSSWriteLock lock; + slot = PK11_ImportCertForKey( + cert, + cert_obj->GetDefaultNickname(net::USER_CERT).c_str(), + NULL); + } + + if (!slot) { + LOG(ERROR) << "Couldn't import user certificate."; + return ERR_ADD_USER_CERT_FAILED; + } + PK11_FreeSlot(slot); + NotifyObserversOfCertAdded(cert_obj); + return OK; +} + +} // namespace net diff --git a/net/cert/cert_database_openssl.cc b/net/cert/cert_database_openssl.cc new file mode 100644 index 0000000..23b64cc --- /dev/null +++ b/net/cert/cert_database_openssl.cc @@ -0,0 +1,58 @@ +// Copyright (c) 2012 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 "net/cert/cert_database.h" + +#include <openssl/x509.h> + +#include "base/logging.h" +#include "base/observer_list_threadsafe.h" +#include "crypto/openssl_util.h" +#include "net/base/crypto_module.h" +#include "net/base/net_errors.h" +#include "net/base/openssl_private_key_store.h" +#include "net/cert/x509_certificate.h" + +namespace net { + +CertDatabase::CertDatabase() + : observer_list_(new ObserverListThreadSafe<Observer>) { +} + +CertDatabase::~CertDatabase() {} + +// This method is used to check a client certificate before trying to +// install it on the system, which will happen later by calling +// AddUserCert() below. +// +// On the Linux/OpenSSL build, there is simply no system keystore, but +// OpenSSLPrivateKeyStore() implements a small in-memory store for +// (public/private) key pairs generated through keygen. +// +// Try to check for a private key in the in-memory store to check +// for the case when the browser is trying to install a server-generated +// certificate from a <keygen> exchange. +int CertDatabase::CheckUserCert(X509Certificate* cert) { + if (!cert) + return ERR_CERT_INVALID; + if (cert->HasExpired()) + return ERR_CERT_DATE_INVALID; + + // X509_PUBKEY_get() transfers ownership, not X509_get_X509_PUBKEY() + crypto::ScopedOpenSSL<EVP_PKEY, EVP_PKEY_free> public_key( + X509_PUBKEY_get(X509_get_X509_PUBKEY(cert->os_cert_handle()))); + + if (!OpenSSLPrivateKeyStore::HasPrivateKey(public_key.get())) + return ERR_NO_PRIVATE_KEY_FOR_CERT; + + return OK; +} + +int CertDatabase::AddUserCert(X509Certificate* cert) { + // There is no certificate store on the Linux/OpenSSL build. + NOTIMPLEMENTED(); + return ERR_NOT_IMPLEMENTED; +} + +} // namespace net diff --git a/net/cert/cert_database_win.cc b/net/cert/cert_database_win.cc new file mode 100644 index 0000000..9bf378c --- /dev/null +++ b/net/cert/cert_database_win.cc @@ -0,0 +1,61 @@ +// Copyright (c) 2010 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 "net/cert/cert_database.h" + +#include <windows.h> +#include <wincrypt.h> +#pragma comment(lib, "crypt32.lib") + +#include "base/observer_list_threadsafe.h" +#include "net/base/net_errors.h" +#include "net/cert/x509_certificate.h" + +namespace net { + +CertDatabase::CertDatabase() + : observer_list_(new ObserverListThreadSafe<Observer>) { +} + +CertDatabase::~CertDatabase() {} + +int CertDatabase::CheckUserCert(X509Certificate* cert) { + if (!cert) + return ERR_CERT_INVALID; + if (cert->HasExpired()) + return ERR_CERT_DATE_INVALID; + + // TODO(rsleevi): Should CRYPT_FIND_SILENT_KEYSET_FLAG be specified? A UI + // may be shown here / this call may block. + if (!CryptFindCertificateKeyProvInfo(cert->os_cert_handle(), 0, NULL)) + return ERR_NO_PRIVATE_KEY_FOR_CERT; + + return OK; +} + +int CertDatabase::AddUserCert(X509Certificate* cert) { + // TODO(rsleevi): Would it be more appropriate to have the CertDatabase take + // construction parameters (Keychain filepath on Mac OS X, PKCS #11 slot on + // NSS, and Store Type / Path) here? For now, certs will be stashed into the + // user's personal store, which will not automatically mark them as trusted, + // but will allow them to be used for client auth. + HCERTSTORE cert_db = CertOpenSystemStore(NULL, L"MY"); + if (!cert_db) + return ERR_ADD_USER_CERT_FAILED; + + BOOL added = CertAddCertificateContextToStore(cert_db, + cert->os_cert_handle(), + CERT_STORE_ADD_USE_EXISTING, + NULL); + + CertCloseStore(cert_db, 0); + + if (!added) + return ERR_ADD_USER_CERT_FAILED; + + NotifyObserversOfCertAdded(cert); + return OK; +} + +} // namespace net diff --git a/net/cert/cert_status_flags.cc b/net/cert/cert_status_flags.cc new file mode 100644 index 0000000..8cb736c --- /dev/null +++ b/net/cert/cert_status_flags.cc @@ -0,0 +1,83 @@ +// Copyright (c) 2011 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 "net/cert/cert_status_flags.h" + +#include "base/logging.h" +#include "net/base/net_errors.h" + +namespace net { + +bool IsCertStatusMinorError(CertStatus cert_status) { + static const CertStatus kMinorErrors = + CERT_STATUS_UNABLE_TO_CHECK_REVOCATION | + CERT_STATUS_NO_REVOCATION_MECHANISM; + cert_status &= CERT_STATUS_ALL_ERRORS; + return cert_status != 0 && (cert_status & ~kMinorErrors) == 0; +} + +CertStatus MapNetErrorToCertStatus(int error) { + switch (error) { + case ERR_CERT_COMMON_NAME_INVALID: + return CERT_STATUS_COMMON_NAME_INVALID; + case ERR_CERT_DATE_INVALID: + return CERT_STATUS_DATE_INVALID; + case ERR_CERT_AUTHORITY_INVALID: + return CERT_STATUS_AUTHORITY_INVALID; + case ERR_CERT_NO_REVOCATION_MECHANISM: + return CERT_STATUS_NO_REVOCATION_MECHANISM; + case ERR_CERT_UNABLE_TO_CHECK_REVOCATION: + return CERT_STATUS_UNABLE_TO_CHECK_REVOCATION; + case ERR_CERT_REVOKED: + return CERT_STATUS_REVOKED; + // We added the ERR_CERT_CONTAINS_ERRORS error code when we were using + // WinInet, but we never figured out how it differs from ERR_CERT_INVALID. + // We should not use ERR_CERT_CONTAINS_ERRORS in new code. + case ERR_CERT_CONTAINS_ERRORS: + NOTREACHED(); + // Falls through. + case ERR_CERT_INVALID: + return CERT_STATUS_INVALID; + case ERR_CERT_WEAK_SIGNATURE_ALGORITHM: + return CERT_STATUS_WEAK_SIGNATURE_ALGORITHM; + case ERR_CERT_WEAK_KEY: + return CERT_STATUS_WEAK_KEY; + default: + return 0; + } +} + +int MapCertStatusToNetError(CertStatus cert_status) { + // A certificate may have multiple errors. We report the most + // serious error. + + // Unrecoverable errors + if (cert_status & CERT_STATUS_REVOKED) + return ERR_CERT_REVOKED; + if (cert_status & CERT_STATUS_INVALID) + return ERR_CERT_INVALID; + + // Recoverable errors + if (cert_status & CERT_STATUS_AUTHORITY_INVALID) + return ERR_CERT_AUTHORITY_INVALID; + if (cert_status & CERT_STATUS_COMMON_NAME_INVALID) + return ERR_CERT_COMMON_NAME_INVALID; + if (cert_status & CERT_STATUS_WEAK_SIGNATURE_ALGORITHM) + return ERR_CERT_WEAK_SIGNATURE_ALGORITHM; + if (cert_status & CERT_STATUS_WEAK_KEY) + return ERR_CERT_WEAK_KEY; + if (cert_status & CERT_STATUS_DATE_INVALID) + return ERR_CERT_DATE_INVALID; + + // Unknown status. Give it the benefit of the doubt. + if (cert_status & CERT_STATUS_UNABLE_TO_CHECK_REVOCATION) + return ERR_CERT_UNABLE_TO_CHECK_REVOCATION; + if (cert_status & CERT_STATUS_NO_REVOCATION_MECHANISM) + return ERR_CERT_NO_REVOCATION_MECHANISM; + + NOTREACHED(); + return ERR_UNEXPECTED; +} + +} // namespace net diff --git a/net/cert/cert_status_flags.h b/net/cert/cert_status_flags.h new file mode 100644 index 0000000..8431032 --- /dev/null +++ b/net/cert/cert_status_flags.h @@ -0,0 +1,61 @@ +// Copyright (c) 2012 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. + +#ifndef NET_CERT_CERT_STATUS_FLAGS_H_ +#define NET_CERT_CERT_STATUS_FLAGS_H_ + +#include "base/basictypes.h" +#include "net/base/net_export.h" + +namespace net { + +// Bitmask of status flags of a certificate, representing any errors, as well as +// other non-error status information such as whether the certificate is EV. +typedef uint32 CertStatus; + +// The possible status bits for CertStatus. +// NOTE: Because these names have appeared in bug reports, we preserve them as +// MACRO_STYLE for continuity, instead of renaming them to kConstantStyle as +// befits most static consts. +// Bits 0 to 15 are for errors. +static const CertStatus CERT_STATUS_ALL_ERRORS = 0xFFFF; +static const CertStatus CERT_STATUS_COMMON_NAME_INVALID = 1 << 0; +static const CertStatus CERT_STATUS_DATE_INVALID = 1 << 1; +static const CertStatus CERT_STATUS_AUTHORITY_INVALID = 1 << 2; +// 1 << 3 is reserved for ERR_CERT_CONTAINS_ERRORS (not useful with WinHTTP). +static const CertStatus CERT_STATUS_NO_REVOCATION_MECHANISM = 1 << 4; +static const CertStatus CERT_STATUS_UNABLE_TO_CHECK_REVOCATION = 1 << 5; +static const CertStatus CERT_STATUS_REVOKED = 1 << 6; +static const CertStatus CERT_STATUS_INVALID = 1 << 7; +static const CertStatus CERT_STATUS_WEAK_SIGNATURE_ALGORITHM = 1 << 8; +// 1 << 9 was used for CERT_STATUS_NOT_IN_DNS +static const CertStatus CERT_STATUS_NON_UNIQUE_NAME = 1 << 10; +static const CertStatus CERT_STATUS_WEAK_KEY = 1 << 11; + +// Bits 16 to 31 are for non-error statuses. +static const CertStatus CERT_STATUS_IS_EV = 1 << 16; +static const CertStatus CERT_STATUS_REV_CHECKING_ENABLED = 1 << 17; +// bit 18 was CERT_STATUS_IS_DNSSEC. + +// Returns true if the specified cert status has an error set. +static inline bool IsCertStatusError(CertStatus status) { + return (CERT_STATUS_ALL_ERRORS & status) != 0; +} + +// IsCertStatusMinorError returns true iff |cert_status| indicates a condition +// that should typically be ignored by automated requests. (i.e. a revocation +// check failure.) +NET_EXPORT bool IsCertStatusMinorError(CertStatus cert_status); + +// Maps a network error code to the equivalent certificate status flag. If +// the error code is not a certificate error, it is mapped to 0. +NET_EXPORT CertStatus MapNetErrorToCertStatus(int error); + +// Maps the most serious certificate error in the certificate status flags +// to the equivalent network error code. +NET_EXPORT int MapCertStatusToNetError(CertStatus cert_status); + +} // namespace net + +#endif // NET_CERT_CERT_STATUS_FLAGS_H_ diff --git a/net/cert/cert_trust_anchor_provider.h b/net/cert/cert_trust_anchor_provider.h new file mode 100644 index 0000000..1712b2d --- /dev/null +++ b/net/cert/cert_trust_anchor_provider.h @@ -0,0 +1,33 @@ +// Copyright (c) 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. + +#ifndef NET_CERT_CERT_TRUST_ANCHOR_PROVIDER_H_ +#define NET_CERT_CERT_TRUST_ANCHOR_PROVIDER_H_ + +#include <vector> + +#include "base/memory/ref_counted.h" +#include "net/base/net_export.h" + +namespace net { + +class X509Certificate; +typedef std::vector<scoped_refptr<X509Certificate> > CertificateList; + +// Interface to retrieve the current list of additional trust anchors. +// This is used by CertVerifier to get a list of anchors to trust in addition to +// the anchors known to the CertVerifier. +class NET_EXPORT CertTrustAnchorProvider { + public: + virtual ~CertTrustAnchorProvider() {} + + // Returns a list of certificates to be used as trust anchors during + // certificate validation, in addition to (eg: the union of) any pre-existing + // or pre-configured trust anchors. + virtual const CertificateList& GetAdditionalTrustAnchors() = 0; +}; + +} // namespace net + +#endif // NET_CERT_CERT_TRUST_ANCHOR_PROVIDER_H_ diff --git a/net/cert/cert_type.h b/net/cert/cert_type.h new file mode 100644 index 0000000..cb21227 --- /dev/null +++ b/net/cert/cert_type.h @@ -0,0 +1,28 @@ +// Copyright (c) 2010 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. + +#ifndef NET_CERT_CERT_TYPE_H_ +#define NET_CERT_CERT_TYPE_H_ + +namespace net { + +// Constants to classify the type of a certificate. +// This is only used in the context of CertDatabase, but is defined outside to +// avoid an awkwardly long type name. +// The type is a combination of intrinsic properties, such as the presense of an +// Certificate Authority Basic Constraint, and assigned trust values. For +// example, a cert with no basic constraints or trust would be classified as +// UNKNOWN_CERT. If that cert is then trusted with SetCertTrust(cert, +// SERVER_CERT, TRUSTED_SSL), it would become a SERVER_CERT. +enum CertType { + UNKNOWN_CERT, + CA_CERT, + USER_CERT, + SERVER_CERT, + NUM_CERT_TYPES +}; + +} // namespace net + +#endif // NET_CERT_CERT_TYPE_H_ diff --git a/net/cert/cert_verifier.cc b/net/cert/cert_verifier.cc new file mode 100644 index 0000000..e4acec4 --- /dev/null +++ b/net/cert/cert_verifier.cc @@ -0,0 +1,16 @@ +// Copyright (c) 2012 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 "net/cert/cert_verifier.h" + +#include "net/cert/cert_verify_proc.h" +#include "net/cert/multi_threaded_cert_verifier.h" + +namespace net { + +CertVerifier* CertVerifier::CreateDefault() { + return new MultiThreadedCertVerifier(CertVerifyProc::CreateDefault()); +} + +} // namespace net diff --git a/net/cert/cert_verifier.h b/net/cert/cert_verifier.h new file mode 100644 index 0000000..01a9918 --- /dev/null +++ b/net/cert/cert_verifier.h @@ -0,0 +1,113 @@ +// Copyright (c) 2012 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. + +#ifndef NET_CERT_CERT_VERIFIER_H_ +#define NET_CERT_CERT_VERIFIER_H_ + +#include <string> + +#include "base/basictypes.h" +#include "net/base/completion_callback.h" +#include "net/base/net_export.h" + +namespace net { + +class BoundNetLog; +class CertVerifyResult; +class CRLSet; +class X509Certificate; + +// CertVerifier represents a service for verifying certificates. +// +// CertVerifiers can handle multiple requests at a time. A simpler alternative +// for consumers that only have 1 outstanding request at a time is to create a +// SingleRequestCertVerifier wrapper around CertVerifier (which will +// automatically cancel the single request when it goes out of scope). +class NET_EXPORT CertVerifier { + public: + // Opaque pointer type used to cancel outstanding requests. + typedef void* RequestHandle; + + enum VerifyFlags { + // If set, enables online revocation checking via CRLs and OCSP for the + // certificate chain. + VERIFY_REV_CHECKING_ENABLED = 1 << 0, + + // If set, and the certificate being verified may be an EV certificate, + // attempt to verify the certificate according to the EV processing + // guidelines. In order to successfully verify a certificate as EV, + // either an online or offline revocation check must be successfully + // completed. To ensure it's possible to complete a revocation check, + // callers should also specify either VERIFY_REV_CHECKING_ENABLED or + // VERIFY_REV_CHECKING_ENABLED_EV_ONLY (to enable online checks), and + // VERIFY_CERT_IO_ENABLED (to enable network fetches for online checks). + VERIFY_EV_CERT = 1 << 1, + + // If set, permits NSS to use the network when verifying certificates, + // such as to fetch missing intermediates or to check OCSP or CRLs. + // TODO(rsleevi): http://crbug.com/143300 - Define this flag for all + // verification engines with well-defined semantics, rather than being + // NSS only. + VERIFY_CERT_IO_ENABLED = 1 << 2, + + // If set, enables online revocation checking via CRLs or OCSP, but only + // for certificates which may be EV, and only when VERIFY_EV_CERT is also + // set. + VERIFY_REV_CHECKING_ENABLED_EV_ONLY = 1 << 3, + }; + + // When the verifier is destroyed, all certificate verification requests are + // canceled, and their completion callbacks will not be called. + virtual ~CertVerifier() {} + + // Verifies the given certificate against the given hostname as an SSL server. + // Returns OK if successful or an error code upon failure. + // + // The |*verify_result| structure, including the |verify_result->cert_status| + // bitmask, is always filled out regardless of the return value. If the + // certificate has multiple errors, the corresponding status flags are set in + // |verify_result->cert_status|, and the error code for the most serious + // error is returned. + // + // |flags| is bitwise OR'd of VerifyFlags. + // If VERIFY_REV_CHECKING_ENABLED is set in |flags|, certificate revocation + // checking is performed. + // + // If VERIFY_EV_CERT is set in |flags| too, EV certificate verification is + // performed. If |flags| is VERIFY_EV_CERT (that is, + // VERIFY_REV_CHECKING_ENABLED is not set), EV certificate verification will + // not be performed. + // + // |crl_set| points to an optional CRLSet structure which can be used to + // avoid revocation checks over the network. + // + // |callback| must not be null. ERR_IO_PENDING is returned if the operation + // could not be completed synchronously, in which case the result code will + // be passed to the callback when available. + // + // |*out_req| will be filled with a handle to the async request. + // This handle is not valid after the request has completed. + // + // TODO(rsleevi): Move CRLSet* out of the CertVerifier signature. + virtual int Verify(X509Certificate* cert, + const std::string& hostname, + int flags, + CRLSet* crl_set, + CertVerifyResult* verify_result, + const CompletionCallback& callback, + RequestHandle* out_req, + const BoundNetLog& net_log) = 0; + + // Cancels the specified request. |req| is the handle returned by Verify(). + // After a request is canceled, its completion callback will not be called. + virtual void CancelRequest(RequestHandle req) = 0; + + // Creates a CertVerifier implementation that verifies certificates using + // the preferred underlying cryptographic libraries. + static CertVerifier* CreateDefault(); +}; + +} // namespace net + +#endif // NET_CERT_CERT_VERIFIER_H_ diff --git a/net/cert/cert_verify_proc.cc b/net/cert/cert_verify_proc.cc new file mode 100644 index 0000000..a7f56b7 --- /dev/null +++ b/net/cert/cert_verify_proc.cc @@ -0,0 +1,289 @@ +// Copyright (c) 2012 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 "net/cert/cert_verify_proc.h" + +#include "base/metrics/histogram.h" +#include "base/sha1.h" +#include "build/build_config.h" +#include "net/base/net_errors.h" +#include "net/cert/cert_status_flags.h" +#include "net/cert/cert_verifier.h" +#include "net/cert/cert_verify_result.h" +#include "net/cert/crl_set.h" +#include "net/cert/x509_certificate.h" + +#if defined(USE_NSS) || defined(OS_IOS) +#include "net/cert/cert_verify_proc_nss.h" +#elif defined(USE_OPENSSL) && !defined(OS_ANDROID) +#include "net/cert/cert_verify_proc_openssl.h" +#elif defined(OS_ANDROID) +#include "net/cert/cert_verify_proc_android.h" +#elif defined(OS_MACOSX) +#include "net/cert/cert_verify_proc_mac.h" +#elif defined(OS_WIN) +#include "net/cert/cert_verify_proc_win.h" +#else +#error Implement certificate verification. +#endif + + +namespace net { + +namespace { + +// Returns true if |type| is |kPublicKeyTypeRSA| or |kPublicKeyTypeDSA|, and +// if |size_bits| is < 1024. Note that this means there may be false +// negatives: keys for other algorithms and which are weak will pass this +// test. +bool IsWeakKey(X509Certificate::PublicKeyType type, size_t size_bits) { + switch (type) { + case X509Certificate::kPublicKeyTypeRSA: + case X509Certificate::kPublicKeyTypeDSA: + return size_bits < 1024; + default: + return false; + } +} + +} // namespace + +// static +CertVerifyProc* CertVerifyProc::CreateDefault() { +#if defined(USE_NSS) || defined(OS_IOS) + return new CertVerifyProcNSS(); +#elif defined(USE_OPENSSL) && !defined(OS_ANDROID) + return new CertVerifyProcOpenSSL(); +#elif defined(OS_ANDROID) + return new CertVerifyProcAndroid(); +#elif defined(OS_MACOSX) + return new CertVerifyProcMac(); +#elif defined(OS_WIN) + return new CertVerifyProcWin(); +#else + return NULL; +#endif +} + +CertVerifyProc::CertVerifyProc() {} + +CertVerifyProc::~CertVerifyProc() {} + +int CertVerifyProc::Verify(X509Certificate* cert, + const std::string& hostname, + int flags, + CRLSet* crl_set, + const CertificateList& additional_trust_anchors, + CertVerifyResult* verify_result) { + verify_result->Reset(); + verify_result->verified_cert = cert; + + if (IsBlacklisted(cert)) { + verify_result->cert_status |= CERT_STATUS_REVOKED; + return ERR_CERT_REVOKED; + } + + // If EV verification was requested and no CRLSet is present, or if the + // CRLSet has expired, then enable online revocation checks. If the online + // check fails, EV status won't be shown. + // + // TODO(rsleevi): http://crbug.com/142974 - Allow preferences to fully + // disable revocation checking. + if ((flags & CertVerifier::VERIFY_EV_CERT) && + (!crl_set || crl_set->IsExpired())) { + flags |= CertVerifier::VERIFY_REV_CHECKING_ENABLED_EV_ONLY; + } + + int rv = VerifyInternal(cert, hostname, flags, crl_set, + additional_trust_anchors, verify_result); + + // This check is done after VerifyInternal so that VerifyInternal can fill + // in the list of public key hashes. + if (IsPublicKeyBlacklisted(verify_result->public_key_hashes)) { + verify_result->cert_status |= CERT_STATUS_REVOKED; + rv = MapCertStatusToNetError(verify_result->cert_status); + } + + // Check for weak keys in the entire verified chain. + size_t size_bits = 0; + X509Certificate::PublicKeyType type = + X509Certificate::kPublicKeyTypeUnknown; + bool weak_key = false; + + X509Certificate::GetPublicKeyInfo( + verify_result->verified_cert->os_cert_handle(), &size_bits, &type); + if (IsWeakKey(type, size_bits)) { + weak_key = true; + } else { + const X509Certificate::OSCertHandles& intermediates = + verify_result->verified_cert->GetIntermediateCertificates(); + for (size_t i = 0; i < intermediates.size(); ++i) { + X509Certificate::GetPublicKeyInfo(intermediates[i], &size_bits, &type); + if (IsWeakKey(type, size_bits)) + weak_key = true; + } + } + + if (weak_key) { + verify_result->cert_status |= CERT_STATUS_WEAK_KEY; + // Avoid replacing a more serious error, such as an OS/library failure, + // by ensuring that if verification failed, it failed with a certificate + // error. + if (rv == OK || IsCertificateError(rv)) + rv = MapCertStatusToNetError(verify_result->cert_status); + } + + // Treat certificates signed using broken signature algorithms as invalid. + if (verify_result->has_md2 || verify_result->has_md4) { + verify_result->cert_status |= CERT_STATUS_INVALID; + rv = MapCertStatusToNetError(verify_result->cert_status); + } + + // Flag certificates using weak signature algorithms. + if (verify_result->has_md5) { + verify_result->cert_status |= CERT_STATUS_WEAK_SIGNATURE_ALGORITHM; + // Avoid replacing a more serious error, such as an OS/library failure, + // by ensuring that if verification failed, it failed with a certificate + // error. + if (rv == OK || IsCertificateError(rv)) + rv = MapCertStatusToNetError(verify_result->cert_status); + } + + return rv; +} + +// static +bool CertVerifyProc::IsBlacklisted(X509Certificate* cert) { + static const unsigned kComodoSerialBytes = 16; + static const uint8 kComodoSerials[][kComodoSerialBytes] = { + // Not a real certificate. For testing only. + {0x07,0x7a,0x59,0xbc,0xd5,0x34,0x59,0x60,0x1c,0xa6,0x90,0x72,0x67,0xa6,0xdd,0x1c}, + + // The next nine certificates all expire on Fri Mar 14 23:59:59 2014. + // Some serial numbers actually have a leading 0x00 byte required to + // encode a positive integer in DER if the most significant bit is 0. + // We omit the leading 0x00 bytes to make all serial numbers 16 bytes. + + // Subject: CN=mail.google.com + // subjectAltName dNSName: mail.google.com, www.mail.google.com + {0x04,0x7e,0xcb,0xe9,0xfc,0xa5,0x5f,0x7b,0xd0,0x9e,0xae,0x36,0xe1,0x0c,0xae,0x1e}, + // Subject: CN=global trustee + // subjectAltName dNSName: global trustee + // Note: not a CA certificate. + {0xd8,0xf3,0x5f,0x4e,0xb7,0x87,0x2b,0x2d,0xab,0x06,0x92,0xe3,0x15,0x38,0x2f,0xb0}, + // Subject: CN=login.live.com + // subjectAltName dNSName: login.live.com, www.login.live.com + {0xb0,0xb7,0x13,0x3e,0xd0,0x96,0xf9,0xb5,0x6f,0xae,0x91,0xc8,0x74,0xbd,0x3a,0xc0}, + // Subject: CN=addons.mozilla.org + // subjectAltName dNSName: addons.mozilla.org, www.addons.mozilla.org + {0x92,0x39,0xd5,0x34,0x8f,0x40,0xd1,0x69,0x5a,0x74,0x54,0x70,0xe1,0xf2,0x3f,0x43}, + // Subject: CN=login.skype.com + // subjectAltName dNSName: login.skype.com, www.login.skype.com + {0xe9,0x02,0x8b,0x95,0x78,0xe4,0x15,0xdc,0x1a,0x71,0x0a,0x2b,0x88,0x15,0x44,0x47}, + // Subject: CN=login.yahoo.com + // subjectAltName dNSName: login.yahoo.com, www.login.yahoo.com + {0xd7,0x55,0x8f,0xda,0xf5,0xf1,0x10,0x5b,0xb2,0x13,0x28,0x2b,0x70,0x77,0x29,0xa3}, + // Subject: CN=www.google.com + // subjectAltName dNSName: www.google.com, google.com + {0xf5,0xc8,0x6a,0xf3,0x61,0x62,0xf1,0x3a,0x64,0xf5,0x4f,0x6d,0xc9,0x58,0x7c,0x06}, + // Subject: CN=login.yahoo.com + // subjectAltName dNSName: login.yahoo.com + {0x39,0x2a,0x43,0x4f,0x0e,0x07,0xdf,0x1f,0x8a,0xa3,0x05,0xde,0x34,0xe0,0xc2,0x29}, + // Subject: CN=login.yahoo.com + // subjectAltName dNSName: login.yahoo.com + {0x3e,0x75,0xce,0xd4,0x6b,0x69,0x30,0x21,0x21,0x88,0x30,0xae,0x86,0xa8,0x2a,0x71}, + }; + + const std::string& serial_number = cert->serial_number(); + if (!serial_number.empty() && (serial_number[0] & 0x80) != 0) { + // This is a negative serial number, which isn't technically allowed but + // which probably happens. In order to avoid confusing a negative serial + // number with a positive one once the leading zeros have been removed, we + // disregard it. + return false; + } + + base::StringPiece serial(serial_number); + // Remove leading zeros. + while (serial.size() > 1 && serial[0] == 0) + serial.remove_prefix(1); + + if (serial.size() == kComodoSerialBytes) { + for (unsigned i = 0; i < arraysize(kComodoSerials); i++) { + if (memcmp(kComodoSerials[i], serial.data(), kComodoSerialBytes) == 0) { + UMA_HISTOGRAM_ENUMERATION("Net.SSLCertBlacklisted", i, + arraysize(kComodoSerials) + 1); + return true; + } + } + } + + return false; +} + +// static +// NOTE: This implementation assumes and enforces that the hashes are SHA1. +bool CertVerifyProc::IsPublicKeyBlacklisted( + const HashValueVector& public_key_hashes) { + static const unsigned kNumHashes = 10; + static const uint8 kHashes[kNumHashes][base::kSHA1Length] = { + // Subject: CN=DigiNotar Root CA + // Issuer: CN=Entrust.net x2 and self-signed + {0x41, 0x0f, 0x36, 0x36, 0x32, 0x58, 0xf3, 0x0b, 0x34, 0x7d, + 0x12, 0xce, 0x48, 0x63, 0xe4, 0x33, 0x43, 0x78, 0x06, 0xa8}, + // Subject: CN=DigiNotar Cyber CA + // Issuer: CN=GTE CyberTrust Global Root + {0xc4, 0xf9, 0x66, 0x37, 0x16, 0xcd, 0x5e, 0x71, 0xd6, 0x95, + 0x0b, 0x5f, 0x33, 0xce, 0x04, 0x1c, 0x95, 0xb4, 0x35, 0xd1}, + // Subject: CN=DigiNotar Services 1024 CA + // Issuer: CN=Entrust.net + {0xe2, 0x3b, 0x8d, 0x10, 0x5f, 0x87, 0x71, 0x0a, 0x68, 0xd9, + 0x24, 0x80, 0x50, 0xeb, 0xef, 0xc6, 0x27, 0xbe, 0x4c, 0xa6}, + // Subject: CN=DigiNotar PKIoverheid CA Organisatie - G2 + // Issuer: CN=Staat der Nederlanden Organisatie CA - G2 + {0x7b, 0x2e, 0x16, 0xbc, 0x39, 0xbc, 0xd7, 0x2b, 0x45, 0x6e, + 0x9f, 0x05, 0x5d, 0x1d, 0xe6, 0x15, 0xb7, 0x49, 0x45, 0xdb}, + // Subject: CN=DigiNotar PKIoverheid CA Overheid en Bedrijven + // Issuer: CN=Staat der Nederlanden Overheid CA + {0xe8, 0xf9, 0x12, 0x00, 0xc6, 0x5c, 0xee, 0x16, 0xe0, 0x39, + 0xb9, 0xf8, 0x83, 0x84, 0x16, 0x61, 0x63, 0x5f, 0x81, 0xc5}, + // Subject: O=Digicert Sdn. Bhd. + // Issuer: CN=GTE CyberTrust Global Root + // Expires: Jul 17 15:16:54 2012 GMT + {0x01, 0x29, 0xbc, 0xd5, 0xb4, 0x48, 0xae, 0x8d, 0x24, 0x96, + 0xd1, 0xc3, 0xe1, 0x97, 0x23, 0x91, 0x90, 0x88, 0xe1, 0x52}, + // Subject: O=Digicert Sdn. Bhd. + // Issuer: CN=Entrust.net Certification Authority (2048) + // Expires: Jul 16 17:53:37 2015 GMT + {0xd3, 0x3c, 0x5b, 0x41, 0xe4, 0x5c, 0xc4, 0xb3, 0xbe, 0x9a, + 0xd6, 0x95, 0x2c, 0x4e, 0xcc, 0x25, 0x28, 0x03, 0x29, 0x81}, + // Issuer: CN=Trustwave Organization Issuing CA, Level 2 + // Covers two certificates, the latter of which expires Apr 15 21:09:30 + // 2021 GMT. + {0xe1, 0x2d, 0x89, 0xf5, 0x6d, 0x22, 0x76, 0xf8, 0x30, 0xe6, + 0xce, 0xaf, 0xa6, 0x6c, 0x72, 0x5c, 0x0b, 0x41, 0xa9, 0x32}, + // Cyberoam CA certificate. Private key leaked, but this certificate would + // only have been installed by Cyberoam customers. The certificate expires + // in 2036, but we can probably remove in a couple of years (2014). + {0xd9, 0xf5, 0xc6, 0xce, 0x57, 0xff, 0xaa, 0x39, 0xcc, 0x7e, + 0xd1, 0x72, 0xbd, 0x53, 0xe0, 0xd3, 0x07, 0x83, 0x4b, 0xd1}, + // Win32/Sirefef.gen!C generates fake certifciates with this public key. + {0xa4, 0xf5, 0x6e, 0x9e, 0x1d, 0x9a, 0x3b, 0x7b, 0x1a, 0xc3, + 0x31, 0xcf, 0x64, 0xfc, 0x76, 0x2c, 0xd0, 0x51, 0xfb, 0xa4}, + }; + + for (unsigned i = 0; i < kNumHashes; i++) { + for (HashValueVector::const_iterator j = public_key_hashes.begin(); + j != public_key_hashes.end(); ++j) { + if (j->tag == HASH_VALUE_SHA1 && + memcmp(j->data(), kHashes[i], base::kSHA1Length) == 0) { + return true; + } + } + } + + return false; +} + +} // namespace net diff --git a/net/cert/cert_verify_proc.h b/net/cert/cert_verify_proc.h new file mode 100644 index 0000000..c85bf75 --- /dev/null +++ b/net/cert/cert_verify_proc.h @@ -0,0 +1,96 @@ +// Copyright (c) 2012 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. + +#ifndef NET_CERT_CERT_VERIFY_PROC_H_ +#define NET_CERT_CERT_VERIFY_PROC_H_ + +#include <string> +#include <vector> + +#include "base/gtest_prod_util.h" +#include "base/memory/ref_counted.h" +#include "net/base/net_export.h" +#include "net/cert/x509_cert_types.h" + +namespace net { + +class CertVerifyResult; +class CRLSet; +class X509Certificate; +typedef std::vector<scoped_refptr<X509Certificate> > CertificateList; + +// Class to perform certificate path building and verification for various +// certificate uses. All methods of this class must be thread-safe, as they +// may be called from various non-joinable worker threads. +class NET_EXPORT CertVerifyProc + : public base::RefCountedThreadSafe<CertVerifyProc> { + public: + // Creates and returns the default CertVerifyProc. + static CertVerifyProc* CreateDefault(); + + // Verifies the certificate against the given hostname as an SSL server + // certificate. Returns OK if successful or an error code upon failure. + // + // The |*verify_result| structure, including the |verify_result->cert_status| + // bitmask, is always filled out regardless of the return value. If the + // certificate has multiple errors, the corresponding status flags are set in + // |verify_result->cert_status|, and the error code for the most serious + // error is returned. + // + // |flags| is bitwise OR'd of VerifyFlags: + // + // If VERIFY_REV_CHECKING_ENABLED is set in |flags|, online certificate + // revocation checking is performed (i.e. OCSP and downloading CRLs). CRLSet + // based revocation checking is always enabled, regardless of this flag, if + // |crl_set| is given. + // + // If VERIFY_EV_CERT is set in |flags| too, EV certificate verification is + // performed. + // + // |crl_set| points to an optional CRLSet structure which can be used to + // avoid revocation checks over the network. + // + // |additional_trust_anchors| lists certificates that can be trusted when + // building a certificate chain, in addition to the anchors known to the + // implementation. + int Verify(X509Certificate* cert, + const std::string& hostname, + int flags, + CRLSet* crl_set, + const CertificateList& additional_trust_anchors, + CertVerifyResult* verify_result); + + // Returns true if the implementation supports passing additional trust + // anchors to the Verify() call. The |additional_trust_anchors| parameter + // passed to Verify() is ignored when this returns false. + virtual bool SupportsAdditionalTrustAnchors() const = 0; + + protected: + friend class base::RefCountedThreadSafe<CertVerifyProc>; + FRIEND_TEST_ALL_PREFIXES(CertVerifyProcTest, DigiNotarCerts); + + CertVerifyProc(); + virtual ~CertVerifyProc(); + + private: + // Performs the actual verification using the desired underlying + // cryptographic library. + virtual int VerifyInternal(X509Certificate* cert, + const std::string& hostname, + int flags, + CRLSet* crl_set, + const CertificateList& additional_trust_anchors, + CertVerifyResult* verify_result) = 0; + + // Returns true if |cert| is explicitly blacklisted. + static bool IsBlacklisted(X509Certificate* cert); + + // IsPublicKeyBlacklisted returns true iff one of |public_key_hashes| (which + // are hashes of SubjectPublicKeyInfo structures) is explicitly blocked. + static bool IsPublicKeyBlacklisted(const HashValueVector& public_key_hashes); +}; + +} // namespace net + +#endif // NET_CERT_CERT_VERIFY_PROC_H_ diff --git a/net/cert/cert_verify_proc_android.cc b/net/cert/cert_verify_proc_android.cc new file mode 100644 index 0000000..71456e2 --- /dev/null +++ b/net/cert/cert_verify_proc_android.cc @@ -0,0 +1,117 @@ +// Copyright (c) 2012 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 "net/cert/cert_verify_proc_android.h" + +#include <string> +#include <vector> + +#include "base/logging.h" +#include "net/android/cert_verify_result_android.h" +#include "net/android/network_library.h" +#include "net/base/net_errors.h" +#include "net/cert/cert_status_flags.h" +#include "net/cert/cert_verify_result.h" +#include "net/cert/x509_certificate.h" + +namespace net { + +namespace { + +// Returns true if the certificate verification call was successful (regardless +// of its result), i.e. if |verify_result| was set. Otherwise returns false. +bool VerifyFromAndroidTrustManager(const std::vector<std::string>& cert_bytes, + CertVerifyResult* verify_result) { + // TODO(joth): Fetch the authentication type from SSL rather than hardcode. + android::CertVerifyResultAndroid android_result = + android::VerifyX509CertChain(cert_bytes, "RSA"); + switch (android_result) { + case android::VERIFY_FAILED: + return false; + case android::VERIFY_OK: + break; + case android::VERIFY_NO_TRUSTED_ROOT: + verify_result->cert_status |= CERT_STATUS_AUTHORITY_INVALID; + break; + case android::VERIFY_EXPIRED: + case android::VERIFY_NOT_YET_VALID: + verify_result->cert_status |= CERT_STATUS_DATE_INVALID; + break; + case android::VERIFY_UNABLE_TO_PARSE: + verify_result->cert_status |= CERT_STATUS_INVALID; + break; + default: + NOTREACHED(); + verify_result->cert_status |= CERT_STATUS_INVALID; + break; + } + return true; +} + +bool GetChainDEREncodedBytes(X509Certificate* cert, + std::vector<std::string>* chain_bytes) { + X509Certificate::OSCertHandle cert_handle = cert->os_cert_handle(); + X509Certificate::OSCertHandles cert_handles = + cert->GetIntermediateCertificates(); + + // Make sure the peer's own cert is the first in the chain, if it's not + // already there. + if (cert_handles.empty() || cert_handles[0] != cert_handle) + cert_handles.insert(cert_handles.begin(), cert_handle); + + chain_bytes->reserve(cert_handles.size()); + for (X509Certificate::OSCertHandles::const_iterator it = + cert_handles.begin(); it != cert_handles.end(); ++it) { + std::string cert_bytes; + if(!X509Certificate::GetDEREncoded(*it, &cert_bytes)) + return false; + chain_bytes->push_back(cert_bytes); + } + return true; +} + +} // namespace + +CertVerifyProcAndroid::CertVerifyProcAndroid() {} + +CertVerifyProcAndroid::~CertVerifyProcAndroid() {} + +bool CertVerifyProcAndroid::SupportsAdditionalTrustAnchors() const { + return false; +} + +int CertVerifyProcAndroid::VerifyInternal( + X509Certificate* cert, + const std::string& hostname, + int flags, + CRLSet* crl_set, + const CertificateList& additional_trust_anchors, + CertVerifyResult* verify_result) { + if (!cert->VerifyNameMatch(hostname)) + verify_result->cert_status |= CERT_STATUS_COMMON_NAME_INVALID; + + std::vector<std::string> cert_bytes; + if (!GetChainDEREncodedBytes(cert, &cert_bytes)) + return ERR_CERT_INVALID; + if (!VerifyFromAndroidTrustManager(cert_bytes, verify_result)) { + NOTREACHED(); + return ERR_FAILED; + } + if (IsCertStatusError(verify_result->cert_status)) + return MapCertStatusToNetError(verify_result->cert_status); + + // TODO(ppi): Implement missing functionality: yielding the constructed trust + // chain, public key hashes of its certificates and |is_issued_by_known_root| + // flag. All of the above require specific support from the platform, missing + // in the Java APIs. See also: http://crbug.com/116838 + + // Until the required support is available in the platform, we don't know if + // the trust root at the end of the chain was standard or user-added, so we + // mark all correctly verified certificates as issued by a known root. + verify_result->is_issued_by_known_root = true; + + return OK; +} + +} // namespace net diff --git a/net/cert/cert_verify_proc_android.h b/net/cert/cert_verify_proc_android.h new file mode 100644 index 0000000..ca8746b --- /dev/null +++ b/net/cert/cert_verify_proc_android.h @@ -0,0 +1,34 @@ +// Copyright (c) 2012 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. + +#ifndef NET_CERT_CERT_VERIFY_PROC_ANDROID_H_ +#define NET_CERT_CERT_VERIFY_PROC_ANDROID_H_ + +#include "net/cert/cert_verify_proc.h" + +namespace net { + +// Performs certificate verification on Android by calling the platform +// TrustManager through JNI. +class CertVerifyProcAndroid : public CertVerifyProc { + public: + CertVerifyProcAndroid(); + + virtual bool SupportsAdditionalTrustAnchors() const OVERRIDE; + + protected: + virtual ~CertVerifyProcAndroid(); + + private: + virtual int VerifyInternal(X509Certificate* cert, + const std::string& hostname, + int flags, + CRLSet* crl_set, + const CertificateList& additional_trust_anchors, + CertVerifyResult* verify_result) OVERRIDE; +}; + +} // namespace net + +#endif // NET_CERT_CERT_VERIFY_PROC_ANDROID_H_ diff --git a/net/cert/cert_verify_proc_mac.cc b/net/cert/cert_verify_proc_mac.cc new file mode 100644 index 0000000..fe2fb9e --- /dev/null +++ b/net/cert/cert_verify_proc_mac.cc @@ -0,0 +1,598 @@ +// Copyright (c) 2012 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 "net/cert/cert_verify_proc_mac.h" + +#include <CommonCrypto/CommonDigest.h> +#include <CoreServices/CoreServices.h> +#include <Security/Security.h> + +#include <string> +#include <vector> + +#include "base/logging.h" +#include "base/mac/mac_logging.h" +#include "base/mac/scoped_cftyperef.h" +#include "base/sha1.h" +#include "base/string_piece.h" +#include "base/synchronization/lock.h" +#include "crypto/mac_security_services_lock.h" +#include "crypto/nss_util.h" +#include "crypto/sha2.h" +#include "net/base/net_errors.h" +#include "net/cert/asn1_util.h" +#include "net/cert/cert_status_flags.h" +#include "net/cert/cert_verifier.h" +#include "net/cert/cert_verify_result.h" +#include "net/cert/crl_set.h" +#include "net/cert/test_root_certs.h" +#include "net/cert/x509_certificate.h" +#include "net/cert/x509_certificate_known_roots_mac.h" +#include "net/cert/x509_util_mac.h" + +// From 10.7.2 libsecurity_keychain-55035/lib/SecTrustPriv.h, for use with +// SecTrustCopyExtendedResult. +#ifndef kSecEVOrganizationName +#define kSecEVOrganizationName CFSTR("Organization") +#endif + +using base::mac::ScopedCFTypeRef; + +namespace net { + +namespace { + +typedef OSStatus (*SecTrustCopyExtendedResultFuncPtr)(SecTrustRef, + CFDictionaryRef*); + +int NetErrorFromOSStatus(OSStatus status) { + switch (status) { + case noErr: + return OK; + case errSecNotAvailable: + case errSecNoCertificateModule: + case errSecNoPolicyModule: + return ERR_NOT_IMPLEMENTED; + case errSecAuthFailed: + return ERR_ACCESS_DENIED; + default: { + OSSTATUS_LOG(ERROR, status) << "Unknown error mapped to ERR_FAILED"; + return ERR_FAILED; + } + } +} + +CertStatus CertStatusFromOSStatus(OSStatus status) { + switch (status) { + case noErr: + return 0; + + case CSSMERR_TP_INVALID_ANCHOR_CERT: + case CSSMERR_TP_NOT_TRUSTED: + case CSSMERR_TP_INVALID_CERT_AUTHORITY: + return CERT_STATUS_AUTHORITY_INVALID; + + case CSSMERR_TP_CERT_EXPIRED: + case CSSMERR_TP_CERT_NOT_VALID_YET: + // "Expired" and "not yet valid" collapse into a single status. + return CERT_STATUS_DATE_INVALID; + + case CSSMERR_TP_CERT_REVOKED: + case CSSMERR_TP_CERT_SUSPENDED: + return CERT_STATUS_REVOKED; + + case CSSMERR_APPLETP_HOSTNAME_MISMATCH: + return CERT_STATUS_COMMON_NAME_INVALID; + + case CSSMERR_APPLETP_CRL_NOT_FOUND: + case CSSMERR_APPLETP_OCSP_UNAVAILABLE: + case CSSMERR_APPLETP_INCOMPLETE_REVOCATION_CHECK: + return CERT_STATUS_NO_REVOCATION_MECHANISM; + + case CSSMERR_APPLETP_CRL_EXPIRED: + case CSSMERR_APPLETP_CRL_NOT_VALID_YET: + case CSSMERR_APPLETP_CRL_SERVER_DOWN: + case CSSMERR_APPLETP_CRL_NOT_TRUSTED: + case CSSMERR_APPLETP_CRL_INVALID_ANCHOR_CERT: + case CSSMERR_APPLETP_CRL_POLICY_FAIL: + case CSSMERR_APPLETP_OCSP_BAD_RESPONSE: + case CSSMERR_APPLETP_OCSP_BAD_REQUEST: + case CSSMERR_APPLETP_OCSP_STATUS_UNRECOGNIZED: + case CSSMERR_APPLETP_NETWORK_FAILURE: + case CSSMERR_APPLETP_OCSP_NOT_TRUSTED: + case CSSMERR_APPLETP_OCSP_INVALID_ANCHOR_CERT: + case CSSMERR_APPLETP_OCSP_SIG_ERROR: + case CSSMERR_APPLETP_OCSP_NO_SIGNER: + case CSSMERR_APPLETP_OCSP_RESP_MALFORMED_REQ: + case CSSMERR_APPLETP_OCSP_RESP_INTERNAL_ERR: + case CSSMERR_APPLETP_OCSP_RESP_TRY_LATER: + case CSSMERR_APPLETP_OCSP_RESP_SIG_REQUIRED: + case CSSMERR_APPLETP_OCSP_RESP_UNAUTHORIZED: + case CSSMERR_APPLETP_OCSP_NONCE_MISMATCH: + // We asked for a revocation check, but didn't get it. + return CERT_STATUS_UNABLE_TO_CHECK_REVOCATION; + + case CSSMERR_APPLETP_CRL_BAD_URI: + case CSSMERR_APPLETP_IDP_FAIL: + return CERT_STATUS_INVALID; + + case CSSMERR_CSP_UNSUPPORTED_KEY_SIZE: + // Mapping UNSUPPORTED_KEY_SIZE to CERT_STATUS_WEAK_KEY is not strictly + // accurate, as the error may have been returned due to a key size + // that exceeded the maximum supported. However, within + // CertVerifyProcMac::VerifyInternal(), this code should only be + // encountered as a certificate status code, and only when the key size + // is smaller than the minimum required (1024 bits). + return CERT_STATUS_WEAK_KEY; + + default: { + // Failure was due to something Chromium doesn't define a + // specific status for (such as basic constraints violation, or + // unknown critical extension) + OSSTATUS_LOG(WARNING, status) + << "Unknown error mapped to CERT_STATUS_INVALID"; + return CERT_STATUS_INVALID; + } + } +} + +// Creates a series of SecPolicyRefs to be added to a SecTrustRef used to +// validate a certificate for an SSL server. |hostname| contains the name of +// the SSL server that the certificate should be verified against. |flags| is +// a bitwise-OR of VerifyFlags that can further alter how trust is validated, +// such as how revocation is checked. If successful, returns noErr, and +// stores the resultant array of SecPolicyRefs in |policies|. +OSStatus CreateTrustPolicies(const std::string& hostname, + int flags, + ScopedCFTypeRef<CFArrayRef>* policies) { + ScopedCFTypeRef<CFMutableArrayRef> local_policies( + CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks)); + if (!local_policies) + return memFullErr; + + SecPolicyRef ssl_policy; + OSStatus status = x509_util::CreateSSLServerPolicy(hostname, &ssl_policy); + if (status) + return status; + CFArrayAppendValue(local_policies, ssl_policy); + CFRelease(ssl_policy); + + // Explicitly add revocation policies, in order to override system + // revocation checking policies and instead respect the application-level + // revocation preference. + status = x509_util::CreateRevocationPolicies( + (flags & CertVerifier::VERIFY_REV_CHECKING_ENABLED), + (flags & CertVerifier::VERIFY_REV_CHECKING_ENABLED_EV_ONLY), + local_policies); + if (status) + return status; + + policies->reset(local_policies.release()); + return noErr; +} + +// Saves some information about the certificate chain |cert_chain| in +// |*verify_result|. The caller MUST initialize |*verify_result| before +// calling this function. +void GetCertChainInfo(CFArrayRef cert_chain, + CSSM_TP_APPLE_EVIDENCE_INFO* chain_info, + CertVerifyResult* verify_result) { + SecCertificateRef verified_cert = NULL; + std::vector<SecCertificateRef> verified_chain; + for (CFIndex i = 0, count = CFArrayGetCount(cert_chain); i < count; ++i) { + SecCertificateRef chain_cert = reinterpret_cast<SecCertificateRef>( + const_cast<void*>(CFArrayGetValueAtIndex(cert_chain, i))); + if (i == 0) { + verified_cert = chain_cert; + } else { + verified_chain.push_back(chain_cert); + } + + if ((chain_info[i].StatusBits & CSSM_CERT_STATUS_IS_IN_ANCHORS) || + (chain_info[i].StatusBits & CSSM_CERT_STATUS_IS_ROOT)) { + // The current certificate is either in the user's trusted store or is + // a root (self-signed) certificate. Ignore the signature algorithm for + // these certificates, as it is meaningless for security. We allow + // self-signed certificates (i == 0 & IS_ROOT), since we accept that + // any security assertions by such a cert are inherently meaningless. + continue; + } + + x509_util::CSSMCachedCertificate cached_cert; + OSStatus status = cached_cert.Init(chain_cert); + if (status) + continue; + x509_util::CSSMFieldValue signature_field; + status = cached_cert.GetField(&CSSMOID_X509V1SignatureAlgorithm, + &signature_field); + if (status || !signature_field.field()) + continue; + // Match the behaviour of OS X system tools and defensively check that + // sizes are appropriate. This would indicate a critical failure of the + // OS X certificate library, but based on history, it is best to play it + // safe. + const CSSM_X509_ALGORITHM_IDENTIFIER* sig_algorithm = + signature_field.GetAs<CSSM_X509_ALGORITHM_IDENTIFIER>(); + if (!sig_algorithm) + continue; + + const CSSM_OID* alg_oid = &sig_algorithm->algorithm; + if (CSSMOIDEqual(alg_oid, &CSSMOID_MD2WithRSA)) { + verify_result->has_md2 = true; + if (i != 0) + verify_result->has_md2_ca = true; + } else if (CSSMOIDEqual(alg_oid, &CSSMOID_MD4WithRSA)) { + verify_result->has_md4 = true; + } else if (CSSMOIDEqual(alg_oid, &CSSMOID_MD5WithRSA)) { + verify_result->has_md5 = true; + if (i != 0) + verify_result->has_md5_ca = true; + } + } + if (!verified_cert) + return; + + verify_result->verified_cert = + X509Certificate::CreateFromHandle(verified_cert, verified_chain); +} + +void AppendPublicKeyHashes(CFArrayRef chain, + HashValueVector* hashes) { + const CFIndex n = CFArrayGetCount(chain); + for (CFIndex i = 0; i < n; i++) { + SecCertificateRef cert = reinterpret_cast<SecCertificateRef>( + const_cast<void*>(CFArrayGetValueAtIndex(chain, i))); + + CSSM_DATA cert_data; + OSStatus err = SecCertificateGetData(cert, &cert_data); + DCHECK_EQ(err, noErr); + base::StringPiece der_bytes(reinterpret_cast<const char*>(cert_data.Data), + cert_data.Length); + base::StringPiece spki_bytes; + if (!asn1::ExtractSPKIFromDERCert(der_bytes, &spki_bytes)) + continue; + + HashValue sha1(HASH_VALUE_SHA1); + CC_SHA1(spki_bytes.data(), spki_bytes.size(), sha1.data()); + hashes->push_back(sha1); + + HashValue sha256(HASH_VALUE_SHA256); + CC_SHA256(spki_bytes.data(), spki_bytes.size(), sha256.data()); + hashes->push_back(sha256); + } +} + +bool CheckRevocationWithCRLSet(CFArrayRef chain, CRLSet* crl_set) { + if (CFArrayGetCount(chain) == 0) + return true; + + // We iterate from the root certificate down to the leaf, keeping track of + // the issuer's SPKI at each step. + std::string issuer_spki_hash; + for (CFIndex i = CFArrayGetCount(chain) - 1; i >= 0; i--) { + SecCertificateRef cert = reinterpret_cast<SecCertificateRef>( + const_cast<void*>(CFArrayGetValueAtIndex(chain, i))); + + CSSM_DATA cert_data; + OSStatus err = SecCertificateGetData(cert, &cert_data); + if (err != noErr) { + NOTREACHED(); + continue; + } + base::StringPiece der_bytes(reinterpret_cast<const char*>(cert_data.Data), + cert_data.Length); + base::StringPiece spki; + if (!asn1::ExtractSPKIFromDERCert(der_bytes, &spki)) { + NOTREACHED(); + continue; + } + + const std::string spki_hash = crypto::SHA256HashString(spki); + x509_util::CSSMCachedCertificate cached_cert; + if (cached_cert.Init(cert) != CSSM_OK) { + NOTREACHED(); + continue; + } + x509_util::CSSMFieldValue serial_number; + err = cached_cert.GetField(&CSSMOID_X509V1SerialNumber, &serial_number); + if (err || !serial_number.field()) { + NOTREACHED(); + continue; + } + + base::StringPiece serial( + reinterpret_cast<const char*>(serial_number.field()->Data), + serial_number.field()->Length); + + CRLSet::Result result = crl_set->CheckSPKI(spki_hash); + + if (result != CRLSet::REVOKED && !issuer_spki_hash.empty()) + result = crl_set->CheckSerial(serial, issuer_spki_hash); + + issuer_spki_hash = spki_hash; + + switch (result) { + case CRLSet::REVOKED: + return false; + case CRLSet::UNKNOWN: + case CRLSet::GOOD: + continue; + default: + NOTREACHED(); + return false; + } + } + + return true; +} + +// IsIssuedByKnownRoot returns true if the given chain is rooted at a root CA +// that we recognise as a standard root. +// static +bool IsIssuedByKnownRoot(CFArrayRef chain) { + int n = CFArrayGetCount(chain); + if (n < 1) + return false; + SecCertificateRef root_ref = reinterpret_cast<SecCertificateRef>( + const_cast<void*>(CFArrayGetValueAtIndex(chain, n - 1))); + SHA1HashValue hash = X509Certificate::CalculateFingerprint(root_ref); + return IsSHA1HashInSortedArray( + hash, &kKnownRootCertSHA1Hashes[0][0], sizeof(kKnownRootCertSHA1Hashes)); +} + +} // namespace + +CertVerifyProcMac::CertVerifyProcMac() {} + +CertVerifyProcMac::~CertVerifyProcMac() {} + +bool CertVerifyProcMac::SupportsAdditionalTrustAnchors() const { + return false; +} + +int CertVerifyProcMac::VerifyInternal( + X509Certificate* cert, + const std::string& hostname, + int flags, + CRLSet* crl_set, + const CertificateList& additional_trust_anchors, + CertVerifyResult* verify_result) { + ScopedCFTypeRef<CFArrayRef> trust_policies; + OSStatus status = CreateTrustPolicies(hostname, flags, &trust_policies); + if (status) + return NetErrorFromOSStatus(status); + + // Create and configure a SecTrustRef, which takes our certificate(s) + // and our SSL SecPolicyRef. SecTrustCreateWithCertificates() takes an + // array of certificates, the first of which is the certificate we're + // verifying, and the subsequent (optional) certificates are used for + // chain building. + ScopedCFTypeRef<CFArrayRef> cert_array(cert->CreateOSCertChainForCert()); + + // Serialize all calls that may use the Keychain, to work around various + // issues in OS X 10.6+ with multi-threaded access to Security.framework. + base::AutoLock lock(crypto::GetMacSecurityServicesLock()); + + SecTrustRef trust_ref = NULL; + status = SecTrustCreateWithCertificates(cert_array, trust_policies, + &trust_ref); + if (status) + return NetErrorFromOSStatus(status); + ScopedCFTypeRef<SecTrustRef> scoped_trust_ref(trust_ref); + + if (TestRootCerts::HasInstance()) { + status = TestRootCerts::GetInstance()->FixupSecTrustRef(trust_ref); + if (status) + return NetErrorFromOSStatus(status); + } + + CSSM_APPLE_TP_ACTION_DATA tp_action_data; + memset(&tp_action_data, 0, sizeof(tp_action_data)); + tp_action_data.Version = CSSM_APPLE_TP_ACTION_VERSION; + // Allow CSSM to download any missing intermediate certificates if an + // authorityInfoAccess extension or issuerAltName extension is present. + tp_action_data.ActionFlags = CSSM_TP_ACTION_FETCH_CERT_FROM_NET | + CSSM_TP_ACTION_TRUST_SETTINGS; + + // Note: For EV certificates, the Apple TP will handle setting these flags + // as part of EV evaluation. + if (flags & CertVerifier::VERIFY_REV_CHECKING_ENABLED) { + // Require a positive result from an OCSP responder or a CRL (or both) + // for every certificate in the chain. The Apple TP automatically + // excludes the self-signed root from this requirement. If a certificate + // is missing both a crlDistributionPoints extension and an + // authorityInfoAccess extension with an OCSP responder URL, then we + // will get a kSecTrustResultRecoverableTrustFailure back from + // SecTrustEvaluate(), with a + // CSSMERR_APPLETP_INCOMPLETE_REVOCATION_CHECK error code. In that case, + // we'll set our own result to include + // CERT_STATUS_NO_REVOCATION_MECHANISM. If one or both extensions are + // present, and a check fails (server unavailable, OCSP retry later, + // signature mismatch), then we'll set our own result to include + // CERT_STATUS_UNABLE_TO_CHECK_REVOCATION. + tp_action_data.ActionFlags |= CSSM_TP_ACTION_REQUIRE_REV_PER_CERT; + verify_result->cert_status |= CERT_STATUS_REV_CHECKING_ENABLED; + + // Note, even if revocation checking is disabled, SecTrustEvaluate() will + // modify the OCSP options so as to attempt OCSP checking if it believes a + // certificate may chain to an EV root. However, because network fetches + // are disabled in CreateTrustPolicies() when revocation checking is + // disabled, these will only go against the local cache. + } + + CFDataRef action_data_ref = + CFDataCreateWithBytesNoCopy(kCFAllocatorDefault, + reinterpret_cast<UInt8*>(&tp_action_data), + sizeof(tp_action_data), kCFAllocatorNull); + if (!action_data_ref) + return ERR_OUT_OF_MEMORY; + ScopedCFTypeRef<CFDataRef> scoped_action_data_ref(action_data_ref); + status = SecTrustSetParameters(trust_ref, CSSM_TP_ACTION_DEFAULT, + action_data_ref); + if (status) + return NetErrorFromOSStatus(status); + + // Verify the certificate. A non-zero result from SecTrustGetResult() + // indicates that some fatal error occurred and the chain couldn't be + // processed, not that the chain contains no errors. We need to examine the + // output of SecTrustGetResult() to determine that. + SecTrustResultType trust_result; + status = SecTrustEvaluate(trust_ref, &trust_result); + if (status) + return NetErrorFromOSStatus(status); + CFArrayRef completed_chain = NULL; + CSSM_TP_APPLE_EVIDENCE_INFO* chain_info; + status = SecTrustGetResult(trust_ref, &trust_result, &completed_chain, + &chain_info); + if (status) + return NetErrorFromOSStatus(status); + ScopedCFTypeRef<CFArrayRef> scoped_completed_chain(completed_chain); + + if (crl_set && !CheckRevocationWithCRLSet(completed_chain, crl_set)) + verify_result->cert_status |= CERT_STATUS_REVOKED; + + GetCertChainInfo(scoped_completed_chain.get(), chain_info, verify_result); + + // As of Security Update 2012-002/OS X 10.7.4, when an RSA key < 1024 bits + // is encountered, CSSM returns CSSMERR_TP_VERIFY_ACTION_FAILED and adds + // CSSMERR_CSP_UNSUPPORTED_KEY_SIZE as a certificate status. Avoid mapping + // the CSSMERR_TP_VERIFY_ACTION_FAILED to CERT_STATUS_INVALID if the only + // error was due to an unsupported key size. + bool policy_failed = false; + bool weak_key = false; + + // Evaluate the results + OSStatus cssm_result; + switch (trust_result) { + case kSecTrustResultUnspecified: + case kSecTrustResultProceed: + // Certificate chain is valid and trusted ("unspecified" indicates that + // the user has not explicitly set a trust setting) + break; + + case kSecTrustResultDeny: + case kSecTrustResultConfirm: + // Certificate chain is explicitly untrusted. For kSecTrustResultConfirm, + // we're following what Secure Transport does and treating it as + // "deny". + verify_result->cert_status |= CERT_STATUS_AUTHORITY_INVALID; + break; + + case kSecTrustResultRecoverableTrustFailure: + // Certificate chain has a failure that can be overridden by the user. + status = SecTrustGetCssmResultCode(trust_ref, &cssm_result); + if (status) + return NetErrorFromOSStatus(status); + if (cssm_result == CSSMERR_TP_VERIFY_ACTION_FAILED) { + policy_failed = true; + } else { + verify_result->cert_status |= CertStatusFromOSStatus(cssm_result); + } + // Walk the chain of error codes in the CSSM_TP_APPLE_EVIDENCE_INFO + // structure which can catch multiple errors from each certificate. + for (CFIndex index = 0, chain_count = CFArrayGetCount(completed_chain); + index < chain_count; ++index) { + if (chain_info[index].StatusBits & CSSM_CERT_STATUS_EXPIRED || + chain_info[index].StatusBits & CSSM_CERT_STATUS_NOT_VALID_YET) + verify_result->cert_status |= CERT_STATUS_DATE_INVALID; + if (!IsCertStatusError(verify_result->cert_status) && + chain_info[index].NumStatusCodes == 0) { + LOG(WARNING) << "chain_info[" << index << "].NumStatusCodes is 0" + ", chain_info[" << index << "].StatusBits is " + << chain_info[index].StatusBits; + } + for (uint32 status_code_index = 0; + status_code_index < chain_info[index].NumStatusCodes; + ++status_code_index) { + CertStatus mapped_status = CertStatusFromOSStatus( + chain_info[index].StatusCodes[status_code_index]); + if (mapped_status == CERT_STATUS_WEAK_KEY) + weak_key = true; + verify_result->cert_status |= mapped_status; + } + } + if (policy_failed && !weak_key) { + // If CSSMERR_TP_VERIFY_ACTION_FAILED wasn't returned due to a weak + // key, map it back to an appropriate error code. + verify_result->cert_status |= CertStatusFromOSStatus(cssm_result); + } + if (!IsCertStatusError(verify_result->cert_status)) { + LOG(ERROR) << "cssm_result=" << cssm_result; + verify_result->cert_status |= CERT_STATUS_INVALID; + NOTREACHED(); + } + break; + + default: + status = SecTrustGetCssmResultCode(trust_ref, &cssm_result); + if (status) + return NetErrorFromOSStatus(status); + verify_result->cert_status |= CertStatusFromOSStatus(cssm_result); + if (!IsCertStatusError(verify_result->cert_status)) { + LOG(WARNING) << "trust_result=" << trust_result; + verify_result->cert_status |= CERT_STATUS_INVALID; + } + break; + } + + // Perform hostname verification independent of SecTrustEvaluate. In order to + // do so, mask off any reported name errors first. + verify_result->cert_status &= ~CERT_STATUS_COMMON_NAME_INVALID; + if (!cert->VerifyNameMatch(hostname)) + verify_result->cert_status |= CERT_STATUS_COMMON_NAME_INVALID; + + // TODO(wtc): Suppress CERT_STATUS_NO_REVOCATION_MECHANISM for now to be + // compatible with Windows, which in turn implements this behavior to be + // compatible with WinHTTP, which doesn't report this error (bug 3004). + verify_result->cert_status &= ~CERT_STATUS_NO_REVOCATION_MECHANISM; + + AppendPublicKeyHashes(completed_chain, &verify_result->public_key_hashes); + verify_result->is_issued_by_known_root = IsIssuedByKnownRoot(completed_chain); + + if (IsCertStatusError(verify_result->cert_status)) + return MapCertStatusToNetError(verify_result->cert_status); + + if (flags & CertVerifier::VERIFY_EV_CERT) { + // Determine the certificate's EV status using SecTrustCopyExtendedResult(), + // which we need to look up because the function wasn't added until + // Mac OS X 10.5.7. + // Note: "ExtendedResult" means extended validation results. + CFBundleRef bundle = + CFBundleGetBundleWithIdentifier(CFSTR("com.apple.security")); + if (bundle) { + SecTrustCopyExtendedResultFuncPtr copy_extended_result = + reinterpret_cast<SecTrustCopyExtendedResultFuncPtr>( + CFBundleGetFunctionPointerForName(bundle, + CFSTR("SecTrustCopyExtendedResult"))); + if (copy_extended_result) { + CFDictionaryRef ev_dict_temp = NULL; + status = copy_extended_result(trust_ref, &ev_dict_temp); + ScopedCFTypeRef<CFDictionaryRef> ev_dict(ev_dict_temp); + ev_dict_temp = NULL; + if (status == noErr && ev_dict) { + // In 10.7.3, SecTrustCopyExtendedResult returns noErr and populates + // ev_dict even for non-EV certificates, but only EV certificates + // will cause ev_dict to contain kSecEVOrganizationName. In previous + // releases, SecTrustCopyExtendedResult would only return noErr and + // populate ev_dict for EV certificates, but would always include + // kSecEVOrganizationName in that case, so checking for this key is + // appropriate for all known versions of SecTrustCopyExtendedResult. + // The actual organization name is unneeded here and can be accessed + // through other means. All that matters here is the OS' conception + // of whether or not the certificate is EV. + if (CFDictionaryContainsKey(ev_dict, + kSecEVOrganizationName)) { + verify_result->cert_status |= CERT_STATUS_IS_EV; + if (flags & CertVerifier::VERIFY_REV_CHECKING_ENABLED_EV_ONLY) + verify_result->cert_status |= CERT_STATUS_REV_CHECKING_ENABLED; + } + } + } + } + } + + return OK; +} + +} // namespace net diff --git a/net/cert/cert_verify_proc_mac.h b/net/cert/cert_verify_proc_mac.h new file mode 100644 index 0000000..cb55767 --- /dev/null +++ b/net/cert/cert_verify_proc_mac.h @@ -0,0 +1,34 @@ +// Copyright (c) 2012 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. + +#ifndef NET_CERT_CERT_VERIFY_PROC_MAC_H_ +#define NET_CERT_CERT_VERIFY_PROC_MAC_H_ + +#include "net/cert/cert_verify_proc.h" + +namespace net { + +// Performs certificate path construction and validation using OS X's +// Security.framework. +class CertVerifyProcMac : public CertVerifyProc { + public: + CertVerifyProcMac(); + + virtual bool SupportsAdditionalTrustAnchors() const OVERRIDE; + + protected: + virtual ~CertVerifyProcMac(); + + private: + virtual int VerifyInternal(X509Certificate* cert, + const std::string& hostname, + int flags, + CRLSet* crl_set, + const CertificateList& additional_trust_anchors, + CertVerifyResult* verify_result) OVERRIDE; +}; + +} // namespace net + +#endif // NET_CERT_CERT_VERIFY_PROC_MAC_H_ diff --git a/net/cert/cert_verify_proc_nss.cc b/net/cert/cert_verify_proc_nss.cc new file mode 100644 index 0000000..8a30970 --- /dev/null +++ b/net/cert/cert_verify_proc_nss.cc @@ -0,0 +1,881 @@ +// Copyright (c) 2012 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 "net/cert/cert_verify_proc_nss.h" + +#include <string> +#include <vector> + +#include <cert.h> +#include <nss.h> +#include <prerror.h> +#include <secerr.h> +#include <sechash.h> +#include <sslerr.h> + +#include "base/logging.h" +#include "crypto/nss_util.h" +#include "crypto/scoped_nss_types.h" +#include "crypto/sha2.h" +#include "net/base/net_errors.h" +#include "net/cert/asn1_util.h" +#include "net/cert/cert_status_flags.h" +#include "net/cert/cert_verifier.h" +#include "net/cert/cert_verify_result.h" +#include "net/cert/crl_set.h" +#include "net/cert/ev_root_ca_metadata.h" +#include "net/cert/x509_certificate.h" +#include "net/cert/x509_util_nss.h" + +#if defined(OS_IOS) +#include <CommonCrypto/CommonDigest.h> +#include "net/cert/x509_util_ios.h" +#endif // defined(OS_IOS) + +#define NSS_VERSION_NUM (NSS_VMAJOR * 10000 + NSS_VMINOR * 100 + NSS_VPATCH) +#if NSS_VERSION_NUM < 31305 +// Added in NSS 3.13.5. +#define SEC_ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED -8016 +#endif + +#if NSS_VERSION_NUM < 31402 +// Added in NSS 3.14.2. +#define cert_pi_useOnlyTrustAnchors static_cast<CERTValParamInType>(14) +#endif + +namespace net { + +namespace { + +typedef scoped_ptr_malloc< + CERTCertificatePolicies, + crypto::NSSDestroyer<CERTCertificatePolicies, + CERT_DestroyCertificatePoliciesExtension> > + ScopedCERTCertificatePolicies; + +typedef scoped_ptr_malloc< + CERTCertList, + crypto::NSSDestroyer<CERTCertList, CERT_DestroyCertList> > + ScopedCERTCertList; + +// ScopedCERTValOutParam manages destruction of values in the CERTValOutParam +// array that cvout points to. cvout must be initialized as passed to +// CERT_PKIXVerifyCert, so that the array must be terminated with +// cert_po_end type. +// When it goes out of scope, it destroys values of cert_po_trustAnchor +// and cert_po_certList types, but doesn't release the array itself. +class ScopedCERTValOutParam { + public: + explicit ScopedCERTValOutParam(CERTValOutParam* cvout) + : cvout_(cvout) {} + + ~ScopedCERTValOutParam() { + if (cvout_ == NULL) + return; + for (CERTValOutParam *p = cvout_; p->type != cert_po_end; p++) { + switch (p->type) { + case cert_po_trustAnchor: + if (p->value.pointer.cert) { + CERT_DestroyCertificate(p->value.pointer.cert); + p->value.pointer.cert = NULL; + } + break; + case cert_po_certList: + if (p->value.pointer.chain) { + CERT_DestroyCertList(p->value.pointer.chain); + p->value.pointer.chain = NULL; + } + break; + default: + break; + } + } + } + + private: + CERTValOutParam* cvout_; + + DISALLOW_COPY_AND_ASSIGN(ScopedCERTValOutParam); +}; + +// Map PORT_GetError() return values to our network error codes. +int MapSecurityError(int err) { + switch (err) { + case PR_DIRECTORY_LOOKUP_ERROR: // DNS lookup error. + return ERR_NAME_NOT_RESOLVED; + case SEC_ERROR_INVALID_ARGS: + return ERR_INVALID_ARGUMENT; + case SSL_ERROR_BAD_CERT_DOMAIN: + return ERR_CERT_COMMON_NAME_INVALID; + case SEC_ERROR_INVALID_TIME: + case SEC_ERROR_EXPIRED_CERTIFICATE: + case SEC_ERROR_EXPIRED_ISSUER_CERTIFICATE: + return ERR_CERT_DATE_INVALID; + case SEC_ERROR_UNKNOWN_ISSUER: + case SEC_ERROR_UNTRUSTED_ISSUER: + case SEC_ERROR_CA_CERT_INVALID: + return ERR_CERT_AUTHORITY_INVALID; + // TODO(port): map ERR_CERT_NO_REVOCATION_MECHANISM. + case SEC_ERROR_OCSP_BAD_HTTP_RESPONSE: + case SEC_ERROR_OCSP_SERVER_ERROR: + return ERR_CERT_UNABLE_TO_CHECK_REVOCATION; + case SEC_ERROR_REVOKED_CERTIFICATE: + case SEC_ERROR_UNTRUSTED_CERT: // Treat as revoked. + return ERR_CERT_REVOKED; + case SEC_ERROR_BAD_DER: + case SEC_ERROR_BAD_SIGNATURE: + case SEC_ERROR_CERT_NOT_VALID: + // TODO(port): add an ERR_CERT_WRONG_USAGE error code. + case SEC_ERROR_CERT_USAGES_INVALID: + case SEC_ERROR_INADEQUATE_KEY_USAGE: // Key usage. + case SEC_ERROR_INADEQUATE_CERT_TYPE: // Extended key usage and whether + // the certificate is a CA. + case SEC_ERROR_POLICY_VALIDATION_FAILED: + case SEC_ERROR_CERT_NOT_IN_NAME_SPACE: + case SEC_ERROR_PATH_LEN_CONSTRAINT_INVALID: + case SEC_ERROR_UNKNOWN_CRITICAL_EXTENSION: + case SEC_ERROR_EXTENSION_VALUE_INVALID: + return ERR_CERT_INVALID; + case SEC_ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED: + return ERR_CERT_WEAK_SIGNATURE_ALGORITHM; + default: + LOG(WARNING) << "Unknown error " << err << " mapped to net::ERR_FAILED"; + return ERR_FAILED; + } +} + +// Map PORT_GetError() return values to our cert status flags. +CertStatus MapCertErrorToCertStatus(int err) { + int net_error = MapSecurityError(err); + return MapNetErrorToCertStatus(net_error); +} + +// Saves some information about the certificate chain cert_list in +// *verify_result. The caller MUST initialize *verify_result before calling +// this function. +// Note that cert_list[0] is the end entity certificate. +void GetCertChainInfo(CERTCertList* cert_list, + CERTCertificate* root_cert, + CertVerifyResult* verify_result) { + // NOTE: Using a NSS library before 3.12.3.1 will crash below. To see the + // NSS version currently in use: + // 1. use ldd on the chrome executable for NSS's location (ie. libnss3.so*) + // 2. use ident libnss3.so* for the library's version + DCHECK(cert_list); + + CERTCertificate* verified_cert = NULL; + std::vector<CERTCertificate*> verified_chain; + int i = 0; + for (CERTCertListNode* node = CERT_LIST_HEAD(cert_list); + !CERT_LIST_END(node, cert_list); + node = CERT_LIST_NEXT(node), ++i) { + if (i == 0) { + verified_cert = node->cert; + } else { + // Because of an NSS bug, CERT_PKIXVerifyCert may chain a self-signed + // certificate of a root CA to another certificate of the same root CA + // key. Detect that error and ignore the root CA certificate. + // See https://bugzilla.mozilla.org/show_bug.cgi?id=721288. + if (node->cert->isRoot) { + // NOTE: isRoot doesn't mean the certificate is a trust anchor. It + // means the certificate is self-signed. Here we assume isRoot only + // implies the certificate is self-issued. + CERTCertListNode* next_node = CERT_LIST_NEXT(node); + CERTCertificate* next_cert; + if (!CERT_LIST_END(next_node, cert_list)) { + next_cert = next_node->cert; + } else { + next_cert = root_cert; + } + // Test that |node->cert| is actually a self-signed certificate + // whose key is equal to |next_cert|, and not a self-issued + // certificate signed by another key of the same CA. + if (next_cert && SECITEM_ItemsAreEqual(&node->cert->derPublicKey, + &next_cert->derPublicKey)) { + continue; + } + } + verified_chain.push_back(node->cert); + } + + SECAlgorithmID& signature = node->cert->signature; + SECOidTag oid_tag = SECOID_FindOIDTag(&signature.algorithm); + switch (oid_tag) { + case SEC_OID_PKCS1_MD5_WITH_RSA_ENCRYPTION: + verify_result->has_md5 = true; + if (i != 0) + verify_result->has_md5_ca = true; + break; + case SEC_OID_PKCS1_MD2_WITH_RSA_ENCRYPTION: + verify_result->has_md2 = true; + if (i != 0) + verify_result->has_md2_ca = true; + break; + case SEC_OID_PKCS1_MD4_WITH_RSA_ENCRYPTION: + verify_result->has_md4 = true; + break; + default: + break; + } + } + + if (root_cert) + verified_chain.push_back(root_cert); +#if defined(OS_IOS) + verify_result->verified_cert = + x509_util_ios::CreateCertFromNSSHandles(verified_cert, verified_chain); +#else + verify_result->verified_cert = + X509Certificate::CreateFromHandle(verified_cert, verified_chain); +#endif // defined(OS_IOS) +} + +// IsKnownRoot returns true if the given certificate is one that we believe +// is a standard (as opposed to user-installed) root. +bool IsKnownRoot(CERTCertificate* root) { + if (!root || !root->slot) + return false; + + // This magic name is taken from + // http://bonsai.mozilla.org/cvsblame.cgi?file=mozilla/security/nss/lib/ckfw/builtins/constants.c&rev=1.13&mark=86,89#79 + return 0 == strcmp(PK11_GetSlotName(root->slot), + "NSS Builtin Objects"); +} + +// Returns true if the given certificate is one of the additional trust anchors. +bool IsAdditionalTrustAnchor(CERTCertList* additional_trust_anchors, + CERTCertificate* root) { + if (!additional_trust_anchors || !root) + return false; + for (CERTCertListNode* node = CERT_LIST_HEAD(additional_trust_anchors); + !CERT_LIST_END(node, additional_trust_anchors); + node = CERT_LIST_NEXT(node)) { + if (CERT_CompareCerts(node->cert, root)) + return true; + } + return false; +} + +enum CRLSetResult { + kCRLSetRevoked, + kCRLSetOk, + kCRLSetError, +}; + +// CheckRevocationWithCRLSet attempts to check each element of |cert_list| +// against |crl_set|. It returns: +// kCRLSetRevoked: if any element of the chain is known to have been revoked. +// kCRLSetError: if an error occurs in processing. +// kCRLSetOk: if no element in the chain is known to have been revoked. +CRLSetResult CheckRevocationWithCRLSet(CERTCertList* cert_list, + CERTCertificate* root, + CRLSet* crl_set) { + std::vector<CERTCertificate*> certs; + + if (cert_list) { + for (CERTCertListNode* node = CERT_LIST_HEAD(cert_list); + !CERT_LIST_END(node, cert_list); + node = CERT_LIST_NEXT(node)) { + certs.push_back(node->cert); + } + } + if (root) + certs.push_back(root); + + // We iterate from the root certificate down to the leaf, keeping track of + // the issuer's SPKI at each step. + std::string issuer_spki_hash; + for (std::vector<CERTCertificate*>::reverse_iterator i = certs.rbegin(); + i != certs.rend(); ++i) { + CERTCertificate* cert = *i; + + base::StringPiece der(reinterpret_cast<char*>(cert->derCert.data), + cert->derCert.len); + + base::StringPiece spki; + if (!asn1::ExtractSPKIFromDERCert(der, &spki)) { + NOTREACHED(); + return kCRLSetError; + } + const std::string spki_hash = crypto::SHA256HashString(spki); + + base::StringPiece serial_number = base::StringPiece( + reinterpret_cast<char*>(cert->serialNumber.data), + cert->serialNumber.len); + + CRLSet::Result result = crl_set->CheckSPKI(spki_hash); + + if (result != CRLSet::REVOKED && !issuer_spki_hash.empty()) + result = crl_set->CheckSerial(serial_number, issuer_spki_hash); + + issuer_spki_hash = spki_hash; + + switch (result) { + case CRLSet::REVOKED: + return kCRLSetRevoked; + case CRLSet::UNKNOWN: + case CRLSet::GOOD: + continue; + default: + NOTREACHED(); + return kCRLSetError; + } + } + + return kCRLSetOk; +} + +// Forward declarations. +SECStatus RetryPKIXVerifyCertWithWorkarounds( + CERTCertificate* cert_handle, int num_policy_oids, + bool cert_io_enabled, std::vector<CERTValInParam>* cvin, + CERTValOutParam* cvout); +SECOidTag GetFirstCertPolicy(CERTCertificate* cert_handle); + +// Call CERT_PKIXVerifyCert for the cert_handle. +// Verification results are stored in an array of CERTValOutParam. +// If policy_oids is not NULL and num_policy_oids is positive, policies +// are also checked. +// additional_trust_anchors is an optional list of certificates that can be +// trusted as anchors when building a certificate chain. +// Caller must initialize cvout before calling this function. +SECStatus PKIXVerifyCert(CERTCertificate* cert_handle, + bool check_revocation, + bool cert_io_enabled, + const SECOidTag* policy_oids, + int num_policy_oids, + CERTCertList* additional_trust_anchors, + CERTValOutParam* cvout) { + bool use_crl = check_revocation; + bool use_ocsp = check_revocation; + + // These CAs have multiple keys, which trigger two bugs in NSS's CRL code. + // 1. NSS may use one key to verify a CRL signed with another key, + // incorrectly concluding that the CRL's signature is invalid. + // Hopefully this bug will be fixed in NSS 3.12.9. + // 2. NSS considers all certificates issued by the CA as revoked when it + // receives a CRL with an invalid signature. This overly strict policy + // has been relaxed in NSS 3.12.7. See + // https://bugzilla.mozilla.org/show_bug.cgi?id=562542. + // So we have to turn off CRL checking for these CAs. See + // http://crbug.com/55695. + static const char* const kMultipleKeyCA[] = { + "CN=Microsoft Secure Server Authority," + "DC=redmond,DC=corp,DC=microsoft,DC=com", + "CN=Microsoft Secure Server Authority", + }; + + if (!NSS_VersionCheck("3.12.7")) { + for (size_t i = 0; i < arraysize(kMultipleKeyCA); ++i) { + if (strcmp(cert_handle->issuerName, kMultipleKeyCA[i]) == 0) { + use_crl = false; + break; + } + } + } + + PRUint64 revocation_method_flags = + CERT_REV_M_DO_NOT_TEST_USING_THIS_METHOD | + CERT_REV_M_ALLOW_NETWORK_FETCHING | + CERT_REV_M_IGNORE_IMPLICIT_DEFAULT_SOURCE | + CERT_REV_M_IGNORE_MISSING_FRESH_INFO | + CERT_REV_M_STOP_TESTING_ON_FRESH_INFO; + PRUint64 revocation_method_independent_flags = + CERT_REV_MI_TEST_ALL_LOCAL_INFORMATION_FIRST; + if (check_revocation && policy_oids && num_policy_oids > 0) { + // EV verification requires revocation checking. Consider the certificate + // revoked if we don't have revocation info. + // TODO(wtc): Add a bool parameter to expressly specify we're doing EV + // verification or we want strict revocation flags. + revocation_method_flags |= CERT_REV_M_REQUIRE_INFO_ON_MISSING_SOURCE; + revocation_method_independent_flags |= + CERT_REV_MI_REQUIRE_SOME_FRESH_INFO_AVAILABLE; + } else { + revocation_method_flags |= CERT_REV_M_SKIP_TEST_ON_MISSING_SOURCE; + revocation_method_independent_flags |= + CERT_REV_MI_NO_OVERALL_INFO_REQUIREMENT; + } + PRUint64 method_flags[2]; + method_flags[cert_revocation_method_crl] = revocation_method_flags; + method_flags[cert_revocation_method_ocsp] = revocation_method_flags; + + if (use_crl) { + method_flags[cert_revocation_method_crl] |= + CERT_REV_M_TEST_USING_THIS_METHOD; + } + if (use_ocsp) { + method_flags[cert_revocation_method_ocsp] |= + CERT_REV_M_TEST_USING_THIS_METHOD; + } + + CERTRevocationMethodIndex preferred_revocation_methods[1]; + if (use_ocsp) { + preferred_revocation_methods[0] = cert_revocation_method_ocsp; + } else { + preferred_revocation_methods[0] = cert_revocation_method_crl; + } + + CERTRevocationFlags revocation_flags; + revocation_flags.leafTests.number_of_defined_methods = + arraysize(method_flags); + revocation_flags.leafTests.cert_rev_flags_per_method = method_flags; + revocation_flags.leafTests.number_of_preferred_methods = + arraysize(preferred_revocation_methods); + revocation_flags.leafTests.preferred_methods = preferred_revocation_methods; + revocation_flags.leafTests.cert_rev_method_independent_flags = + revocation_method_independent_flags; + + revocation_flags.chainTests.number_of_defined_methods = + arraysize(method_flags); + revocation_flags.chainTests.cert_rev_flags_per_method = method_flags; + revocation_flags.chainTests.number_of_preferred_methods = + arraysize(preferred_revocation_methods); + revocation_flags.chainTests.preferred_methods = preferred_revocation_methods; + revocation_flags.chainTests.cert_rev_method_independent_flags = + revocation_method_independent_flags; + + + std::vector<CERTValInParam> cvin; + cvin.reserve(7); + CERTValInParam in_param; + in_param.type = cert_pi_revocationFlags; + in_param.value.pointer.revocation = &revocation_flags; + cvin.push_back(in_param); + if (policy_oids && num_policy_oids > 0) { + in_param.type = cert_pi_policyOID; + in_param.value.arraySize = num_policy_oids; + in_param.value.array.oids = policy_oids; + cvin.push_back(in_param); + } + if (additional_trust_anchors) { + in_param.type = cert_pi_trustAnchors; + in_param.value.pointer.chain = additional_trust_anchors; + cvin.push_back(in_param); + in_param.type = cert_pi_useOnlyTrustAnchors; + in_param.value.scalar.b = PR_FALSE; + cvin.push_back(in_param); + } + in_param.type = cert_pi_end; + cvin.push_back(in_param); + + SECStatus rv = CERT_PKIXVerifyCert(cert_handle, certificateUsageSSLServer, + &cvin[0], cvout, NULL); + if (rv != SECSuccess) { + rv = RetryPKIXVerifyCertWithWorkarounds(cert_handle, num_policy_oids, + cert_io_enabled, &cvin, cvout); + } + return rv; +} + +// PKIXVerifyCert calls this function to work around some bugs in +// CERT_PKIXVerifyCert. All the arguments of this function are either the +// arguments or local variables of PKIXVerifyCert. +SECStatus RetryPKIXVerifyCertWithWorkarounds( + CERTCertificate* cert_handle, int num_policy_oids, + bool cert_io_enabled, std::vector<CERTValInParam>* cvin, + CERTValOutParam* cvout) { + // We call this function when the first CERT_PKIXVerifyCert call in + // PKIXVerifyCert failed, so we initialize |rv| to SECFailure. + SECStatus rv = SECFailure; + int nss_error = PORT_GetError(); + CERTValInParam in_param; + + // If we get SEC_ERROR_UNKNOWN_ISSUER, we may be missing an intermediate + // CA certificate, so we retry with cert_pi_useAIACertFetch. + // cert_pi_useAIACertFetch has several bugs in its error handling and + // error reporting (NSS bug 528743), so we don't use it by default. + // Note: When building a certificate chain, CERT_PKIXVerifyCert may + // incorrectly pick a CA certificate with the same subject name as the + // missing intermediate CA certificate, and fail with the + // SEC_ERROR_BAD_SIGNATURE error (NSS bug 524013), so we also retry with + // cert_pi_useAIACertFetch on SEC_ERROR_BAD_SIGNATURE. + if (cert_io_enabled && + (nss_error == SEC_ERROR_UNKNOWN_ISSUER || + nss_error == SEC_ERROR_BAD_SIGNATURE)) { + DCHECK_EQ(cvin->back().type, cert_pi_end); + cvin->pop_back(); + in_param.type = cert_pi_useAIACertFetch; + in_param.value.scalar.b = PR_TRUE; + cvin->push_back(in_param); + in_param.type = cert_pi_end; + cvin->push_back(in_param); + rv = CERT_PKIXVerifyCert(cert_handle, certificateUsageSSLServer, + &(*cvin)[0], cvout, NULL); + if (rv == SECSuccess) + return rv; + int new_nss_error = PORT_GetError(); + if (new_nss_error == SEC_ERROR_INVALID_ARGS || + new_nss_error == SEC_ERROR_UNKNOWN_AIA_LOCATION_TYPE || + new_nss_error == SEC_ERROR_BAD_INFO_ACCESS_LOCATION || + new_nss_error == SEC_ERROR_BAD_HTTP_RESPONSE || + new_nss_error == SEC_ERROR_BAD_LDAP_RESPONSE || + !IS_SEC_ERROR(new_nss_error)) { + // Use the original error code because of cert_pi_useAIACertFetch's + // bad error reporting. + PORT_SetError(nss_error); + return rv; + } + nss_error = new_nss_error; + } + + // If an intermediate CA certificate has requireExplicitPolicy in its + // policyConstraints extension, CERT_PKIXVerifyCert fails with + // SEC_ERROR_POLICY_VALIDATION_FAILED because we didn't specify any + // certificate policy (NSS bug 552775). So we retry with the certificate + // policy found in the server certificate. + if (nss_error == SEC_ERROR_POLICY_VALIDATION_FAILED && + num_policy_oids == 0) { + SECOidTag policy = GetFirstCertPolicy(cert_handle); + if (policy != SEC_OID_UNKNOWN) { + DCHECK_EQ(cvin->back().type, cert_pi_end); + cvin->pop_back(); + in_param.type = cert_pi_policyOID; + in_param.value.arraySize = 1; + in_param.value.array.oids = &policy; + cvin->push_back(in_param); + in_param.type = cert_pi_end; + cvin->push_back(in_param); + rv = CERT_PKIXVerifyCert(cert_handle, certificateUsageSSLServer, + &(*cvin)[0], cvout, NULL); + if (rv != SECSuccess) { + // Use the original error code. + PORT_SetError(nss_error); + } + } + } + + return rv; +} + +// Decodes the certificatePolicies extension of the certificate. Returns +// NULL if the certificate doesn't have the extension or the extension can't +// be decoded. The returned value must be freed with a +// CERT_DestroyCertificatePoliciesExtension call. +CERTCertificatePolicies* DecodeCertPolicies( + CERTCertificate* cert_handle) { + SECItem policy_ext; + SECStatus rv = CERT_FindCertExtension(cert_handle, + SEC_OID_X509_CERTIFICATE_POLICIES, + &policy_ext); + if (rv != SECSuccess) + return NULL; + CERTCertificatePolicies* policies = + CERT_DecodeCertificatePoliciesExtension(&policy_ext); + SECITEM_FreeItem(&policy_ext, PR_FALSE); + return policies; +} + +// Returns the OID tag for the first certificate policy in the certificate's +// certificatePolicies extension. Returns SEC_OID_UNKNOWN if the certificate +// has no certificate policy. +SECOidTag GetFirstCertPolicy(CERTCertificate* cert_handle) { + ScopedCERTCertificatePolicies policies(DecodeCertPolicies(cert_handle)); + if (!policies.get()) + return SEC_OID_UNKNOWN; + + CERTPolicyInfo* policy_info = policies->policyInfos[0]; + if (!policy_info) + return SEC_OID_UNKNOWN; + if (policy_info->oid != SEC_OID_UNKNOWN) + return policy_info->oid; + + // The certificate policy is unknown to NSS. We need to create a dynamic + // OID tag for the policy. + SECOidData od; + od.oid.len = policy_info->policyID.len; + od.oid.data = policy_info->policyID.data; + od.offset = SEC_OID_UNKNOWN; + // NSS doesn't allow us to pass an empty description, so I use a hardcoded, + // default description here. The description doesn't need to be unique for + // each OID. + od.desc = "a certificate policy"; + od.mechanism = CKM_INVALID_MECHANISM; + od.supportedExtension = INVALID_CERT_EXTENSION; + return SECOID_AddEntry(&od); +} + +HashValue CertPublicKeyHashSHA1(CERTCertificate* cert) { + HashValue hash(HASH_VALUE_SHA1); +#if defined(OS_IOS) + CC_SHA1(cert->derPublicKey.data, cert->derPublicKey.len, hash.data()); +#else + SECStatus rv = HASH_HashBuf(HASH_AlgSHA1, hash.data(), + cert->derPublicKey.data, cert->derPublicKey.len); + DCHECK_EQ(SECSuccess, rv); +#endif + return hash; +} + +HashValue CertPublicKeyHashSHA256(CERTCertificate* cert) { + HashValue hash(HASH_VALUE_SHA256); +#if defined(OS_IOS) + CC_SHA256(cert->derPublicKey.data, cert->derPublicKey.len, hash.data()); +#else + SECStatus rv = HASH_HashBuf(HASH_AlgSHA256, hash.data(), + cert->derPublicKey.data, cert->derPublicKey.len); + DCHECK_EQ(rv, SECSuccess); +#endif + return hash; +} + +void AppendPublicKeyHashes(CERTCertList* cert_list, + CERTCertificate* root_cert, + HashValueVector* hashes) { + for (CERTCertListNode* node = CERT_LIST_HEAD(cert_list); + !CERT_LIST_END(node, cert_list); + node = CERT_LIST_NEXT(node)) { + hashes->push_back(CertPublicKeyHashSHA1(node->cert)); + hashes->push_back(CertPublicKeyHashSHA256(node->cert)); + } + if (root_cert) { + hashes->push_back(CertPublicKeyHashSHA1(root_cert)); + hashes->push_back(CertPublicKeyHashSHA256(root_cert)); + } +} + +// Returns true if |cert_handle| contains a policy OID that is an EV policy +// OID according to |metadata|, storing the resulting policy OID in +// |*ev_policy_oid|. A true return is not sufficient to establish that a +// certificate is EV, but a false return is sufficient to establish the +// certificate cannot be EV. +bool IsEVCandidate(EVRootCAMetadata* metadata, + CERTCertificate* cert_handle, + SECOidTag* ev_policy_oid) { + DCHECK(cert_handle); + ScopedCERTCertificatePolicies policies(DecodeCertPolicies(cert_handle)); + if (!policies.get()) + return false; + + CERTPolicyInfo** policy_infos = policies->policyInfos; + while (*policy_infos != NULL) { + CERTPolicyInfo* policy_info = *policy_infos++; + // If the Policy OID is unknown, that implicitly means it has not been + // registered as an EV policy. + if (policy_info->oid == SEC_OID_UNKNOWN) + continue; + if (metadata->IsEVPolicyOID(policy_info->oid)) { + *ev_policy_oid = policy_info->oid; + return true; + } + } + + return false; +} + +// Studied Mozilla's code (esp. security/manager/ssl/src/nsIdentityChecking.cpp +// and nsNSSCertHelper.cpp) to learn how to verify EV certificate. +// TODO(wtc): A possible optimization is that we get the trust anchor from +// the first PKIXVerifyCert call. We look up the EV policy for the trust +// anchor. If the trust anchor has no EV policy, we know the cert isn't EV. +// Otherwise, we pass just that EV policy (as opposed to all the EV policies) +// to the second PKIXVerifyCert call. +bool VerifyEV(CERTCertificate* cert_handle, + int flags, + CRLSet* crl_set, + EVRootCAMetadata* metadata, + SECOidTag ev_policy_oid, + CERTCertList* additional_trust_anchors) { + CERTValOutParam cvout[3]; + int cvout_index = 0; + cvout[cvout_index].type = cert_po_certList; + cvout[cvout_index].value.pointer.chain = NULL; + int cvout_cert_list_index = cvout_index; + cvout_index++; + cvout[cvout_index].type = cert_po_trustAnchor; + cvout[cvout_index].value.pointer.cert = NULL; + int cvout_trust_anchor_index = cvout_index; + cvout_index++; + cvout[cvout_index].type = cert_po_end; + ScopedCERTValOutParam scoped_cvout(cvout); + + bool rev_checking_enabled = + (flags & CertVerifier::VERIFY_REV_CHECKING_ENABLED) || + (flags & CertVerifier::VERIFY_REV_CHECKING_ENABLED_EV_ONLY); + + SECStatus status = PKIXVerifyCert( + cert_handle, + rev_checking_enabled, + flags & CertVerifier::VERIFY_CERT_IO_ENABLED, + &ev_policy_oid, + 1, + additional_trust_anchors, + cvout); + if (status != SECSuccess) + return false; + + CERTCertificate* root_ca = + cvout[cvout_trust_anchor_index].value.pointer.cert; + if (root_ca == NULL) + return false; + + // This second PKIXVerifyCert call could have found a different certification + // path and one or more of the certificates on this new path, that weren't on + // the old path, might have been revoked. + if (crl_set) { + CRLSetResult crl_set_result = CheckRevocationWithCRLSet( + cvout[cvout_cert_list_index].value.pointer.chain, + cvout[cvout_trust_anchor_index].value.pointer.cert, + crl_set); + if (crl_set_result == kCRLSetRevoked) + return false; + } + +#if defined(OS_IOS) + SHA1HashValue fingerprint = x509_util_ios::CalculateFingerprintNSS(root_ca); +#else + SHA1HashValue fingerprint = + X509Certificate::CalculateFingerprint(root_ca); +#endif + return metadata->HasEVPolicyOID(fingerprint, ev_policy_oid); +} + +CERTCertList* CertificateListToCERTCertList(const CertificateList& list) { + CERTCertList* result = CERT_NewCertList(); + for (size_t i = 0; i < list.size(); ++i) { +#if defined(OS_IOS) + // X509Certificate::os_cert_handle() on iOS is a SecCertificateRef; convert + // it to an NSS CERTCertificate. + CERTCertificate* cert = x509_util_ios::CreateNSSCertHandleFromOSHandle( + list[i]->os_cert_handle()); +#else + CERTCertificate* cert = list[i]->os_cert_handle(); +#endif + CERT_AddCertToListTail(result, CERT_DupCertificate(cert)); + } + return result; +} + +} // namespace + +CertVerifyProcNSS::CertVerifyProcNSS() {} + +CertVerifyProcNSS::~CertVerifyProcNSS() {} + +bool CertVerifyProcNSS::SupportsAdditionalTrustAnchors() const { + // This requires APIs introduced in 3.14.2. + return NSS_VersionCheck("3.14.2"); +} + +int CertVerifyProcNSS::VerifyInternal( + X509Certificate* cert, + const std::string& hostname, + int flags, + CRLSet* crl_set, + const CertificateList& additional_trust_anchors, + CertVerifyResult* verify_result) { +#if defined(OS_IOS) + // For iOS, the entire chain must be loaded into NSS's in-memory certificate + // store. + x509_util_ios::NSSCertChain scoped_chain(cert); + CERTCertificate* cert_handle = scoped_chain.cert_handle(); +#else + CERTCertificate* cert_handle = cert->os_cert_handle(); +#endif // defined(OS_IOS) + + // Make sure that the hostname matches with the common name of the cert. + SECStatus status = CERT_VerifyCertName(cert_handle, hostname.c_str()); + if (status != SECSuccess) + verify_result->cert_status |= CERT_STATUS_COMMON_NAME_INVALID; + + // Make sure that the cert is valid now. + SECCertTimeValidity validity = CERT_CheckCertValidTimes( + cert_handle, PR_Now(), PR_TRUE); + if (validity != secCertTimeValid) + verify_result->cert_status |= CERT_STATUS_DATE_INVALID; + + CERTValOutParam cvout[3]; + int cvout_index = 0; + cvout[cvout_index].type = cert_po_certList; + cvout[cvout_index].value.pointer.chain = NULL; + int cvout_cert_list_index = cvout_index; + cvout_index++; + cvout[cvout_index].type = cert_po_trustAnchor; + cvout[cvout_index].value.pointer.cert = NULL; + int cvout_trust_anchor_index = cvout_index; + cvout_index++; + cvout[cvout_index].type = cert_po_end; + ScopedCERTValOutParam scoped_cvout(cvout); + + EVRootCAMetadata* metadata = EVRootCAMetadata::GetInstance(); + SECOidTag ev_policy_oid = SEC_OID_UNKNOWN; + bool is_ev_candidate = + (flags & CertVerifier::VERIFY_EV_CERT) && + IsEVCandidate(metadata, cert_handle, &ev_policy_oid); + bool cert_io_enabled = flags & CertVerifier::VERIFY_CERT_IO_ENABLED; + bool check_revocation = + cert_io_enabled && + ((flags & CertVerifier::VERIFY_REV_CHECKING_ENABLED) || + ((flags & CertVerifier::VERIFY_REV_CHECKING_ENABLED_EV_ONLY) && + is_ev_candidate)); + if (check_revocation) + verify_result->cert_status |= CERT_STATUS_REV_CHECKING_ENABLED; + + ScopedCERTCertList trust_anchors; + if (SupportsAdditionalTrustAnchors() && !additional_trust_anchors.empty()) { + trust_anchors.reset( + CertificateListToCERTCertList(additional_trust_anchors)); + } + + status = PKIXVerifyCert(cert_handle, check_revocation, cert_io_enabled, + NULL, 0, trust_anchors.get(), cvout); + + if (status == SECSuccess) { + AppendPublicKeyHashes(cvout[cvout_cert_list_index].value.pointer.chain, + cvout[cvout_trust_anchor_index].value.pointer.cert, + &verify_result->public_key_hashes); + + verify_result->is_issued_by_known_root = + IsKnownRoot(cvout[cvout_trust_anchor_index].value.pointer.cert); + verify_result->is_issued_by_additional_trust_anchor = + IsAdditionalTrustAnchor( + trust_anchors.get(), + cvout[cvout_trust_anchor_index].value.pointer.cert); + + GetCertChainInfo(cvout[cvout_cert_list_index].value.pointer.chain, + cvout[cvout_trust_anchor_index].value.pointer.cert, + verify_result); + } + + if (crl_set) { + CRLSetResult crl_set_result = CheckRevocationWithCRLSet( + cvout[cvout_cert_list_index].value.pointer.chain, + cvout[cvout_trust_anchor_index].value.pointer.cert, + crl_set); + if (crl_set_result == kCRLSetRevoked) { + PORT_SetError(SEC_ERROR_REVOKED_CERTIFICATE); + status = SECFailure; + } + } + + if (status != SECSuccess) { + int err = PORT_GetError(); + LOG(ERROR) << "CERT_PKIXVerifyCert for " << hostname + << " failed err=" << err; + // CERT_PKIXVerifyCert rerports the wrong error code for + // expired certificates (NSS bug 491174) + if (err == SEC_ERROR_CERT_NOT_VALID && + (verify_result->cert_status & CERT_STATUS_DATE_INVALID)) + err = SEC_ERROR_EXPIRED_CERTIFICATE; + CertStatus cert_status = MapCertErrorToCertStatus(err); + if (cert_status) { + verify_result->cert_status |= cert_status; + return MapCertStatusToNetError(verify_result->cert_status); + } + // |err| is not a certificate error. + return MapSecurityError(err); + } + + if (IsCertStatusError(verify_result->cert_status)) + return MapCertStatusToNetError(verify_result->cert_status); + + if ((flags & CertVerifier::VERIFY_EV_CERT) && is_ev_candidate && + VerifyEV(cert_handle, flags, crl_set, metadata, ev_policy_oid, + trust_anchors.get())) { + verify_result->cert_status |= CERT_STATUS_IS_EV; + } + + return OK; +} + +} // namespace net diff --git a/net/cert/cert_verify_proc_nss.h b/net/cert/cert_verify_proc_nss.h new file mode 100644 index 0000000..f8bb853 --- /dev/null +++ b/net/cert/cert_verify_proc_nss.h @@ -0,0 +1,34 @@ +// Copyright (c) 2012 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. + +#ifndef NET_CERT_CERT_VERIFY_PROC_NSS_H_ +#define NET_CERT_CERT_VERIFY_PROC_NSS_H_ + +#include "net/base/net_export.h" +#include "net/cert/cert_verify_proc.h" + +namespace net { + +// Performs certificate path construction and validation using NSS's libpkix. +class NET_EXPORT_PRIVATE CertVerifyProcNSS : public CertVerifyProc { + public: + CertVerifyProcNSS(); + + virtual bool SupportsAdditionalTrustAnchors() const OVERRIDE; + + protected: + virtual ~CertVerifyProcNSS(); + + private: + virtual int VerifyInternal(X509Certificate* cert, + const std::string& hostname, + int flags, + CRLSet* crl_set, + const CertificateList& additional_trust_anchors, + CertVerifyResult* verify_result) OVERRIDE; +}; + +} // namespace net + +#endif // NET_CERT_CERT_VERIFY_PROC_NSS_H_ diff --git a/net/cert/cert_verify_proc_openssl.cc b/net/cert/cert_verify_proc_openssl.cc new file mode 100644 index 0000000..0328599 --- /dev/null +++ b/net/cert/cert_verify_proc_openssl.cc @@ -0,0 +1,230 @@ +// Copyright (c) 2012 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 "net/cert/cert_verify_proc_openssl.h" + +#include <openssl/x509v3.h> + +#include <string> +#include <vector> + +#include "base/logging.h" +#include "base/sha1.h" +#include "crypto/openssl_util.h" +#include "crypto/sha2.h" +#include "net/base/net_errors.h" +#include "net/cert/asn1_util.h" +#include "net/cert/cert_status_flags.h" +#include "net/cert/cert_verifier.h" +#include "net/cert/cert_verify_result.h" +#include "net/cert/x509_certificate.h" + +namespace net { + +namespace { + +// Maps X509_STORE_CTX_get_error() return values to our cert status flags. +CertStatus MapCertErrorToCertStatus(int err) { + switch (err) { + case X509_V_ERR_SUBJECT_ISSUER_MISMATCH: + return CERT_STATUS_COMMON_NAME_INVALID; + case X509_V_ERR_CERT_NOT_YET_VALID: + case X509_V_ERR_CERT_HAS_EXPIRED: + case X509_V_ERR_CRL_NOT_YET_VALID: + case X509_V_ERR_CRL_HAS_EXPIRED: + case X509_V_ERR_ERROR_IN_CERT_NOT_BEFORE_FIELD: + case X509_V_ERR_ERROR_IN_CERT_NOT_AFTER_FIELD: + case X509_V_ERR_ERROR_IN_CRL_LAST_UPDATE_FIELD: + case X509_V_ERR_ERROR_IN_CRL_NEXT_UPDATE_FIELD: + return CERT_STATUS_DATE_INVALID; + case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT: + case X509_V_ERR_UNABLE_TO_GET_CRL: + case X509_V_ERR_INVALID_CA: + case X509_V_ERR_UNABLE_TO_GET_CRL_ISSUER: + case X509_V_ERR_INVALID_NON_CA: + case X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT: + case X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN: + case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY: + return CERT_STATUS_AUTHORITY_INVALID; +#if 0 +// TODO(bulach): what should we map to these status? + return CERT_STATUS_NO_REVOCATION_MECHANISM; + return CERT_STATUS_UNABLE_TO_CHECK_REVOCATION; +#endif + case X509_V_ERR_CERT_REVOKED: + return CERT_STATUS_REVOKED; + // All these status are mapped to CERT_STATUS_INVALID. + case X509_V_ERR_UNABLE_TO_DECRYPT_CERT_SIGNATURE: + case X509_V_ERR_UNABLE_TO_DECRYPT_CRL_SIGNATURE: + case X509_V_ERR_UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY: + case X509_V_ERR_CERT_SIGNATURE_FAILURE: + case X509_V_ERR_CRL_SIGNATURE_FAILURE: + case X509_V_ERR_OUT_OF_MEM: + case X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE: + case X509_V_ERR_CERT_CHAIN_TOO_LONG: + case X509_V_ERR_PATH_LENGTH_EXCEEDED: + case X509_V_ERR_INVALID_PURPOSE: + case X509_V_ERR_CERT_UNTRUSTED: + case X509_V_ERR_CERT_REJECTED: + case X509_V_ERR_AKID_SKID_MISMATCH: + case X509_V_ERR_AKID_ISSUER_SERIAL_MISMATCH: + case X509_V_ERR_UNHANDLED_CRITICAL_EXTENSION: + case X509_V_ERR_KEYUSAGE_NO_CERTSIGN: + case X509_V_ERR_KEYUSAGE_NO_CRL_SIGN: + case X509_V_ERR_UNHANDLED_CRITICAL_CRL_EXTENSION: + case X509_V_ERR_PROXY_PATH_LENGTH_EXCEEDED: + case X509_V_ERR_KEYUSAGE_NO_DIGITAL_SIGNATURE: + case X509_V_ERR_PROXY_CERTIFICATES_NOT_ALLOWED: + case X509_V_ERR_INVALID_EXTENSION: + case X509_V_ERR_INVALID_POLICY_EXTENSION: + case X509_V_ERR_NO_EXPLICIT_POLICY: + case X509_V_ERR_UNNESTED_RESOURCE: + case X509_V_ERR_APPLICATION_VERIFICATION: + return CERT_STATUS_INVALID; + default: + NOTREACHED() << "Invalid X509 err " << err; + return CERT_STATUS_INVALID; + } +} + +// sk_X509_free is a function-style macro, so can't be used as a template +// param directly. +void sk_X509_free_fn(STACK_OF(X509)* st) { + sk_X509_free(st); +} + +void GetCertChainInfo(X509_STORE_CTX* store_ctx, + CertVerifyResult* verify_result) { + STACK_OF(X509)* chain = X509_STORE_CTX_get_chain(store_ctx); + X509* verified_cert = NULL; + std::vector<X509*> verified_chain; + for (int i = 0; i < sk_X509_num(chain); ++i) { + X509* cert = sk_X509_value(chain, i); + if (i == 0) { + verified_cert = cert; + } else { + verified_chain.push_back(cert); + } + + // Only check the algorithm status for certificates that are not in the + // trust store. + if (i < store_ctx->last_untrusted) { + int sig_alg = OBJ_obj2nid(cert->sig_alg->algorithm); + if (sig_alg == NID_md2WithRSAEncryption) { + verify_result->has_md2 = true; + if (i != 0) + verify_result->has_md2_ca = true; + } else if (sig_alg == NID_md4WithRSAEncryption) { + verify_result->has_md4 = true; + } else if (sig_alg == NID_md5WithRSAEncryption) { + verify_result->has_md5 = true; + if (i != 0) + verify_result->has_md5_ca = true; + } + } + } + + if (verified_cert) { + verify_result->verified_cert = + X509Certificate::CreateFromHandle(verified_cert, verified_chain); + } +} + +void AppendPublicKeyHashes(X509_STORE_CTX* store_ctx, + HashValueVector* hashes) { + STACK_OF(X509)* chain = X509_STORE_CTX_get_chain(store_ctx); + for (int i = 0; i < sk_X509_num(chain); ++i) { + X509* cert = sk_X509_value(chain, i); + + std::string der_data; + if (!X509Certificate::GetDEREncoded(cert, &der_data)) + continue; + + base::StringPiece der_bytes(der_data); + base::StringPiece spki_bytes; + if (!asn1::ExtractSPKIFromDERCert(der_bytes, &spki_bytes)) + continue; + + HashValue sha1(HASH_VALUE_SHA1); + base::SHA1HashBytes(reinterpret_cast<const uint8*>(spki_bytes.data()), + spki_bytes.size(), sha1.data()); + hashes->push_back(sha1); + + HashValue sha256(HASH_VALUE_SHA256); + crypto::SHA256HashString(spki_bytes, sha1.data(), crypto::kSHA256Length); + hashes->push_back(sha256); + } +} + +} // namespace + +CertVerifyProcOpenSSL::CertVerifyProcOpenSSL() {} + +CertVerifyProcOpenSSL::~CertVerifyProcOpenSSL() {} + +bool CertVerifyProcOpenSSL::SupportsAdditionalTrustAnchors() const { + return false; +} + +int CertVerifyProcOpenSSL::VerifyInternal( + X509Certificate* cert, + const std::string& hostname, + int flags, + CRLSet* crl_set, + const CertificateList& additional_trust_anchors, + CertVerifyResult* verify_result) { + crypto::EnsureOpenSSLInit(); + + if (!cert->VerifyNameMatch(hostname)) + verify_result->cert_status |= CERT_STATUS_COMMON_NAME_INVALID; + + crypto::ScopedOpenSSL<X509_STORE_CTX, X509_STORE_CTX_free> ctx( + X509_STORE_CTX_new()); + + crypto::ScopedOpenSSL<STACK_OF(X509), sk_X509_free_fn> intermediates( + sk_X509_new_null()); + if (!intermediates.get()) + return ERR_OUT_OF_MEMORY; + + const X509Certificate::OSCertHandles& os_intermediates = + cert->GetIntermediateCertificates(); + for (X509Certificate::OSCertHandles::const_iterator it = + os_intermediates.begin(); it != os_intermediates.end(); ++it) { + if (!sk_X509_push(intermediates.get(), *it)) + return ERR_OUT_OF_MEMORY; + } + if (X509_STORE_CTX_init(ctx.get(), X509Certificate::cert_store(), + cert->os_cert_handle(), intermediates.get()) != 1) { + NOTREACHED(); + return ERR_FAILED; + } + + if (X509_verify_cert(ctx.get()) != 1) { + int x509_error = X509_STORE_CTX_get_error(ctx.get()); + CertStatus cert_status = MapCertErrorToCertStatus(x509_error); + LOG(ERROR) << "X509 Verification error " + << X509_verify_cert_error_string(x509_error) + << " : " << x509_error + << " : " << X509_STORE_CTX_get_error_depth(ctx.get()) + << " : " << cert_status; + verify_result->cert_status |= cert_status; + } + + GetCertChainInfo(ctx.get(), verify_result); + AppendPublicKeyHashes(ctx.get(), &verify_result->public_key_hashes); + if (IsCertStatusError(verify_result->cert_status)) + return MapCertStatusToNetError(verify_result->cert_status); + + // Currently we only ues OpenSSL's default root CA paths, so treat all + // correctly verified certs as being from a known root. + // TODO(joth): if the motivations described in + // http://src.chromium.org/viewvc/chrome?view=rev&revision=80778 become an + // issue on OpenSSL builds, we will need to embed a hardcoded list of well + // known root CAs, as per the _mac and _win versions. + verify_result->is_issued_by_known_root = true; + + return OK; +} + +} // namespace net diff --git a/net/cert/cert_verify_proc_openssl.h b/net/cert/cert_verify_proc_openssl.h new file mode 100644 index 0000000..d0d2574 --- /dev/null +++ b/net/cert/cert_verify_proc_openssl.h @@ -0,0 +1,33 @@ +// Copyright (c) 2012 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. + +#ifndef NET_CERT_CERT_VERIFY_PROC_OPENSSL_H_ +#define NET_CERT_CERT_VERIFY_PROC_OPENSSL_H_ + +#include "net/cert/cert_verify_proc.h" + +namespace net { + +// Performs certificate path construction and validation using OpenSSL. +class CertVerifyProcOpenSSL : public CertVerifyProc { + public: + CertVerifyProcOpenSSL(); + + virtual bool SupportsAdditionalTrustAnchors() const OVERRIDE; + + protected: + virtual ~CertVerifyProcOpenSSL(); + + private: + virtual int VerifyInternal(X509Certificate* cert, + const std::string& hostname, + int flags, + CRLSet* crl_set, + const CertificateList& additional_trust_anchors, + CertVerifyResult* verify_result) OVERRIDE; +}; + +} // namespace net + +#endif // NET_CERT_CERT_VERIFY_PROC_OPENSSL_H_ diff --git a/net/cert/cert_verify_proc_unittest.cc b/net/cert/cert_verify_proc_unittest.cc new file mode 100644 index 0000000..a7e7b8d --- /dev/null +++ b/net/cert/cert_verify_proc_unittest.cc @@ -0,0 +1,1068 @@ +// Copyright (c) 2012 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 "net/cert/cert_verify_proc.h" + +#include <vector> + +#include "base/files/file_path.h" +#include "base/logging.h" +#include "base/sha1.h" +#include "base/string_number_conversions.h" +#include "net/base/net_errors.h" +#include "net/base/test_data_directory.h" +#include "net/cert/asn1_util.h" +#include "net/cert/cert_status_flags.h" +#include "net/cert/cert_verifier.h" +#include "net/cert/cert_verify_result.h" +#include "net/cert/crl_set.h" +#include "net/cert/test_root_certs.h" +#include "net/cert/x509_certificate.h" +#include "net/test/cert_test_util.h" +#include "net/test/test_certificate_data.h" +#include "testing/gtest/include/gtest/gtest.h" + +#if defined(OS_WIN) +#include "base/win/windows_version.h" +#elif defined(OS_MACOSX) && !defined(OS_IOS) +#include "base/mac/mac_util.h" +#endif + +using base::HexEncode; + +namespace net { + +namespace { + +// A certificate for www.paypal.com with a NULL byte in the common name. +// From http://www.gossamer-threads.com/lists/fulldisc/full-disclosure/70363 +unsigned char paypal_null_fingerprint[] = { + 0x4c, 0x88, 0x9e, 0x28, 0xd7, 0x7a, 0x44, 0x1e, 0x13, 0xf2, 0x6a, 0xba, + 0x1f, 0xe8, 0x1b, 0xd6, 0xab, 0x7b, 0xe8, 0xd7 +}; + +} // namespace + +class CertVerifyProcTest : public testing::Test { + public: + CertVerifyProcTest() + : verify_proc_(CertVerifyProc::CreateDefault()) { + } + virtual ~CertVerifyProcTest() {} + + protected: + bool SupportsAdditionalTrustAnchors() { + return verify_proc_->SupportsAdditionalTrustAnchors(); + } + + int Verify(X509Certificate* cert, + const std::string& hostname, + int flags, + CRLSet* crl_set, + const CertificateList& additional_trust_anchors, + CertVerifyResult* verify_result) { + return verify_proc_->Verify(cert, hostname, flags, crl_set, + additional_trust_anchors, verify_result); + } + + const CertificateList empty_cert_list_; + + private: + scoped_refptr<CertVerifyProc> verify_proc_; +}; + +TEST_F(CertVerifyProcTest, WithoutRevocationChecking) { + // Check that verification without revocation checking works. + CertificateList certs = CreateCertificateListFromFile( + GetTestCertsDirectory(), + "googlenew.chain.pem", + X509Certificate::FORMAT_PEM_CERT_SEQUENCE); + + X509Certificate::OSCertHandles intermediates; + intermediates.push_back(certs[1]->os_cert_handle()); + + scoped_refptr<X509Certificate> google_full_chain = + X509Certificate::CreateFromHandle(certs[0]->os_cert_handle(), + intermediates); + + CertVerifyResult verify_result; + EXPECT_EQ(OK, Verify(google_full_chain, "www.google.com", 0 /* flags */, + NULL, empty_cert_list_, &verify_result)); +} + +#if defined(OS_ANDROID) || defined(USE_OPENSSL) +// TODO(jnd): http://crbug.com/117478 - EV verification is not yet supported. +#define MAYBE_EVVerification DISABLED_EVVerification +#else +#define MAYBE_EVVerification EVVerification +#endif +TEST_F(CertVerifyProcTest, MAYBE_EVVerification) { + // This certificate will expire Jun 21, 2013. + CertificateList certs = CreateCertificateListFromFile( + GetTestCertsDirectory(), + "comodo.chain.pem", + X509Certificate::FORMAT_PEM_CERT_SEQUENCE); + ASSERT_EQ(3U, certs.size()); + + X509Certificate::OSCertHandles intermediates; + intermediates.push_back(certs[1]->os_cert_handle()); + intermediates.push_back(certs[2]->os_cert_handle()); + + scoped_refptr<X509Certificate> comodo_chain = + X509Certificate::CreateFromHandle(certs[0]->os_cert_handle(), + intermediates); + + scoped_refptr<CRLSet> crl_set(CRLSet::EmptyCRLSetForTesting()); + CertVerifyResult verify_result; + int flags = CertVerifier::VERIFY_EV_CERT; + int error = Verify(comodo_chain, "comodo.com", flags, crl_set.get(), + empty_cert_list_, &verify_result); + EXPECT_EQ(OK, error); + EXPECT_TRUE(verify_result.cert_status & CERT_STATUS_IS_EV); +} + +TEST_F(CertVerifyProcTest, PaypalNullCertParsing) { + scoped_refptr<X509Certificate> paypal_null_cert( + X509Certificate::CreateFromBytes( + reinterpret_cast<const char*>(paypal_null_der), + sizeof(paypal_null_der))); + + ASSERT_NE(static_cast<X509Certificate*>(NULL), paypal_null_cert); + + const SHA1HashValue& fingerprint = + paypal_null_cert->fingerprint(); + for (size_t i = 0; i < 20; ++i) + EXPECT_EQ(paypal_null_fingerprint[i], fingerprint.data[i]); + + int flags = 0; + CertVerifyResult verify_result; + int error = Verify(paypal_null_cert, "www.paypal.com", flags, NULL, + empty_cert_list_, &verify_result); +#if defined(USE_NSS) || defined(OS_IOS) || defined(OS_ANDROID) + EXPECT_EQ(ERR_CERT_COMMON_NAME_INVALID, error); +#else + // TOOD(bulach): investigate why macosx and win aren't returning + // ERR_CERT_INVALID or ERR_CERT_COMMON_NAME_INVALID. + EXPECT_EQ(ERR_CERT_AUTHORITY_INVALID, error); +#endif + // Either the system crypto library should correctly report a certificate + // name mismatch, or our certificate blacklist should cause us to report an + // invalid certificate. +#if defined(USE_NSS) || defined(OS_WIN) || defined(OS_IOS) + EXPECT_TRUE(verify_result.cert_status & + (CERT_STATUS_COMMON_NAME_INVALID | CERT_STATUS_INVALID)); +#endif +} + +// A regression test for http://crbug.com/31497. +// This certificate will expire on 2012-04-08. The test will still +// pass if error == ERR_CERT_DATE_INVALID. TODO(wtc): generate test +// certificates for this unit test. http://crbug.com/111742 +TEST_F(CertVerifyProcTest, IntermediateCARequireExplicitPolicy) { + base::FilePath certs_dir = GetTestCertsDirectory(); + + scoped_refptr<X509Certificate> server_cert = + ImportCertFromFile(certs_dir, "www_us_army_mil_cert.der"); + ASSERT_NE(static_cast<X509Certificate*>(NULL), server_cert); + + // The intermediate CA certificate's policyConstraints extension has a + // requireExplicitPolicy field with SkipCerts=0. + scoped_refptr<X509Certificate> intermediate_cert = + ImportCertFromFile(certs_dir, "dod_ca_17_cert.der"); + ASSERT_NE(static_cast<X509Certificate*>(NULL), intermediate_cert); + + scoped_refptr<X509Certificate> root_cert = + ImportCertFromFile(certs_dir, "dod_root_ca_2_cert.der"); + ScopedTestRoot scoped_root(root_cert); + + X509Certificate::OSCertHandles intermediates; + intermediates.push_back(intermediate_cert->os_cert_handle()); + scoped_refptr<X509Certificate> cert_chain = + X509Certificate::CreateFromHandle(server_cert->os_cert_handle(), + intermediates); + + int flags = 0; + CertVerifyResult verify_result; + int error = Verify(cert_chain, "www.us.army.mil", flags, NULL, + empty_cert_list_, &verify_result); + if (error == OK) { + EXPECT_EQ(0U, verify_result.cert_status); + } else { + EXPECT_EQ(ERR_CERT_DATE_INVALID, error); + EXPECT_EQ(CERT_STATUS_DATE_INVALID, verify_result.cert_status); + } +} + + +// Test for bug 58437. +// This certificate will expire on 2011-12-21. The test will still +// pass if error == ERR_CERT_DATE_INVALID. +// This test is DISABLED because it appears that we cannot do +// certificate revocation checking when running all of the net unit tests. +// This test passes when run individually, but when run with all of the net +// unit tests, the call to PKIXVerifyCert returns the NSS error -8180, which is +// SEC_ERROR_REVOKED_CERTIFICATE. This indicates a lack of revocation +// status, i.e. that the revocation check is failing for some reason. +TEST_F(CertVerifyProcTest, DISABLED_GlobalSignR3EVTest) { + base::FilePath certs_dir = GetTestCertsDirectory(); + + scoped_refptr<X509Certificate> server_cert = + ImportCertFromFile(certs_dir, "2029_globalsign_com_cert.pem"); + ASSERT_NE(static_cast<X509Certificate*>(NULL), server_cert); + + scoped_refptr<X509Certificate> intermediate_cert = + ImportCertFromFile(certs_dir, "globalsign_ev_sha256_ca_cert.pem"); + ASSERT_NE(static_cast<X509Certificate*>(NULL), intermediate_cert); + + X509Certificate::OSCertHandles intermediates; + intermediates.push_back(intermediate_cert->os_cert_handle()); + scoped_refptr<X509Certificate> cert_chain = + X509Certificate::CreateFromHandle(server_cert->os_cert_handle(), + intermediates); + + CertVerifyResult verify_result; + int flags = CertVerifier::VERIFY_REV_CHECKING_ENABLED | + CertVerifier::VERIFY_EV_CERT; + int error = Verify(cert_chain, "2029.globalsign.com", flags, NULL, + empty_cert_list_, &verify_result); + if (error == OK) + EXPECT_TRUE(verify_result.cert_status & CERT_STATUS_IS_EV); + else + EXPECT_EQ(ERR_CERT_DATE_INVALID, error); +} + +// Test that verifying an ECDSA certificate doesn't crash on XP. (See +// crbug.com/144466). +TEST_F(CertVerifyProcTest, ECDSA_RSA) { + base::FilePath certs_dir = GetTestCertsDirectory(); + + scoped_refptr<X509Certificate> cert = + ImportCertFromFile(certs_dir, + "prime256v1-ecdsa-ee-by-1024-rsa-intermediate.pem"); + + CertVerifyResult verify_result; + Verify(cert, "127.0.0.1", 0, NULL, empty_cert_list_, &verify_result); + + // We don't check verify_result because the certificate is signed by an + // unknown CA and will be considered invalid on XP because of the ECDSA + // public key. +} + +// Currently, only RSA and DSA keys are checked for weakness, and our example +// weak size is 768. These could change in the future. +// +// Note that this means there may be false negatives: keys for other +// algorithms and which are weak will pass this test. +static bool IsWeakKeyType(const std::string& key_type) { + size_t pos = key_type.find("-"); + std::string size = key_type.substr(0, pos); + std::string type = key_type.substr(pos + 1); + + if (type == "rsa" || type == "dsa") + return size == "768"; + + return false; +} + +TEST_F(CertVerifyProcTest, RejectWeakKeys) { + base::FilePath certs_dir = GetTestCertsDirectory(); + typedef std::vector<std::string> Strings; + Strings key_types; + + // generate-weak-test-chains.sh currently has: + // key_types="768-rsa 1024-rsa 2048-rsa prime256v1-ecdsa" + // We must use the same key types here. The filenames generated look like: + // 2048-rsa-ee-by-768-rsa-intermediate.pem + key_types.push_back("768-rsa"); + key_types.push_back("1024-rsa"); + key_types.push_back("2048-rsa"); + + bool use_ecdsa = true; +#if defined(OS_WIN) + use_ecdsa = base::win::GetVersion() > base::win::VERSION_XP; +#endif + + if (use_ecdsa) + key_types.push_back("prime256v1-ecdsa"); + + // Add the root that signed the intermediates for this test. + scoped_refptr<X509Certificate> root_cert = + ImportCertFromFile(certs_dir, "2048-rsa-root.pem"); + ASSERT_NE(static_cast<X509Certificate*>(NULL), root_cert); + ScopedTestRoot scoped_root(root_cert); + + // Now test each chain. + for (Strings::const_iterator ee_type = key_types.begin(); + ee_type != key_types.end(); ++ee_type) { + for (Strings::const_iterator signer_type = key_types.begin(); + signer_type != key_types.end(); ++signer_type) { + std::string basename = *ee_type + "-ee-by-" + *signer_type + + "-intermediate.pem"; + SCOPED_TRACE(basename); + scoped_refptr<X509Certificate> ee_cert = + ImportCertFromFile(certs_dir, basename); + ASSERT_NE(static_cast<X509Certificate*>(NULL), ee_cert); + + basename = *signer_type + "-intermediate.pem"; + scoped_refptr<X509Certificate> intermediate = + ImportCertFromFile(certs_dir, basename); + ASSERT_NE(static_cast<X509Certificate*>(NULL), intermediate); + + X509Certificate::OSCertHandles intermediates; + intermediates.push_back(intermediate->os_cert_handle()); + scoped_refptr<X509Certificate> cert_chain = + X509Certificate::CreateFromHandle(ee_cert->os_cert_handle(), + intermediates); + + CertVerifyResult verify_result; + int error = Verify(cert_chain, "127.0.0.1", 0, NULL, + empty_cert_list_, &verify_result); + + if (IsWeakKeyType(*ee_type) || IsWeakKeyType(*signer_type)) { + EXPECT_NE(OK, error); + EXPECT_EQ(CERT_STATUS_WEAK_KEY, + verify_result.cert_status & CERT_STATUS_WEAK_KEY); + EXPECT_NE(CERT_STATUS_INVALID, + verify_result.cert_status & CERT_STATUS_INVALID); + } else { + EXPECT_EQ(OK, error); + EXPECT_EQ(0U, verify_result.cert_status & CERT_STATUS_WEAK_KEY); + } + } + } +} + +// Test for bug 108514. +// The certificate will expire on 2012-07-20. The test will still +// pass if error == ERR_CERT_DATE_INVALID. TODO(rsleevi): generate test +// certificates for this unit test. http://crbug.com/111730 +TEST_F(CertVerifyProcTest, ExtraneousMD5RootCert) { + base::FilePath certs_dir = GetTestCertsDirectory(); + + scoped_refptr<X509Certificate> server_cert = + ImportCertFromFile(certs_dir, "images_etrade_wallst_com.pem"); + ASSERT_NE(static_cast<X509Certificate*>(NULL), server_cert); + + scoped_refptr<X509Certificate> intermediate_cert = + ImportCertFromFile(certs_dir, "globalsign_orgv1_ca.pem"); + ASSERT_NE(static_cast<X509Certificate*>(NULL), intermediate_cert); + + scoped_refptr<X509Certificate> md5_root_cert = + ImportCertFromFile(certs_dir, "globalsign_root_ca_md5.pem"); + ASSERT_NE(static_cast<X509Certificate*>(NULL), md5_root_cert); + + X509Certificate::OSCertHandles intermediates; + intermediates.push_back(intermediate_cert->os_cert_handle()); + intermediates.push_back(md5_root_cert->os_cert_handle()); + scoped_refptr<X509Certificate> cert_chain = + X509Certificate::CreateFromHandle(server_cert->os_cert_handle(), + intermediates); + + CertVerifyResult verify_result; + int flags = 0; + int error = Verify(cert_chain, "images.etrade.wallst.com", flags, NULL, + empty_cert_list_, &verify_result); + if (error != OK) + EXPECT_EQ(ERR_CERT_DATE_INVALID, error); + + EXPECT_FALSE(verify_result.has_md5); + EXPECT_FALSE(verify_result.has_md5_ca); +} + +// Test for bug 94673. +TEST_F(CertVerifyProcTest, GoogleDigiNotarTest) { + base::FilePath certs_dir = GetTestCertsDirectory(); + + scoped_refptr<X509Certificate> server_cert = + ImportCertFromFile(certs_dir, "google_diginotar.pem"); + ASSERT_NE(static_cast<X509Certificate*>(NULL), server_cert); + + scoped_refptr<X509Certificate> intermediate_cert = + ImportCertFromFile(certs_dir, "diginotar_public_ca_2025.pem"); + ASSERT_NE(static_cast<X509Certificate*>(NULL), intermediate_cert); + + X509Certificate::OSCertHandles intermediates; + intermediates.push_back(intermediate_cert->os_cert_handle()); + scoped_refptr<X509Certificate> cert_chain = + X509Certificate::CreateFromHandle(server_cert->os_cert_handle(), + intermediates); + + CertVerifyResult verify_result; + int flags = CertVerifier::VERIFY_REV_CHECKING_ENABLED; + int error = Verify(cert_chain, "mail.google.com", flags, NULL, + empty_cert_list_, &verify_result); + EXPECT_NE(OK, error); + + // Now turn off revocation checking. Certificate verification should still + // fail. + flags = 0; + error = Verify(cert_chain, "mail.google.com", flags, NULL, + empty_cert_list_, &verify_result); + EXPECT_NE(OK, error); +} + +TEST_F(CertVerifyProcTest, DigiNotarCerts) { + static const char* const kDigiNotarFilenames[] = { + "diginotar_root_ca.pem", + "diginotar_cyber_ca.pem", + "diginotar_services_1024_ca.pem", + "diginotar_pkioverheid.pem", + "diginotar_pkioverheid_g2.pem", + NULL, + }; + + base::FilePath certs_dir = GetTestCertsDirectory(); + + for (size_t i = 0; kDigiNotarFilenames[i]; i++) { + scoped_refptr<X509Certificate> diginotar_cert = + ImportCertFromFile(certs_dir, kDigiNotarFilenames[i]); + std::string der_bytes; + ASSERT_TRUE(X509Certificate::GetDEREncoded( + diginotar_cert->os_cert_handle(), &der_bytes)); + + base::StringPiece spki; + ASSERT_TRUE(asn1::ExtractSPKIFromDERCert(der_bytes, &spki)); + + std::string spki_sha1 = base::SHA1HashString(spki.as_string()); + + HashValueVector public_keys; + HashValue hash(HASH_VALUE_SHA1); + ASSERT_EQ(hash.size(), spki_sha1.size()); + memcpy(hash.data(), spki_sha1.data(), spki_sha1.size()); + public_keys.push_back(hash); + + EXPECT_TRUE(CertVerifyProc::IsPublicKeyBlacklisted(public_keys)) << + "Public key not blocked for " << kDigiNotarFilenames[i]; + } +} + +TEST_F(CertVerifyProcTest, TestKnownRoot) { + base::FilePath certs_dir = GetTestCertsDirectory(); + CertificateList certs = CreateCertificateListFromFile( + certs_dir, "certse.pem", X509Certificate::FORMAT_AUTO); + ASSERT_EQ(3U, certs.size()); + + X509Certificate::OSCertHandles intermediates; + intermediates.push_back(certs[1]->os_cert_handle()); + intermediates.push_back(certs[2]->os_cert_handle()); + + scoped_refptr<X509Certificate> cert_chain = + X509Certificate::CreateFromHandle(certs[0]->os_cert_handle(), + intermediates); + + int flags = 0; + CertVerifyResult verify_result; + // This will blow up, June 8th, 2014. Sorry! Please disable and file a bug + // against agl. See also PublicKeyHashes. + int error = Verify(cert_chain, "cert.se", flags, NULL, + empty_cert_list_, &verify_result); + EXPECT_EQ(OK, error); + EXPECT_EQ(0U, verify_result.cert_status); + EXPECT_TRUE(verify_result.is_issued_by_known_root); +} + +TEST_F(CertVerifyProcTest, PublicKeyHashes) { + base::FilePath certs_dir = GetTestCertsDirectory(); + CertificateList certs = CreateCertificateListFromFile( + certs_dir, "certse.pem", X509Certificate::FORMAT_AUTO); + ASSERT_EQ(3U, certs.size()); + + X509Certificate::OSCertHandles intermediates; + intermediates.push_back(certs[1]->os_cert_handle()); + intermediates.push_back(certs[2]->os_cert_handle()); + + scoped_refptr<X509Certificate> cert_chain = + X509Certificate::CreateFromHandle(certs[0]->os_cert_handle(), + intermediates); + int flags = 0; + CertVerifyResult verify_result; + + // This will blow up, June 8th, 2014. Sorry! Please disable and file a bug + // against agl. See also TestKnownRoot. + int error = Verify(cert_chain, "cert.se", flags, NULL, + empty_cert_list_, &verify_result); + EXPECT_EQ(OK, error); + EXPECT_EQ(0U, verify_result.cert_status); + ASSERT_LE(3u, verify_result.public_key_hashes.size()); + + HashValueVector sha1_hashes; + for (unsigned i = 0; i < verify_result.public_key_hashes.size(); ++i) { + if (verify_result.public_key_hashes[i].tag != HASH_VALUE_SHA1) + continue; + sha1_hashes.push_back(verify_result.public_key_hashes[i]); + } + ASSERT_LE(3u, sha1_hashes.size()); + + for (unsigned i = 0; i < 3; ++i) { + EXPECT_EQ(HexEncode(kCertSESPKIs[i], base::kSHA1Length), + HexEncode(sha1_hashes[i].data(), base::kSHA1Length)); + } +} + +// A regression test for http://crbug.com/70293. +// The Key Usage extension in this RSA SSL server certificate does not have +// the keyEncipherment bit. +TEST_F(CertVerifyProcTest, InvalidKeyUsage) { + base::FilePath certs_dir = GetTestCertsDirectory(); + + scoped_refptr<X509Certificate> server_cert = + ImportCertFromFile(certs_dir, "invalid_key_usage_cert.der"); + ASSERT_NE(static_cast<X509Certificate*>(NULL), server_cert); + + int flags = 0; + CertVerifyResult verify_result; + int error = Verify(server_cert, "jira.aquameta.com", flags, NULL, + empty_cert_list_, &verify_result); +#if defined(USE_OPENSSL) + // This certificate has two errors: "invalid key usage" and "untrusted CA". + // However, OpenSSL returns only one (the latter), and we can't detect + // the other errors. + EXPECT_EQ(ERR_CERT_AUTHORITY_INVALID, error); +#else + EXPECT_EQ(ERR_CERT_INVALID, error); + EXPECT_TRUE(verify_result.cert_status & CERT_STATUS_INVALID); +#endif + // TODO(wtc): fix http://crbug.com/75520 to get all the certificate errors + // from NSS. +#if !defined(USE_NSS) && !defined(OS_IOS) + // The certificate is issued by an unknown CA. + EXPECT_TRUE(verify_result.cert_status & CERT_STATUS_AUTHORITY_INVALID); +#endif +} + +// Basic test for returning the chain in CertVerifyResult. Note that the +// returned chain may just be a reflection of the originally supplied chain; +// that is, if any errors occur, the default chain returned is an exact copy +// of the certificate to be verified. The remaining VerifyReturn* tests are +// used to ensure that the actual, verified chain is being returned by +// Verify(). +TEST_F(CertVerifyProcTest, VerifyReturnChainBasic) { + base::FilePath certs_dir = GetTestCertsDirectory(); + CertificateList certs = CreateCertificateListFromFile( + certs_dir, "x509_verify_results.chain.pem", + X509Certificate::FORMAT_AUTO); + ASSERT_EQ(3U, certs.size()); + + X509Certificate::OSCertHandles intermediates; + intermediates.push_back(certs[1]->os_cert_handle()); + intermediates.push_back(certs[2]->os_cert_handle()); + + ScopedTestRoot scoped_root(certs[2]); + + scoped_refptr<X509Certificate> google_full_chain = + X509Certificate::CreateFromHandle(certs[0]->os_cert_handle(), + intermediates); + ASSERT_NE(static_cast<X509Certificate*>(NULL), google_full_chain); + ASSERT_EQ(2U, google_full_chain->GetIntermediateCertificates().size()); + + CertVerifyResult verify_result; + EXPECT_EQ(static_cast<X509Certificate*>(NULL), verify_result.verified_cert); + int error = Verify(google_full_chain, "127.0.0.1", 0, NULL, + empty_cert_list_, &verify_result); + EXPECT_EQ(OK, error); + ASSERT_NE(static_cast<X509Certificate*>(NULL), verify_result.verified_cert); + + EXPECT_NE(google_full_chain, verify_result.verified_cert); + EXPECT_TRUE(X509Certificate::IsSameOSCert( + google_full_chain->os_cert_handle(), + verify_result.verified_cert->os_cert_handle())); + const X509Certificate::OSCertHandles& return_intermediates = + verify_result.verified_cert->GetIntermediateCertificates(); + ASSERT_EQ(2U, return_intermediates.size()); + EXPECT_TRUE(X509Certificate::IsSameOSCert(return_intermediates[0], + certs[1]->os_cert_handle())); + EXPECT_TRUE(X509Certificate::IsSameOSCert(return_intermediates[1], + certs[2]->os_cert_handle())); +} + +// Test that the certificate returned in CertVerifyResult is able to reorder +// certificates that are not ordered from end-entity to root. While this is +// a protocol violation if sent during a TLS handshake, if multiple sources +// of intermediate certificates are combined, it's possible that order may +// not be maintained. +TEST_F(CertVerifyProcTest, VerifyReturnChainProperlyOrdered) { + base::FilePath certs_dir = GetTestCertsDirectory(); + CertificateList certs = CreateCertificateListFromFile( + certs_dir, "x509_verify_results.chain.pem", + X509Certificate::FORMAT_AUTO); + ASSERT_EQ(3U, certs.size()); + + // Construct the chain out of order. + X509Certificate::OSCertHandles intermediates; + intermediates.push_back(certs[2]->os_cert_handle()); + intermediates.push_back(certs[1]->os_cert_handle()); + + ScopedTestRoot scoped_root(certs[2]); + + scoped_refptr<X509Certificate> google_full_chain = + X509Certificate::CreateFromHandle(certs[0]->os_cert_handle(), + intermediates); + ASSERT_NE(static_cast<X509Certificate*>(NULL), google_full_chain); + ASSERT_EQ(2U, google_full_chain->GetIntermediateCertificates().size()); + + CertVerifyResult verify_result; + EXPECT_EQ(static_cast<X509Certificate*>(NULL), verify_result.verified_cert); + int error = Verify(google_full_chain, "127.0.0.1", 0, NULL, + empty_cert_list_, &verify_result); + EXPECT_EQ(OK, error); + ASSERT_NE(static_cast<X509Certificate*>(NULL), verify_result.verified_cert); + + EXPECT_NE(google_full_chain, verify_result.verified_cert); + EXPECT_TRUE(X509Certificate::IsSameOSCert( + google_full_chain->os_cert_handle(), + verify_result.verified_cert->os_cert_handle())); + const X509Certificate::OSCertHandles& return_intermediates = + verify_result.verified_cert->GetIntermediateCertificates(); + ASSERT_EQ(2U, return_intermediates.size()); + EXPECT_TRUE(X509Certificate::IsSameOSCert(return_intermediates[0], + certs[1]->os_cert_handle())); + EXPECT_TRUE(X509Certificate::IsSameOSCert(return_intermediates[1], + certs[2]->os_cert_handle())); +} + +// Test that Verify() filters out certificates which are not related to +// or part of the certificate chain being verified. +TEST_F(CertVerifyProcTest, VerifyReturnChainFiltersUnrelatedCerts) { + base::FilePath certs_dir = GetTestCertsDirectory(); + CertificateList certs = CreateCertificateListFromFile( + certs_dir, "x509_verify_results.chain.pem", + X509Certificate::FORMAT_AUTO); + ASSERT_EQ(3U, certs.size()); + ScopedTestRoot scoped_root(certs[2]); + + scoped_refptr<X509Certificate> unrelated_dod_certificate = + ImportCertFromFile(certs_dir, "dod_ca_17_cert.der"); + scoped_refptr<X509Certificate> unrelated_dod_certificate2 = + ImportCertFromFile(certs_dir, "dod_root_ca_2_cert.der"); + ASSERT_NE(static_cast<X509Certificate*>(NULL), unrelated_dod_certificate); + ASSERT_NE(static_cast<X509Certificate*>(NULL), unrelated_dod_certificate2); + + // Interject unrelated certificates into the list of intermediates. + X509Certificate::OSCertHandles intermediates; + intermediates.push_back(unrelated_dod_certificate->os_cert_handle()); + intermediates.push_back(certs[1]->os_cert_handle()); + intermediates.push_back(unrelated_dod_certificate2->os_cert_handle()); + intermediates.push_back(certs[2]->os_cert_handle()); + + scoped_refptr<X509Certificate> google_full_chain = + X509Certificate::CreateFromHandle(certs[0]->os_cert_handle(), + intermediates); + ASSERT_NE(static_cast<X509Certificate*>(NULL), google_full_chain); + ASSERT_EQ(4U, google_full_chain->GetIntermediateCertificates().size()); + + CertVerifyResult verify_result; + EXPECT_EQ(static_cast<X509Certificate*>(NULL), verify_result.verified_cert); + int error = Verify(google_full_chain, "127.0.0.1", 0, NULL, + empty_cert_list_, &verify_result); + EXPECT_EQ(OK, error); + ASSERT_NE(static_cast<X509Certificate*>(NULL), verify_result.verified_cert); + + EXPECT_NE(google_full_chain, verify_result.verified_cert); + EXPECT_TRUE(X509Certificate::IsSameOSCert( + google_full_chain->os_cert_handle(), + verify_result.verified_cert->os_cert_handle())); + const X509Certificate::OSCertHandles& return_intermediates = + verify_result.verified_cert->GetIntermediateCertificates(); + ASSERT_EQ(2U, return_intermediates.size()); + EXPECT_TRUE(X509Certificate::IsSameOSCert(return_intermediates[0], + certs[1]->os_cert_handle())); + EXPECT_TRUE(X509Certificate::IsSameOSCert(return_intermediates[1], + certs[2]->os_cert_handle())); +} + +TEST_F(CertVerifyProcTest, AdditionalTrustAnchors) { + if (!SupportsAdditionalTrustAnchors()) { + LOG(INFO) << "Skipping this test in this platform."; + return; + } + + // |ca_cert| is the issuer of |cert|. + CertificateList ca_cert_list = CreateCertificateListFromFile( + GetTestCertsDirectory(), "root_ca_cert.crt", + X509Certificate::FORMAT_AUTO); + ASSERT_EQ(1U, ca_cert_list.size()); + scoped_refptr<X509Certificate> ca_cert(ca_cert_list[0]); + + CertificateList cert_list = CreateCertificateListFromFile( + GetTestCertsDirectory(), "ok_cert.pem", + X509Certificate::FORMAT_AUTO); + ASSERT_EQ(1U, cert_list.size()); + scoped_refptr<X509Certificate> cert(cert_list[0]); + + // Verification of |cert| fails when |ca_cert| is not in the trust anchors + // list. + int flags = 0; + CertVerifyResult verify_result; + int error = Verify(cert, "127.0.0.1", flags, NULL, + empty_cert_list_, &verify_result); + EXPECT_EQ(ERR_CERT_AUTHORITY_INVALID, error); + EXPECT_EQ(CERT_STATUS_AUTHORITY_INVALID, verify_result.cert_status); + EXPECT_FALSE(verify_result.is_issued_by_additional_trust_anchor); + + // Now add the |ca_cert| to the |trust_anchors|, and verification should pass. + CertificateList trust_anchors; + trust_anchors.push_back(ca_cert); + error = Verify(cert, "127.0.0.1", flags, NULL, trust_anchors, &verify_result); + EXPECT_EQ(OK, error); + EXPECT_EQ(0U, verify_result.cert_status); + EXPECT_TRUE(verify_result.is_issued_by_additional_trust_anchor); + + // Clearing the |trust_anchors| makes verification fail again (the cache + // should be skipped). + error = Verify(cert, "127.0.0.1", flags, NULL, + empty_cert_list_, &verify_result); + EXPECT_EQ(ERR_CERT_AUTHORITY_INVALID, error); + EXPECT_EQ(CERT_STATUS_AUTHORITY_INVALID, verify_result.cert_status); + EXPECT_FALSE(verify_result.is_issued_by_additional_trust_anchor); +} + +#if defined(USE_NSS) || defined(OS_IOS) || defined(OS_WIN) || defined(OS_MACOSX) +static const uint8 kCRLSetThawteSPKIBlocked[] = { + 0x8e, 0x00, 0x7b, 0x22, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x3a, + 0x30, 0x2c, 0x22, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, + 0x65, 0x22, 0x3a, 0x22, 0x43, 0x52, 0x4c, 0x53, 0x65, 0x74, 0x22, 0x2c, 0x22, + 0x53, 0x65, 0x71, 0x75, 0x65, 0x6e, 0x63, 0x65, 0x22, 0x3a, 0x30, 0x2c, 0x22, + 0x44, 0x65, 0x6c, 0x74, 0x61, 0x46, 0x72, 0x6f, 0x6d, 0x22, 0x3a, 0x30, 0x2c, + 0x22, 0x4e, 0x75, 0x6d, 0x50, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x73, 0x22, 0x3a, + 0x30, 0x2c, 0x22, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x65, 0x64, 0x53, 0x50, 0x4b, + 0x49, 0x73, 0x22, 0x3a, 0x5b, 0x22, 0x36, 0x58, 0x36, 0x4d, 0x78, 0x52, 0x37, + 0x58, 0x70, 0x4d, 0x51, 0x4b, 0x78, 0x49, 0x41, 0x39, 0x50, 0x6a, 0x36, 0x37, + 0x36, 0x38, 0x76, 0x74, 0x55, 0x6b, 0x6b, 0x7a, 0x48, 0x79, 0x7a, 0x41, 0x6f, + 0x6d, 0x6f, 0x4f, 0x68, 0x4b, 0x55, 0x6e, 0x7a, 0x73, 0x55, 0x3d, 0x22, 0x5d, + 0x7d, +}; + +static const uint8 kCRLSetThawteSerialBlocked[] = { + 0x60, 0x00, 0x7b, 0x22, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x3a, + 0x30, 0x2c, 0x22, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, + 0x65, 0x22, 0x3a, 0x22, 0x43, 0x52, 0x4c, 0x53, 0x65, 0x74, 0x22, 0x2c, 0x22, + 0x53, 0x65, 0x71, 0x75, 0x65, 0x6e, 0x63, 0x65, 0x22, 0x3a, 0x30, 0x2c, 0x22, + 0x44, 0x65, 0x6c, 0x74, 0x61, 0x46, 0x72, 0x6f, 0x6d, 0x22, 0x3a, 0x30, 0x2c, + 0x22, 0x4e, 0x75, 0x6d, 0x50, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x73, 0x22, 0x3a, + 0x31, 0x2c, 0x22, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x65, 0x64, 0x53, 0x50, 0x4b, + 0x49, 0x73, 0x22, 0x3a, 0x5b, 0x5d, 0x7d, 0xb1, 0x12, 0x41, 0x42, 0xa5, 0xa1, + 0xa5, 0xa2, 0x88, 0x19, 0xc7, 0x35, 0x34, 0x0e, 0xff, 0x8c, 0x9e, 0x2f, 0x81, + 0x68, 0xfe, 0xe3, 0xba, 0x18, 0x7f, 0x25, 0x3b, 0xc1, 0xa3, 0x92, 0xd7, 0xe2, + // Note that this is actually blocking two serial numbers because on XP and + // Vista, CryptoAPI finds a different Thawte certificate. + 0x02, 0x00, 0x00, 0x00, + 0x04, 0x30, 0x00, 0x00, 0x02, + 0x04, 0x30, 0x00, 0x00, 0x06, +}; + +static const uint8 kCRLSetGoogleSerialBlocked[] = { + 0x60, 0x00, 0x7b, 0x22, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x3a, + 0x30, 0x2c, 0x22, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, + 0x65, 0x22, 0x3a, 0x22, 0x43, 0x52, 0x4c, 0x53, 0x65, 0x74, 0x22, 0x2c, 0x22, + 0x53, 0x65, 0x71, 0x75, 0x65, 0x6e, 0x63, 0x65, 0x22, 0x3a, 0x30, 0x2c, 0x22, + 0x44, 0x65, 0x6c, 0x74, 0x61, 0x46, 0x72, 0x6f, 0x6d, 0x22, 0x3a, 0x30, 0x2c, + 0x22, 0x4e, 0x75, 0x6d, 0x50, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x73, 0x22, 0x3a, + 0x31, 0x2c, 0x22, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x65, 0x64, 0x53, 0x50, 0x4b, + 0x49, 0x73, 0x22, 0x3a, 0x5b, 0x5d, 0x7d, 0xe9, 0x7e, 0x8c, 0xc5, 0x1e, 0xd7, + 0xa4, 0xc4, 0x0a, 0xc4, 0x80, 0x3d, 0x3e, 0x3e, 0xbb, 0xeb, 0xcb, 0xed, 0x52, + 0x49, 0x33, 0x1f, 0x2c, 0xc0, 0xa2, 0x6a, 0x0e, 0x84, 0xa5, 0x27, 0xce, 0xc5, + 0x01, 0x00, 0x00, 0x00, 0x10, 0x4f, 0x9d, 0x96, 0xd9, 0x66, 0xb0, 0x99, 0x2b, + 0x54, 0xc2, 0x95, 0x7c, 0xb4, 0x15, 0x7d, 0x4d, +}; + +// Test that CRLSets are effective in making a certificate appear to be +// revoked. +TEST_F(CertVerifyProcTest, CRLSet) { + CertificateList certs = CreateCertificateListFromFile( + GetTestCertsDirectory(), + "googlenew.chain.pem", + X509Certificate::FORMAT_PEM_CERT_SEQUENCE); + + X509Certificate::OSCertHandles intermediates; + intermediates.push_back(certs[1]->os_cert_handle()); + + scoped_refptr<X509Certificate> google_full_chain = + X509Certificate::CreateFromHandle(certs[0]->os_cert_handle(), + intermediates); + + CertVerifyResult verify_result; + int error = Verify(google_full_chain, "www.google.com", 0, NULL, + empty_cert_list_, &verify_result); + EXPECT_EQ(OK, error); + + // First test blocking by SPKI. + base::StringPiece crl_set_bytes( + reinterpret_cast<const char*>(kCRLSetThawteSPKIBlocked), + sizeof(kCRLSetThawteSPKIBlocked)); + scoped_refptr<CRLSet> crl_set; + ASSERT_TRUE(CRLSet::Parse(crl_set_bytes, &crl_set)); + + error = Verify(google_full_chain, "www.google.com", 0, crl_set.get(), + empty_cert_list_, &verify_result); + EXPECT_EQ(ERR_CERT_REVOKED, error); + + // Second, test revocation by serial number of a cert directly under the + // root. + crl_set_bytes = base::StringPiece( + reinterpret_cast<const char*>(kCRLSetThawteSerialBlocked), + sizeof(kCRLSetThawteSerialBlocked)); + ASSERT_TRUE(CRLSet::Parse(crl_set_bytes, &crl_set)); + + error = Verify(google_full_chain, "www.google.com", 0, crl_set.get(), + empty_cert_list_, &verify_result); + EXPECT_EQ(ERR_CERT_REVOKED, error); + + // Lastly, test revocation by serial number of a certificate not under the + // root. + crl_set_bytes = base::StringPiece( + reinterpret_cast<const char*>(kCRLSetGoogleSerialBlocked), + sizeof(kCRLSetGoogleSerialBlocked)); + ASSERT_TRUE(CRLSet::Parse(crl_set_bytes, &crl_set)); + + error = Verify(google_full_chain, "www.google.com", 0, crl_set.get(), + empty_cert_list_, &verify_result); + EXPECT_EQ(ERR_CERT_REVOKED, error); +} +#endif + +struct WeakDigestTestData { + const char* root_cert_filename; + const char* intermediate_cert_filename; + const char* ee_cert_filename; + bool expected_has_md5; + bool expected_has_md4; + bool expected_has_md2; + bool expected_has_md5_ca; + bool expected_has_md2_ca; +}; + +// GTest 'magic' pretty-printer, so that if/when a test fails, it knows how +// to output the parameter that was passed. Without this, it will simply +// attempt to print out the first twenty bytes of the object, which depending +// on platform and alignment, may result in an invalid read. +void PrintTo(const WeakDigestTestData& data, std::ostream* os) { + *os << "root: " + << (data.root_cert_filename ? data.root_cert_filename : "none") + << "; intermediate: " << data.intermediate_cert_filename + << "; end-entity: " << data.ee_cert_filename; +} + +class CertVerifyProcWeakDigestTest + : public CertVerifyProcTest, + public testing::WithParamInterface<WeakDigestTestData> { + public: + CertVerifyProcWeakDigestTest() {} + virtual ~CertVerifyProcWeakDigestTest() {} +}; + +TEST_P(CertVerifyProcWeakDigestTest, Verify) { + WeakDigestTestData data = GetParam(); + base::FilePath certs_dir = GetTestCertsDirectory(); + + ScopedTestRoot test_root; + if (data.root_cert_filename) { + scoped_refptr<X509Certificate> root_cert = + ImportCertFromFile(certs_dir, data.root_cert_filename); + ASSERT_NE(static_cast<X509Certificate*>(NULL), root_cert); + test_root.Reset(root_cert); + } + + scoped_refptr<X509Certificate> intermediate_cert = + ImportCertFromFile(certs_dir, data.intermediate_cert_filename); + ASSERT_NE(static_cast<X509Certificate*>(NULL), intermediate_cert); + scoped_refptr<X509Certificate> ee_cert = + ImportCertFromFile(certs_dir, data.ee_cert_filename); + ASSERT_NE(static_cast<X509Certificate*>(NULL), ee_cert); + + X509Certificate::OSCertHandles intermediates; + intermediates.push_back(intermediate_cert->os_cert_handle()); + + scoped_refptr<X509Certificate> ee_chain = + X509Certificate::CreateFromHandle(ee_cert->os_cert_handle(), + intermediates); + ASSERT_NE(static_cast<X509Certificate*>(NULL), ee_chain); + + int flags = 0; + CertVerifyResult verify_result; + int rv = Verify(ee_chain, "127.0.0.1", flags, NULL, + empty_cert_list_, &verify_result); + EXPECT_EQ(data.expected_has_md5, verify_result.has_md5); + EXPECT_EQ(data.expected_has_md4, verify_result.has_md4); + EXPECT_EQ(data.expected_has_md2, verify_result.has_md2); + EXPECT_EQ(data.expected_has_md5_ca, verify_result.has_md5_ca); + EXPECT_EQ(data.expected_has_md2_ca, verify_result.has_md2_ca); + EXPECT_FALSE(verify_result.is_issued_by_additional_trust_anchor); + + // Ensure that MD4 and MD2 are tagged as invalid. + if (data.expected_has_md4 || data.expected_has_md2) { + EXPECT_EQ(CERT_STATUS_INVALID, + verify_result.cert_status & CERT_STATUS_INVALID); + } + + // Ensure that MD5 is flagged as weak. + if (data.expected_has_md5) { + EXPECT_EQ( + CERT_STATUS_WEAK_SIGNATURE_ALGORITHM, + verify_result.cert_status & CERT_STATUS_WEAK_SIGNATURE_ALGORITHM); + } + + // If a root cert is present, then check that the chain was rejected if any + // weak algorithms are present. This is only checked when a root cert is + // present because the error reported for incomplete chains with weak + // algorithms depends on which implementation was used to validate (NSS, + // OpenSSL, CryptoAPI, Security.framework) and upon which weak algorithm + // present (MD2, MD4, MD5). + if (data.root_cert_filename) { + if (data.expected_has_md4 || data.expected_has_md2) { + EXPECT_EQ(ERR_CERT_INVALID, rv); + } else if (data.expected_has_md5) { + EXPECT_EQ(ERR_CERT_WEAK_SIGNATURE_ALGORITHM, rv); + } else { + EXPECT_EQ(OK, rv); + } + } +} + +// Unlike TEST/TEST_F, which are macros that expand to further macros, +// INSTANTIATE_TEST_CASE_P is a macro that expands directly to code that +// stringizes the arguments. As a result, macros passed as parameters (such as +// prefix or test_case_name) will not be expanded by the preprocessor. To work +// around this, indirect the macro for INSTANTIATE_TEST_CASE_P, so that the +// pre-processor will expand macros such as MAYBE_test_name before +// instantiating the test. +#define WRAPPED_INSTANTIATE_TEST_CASE_P(prefix, test_case_name, generator) \ + INSTANTIATE_TEST_CASE_P(prefix, test_case_name, generator) + +// The signature algorithm of the root CA should not matter. +const WeakDigestTestData kVerifyRootCATestData[] = { + { "weak_digest_md5_root.pem", "weak_digest_sha1_intermediate.pem", + "weak_digest_sha1_ee.pem", false, false, false, false, false }, +#if defined(USE_OPENSSL) || defined(OS_WIN) + // MD4 is not supported by OS X / NSS + { "weak_digest_md4_root.pem", "weak_digest_sha1_intermediate.pem", + "weak_digest_sha1_ee.pem", false, false, false, false, false }, +#endif + { "weak_digest_md2_root.pem", "weak_digest_sha1_intermediate.pem", + "weak_digest_sha1_ee.pem", false, false, false, false, false }, +}; +INSTANTIATE_TEST_CASE_P(VerifyRoot, CertVerifyProcWeakDigestTest, + testing::ValuesIn(kVerifyRootCATestData)); + +// The signature algorithm of intermediates should be properly detected. +const WeakDigestTestData kVerifyIntermediateCATestData[] = { + { "weak_digest_sha1_root.pem", "weak_digest_md5_intermediate.pem", + "weak_digest_sha1_ee.pem", true, false, false, true, false }, +#if defined(USE_OPENSSL) || defined(OS_WIN) + // MD4 is not supported by OS X / NSS + { "weak_digest_sha1_root.pem", "weak_digest_md4_intermediate.pem", + "weak_digest_sha1_ee.pem", false, true, false, false, false }, +#endif + { "weak_digest_sha1_root.pem", "weak_digest_md2_intermediate.pem", + "weak_digest_sha1_ee.pem", false, false, true, false, true }, +}; +// Disabled on NSS - MD4 is not supported, and MD2 and MD5 are disabled. +#if defined(USE_NSS) || defined(OS_IOS) +#define MAYBE_VerifyIntermediate DISABLED_VerifyIntermediate +#else +#define MAYBE_VerifyIntermediate VerifyIntermediate +#endif +WRAPPED_INSTANTIATE_TEST_CASE_P( + MAYBE_VerifyIntermediate, + CertVerifyProcWeakDigestTest, + testing::ValuesIn(kVerifyIntermediateCATestData)); + +// The signature algorithm of end-entity should be properly detected. +const WeakDigestTestData kVerifyEndEntityTestData[] = { + { "weak_digest_sha1_root.pem", "weak_digest_sha1_intermediate.pem", + "weak_digest_md5_ee.pem", true, false, false, false, false }, +#if defined(USE_OPENSSL) || defined(OS_WIN) + // MD4 is not supported by OS X / NSS + { "weak_digest_sha1_root.pem", "weak_digest_sha1_intermediate.pem", + "weak_digest_md4_ee.pem", false, true, false, false, false }, +#endif + { "weak_digest_sha1_root.pem", "weak_digest_sha1_intermediate.pem", + "weak_digest_md2_ee.pem", false, false, true, false, false }, +}; +// Disabled on NSS - NSS caches chains/signatures in such a way that cannot +// be cleared until NSS is cleanly shutdown, which is not presently supported +// in Chromium. +#if defined(USE_NSS) || defined(OS_IOS) +#define MAYBE_VerifyEndEntity DISABLED_VerifyEndEntity +#else +#define MAYBE_VerifyEndEntity VerifyEndEntity +#endif +WRAPPED_INSTANTIATE_TEST_CASE_P(MAYBE_VerifyEndEntity, + CertVerifyProcWeakDigestTest, + testing::ValuesIn(kVerifyEndEntityTestData)); + +// Incomplete chains should still report the status of the intermediate. +const WeakDigestTestData kVerifyIncompleteIntermediateTestData[] = { + { NULL, "weak_digest_md5_intermediate.pem", "weak_digest_sha1_ee.pem", + true, false, false, true, false }, +#if defined(USE_OPENSSL) || defined(OS_WIN) + // MD4 is not supported by OS X / NSS + { NULL, "weak_digest_md4_intermediate.pem", "weak_digest_sha1_ee.pem", + false, true, false, false, false }, +#endif + { NULL, "weak_digest_md2_intermediate.pem", "weak_digest_sha1_ee.pem", + false, false, true, false, true }, +}; +// Disabled on NSS - libpkix does not return constructed chains on error, +// preventing us from detecting/inspecting the verified chain. +#if defined(USE_NSS) || defined(OS_IOS) +#define MAYBE_VerifyIncompleteIntermediate \ + DISABLED_VerifyIncompleteIntermediate +#else +#define MAYBE_VerifyIncompleteIntermediate VerifyIncompleteIntermediate +#endif +WRAPPED_INSTANTIATE_TEST_CASE_P( + MAYBE_VerifyIncompleteIntermediate, + CertVerifyProcWeakDigestTest, + testing::ValuesIn(kVerifyIncompleteIntermediateTestData)); + +// Incomplete chains should still report the status of the end-entity. +const WeakDigestTestData kVerifyIncompleteEETestData[] = { + { NULL, "weak_digest_sha1_intermediate.pem", "weak_digest_md5_ee.pem", + true, false, false, false, false }, +#if defined(USE_OPENSSL) || defined(OS_WIN) + // MD4 is not supported by OS X / NSS + { NULL, "weak_digest_sha1_intermediate.pem", "weak_digest_md4_ee.pem", + false, true, false, false, false }, +#endif + { NULL, "weak_digest_sha1_intermediate.pem", "weak_digest_md2_ee.pem", + false, false, true, false, false }, +}; +// Disabled on NSS - libpkix does not return constructed chains on error, +// preventing us from detecting/inspecting the verified chain. +#if defined(USE_NSS) || defined(OS_IOS) +#define MAYBE_VerifyIncompleteEndEntity DISABLED_VerifyIncompleteEndEntity +#else +#define MAYBE_VerifyIncompleteEndEntity VerifyIncompleteEndEntity +#endif +WRAPPED_INSTANTIATE_TEST_CASE_P( + MAYBE_VerifyIncompleteEndEntity, + CertVerifyProcWeakDigestTest, + testing::ValuesIn(kVerifyIncompleteEETestData)); + +// Differing algorithms between the intermediate and the EE should still be +// reported. +const WeakDigestTestData kVerifyMixedTestData[] = { + { "weak_digest_sha1_root.pem", "weak_digest_md5_intermediate.pem", + "weak_digest_md2_ee.pem", true, false, true, true, false }, + { "weak_digest_sha1_root.pem", "weak_digest_md2_intermediate.pem", + "weak_digest_md5_ee.pem", true, false, true, false, true }, +#if defined(USE_OPENSSL) || defined(OS_WIN) + // MD4 is not supported by OS X / NSS + { "weak_digest_sha1_root.pem", "weak_digest_md4_intermediate.pem", + "weak_digest_md2_ee.pem", false, true, true, false, false }, +#endif +}; +// NSS does not support MD4 and does not enable MD2 by default, making all +// permutations invalid. +#if defined(USE_NSS) || defined(OS_IOS) +#define MAYBE_VerifyMixed DISABLED_VerifyMixed +#else +#define MAYBE_VerifyMixed VerifyMixed +#endif +WRAPPED_INSTANTIATE_TEST_CASE_P( + MAYBE_VerifyMixed, + CertVerifyProcWeakDigestTest, + testing::ValuesIn(kVerifyMixedTestData)); + +} // namespace net diff --git a/net/cert/cert_verify_proc_win.cc b/net/cert/cert_verify_proc_win.cc new file mode 100644 index 0000000..0e48b2d --- /dev/null +++ b/net/cert/cert_verify_proc_win.cc @@ -0,0 +1,755 @@ +// Copyright (c) 2012 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 "net/cert/cert_verify_proc_win.h" + +#include <string> +#include <vector> + +#include "base/memory/scoped_ptr.h" +#include "base/sha1.h" +#include "base/string_util.h" +#include "base/utf_string_conversions.h" +#include "crypto/capi_util.h" +#include "crypto/scoped_capi_types.h" +#include "crypto/sha2.h" +#include "net/base/net_errors.h" +#include "net/cert/asn1_util.h" +#include "net/cert/cert_status_flags.h" +#include "net/cert/cert_verifier.h" +#include "net/cert/cert_verify_result.h" +#include "net/cert/crl_set.h" +#include "net/cert/ev_root_ca_metadata.h" +#include "net/cert/test_root_certs.h" +#include "net/cert/x509_certificate.h" +#include "net/cert/x509_certificate_known_roots_win.h" + +#pragma comment(lib, "crypt32.lib") + +#if !defined(CERT_TRUST_HAS_WEAK_SIGNATURE) +// This was introduced in Windows 8 / Windows Server 2012, but retroactively +// ported as far back as Windows XP via system update. +#define CERT_TRUST_HAS_WEAK_SIGNATURE 0x00100000 +#endif + +namespace net { + +namespace { + +struct FreeChainEngineFunctor { + void operator()(HCERTCHAINENGINE engine) const { + if (engine) + CertFreeCertificateChainEngine(engine); + } +}; + +struct FreeCertChainContextFunctor { + void operator()(PCCERT_CHAIN_CONTEXT chain_context) const { + if (chain_context) + CertFreeCertificateChain(chain_context); + } +}; + +struct FreeCertContextFunctor { + void operator()(PCCERT_CONTEXT context) const { + if (context) + CertFreeCertificateContext(context); + } +}; + +typedef crypto::ScopedCAPIHandle<HCERTCHAINENGINE, FreeChainEngineFunctor> + ScopedHCERTCHAINENGINE; + +typedef scoped_ptr_malloc<const CERT_CHAIN_CONTEXT, + FreeCertChainContextFunctor> + ScopedPCCERT_CHAIN_CONTEXT; + +typedef scoped_ptr_malloc<const CERT_CONTEXT, + FreeCertContextFunctor> ScopedPCCERT_CONTEXT; + +//----------------------------------------------------------------------------- + +int MapSecurityError(SECURITY_STATUS err) { + // There are numerous security error codes, but these are the ones we thus + // far find interesting. + switch (err) { + case SEC_E_WRONG_PRINCIPAL: // Schannel + case CERT_E_CN_NO_MATCH: // CryptoAPI + return ERR_CERT_COMMON_NAME_INVALID; + case SEC_E_UNTRUSTED_ROOT: // Schannel + case CERT_E_UNTRUSTEDROOT: // CryptoAPI + return ERR_CERT_AUTHORITY_INVALID; + case SEC_E_CERT_EXPIRED: // Schannel + case CERT_E_EXPIRED: // CryptoAPI + return ERR_CERT_DATE_INVALID; + case CRYPT_E_NO_REVOCATION_CHECK: + return ERR_CERT_NO_REVOCATION_MECHANISM; + case CRYPT_E_REVOCATION_OFFLINE: + return ERR_CERT_UNABLE_TO_CHECK_REVOCATION; + case CRYPT_E_REVOKED: // Schannel and CryptoAPI + return ERR_CERT_REVOKED; + case SEC_E_CERT_UNKNOWN: + case CERT_E_ROLE: + return ERR_CERT_INVALID; + case CERT_E_WRONG_USAGE: + // TODO(wtc): Should we add ERR_CERT_WRONG_USAGE? + return ERR_CERT_INVALID; + // We received an unexpected_message or illegal_parameter alert message + // from the server. + case SEC_E_ILLEGAL_MESSAGE: + return ERR_SSL_PROTOCOL_ERROR; + case SEC_E_ALGORITHM_MISMATCH: + return ERR_SSL_VERSION_OR_CIPHER_MISMATCH; + case SEC_E_INVALID_HANDLE: + return ERR_UNEXPECTED; + case SEC_E_OK: + return OK; + default: + LOG(WARNING) << "Unknown error " << err << " mapped to net::ERR_FAILED"; + return ERR_FAILED; + } +} + +// Map the errors in the chain_context->TrustStatus.dwErrorStatus returned by +// CertGetCertificateChain to our certificate status flags. +int MapCertChainErrorStatusToCertStatus(DWORD error_status) { + CertStatus cert_status = 0; + + // We don't include CERT_TRUST_IS_NOT_TIME_NESTED because it's obsolete and + // we wouldn't consider it an error anyway + const DWORD kDateInvalidErrors = CERT_TRUST_IS_NOT_TIME_VALID | + CERT_TRUST_CTL_IS_NOT_TIME_VALID; + if (error_status & kDateInvalidErrors) + cert_status |= CERT_STATUS_DATE_INVALID; + + const DWORD kAuthorityInvalidErrors = CERT_TRUST_IS_UNTRUSTED_ROOT | + CERT_TRUST_IS_EXPLICIT_DISTRUST | + CERT_TRUST_IS_PARTIAL_CHAIN; + if (error_status & kAuthorityInvalidErrors) + cert_status |= CERT_STATUS_AUTHORITY_INVALID; + + if ((error_status & CERT_TRUST_REVOCATION_STATUS_UNKNOWN) && + !(error_status & CERT_TRUST_IS_OFFLINE_REVOCATION)) + cert_status |= CERT_STATUS_NO_REVOCATION_MECHANISM; + + if (error_status & CERT_TRUST_IS_OFFLINE_REVOCATION) + cert_status |= CERT_STATUS_UNABLE_TO_CHECK_REVOCATION; + + if (error_status & CERT_TRUST_IS_REVOKED) + cert_status |= CERT_STATUS_REVOKED; + + const DWORD kWrongUsageErrors = CERT_TRUST_IS_NOT_VALID_FOR_USAGE | + CERT_TRUST_CTL_IS_NOT_VALID_FOR_USAGE; + if (error_status & kWrongUsageErrors) { + // TODO(wtc): Should we add CERT_STATUS_WRONG_USAGE? + cert_status |= CERT_STATUS_INVALID; + } + + if (error_status & CERT_TRUST_IS_NOT_SIGNATURE_VALID) { + // Check for a signature that does not meet the OS criteria for strong + // signatures. + // Note: These checks may be more restrictive than the current weak key + // criteria implemented within CertVerifier, such as excluding SHA-1 or + // excluding RSA keys < 2048 bits. However, if the user has configured + // these more stringent checks, respect that configuration and err on the + // more restrictive criteria. + if (error_status & CERT_TRUST_HAS_WEAK_SIGNATURE) { + cert_status |= CERT_STATUS_WEAK_KEY; + } else { + cert_status |= CERT_STATUS_INVALID; + } + } + + // The rest of the errors. + const DWORD kCertInvalidErrors = + CERT_TRUST_IS_CYCLIC | + CERT_TRUST_INVALID_EXTENSION | + CERT_TRUST_INVALID_POLICY_CONSTRAINTS | + CERT_TRUST_INVALID_BASIC_CONSTRAINTS | + CERT_TRUST_INVALID_NAME_CONSTRAINTS | + CERT_TRUST_CTL_IS_NOT_SIGNATURE_VALID | + CERT_TRUST_HAS_NOT_SUPPORTED_NAME_CONSTRAINT | + CERT_TRUST_HAS_NOT_DEFINED_NAME_CONSTRAINT | + CERT_TRUST_HAS_NOT_PERMITTED_NAME_CONSTRAINT | + CERT_TRUST_HAS_EXCLUDED_NAME_CONSTRAINT | + CERT_TRUST_NO_ISSUANCE_CHAIN_POLICY | + CERT_TRUST_HAS_NOT_SUPPORTED_CRITICAL_EXT; + if (error_status & kCertInvalidErrors) + cert_status |= CERT_STATUS_INVALID; + + return cert_status; +} + +// Returns true if any common name in the certificate's Subject field contains +// a NULL character. +bool CertSubjectCommonNameHasNull(PCCERT_CONTEXT cert) { + CRYPT_DECODE_PARA decode_para; + decode_para.cbSize = sizeof(decode_para); + decode_para.pfnAlloc = crypto::CryptAlloc; + decode_para.pfnFree = crypto::CryptFree; + CERT_NAME_INFO* name_info = NULL; + DWORD name_info_size = 0; + BOOL rv; + rv = CryptDecodeObjectEx(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, + X509_NAME, + cert->pCertInfo->Subject.pbData, + cert->pCertInfo->Subject.cbData, + CRYPT_DECODE_ALLOC_FLAG | CRYPT_DECODE_NOCOPY_FLAG, + &decode_para, + &name_info, + &name_info_size); + if (rv) { + scoped_ptr_malloc<CERT_NAME_INFO> scoped_name_info(name_info); + + // The Subject field may have multiple common names. According to the + // "PKI Layer Cake" paper, CryptoAPI uses every common name in the + // Subject field, so we inspect every common name. + // + // From RFC 5280: + // X520CommonName ::= CHOICE { + // teletexString TeletexString (SIZE (1..ub-common-name)), + // printableString PrintableString (SIZE (1..ub-common-name)), + // universalString UniversalString (SIZE (1..ub-common-name)), + // utf8String UTF8String (SIZE (1..ub-common-name)), + // bmpString BMPString (SIZE (1..ub-common-name)) } + // + // We also check IA5String and VisibleString. + for (DWORD i = 0; i < name_info->cRDN; ++i) { + PCERT_RDN rdn = &name_info->rgRDN[i]; + for (DWORD j = 0; j < rdn->cRDNAttr; ++j) { + PCERT_RDN_ATTR rdn_attr = &rdn->rgRDNAttr[j]; + if (strcmp(rdn_attr->pszObjId, szOID_COMMON_NAME) == 0) { + switch (rdn_attr->dwValueType) { + // After the CryptoAPI ASN.1 security vulnerabilities described in + // http://www.microsoft.com/technet/security/Bulletin/MS09-056.mspx + // were patched, we get CERT_RDN_ENCODED_BLOB for a common name + // that contains a NULL character. + case CERT_RDN_ENCODED_BLOB: + break; + // Array of 8-bit characters. + case CERT_RDN_PRINTABLE_STRING: + case CERT_RDN_TELETEX_STRING: + case CERT_RDN_IA5_STRING: + case CERT_RDN_VISIBLE_STRING: + for (DWORD k = 0; k < rdn_attr->Value.cbData; ++k) { + if (rdn_attr->Value.pbData[k] == '\0') + return true; + } + break; + // Array of 16-bit characters. + case CERT_RDN_BMP_STRING: + case CERT_RDN_UTF8_STRING: { + DWORD num_wchars = rdn_attr->Value.cbData / 2; + wchar_t* common_name = + reinterpret_cast<wchar_t*>(rdn_attr->Value.pbData); + for (DWORD k = 0; k < num_wchars; ++k) { + if (common_name[k] == L'\0') + return true; + } + break; + } + // Array of ints (32-bit). + case CERT_RDN_UNIVERSAL_STRING: { + DWORD num_ints = rdn_attr->Value.cbData / 4; + int* common_name = + reinterpret_cast<int*>(rdn_attr->Value.pbData); + for (DWORD k = 0; k < num_ints; ++k) { + if (common_name[k] == 0) + return true; + } + break; + } + default: + NOTREACHED(); + break; + } + } + } + } + } + return false; +} + +// IsIssuedByKnownRoot returns true if the given chain is rooted at a root CA +// which we recognise as a standard root. +// static +bool IsIssuedByKnownRoot(PCCERT_CHAIN_CONTEXT chain_context) { + PCERT_SIMPLE_CHAIN first_chain = chain_context->rgpChain[0]; + int num_elements = first_chain->cElement; + if (num_elements < 1) + return false; + PCERT_CHAIN_ELEMENT* element = first_chain->rgpElement; + PCCERT_CONTEXT cert = element[num_elements - 1]->pCertContext; + + SHA1HashValue hash = X509Certificate::CalculateFingerprint(cert); + return IsSHA1HashInSortedArray( + hash, &kKnownRootCertSHA1Hashes[0][0], sizeof(kKnownRootCertSHA1Hashes)); +} + +// Saves some information about the certificate chain |chain_context| in +// |*verify_result|. The caller MUST initialize |*verify_result| before +// calling this function. +void GetCertChainInfo(PCCERT_CHAIN_CONTEXT chain_context, + CertVerifyResult* verify_result) { + if (chain_context->cChain == 0) + return; + + PCERT_SIMPLE_CHAIN first_chain = chain_context->rgpChain[0]; + int num_elements = first_chain->cElement; + PCERT_CHAIN_ELEMENT* element = first_chain->rgpElement; + + PCCERT_CONTEXT verified_cert = NULL; + std::vector<PCCERT_CONTEXT> verified_chain; + + bool has_root_ca = num_elements > 1 && + !(chain_context->TrustStatus.dwErrorStatus & + CERT_TRUST_IS_PARTIAL_CHAIN); + + // Each chain starts with the end entity certificate (i = 0) and ends with + // either the root CA certificate or the last available intermediate. If a + // root CA certificate is present, do not inspect the signature algorithm of + // the root CA certificate because the signature on the trust anchor is not + // important. + if (has_root_ca) { + // If a full chain was constructed, regardless of whether it was trusted, + // don't inspect the root's signature algorithm. + num_elements -= 1; + } + + for (int i = 0; i < num_elements; ++i) { + PCCERT_CONTEXT cert = element[i]->pCertContext; + if (i == 0) { + verified_cert = cert; + } else { + verified_chain.push_back(cert); + } + + const char* algorithm = cert->pCertInfo->SignatureAlgorithm.pszObjId; + if (strcmp(algorithm, szOID_RSA_MD5RSA) == 0) { + // md5WithRSAEncryption: 1.2.840.113549.1.1.4 + verify_result->has_md5 = true; + if (i != 0) + verify_result->has_md5_ca = true; + } else if (strcmp(algorithm, szOID_RSA_MD2RSA) == 0) { + // md2WithRSAEncryption: 1.2.840.113549.1.1.2 + verify_result->has_md2 = true; + if (i != 0) + verify_result->has_md2_ca = true; + } else if (strcmp(algorithm, szOID_RSA_MD4RSA) == 0) { + // md4WithRSAEncryption: 1.2.840.113549.1.1.3 + verify_result->has_md4 = true; + } + } + + if (verified_cert) { + // Add the root certificate, if present, as it was not added above. + if (has_root_ca) + verified_chain.push_back(element[num_elements]->pCertContext); + verify_result->verified_cert = + X509Certificate::CreateFromHandle(verified_cert, verified_chain); + } +} + +// Decodes the cert's certificatePolicies extension into a CERT_POLICIES_INFO +// structure and stores it in *output. +void GetCertPoliciesInfo(PCCERT_CONTEXT cert, + scoped_ptr_malloc<CERT_POLICIES_INFO>* output) { + PCERT_EXTENSION extension = CertFindExtension(szOID_CERT_POLICIES, + cert->pCertInfo->cExtension, + cert->pCertInfo->rgExtension); + if (!extension) + return; + + CRYPT_DECODE_PARA decode_para; + decode_para.cbSize = sizeof(decode_para); + decode_para.pfnAlloc = crypto::CryptAlloc; + decode_para.pfnFree = crypto::CryptFree; + CERT_POLICIES_INFO* policies_info = NULL; + DWORD policies_info_size = 0; + BOOL rv; + rv = CryptDecodeObjectEx(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, + szOID_CERT_POLICIES, + extension->Value.pbData, + extension->Value.cbData, + CRYPT_DECODE_ALLOC_FLAG | CRYPT_DECODE_NOCOPY_FLAG, + &decode_para, + &policies_info, + &policies_info_size); + if (rv) + output->reset(policies_info); +} + +bool CheckRevocationWithCRLSet(PCCERT_CHAIN_CONTEXT chain, + CRLSet* crl_set) { + if (chain->cChain == 0) + return true; + + const PCERT_SIMPLE_CHAIN first_chain = chain->rgpChain[0]; + const PCERT_CHAIN_ELEMENT* element = first_chain->rgpElement; + + const int num_elements = first_chain->cElement; + if (num_elements == 0) + return true; + + // We iterate from the root certificate down to the leaf, keeping track of + // the issuer's SPKI at each step. + std::string issuer_spki_hash; + for (int i = num_elements - 1; i >= 0; i--) { + PCCERT_CONTEXT cert = element[i]->pCertContext; + + base::StringPiece der_bytes( + reinterpret_cast<const char*>(cert->pbCertEncoded), + cert->cbCertEncoded); + + base::StringPiece spki; + if (!asn1::ExtractSPKIFromDERCert(der_bytes, &spki)) { + NOTREACHED(); + continue; + } + + const std::string spki_hash = crypto::SHA256HashString(spki); + + const CRYPT_INTEGER_BLOB* serial_blob = &cert->pCertInfo->SerialNumber; + scoped_array<uint8> serial_bytes(new uint8[serial_blob->cbData]); + // The bytes of the serial number are stored little-endian. + for (unsigned j = 0; j < serial_blob->cbData; j++) + serial_bytes[j] = serial_blob->pbData[serial_blob->cbData - j - 1]; + base::StringPiece serial(reinterpret_cast<const char*>(serial_bytes.get()), + serial_blob->cbData); + + CRLSet::Result result = crl_set->CheckSPKI(spki_hash); + + if (result != CRLSet::REVOKED && !issuer_spki_hash.empty()) + result = crl_set->CheckSerial(serial, issuer_spki_hash); + + issuer_spki_hash = spki_hash; + + switch (result) { + case CRLSet::REVOKED: + return false; + case CRLSet::UNKNOWN: + case CRLSet::GOOD: + continue; + default: + NOTREACHED(); + continue; + } + } + + return true; +} + +void AppendPublicKeyHashes(PCCERT_CHAIN_CONTEXT chain, + HashValueVector* hashes) { + if (chain->cChain == 0) + return; + + PCERT_SIMPLE_CHAIN first_chain = chain->rgpChain[0]; + PCERT_CHAIN_ELEMENT* const element = first_chain->rgpElement; + + const DWORD num_elements = first_chain->cElement; + for (DWORD i = 0; i < num_elements; i++) { + PCCERT_CONTEXT cert = element[i]->pCertContext; + + base::StringPiece der_bytes( + reinterpret_cast<const char*>(cert->pbCertEncoded), + cert->cbCertEncoded); + base::StringPiece spki_bytes; + if (!asn1::ExtractSPKIFromDERCert(der_bytes, &spki_bytes)) + continue; + + HashValue sha1(HASH_VALUE_SHA1); + base::SHA1HashBytes(reinterpret_cast<const uint8*>(spki_bytes.data()), + spki_bytes.size(), sha1.data()); + hashes->push_back(sha1); + + HashValue sha256(HASH_VALUE_SHA256); + crypto::SHA256HashString(spki_bytes, sha1.data(), crypto::kSHA256Length); + hashes->push_back(sha256); + } +} + +// Returns true if the certificate is an extended-validation certificate. +// +// This function checks the certificatePolicies extensions of the +// certificates in the certificate chain according to Section 7 (pp. 11-12) +// of the EV Certificate Guidelines Version 1.0 at +// http://cabforum.org/EV_Certificate_Guidelines.pdf. +bool CheckEV(PCCERT_CHAIN_CONTEXT chain_context, + bool rev_checking_enabled, + const char* policy_oid) { + DCHECK_NE(static_cast<DWORD>(0), chain_context->cChain); + // If the cert doesn't match any of the policies, the + // CERT_TRUST_IS_NOT_VALID_FOR_USAGE bit (0x10) in + // chain_context->TrustStatus.dwErrorStatus is set. + DWORD error_status = chain_context->TrustStatus.dwErrorStatus; + + if (!rev_checking_enabled) { + // If online revocation checking is disabled then we will have still + // requested that the revocation cache be checked. However, that will often + // cause the following two error bits to be set. These error bits mean that + // the local OCSP/CRL is stale or missing entries for these certificates. + // Since they are expected, we mask them away. + error_status &= ~(CERT_TRUST_IS_OFFLINE_REVOCATION | + CERT_TRUST_REVOCATION_STATUS_UNKNOWN); + } + if (!chain_context->cChain || error_status != CERT_TRUST_NO_ERROR) + return false; + + // Check the end certificate simple chain (chain_context->rgpChain[0]). + // If the end certificate's certificatePolicies extension contains the + // EV policy OID of the root CA, return true. + PCERT_CHAIN_ELEMENT* element = chain_context->rgpChain[0]->rgpElement; + int num_elements = chain_context->rgpChain[0]->cElement; + if (num_elements < 2) + return false; + + // Look up the EV policy OID of the root CA. + PCCERT_CONTEXT root_cert = element[num_elements - 1]->pCertContext; + SHA1HashValue fingerprint = + X509Certificate::CalculateFingerprint(root_cert); + EVRootCAMetadata* metadata = EVRootCAMetadata::GetInstance(); + return metadata->HasEVPolicyOID(fingerprint, policy_oid); +} + +} // namespace + +CertVerifyProcWin::CertVerifyProcWin() {} + +CertVerifyProcWin::~CertVerifyProcWin() {} + +bool CertVerifyProcWin::SupportsAdditionalTrustAnchors() const { + return false; +} + +int CertVerifyProcWin::VerifyInternal( + X509Certificate* cert, + const std::string& hostname, + int flags, + CRLSet* crl_set, + const CertificateList& additional_trust_anchors, + CertVerifyResult* verify_result) { + PCCERT_CONTEXT cert_handle = cert->os_cert_handle(); + if (!cert_handle) + return ERR_UNEXPECTED; + + // Build and validate certificate chain. + CERT_CHAIN_PARA chain_para; + memset(&chain_para, 0, sizeof(chain_para)); + chain_para.cbSize = sizeof(chain_para); + // ExtendedKeyUsage. + // We still need to request szOID_SERVER_GATED_CRYPTO and szOID_SGC_NETSCAPE + // today because some certificate chains need them. IE also requests these + // two usages. + static const LPSTR usage[] = { + szOID_PKIX_KP_SERVER_AUTH, + szOID_SERVER_GATED_CRYPTO, + szOID_SGC_NETSCAPE + }; + chain_para.RequestedUsage.dwType = USAGE_MATCH_TYPE_OR; + chain_para.RequestedUsage.Usage.cUsageIdentifier = arraysize(usage); + chain_para.RequestedUsage.Usage.rgpszUsageIdentifier = + const_cast<LPSTR*>(usage); + + // Get the certificatePolicies extension of the certificate. + scoped_ptr_malloc<CERT_POLICIES_INFO> policies_info; + LPSTR ev_policy_oid = NULL; + if (flags & CertVerifier::VERIFY_EV_CERT) { + GetCertPoliciesInfo(cert_handle, &policies_info); + if (policies_info.get()) { + EVRootCAMetadata* metadata = EVRootCAMetadata::GetInstance(); + for (DWORD i = 0; i < policies_info->cPolicyInfo; ++i) { + LPSTR policy_oid = policies_info->rgPolicyInfo[i].pszPolicyIdentifier; + if (metadata->IsEVPolicyOID(policy_oid)) { + ev_policy_oid = policy_oid; + chain_para.RequestedIssuancePolicy.dwType = USAGE_MATCH_TYPE_AND; + chain_para.RequestedIssuancePolicy.Usage.cUsageIdentifier = 1; + chain_para.RequestedIssuancePolicy.Usage.rgpszUsageIdentifier = + &ev_policy_oid; + break; + } + } + } + } + + // We can set CERT_CHAIN_RETURN_LOWER_QUALITY_CONTEXTS to get more chains. + DWORD chain_flags = CERT_CHAIN_CACHE_END_CERT | + CERT_CHAIN_REVOCATION_CHECK_CHAIN_EXCLUDE_ROOT; + const bool rev_checking_enabled = + (flags & CertVerifier::VERIFY_REV_CHECKING_ENABLED) || + (ev_policy_oid != NULL && + (flags & CertVerifier::VERIFY_REV_CHECKING_ENABLED_EV_ONLY)); + + if (rev_checking_enabled) { + verify_result->cert_status |= CERT_STATUS_REV_CHECKING_ENABLED; + } else { + chain_flags |= CERT_CHAIN_REVOCATION_CHECK_CACHE_ONLY; + } + + // For non-test scenarios, use the default HCERTCHAINENGINE, NULL, which + // corresponds to HCCE_CURRENT_USER and is is initialized as needed by + // crypt32. However, when testing, it is necessary to create a new + // HCERTCHAINENGINE and use that instead. This is because each + // HCERTCHAINENGINE maintains a cache of information about certificates + // encountered, and each test run may modify the trust status of a + // certificate. + ScopedHCERTCHAINENGINE chain_engine(NULL); + if (TestRootCerts::HasInstance()) + chain_engine.reset(TestRootCerts::GetInstance()->GetChainEngine()); + + ScopedPCCERT_CONTEXT cert_list(cert->CreateOSCertChainForCert()); + PCCERT_CHAIN_CONTEXT chain_context; + // IE passes a non-NULL pTime argument that specifies the current system + // time. IE passes CERT_CHAIN_REVOCATION_CHECK_CHAIN_EXCLUDE_ROOT as the + // chain_flags argument. + if (!CertGetCertificateChain( + chain_engine, + cert_list.get(), + NULL, // current system time + cert_list->hCertStore, + &chain_para, + chain_flags, + NULL, // reserved + &chain_context)) { + verify_result->cert_status |= CERT_STATUS_INVALID; + return MapSecurityError(GetLastError()); + } + + if (chain_context->TrustStatus.dwErrorStatus & + CERT_TRUST_IS_NOT_VALID_FOR_USAGE) { + ev_policy_oid = NULL; + chain_para.RequestedIssuancePolicy.Usage.cUsageIdentifier = 0; + chain_para.RequestedIssuancePolicy.Usage.rgpszUsageIdentifier = NULL; + CertFreeCertificateChain(chain_context); + if (!CertGetCertificateChain( + chain_engine, + cert_list.get(), + NULL, // current system time + cert_list->hCertStore, + &chain_para, + chain_flags, + NULL, // reserved + &chain_context)) { + verify_result->cert_status |= CERT_STATUS_INVALID; + return MapSecurityError(GetLastError()); + } + } + + ScopedPCCERT_CHAIN_CONTEXT scoped_chain_context(chain_context); + + GetCertChainInfo(chain_context, verify_result); + verify_result->cert_status |= MapCertChainErrorStatusToCertStatus( + chain_context->TrustStatus.dwErrorStatus); + + // Flag certificates that have a Subject common name with a NULL character. + if (CertSubjectCommonNameHasNull(cert_handle)) + verify_result->cert_status |= CERT_STATUS_INVALID; + + if (crl_set && !CheckRevocationWithCRLSet(chain_context, crl_set)) + verify_result->cert_status |= CERT_STATUS_REVOKED; + + std::wstring wstr_hostname = ASCIIToWide(hostname); + + SSL_EXTRA_CERT_CHAIN_POLICY_PARA extra_policy_para; + memset(&extra_policy_para, 0, sizeof(extra_policy_para)); + extra_policy_para.cbSize = sizeof(extra_policy_para); + extra_policy_para.dwAuthType = AUTHTYPE_SERVER; + extra_policy_para.fdwChecks = 0; + extra_policy_para.pwszServerName = + const_cast<wchar_t*>(wstr_hostname.c_str()); + + CERT_CHAIN_POLICY_PARA policy_para; + memset(&policy_para, 0, sizeof(policy_para)); + policy_para.cbSize = sizeof(policy_para); + policy_para.dwFlags = 0; + policy_para.pvExtraPolicyPara = &extra_policy_para; + + CERT_CHAIN_POLICY_STATUS policy_status; + memset(&policy_status, 0, sizeof(policy_status)); + policy_status.cbSize = sizeof(policy_status); + + if (!CertVerifyCertificateChainPolicy( + CERT_CHAIN_POLICY_SSL, + chain_context, + &policy_para, + &policy_status)) { + return MapSecurityError(GetLastError()); + } + + if (policy_status.dwError) { + verify_result->cert_status |= MapNetErrorToCertStatus( + MapSecurityError(policy_status.dwError)); + + // CertVerifyCertificateChainPolicy reports only one error (in + // policy_status.dwError) if the certificate has multiple errors. + // CertGetCertificateChain doesn't report certificate name mismatch, so + // CertVerifyCertificateChainPolicy is the only function that can report + // certificate name mismatch. + // + // To prevent a potential certificate name mismatch from being hidden by + // some other certificate error, if we get any other certificate error, + // we call CertVerifyCertificateChainPolicy again, ignoring all other + // certificate errors. Both extra_policy_para.fdwChecks and + // policy_para.dwFlags allow us to ignore certificate errors, so we set + // them both. + if (policy_status.dwError != CERT_E_CN_NO_MATCH) { + const DWORD extra_ignore_flags = + 0x00000080 | // SECURITY_FLAG_IGNORE_REVOCATION + 0x00000100 | // SECURITY_FLAG_IGNORE_UNKNOWN_CA + 0x00002000 | // SECURITY_FLAG_IGNORE_CERT_DATE_INVALID + 0x00000200; // SECURITY_FLAG_IGNORE_WRONG_USAGE + extra_policy_para.fdwChecks = extra_ignore_flags; + const DWORD ignore_flags = + CERT_CHAIN_POLICY_IGNORE_ALL_NOT_TIME_VALID_FLAGS | + CERT_CHAIN_POLICY_IGNORE_INVALID_BASIC_CONSTRAINTS_FLAG | + CERT_CHAIN_POLICY_ALLOW_UNKNOWN_CA_FLAG | + CERT_CHAIN_POLICY_IGNORE_WRONG_USAGE_FLAG | + CERT_CHAIN_POLICY_IGNORE_INVALID_NAME_FLAG | + CERT_CHAIN_POLICY_IGNORE_INVALID_POLICY_FLAG | + CERT_CHAIN_POLICY_IGNORE_ALL_REV_UNKNOWN_FLAGS | + CERT_CHAIN_POLICY_ALLOW_TESTROOT_FLAG | + CERT_CHAIN_POLICY_TRUST_TESTROOT_FLAG | + CERT_CHAIN_POLICY_IGNORE_NOT_SUPPORTED_CRITICAL_EXT_FLAG | + CERT_CHAIN_POLICY_IGNORE_PEER_TRUST_FLAG; + policy_para.dwFlags = ignore_flags; + if (!CertVerifyCertificateChainPolicy( + CERT_CHAIN_POLICY_SSL, + chain_context, + &policy_para, + &policy_status)) { + return MapSecurityError(GetLastError()); + } + if (policy_status.dwError) { + verify_result->cert_status |= MapNetErrorToCertStatus( + MapSecurityError(policy_status.dwError)); + } + } + } + + // TODO(wtc): Suppress CERT_STATUS_NO_REVOCATION_MECHANISM for now to be + // compatible with WinHTTP, which doesn't report this error (bug 3004). + verify_result->cert_status &= ~CERT_STATUS_NO_REVOCATION_MECHANISM; + + if (!rev_checking_enabled) { + // If we didn't do online revocation checking then Windows will report + // CERT_UNABLE_TO_CHECK_REVOCATION unless it had cached OCSP or CRL + // information for every certificate. We only want to put up revoked + // statuses from the offline checks so we squash this error. + verify_result->cert_status &= ~CERT_STATUS_UNABLE_TO_CHECK_REVOCATION; + } + + AppendPublicKeyHashes(chain_context, &verify_result->public_key_hashes); + verify_result->is_issued_by_known_root = IsIssuedByKnownRoot(chain_context); + + if (IsCertStatusError(verify_result->cert_status)) + return MapCertStatusToNetError(verify_result->cert_status); + + if (ev_policy_oid && + CheckEV(chain_context, rev_checking_enabled, ev_policy_oid)) { + verify_result->cert_status |= CERT_STATUS_IS_EV; + } + return OK; +} + +} // namespace net diff --git a/net/cert/cert_verify_proc_win.h b/net/cert/cert_verify_proc_win.h new file mode 100644 index 0000000..147f47a --- /dev/null +++ b/net/cert/cert_verify_proc_win.h @@ -0,0 +1,34 @@ +// Copyright (c) 2012 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. + +#ifndef NET_CERT_CERT_VERIFY_PROC_WIN_H_ +#define NET_CERT_CERT_VERIFY_PROC_WIN_H_ + +#include "net/cert/cert_verify_proc.h" + +namespace net { + +// Performs certificate path construction and validation using Windows' +// CryptoAPI. +class CertVerifyProcWin : public CertVerifyProc { + public: + CertVerifyProcWin(); + + virtual bool SupportsAdditionalTrustAnchors() const OVERRIDE; + + protected: + virtual ~CertVerifyProcWin(); + + private: + virtual int VerifyInternal(X509Certificate* cert, + const std::string& hostname, + int flags, + CRLSet* crl_set, + const CertificateList& additional_trust_anchors, + CertVerifyResult* verify_result) OVERRIDE; +}; + +} // namespace net + +#endif // NET_CERT_CERT_VERIFY_PROC_WIN_H_ diff --git a/net/cert/cert_verify_result.cc b/net/cert/cert_verify_result.cc new file mode 100644 index 0000000..f076339 --- /dev/null +++ b/net/cert/cert_verify_result.cc @@ -0,0 +1,32 @@ +// Copyright (c) 2011 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 "net/cert/cert_verify_result.h" + +#include "net/cert/x509_certificate.h" + +namespace net { + +CertVerifyResult::CertVerifyResult() { + Reset(); +} + +CertVerifyResult::~CertVerifyResult() { +} + +void CertVerifyResult::Reset() { + verified_cert = NULL; + cert_status = 0; + has_md5 = false; + has_md2 = false; + has_md4 = false; + has_md5_ca = false; + has_md2_ca = false; + is_issued_by_known_root = false; + is_issued_by_additional_trust_anchor = false; + + public_key_hashes.clear(); +} + +} // namespace net diff --git a/net/cert/cert_verify_result.h b/net/cert/cert_verify_result.h new file mode 100644 index 0000000..c60e852 --- /dev/null +++ b/net/cert/cert_verify_result.h @@ -0,0 +1,66 @@ +// Copyright (c) 2011 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. + +#ifndef NET_CERT_CERT_VERIFY_RESULT_H_ +#define NET_CERT_CERT_VERIFY_RESULT_H_ + +#include <vector> + +#include "base/memory/ref_counted.h" +#include "net/base/net_export.h" +#include "net/cert/cert_status_flags.h" +#include "net/cert/x509_cert_types.h" + +namespace net { + +class X509Certificate; + +// The result of certificate verification. +class NET_EXPORT CertVerifyResult { + public: + CertVerifyResult(); + ~CertVerifyResult(); + + void Reset(); + + // The certificate and chain that was constructed during verification. + // Note that the though the verified certificate will match the originally + // supplied certificate, the intermediate certificates stored within may + // be substantially different. In the event of a verification failure, this + // will contain the chain as supplied by the server. This may be NULL if + // running within the sandbox. + scoped_refptr<X509Certificate> verified_cert; + + // Bitmask of CERT_STATUS_* from net/base/cert_status_flags.h. Note that + // these status flags apply to the certificate chain returned in + // |verified_cert|, rather than the originally supplied certificate + // chain. + CertStatus cert_status; + + // Properties of the certificate chain. + bool has_md5; + bool has_md2; + bool has_md4; + bool has_md5_ca; + bool has_md2_ca; + + // If the certificate was successfully verified then this contains the + // hashes, in several hash algorithms, of the SubjectPublicKeyInfos of the + // chain. + HashValueVector public_key_hashes; + + // is_issued_by_known_root is true if we recognise the root CA as a standard + // root. If it isn't then it's probably the case that this certificate was + // generated by a MITM proxy whose root has been installed locally. This is + // meaningless if the certificate was not trusted. + bool is_issued_by_known_root; + + // is_issued_by_additional_trust_anchor is true if the root CA used for this + // verification came from the list of additional trust anchors. + bool is_issued_by_additional_trust_anchor; +}; + +} // namespace net + +#endif // NET_CERT_CERT_VERIFY_RESULT_H_ diff --git a/net/cert/crl_set.cc b/net/cert/crl_set.cc new file mode 100644 index 0000000..bc124fd --- /dev/null +++ b/net/cert/crl_set.cc @@ -0,0 +1,593 @@ +// Copyright (c) 2012 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 "base/base64.h" +#include "base/format_macros.h" +#include "base/json/json_reader.h" +#include "base/logging.h" +#include "base/stl_util.h" +#include "base/string_util.h" +#include "base/stringprintf.h" +#include "base/time.h" +#include "base/values.h" +#include "crypto/sha2.h" +#include "net/cert/crl_set.h" +#include "third_party/zlib/zlib.h" + +namespace net { + +// Decompress zlib decompressed |in| into |out|. |out_len| is the number of +// bytes at |out| and must be exactly equal to the size of the decompressed +// data. +static bool DecompressZlib(uint8* out, int out_len, base::StringPiece in) { + z_stream z; + memset(&z, 0, sizeof(z)); + + z.next_in = reinterpret_cast<Bytef*>(const_cast<char*>(in.data())); + z.avail_in = in.size(); + z.next_out = reinterpret_cast<Bytef*>(out); + z.avail_out = out_len; + + if (inflateInit(&z) != Z_OK) + return false; + bool ret = false; + int r = inflate(&z, Z_FINISH); + if (r != Z_STREAM_END) + goto err; + if (z.avail_in || z.avail_out) + goto err; + ret = true; + + err: + inflateEnd(&z); + return ret; +} + +CRLSet::CRLSet() + : sequence_(0), + not_after_(0) { +} + +CRLSet::~CRLSet() { +} + +// CRLSet format: +// +// uint16le header_len +// byte[header_len] header_bytes +// repeated { +// byte[32] parent_spki_sha256 +// uint32le num_serials +// [num_serials] { +// uint8 serial_length; +// byte[serial_length] serial; +// } +// +// header_bytes consists of a JSON dictionary with the following keys: +// Version (int): currently 0 +// ContentType (string): "CRLSet" or "CRLSetDelta" (magic value) +// DeltaFrom (int32): if this is a delta update (see below), then this +// contains the sequence number of the base CRLSet. +// Sequence (int32): the monotonic sequence number of this CRL set. +// +// A delta CRLSet is similar to a CRLSet: +// +// struct CompressedChanges { +// uint32le uncompressed_size +// uint32le compressed_size +// byte[compressed_size] zlib_data +// } +// +// uint16le header_len +// byte[header_len] header_bytes +// CompressedChanges crl_changes +// [crl_changes.uncompressed_size] { +// switch (crl_changes[i]) { +// case 0: +// // CRL is the same +// case 1: +// // New CRL inserted +// // See CRL structure from the non-delta format +// case 2: +// // CRL deleted +// case 3: +// // CRL changed +// CompressedChanges serials_changes +// [serials_changes.uncompressed_size] { +// switch (serials_changes[i]) { +// case 0: +// // the serial is the same +// case 1: +// // serial inserted +// uint8 serial_length +// byte[serial_length] serial +// case 2: +// // serial deleted +// } +// } +// } +// } +// +// A delta CRLSet applies to a specific CRL set as given in the +// header's "DeltaFrom" value. The delta describes the changes to each CRL +// in turn with a zlib compressed array of options: either the CRL is the same, +// a new CRL is inserted, the CRL is deleted or the CRL is updated. In the case +// of an update, the serials in the CRL are considered in the same fashion +// except there is no delta update of a serial number: they are either +// inserted, deleted or left the same. + +// ReadHeader reads the header (including length prefix) from |data| and +// updates |data| to remove the header on return. Caller takes ownership of the +// returned pointer. +static base::DictionaryValue* ReadHeader(base::StringPiece* data) { + if (data->size() < 2) + return NULL; + uint16 header_len; + memcpy(&header_len, data->data(), 2); // assumes little-endian. + data->remove_prefix(2); + + if (data->size() < header_len) + return NULL; + + const base::StringPiece header_bytes(data->data(), header_len); + data->remove_prefix(header_len); + + scoped_ptr<Value> header(base::JSONReader::Read( + header_bytes, base::JSON_ALLOW_TRAILING_COMMAS)); + if (header.get() == NULL) + return NULL; + + if (!header->IsType(Value::TYPE_DICTIONARY)) + return NULL; + return reinterpret_cast<base::DictionaryValue*>(header.release()); +} + +// kCurrentFileVersion is the version of the CRLSet file format that we +// currently implement. +static const int kCurrentFileVersion = 0; + +static bool ReadCRL(base::StringPiece* data, std::string* out_parent_spki_hash, + std::vector<std::string>* out_serials) { + if (data->size() < crypto::kSHA256Length) + return false; + *out_parent_spki_hash = std::string(data->data(), crypto::kSHA256Length); + data->remove_prefix(crypto::kSHA256Length); + + if (data->size() < sizeof(uint32)) + return false; + uint32 num_serials; + memcpy(&num_serials, data->data(), sizeof(uint32)); // assumes little endian + data->remove_prefix(sizeof(uint32)); + + for (uint32 i = 0; i < num_serials; ++i) { + uint8 serial_length; + if (data->size() < sizeof(uint8)) + return false; + memcpy(&serial_length, data->data(), sizeof(uint8)); + data->remove_prefix(sizeof(uint8)); + + if (data->size() < serial_length) + return false; + std::string serial(data->data(), serial_length); + data->remove_prefix(serial_length); + out_serials->push_back(serial); + } + + return true; +} + +bool CRLSet::CopyBlockedSPKIsFromHeader(base::DictionaryValue* header_dict) { + ListValue* blocked_spkis_list = NULL; + if (!header_dict->GetList("BlockedSPKIs", &blocked_spkis_list)) { + // BlockedSPKIs is optional, so it's fine if we don't find it. + return true; + } + + blocked_spkis_.clear(); + + for (size_t i = 0; i < blocked_spkis_list->GetSize(); ++i) { + std::string spki_sha256_base64, spki_sha256; + if (!blocked_spkis_list->GetString(i, &spki_sha256_base64)) + return false; + if (!base::Base64Decode(spki_sha256_base64, &spki_sha256)) + return false; + blocked_spkis_.push_back(spki_sha256); + } + + return true; +} + +// static +bool CRLSet::Parse(base::StringPiece data, scoped_refptr<CRLSet>* out_crl_set) { + // Other parts of Chrome assume that we're little endian, so we don't lose + // anything by doing this. +#if defined(__BYTE_ORDER) + // Linux check + COMPILE_ASSERT(__BYTE_ORDER == __LITTLE_ENDIAN, assumes_little_endian); +#elif defined(__BIG_ENDIAN__) + // Mac check + #error assumes little endian +#endif + + scoped_ptr<base::DictionaryValue> header_dict(ReadHeader(&data)); + if (!header_dict.get()) + return false; + + std::string contents; + if (!header_dict->GetString("ContentType", &contents)) + return false; + if (contents != "CRLSet") + return false; + + int version; + if (!header_dict->GetInteger("Version", &version) || + version != kCurrentFileVersion) { + return false; + } + + int sequence; + if (!header_dict->GetInteger("Sequence", &sequence)) + return false; + + double not_after; + if (!header_dict->GetDouble("NotAfter", ¬_after)) { + // NotAfter is optional for now. + not_after = 0; + } + if (not_after < 0) + return false; + + scoped_refptr<CRLSet> crl_set(new CRLSet); + crl_set->sequence_ = static_cast<uint32>(sequence); + crl_set->not_after_ = static_cast<uint64>(not_after); + + for (size_t crl_index = 0; !data.empty(); crl_index++) { + std::string parent_spki_sha256; + std::vector<std::string> serials; + if (!ReadCRL(&data, &parent_spki_sha256, &serials)) + return false; + + crl_set->crls_.push_back(std::make_pair(parent_spki_sha256, serials)); + crl_set->crls_index_by_issuer_[parent_spki_sha256] = crl_index; + } + + if (!crl_set->CopyBlockedSPKIsFromHeader(header_dict.get())) + return false; + + *out_crl_set = crl_set; + return true; +} + +// kMaxUncompressedChangesLength is the largest changes array that we'll +// accept. This bounds the number of CRLs in the CRLSet as well as the number +// of serial numbers in a given CRL. +static const unsigned kMaxUncompressedChangesLength = 1024 * 1024; + +static bool ReadChanges(base::StringPiece* data, + std::vector<uint8>* out_changes) { + uint32 uncompressed_size, compressed_size; + if (data->size() < 2 * sizeof(uint32)) + return false; + // assumes little endian. + memcpy(&uncompressed_size, data->data(), sizeof(uint32)); + data->remove_prefix(4); + memcpy(&compressed_size, data->data(), sizeof(uint32)); + data->remove_prefix(4); + + if (uncompressed_size > kMaxUncompressedChangesLength) + return false; + if (data->size() < compressed_size) + return false; + + out_changes->clear(); + if (uncompressed_size == 0) + return true; + + out_changes->resize(uncompressed_size); + base::StringPiece compressed(data->data(), compressed_size); + data->remove_prefix(compressed_size); + return DecompressZlib(&(*out_changes)[0], uncompressed_size, compressed); +} + +// These are the range coder symbols used in delta updates. +enum { + SYMBOL_SAME = 0, + SYMBOL_INSERT = 1, + SYMBOL_DELETE = 2, + SYMBOL_CHANGED = 3, +}; + +bool ReadDeltaCRL(base::StringPiece* data, + const std::vector<std::string>& old_serials, + std::vector<std::string>* out_serials) { + std::vector<uint8> changes; + if (!ReadChanges(data, &changes)) + return false; + + size_t i = 0; + for (std::vector<uint8>::const_iterator k = changes.begin(); + k != changes.end(); ++k) { + if (*k == SYMBOL_SAME) { + if (i >= old_serials.size()) + return false; + out_serials->push_back(old_serials[i]); + i++; + } else if (*k == SYMBOL_INSERT) { + uint8 serial_length; + if (data->size() < sizeof(uint8)) + return false; + memcpy(&serial_length, data->data(), sizeof(uint8)); + data->remove_prefix(sizeof(uint8)); + + if (data->size() < serial_length) + return false; + const std::string serial(data->data(), serial_length); + data->remove_prefix(serial_length); + + out_serials->push_back(serial); + } else if (*k == SYMBOL_DELETE) { + if (i >= old_serials.size()) + return false; + i++; + } else { + NOTREACHED(); + return false; + } + } + + if (i != old_serials.size()) + return false; + return true; +} + +bool CRLSet::ApplyDelta(const base::StringPiece& in_data, + scoped_refptr<CRLSet>* out_crl_set) { + base::StringPiece data(in_data); + scoped_ptr<base::DictionaryValue> header_dict(ReadHeader(&data)); + if (!header_dict.get()) + return false; + + std::string contents; + if (!header_dict->GetString("ContentType", &contents)) + return false; + if (contents != "CRLSetDelta") + return false; + + int version; + if (!header_dict->GetInteger("Version", &version) || + version != kCurrentFileVersion) { + return false; + } + + int sequence, delta_from; + if (!header_dict->GetInteger("Sequence", &sequence) || + !header_dict->GetInteger("DeltaFrom", &delta_from) || + delta_from < 0 || + static_cast<uint32>(delta_from) != sequence_) { + return false; + } + + double not_after; + if (!header_dict->GetDouble("NotAfter", ¬_after)) { + // NotAfter is optional for now. + not_after = 0; + } + if (not_after < 0) + return false; + + scoped_refptr<CRLSet> crl_set(new CRLSet); + crl_set->sequence_ = static_cast<uint32>(sequence); + crl_set->not_after_ = static_cast<uint64>(not_after); + + if (!crl_set->CopyBlockedSPKIsFromHeader(header_dict.get())) + return false; + + std::vector<uint8> crl_changes; + + if (!ReadChanges(&data, &crl_changes)) + return false; + + size_t i = 0, j = 0; + for (std::vector<uint8>::const_iterator k = crl_changes.begin(); + k != crl_changes.end(); ++k) { + if (*k == SYMBOL_SAME) { + if (i >= crls_.size()) + return false; + crl_set->crls_.push_back(crls_[i]); + crl_set->crls_index_by_issuer_[crls_[i].first] = j; + i++; + j++; + } else if (*k == SYMBOL_INSERT) { + std::string parent_spki_hash; + std::vector<std::string> serials; + if (!ReadCRL(&data, &parent_spki_hash, &serials)) + return false; + crl_set->crls_.push_back(std::make_pair(parent_spki_hash, serials)); + crl_set->crls_index_by_issuer_[parent_spki_hash] = j; + j++; + } else if (*k == SYMBOL_DELETE) { + if (i >= crls_.size()) + return false; + i++; + } else if (*k == SYMBOL_CHANGED) { + if (i >= crls_.size()) + return false; + std::vector<std::string> serials; + if (!ReadDeltaCRL(&data, crls_[i].second, &serials)) + return false; + crl_set->crls_.push_back(std::make_pair(crls_[i].first, serials)); + crl_set->crls_index_by_issuer_[crls_[i].first] = j; + i++; + j++; + } else { + NOTREACHED(); + return false; + } + } + + if (!data.empty()) + return false; + if (i != crls_.size()) + return false; + + *out_crl_set = crl_set; + return true; +} + +// static +bool CRLSet::GetIsDeltaUpdate(const base::StringPiece& in_data, + bool* is_delta) { + base::StringPiece data(in_data); + scoped_ptr<base::DictionaryValue> header_dict(ReadHeader(&data)); + if (!header_dict.get()) + return false; + + std::string contents; + if (!header_dict->GetString("ContentType", &contents)) + return false; + + if (contents == "CRLSet") { + *is_delta = false; + } else if (contents == "CRLSetDelta") { + *is_delta = true; + } else { + return false; + } + + return true; +} + +std::string CRLSet::Serialize() const { + std::string header = base::StringPrintf( + "{" + "\"Version\":0," + "\"ContentType\":\"CRLSet\"," + "\"Sequence\":%u," + "\"DeltaFrom\":0," + "\"NumParents\":%u," + "\"BlockedSPKIs\":[", + static_cast<unsigned>(sequence_), + static_cast<unsigned>(crls_.size())); + + for (std::vector<std::string>::const_iterator i = blocked_spkis_.begin(); + i != blocked_spkis_.end(); ++i) { + std::string spki_hash_base64; + base::Base64Encode(*i, &spki_hash_base64); + + if (i != blocked_spkis_.begin()) + header += ","; + header += "\"" + spki_hash_base64 + "\""; + } + header += "]"; + if (not_after_ != 0) + header += base::StringPrintf(",\"NotAfter\":%" PRIu64, not_after_); + header += "}"; + + size_t len = 2 /* header len */ + header.size(); + + for (CRLList::const_iterator i = crls_.begin(); i != crls_.end(); ++i) { + len += i->first.size() + 4 /* num serials */; + for (std::vector<std::string>::const_iterator j = i->second.begin(); + j != i->second.end(); ++j) { + len += 1 /* serial length */ + j->size(); + } + } + + std::string ret; + char* out = WriteInto(&ret, len + 1 /* to include final NUL */); + size_t off = 0; + out[off++] = header.size(); + out[off++] = header.size() >> 8; + memcpy(out + off, header.data(), header.size()); + off += header.size(); + + for (CRLList::const_iterator i = crls_.begin(); i != crls_.end(); ++i) { + memcpy(out + off, i->first.data(), i->first.size()); + off += i->first.size(); + const uint32 num_serials = i->second.size(); + memcpy(out + off, &num_serials, sizeof(num_serials)); + off += sizeof(num_serials); + + for (std::vector<std::string>::const_iterator j = i->second.begin(); + j != i->second.end(); ++j) { + out[off++] = j->size(); + memcpy(out + off, j->data(), j->size()); + off += j->size(); + } + } + + CHECK_EQ(off, len); + return ret; +} + +CRLSet::Result CRLSet::CheckSPKI(const base::StringPiece& spki_hash) const { + for (std::vector<std::string>::const_iterator i = blocked_spkis_.begin(); + i != blocked_spkis_.end(); ++i) { + if (spki_hash.size() == i->size() && + memcmp(spki_hash.data(), i->data(), i->size()) == 0) { + return REVOKED; + } + } + + return GOOD; +} + +CRLSet::Result CRLSet::CheckSerial( + const base::StringPiece& serial_number, + const base::StringPiece& issuer_spki_hash) const { + base::StringPiece serial(serial_number); + + if (!serial.empty() && (serial[0] & 0x80) != 0) { + // This serial number is negative but the process which generates CRL sets + // will reject any certificates with negative serial numbers as invalid. + return UNKNOWN; + } + + // Remove any leading zero bytes. + while (serial.size() > 1 && serial[0] == 0x00) + serial.remove_prefix(1); + + std::map<std::string, size_t>::const_iterator i = + crls_index_by_issuer_.find(issuer_spki_hash.as_string()); + if (i == crls_index_by_issuer_.end()) + return UNKNOWN; + const std::vector<std::string>& serials = crls_[i->second].second; + + for (std::vector<std::string>::const_iterator i = serials.begin(); + i != serials.end(); ++i) { + if (base::StringPiece(*i) == serial) + return REVOKED; + } + + return GOOD; +} + +bool CRLSet::IsExpired() const { + if (not_after_ == 0) + return false; + + uint64 now = base::Time::Now().ToTimeT(); + return now > not_after_; +} + +uint32 CRLSet::sequence() const { + return sequence_; +} + +const CRLSet::CRLList& CRLSet::crls() const { + return crls_; +} + +// static +CRLSet* CRLSet::EmptyCRLSetForTesting() { + return new CRLSet; +} + +CRLSet* CRLSet::ExpiredCRLSetForTesting() { + CRLSet* crl_set = new CRLSet; + crl_set->not_after_ = 1; + return crl_set; +} + +} // namespace net diff --git a/net/cert/crl_set.h b/net/cert/crl_set.h new file mode 100644 index 0000000..ae6a2a2 --- /dev/null +++ b/net/cert/crl_set.h @@ -0,0 +1,118 @@ +// Copyright (c) 2012 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. + +#ifndef NET_CERT_CRL_SET_H_ +#define NET_CERT_CRL_SET_H_ + +#include <map> +#include <string> +#include <utility> +#include <vector> + +#include "base/memory/ref_counted.h" +#include "base/string_piece.h" +#include "net/base/net_export.h" + +namespace base { +class DictionaryValue; +} + +namespace net { + +// A CRLSet is a structure that lists the serial numbers of revoked +// certificates from a number of issuers where issuers are identified by the +// SHA256 of their SubjectPublicKeyInfo. +class NET_EXPORT CRLSet : public base::RefCountedThreadSafe<CRLSet> { + public: + enum Result { + REVOKED, // the certificate should be rejected. + UNKNOWN, // the CRL for the certificate is not included in the set. + GOOD, // the certificate is not listed. + }; + + // Parse parses the bytes in |data| and, on success, puts a new CRLSet in + // |out_crl_set| and returns true. + static bool Parse(base::StringPiece data, + scoped_refptr<CRLSet>* out_crl_set); + + // CheckSPKI checks whether the given SPKI has been listed as blocked. + // spki_hash: the SHA256 of the SubjectPublicKeyInfo of the certificate. + Result CheckSPKI(const base::StringPiece& spki_hash) const; + + // CheckSerial returns the information contained in the set for a given + // certificate: + // serial_number: the serial number of the certificate + // issuer_spki_hash: the SHA256 of the SubjectPublicKeyInfo of the CRL + // signer + Result CheckSerial( + const base::StringPiece& serial_number, + const base::StringPiece& issuer_spki_hash) const; + + // IsExpired returns true iff the current time is past the NotAfter time + // specified in the CRLSet. + bool IsExpired() const; + + // ApplyDelta returns a new CRLSet in |out_crl_set| that is the result of + // updating the current CRL set with the delta information in |delta_bytes|. + bool ApplyDelta(const base::StringPiece& delta_bytes, + scoped_refptr<CRLSet>* out_crl_set); + + // GetIsDeltaUpdate extracts the header from |bytes|, sets *is_delta to + // whether |bytes| is a delta CRL set or not and returns true. In the event + // of a parse error, it returns false. + static bool GetIsDeltaUpdate(const base::StringPiece& bytes, bool *is_delta); + + // Serialize returns a string of bytes suitable for passing to Parse. Parsing + // and serializing a CRLSet is a lossless operation - the resulting bytes + // will be equal. + std::string Serialize() const; + + // sequence returns the sequence number of this CRL set. CRL sets generated + // by the same source are given strictly monotonically increasing sequence + // numbers. + uint32 sequence() const; + + // CRLList contains a list of (issuer SPKI hash, revoked serial numbers) + // pairs. + typedef std::vector< std::pair<std::string, std::vector<std::string> > > + CRLList; + + // crls returns the internal state of this CRLSet. It should only be used in + // testing. + const CRLList& crls() const; + + // EmptyCRLSetForTesting returns a valid, but empty, CRLSet for unit tests. + static CRLSet* EmptyCRLSetForTesting(); + + // ExpiredCRLSetForTesting returns a expired, empty CRLSet for unit tests. + static CRLSet* ExpiredCRLSetForTesting(); + + private: + CRLSet(); + ~CRLSet(); + + friend class base::RefCountedThreadSafe<CRLSet>; + + // CopyBlockedSPKIsFromHeader sets |blocked_spkis_| to the list of values + // from "BlockedSPKIs" in |header_dict|. + bool CopyBlockedSPKIsFromHeader(base::DictionaryValue* header_dict); + + uint32 sequence_; + CRLList crls_; + // not_after_ contains the time, in UNIX epoch seconds, after which the + // CRLSet should be considered stale, or 0 if no such time was given. + uint64 not_after_; + // crls_index_by_issuer_ maps from issuer SPKI hashes to the index in |crls_| + // where the information for that issuer can be found. We have both |crls_| + // and |crls_index_by_issuer_| because, when applying a delta update, we need + // to identify a CRL by index. + std::map<std::string, size_t> crls_index_by_issuer_; + // blocked_spkis_ contains the SHA256 hashes of SPKIs which are to be blocked + // no matter where in a certificate chain they might appear. + std::vector<std::string> blocked_spkis_; +}; + +} // namespace net + +#endif // NET_CERT_CRL_SET_H_ diff --git a/net/cert/crl_set_unittest.cc b/net/cert/crl_set_unittest.cc new file mode 100644 index 0000000..88eb654 --- /dev/null +++ b/net/cert/crl_set_unittest.cc @@ -0,0 +1,326 @@ +// Copyright (c) 2012 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 "net/cert/crl_set.h" +#include "testing/gtest/include/gtest/gtest.h" + +// These data blocks were generated using a lot of code that is still in +// development. For now, if you need to update them, you have to contact agl. +static const uint8 kGIACRLSet[] = { + 0x60, 0x00, 0x7b, 0x22, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x3a, + 0x30, 0x2c, 0x22, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, + 0x65, 0x22, 0x3a, 0x22, 0x43, 0x52, 0x4c, 0x53, 0x65, 0x74, 0x22, 0x2c, 0x22, + 0x53, 0x65, 0x71, 0x75, 0x65, 0x6e, 0x63, 0x65, 0x22, 0x3a, 0x30, 0x2c, 0x22, + 0x44, 0x65, 0x6c, 0x74, 0x61, 0x46, 0x72, 0x6f, 0x6d, 0x22, 0x3a, 0x30, 0x2c, + 0x22, 0x4e, 0x75, 0x6d, 0x50, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x73, 0x22, 0x3a, + 0x31, 0x2c, 0x22, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x65, 0x64, 0x53, 0x50, 0x4b, + 0x49, 0x73, 0x22, 0x3a, 0x5b, 0x5d, 0x7d, 0xb6, 0xb9, 0x54, 0x32, 0xab, 0xae, + 0x57, 0xfe, 0x02, 0x0c, 0xb2, 0xb7, 0x4f, 0x4f, 0x9f, 0x91, 0x73, 0xc8, 0xc7, + 0x08, 0xaf, 0xc9, 0xe7, 0x32, 0xac, 0xe2, 0x32, 0x79, 0x04, 0x7c, 0x6d, 0x05, + 0x0d, 0x00, 0x00, 0x00, 0x0a, 0x10, 0x0d, 0x7f, 0x30, 0x00, 0x03, 0x00, 0x00, + 0x23, 0xb0, 0x0a, 0x10, 0x0e, 0x37, 0x06, 0x00, 0x03, 0x00, 0x00, 0x23, 0xb1, + 0x0a, 0x16, 0x25, 0x42, 0x54, 0x00, 0x03, 0x00, 0x00, 0x14, 0x51, 0x0a, 0x16, + 0x69, 0xd1, 0xd7, 0x00, 0x03, 0x00, 0x00, 0x14, 0x52, 0x0a, 0x16, 0x70, 0x8c, + 0x22, 0x00, 0x03, 0x00, 0x00, 0x14, 0x53, 0x0a, 0x16, 0x71, 0x31, 0x2c, 0x00, + 0x03, 0x00, 0x00, 0x14, 0x54, 0x0a, 0x16, 0x7d, 0x75, 0x9d, 0x00, 0x03, 0x00, + 0x00, 0x14, 0x55, 0x0a, 0x1f, 0xee, 0xf9, 0x49, 0x00, 0x03, 0x00, 0x00, 0x23, + 0xae, 0x0a, 0x1f, 0xfc, 0xd1, 0x89, 0x00, 0x03, 0x00, 0x00, 0x23, 0xaf, 0x0a, + 0x61, 0xdd, 0xc7, 0x48, 0x00, 0x03, 0x00, 0x00, 0x18, 0x0e, 0x0a, 0x61, 0xe6, + 0x12, 0x64, 0x00, 0x03, 0x00, 0x00, 0x18, 0x0f, 0x0a, 0x61, 0xe9, 0x46, 0x56, + 0x00, 0x03, 0x00, 0x00, 0x18, 0x10, 0x0a, 0x64, 0x63, 0x49, 0xd2, 0x00, 0x03, + 0x00, 0x00, 0x1d, 0x77, +}; + +static const uint8 kNoopDeltaCRL[] = { + 0xc3, 0x00, 0x7b, 0x22, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x3a, + 0x30, 0x2c, 0x22, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, + 0x65, 0x22, 0x3a, 0x22, 0x43, 0x52, 0x4c, 0x53, 0x65, 0x74, 0x44, 0x65, 0x6c, + 0x74, 0x61, 0x22, 0x2c, 0x22, 0x53, 0x65, 0x71, 0x75, 0x65, 0x6e, 0x63, 0x65, + 0x22, 0x3a, 0x30, 0x2c, 0x22, 0x4e, 0x65, 0x78, 0x74, 0x55, 0x70, 0x64, 0x61, + 0x74, 0x65, 0x22, 0x3a, 0x31, 0x33, 0x31, 0x31, 0x31, 0x32, 0x33, 0x37, 0x39, + 0x33, 0x2c, 0x22, 0x57, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x53, 0x65, 0x63, 0x73, + 0x22, 0x3a, 0x34, 0x33, 0x32, 0x30, 0x30, 0x2c, 0x22, 0x44, 0x65, 0x6c, 0x74, + 0x61, 0x46, 0x72, 0x6f, 0x6d, 0x22, 0x3a, 0x30, 0x2c, 0x22, 0x4e, 0x75, 0x6d, + 0x50, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x73, 0x22, 0x3a, 0x31, 0x2c, 0x22, 0x53, + 0x69, 0x67, 0x6e, 0x69, 0x6e, 0x67, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, + 0x65, 0x79, 0x22, 0x3a, 0x22, 0x22, 0x2c, 0x22, 0x53, 0x69, 0x67, 0x6e, 0x69, + 0x6e, 0x67, 0x4b, 0x65, 0x79, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, + 0x65, 0x22, 0x3a, 0x22, 0x22, 0x2c, 0x22, 0x50, 0x61, 0x79, 0x6c, 0x6f, 0x61, + 0x64, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x22, 0x3a, 0x22, + 0x22, 0x7d, 0x01, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x78, 0x9c, 0x62, + 0x00, 0x04, 0x00, 0x00, 0xff, 0xff, 0x00, 0x01, 0x00, 0x01, +}; + +static const uint8 kAddCRLDelta[] = { + 0xc3, 0x00, 0x7b, 0x22, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x3a, + 0x30, 0x2c, 0x22, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, + 0x65, 0x22, 0x3a, 0x22, 0x43, 0x52, 0x4c, 0x53, 0x65, 0x74, 0x44, 0x65, 0x6c, + 0x74, 0x61, 0x22, 0x2c, 0x22, 0x53, 0x65, 0x71, 0x75, 0x65, 0x6e, 0x63, 0x65, + 0x22, 0x3a, 0x30, 0x2c, 0x22, 0x4e, 0x65, 0x78, 0x74, 0x55, 0x70, 0x64, 0x61, + 0x74, 0x65, 0x22, 0x3a, 0x31, 0x33, 0x31, 0x31, 0x31, 0x32, 0x35, 0x39, 0x34, + 0x38, 0x2c, 0x22, 0x57, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x53, 0x65, 0x63, 0x73, + 0x22, 0x3a, 0x34, 0x33, 0x32, 0x30, 0x30, 0x2c, 0x22, 0x44, 0x65, 0x6c, 0x74, + 0x61, 0x46, 0x72, 0x6f, 0x6d, 0x22, 0x3a, 0x30, 0x2c, 0x22, 0x4e, 0x75, 0x6d, + 0x50, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x73, 0x22, 0x3a, 0x32, 0x2c, 0x22, 0x53, + 0x69, 0x67, 0x6e, 0x69, 0x6e, 0x67, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, + 0x65, 0x79, 0x22, 0x3a, 0x22, 0x22, 0x2c, 0x22, 0x53, 0x69, 0x67, 0x6e, 0x69, + 0x6e, 0x67, 0x4b, 0x65, 0x79, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, + 0x65, 0x22, 0x3a, 0x22, 0x22, 0x2c, 0x22, 0x50, 0x61, 0x79, 0x6c, 0x6f, 0x61, + 0x64, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x22, 0x3a, 0x22, + 0x22, 0x7d, 0x02, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x78, 0x9c, 0x62, + 0x60, 0x04, 0x04, 0x00, 0x00, 0xff, 0xff, 0x00, 0x03, 0x00, 0x02, 0xe4, 0x2f, + 0x24, 0xbd, 0x4d, 0x37, 0xf4, 0xaa, 0x2e, 0x56, 0xb9, 0x79, 0xd8, 0x3d, 0x1e, + 0x65, 0x21, 0x9f, 0xe0, 0xe9, 0xe3, 0xa3, 0x82, 0xa1, 0xb3, 0xcb, 0x66, 0xc9, + 0x39, 0x55, 0xde, 0x75, 0x0c, 0x00, 0x00, 0x00, 0x01, 0x02, 0x01, 0x03, 0x01, + 0x04, 0x01, 0x05, 0x01, 0x06, 0x01, 0x07, 0x01, 0x08, 0x01, 0x09, 0x01, 0x2f, + 0x01, 0x30, 0x01, 0x31, 0x01, 0x32, +}; + +static const uint8 kRemoveCRLDelta[] = { + 0xc3, 0x00, 0x7b, 0x22, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x3a, + 0x30, 0x2c, 0x22, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, + 0x65, 0x22, 0x3a, 0x22, 0x43, 0x52, 0x4c, 0x53, 0x65, 0x74, 0x44, 0x65, 0x6c, + 0x74, 0x61, 0x22, 0x2c, 0x22, 0x53, 0x65, 0x71, 0x75, 0x65, 0x6e, 0x63, 0x65, + 0x22, 0x3a, 0x30, 0x2c, 0x22, 0x4e, 0x65, 0x78, 0x74, 0x55, 0x70, 0x64, 0x61, + 0x74, 0x65, 0x22, 0x3a, 0x31, 0x33, 0x31, 0x31, 0x31, 0x32, 0x36, 0x31, 0x31, + 0x36, 0x2c, 0x22, 0x57, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x53, 0x65, 0x63, 0x73, + 0x22, 0x3a, 0x34, 0x33, 0x32, 0x30, 0x30, 0x2c, 0x22, 0x44, 0x65, 0x6c, 0x74, + 0x61, 0x46, 0x72, 0x6f, 0x6d, 0x22, 0x3a, 0x30, 0x2c, 0x22, 0x4e, 0x75, 0x6d, + 0x50, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x73, 0x22, 0x3a, 0x31, 0x2c, 0x22, 0x53, + 0x69, 0x67, 0x6e, 0x69, 0x6e, 0x67, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, + 0x65, 0x79, 0x22, 0x3a, 0x22, 0x22, 0x2c, 0x22, 0x53, 0x69, 0x67, 0x6e, 0x69, + 0x6e, 0x67, 0x4b, 0x65, 0x79, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, + 0x65, 0x22, 0x3a, 0x22, 0x22, 0x2c, 0x22, 0x50, 0x61, 0x79, 0x6c, 0x6f, 0x61, + 0x64, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x22, 0x3a, 0x22, + 0x22, 0x7d, 0x02, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x78, 0x9c, 0x62, + 0x60, 0x02, 0x04, 0x00, 0x00, 0xff, 0xff, 0x00, 0x04, 0x00, 0x03, +}; + +static const uint8 kUpdateSerialsDelta[] = { + 0xc3, 0x00, 0x7b, 0x22, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x3a, + 0x30, 0x2c, 0x22, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, + 0x65, 0x22, 0x3a, 0x22, 0x43, 0x52, 0x4c, 0x53, 0x65, 0x74, 0x44, 0x65, 0x6c, + 0x74, 0x61, 0x22, 0x2c, 0x22, 0x53, 0x65, 0x71, 0x75, 0x65, 0x6e, 0x63, 0x65, + 0x22, 0x3a, 0x30, 0x2c, 0x22, 0x4e, 0x65, 0x78, 0x74, 0x55, 0x70, 0x64, 0x61, + 0x74, 0x65, 0x22, 0x3a, 0x31, 0x33, 0x31, 0x31, 0x31, 0x32, 0x36, 0x34, 0x36, + 0x31, 0x2c, 0x22, 0x57, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x53, 0x65, 0x63, 0x73, + 0x22, 0x3a, 0x34, 0x33, 0x32, 0x30, 0x30, 0x2c, 0x22, 0x44, 0x65, 0x6c, 0x74, + 0x61, 0x46, 0x72, 0x6f, 0x6d, 0x22, 0x3a, 0x30, 0x2c, 0x22, 0x4e, 0x75, 0x6d, + 0x50, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x73, 0x22, 0x3a, 0x31, 0x2c, 0x22, 0x53, + 0x69, 0x67, 0x6e, 0x69, 0x6e, 0x67, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, + 0x65, 0x79, 0x22, 0x3a, 0x22, 0x22, 0x2c, 0x22, 0x53, 0x69, 0x67, 0x6e, 0x69, + 0x6e, 0x67, 0x4b, 0x65, 0x79, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, + 0x65, 0x22, 0x3a, 0x22, 0x22, 0x2c, 0x22, 0x50, 0x61, 0x79, 0x6c, 0x6f, 0x61, + 0x64, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x22, 0x3a, 0x22, + 0x22, 0x7d, 0x01, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x78, 0x9c, 0x62, + 0x06, 0x04, 0x00, 0x00, 0xff, 0xff, 0x00, 0x04, 0x00, 0x04, 0x2d, 0x00, 0x00, + 0x00, 0x15, 0x00, 0x00, 0x00, 0x78, 0x9c, 0x62, 0x80, 0x00, 0x46, 0x2c, 0x00, + 0x45, 0x14, 0xac, 0x08, 0x10, 0x00, 0x00, 0xff, 0xff, 0x02, 0xe1, 0x00, 0x21, + 0x0a, 0x10, 0x0d, 0x7f, 0x30, 0x00, 0x03, 0x00, 0x00, 0x23, 0xb0, 0x0a, 0x10, + 0x0d, 0x7f, 0x30, 0x00, 0x03, 0x00, 0x00, 0x23, 0xb0, 0x0a, 0x10, 0x0d, 0x7f, + 0x30, 0x00, 0x03, 0x00, 0x00, 0x23, 0xb0, 0x0a, 0x10, 0x0d, 0x7f, 0x30, 0x00, + 0x03, 0x00, 0x00, 0x23, 0xb0, 0x0a, 0x10, 0x0d, 0x7f, 0x30, 0x00, 0x03, 0x00, + 0x00, 0x23, 0xb0, 0x0a, 0x10, 0x0d, 0x7f, 0x30, 0x00, 0x03, 0x00, 0x00, 0x23, + 0xb0, 0x0a, 0x10, 0x0d, 0x7f, 0x30, 0x00, 0x03, 0x00, 0x00, 0x23, 0xb0, 0x0a, + 0x10, 0x0d, 0x7f, 0x30, 0x00, 0x03, 0x00, 0x00, 0x23, 0xb0, 0x0a, 0x10, 0x0d, + 0x7f, 0x30, 0x00, 0x03, 0x00, 0x00, 0x23, 0xb0, 0x0a, 0x10, 0x0d, 0x7f, 0x30, + 0x00, 0x03, 0x00, 0x00, 0x23, 0xb0, 0x0a, 0x10, 0x0d, 0x7f, 0x30, 0x00, 0x03, + 0x00, 0x00, 0x23, 0xb0, 0x0a, 0x10, 0x0d, 0x7f, 0x30, 0x00, 0x03, 0x00, 0x00, + 0x23, 0xb0, 0x0a, 0x10, 0x0d, 0x7f, 0x30, 0x00, 0x03, 0x00, 0x00, 0x23, 0xb0, + 0x0a, 0x10, 0x0d, 0x7f, 0x30, 0x00, 0x03, 0x00, 0x00, 0x23, 0xb0, 0x0a, 0x10, + 0x0d, 0x7f, 0x30, 0x00, 0x03, 0x00, 0x00, 0x23, 0xb0, 0x0a, 0x10, 0x0d, 0x7f, + 0x30, 0x00, 0x03, 0x00, 0x00, 0x23, 0xb0, 0x0a, 0x10, 0x0d, 0x7f, 0x30, 0x00, + 0x03, 0x00, 0x00, 0x23, 0xb0, 0x0a, 0x10, 0x0d, 0x7f, 0x30, 0x00, 0x03, 0x00, + 0x00, 0x23, 0xb0, 0x0a, 0x10, 0x0d, 0x7f, 0x30, 0x00, 0x03, 0x00, 0x00, 0x23, + 0xb0, 0x0a, 0x10, 0x0d, 0x7f, 0x30, 0x00, 0x03, 0x00, 0x00, 0x23, 0xb0, 0x0a, + 0x10, 0x0d, 0x7f, 0x30, 0x00, 0x03, 0x00, 0x00, 0x23, 0xb0, 0x0a, 0x10, 0x0d, + 0x7f, 0x30, 0x00, 0x03, 0x00, 0x00, 0x23, 0xb0, 0x0a, 0x10, 0x0d, 0x7f, 0x30, + 0x00, 0x03, 0x00, 0x00, 0x23, 0xb0, 0x0a, 0x10, 0x0d, 0x7f, 0x30, 0x00, 0x03, + 0x00, 0x00, 0x23, 0xb0, 0x0a, 0x10, 0x0d, 0x7f, 0x30, 0x00, 0x03, 0x00, 0x00, + 0x23, 0xb0, 0x0a, 0x10, 0x0d, 0x7f, 0x30, 0x00, 0x03, 0x00, 0x00, 0x23, 0xb0, + 0x0a, 0x10, 0x0d, 0x7f, 0x30, 0x00, 0x03, 0x00, 0x00, 0x23, 0xb0, 0x0a, 0x10, + 0x0d, 0x7f, 0x30, 0x00, 0x03, 0x00, 0x00, 0x23, 0xb0, 0x0a, 0x10, 0x0d, 0x7f, + 0x30, 0x00, 0x03, 0x00, 0x00, 0x23, 0xb0, 0x0a, 0x10, 0x0d, 0x7f, 0x30, 0x00, + 0x03, 0x00, 0x00, 0x23, 0xb0, 0x0a, 0x10, 0x0d, 0x7f, 0x30, 0x00, 0x03, 0x00, + 0x00, 0x23, 0xb0, 0x0a, 0x10, 0x0d, 0x7f, 0x30, 0x00, 0x03, 0x00, 0x00, 0x23, + 0xb0, +}; + +static const uint8 kBlockedSPKICRLSet[] = { + 0x8e, 0x00, 0x7b, 0x22, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x3a, + 0x30, 0x2c, 0x22, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, + 0x65, 0x22, 0x3a, 0x22, 0x43, 0x52, 0x4c, 0x53, 0x65, 0x74, 0x22, 0x2c, 0x22, + 0x53, 0x65, 0x71, 0x75, 0x65, 0x6e, 0x63, 0x65, 0x22, 0x3a, 0x30, 0x2c, 0x22, + 0x44, 0x65, 0x6c, 0x74, 0x61, 0x46, 0x72, 0x6f, 0x6d, 0x22, 0x3a, 0x30, 0x2c, + 0x22, 0x4e, 0x75, 0x6d, 0x50, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x73, 0x22, 0x3a, + 0x30, 0x2c, 0x22, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x65, 0x64, 0x53, 0x50, 0x4b, + 0x49, 0x73, 0x22, 0x3a, 0x5b, 0x22, 0x34, 0x37, 0x44, 0x45, 0x51, 0x70, 0x6a, + 0x38, 0x48, 0x42, 0x53, 0x61, 0x2b, 0x2f, 0x54, 0x49, 0x6d, 0x57, 0x2b, 0x35, + 0x4a, 0x43, 0x65, 0x75, 0x51, 0x65, 0x52, 0x6b, 0x6d, 0x35, 0x4e, 0x4d, 0x70, + 0x4a, 0x57, 0x5a, 0x47, 0x33, 0x68, 0x53, 0x75, 0x46, 0x55, 0x3d, 0x22, 0x5d, + 0x7d, +}; + +static const uint8 kExpiredCRLSet[] = { + 0x6d, 0x00, 0x7b, 0x22, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x3a, + 0x30, 0x2c, 0x22, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, + 0x65, 0x22, 0x3a, 0x22, 0x43, 0x52, 0x4c, 0x53, 0x65, 0x74, 0x22, 0x2c, 0x22, + 0x53, 0x65, 0x71, 0x75, 0x65, 0x6e, 0x63, 0x65, 0x22, 0x3a, 0x31, 0x2c, 0x22, + 0x44, 0x65, 0x6c, 0x74, 0x61, 0x46, 0x72, 0x6f, 0x6d, 0x22, 0x3a, 0x30, 0x2c, + 0x22, 0x4e, 0x75, 0x6d, 0x50, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x73, 0x22, 0x3a, + 0x30, 0x2c, 0x22, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x65, 0x64, 0x53, 0x50, 0x4b, + 0x49, 0x73, 0x22, 0x3a, 0x5b, 0x5d, 0x2c, 0x22, 0x4e, 0x6f, 0x74, 0x41, 0x66, + 0x74, 0x65, 0x72, 0x22, 0x3a, 0x31, 0x7d, +}; + +// kGIASPKISHA256 is the SHA256 digest the Google Internet Authority's +// SubjectPublicKeyInfo. +static const uint8 kGIASPKISHA256[32] = { + 0xb6, 0xb9, 0x54, 0x32, 0xab, 0xae, 0x57, 0xfe, 0x02, 0x0c, 0xb2, 0xb7, 0x4f, + 0x4f, 0x9f, 0x91, 0x73, 0xc8, 0xc7, 0x08, 0xaf, 0xc9, 0xe7, 0x32, 0xac, 0xe2, + 0x32, 0x79, 0x04, 0x7c, 0x6d, 0x05, +}; + +TEST(CRLSetTest, Parse) { + base::StringPiece s(reinterpret_cast<const char*>(kGIACRLSet), + sizeof(kGIACRLSet)); + scoped_refptr<net::CRLSet> set; + EXPECT_TRUE(net::CRLSet::Parse(s, &set)); + ASSERT_TRUE(set.get() != NULL); + + const net::CRLSet::CRLList& crls = set->crls(); + ASSERT_EQ(1u, crls.size()); + const std::vector<std::string>& serials = crls[0].second; + static const unsigned kExpectedNumSerials = 13; + ASSERT_EQ(kExpectedNumSerials, serials.size()); + EXPECT_EQ(std::string("\x10\x0D\x7F\x30\x00\x03\x00\x00\x23\xB0", 10), + serials[0]); + EXPECT_EQ(std::string("\x64\x63\x49\xD2\x00\x03\x00\x00\x1D\x77", 10), + serials[kExpectedNumSerials - 1]); + + const std::string gia_spki_hash( + reinterpret_cast<const char*>(kGIASPKISHA256), + sizeof(kGIASPKISHA256)); + EXPECT_EQ(net::CRLSet::REVOKED, set->CheckSerial( + std::string("\x16\x7D\x75\x9D\x00\x03\x00\x00\x14\x55", 10), + gia_spki_hash)); + EXPECT_EQ(net::CRLSet::GOOD, set->CheckSerial( + std::string("\x47\x54\x3E\x79\x00\x03\x00\x00\x14\xF5", 10), + gia_spki_hash)); + + EXPECT_FALSE(set->IsExpired()); +} + +TEST(CRLSetTest, NoOpDeltaUpdate) { + base::StringPiece s(reinterpret_cast<const char*>(kGIACRLSet), + sizeof(kGIACRLSet)); + scoped_refptr<net::CRLSet> set; + EXPECT_TRUE(net::CRLSet::Parse(s, &set)); + ASSERT_TRUE(set.get() != NULL); + + scoped_refptr<net::CRLSet> delta_set; + base::StringPiece delta(reinterpret_cast<const char*>(kNoopDeltaCRL), + sizeof(kNoopDeltaCRL)); + EXPECT_TRUE(set->ApplyDelta(delta, &delta_set)); + ASSERT_TRUE(delta_set.get() != NULL); + + std::string out = delta_set->Serialize(); + EXPECT_EQ(s.as_string(), out); +} + +TEST(CRLSetTest, AddCRLDelta) { + base::StringPiece s(reinterpret_cast<const char*>(kGIACRLSet), + sizeof(kGIACRLSet)); + scoped_refptr<net::CRLSet> set; + EXPECT_TRUE(net::CRLSet::Parse(s, &set)); + ASSERT_TRUE(set.get() != NULL); + + scoped_refptr<net::CRLSet> delta_set; + base::StringPiece delta(reinterpret_cast<const char*>(kAddCRLDelta), + sizeof(kAddCRLDelta)); + EXPECT_TRUE(set->ApplyDelta(delta, &delta_set)); + ASSERT_TRUE(delta_set.get() != NULL); + + const net::CRLSet::CRLList& crls = delta_set->crls(); + ASSERT_EQ(2u, crls.size()); + const std::vector<std::string>& serials = crls[1].second; + ASSERT_EQ(12u, serials.size()); + EXPECT_EQ(std::string("\x02", 1), serials[0]); + EXPECT_EQ(std::string("\x03", 1), serials[1]); + EXPECT_EQ(std::string("\x04", 1), serials[2]); +} + +TEST(CRLSetTest, AddRemoveCRLDelta) { + base::StringPiece s(reinterpret_cast<const char*>(kGIACRLSet), + sizeof(kGIACRLSet)); + scoped_refptr<net::CRLSet> set; + EXPECT_TRUE(net::CRLSet::Parse(s, &set)); + ASSERT_TRUE(set.get() != NULL); + + scoped_refptr<net::CRLSet> delta_set; + base::StringPiece delta(reinterpret_cast<const char*>(kAddCRLDelta), + sizeof(kAddCRLDelta)); + EXPECT_TRUE(set->ApplyDelta(delta, &delta_set)); + ASSERT_TRUE(delta_set.get() != NULL); + + scoped_refptr<net::CRLSet> delta2_set; + base::StringPiece delta2(reinterpret_cast<const char*>(kRemoveCRLDelta), + sizeof(kRemoveCRLDelta)); + EXPECT_TRUE(delta_set->ApplyDelta(delta2, &delta2_set)); + ASSERT_TRUE(delta2_set.get() != NULL); + + const net::CRLSet::CRLList& crls = delta2_set->crls(); + ASSERT_EQ(1u, crls.size()); + const std::vector<std::string>& serials = crls[0].second; + ASSERT_EQ(13u, serials.size()); +} + +TEST(CRLSetTest, UpdateSerialsDelta) { + base::StringPiece s(reinterpret_cast<const char*>(kGIACRLSet), + sizeof(kGIACRLSet)); + scoped_refptr<net::CRLSet> set; + EXPECT_TRUE(net::CRLSet::Parse(s, &set)); + ASSERT_TRUE(set.get() != NULL); + + scoped_refptr<net::CRLSet> delta_set; + base::StringPiece delta(reinterpret_cast<const char*>(kUpdateSerialsDelta), + sizeof(kUpdateSerialsDelta)); + EXPECT_TRUE(set->ApplyDelta(delta, &delta_set)); + ASSERT_TRUE(delta_set.get() != NULL); + + const net::CRLSet::CRLList& crls = delta_set->crls(); + ASSERT_EQ(1u, crls.size()); + const std::vector<std::string>& serials = crls[0].second; + EXPECT_EQ(45u, serials.size()); +} + +TEST(CRLSetTest, BlockedSPKIs) { + base::StringPiece s(reinterpret_cast<const char*>(kBlockedSPKICRLSet), + sizeof(kBlockedSPKICRLSet)); + scoped_refptr<net::CRLSet> set; + EXPECT_TRUE(net::CRLSet::Parse(s, &set)); + ASSERT_TRUE(set.get() != NULL); + + const uint8 spki_hash[] = { + 227, 176, 196, 66, 152, 252, 28, 20, 154, 251, 244, 200, 153, 111, 185, 36, + 39, 174, 65, 228, 100, 155, 147, 76, 164, 149, 153, 27, 120, 82, 184, 85, + 0, + }; + + EXPECT_EQ(net::CRLSet::GOOD, set->CheckSPKI("")); + EXPECT_EQ(net::CRLSet::REVOKED, set->CheckSPKI( + reinterpret_cast<const char*>(spki_hash))); +} + +TEST(CRLSetTest, Expired) { + // This CRLSet has an expiry value set to one second past midnight, 1st Jan, + // 1970. + base::StringPiece s(reinterpret_cast<const char*>(kExpiredCRLSet), + sizeof(kExpiredCRLSet)); + scoped_refptr<net::CRLSet> set; + EXPECT_TRUE(net::CRLSet::Parse(s, &set)); + ASSERT_TRUE(set.get() != NULL); + + EXPECT_TRUE(set->IsExpired()); +} diff --git a/net/cert/ev_root_ca_metadata.cc b/net/cert/ev_root_ca_metadata.cc new file mode 100644 index 0000000..6ef746b --- /dev/null +++ b/net/cert/ev_root_ca_metadata.cc @@ -0,0 +1,553 @@ +// Copyright (c) 2012 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 "net/cert/ev_root_ca_metadata.h" + +#if defined(USE_NSS) || defined(OS_IOS) +#include <cert.h> +#include <pkcs11n.h> +#include <secerr.h> +#include <secoid.h> +#elif defined(OS_WIN) +#include <stdlib.h> +#endif + +#include "base/lazy_instance.h" +#include "base/logging.h" +#if defined(USE_NSS) || defined(OS_IOS) +#include "crypto/nss_util.h" +#endif + +namespace net { + +// Raw metadata. +struct EVMetadata { + // kMaxOIDsPerCA is the number of OIDs that we can support per root CA. At + // least one CA has different EV policies for businuss vs government + // entities and, in the case of cross-signing, we might need to list another + // CA's policy OID under the cross-signing root. + static const size_t kMaxOIDsPerCA = 2; + // This is the maximum length of an OID string (including the trailing NUL). + static const size_t kMaxOIDLength = 32; + + // The SHA-1 fingerprint of the root CA certificate, used as a unique + // identifier for a root CA certificate. + SHA1HashValue fingerprint; + + // The EV policy OIDs of the root CA. + const char policy_oids[kMaxOIDsPerCA][kMaxOIDLength]; +}; + +static const EVMetadata ev_root_ca_metadata[] = { + // AC Camerfirma S.A. Chambers of Commerce Root - 2008 + // https://www.camerfirma.com + { { { 0x78, 0x6a, 0x74, 0xac, 0x76, 0xab, 0x14, 0x7f, 0x9c, 0x6a, + 0x30, 0x50, 0xba, 0x9e, 0xa8, 0x7e, 0xfe, 0x9a, 0xce, 0x3c } }, + { // AC Camerfirma uses the last two arcs to track how the private key is + // managed - the effective verification policy is the same. + "1.3.6.1.4.1.17326.10.14.2.1.2", + "1.3.6.1.4.1.17326.10.14.2.2.2", }, + }, + // AC Camerfirma S.A. Global Chambersign Root - 2008 + // https://server2.camerfirma.com:8082 + { { { 0x4a, 0xbd, 0xee, 0xec, 0x95, 0x0d, 0x35, 0x9c, 0x89, 0xae, + 0xc7, 0x52, 0xa1, 0x2c, 0x5b, 0x29, 0xf6, 0xd6, 0xaa, 0x0c } }, + { // AC Camerfirma uses the last two arcs to track how the private key is + // managed - the effective verification policy is the same. + "1.3.6.1.4.1.17326.10.8.12.1.2", + "1.3.6.1.4.1.17326.10.8.12.2.2", }, + }, + // AddTrust External CA Root + // https://addtrustexternalcaroot-ev.comodoca.com + { { { 0x02, 0xfa, 0xf3, 0xe2, 0x91, 0x43, 0x54, 0x68, 0x60, 0x78, + 0x57, 0x69, 0x4d, 0xf5, 0xe4, 0x5b, 0x68, 0x85, 0x18, 0x68 } }, + { + "1.3.6.1.4.1.6449.1.2.1.5.1", + // This is the Network Solutions EV OID. However, this root + // cross-certifies NetSol and so we need it here too. + "1.3.6.1.4.1.782.1.2.1.8.1", + }, + }, + // AffirmTrust Commercial + // https://commercial.affirmtrust.com/ + { { { 0xf9, 0xb5, 0xb6, 0x32, 0x45, 0x5f, 0x9c, 0xbe, 0xec, 0x57, + 0x5f, 0x80, 0xdc, 0xe9, 0x6e, 0x2c, 0xc7, 0xb2, 0x78, 0xb7 } }, + {"1.3.6.1.4.1.34697.2.1", ""}, + }, + // AffirmTrust Networking + // https://networking.affirmtrust.com:4431 + { { { 0x29, 0x36, 0x21, 0x02, 0x8b, 0x20, 0xed, 0x02, 0xf5, 0x66, + 0xc5, 0x32, 0xd1, 0xd6, 0xed, 0x90, 0x9f, 0x45, 0x00, 0x2f } }, + {"1.3.6.1.4.1.34697.2.2", ""}, + }, + // AffirmTrust Premium + // https://premium.affirmtrust.com:4432/ + { { { 0xd8, 0xa6, 0x33, 0x2c, 0xe0, 0x03, 0x6f, 0xb1, 0x85, 0xf6, + 0x63, 0x4f, 0x7d, 0x6a, 0x06, 0x65, 0x26, 0x32, 0x28, 0x27 } }, + {"1.3.6.1.4.1.34697.2.3", ""}, + }, + // AffirmTrust Premium ECC + // https://premiumecc.affirmtrust.com:4433/ + { { { 0xb8, 0x23, 0x6b, 0x00, 0x2f, 0x1d, 0x16, 0x86, 0x53, 0x01, + 0x55, 0x6c, 0x11, 0xa4, 0x37, 0xca, 0xeb, 0xff, 0xc3, 0xbb } }, + {"1.3.6.1.4.1.34697.2.4", ""}, + }, + // CertPlus Class 2 Primary CA (KEYNECTIS) + // https://www.keynectis.com/ + { { { 0x74, 0x20, 0x74, 0x41, 0x72, 0x9c, 0xdd, 0x92, 0xec, 0x79, + 0x31, 0xd8, 0x23, 0x10, 0x8d, 0xc2, 0x81, 0x92, 0xe2, 0xbb } }, + {"1.3.6.1.4.1.22234.2.5.2.3.1", ""}, + }, + // Certum Trusted Network CA + // https://juice.certum.pl/ + { { { 0x07, 0xe0, 0x32, 0xe0, 0x20, 0xb7, 0x2c, 0x3f, 0x19, 0x2f, + 0x06, 0x28, 0xa2, 0x59, 0x3a, 0x19, 0xa7, 0x0f, 0x06, 0x9e } }, + {"1.2.616.1.113527.2.5.1.1", ""}, + }, + // COMODO Certification Authority + // https://secure.comodo.com/ + { { { 0x66, 0x31, 0xbf, 0x9e, 0xf7, 0x4f, 0x9e, 0xb6, 0xc9, 0xd5, + 0xa6, 0x0c, 0xba, 0x6a, 0xbe, 0xd1, 0xf7, 0xbd, 0xef, 0x7b } }, + {"1.3.6.1.4.1.6449.1.2.1.5.1", ""}, + }, + // COMODO Certification Authority (reissued certificate with NotBefore of Jan + // 1 00:00:00 2011 GMT) + // https://secure.comodo.com/ + { { { 0xee, 0x86, 0x93, 0x87, 0xff, 0xfd, 0x83, 0x49, 0xab, 0x5a, + 0xd1, 0x43, 0x22, 0x58, 0x87, 0x89, 0xa4, 0x57, 0xb0, 0x12 } }, + {"1.3.6.1.4.1.6449.1.2.1.5.1", ""}, + }, + // COMODO ECC Certification Authority + // https://comodoecccertificationauthority-ev.comodoca.com/ + { { { 0x9f, 0x74, 0x4e, 0x9f, 0x2b, 0x4d, 0xba, 0xec, 0x0f, 0x31, + 0x2c, 0x50, 0xb6, 0x56, 0x3b, 0x8e, 0x2d, 0x93, 0xc3, 0x11 } }, + {"1.3.6.1.4.1.6449.1.2.1.5.1", ""}, + }, + // Cybertrust Global Root + // https://evup.cybertrust.ne.jp/ctj-ev-upgrader/evseal.gif + { { { 0x5f, 0x43, 0xe5, 0xb1, 0xbf, 0xf8, 0x78, 0x8c, 0xac, 0x1c, + 0xc7, 0xca, 0x4a, 0x9a, 0xc6, 0x22, 0x2b, 0xcc, 0x34, 0xc6 } }, + {"1.3.6.1.4.1.6334.1.100.1", ""}, + }, + // DigiCert High Assurance EV Root CA + // https://www.digicert.com + { { { 0x5f, 0xb7, 0xee, 0x06, 0x33, 0xe2, 0x59, 0xdb, 0xad, 0x0c, + 0x4c, 0x9a, 0xe6, 0xd3, 0x8f, 0x1a, 0x61, 0xc7, 0xdc, 0x25 } }, + {"2.16.840.1.114412.2.1", ""}, + }, + // D-TRUST Root Class 3 CA 2 EV 2009 + // https://certdemo-ev-valid.ssl.d-trust.net/ + { { { 0x96, 0xc9, 0x1b, 0x0b, 0x95, 0xb4, 0x10, 0x98, 0x42, 0xfa, + 0xd0, 0xd8, 0x22, 0x79, 0xfe, 0x60, 0xfa, 0xb9, 0x16, 0x83 } }, + {"1.3.6.1.4.1.4788.2.202.1", ""}, + }, + // Entrust.net Secure Server Certification Authority + // https://www.entrust.net/ + { { { 0x99, 0xa6, 0x9b, 0xe6, 0x1a, 0xfe, 0x88, 0x6b, 0x4d, 0x2b, + 0x82, 0x00, 0x7c, 0xb8, 0x54, 0xfc, 0x31, 0x7e, 0x15, 0x39 } }, + {"2.16.840.1.114028.10.1.2", ""}, + }, + // Entrust Root Certification Authority + // https://www.entrust.net/ + { { { 0xb3, 0x1e, 0xb1, 0xb7, 0x40, 0xe3, 0x6c, 0x84, 0x02, 0xda, + 0xdc, 0x37, 0xd4, 0x4d, 0xf5, 0xd4, 0x67, 0x49, 0x52, 0xf9 } }, + {"2.16.840.1.114028.10.1.2", ""}, + }, + // Equifax Secure Certificate Authority (GeoTrust) + // https://www.geotrust.com/ + { { { 0xd2, 0x32, 0x09, 0xad, 0x23, 0xd3, 0x14, 0x23, 0x21, 0x74, + 0xe4, 0x0d, 0x7f, 0x9d, 0x62, 0x13, 0x97, 0x86, 0x63, 0x3a } }, + {"1.3.6.1.4.1.14370.1.6", ""}, + }, + // GeoTrust Primary Certification Authority + // https://www.geotrust.com/ + { { { 0x32, 0x3c, 0x11, 0x8e, 0x1b, 0xf7, 0xb8, 0xb6, 0x52, 0x54, + 0xe2, 0xe2, 0x10, 0x0d, 0xd6, 0x02, 0x90, 0x37, 0xf0, 0x96 } }, + {"1.3.6.1.4.1.14370.1.6", ""}, + }, + // GeoTrust Primary Certification Authority - G2 + { { { 0x8d, 0x17, 0x84, 0xd5, 0x37, 0xf3, 0x03, 0x7d, 0xec, 0x70, + 0xfe, 0x57, 0x8b, 0x51, 0x9a, 0x99, 0xe6, 0x10, 0xd7, 0xb0 } }, + {"1.3.6.1.4.1.14370.1.6", ""}, + }, + // GeoTrust Primary Certification Authority - G3 + { { { 0x03, 0x9e, 0xed, 0xb8, 0x0b, 0xe7, 0xa0, 0x3c, 0x69, 0x53, + 0x89, 0x3b, 0x20, 0xd2, 0xd9, 0x32, 0x3a, 0x4c, 0x2a, 0xfd } }, + {"1.3.6.1.4.1.14370.1.6", ""}, + }, + // GlobalSign Root CA - R2 + // https://www.globalsign.com/ + { { { 0x75, 0xe0, 0xab, 0xb6, 0x13, 0x85, 0x12, 0x27, 0x1c, 0x04, + 0xf8, 0x5f, 0xdd, 0xde, 0x38, 0xe4, 0xb7, 0x24, 0x2e, 0xfe } }, + {"1.3.6.1.4.1.4146.1.1", ""}, + }, + // GlobalSign Root CA + { { { 0xb1, 0xbc, 0x96, 0x8b, 0xd4, 0xf4, 0x9d, 0x62, 0x2a, 0xa8, + 0x9a, 0x81, 0xf2, 0x15, 0x01, 0x52, 0xa4, 0x1d, 0x82, 0x9c } }, + {"1.3.6.1.4.1.4146.1.1", ""}, + }, + // GlobalSign Root CA - R3 + // https://2029.globalsign.com/ + { { { 0xd6, 0x9b, 0x56, 0x11, 0x48, 0xf0, 0x1c, 0x77, 0xc5, 0x45, + 0x78, 0xc1, 0x09, 0x26, 0xdf, 0x5b, 0x85, 0x69, 0x76, 0xad } }, + {"1.3.6.1.4.1.4146.1.1", ""}, + }, + // Go Daddy Class 2 Certification Authority + // https://www.godaddy.com/ + { { { 0x27, 0x96, 0xba, 0xe6, 0x3f, 0x18, 0x01, 0xe2, 0x77, 0x26, + 0x1b, 0xa0, 0xd7, 0x77, 0x70, 0x02, 0x8f, 0x20, 0xee, 0xe4 } }, + {"2.16.840.1.114413.1.7.23.3", ""}, + }, + // GTE CyberTrust Global Root + // https://www.cybertrust.ne.jp/ + { { { 0x97, 0x81, 0x79, 0x50, 0xd8, 0x1c, 0x96, 0x70, 0xcc, 0x34, + 0xd8, 0x09, 0xcf, 0x79, 0x44, 0x31, 0x36, 0x7e, 0xf4, 0x74 } }, + {"1.3.6.1.4.1.6334.1.100.1", ""}, + }, + // Izenpe.com - SHA256 root + // The first OID is for businesses and the second for government entities. + // These are the test sites, respectively: + // https://servicios.izenpe.com + // https://servicios1.izenpe.com + { { { 0x2f, 0x78, 0x3d, 0x25, 0x52, 0x18, 0xa7, 0x4a, 0x65, 0x39, + 0x71, 0xb5, 0x2c, 0xa2, 0x9c, 0x45, 0x15, 0x6f, 0xe9, 0x19} }, + {"1.3.6.1.4.1.14777.6.1.1", "1.3.6.1.4.1.14777.6.1.2"}, + }, + // Izenpe.com - SHA1 root + // Windows XP finds this, SHA1, root instead. The policy OIDs are the same as + // for the SHA256 root, above. + { { { 0x30, 0x77, 0x9e, 0x93, 0x15, 0x02, 0x2e, 0x94, 0x85, 0x6a, + 0x3f, 0xf8, 0xbc, 0xf8, 0x15, 0xb0, 0x82, 0xf9, 0xae, 0xfd} }, + {"1.3.6.1.4.1.14777.6.1.1", "1.3.6.1.4.1.14777.6.1.2"}, + }, + // Network Solutions Certificate Authority + // https://www.networksolutions.com/website-packages/index.jsp + { { { 0x74, 0xf8, 0xa3, 0xc3, 0xef, 0xe7, 0xb3, 0x90, 0x06, 0x4b, + 0x83, 0x90, 0x3c, 0x21, 0x64, 0x60, 0x20, 0xe5, 0xdf, 0xce } }, + {"1.3.6.1.4.1.782.1.2.1.8.1", ""}, + }, + // Network Solutions Certificate Authority (reissued certificate with + // NotBefore of Jan 1 00:00:00 2011 GMT). + // https://www.networksolutions.com/website-packages/index.jsp + { { { 0x71, 0x89, 0x9a, 0x67, 0xbf, 0x33, 0xaf, 0x31, 0xbe, 0xfd, + 0xc0, 0x71, 0xf8, 0xf7, 0x33, 0xb1, 0x83, 0x85, 0x63, 0x32 } }, + {"1.3.6.1.4.1.782.1.2.1.8.1", ""}, + }, + // QuoVadis Root CA 2 + // https://www.quovadis.bm/ + { { { 0xca, 0x3a, 0xfb, 0xcf, 0x12, 0x40, 0x36, 0x4b, 0x44, 0xb2, + 0x16, 0x20, 0x88, 0x80, 0x48, 0x39, 0x19, 0x93, 0x7c, 0xf7 } }, + {"1.3.6.1.4.1.8024.0.2.100.1.2", ""}, + }, + // SecureTrust CA, SecureTrust Corporation + // https://www.securetrust.com + // https://www.trustwave.com/ + { { { 0x87, 0x82, 0xc6, 0xc3, 0x04, 0x35, 0x3b, 0xcf, 0xd2, 0x96, + 0x92, 0xd2, 0x59, 0x3e, 0x7d, 0x44, 0xd9, 0x34, 0xff, 0x11 } }, + {"2.16.840.1.114404.1.1.2.4.1", ""}, + }, + // Secure Global CA, SecureTrust Corporation + { { { 0x3a, 0x44, 0x73, 0x5a, 0xe5, 0x81, 0x90, 0x1f, 0x24, 0x86, + 0x61, 0x46, 0x1e, 0x3b, 0x9c, 0xc4, 0x5f, 0xf5, 0x3a, 0x1b } }, + {"2.16.840.1.114404.1.1.2.4.1", ""}, + }, + // Security Communication RootCA1 + // https://www.secomtrust.net/contact/form.html + { { { 0x36, 0xb1, 0x2b, 0x49, 0xf9, 0x81, 0x9e, 0xd7, 0x4c, 0x9e, + 0xbc, 0x38, 0x0f, 0xc6, 0x56, 0x8f, 0x5d, 0xac, 0xb2, 0xf7 } }, + {"1.2.392.200091.100.721.1", ""}, + }, + // Security Communication EV RootCA1 + // https://www.secomtrust.net/contact/form.html + { { { 0xfe, 0xb8, 0xc4, 0x32, 0xdc, 0xf9, 0x76, 0x9a, 0xce, 0xae, + 0x3d, 0xd8, 0x90, 0x8f, 0xfd, 0x28, 0x86, 0x65, 0x64, 0x7d } }, + {"1.2.392.200091.100.721.1", ""}, + }, + // StartCom Certification Authority + // https://www.startssl.com/ + { { { 0x3e, 0x2b, 0xf7, 0xf2, 0x03, 0x1b, 0x96, 0xf3, 0x8c, 0xe6, + 0xc4, 0xd8, 0xa8, 0x5d, 0x3e, 0x2d, 0x58, 0x47, 0x6a, 0x0f } }, + {"1.3.6.1.4.1.23223.1.1.1", ""}, + }, + // Starfield Class 2 Certification Authority + // https://www.starfieldtech.com/ + { { { 0xad, 0x7e, 0x1c, 0x28, 0xb0, 0x64, 0xef, 0x8f, 0x60, 0x03, + 0x40, 0x20, 0x14, 0xc3, 0xd0, 0xe3, 0x37, 0x0e, 0xb5, 0x8a } }, + {"2.16.840.1.114414.1.7.23.3", ""}, + }, + // SwissSign Gold CA - G2 + // https://testevg2.swisssign.net/ + { { { 0xd8, 0xc5, 0x38, 0x8a, 0xb7, 0x30, 0x1b, 0x1b, 0x6e, 0xd4, + 0x7a, 0xe6, 0x45, 0x25, 0x3a, 0x6f, 0x9f, 0x1a, 0x27, 0x61 } }, + {"2.16.756.1.89.1.2.1.1", ""}, + }, + // Thawte Premium Server CA + // https://www.thawte.com/ + { { { 0x62, 0x7f, 0x8d, 0x78, 0x27, 0x65, 0x63, 0x99, 0xd2, 0x7d, + 0x7f, 0x90, 0x44, 0xc9, 0xfe, 0xb3, 0xf3, 0x3e, 0xfa, 0x9a } }, + {"2.16.840.1.113733.1.7.48.1", ""}, + }, + // thawte Primary Root CA + // https://www.thawte.com/ + { { { 0x91, 0xc6, 0xd6, 0xee, 0x3e, 0x8a, 0xc8, 0x63, 0x84, 0xe5, + 0x48, 0xc2, 0x99, 0x29, 0x5c, 0x75, 0x6c, 0x81, 0x7b, 0x81 } }, + {"2.16.840.1.113733.1.7.48.1", ""}, + }, + // thawte Primary Root CA - G2 + { { { 0xaa, 0xdb, 0xbc, 0x22, 0x23, 0x8f, 0xc4, 0x01, 0xa1, 0x27, + 0xbb, 0x38, 0xdd, 0xf4, 0x1d, 0xdb, 0x08, 0x9e, 0xf0, 0x12 } }, + {"2.16.840.1.113733.1.7.48.1", ""}, + }, + // thawte Primary Root CA - G3 + { { { 0xf1, 0x8b, 0x53, 0x8d, 0x1b, 0xe9, 0x03, 0xb6, 0xa6, 0xf0, + 0x56, 0x43, 0x5b, 0x17, 0x15, 0x89, 0xca, 0xf3, 0x6b, 0xf2 } }, + {"2.16.840.1.113733.1.7.48.1", ""}, + }, + // TWCA Root Certification Authority + // https://evssldemo.twca.com.tw/index.html + { { { 0xcf, 0x9e, 0x87, 0x6d, 0xd3, 0xeb, 0xfc, 0x42, 0x26, 0x97, + 0xa3, 0xb5, 0xa3, 0x7a, 0xa0, 0x76, 0xa9, 0x06, 0x23, 0x48 } }, + {"1.3.6.1.4.1.40869.1.1.22.3", ""}, + }, + // T-TeleSec GlobalRoot Class 3 + // http://www.telesec.de/ / https://root-class3.test.telesec.de/ + { { { 0x55, 0xa6, 0x72, 0x3e, 0xcb, 0xf2, 0xec, 0xcd, 0xc3, 0x23, + 0x74, 0x70, 0x19, 0x9d, 0x2a, 0xbe, 0x11, 0xe3, 0x81, 0xd1 } }, + {"1.3.6.1.4.1.7879.13.24.1", "" }, + }, + // UTN - DATACorp SGC + { { { 0x58, 0x11, 0x9f, 0x0e, 0x12, 0x82, 0x87, 0xea, 0x50, 0xfd, + 0xd9, 0x87, 0x45, 0x6f, 0x4f, 0x78, 0xdc, 0xfa, 0xd6, 0xd4 } }, + {"1.3.6.1.4.1.6449.1.2.1.5.1", ""}, + }, + // UTN-USERFirst-Hardware + { { { 0x04, 0x83, 0xed, 0x33, 0x99, 0xac, 0x36, 0x08, 0x05, 0x87, + 0x22, 0xed, 0xbc, 0x5e, 0x46, 0x00, 0xe3, 0xbe, 0xf9, 0xd7 } }, + { + "1.3.6.1.4.1.6449.1.2.1.5.1", + // This is the Network Solutions EV OID. However, this root + // cross-certifies NetSol and so we need it here too. + "1.3.6.1.4.1.782.1.2.1.8.1", + }, + }, + // ValiCert Class 2 Policy Validation Authority + { { { 0x31, 0x7a, 0x2a, 0xd0, 0x7f, 0x2b, 0x33, 0x5e, 0xf5, 0xa1, + 0xc3, 0x4e, 0x4b, 0x57, 0xe8, 0xb7, 0xd8, 0xf1, 0xfc, 0xa6 } }, + {"2.16.840.1.114413.1.7.23.3", "2.16.840.1.114414.1.7.23.3"}, + }, + // VeriSign Class 3 Public Primary Certification Authority + // https://www.verisign.com/ + { { { 0x74, 0x2c, 0x31, 0x92, 0xe6, 0x07, 0xe4, 0x24, 0xeb, 0x45, + 0x49, 0x54, 0x2b, 0xe1, 0xbb, 0xc5, 0x3e, 0x61, 0x74, 0xe2 } }, + {"2.16.840.1.113733.1.7.23.6", ""}, + }, + // VeriSign Class 3 Public Primary Certification Authority - G4 + { { { 0x22, 0xD5, 0xD8, 0xDF, 0x8F, 0x02, 0x31, 0xD1, 0x8D, 0xF7, + 0x9D, 0xB7, 0xCF, 0x8A, 0x2D, 0x64, 0xC9, 0x3F, 0x6C, 0x3A } }, + {"2.16.840.1.113733.1.7.23.6", ""}, + }, + // VeriSign Class 3 Public Primary Certification Authority - G5 + // https://www.verisign.com/ + { { { 0x4e, 0xb6, 0xd5, 0x78, 0x49, 0x9b, 0x1c, 0xcf, 0x5f, 0x58, + 0x1e, 0xad, 0x56, 0xbe, 0x3d, 0x9b, 0x67, 0x44, 0xa5, 0xe5 } }, + {"2.16.840.1.113733.1.7.23.6", ""}, + }, + // VeriSign Universal Root Certification Authority + { { { 0x36, 0x79, 0xca, 0x35, 0x66, 0x87, 0x72, 0x30, 0x4d, 0x30, + 0xa5, 0xfb, 0x87, 0x3b, 0x0f, 0xa7, 0x7b, 0xb7, 0x0d, 0x54 } }, + {"2.16.840.1.113733.1.7.23.6", ""}, + }, + // Wells Fargo WellsSecure Public Root Certificate Authority + // https://nerys.wellsfargo.com/test.html + { { { 0xe7, 0xb4, 0xf6, 0x9d, 0x61, 0xec, 0x90, 0x69, 0xdb, 0x7e, + 0x90, 0xa7, 0x40, 0x1a, 0x3c, 0xf4, 0x7d, 0x4f, 0xe8, 0xee } }, + {"2.16.840.1.114171.500.9", ""}, + }, + // XRamp Global Certification Authority + { { { 0xb8, 0x01, 0x86, 0xd1, 0xeb, 0x9c, 0x86, 0xa5, 0x41, 0x04, + 0xcf, 0x30, 0x54, 0xf3, 0x4c, 0x52, 0xb7, 0xe5, 0x58, 0xc6 } }, + {"2.16.840.1.114404.1.1.2.4.1", ""}, + } +}; + +static base::LazyInstance<EVRootCAMetadata>::Leaky + g_ev_root_ca_metadata = LAZY_INSTANCE_INITIALIZER; + +// static +EVRootCAMetadata* EVRootCAMetadata::GetInstance() { + return g_ev_root_ca_metadata.Pointer(); +} + +#if defined(USE_NSS) || defined(OS_IOS) +bool EVRootCAMetadata::IsEVPolicyOID(PolicyOID policy_oid) const { + return policy_oids_.find(policy_oid) != policy_oids_.end(); +} + +bool EVRootCAMetadata::HasEVPolicyOID( + const SHA1HashValue& fingerprint, + PolicyOID policy_oid) const { + PolicyOIDMap::const_iterator iter = ev_policy_.find(fingerprint); + if (iter == ev_policy_.end()) + return false; + for (std::vector<PolicyOID>::const_iterator + j = iter->second.begin(); j != iter->second.end(); ++j) { + if (*j == policy_oid) + return true; + } + return false; +} + +bool EVRootCAMetadata::AddEVCA(const SHA1HashValue& fingerprint, + const char* policy) { + if (ev_policy_.find(fingerprint) != ev_policy_.end()) + return false; + + PolicyOID oid; + if (!RegisterOID(policy, &oid)) + return false; + + ev_policy_[fingerprint].push_back(oid); + policy_oids_.insert(oid); + + return true; +} + +bool EVRootCAMetadata::RemoveEVCA(const SHA1HashValue& fingerprint) { + PolicyOIDMap::iterator it = ev_policy_.find(fingerprint); + if (it == ev_policy_.end()) + return false; + PolicyOID oid = it->second[0]; + ev_policy_.erase(it); + policy_oids_.erase(oid); + return true; +} + +// static +bool EVRootCAMetadata::RegisterOID(const char* policy, + PolicyOID* out) { + PRUint8 buf[64]; + SECItem oid_item; + oid_item.data = buf; + oid_item.len = sizeof(buf); + SECStatus status = SEC_StringToOID(NULL, &oid_item, policy, 0); + if (status != SECSuccess) + return false; + + // Register the OID. + SECOidData od; + od.oid.len = oid_item.len; + od.oid.data = oid_item.data; + od.offset = SEC_OID_UNKNOWN; + od.desc = policy; + od.mechanism = CKM_INVALID_MECHANISM; + od.supportedExtension = INVALID_CERT_EXTENSION; + *out = SECOID_AddEntry(&od); + return *out != SEC_OID_UNKNOWN; +} + +#elif defined(OS_WIN) + +bool EVRootCAMetadata::IsEVPolicyOID(PolicyOID policy_oid) const { + for (size_t i = 0; i < arraysize(ev_root_ca_metadata); i++) { + for (size_t j = 0; j < arraysize(ev_root_ca_metadata[i].policy_oids); j++) { + if (ev_root_ca_metadata[i].policy_oids[j][0] == '\0') + break; + if (strcmp(policy_oid, ev_root_ca_metadata[i].policy_oids[j]) == 0) + return true; + } + } + + for (ExtraEVCAMap::const_iterator i = extra_cas_.begin(); + i != extra_cas_.end(); i++) { + if (i->second == policy_oid) + return true; + } + + return false; +} + +bool EVRootCAMetadata::HasEVPolicyOID(const SHA1HashValue& fingerprint, + PolicyOID policy_oid) const { + for (size_t i = 0; i < arraysize(ev_root_ca_metadata); i++) { + if (!fingerprint.Equals(ev_root_ca_metadata[i].fingerprint)) + continue; + for (size_t j = 0; j < arraysize(ev_root_ca_metadata[i].policy_oids); j++) { + if (ev_root_ca_metadata[i].policy_oids[j][0] == '\0') + break; + if (strcmp(policy_oid, ev_root_ca_metadata[i].policy_oids[j]) == 0) + return true; + } + return false; + } + + ExtraEVCAMap::const_iterator it = extra_cas_.find(fingerprint); + return it != extra_cas_.end() && it->second == policy_oid; +} + +bool EVRootCAMetadata::AddEVCA(const SHA1HashValue& fingerprint, + const char* policy) { + for (size_t i = 0; i < arraysize(ev_root_ca_metadata); i++) { + if (fingerprint.Equals(ev_root_ca_metadata[i].fingerprint)) + return false; + } + + if (extra_cas_.find(fingerprint) != extra_cas_.end()) + return false; + + extra_cas_[fingerprint] = policy; + return true; +} + +bool EVRootCAMetadata::RemoveEVCA(const SHA1HashValue& fingerprint) { + ExtraEVCAMap::iterator it = extra_cas_.find(fingerprint); + if (it == extra_cas_.end()) + return false; + extra_cas_.erase(it); + return true; +} + +#else + +// These are just stub functions for platforms where we don't use this EV +// metadata. + +bool EVRootCAMetadata::AddEVCA(const SHA1HashValue& fingerprint, + const char* policy) { + return true; +} + +bool EVRootCAMetadata::RemoveEVCA(const SHA1HashValue& fingerprint) { + return true; +} + +#endif + +EVRootCAMetadata::EVRootCAMetadata() { + // Constructs the object from the raw metadata in ev_root_ca_metadata. +#if defined(USE_NSS) || defined(OS_IOS) + crypto::EnsureNSSInit(); + + for (size_t i = 0; i < arraysize(ev_root_ca_metadata); i++) { + const EVMetadata& metadata = ev_root_ca_metadata[i]; + for (size_t j = 0; j < arraysize(metadata.policy_oids); j++) { + if (metadata.policy_oids[j][0] == '\0') + break; + const char* policy_oid = metadata.policy_oids[j]; + + PolicyOID policy; + if (!RegisterOID(policy_oid, &policy)) { + LOG(ERROR) << "Failed to register OID: " << policy_oid; + continue; + } + + ev_policy_[metadata.fingerprint].push_back(policy); + policy_oids_.insert(policy); + } + } +#endif +} + +EVRootCAMetadata::~EVRootCAMetadata() { } + +} // namespace net diff --git a/net/cert/ev_root_ca_metadata.h b/net/cert/ev_root_ca_metadata.h new file mode 100644 index 0000000..aad7848 --- /dev/null +++ b/net/cert/ev_root_ca_metadata.h @@ -0,0 +1,89 @@ +// Copyright (c) 2012 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. + +#ifndef NET_CERT_EV_ROOT_CA_METADATA_H_ +#define NET_CERT_EV_ROOT_CA_METADATA_H_ + +#include "build/build_config.h" + +#if defined(USE_NSS) || defined(OS_IOS) +#include <secoidt.h> +#endif + +#include <map> +#include <set> +#include <string> +#include <vector> + +#include "net/base/net_export.h" +#include "net/cert/x509_certificate.h" + +namespace base { +template <typename T> +struct DefaultLazyInstanceTraits; +} // namespace base + +namespace net { + +// A singleton. This class stores the meta data of the root CAs that issue +// extended-validation (EV) certificates. +class NET_EXPORT_PRIVATE EVRootCAMetadata { + public: +#if defined(USE_NSS) || defined(OS_IOS) + typedef SECOidTag PolicyOID; +#elif defined(OS_WIN) + typedef const char* PolicyOID; +#endif + + static EVRootCAMetadata* GetInstance(); + +#if defined(USE_NSS) || defined(OS_WIN) || defined(OS_IOS) + // Returns true if policy_oid is an EV policy OID of some root CA. + bool IsEVPolicyOID(PolicyOID policy_oid) const; + + // Returns true if the root CA with the given certificate fingerprint has + // the EV policy OID policy_oid. + bool HasEVPolicyOID(const SHA1HashValue& fingerprint, + PolicyOID policy_oid) const; +#endif + + // AddEVCA adds an EV CA to the list of known EV CAs with the given policy. + // |policy| is expressed as a string of dotted numbers. It returns true on + // success. + bool AddEVCA(const SHA1HashValue& fingerprint, const char* policy); + + // RemoveEVCA removes an EV CA that was previously added by AddEVCA. It + // returns true on success. + bool RemoveEVCA(const SHA1HashValue& fingerprint); + + private: + friend struct base::DefaultLazyInstanceTraits<EVRootCAMetadata>; + + EVRootCAMetadata(); + ~EVRootCAMetadata(); + +#if defined(USE_NSS) || defined(OS_IOS) + typedef std::map<SHA1HashValue, std::vector<PolicyOID>, + SHA1HashValueLessThan> PolicyOIDMap; + + // RegisterOID registers |policy|, a policy OID in dotted string form, and + // writes the memoized form to |*out|. It returns true on success. + static bool RegisterOID(const char* policy, PolicyOID* out); + + PolicyOIDMap ev_policy_; + std::set<PolicyOID> policy_oids_; +#elif defined(OS_WIN) + typedef std::map<SHA1HashValue, std::string, + SHA1HashValueLessThan> ExtraEVCAMap; + + // extra_cas_ contains any EV CA metadata that was added at runtime. + ExtraEVCAMap extra_cas_; +#endif + + DISALLOW_COPY_AND_ASSIGN(EVRootCAMetadata); +}; + +} // namespace net + +#endif // NET_CERT_EV_ROOT_CA_METADATA_H_ diff --git a/net/cert/ev_root_ca_metadata_unittest.cc b/net/cert/ev_root_ca_metadata_unittest.cc new file mode 100644 index 0000000..2c845db --- /dev/null +++ b/net/cert/ev_root_ca_metadata_unittest.cc @@ -0,0 +1,144 @@ +// Copyright (c) 2012 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 "net/cert/ev_root_ca_metadata.h" + +#include "net/cert/x509_cert_types.h" +#include "net/test/cert_test_util.h" +#include "testing/gtest/include/gtest/gtest.h" + +#if defined(USE_NSS) +#include "crypto/scoped_nss_types.h" +#endif + +namespace net { + +namespace { + +static const char kVerisignPolicy[] = "2.16.840.1.113733.1.7.23.6"; +static const char kThawtePolicy[] = "2.16.840.1.113733.1.7.48.1"; +static const char kFakePolicy[] = "2.16.840.1.42"; +static const SHA1HashValue kVerisignFingerprint = + { { 0x74, 0x2c, 0x31, 0x92, 0xe6, 0x07, 0xe4, 0x24, 0xeb, 0x45, + 0x49, 0x54, 0x2b, 0xe1, 0xbb, 0xc5, 0x3e, 0x61, 0x74, 0xe2 } }; +static const SHA1HashValue kFakeFingerprint = + { { 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, + 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99 } }; + +#if defined(USE_NSS) || defined(OS_WIN) +class EVOidData { + public: + EVOidData(); + bool Init(); + + EVRootCAMetadata::PolicyOID verisign_policy; + EVRootCAMetadata::PolicyOID thawte_policy; + EVRootCAMetadata::PolicyOID fake_policy; +}; + +#endif // defined(USE_NSS) || defined(OS_WIN) + +#if defined(USE_NSS) + +SECOidTag RegisterOID(PLArenaPool* arena, const char* oid_string) { + SECOidData oid_data; + memset(&oid_data, 0, sizeof(oid_data)); + oid_data.offset = SEC_OID_UNKNOWN; + oid_data.desc = oid_string; + oid_data.mechanism = CKM_INVALID_MECHANISM; + oid_data.supportedExtension = INVALID_CERT_EXTENSION; + + SECStatus rv = SEC_StringToOID(arena, &oid_data.oid, oid_string, 0); + if (rv != SECSuccess) + return SEC_OID_UNKNOWN; + + return SECOID_AddEntry(&oid_data); +} + +EVOidData::EVOidData() + : verisign_policy(SEC_OID_UNKNOWN), + thawte_policy(SEC_OID_UNKNOWN), + fake_policy(SEC_OID_UNKNOWN) { +} + +bool EVOidData::Init() { + crypto::ScopedPLArenaPool pool(PORT_NewArena(DER_DEFAULT_CHUNKSIZE)); + if (!pool.get()) + return false; + + verisign_policy = RegisterOID(pool.get(), kVerisignPolicy); + thawte_policy = RegisterOID(pool.get(), kThawtePolicy); + fake_policy = RegisterOID(pool.get(), kFakePolicy); + + return verisign_policy != SEC_OID_UNKNOWN && + thawte_policy != SEC_OID_UNKNOWN && + fake_policy != SEC_OID_UNKNOWN; +} + +#elif defined(OS_WIN) + +EVOidData::EVOidData() + : verisign_policy(kVerisignPolicy), + thawte_policy(kThawtePolicy), + fake_policy(kFakePolicy) { +} + +bool EVOidData::Init() { + return true; +} + +#endif + +#if defined(USE_NSS) || defined(OS_WIN) + +class EVRootCAMetadataTest : public testing::Test { + protected: + virtual void SetUp() OVERRIDE { + ASSERT_TRUE(ev_oid_data.Init()); + } + + EVOidData ev_oid_data; +}; + +TEST_F(EVRootCAMetadataTest, Basic) { + EVRootCAMetadata* ev_metadata(EVRootCAMetadata::GetInstance()); + + EXPECT_TRUE(ev_metadata->IsEVPolicyOID(ev_oid_data.verisign_policy)); + EXPECT_FALSE(ev_metadata->IsEVPolicyOID(ev_oid_data.fake_policy)); + EXPECT_TRUE(ev_metadata->HasEVPolicyOID(kVerisignFingerprint, + ev_oid_data.verisign_policy)); + EXPECT_FALSE(ev_metadata->HasEVPolicyOID(kFakeFingerprint, + ev_oid_data.verisign_policy)); + EXPECT_FALSE(ev_metadata->HasEVPolicyOID(kVerisignFingerprint, + ev_oid_data.fake_policy)); + EXPECT_FALSE(ev_metadata->HasEVPolicyOID(kVerisignFingerprint, + ev_oid_data.thawte_policy)); +} + +TEST_F(EVRootCAMetadataTest, AddRemove) { + EVRootCAMetadata* ev_metadata(EVRootCAMetadata::GetInstance()); + + EXPECT_FALSE(ev_metadata->IsEVPolicyOID(ev_oid_data.fake_policy)); + EXPECT_FALSE(ev_metadata->HasEVPolicyOID(kFakeFingerprint, + ev_oid_data.fake_policy)); + + { + ScopedTestEVPolicy test_ev_policy(ev_metadata, kFakeFingerprint, + kFakePolicy); + + EXPECT_TRUE(ev_metadata->IsEVPolicyOID(ev_oid_data.fake_policy)); + EXPECT_TRUE(ev_metadata->HasEVPolicyOID(kFakeFingerprint, + ev_oid_data.fake_policy)); + } + + EXPECT_FALSE(ev_metadata->IsEVPolicyOID(ev_oid_data.fake_policy)); + EXPECT_FALSE(ev_metadata->HasEVPolicyOID(kFakeFingerprint, + ev_oid_data.fake_policy)); +} + +#endif // defined(USE_NSS) || defined(OS_WIN) + +} // namespace + +} // namespace net diff --git a/net/cert/mock_cert_verifier.cc b/net/cert/mock_cert_verifier.cc new file mode 100644 index 0000000..976de1a --- /dev/null +++ b/net/cert/mock_cert_verifier.cc @@ -0,0 +1,83 @@ +// Copyright (c) 2012 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 "net/cert/mock_cert_verifier.h" + +#include "base/memory/ref_counted.h" +#include "base/string_util.h" +#include "net/base/net_errors.h" +#include "net/cert/cert_status_flags.h" +#include "net/cert/cert_verify_result.h" +#include "net/cert/x509_certificate.h" + +namespace net { + +struct MockCertVerifier::Rule { + Rule(X509Certificate* cert, + const std::string& hostname, + const CertVerifyResult& result, + int rv) + : cert(cert), + hostname(hostname), + result(result), + rv(rv) { + DCHECK(cert); + DCHECK(result.verified_cert); + } + + scoped_refptr<X509Certificate> cert; + std::string hostname; + CertVerifyResult result; + int rv; +}; + +MockCertVerifier::MockCertVerifier() : default_result_(ERR_CERT_INVALID) {} + +MockCertVerifier::~MockCertVerifier() {} + +int MockCertVerifier::Verify(X509Certificate* cert, + const std::string& hostname, + int flags, + CRLSet* crl_set, + CertVerifyResult* verify_result, + const CompletionCallback& callback, + RequestHandle* out_req, + const BoundNetLog& net_log) { + RuleList::const_iterator it; + for (it = rules_.begin(); it != rules_.end(); ++it) { + // Check just the server cert. Intermediates will be ignored. + if (!it->cert->Equals(cert)) + continue; + if (!MatchPattern(hostname, it->hostname)) + continue; + *verify_result = it->result; + return it->rv; + } + + // Fall through to the default. + verify_result->verified_cert = cert; + verify_result->cert_status = MapNetErrorToCertStatus(default_result_); + return default_result_; +} + +void MockCertVerifier::CancelRequest(RequestHandle req) { + NOTIMPLEMENTED(); +} + +void MockCertVerifier::AddResultForCert(X509Certificate* cert, + const CertVerifyResult& verify_result, + int rv) { + AddResultForCertAndHost(cert, "*", verify_result, rv); +} + +void MockCertVerifier::AddResultForCertAndHost( + X509Certificate* cert, + const std::string& host_pattern, + const CertVerifyResult& verify_result, + int rv) { + Rule rule(cert, host_pattern, verify_result, rv); + rules_.push_back(rule); +} + +} // namespace net diff --git a/net/cert/mock_cert_verifier.h b/net/cert/mock_cert_verifier.h new file mode 100644 index 0000000..5af7b7e --- /dev/null +++ b/net/cert/mock_cert_verifier.h @@ -0,0 +1,69 @@ +// Copyright (c) 2012 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. + +#ifndef NET_CERT_MOCK_CERT_VERIFIER_H_ +#define NET_CERT_MOCK_CERT_VERIFIER_H_ + +#include <list> + +#include "net/cert/cert_verifier.h" +#include "net/cert/cert_verify_result.h" + +namespace net { + +class MockCertVerifier : public CertVerifier { + public: + // Creates a new MockCertVerifier. By default, any call to Verify() will + // result in the cert status being flagged as CERT_STATUS_INVALID and return + // an ERR_CERT_INVALID network error code. This behaviour can be overridden + // by calling set_default_result() to change the default return value for + // Verify() or by calling one of the AddResult*() methods to specifically + // handle a certificate or certificate and host. + MockCertVerifier(); + + virtual ~MockCertVerifier(); + + // CertVerifier implementation + virtual int Verify(X509Certificate* cert, + const std::string& hostname, + int flags, + CRLSet* crl_set, + CertVerifyResult* verify_result, + const CompletionCallback& callback, + RequestHandle* out_req, + const BoundNetLog& net_log) OVERRIDE; + virtual void CancelRequest(RequestHandle req) OVERRIDE; + + // Sets the default return value for Verify() for certificates/hosts that do + // not have explicit results added via the AddResult*() methods. + void set_default_result(int default_result) { + default_result_ = default_result; + } + + // Adds a rule that will cause any call to Verify() for |cert| to return rv, + // copying |verify_result| into the verified result. + // Note: Only the primary certificate of |cert| is checked. Any intermediate + // certificates will be ignored. + void AddResultForCert(X509Certificate* cert, + const CertVerifyResult& verify_result, + int rv); + + // Same as AddResultForCert(), but further restricts it to only return for + // hostnames that match |host_pattern|. + void AddResultForCertAndHost(X509Certificate* cert, + const std::string& host_pattern, + const CertVerifyResult& verify_result, + int rv); + + private: + struct Rule; + typedef std::list<Rule> RuleList; + + int default_result_; + RuleList rules_; +}; + +} // namespace net + +#endif // NET_CERT_MOCK_CERT_VERIFIER_H_ diff --git a/net/cert/multi_threaded_cert_verifier.cc b/net/cert/multi_threaded_cert_verifier.cc new file mode 100644 index 0000000..daac0d0 --- /dev/null +++ b/net/cert/multi_threaded_cert_verifier.cc @@ -0,0 +1,554 @@ +// Copyright (c) 2012 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 "net/cert/multi_threaded_cert_verifier.h" + +#include <algorithm> + +#include "base/bind.h" +#include "base/bind_helpers.h" +#include "base/compiler_specific.h" +#include "base/message_loop.h" +#include "base/metrics/histogram.h" +#include "base/stl_util.h" +#include "base/synchronization/lock.h" +#include "base/threading/worker_pool.h" +#include "base/time.h" +#include "net/base/net_errors.h" +#include "net/base/net_log.h" +#include "net/cert/cert_trust_anchor_provider.h" +#include "net/cert/cert_verify_proc.h" +#include "net/cert/crl_set.h" +#include "net/cert/x509_certificate.h" +#include "net/cert/x509_certificate_net_log_param.h" + +#if defined(USE_NSS) || defined(OS_IOS) +#include <private/pprthred.h> // PR_DetachThread +#endif + +namespace net { + +//////////////////////////////////////////////////////////////////////////// + +// Life of a request: +// +// MultiThreadedCertVerifier CertVerifierJob CertVerifierWorker Request +// | (origin loop) (worker loop) +// | +// Verify() +// |---->-------------------------------------<creates> +// | +// |---->-------------------<creates> +// | +// |---->-------------------------------------------------------<creates> +// | +// |---->---------------------------------------Start +// | | +// | PostTask +// | +// | <starts verifying> +// |---->-------------------AddRequest | +// | +// | +// | +// Finish +// | +// PostTask +// +// | +// DoReply +// |----<-----------------------------------------| +// HandleResult +// | +// |---->------------------HandleResult +// | +// |------>---------------------------Post +// +// +// +// On a cache hit, MultiThreadedCertVerifier::Verify() returns synchronously +// without posting a task to a worker thread. + +namespace { + +// The default value of max_cache_entries_. +const unsigned kMaxCacheEntries = 256; + +// The number of seconds for which we'll cache a cache entry. +const unsigned kTTLSecs = 1800; // 30 minutes. + +} // namespace + +MultiThreadedCertVerifier::CachedResult::CachedResult() : error(ERR_FAILED) {} + +MultiThreadedCertVerifier::CachedResult::~CachedResult() {} + +MultiThreadedCertVerifier::CacheValidityPeriod::CacheValidityPeriod( + const base::Time& now) + : verification_time(now), + expiration_time(now) { +} + +MultiThreadedCertVerifier::CacheValidityPeriod::CacheValidityPeriod( + const base::Time& now, + const base::Time& expiration) + : verification_time(now), + expiration_time(expiration) { +} + +bool MultiThreadedCertVerifier::CacheExpirationFunctor::operator()( + const CacheValidityPeriod& now, + const CacheValidityPeriod& expiration) const { + // Ensure this functor is being used for expiration only, and not strict + // weak ordering/sorting. |now| should only ever contain a single + // base::Time. + // Note: DCHECK_EQ is not used due to operator<< overloading requirements. + DCHECK(now.verification_time == now.expiration_time); + + // |now| contains only a single time (verification_time), while |expiration| + // contains the validity range - both when the certificate was verified and + // when the verification result should expire. + // + // If the user receives a "not yet valid" message, and adjusts their clock + // foward to the correct time, this will (typically) cause + // now.verification_time to advance past expiration.expiration_time, thus + // treating the cached result as an expired entry and re-verifying. + // If the user receives a "expired" message, and adjusts their clock + // backwards to the correct time, this will cause now.verification_time to + // be less than expiration_verification_time, thus treating the cached + // result as an expired entry and re-verifying. + // If the user receives either of those messages, and does not adjust their + // clock, then the result will be (typically) be cached until the expiration + // TTL. + // + // This algorithm is only problematic if the user consistently keeps + // adjusting their clock backwards in increments smaller than the expiration + // TTL, in which case, cached elements continue to be added. However, + // because the cache has a fixed upper bound, if no entries are expired, a + // 'random' entry will be, thus keeping the memory constraints bounded over + // time. + return now.verification_time >= expiration.verification_time && + now.verification_time < expiration.expiration_time; +}; + + +// Represents the output and result callback of a request. +class CertVerifierRequest { + public: + CertVerifierRequest(const CompletionCallback& callback, + CertVerifyResult* verify_result, + const BoundNetLog& net_log) + : callback_(callback), + verify_result_(verify_result), + net_log_(net_log) { + net_log_.BeginEvent(NetLog::TYPE_CERT_VERIFIER_REQUEST); + } + + ~CertVerifierRequest() { + } + + // Ensures that the result callback will never be made. + void Cancel() { + callback_.Reset(); + verify_result_ = NULL; + net_log_.AddEvent(NetLog::TYPE_CANCELLED); + net_log_.EndEvent(NetLog::TYPE_CERT_VERIFIER_REQUEST); + } + + // Copies the contents of |verify_result| to the caller's + // CertVerifyResult and calls the callback. + void Post(const MultiThreadedCertVerifier::CachedResult& verify_result) { + if (!callback_.is_null()) { + net_log_.EndEvent(NetLog::TYPE_CERT_VERIFIER_REQUEST); + *verify_result_ = verify_result.result; + callback_.Run(verify_result.error); + } + delete this; + } + + bool canceled() const { return callback_.is_null(); } + + const BoundNetLog& net_log() const { return net_log_; } + + private: + CompletionCallback callback_; + CertVerifyResult* verify_result_; + const BoundNetLog net_log_; +}; + + +// CertVerifierWorker runs on a worker thread and takes care of the blocking +// process of performing the certificate verification. Deletes itself +// eventually if Start() succeeds. +class CertVerifierWorker { + public: + CertVerifierWorker(CertVerifyProc* verify_proc, + X509Certificate* cert, + const std::string& hostname, + int flags, + CRLSet* crl_set, + const CertificateList& additional_trust_anchors, + MultiThreadedCertVerifier* cert_verifier) + : verify_proc_(verify_proc), + cert_(cert), + hostname_(hostname), + flags_(flags), + crl_set_(crl_set), + additional_trust_anchors_(additional_trust_anchors), + origin_loop_(MessageLoop::current()), + cert_verifier_(cert_verifier), + canceled_(false), + error_(ERR_FAILED) { + } + + // Returns the certificate being verified. May only be called /before/ + // Start() is called. + X509Certificate* certificate() const { return cert_; } + + bool Start() { + DCHECK_EQ(MessageLoop::current(), origin_loop_); + + return base::WorkerPool::PostTask( + FROM_HERE, base::Bind(&CertVerifierWorker::Run, base::Unretained(this)), + true /* task is slow */); + } + + // Cancel is called from the origin loop when the MultiThreadedCertVerifier is + // getting deleted. + void Cancel() { + DCHECK_EQ(MessageLoop::current(), origin_loop_); + base::AutoLock locked(lock_); + canceled_ = true; + } + + private: + void Run() { + // Runs on a worker thread. + error_ = verify_proc_->Verify(cert_, hostname_, flags_, crl_set_, + additional_trust_anchors_, &verify_result_); +#if defined(USE_NSS) || defined(OS_IOS) + // Detach the thread from NSPR. + // Calling NSS functions attaches the thread to NSPR, which stores + // the NSPR thread ID in thread-specific data. + // The threads in our thread pool terminate after we have called + // PR_Cleanup. Unless we detach them from NSPR, net_unittests gets + // segfaults on shutdown when the threads' thread-specific data + // destructors run. + PR_DetachThread(); +#endif + Finish(); + } + + // DoReply runs on the origin thread. + void DoReply() { + DCHECK_EQ(MessageLoop::current(), origin_loop_); + { + // We lock here because the worker thread could still be in Finished, + // after the PostTask, but before unlocking |lock_|. If we do not lock in + // this case, we will end up deleting a locked Lock, which can lead to + // memory leaks or worse errors. + base::AutoLock locked(lock_); + if (!canceled_) { + cert_verifier_->HandleResult(cert_, hostname_, flags_, + additional_trust_anchors_, error_, + verify_result_); + } + } + delete this; + } + + void Finish() { + // Runs on the worker thread. + // We assume that the origin loop outlives the MultiThreadedCertVerifier. If + // the MultiThreadedCertVerifier is deleted, it will call Cancel on us. If + // it does so before the Acquire, we'll delete ourselves and return. If it's + // trying to do so concurrently, then it'll block on the lock and we'll call + // PostTask while the MultiThreadedCertVerifier (and therefore the + // MessageLoop) is still alive. + // If it does so after this function, we assume that the MessageLoop will + // process pending tasks. In which case we'll notice the |canceled_| flag + // in DoReply. + + bool canceled; + { + base::AutoLock locked(lock_); + canceled = canceled_; + if (!canceled) { + origin_loop_->PostTask( + FROM_HERE, base::Bind( + &CertVerifierWorker::DoReply, base::Unretained(this))); + } + } + + if (canceled) + delete this; + } + + scoped_refptr<CertVerifyProc> verify_proc_; + scoped_refptr<X509Certificate> cert_; + const std::string hostname_; + const int flags_; + scoped_refptr<CRLSet> crl_set_; + const CertificateList additional_trust_anchors_; + MessageLoop* const origin_loop_; + MultiThreadedCertVerifier* const cert_verifier_; + + // lock_ protects canceled_. + base::Lock lock_; + + // If canceled_ is true, + // * origin_loop_ cannot be accessed by the worker thread, + // * cert_verifier_ cannot be accessed by any thread. + bool canceled_; + + int error_; + CertVerifyResult verify_result_; + + DISALLOW_COPY_AND_ASSIGN(CertVerifierWorker); +}; + +// A CertVerifierJob is a one-to-one counterpart of a CertVerifierWorker. It +// lives only on the CertVerifier's origin message loop. +class CertVerifierJob { + public: + CertVerifierJob(CertVerifierWorker* worker, + const BoundNetLog& net_log) + : start_time_(base::TimeTicks::Now()), + worker_(worker), + net_log_(net_log) { + net_log_.BeginEvent( + NetLog::TYPE_CERT_VERIFIER_JOB, + base::Bind(&NetLogX509CertificateCallback, + base::Unretained(worker_->certificate()))); + } + + ~CertVerifierJob() { + if (worker_) { + net_log_.AddEvent(NetLog::TYPE_CANCELLED); + net_log_.EndEvent(NetLog::TYPE_CERT_VERIFIER_JOB); + worker_->Cancel(); + DeleteAllCanceled(); + } + } + + void AddRequest(CertVerifierRequest* request) { + request->net_log().AddEvent( + NetLog::TYPE_CERT_VERIFIER_REQUEST_BOUND_TO_JOB, + net_log_.source().ToEventParametersCallback()); + + requests_.push_back(request); + } + + void HandleResult( + const MultiThreadedCertVerifier::CachedResult& verify_result) { + worker_ = NULL; + net_log_.EndEvent(NetLog::TYPE_CERT_VERIFIER_JOB); + UMA_HISTOGRAM_CUSTOM_TIMES("Net.CertVerifier_Job_Latency", + base::TimeTicks::Now() - start_time_, + base::TimeDelta::FromMilliseconds(1), + base::TimeDelta::FromMinutes(10), + 100); + PostAll(verify_result); + } + + private: + void PostAll(const MultiThreadedCertVerifier::CachedResult& verify_result) { + std::vector<CertVerifierRequest*> requests; + requests_.swap(requests); + + for (std::vector<CertVerifierRequest*>::iterator + i = requests.begin(); i != requests.end(); i++) { + (*i)->Post(verify_result); + // Post() causes the CertVerifierRequest to delete itself. + } + } + + void DeleteAllCanceled() { + for (std::vector<CertVerifierRequest*>::iterator + i = requests_.begin(); i != requests_.end(); i++) { + if ((*i)->canceled()) { + delete *i; + } else { + LOG(DFATAL) << "CertVerifierRequest leaked!"; + } + } + } + + const base::TimeTicks start_time_; + std::vector<CertVerifierRequest*> requests_; + CertVerifierWorker* worker_; + const BoundNetLog net_log_; +}; + +MultiThreadedCertVerifier::MultiThreadedCertVerifier( + CertVerifyProc* verify_proc) + : cache_(kMaxCacheEntries), + requests_(0), + cache_hits_(0), + inflight_joins_(0), + verify_proc_(verify_proc), + trust_anchor_provider_(NULL) { + CertDatabase::GetInstance()->AddObserver(this); +} + +MultiThreadedCertVerifier::~MultiThreadedCertVerifier() { + STLDeleteValues(&inflight_); + CertDatabase::GetInstance()->RemoveObserver(this); +} + +void MultiThreadedCertVerifier::SetCertTrustAnchorProvider( + CertTrustAnchorProvider* trust_anchor_provider) { + DCHECK(CalledOnValidThread()); + trust_anchor_provider_ = trust_anchor_provider; +} + +int MultiThreadedCertVerifier::Verify(X509Certificate* cert, + const std::string& hostname, + int flags, + CRLSet* crl_set, + CertVerifyResult* verify_result, + const CompletionCallback& callback, + RequestHandle* out_req, + const BoundNetLog& net_log) { + DCHECK(CalledOnValidThread()); + + if (callback.is_null() || !verify_result || hostname.empty()) { + *out_req = NULL; + return ERR_INVALID_ARGUMENT; + } + + requests_++; + + const CertificateList empty_cert_list; + const CertificateList& additional_trust_anchors = + trust_anchor_provider_ ? + trust_anchor_provider_->GetAdditionalTrustAnchors() : empty_cert_list; + + const RequestParams key(cert->fingerprint(), cert->ca_fingerprint(), + hostname, flags, additional_trust_anchors); + const CertVerifierCache::value_type* cached_entry = + cache_.Get(key, CacheValidityPeriod(base::Time::Now())); + if (cached_entry) { + ++cache_hits_; + *out_req = NULL; + *verify_result = cached_entry->result; + return cached_entry->error; + } + + // No cache hit. See if an identical request is currently in flight. + CertVerifierJob* job; + std::map<RequestParams, CertVerifierJob*>::const_iterator j; + j = inflight_.find(key); + if (j != inflight_.end()) { + // An identical request is in flight already. We'll just attach our + // callback. + inflight_joins_++; + job = j->second; + } else { + // Need to make a new request. + CertVerifierWorker* worker = + new CertVerifierWorker(verify_proc_, cert, hostname, flags, crl_set, + additional_trust_anchors, this); + job = new CertVerifierJob( + worker, + BoundNetLog::Make(net_log.net_log(), NetLog::SOURCE_CERT_VERIFIER_JOB)); + if (!worker->Start()) { + delete job; + delete worker; + *out_req = NULL; + // TODO(wtc): log to the NetLog. + LOG(ERROR) << "CertVerifierWorker couldn't be started."; + return ERR_INSUFFICIENT_RESOURCES; // Just a guess. + } + inflight_.insert(std::make_pair(key, job)); + } + + CertVerifierRequest* request = + new CertVerifierRequest(callback, verify_result, net_log); + job->AddRequest(request); + *out_req = request; + return ERR_IO_PENDING; +} + +void MultiThreadedCertVerifier::CancelRequest(RequestHandle req) { + DCHECK(CalledOnValidThread()); + CertVerifierRequest* request = reinterpret_cast<CertVerifierRequest*>(req); + request->Cancel(); +} + +MultiThreadedCertVerifier::RequestParams::RequestParams( + const SHA1HashValue& cert_fingerprint_arg, + const SHA1HashValue& ca_fingerprint_arg, + const std::string& hostname_arg, + int flags_arg, + const CertificateList& additional_trust_anchors) + : hostname(hostname_arg), + flags(flags_arg) { + hash_values.reserve(2 + additional_trust_anchors.size()); + hash_values.push_back(cert_fingerprint_arg); + hash_values.push_back(ca_fingerprint_arg); + for (size_t i = 0; i < additional_trust_anchors.size(); ++i) + hash_values.push_back(additional_trust_anchors[i]->fingerprint()); +} + +MultiThreadedCertVerifier::RequestParams::~RequestParams() {} + +bool MultiThreadedCertVerifier::RequestParams::operator<( + const RequestParams& other) const { + // |flags| is compared before |cert_fingerprint|, |ca_fingerprint|, and + // |hostname| under assumption that integer comparisons are faster than + // memory and string comparisons. + if (flags != other.flags) + return flags < other.flags; + if (hostname != other.hostname) + return hostname < other.hostname; + return std::lexicographical_compare( + hash_values.begin(), hash_values.end(), + other.hash_values.begin(), other.hash_values.end(), + net::SHA1HashValueLessThan()); +} + +// HandleResult is called by CertVerifierWorker on the origin message loop. +// It deletes CertVerifierJob. +void MultiThreadedCertVerifier::HandleResult( + X509Certificate* cert, + const std::string& hostname, + int flags, + const CertificateList& additional_trust_anchors, + int error, + const CertVerifyResult& verify_result) { + DCHECK(CalledOnValidThread()); + + const RequestParams key(cert->fingerprint(), cert->ca_fingerprint(), + hostname, flags, additional_trust_anchors); + + CachedResult cached_result; + cached_result.error = error; + cached_result.result = verify_result; + base::Time now = base::Time::Now(); + cache_.Put( + key, cached_result, CacheValidityPeriod(now), + CacheValidityPeriod(now, now + base::TimeDelta::FromSeconds(kTTLSecs))); + + std::map<RequestParams, CertVerifierJob*>::iterator j; + j = inflight_.find(key); + if (j == inflight_.end()) { + NOTREACHED(); + return; + } + CertVerifierJob* job = j->second; + inflight_.erase(j); + + job->HandleResult(cached_result); + delete job; +} + +void MultiThreadedCertVerifier::OnCertTrustChanged( + const X509Certificate* cert) { + DCHECK(CalledOnValidThread()); + + ClearCache(); +} + +} // namespace net diff --git a/net/cert/multi_threaded_cert_verifier.h b/net/cert/multi_threaded_cert_verifier.h new file mode 100644 index 0000000..bc9cd4f --- /dev/null +++ b/net/cert/multi_threaded_cert_verifier.h @@ -0,0 +1,169 @@ +// Copyright (c) 2012 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. + +#ifndef NET_CERT_MULTI_THREADED_CERT_VERIFIER_H_ +#define NET_CERT_MULTI_THREADED_CERT_VERIFIER_H_ + +#include <map> +#include <string> +#include <vector> + +#include "base/basictypes.h" +#include "base/gtest_prod_util.h" +#include "base/memory/ref_counted.h" +#include "base/threading/non_thread_safe.h" +#include "net/base/completion_callback.h" +#include "net/base/expiring_cache.h" +#include "net/base/hash_value.h" +#include "net/base/net_export.h" +#include "net/cert/cert_database.h" +#include "net/cert/cert_verifier.h" +#include "net/cert/cert_verify_result.h" +#include "net/cert/x509_cert_types.h" + +namespace net { + +class CertTrustAnchorProvider; +class CertVerifierJob; +class CertVerifierRequest; +class CertVerifierWorker; +class CertVerifyProc; + +// MultiThreadedCertVerifier is a CertVerifier implementation that runs +// synchronous CertVerifier implementations on worker threads. +class NET_EXPORT_PRIVATE MultiThreadedCertVerifier + : public CertVerifier, + NON_EXPORTED_BASE(public base::NonThreadSafe), + public CertDatabase::Observer { + public: + explicit MultiThreadedCertVerifier(CertVerifyProc* verify_proc); + + // When the verifier is destroyed, all certificate verifications requests are + // canceled, and their completion callbacks will not be called. + virtual ~MultiThreadedCertVerifier(); + + // Configures a source of additional certificates that should be treated as + // trust anchors during verification, provided that the underlying + // CertVerifyProc supports additional trust beyond the default implementation. + // The CertTrustAnchorProvider will only be accessed on the same + // thread that Verify() is called on; that is, it will not be + // accessed from worker threads. + // It must outlive the MultiThreadedCertVerifier. + void SetCertTrustAnchorProvider( + CertTrustAnchorProvider* trust_anchor_provider); + + // CertVerifier implementation + virtual int Verify(X509Certificate* cert, + const std::string& hostname, + int flags, + CRLSet* crl_set, + CertVerifyResult* verify_result, + const CompletionCallback& callback, + CertVerifier::RequestHandle* out_req, + const BoundNetLog& net_log) OVERRIDE; + + virtual void CancelRequest(CertVerifier::RequestHandle req) OVERRIDE; + + private: + friend class CertVerifierWorker; // Calls HandleResult. + friend class CertVerifierRequest; + friend class CertVerifierJob; + friend class MultiThreadedCertVerifierTest; + FRIEND_TEST_ALL_PREFIXES(MultiThreadedCertVerifierTest, CacheHit); + FRIEND_TEST_ALL_PREFIXES(MultiThreadedCertVerifierTest, DifferentCACerts); + FRIEND_TEST_ALL_PREFIXES(MultiThreadedCertVerifierTest, InflightJoin); + FRIEND_TEST_ALL_PREFIXES(MultiThreadedCertVerifierTest, CancelRequest); + FRIEND_TEST_ALL_PREFIXES(MultiThreadedCertVerifierTest, + RequestParamsComparators); + FRIEND_TEST_ALL_PREFIXES(MultiThreadedCertVerifierTest, + CertTrustAnchorProvider); + + // Input parameters of a certificate verification request. + struct NET_EXPORT_PRIVATE RequestParams { + RequestParams(const SHA1HashValue& cert_fingerprint_arg, + const SHA1HashValue& ca_fingerprint_arg, + const std::string& hostname_arg, + int flags_arg, + const CertificateList& additional_trust_anchors); + ~RequestParams(); + + bool operator<(const RequestParams& other) const; + + std::string hostname; + int flags; + std::vector<SHA1HashValue> hash_values; + }; + + // CachedResult contains the result of a certificate verification. + struct CachedResult { + CachedResult(); + ~CachedResult(); + + int error; // The return value of CertVerifier::Verify. + CertVerifyResult result; // The output of CertVerifier::Verify. + }; + + // Rather than having a single validity point along a monotonically increasing + // timeline, certificate verification is based on falling within a range of + // the certificate's NotBefore and NotAfter and based on what the current + // system clock says (which may advance forwards or backwards as users correct + // clock skew). CacheValidityPeriod and CacheExpirationFunctor are helpers to + // ensure that expiration is measured both by the 'general' case (now + cache + // TTL) and by whether or not significant enough clock skew was introduced + // since the last verification. + struct CacheValidityPeriod { + explicit CacheValidityPeriod(const base::Time& now); + CacheValidityPeriod(const base::Time& now, const base::Time& expiration); + + base::Time verification_time; + base::Time expiration_time; + }; + + struct CacheExpirationFunctor { + // Returns true iff |now| is within the validity period of |expiration|. + bool operator()(const CacheValidityPeriod& now, + const CacheValidityPeriod& expiration) const; + }; + + typedef ExpiringCache<RequestParams, CachedResult, CacheValidityPeriod, + CacheExpirationFunctor> CertVerifierCache; + + void HandleResult(X509Certificate* cert, + const std::string& hostname, + int flags, + const CertificateList& additional_trust_anchors, + int error, + const CertVerifyResult& verify_result); + + // CertDatabase::Observer methods: + virtual void OnCertTrustChanged(const X509Certificate* cert) OVERRIDE; + + // For unit testing. + void ClearCache() { cache_.Clear(); } + size_t GetCacheSize() const { return cache_.size(); } + uint64 cache_hits() const { return cache_hits_; } + uint64 requests() const { return requests_; } + uint64 inflight_joins() const { return inflight_joins_; } + + // cache_ maps from a request to a cached result. + CertVerifierCache cache_; + + // inflight_ maps from a request to an active verification which is taking + // place. + std::map<RequestParams, CertVerifierJob*> inflight_; + + uint64 requests_; + uint64 cache_hits_; + uint64 inflight_joins_; + + scoped_refptr<CertVerifyProc> verify_proc_; + + CertTrustAnchorProvider* trust_anchor_provider_; + + DISALLOW_COPY_AND_ASSIGN(MultiThreadedCertVerifier); +}; + +} // namespace net + +#endif // NET_CERT_MULTI_THREADED_CERT_VERIFIER_H_ diff --git a/net/cert/multi_threaded_cert_verifier_unittest.cc b/net/cert/multi_threaded_cert_verifier_unittest.cc new file mode 100644 index 0000000..19afd2e --- /dev/null +++ b/net/cert/multi_threaded_cert_verifier_unittest.cc @@ -0,0 +1,419 @@ +// Copyright (c) 2012 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 "net/cert/multi_threaded_cert_verifier.h" + +#include "base/bind.h" +#include "base/files/file_path.h" +#include "base/format_macros.h" +#include "base/stringprintf.h" +#include "net/base/net_errors.h" +#include "net/base/net_log.h" +#include "net/base/test_completion_callback.h" +#include "net/base/test_data_directory.h" +#include "net/cert/cert_trust_anchor_provider.h" +#include "net/cert/cert_verify_proc.h" +#include "net/cert/cert_verify_result.h" +#include "net/cert/x509_certificate.h" +#include "net/test/cert_test_util.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +using testing::Mock; +using testing::ReturnRef; + +namespace net { + +namespace { + +void FailTest(int /* result */) { + FAIL(); +} + +class MockCertVerifyProc : public CertVerifyProc { + public: + MockCertVerifyProc() {} + + private: + virtual ~MockCertVerifyProc() {} + + // CertVerifyProc implementation + virtual bool SupportsAdditionalTrustAnchors() const OVERRIDE { + return false; + } + + virtual int VerifyInternal(X509Certificate* cert, + const std::string& hostname, + int flags, + CRLSet* crl_set, + const CertificateList& additional_trust_anchors, + CertVerifyResult* verify_result) OVERRIDE { + verify_result->Reset(); + verify_result->verified_cert = cert; + verify_result->cert_status = CERT_STATUS_COMMON_NAME_INVALID; + return ERR_CERT_COMMON_NAME_INVALID; + } +}; + +class MockCertTrustAnchorProvider : public CertTrustAnchorProvider { + public: + MockCertTrustAnchorProvider() {} + virtual ~MockCertTrustAnchorProvider() {} + + MOCK_METHOD0(GetAdditionalTrustAnchors, const CertificateList&()); +}; + +} // namespace + +class MultiThreadedCertVerifierTest : public ::testing::Test { + public: + MultiThreadedCertVerifierTest() : verifier_(new MockCertVerifyProc()) {} + virtual ~MultiThreadedCertVerifierTest() {} + + protected: + MultiThreadedCertVerifier verifier_; +}; + +TEST_F(MultiThreadedCertVerifierTest, CacheHit) { + base::FilePath certs_dir = GetTestCertsDirectory(); + scoped_refptr<X509Certificate> test_cert( + ImportCertFromFile(certs_dir, "ok_cert.pem")); + ASSERT_NE(static_cast<X509Certificate*>(NULL), test_cert); + + int error; + CertVerifyResult verify_result; + TestCompletionCallback callback; + CertVerifier::RequestHandle request_handle; + + error = verifier_.Verify(test_cert, "www.example.com", 0, NULL, + &verify_result, callback.callback(), + &request_handle, BoundNetLog()); + ASSERT_EQ(ERR_IO_PENDING, error); + ASSERT_TRUE(request_handle != NULL); + error = callback.WaitForResult(); + ASSERT_TRUE(IsCertificateError(error)); + ASSERT_EQ(1u, verifier_.requests()); + ASSERT_EQ(0u, verifier_.cache_hits()); + ASSERT_EQ(0u, verifier_.inflight_joins()); + ASSERT_EQ(1u, verifier_.GetCacheSize()); + + error = verifier_.Verify(test_cert, "www.example.com", 0, NULL, + &verify_result, callback.callback(), + &request_handle, BoundNetLog()); + // Synchronous completion. + ASSERT_NE(ERR_IO_PENDING, error); + ASSERT_TRUE(IsCertificateError(error)); + ASSERT_TRUE(request_handle == NULL); + ASSERT_EQ(2u, verifier_.requests()); + ASSERT_EQ(1u, verifier_.cache_hits()); + ASSERT_EQ(0u, verifier_.inflight_joins()); + ASSERT_EQ(1u, verifier_.GetCacheSize()); +} + +// Tests the same server certificate with different intermediate CA +// certificates. These should be treated as different certificate chains even +// though the two X509Certificate objects contain the same server certificate. +TEST_F(MultiThreadedCertVerifierTest, DifferentCACerts) { + base::FilePath certs_dir = GetTestCertsDirectory(); + + scoped_refptr<X509Certificate> server_cert = + ImportCertFromFile(certs_dir, "salesforce_com_test.pem"); + ASSERT_NE(static_cast<X509Certificate*>(NULL), server_cert); + + scoped_refptr<X509Certificate> intermediate_cert1 = + ImportCertFromFile(certs_dir, "verisign_intermediate_ca_2011.pem"); + ASSERT_NE(static_cast<X509Certificate*>(NULL), intermediate_cert1); + + scoped_refptr<X509Certificate> intermediate_cert2 = + ImportCertFromFile(certs_dir, "verisign_intermediate_ca_2016.pem"); + ASSERT_NE(static_cast<X509Certificate*>(NULL), intermediate_cert2); + + X509Certificate::OSCertHandles intermediates; + intermediates.push_back(intermediate_cert1->os_cert_handle()); + scoped_refptr<X509Certificate> cert_chain1 = + X509Certificate::CreateFromHandle(server_cert->os_cert_handle(), + intermediates); + + intermediates.clear(); + intermediates.push_back(intermediate_cert2->os_cert_handle()); + scoped_refptr<X509Certificate> cert_chain2 = + X509Certificate::CreateFromHandle(server_cert->os_cert_handle(), + intermediates); + + int error; + CertVerifyResult verify_result; + TestCompletionCallback callback; + CertVerifier::RequestHandle request_handle; + + error = verifier_.Verify(cert_chain1, "www.example.com", 0, NULL, + &verify_result, callback.callback(), + &request_handle, BoundNetLog()); + ASSERT_EQ(ERR_IO_PENDING, error); + ASSERT_TRUE(request_handle != NULL); + error = callback.WaitForResult(); + ASSERT_TRUE(IsCertificateError(error)); + ASSERT_EQ(1u, verifier_.requests()); + ASSERT_EQ(0u, verifier_.cache_hits()); + ASSERT_EQ(0u, verifier_.inflight_joins()); + ASSERT_EQ(1u, verifier_.GetCacheSize()); + + error = verifier_.Verify(cert_chain2, "www.example.com", 0, NULL, + &verify_result, callback.callback(), + &request_handle, BoundNetLog()); + ASSERT_EQ(ERR_IO_PENDING, error); + ASSERT_TRUE(request_handle != NULL); + error = callback.WaitForResult(); + ASSERT_TRUE(IsCertificateError(error)); + ASSERT_EQ(2u, verifier_.requests()); + ASSERT_EQ(0u, verifier_.cache_hits()); + ASSERT_EQ(0u, verifier_.inflight_joins()); + ASSERT_EQ(2u, verifier_.GetCacheSize()); +} + +// Tests an inflight join. +TEST_F(MultiThreadedCertVerifierTest, InflightJoin) { + base::FilePath certs_dir = GetTestCertsDirectory(); + scoped_refptr<X509Certificate> test_cert( + ImportCertFromFile(certs_dir, "ok_cert.pem")); + ASSERT_NE(static_cast<X509Certificate*>(NULL), test_cert); + + int error; + CertVerifyResult verify_result; + TestCompletionCallback callback; + CertVerifier::RequestHandle request_handle; + CertVerifyResult verify_result2; + TestCompletionCallback callback2; + CertVerifier::RequestHandle request_handle2; + + error = verifier_.Verify(test_cert, "www.example.com", 0, NULL, + &verify_result, callback.callback(), + &request_handle, BoundNetLog()); + ASSERT_EQ(ERR_IO_PENDING, error); + ASSERT_TRUE(request_handle != NULL); + error = verifier_.Verify( + test_cert, "www.example.com", 0, NULL, &verify_result2, + callback2.callback(), &request_handle2, BoundNetLog()); + ASSERT_EQ(ERR_IO_PENDING, error); + ASSERT_TRUE(request_handle2 != NULL); + error = callback.WaitForResult(); + ASSERT_TRUE(IsCertificateError(error)); + error = callback2.WaitForResult(); + ASSERT_TRUE(IsCertificateError(error)); + ASSERT_EQ(2u, verifier_.requests()); + ASSERT_EQ(0u, verifier_.cache_hits()); + ASSERT_EQ(1u, verifier_.inflight_joins()); +} + +// Tests that the callback of a canceled request is never made. +TEST_F(MultiThreadedCertVerifierTest, CancelRequest) { + base::FilePath certs_dir = GetTestCertsDirectory(); + scoped_refptr<X509Certificate> test_cert( + ImportCertFromFile(certs_dir, "ok_cert.pem")); + ASSERT_NE(static_cast<X509Certificate*>(NULL), test_cert); + + int error; + CertVerifyResult verify_result; + CertVerifier::RequestHandle request_handle; + + error = verifier_.Verify( + test_cert, "www.example.com", 0, NULL, &verify_result, + base::Bind(&FailTest), &request_handle, BoundNetLog()); + ASSERT_EQ(ERR_IO_PENDING, error); + ASSERT_TRUE(request_handle != NULL); + verifier_.CancelRequest(request_handle); + + // Issue a few more requests to the worker pool and wait for their + // completion, so that the task of the canceled request (which runs on a + // worker thread) is likely to complete by the end of this test. + TestCompletionCallback callback; + for (int i = 0; i < 5; ++i) { + error = verifier_.Verify( + test_cert, "www2.example.com", 0, NULL, &verify_result, + callback.callback(), &request_handle, BoundNetLog()); + ASSERT_EQ(ERR_IO_PENDING, error); + ASSERT_TRUE(request_handle != NULL); + error = callback.WaitForResult(); + verifier_.ClearCache(); + } +} + +// Tests that a canceled request is not leaked. +TEST_F(MultiThreadedCertVerifierTest, CancelRequestThenQuit) { + base::FilePath certs_dir = GetTestCertsDirectory(); + scoped_refptr<X509Certificate> test_cert( + ImportCertFromFile(certs_dir, "ok_cert.pem")); + ASSERT_NE(static_cast<X509Certificate*>(NULL), test_cert); + + int error; + CertVerifyResult verify_result; + TestCompletionCallback callback; + CertVerifier::RequestHandle request_handle; + + error = verifier_.Verify(test_cert, "www.example.com", 0, NULL, + &verify_result, callback.callback(), + &request_handle, BoundNetLog()); + ASSERT_EQ(ERR_IO_PENDING, error); + ASSERT_TRUE(request_handle != NULL); + verifier_.CancelRequest(request_handle); + // Destroy |verifier| by going out of scope. +} + +TEST_F(MultiThreadedCertVerifierTest, RequestParamsComparators) { + SHA1HashValue a_key; + memset(a_key.data, 'a', sizeof(a_key.data)); + + SHA1HashValue z_key; + memset(z_key.data, 'z', sizeof(z_key.data)); + + const CertificateList empty_list; + CertificateList test_list; + test_list.push_back( + ImportCertFromFile(GetTestCertsDirectory(), "ok_cert.pem")); + + struct { + // Keys to test + MultiThreadedCertVerifier::RequestParams key1; + MultiThreadedCertVerifier::RequestParams key2; + + // Expectation: + // -1 means key1 is less than key2 + // 0 means key1 equals key2 + // 1 means key1 is greater than key2 + int expected_result; + } tests[] = { + { // Test for basic equivalence. + MultiThreadedCertVerifier::RequestParams(a_key, a_key, "www.example.test", + 0, test_list), + MultiThreadedCertVerifier::RequestParams(a_key, a_key, "www.example.test", + 0, test_list), + 0, + }, + { // Test that different certificates but with the same CA and for + // the same host are different validation keys. + MultiThreadedCertVerifier::RequestParams(a_key, a_key, "www.example.test", + 0, test_list), + MultiThreadedCertVerifier::RequestParams(z_key, a_key, "www.example.test", + 0, test_list), + -1, + }, + { // Test that the same EE certificate for the same host, but with + // different chains are different validation keys. + MultiThreadedCertVerifier::RequestParams(a_key, z_key, "www.example.test", + 0, test_list), + MultiThreadedCertVerifier::RequestParams(a_key, a_key, "www.example.test", + 0, test_list), + 1, + }, + { // The same certificate, with the same chain, but for different + // hosts are different validation keys. + MultiThreadedCertVerifier::RequestParams(a_key, a_key, + "www1.example.test", 0, + test_list), + MultiThreadedCertVerifier::RequestParams(a_key, a_key, + "www2.example.test", 0, + test_list), + -1, + }, + { // The same certificate, chain, and host, but with different flags + // are different validation keys. + MultiThreadedCertVerifier::RequestParams(a_key, a_key, "www.example.test", + CertVerifier::VERIFY_EV_CERT, + test_list), + MultiThreadedCertVerifier::RequestParams(a_key, a_key, "www.example.test", + 0, test_list), + 1, + }, + { // Different additional_trust_anchors. + MultiThreadedCertVerifier::RequestParams(a_key, a_key, "www.example.test", + 0, empty_list), + MultiThreadedCertVerifier::RequestParams(a_key, a_key, "www.example.test", + 0, test_list), + -1, + }, + }; + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(tests); ++i) { + SCOPED_TRACE(base::StringPrintf("Test[%" PRIuS "]", i)); + + const MultiThreadedCertVerifier::RequestParams& key1 = tests[i].key1; + const MultiThreadedCertVerifier::RequestParams& key2 = tests[i].key2; + + switch (tests[i].expected_result) { + case -1: + EXPECT_TRUE(key1 < key2); + EXPECT_FALSE(key2 < key1); + break; + case 0: + EXPECT_FALSE(key1 < key2); + EXPECT_FALSE(key2 < key1); + break; + case 1: + EXPECT_FALSE(key1 < key2); + EXPECT_TRUE(key2 < key1); + break; + default: + FAIL() << "Invalid expectation. Can be only -1, 0, 1"; + } + } +} + +TEST_F(MultiThreadedCertVerifierTest, CertTrustAnchorProvider) { + MockCertTrustAnchorProvider trust_provider; + verifier_.SetCertTrustAnchorProvider(&trust_provider); + + scoped_refptr<X509Certificate> test_cert( + ImportCertFromFile(GetTestCertsDirectory(), "ok_cert.pem")); + ASSERT_TRUE(test_cert); + + const CertificateList empty_cert_list; + CertificateList cert_list; + cert_list.push_back(test_cert); + + // Check that Verify() asks the |trust_provider| for the current list of + // additional trust anchors. + int error; + CertVerifyResult verify_result; + TestCompletionCallback callback; + CertVerifier::RequestHandle request_handle; + EXPECT_CALL(trust_provider, GetAdditionalTrustAnchors()) + .WillOnce(ReturnRef(empty_cert_list)); + error = verifier_.Verify(test_cert, "www.example.com", 0, NULL, + &verify_result, callback.callback(), + &request_handle, BoundNetLog()); + Mock::VerifyAndClearExpectations(&trust_provider); + ASSERT_EQ(ERR_IO_PENDING, error); + ASSERT_TRUE(request_handle); + error = callback.WaitForResult(); + EXPECT_EQ(ERR_CERT_COMMON_NAME_INVALID, error); + ASSERT_EQ(1u, verifier_.requests()); + ASSERT_EQ(0u, verifier_.cache_hits()); + + // The next Verify() uses the cached result. + EXPECT_CALL(trust_provider, GetAdditionalTrustAnchors()) + .WillOnce(ReturnRef(empty_cert_list)); + error = verifier_.Verify(test_cert, "www.example.com", 0, NULL, + &verify_result, callback.callback(), + &request_handle, BoundNetLog()); + Mock::VerifyAndClearExpectations(&trust_provider); + EXPECT_EQ(ERR_CERT_COMMON_NAME_INVALID, error); + EXPECT_FALSE(request_handle); + ASSERT_EQ(2u, verifier_.requests()); + ASSERT_EQ(1u, verifier_.cache_hits()); + + // Another Verify() for the same certificate but with a different list of + // trust anchors will not reuse the cache. + EXPECT_CALL(trust_provider, GetAdditionalTrustAnchors()) + .WillOnce(ReturnRef(cert_list)); + error = verifier_.Verify(test_cert, "www.example.com", 0, NULL, + &verify_result, callback.callback(), + &request_handle, BoundNetLog()); + Mock::VerifyAndClearExpectations(&trust_provider); + ASSERT_EQ(ERR_IO_PENDING, error); + ASSERT_TRUE(request_handle != NULL); + error = callback.WaitForResult(); + EXPECT_EQ(ERR_CERT_COMMON_NAME_INVALID, error); + ASSERT_EQ(3u, verifier_.requests()); + ASSERT_EQ(1u, verifier_.cache_hits()); +} + +} // namespace net diff --git a/net/cert/nss_cert_database.cc b/net/cert/nss_cert_database.cc new file mode 100644 index 0000000..651b521 --- /dev/null +++ b/net/cert/nss_cert_database.cc @@ -0,0 +1,344 @@ +// Copyright (c) 2012 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 "net/cert/nss_cert_database.h" + +#include <cert.h> +#include <certdb.h> +#include <keyhi.h> +#include <pk11pub.h> +#include <secmod.h> + +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "base/memory/singleton.h" +#include "base/observer_list_threadsafe.h" +#include "crypto/nss_util.h" +#include "crypto/nss_util_internal.h" +#include "net/base/crypto_module.h" +#include "net/base/net_errors.h" +#include "net/cert/cert_database.h" +#include "net/cert/x509_certificate.h" +#include "net/third_party/mozilla_security_manager/nsNSSCertificateDB.h" +#include "net/third_party/mozilla_security_manager/nsPKCS12Blob.h" + +// In NSS 3.13, CERTDB_VALID_PEER was renamed CERTDB_TERMINAL_RECORD. So we use +// the new name of the macro. +#if !defined(CERTDB_TERMINAL_RECORD) +#define CERTDB_TERMINAL_RECORD CERTDB_VALID_PEER +#endif + +// PSM = Mozilla's Personal Security Manager. +namespace psm = mozilla_security_manager; + +namespace net { + +NSSCertDatabase::ImportCertFailure::ImportCertFailure( + X509Certificate* cert, int err) + : certificate(cert), + net_error(err) {} + +NSSCertDatabase::ImportCertFailure::~ImportCertFailure() {} + +// static +NSSCertDatabase* NSSCertDatabase::GetInstance() { + return Singleton<NSSCertDatabase>::get(); +} + +NSSCertDatabase::NSSCertDatabase() + : observer_list_(new ObserverListThreadSafe<Observer>) { + crypto::EnsureNSSInit(); + psm::EnsurePKCS12Init(); +} + +NSSCertDatabase::~NSSCertDatabase() {} + +void NSSCertDatabase::ListCerts(CertificateList* certs) { + certs->clear(); + + CERTCertList* cert_list = PK11_ListCerts(PK11CertListUnique, NULL); + CERTCertListNode* node; + for (node = CERT_LIST_HEAD(cert_list); + !CERT_LIST_END(node, cert_list); + node = CERT_LIST_NEXT(node)) { + certs->push_back(X509Certificate::CreateFromHandle( + node->cert, X509Certificate::OSCertHandles())); + } + CERT_DestroyCertList(cert_list); +} + +CryptoModule* NSSCertDatabase::GetPublicModule() const { + CryptoModule* module = + CryptoModule::CreateFromHandle(crypto::GetPublicNSSKeySlot()); + // The module is already referenced when returned from + // GetPublicNSSKeySlot, so we need to deref it once. + PK11_FreeSlot(module->os_module_handle()); + + return module; +} + +CryptoModule* NSSCertDatabase::GetPrivateModule() const { + CryptoModule* module = + CryptoModule::CreateFromHandle(crypto::GetPrivateNSSKeySlot()); + // The module is already referenced when returned from + // GetPrivateNSSKeySlot, so we need to deref it once. + PK11_FreeSlot(module->os_module_handle()); + + return module; +} + +void NSSCertDatabase::ListModules(CryptoModuleList* modules, + bool need_rw) const { + modules->clear(); + + PK11SlotList* slot_list = NULL; + // The wincx arg is unused since we don't call PK11_SetIsLoggedInFunc. + slot_list = PK11_GetAllTokens(CKM_INVALID_MECHANISM, + need_rw ? PR_TRUE : PR_FALSE, // needRW + PR_TRUE, // loadCerts (unused) + NULL); // wincx + if (!slot_list) { + LOG(ERROR) << "PK11_GetAllTokens failed: " << PORT_GetError(); + return; + } + + PK11SlotListElement* slot_element = PK11_GetFirstSafe(slot_list); + while (slot_element) { + modules->push_back(CryptoModule::CreateFromHandle(slot_element->slot)); + slot_element = PK11_GetNextSafe(slot_list, slot_element, + PR_FALSE); // restart + } + + PK11_FreeSlotList(slot_list); +} + +int NSSCertDatabase::ImportFromPKCS12( + CryptoModule* module, + const std::string& data, + const base::string16& password, + bool is_extractable, + net::CertificateList* imported_certs) { + int result = psm::nsPKCS12Blob_Import(module->os_module_handle(), + data.data(), data.size(), + password, + is_extractable, + imported_certs); + if (result == net::OK) + NotifyObserversOfCertAdded(NULL); + + return result; +} + +int NSSCertDatabase::ExportToPKCS12( + const CertificateList& certs, + const base::string16& password, + std::string* output) const { + return psm::nsPKCS12Blob_Export(output, certs, password); +} + +X509Certificate* NSSCertDatabase::FindRootInList( + const CertificateList& certificates) const { + DCHECK_GT(certificates.size(), 0U); + + if (certificates.size() == 1) + return certificates[0].get(); + + X509Certificate* cert0 = certificates[0]; + X509Certificate* cert1 = certificates[1]; + X509Certificate* certn_2 = certificates[certificates.size() - 2]; + X509Certificate* certn_1 = certificates[certificates.size() - 1]; + + if (CERT_CompareName(&cert1->os_cert_handle()->issuer, + &cert0->os_cert_handle()->subject) == SECEqual) + return cert0; + if (CERT_CompareName(&certn_2->os_cert_handle()->issuer, + &certn_1->os_cert_handle()->subject) == SECEqual) + return certn_1; + + VLOG(1) << "certificate list is not a hierarchy"; + return cert0; +} + +bool NSSCertDatabase::ImportCACerts(const CertificateList& certificates, + TrustBits trust_bits, + ImportCertFailureList* not_imported) { + X509Certificate* root = FindRootInList(certificates); + bool success = psm::ImportCACerts(certificates, root, trust_bits, + not_imported); + if (success) + NotifyObserversOfCertTrustChanged(NULL); + + return success; +} + +bool NSSCertDatabase::ImportServerCert(const CertificateList& certificates, + TrustBits trust_bits, + ImportCertFailureList* not_imported) { + return psm::ImportServerCert(certificates, trust_bits, not_imported); +} + +NSSCertDatabase::TrustBits NSSCertDatabase::GetCertTrust( + const X509Certificate* cert, + CertType type) const { + CERTCertTrust trust; + SECStatus srv = CERT_GetCertTrust(cert->os_cert_handle(), &trust); + if (srv != SECSuccess) { + LOG(ERROR) << "CERT_GetCertTrust failed with error " << PORT_GetError(); + return TRUST_DEFAULT; + } + // We define our own more "friendly" TrustBits, which means we aren't able to + // round-trip all possible NSS trust flag combinations. We try to map them in + // a sensible way. + switch (type) { + case CA_CERT: { + const unsigned kTrustedCA = CERTDB_TRUSTED_CA | CERTDB_TRUSTED_CLIENT_CA; + const unsigned kCAFlags = kTrustedCA | CERTDB_TERMINAL_RECORD; + + TrustBits trust_bits = TRUST_DEFAULT; + if ((trust.sslFlags & kCAFlags) == CERTDB_TERMINAL_RECORD) + trust_bits |= DISTRUSTED_SSL; + else if (trust.sslFlags & kTrustedCA) + trust_bits |= TRUSTED_SSL; + + if ((trust.emailFlags & kCAFlags) == CERTDB_TERMINAL_RECORD) + trust_bits |= DISTRUSTED_EMAIL; + else if (trust.emailFlags & kTrustedCA) + trust_bits |= TRUSTED_EMAIL; + + if ((trust.objectSigningFlags & kCAFlags) == CERTDB_TERMINAL_RECORD) + trust_bits |= DISTRUSTED_OBJ_SIGN; + else if (trust.objectSigningFlags & kTrustedCA) + trust_bits |= TRUSTED_OBJ_SIGN; + + return trust_bits; + } + case SERVER_CERT: + if (trust.sslFlags & CERTDB_TERMINAL_RECORD) { + if (trust.sslFlags & CERTDB_TRUSTED) + return TRUSTED_SSL; + return DISTRUSTED_SSL; + } + return TRUST_DEFAULT; + default: + return TRUST_DEFAULT; + } +} + +bool NSSCertDatabase::IsUntrusted(const X509Certificate* cert) const { + CERTCertTrust nsstrust; + SECStatus rv = CERT_GetCertTrust(cert->os_cert_handle(), &nsstrust); + if (rv != SECSuccess) { + LOG(ERROR) << "CERT_GetCertTrust failed with error " << PORT_GetError(); + return false; + } + + // The CERTCertTrust structure contains three trust records: + // sslFlags, emailFlags, and objectSigningFlags. The three + // trust records are independent of each other. + // + // If the CERTDB_TERMINAL_RECORD bit in a trust record is set, + // then that trust record is a terminal record. A terminal + // record is used for explicit trust and distrust of an + // end-entity or intermediate CA cert. + // + // In a terminal record, if neither CERTDB_TRUSTED_CA nor + // CERTDB_TRUSTED is set, then the terminal record means + // explicit distrust. On the other hand, if the terminal + // record has either CERTDB_TRUSTED_CA or CERTDB_TRUSTED bit + // set, then the terminal record means explicit trust. + // + // For a root CA, the trust record does not have + // the CERTDB_TERMINAL_RECORD bit set. + + static const unsigned int kTrusted = CERTDB_TRUSTED_CA | CERTDB_TRUSTED; + if ((nsstrust.sslFlags & CERTDB_TERMINAL_RECORD) != 0 && + (nsstrust.sslFlags & kTrusted) == 0) { + return true; + } + if ((nsstrust.emailFlags & CERTDB_TERMINAL_RECORD) != 0 && + (nsstrust.emailFlags & kTrusted) == 0) { + return true; + } + if ((nsstrust.objectSigningFlags & CERTDB_TERMINAL_RECORD) != 0 && + (nsstrust.objectSigningFlags & kTrusted) == 0) { + return true; + } + + // Self-signed certificates that don't have any trust bits set are untrusted. + // Other certificates that don't have any trust bits set may still be trusted + // if they chain up to a trust anchor. + if (CERT_CompareName(&cert->os_cert_handle()->issuer, + &cert->os_cert_handle()->subject) == SECEqual) { + return (nsstrust.sslFlags & kTrusted) == 0 && + (nsstrust.emailFlags & kTrusted) == 0 && + (nsstrust.objectSigningFlags & kTrusted) == 0; + } + + return false; +} + +bool NSSCertDatabase::SetCertTrust(const X509Certificate* cert, + CertType type, + TrustBits trust_bits) { + bool success = psm::SetCertTrust(cert, type, trust_bits); + if (success) + NotifyObserversOfCertTrustChanged(cert); + + return success; +} + +bool NSSCertDatabase::DeleteCertAndKey(const X509Certificate* cert) { + // For some reason, PK11_DeleteTokenCertAndKey only calls + // SEC_DeletePermCertificate if the private key is found. So, we check + // whether a private key exists before deciding which function to call to + // delete the cert. + SECKEYPrivateKey *privKey = PK11_FindKeyByAnyCert(cert->os_cert_handle(), + NULL); + if (privKey) { + SECKEY_DestroyPrivateKey(privKey); + if (PK11_DeleteTokenCertAndKey(cert->os_cert_handle(), NULL)) { + LOG(ERROR) << "PK11_DeleteTokenCertAndKey failed: " << PORT_GetError(); + return false; + } + } else { + if (SEC_DeletePermCertificate(cert->os_cert_handle())) { + LOG(ERROR) << "SEC_DeletePermCertificate failed: " << PORT_GetError(); + return false; + } + } + + NotifyObserversOfCertRemoved(cert); + + return true; +} + +bool NSSCertDatabase::IsReadOnly(const X509Certificate* cert) const { + PK11SlotInfo* slot = cert->os_cert_handle()->slot; + return slot && PK11_IsReadOnly(slot); +} + +void NSSCertDatabase::AddObserver(Observer* observer) { + observer_list_->AddObserver(observer); +} + +void NSSCertDatabase::RemoveObserver(Observer* observer) { + observer_list_->RemoveObserver(observer); +} + +void NSSCertDatabase::NotifyObserversOfCertAdded(const X509Certificate* cert) { + observer_list_->Notify(&Observer::OnCertAdded, make_scoped_refptr(cert)); +} + +void NSSCertDatabase::NotifyObserversOfCertRemoved( + const X509Certificate* cert) { + observer_list_->Notify(&Observer::OnCertRemoved, make_scoped_refptr(cert)); +} + +void NSSCertDatabase::NotifyObserversOfCertTrustChanged( + const X509Certificate* cert) { + observer_list_->Notify( + &Observer::OnCertTrustChanged, make_scoped_refptr(cert)); +} + +} // namespace net diff --git a/net/cert/nss_cert_database.h b/net/cert/nss_cert_database.h new file mode 100644 index 0000000..43d59ea --- /dev/null +++ b/net/cert/nss_cert_database.h @@ -0,0 +1,208 @@ +// Copyright (c) 2012 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. + +#ifndef NET_CERT_NSS_CERT_DATABASE_H_ +#define NET_CERT_NSS_CERT_DATABASE_H_ + +#include <string> +#include <vector> + +#include "base/basictypes.h" +#include "base/memory/ref_counted.h" +#include "base/string16.h" +#include "net/base/net_export.h" +#include "net/cert/cert_type.h" +#include "net/cert/x509_certificate.h" + +template <typename T> struct DefaultSingletonTraits; +template <class ObserverType> class ObserverListThreadSafe; + +namespace net { + +class CryptoModule; +typedef std::vector<scoped_refptr<CryptoModule> > CryptoModuleList; + +// Provides functions to manipulate the NSS certificate stores. +class NET_EXPORT NSSCertDatabase { + public: + + class NET_EXPORT Observer { + public: + virtual ~Observer() {} + + // Will be called when a new certificate is added. + // Called with |cert| == NULL after importing a list of certificates + // in ImportFromPKCS12(). + virtual void OnCertAdded(const X509Certificate* cert) {} + + // Will be called when a certificate is removed. + virtual void OnCertRemoved(const X509Certificate* cert) {} + + // Will be called when a certificate's trust is changed. + // Called with |cert| == NULL after importing a list of certificates + // in ImportCACerts(). + virtual void OnCertTrustChanged(const X509Certificate* cert) {} + + protected: + Observer() {} + + private: + DISALLOW_COPY_AND_ASSIGN(Observer); + }; + + // Stores per-certificate error codes for import failures. + struct NET_EXPORT ImportCertFailure { + public: + ImportCertFailure(X509Certificate* cert, int err); + ~ImportCertFailure(); + + scoped_refptr<X509Certificate> certificate; + int net_error; + }; + typedef std::vector<ImportCertFailure> ImportCertFailureList; + + // Constants that define which usages a certificate is trusted for. + // They are used in combination with CertType to specify trust for each type + // of certificate. + // For a CA_CERT, they specify that the CA is trusted for issuing server and + // client certs of each type. + // For SERVER_CERT, only TRUSTED_SSL makes sense, and specifies the cert is + // trusted as a server. + // For EMAIL_CERT, only TRUSTED_EMAIL makes sense, and specifies the cert is + // trusted for email. + // DISTRUSTED_* specifies that the cert should not be trusted for the given + // usage, regardless of whether it would otherwise inherit trust from the + // issuer chain. + // Use TRUST_DEFAULT to inherit trust as normal. + // NOTE: The actual constants are defined using an enum instead of static + // consts due to compilation/linkage constraints with template functions. + typedef uint32 TrustBits; + enum { + TRUST_DEFAULT = 0, + TRUSTED_SSL = 1 << 0, + TRUSTED_EMAIL = 1 << 1, + TRUSTED_OBJ_SIGN = 1 << 2, + DISTRUSTED_SSL = 1 << 3, + DISTRUSTED_EMAIL = 1 << 4, + DISTRUSTED_OBJ_SIGN = 1 << 5, + }; + + static NSSCertDatabase* GetInstance(); + + // Get a list of unique certificates in the certificate database (one + // instance of all certificates). + void ListCerts(CertificateList* certs); + + // Get the default module for public key data. + // The returned pointer must be stored in a scoped_refptr<CryptoModule>. + CryptoModule* GetPublicModule() const; + + // Get the default module for private key or mixed private/public key data. + // The returned pointer must be stored in a scoped_refptr<CryptoModule>. + CryptoModule* GetPrivateModule() const; + + // Get all modules. + // If |need_rw| is true, only writable modules will be returned. + void ListModules(CryptoModuleList* modules, bool need_rw) const; + + // Import certificates and private keys from PKCS #12 blob into the module. + // If |is_extractable| is false, mark the private key as being unextractable + // from the module. + // Returns OK or a network error code such as ERR_PKCS12_IMPORT_BAD_PASSWORD + // or ERR_PKCS12_IMPORT_ERROR. |imported_certs|, if non-NULL, returns a list + // of certs that were imported. + int ImportFromPKCS12(CryptoModule* module, + const std::string& data, + const base::string16& password, + bool is_extractable, + CertificateList* imported_certs); + + // Export the given certificates and private keys into a PKCS #12 blob, + // storing into |output|. + // Returns the number of certificates successfully exported. + int ExportToPKCS12(const CertificateList& certs, + const base::string16& password, + std::string* output) const; + + // Uses similar logic to nsNSSCertificateDB::handleCACertDownload to find the + // root. Assumes the list is an ordered hierarchy with the root being either + // the first or last element. + // TODO(mattm): improve this to handle any order. + X509Certificate* FindRootInList(const CertificateList& certificates) const; + + // Import CA certificates. + // Tries to import all the certificates given. The root will be trusted + // according to |trust_bits|. Any certificates that could not be imported + // will be listed in |not_imported|. + // Returns false if there is an internal error, otherwise true is returned and + // |not_imported| should be checked for any certificates that were not + // imported. + bool ImportCACerts(const CertificateList& certificates, + TrustBits trust_bits, + ImportCertFailureList* not_imported); + + // Import server certificate. The first cert should be the server cert. Any + // additional certs should be intermediate/CA certs and will be imported but + // not given any trust. + // Any certificates that could not be imported will be listed in + // |not_imported|. + // |trust_bits| can be set to explicitly trust or distrust the certificate, or + // use TRUST_DEFAULT to inherit trust as normal. + // Returns false if there is an internal error, otherwise true is returned and + // |not_imported| should be checked for any certificates that were not + // imported. + bool ImportServerCert(const CertificateList& certificates, + TrustBits trust_bits, + ImportCertFailureList* not_imported); + + // Get trust bits for certificate. + TrustBits GetCertTrust(const X509Certificate* cert, CertType type) const; + + // IsUntrusted returns true if |cert| is specifically untrusted. These + // certificates are stored in the database for the specific purpose of + // rejecting them. + bool IsUntrusted(const X509Certificate* cert) const; + + // Set trust values for certificate. + // Returns true on success or false on failure. + bool SetCertTrust(const X509Certificate* cert, + CertType type, + TrustBits trust_bits); + + // Delete certificate and associated private key (if one exists). + // |cert| is still valid when this function returns. Returns true on + // success. + bool DeleteCertAndKey(const X509Certificate* cert); + + // Check whether cert is stored in a readonly slot. + bool IsReadOnly(const X509Certificate* cert) const; + + // Registers |observer| to receive notifications of certificate changes. The + // thread on which this is called is the thread on which |observer| will be + // called back with notifications. + void AddObserver(Observer* observer); + + // Unregisters |observer| from receiving notifications. This must be called + // on the same thread on which AddObserver() was called. + void RemoveObserver(Observer* observer); + + private: + friend struct DefaultSingletonTraits<NSSCertDatabase>; + + NSSCertDatabase(); + ~NSSCertDatabase(); + + // Broadcasts notifications to all registered observers. + void NotifyObserversOfCertAdded(const X509Certificate* cert); + void NotifyObserversOfCertRemoved(const X509Certificate* cert); + void NotifyObserversOfCertTrustChanged(const X509Certificate* cert); + + const scoped_refptr<ObserverListThreadSafe<Observer> > observer_list_; + + DISALLOW_COPY_AND_ASSIGN(NSSCertDatabase); +}; + +} // namespace net + +#endif // NET_CERT_NSS_CERT_DATABASE_H_ diff --git a/net/cert/nss_cert_database_unittest.cc b/net/cert/nss_cert_database_unittest.cc new file mode 100644 index 0000000..2b3f3f3 --- /dev/null +++ b/net/cert/nss_cert_database_unittest.cc @@ -0,0 +1,929 @@ +// Copyright (c) 2012 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 <cert.h> +#include <certdb.h> +#include <pk11pub.h> + +#include <algorithm> + +#include "base/file_util.h" +#include "base/files/file_path.h" +#include "base/lazy_instance.h" +#include "base/message_loop.h" +#include "base/path_service.h" +#include "base/string16.h" +#include "base/string_util.h" +#include "base/utf_string_conversions.h" +#include "crypto/nss_util.h" +#include "crypto/nss_util_internal.h" +#include "crypto/scoped_nss_types.h" +#include "net/base/crypto_module.h" +#include "net/base/net_errors.h" +#include "net/base/test_data_directory.h" +#include "net/cert/cert_status_flags.h" +#include "net/cert/cert_verify_proc_nss.h" +#include "net/cert/cert_verify_result.h" +#include "net/cert/nss_cert_database.h" +#include "net/cert/x509_certificate.h" +#include "net/test/cert_test_util.h" +#include "net/third_party/mozilla_security_manager/nsNSSCertificateDB.h" +#include "testing/gtest/include/gtest/gtest.h" + +// In NSS 3.13, CERTDB_VALID_PEER was renamed CERTDB_TERMINAL_RECORD. So we use +// the new name of the macro. +#if !defined(CERTDB_TERMINAL_RECORD) +#define CERTDB_TERMINAL_RECORD CERTDB_VALID_PEER +#endif + +namespace net { + +class CertDatabaseNSSTest : public testing::Test { + public: + virtual void SetUp() { + ASSERT_TRUE(test_nssdb_.is_open()); + cert_db_ = NSSCertDatabase::GetInstance(); + slot_ = cert_db_->GetPublicModule(); + + // Test db should be empty at start of test. + EXPECT_EQ(0U, ListCertsInSlot(slot_->os_module_handle()).size()); + } + + virtual void TearDown() { + // Don't try to cleanup if the setup failed. + ASSERT_TRUE(slot_->os_module_handle()); + + EXPECT_TRUE(CleanupSlotContents()); + + // Run the message loop to process any observer callbacks (e.g. for the + // ClientSocketFactory singleton) so that the scoped ref ptrs created in + // NSSCertDatabase::NotifyObservers* get released. + MessageLoop::current()->RunUntilIdle(); + + EXPECT_EQ(0U, ListCertsInSlot(slot_->os_module_handle()).size()); + } + + protected: + static std::string ReadTestFile(const std::string& name) { + std::string result; + base::FilePath cert_path = GetTestCertsDirectory().AppendASCII(name); + EXPECT_TRUE(file_util::ReadFileToString(cert_path, &result)); + return result; + } + + static bool ReadCertIntoList(const std::string& name, + CertificateList* certs) { + scoped_refptr<X509Certificate> cert( + ImportCertFromFile(GetTestCertsDirectory(), name)); + if (!cert.get()) + return false; + + certs->push_back(cert); + return true; + } + + static CertificateList ListCertsInSlot(PK11SlotInfo* slot) { + CertificateList result; + CERTCertList* cert_list = PK11_ListCertsInSlot(slot); + for (CERTCertListNode* node = CERT_LIST_HEAD(cert_list); + !CERT_LIST_END(node, cert_list); + node = CERT_LIST_NEXT(node)) { + result.push_back(X509Certificate::CreateFromHandle( + node->cert, X509Certificate::OSCertHandles())); + } + CERT_DestroyCertList(cert_list); + + // Sort the result so that test comparisons can be deterministic. + std::sort(result.begin(), result.end(), X509Certificate::LessThan()); + return result; + } + + scoped_refptr<CryptoModule> slot_; + NSSCertDatabase* cert_db_; + const CertificateList empty_cert_list_; + + private: + bool CleanupSlotContents() { + bool ok = true; + CertificateList certs = ListCertsInSlot(slot_->os_module_handle()); + CERTCertTrust default_trust = {0}; + for (size_t i = 0; i < certs.size(); ++i) { + // Reset cert trust values to defaults before deleting. Otherwise NSS + // somehow seems to remember the trust which can break following tests. + SECStatus srv = CERT_ChangeCertTrust( + CERT_GetDefaultCertDB(), certs[i]->os_cert_handle(), &default_trust); + if (srv != SECSuccess) + ok = false; + + if (!cert_db_->DeleteCertAndKey(certs[i])) + ok = false; + } + return ok; + } + + crypto::ScopedTestNSSDB test_nssdb_; +}; + +TEST_F(CertDatabaseNSSTest, ListCerts) { + // This test isn't terribly useful, though it will at least let valgrind test + // for leaks. + CertificateList certs; + cert_db_->ListCerts(&certs); + // The test DB is empty, but let's assume there will always be something in + // the other slots. + EXPECT_LT(0U, certs.size()); +} + +TEST_F(CertDatabaseNSSTest, ImportFromPKCS12WrongPassword) { + std::string pkcs12_data = ReadTestFile("client.p12"); + + EXPECT_EQ(ERR_PKCS12_IMPORT_BAD_PASSWORD, + cert_db_->ImportFromPKCS12(slot_, + pkcs12_data, + base::string16(), + true, // is_extractable + NULL)); + + // Test db should still be empty. + EXPECT_EQ(0U, ListCertsInSlot(slot_->os_module_handle()).size()); +} + +TEST_F(CertDatabaseNSSTest, ImportFromPKCS12AsExtractableAndExportAgain) { + std::string pkcs12_data = ReadTestFile("client.p12"); + + EXPECT_EQ(OK, cert_db_->ImportFromPKCS12(slot_, + pkcs12_data, + ASCIIToUTF16("12345"), + true, // is_extractable + NULL)); + + CertificateList cert_list = ListCertsInSlot(slot_->os_module_handle()); + ASSERT_EQ(1U, cert_list.size()); + scoped_refptr<X509Certificate> cert(cert_list[0]); + + EXPECT_EQ("testusercert", + cert->subject().common_name); + + // TODO(mattm): move export test to separate test case? + std::string exported_data; + EXPECT_EQ(1, cert_db_->ExportToPKCS12(cert_list, ASCIIToUTF16("exportpw"), + &exported_data)); + ASSERT_LT(0U, exported_data.size()); + // TODO(mattm): further verification of exported data? +} + +TEST_F(CertDatabaseNSSTest, ImportFromPKCS12Twice) { + std::string pkcs12_data = ReadTestFile("client.p12"); + + EXPECT_EQ(OK, cert_db_->ImportFromPKCS12(slot_, + pkcs12_data, + ASCIIToUTF16("12345"), + true, // is_extractable + NULL)); + EXPECT_EQ(1U, ListCertsInSlot(slot_->os_module_handle()).size()); + + // NSS has a SEC_ERROR_PKCS12_DUPLICATE_DATA error, but it doesn't look like + // it's ever used. This test verifies that. + EXPECT_EQ(OK, cert_db_->ImportFromPKCS12(slot_, + pkcs12_data, + ASCIIToUTF16("12345"), + true, // is_extractable + NULL)); + EXPECT_EQ(1U, ListCertsInSlot(slot_->os_module_handle()).size()); +} + +TEST_F(CertDatabaseNSSTest, ImportFromPKCS12AsUnextractableAndExportAgain) { + std::string pkcs12_data = ReadTestFile("client.p12"); + + EXPECT_EQ(OK, cert_db_->ImportFromPKCS12(slot_, + pkcs12_data, + ASCIIToUTF16("12345"), + false, // is_extractable + NULL)); + + CertificateList cert_list = ListCertsInSlot(slot_->os_module_handle()); + ASSERT_EQ(1U, cert_list.size()); + scoped_refptr<X509Certificate> cert(cert_list[0]); + + EXPECT_EQ("testusercert", + cert->subject().common_name); + + std::string exported_data; + EXPECT_EQ(0, cert_db_->ExportToPKCS12(cert_list, ASCIIToUTF16("exportpw"), + &exported_data)); +} + +// Importing a PKCS#12 file with a certificate but no corresponding +// private key should not mark an existing private key as unextractable. +TEST_F(CertDatabaseNSSTest, ImportFromPKCS12OnlyMarkIncludedKey) { + std::string pkcs12_data = ReadTestFile("client.p12"); + EXPECT_EQ(OK, cert_db_->ImportFromPKCS12(slot_, + pkcs12_data, + ASCIIToUTF16("12345"), + true, // is_extractable + NULL)); + + CertificateList cert_list = ListCertsInSlot(slot_->os_module_handle()); + ASSERT_EQ(1U, cert_list.size()); + + // Now import a PKCS#12 file with just a certificate but no private key. + pkcs12_data = ReadTestFile("client-nokey.p12"); + EXPECT_EQ(OK, cert_db_->ImportFromPKCS12(slot_, + pkcs12_data, + ASCIIToUTF16("12345"), + false, // is_extractable + NULL)); + + cert_list = ListCertsInSlot(slot_->os_module_handle()); + ASSERT_EQ(1U, cert_list.size()); + + // Make sure the imported private key is still extractable. + std::string exported_data; + EXPECT_EQ(1, cert_db_->ExportToPKCS12(cert_list, ASCIIToUTF16("exportpw"), + &exported_data)); + ASSERT_LT(0U, exported_data.size()); +} + +TEST_F(CertDatabaseNSSTest, ImportFromPKCS12InvalidFile) { + std::string pkcs12_data = "Foobarbaz"; + + EXPECT_EQ(ERR_PKCS12_IMPORT_INVALID_FILE, + cert_db_->ImportFromPKCS12(slot_, + pkcs12_data, + base::string16(), + true, // is_extractable + NULL)); + + // Test db should still be empty. + EXPECT_EQ(0U, ListCertsInSlot(slot_->os_module_handle()).size()); +} + +TEST_F(CertDatabaseNSSTest, ImportCACert_SSLTrust) { + CertificateList certs = CreateCertificateListFromFile( + GetTestCertsDirectory(), "root_ca_cert.crt", + X509Certificate::FORMAT_AUTO); + ASSERT_EQ(1U, certs.size()); + EXPECT_FALSE(certs[0]->os_cert_handle()->isperm); + + // Import it. + NSSCertDatabase::ImportCertFailureList failed; + EXPECT_TRUE(cert_db_->ImportCACerts(certs, NSSCertDatabase::TRUSTED_SSL, + &failed)); + + EXPECT_EQ(0U, failed.size()); + + CertificateList cert_list = ListCertsInSlot(slot_->os_module_handle()); + ASSERT_EQ(1U, cert_list.size()); + scoped_refptr<X509Certificate> cert(cert_list[0]); + EXPECT_EQ("Test CA", cert->subject().common_name); + + EXPECT_EQ(NSSCertDatabase::TRUSTED_SSL, + cert_db_->GetCertTrust(cert.get(), CA_CERT)); + + EXPECT_EQ(unsigned(CERTDB_VALID_CA | CERTDB_TRUSTED_CA | + CERTDB_TRUSTED_CLIENT_CA), + cert->os_cert_handle()->trust->sslFlags); + EXPECT_EQ(unsigned(CERTDB_VALID_CA), + cert->os_cert_handle()->trust->emailFlags); + EXPECT_EQ(unsigned(CERTDB_VALID_CA), + cert->os_cert_handle()->trust->objectSigningFlags); +} + +TEST_F(CertDatabaseNSSTest, ImportCACert_EmailTrust) { + CertificateList certs = CreateCertificateListFromFile( + GetTestCertsDirectory(), "root_ca_cert.crt", + X509Certificate::FORMAT_AUTO); + ASSERT_EQ(1U, certs.size()); + EXPECT_FALSE(certs[0]->os_cert_handle()->isperm); + + // Import it. + NSSCertDatabase::ImportCertFailureList failed; + EXPECT_TRUE(cert_db_->ImportCACerts(certs, NSSCertDatabase::TRUSTED_EMAIL, + &failed)); + + EXPECT_EQ(0U, failed.size()); + + CertificateList cert_list = ListCertsInSlot(slot_->os_module_handle()); + ASSERT_EQ(1U, cert_list.size()); + scoped_refptr<X509Certificate> cert(cert_list[0]); + EXPECT_EQ("Test CA", cert->subject().common_name); + + EXPECT_EQ(NSSCertDatabase::TRUSTED_EMAIL, + cert_db_->GetCertTrust(cert.get(), CA_CERT)); + + EXPECT_EQ(unsigned(CERTDB_VALID_CA), + cert->os_cert_handle()->trust->sslFlags); + EXPECT_EQ(unsigned(CERTDB_VALID_CA | CERTDB_TRUSTED_CA | + CERTDB_TRUSTED_CLIENT_CA), + cert->os_cert_handle()->trust->emailFlags); + EXPECT_EQ(unsigned(CERTDB_VALID_CA), + cert->os_cert_handle()->trust->objectSigningFlags); +} + +TEST_F(CertDatabaseNSSTest, ImportCACert_ObjSignTrust) { + CertificateList certs = CreateCertificateListFromFile( + GetTestCertsDirectory(), "root_ca_cert.crt", + X509Certificate::FORMAT_AUTO); + ASSERT_EQ(1U, certs.size()); + EXPECT_FALSE(certs[0]->os_cert_handle()->isperm); + + // Import it. + NSSCertDatabase::ImportCertFailureList failed; + EXPECT_TRUE(cert_db_->ImportCACerts(certs, NSSCertDatabase::TRUSTED_OBJ_SIGN, + &failed)); + + EXPECT_EQ(0U, failed.size()); + + CertificateList cert_list = ListCertsInSlot(slot_->os_module_handle()); + ASSERT_EQ(1U, cert_list.size()); + scoped_refptr<X509Certificate> cert(cert_list[0]); + EXPECT_EQ("Test CA", cert->subject().common_name); + + EXPECT_EQ(NSSCertDatabase::TRUSTED_OBJ_SIGN, + cert_db_->GetCertTrust(cert.get(), CA_CERT)); + + EXPECT_EQ(unsigned(CERTDB_VALID_CA), + cert->os_cert_handle()->trust->sslFlags); + EXPECT_EQ(unsigned(CERTDB_VALID_CA), + cert->os_cert_handle()->trust->emailFlags); + EXPECT_EQ(unsigned(CERTDB_VALID_CA | CERTDB_TRUSTED_CA | + CERTDB_TRUSTED_CLIENT_CA), + cert->os_cert_handle()->trust->objectSigningFlags); +} + +TEST_F(CertDatabaseNSSTest, ImportCA_NotCACert) { + CertificateList certs = CreateCertificateListFromFile( + GetTestCertsDirectory(), "google.single.pem", + X509Certificate::FORMAT_AUTO); + ASSERT_EQ(1U, certs.size()); + EXPECT_FALSE(certs[0]->os_cert_handle()->isperm); + + // Import it. + NSSCertDatabase::ImportCertFailureList failed; + EXPECT_TRUE(cert_db_->ImportCACerts(certs, NSSCertDatabase::TRUSTED_SSL, + &failed)); + ASSERT_EQ(1U, failed.size()); + // Note: this compares pointers directly. It's okay in this case because + // ImportCACerts returns the same pointers that were passed in. In the + // general case IsSameOSCert should be used. + EXPECT_EQ(certs[0], failed[0].certificate); + EXPECT_EQ(ERR_IMPORT_CA_CERT_NOT_CA, failed[0].net_error); + + EXPECT_EQ(0U, ListCertsInSlot(slot_->os_module_handle()).size()); +} + +TEST_F(CertDatabaseNSSTest, ImportCACertHierarchy) { + CertificateList certs; + ASSERT_TRUE(ReadCertIntoList("dod_root_ca_2_cert.der", &certs)); + ASSERT_TRUE(ReadCertIntoList("dod_ca_17_cert.der", &certs)); + ASSERT_TRUE(ReadCertIntoList("www_us_army_mil_cert.der", &certs)); + + // Import it. + NSSCertDatabase::ImportCertFailureList failed; + // Have to specify email trust for the cert verification of the child cert to + // work (see + // http://mxr.mozilla.org/mozilla/source/security/nss/lib/certhigh/certvfy.c#752 + // "XXX This choice of trustType seems arbitrary.") + EXPECT_TRUE(cert_db_->ImportCACerts( + certs, NSSCertDatabase::TRUSTED_SSL | NSSCertDatabase::TRUSTED_EMAIL, + &failed)); + + ASSERT_EQ(2U, failed.size()); + EXPECT_EQ("DOD CA-17", failed[0].certificate->subject().common_name); + EXPECT_EQ(ERR_FAILED, failed[0].net_error); // The certificate expired. + EXPECT_EQ("www.us.army.mil", failed[1].certificate->subject().common_name); + EXPECT_EQ(ERR_IMPORT_CA_CERT_NOT_CA, failed[1].net_error); + + CertificateList cert_list = ListCertsInSlot(slot_->os_module_handle()); + ASSERT_EQ(1U, cert_list.size()); + EXPECT_EQ("DoD Root CA 2", cert_list[0]->subject().common_name); +} + +TEST_F(CertDatabaseNSSTest, ImportCACertHierarchyDupeRoot) { + CertificateList certs; + ASSERT_TRUE(ReadCertIntoList("dod_root_ca_2_cert.der", &certs)); + + // First import just the root. + NSSCertDatabase::ImportCertFailureList failed; + EXPECT_TRUE(cert_db_->ImportCACerts( + certs, NSSCertDatabase::TRUSTED_SSL | NSSCertDatabase::TRUSTED_EMAIL, + &failed)); + + EXPECT_EQ(0U, failed.size()); + CertificateList cert_list = ListCertsInSlot(slot_->os_module_handle()); + ASSERT_EQ(1U, cert_list.size()); + EXPECT_EQ("DoD Root CA 2", cert_list[0]->subject().common_name); + + ASSERT_TRUE(ReadCertIntoList("dod_ca_17_cert.der", &certs)); + ASSERT_TRUE(ReadCertIntoList("www_us_army_mil_cert.der", &certs)); + + // Now import with the other certs in the list too. Even though the root is + // already present, we should still import the rest. + failed.clear(); + EXPECT_TRUE(cert_db_->ImportCACerts( + certs, NSSCertDatabase::TRUSTED_SSL | NSSCertDatabase::TRUSTED_EMAIL, + &failed)); + + ASSERT_EQ(3U, failed.size()); + EXPECT_EQ("DoD Root CA 2", failed[0].certificate->subject().common_name); + EXPECT_EQ(ERR_IMPORT_CERT_ALREADY_EXISTS, failed[0].net_error); + EXPECT_EQ("DOD CA-17", failed[1].certificate->subject().common_name); + EXPECT_EQ(ERR_FAILED, failed[1].net_error); // The certificate expired. + EXPECT_EQ("www.us.army.mil", failed[2].certificate->subject().common_name); + EXPECT_EQ(ERR_IMPORT_CA_CERT_NOT_CA, failed[2].net_error); + + cert_list = ListCertsInSlot(slot_->os_module_handle()); + ASSERT_EQ(1U, cert_list.size()); + EXPECT_EQ("DoD Root CA 2", cert_list[0]->subject().common_name); +} + +TEST_F(CertDatabaseNSSTest, ImportCACertHierarchyUntrusted) { + CertificateList certs; + ASSERT_TRUE(ReadCertIntoList("dod_root_ca_2_cert.der", &certs)); + ASSERT_TRUE(ReadCertIntoList("dod_ca_17_cert.der", &certs)); + + // Import it. + NSSCertDatabase::ImportCertFailureList failed; + EXPECT_TRUE(cert_db_->ImportCACerts(certs, NSSCertDatabase::TRUST_DEFAULT, + &failed)); + + ASSERT_EQ(1U, failed.size()); + EXPECT_EQ("DOD CA-17", failed[0].certificate->subject().common_name); + // TODO(mattm): should check for net error equivalent of + // SEC_ERROR_UNTRUSTED_ISSUER + EXPECT_EQ(ERR_FAILED, failed[0].net_error); + + CertificateList cert_list = ListCertsInSlot(slot_->os_module_handle()); + ASSERT_EQ(1U, cert_list.size()); + EXPECT_EQ("DoD Root CA 2", cert_list[0]->subject().common_name); +} + +TEST_F(CertDatabaseNSSTest, ImportCACertHierarchyTree) { + CertificateList certs; + ASSERT_TRUE(ReadCertIntoList("dod_root_ca_2_cert.der", &certs)); + ASSERT_TRUE(ReadCertIntoList("dod_ca_13_cert.der", &certs)); + ASSERT_TRUE(ReadCertIntoList("dod_ca_17_cert.der", &certs)); + + // Import it. + NSSCertDatabase::ImportCertFailureList failed; + EXPECT_TRUE(cert_db_->ImportCACerts( + certs, NSSCertDatabase::TRUSTED_SSL | NSSCertDatabase::TRUSTED_EMAIL, + &failed)); + + EXPECT_EQ(2U, failed.size()); + EXPECT_EQ("DOD CA-13", failed[0].certificate->subject().common_name); + EXPECT_EQ(ERR_FAILED, failed[0].net_error); // The certificate expired. + EXPECT_EQ("DOD CA-17", failed[1].certificate->subject().common_name); + EXPECT_EQ(ERR_FAILED, failed[1].net_error); // The certificate expired. + + CertificateList cert_list = ListCertsInSlot(slot_->os_module_handle()); + ASSERT_EQ(1U, cert_list.size()); + EXPECT_EQ("DoD Root CA 2", cert_list[0]->subject().common_name); +} + +TEST_F(CertDatabaseNSSTest, ImportCACertNotHierarchy) { + CertificateList certs = CreateCertificateListFromFile( + GetTestCertsDirectory(), "root_ca_cert.crt", + X509Certificate::FORMAT_AUTO); + ASSERT_EQ(1U, certs.size()); + ASSERT_TRUE(ReadCertIntoList("dod_ca_13_cert.der", &certs)); + ASSERT_TRUE(ReadCertIntoList("dod_ca_17_cert.der", &certs)); + + // Import it. + NSSCertDatabase::ImportCertFailureList failed; + EXPECT_TRUE(cert_db_->ImportCACerts( + certs, NSSCertDatabase::TRUSTED_SSL | NSSCertDatabase::TRUSTED_EMAIL | + NSSCertDatabase::TRUSTED_OBJ_SIGN, &failed)); + + ASSERT_EQ(2U, failed.size()); + // TODO(mattm): should check for net error equivalent of + // SEC_ERROR_UNKNOWN_ISSUER + EXPECT_EQ("DOD CA-13", failed[0].certificate->subject().common_name); + EXPECT_EQ(ERR_FAILED, failed[0].net_error); + EXPECT_EQ("DOD CA-17", failed[1].certificate->subject().common_name); + EXPECT_EQ(ERR_FAILED, failed[1].net_error); + + CertificateList cert_list = ListCertsInSlot(slot_->os_module_handle()); + ASSERT_EQ(1U, cert_list.size()); + EXPECT_EQ("Test CA", cert_list[0]->subject().common_name); +} + +// http://crbug.com/108009 - Disabled, as google.chain.pem is an expired +// certificate. +TEST_F(CertDatabaseNSSTest, DISABLED_ImportServerCert) { + // Need to import intermediate cert for the verify of google cert, otherwise + // it will try to fetch it automatically with cert_pi_useAIACertFetch, which + // will cause OCSPCreateSession on the main thread, which is not allowed. + CertificateList certs = CreateCertificateListFromFile( + GetTestCertsDirectory(), "google.chain.pem", + X509Certificate::FORMAT_AUTO); + ASSERT_EQ(2U, certs.size()); + + NSSCertDatabase::ImportCertFailureList failed; + EXPECT_TRUE(cert_db_->ImportServerCert(certs, NSSCertDatabase::TRUST_DEFAULT, + &failed)); + + EXPECT_EQ(0U, failed.size()); + + CertificateList cert_list = ListCertsInSlot(slot_->os_module_handle()); + ASSERT_EQ(2U, cert_list.size()); + scoped_refptr<X509Certificate> goog_cert(cert_list[0]); + scoped_refptr<X509Certificate> thawte_cert(cert_list[1]); + EXPECT_EQ("www.google.com", goog_cert->subject().common_name); + EXPECT_EQ("Thawte SGC CA", thawte_cert->subject().common_name); + + EXPECT_EQ(NSSCertDatabase::TRUST_DEFAULT, + cert_db_->GetCertTrust(goog_cert.get(), SERVER_CERT)); + + EXPECT_EQ(0U, goog_cert->os_cert_handle()->trust->sslFlags); + + scoped_refptr<CertVerifyProc> verify_proc(new CertVerifyProcNSS()); + int flags = 0; + CertVerifyResult verify_result; + int error = verify_proc->Verify(goog_cert, "www.google.com", flags, + NULL, empty_cert_list_, &verify_result); + EXPECT_EQ(OK, error); + EXPECT_EQ(0U, verify_result.cert_status); +} + +TEST_F(CertDatabaseNSSTest, ImportServerCert_SelfSigned) { + CertificateList certs; + ASSERT_TRUE(ReadCertIntoList("punycodetest.der", &certs)); + + NSSCertDatabase::ImportCertFailureList failed; + EXPECT_TRUE(cert_db_->ImportServerCert(certs, NSSCertDatabase::TRUST_DEFAULT, + &failed)); + + EXPECT_EQ(0U, failed.size()); + + CertificateList cert_list = ListCertsInSlot(slot_->os_module_handle()); + ASSERT_EQ(1U, cert_list.size()); + scoped_refptr<X509Certificate> puny_cert(cert_list[0]); + + EXPECT_EQ(NSSCertDatabase::TRUST_DEFAULT, + cert_db_->GetCertTrust(puny_cert.get(), SERVER_CERT)); + EXPECT_EQ(0U, puny_cert->os_cert_handle()->trust->sslFlags); + + scoped_refptr<CertVerifyProc> verify_proc(new CertVerifyProcNSS()); + int flags = 0; + CertVerifyResult verify_result; + int error = verify_proc->Verify(puny_cert, "xn--wgv71a119e.com", flags, + NULL, empty_cert_list_, &verify_result); + EXPECT_EQ(ERR_CERT_AUTHORITY_INVALID, error); + EXPECT_EQ(CERT_STATUS_AUTHORITY_INVALID, verify_result.cert_status); +} + +TEST_F(CertDatabaseNSSTest, ImportServerCert_SelfSigned_Trusted) { + // When using CERT_PKIXVerifyCert (which we do), server trust only works from + // 3.13.4 onwards. See https://bugzilla.mozilla.org/show_bug.cgi?id=647364. + if (!NSS_VersionCheck("3.13.4")) { + LOG(INFO) << "test skipped on NSS < 3.13.4"; + return; + } + + CertificateList certs; + ASSERT_TRUE(ReadCertIntoList("punycodetest.der", &certs)); + + NSSCertDatabase::ImportCertFailureList failed; + EXPECT_TRUE(cert_db_->ImportServerCert(certs, NSSCertDatabase::TRUSTED_SSL, + &failed)); + + EXPECT_EQ(0U, failed.size()); + + CertificateList cert_list = ListCertsInSlot(slot_->os_module_handle()); + ASSERT_EQ(1U, cert_list.size()); + scoped_refptr<X509Certificate> puny_cert(cert_list[0]); + + EXPECT_EQ(NSSCertDatabase::TRUSTED_SSL, + cert_db_->GetCertTrust(puny_cert.get(), SERVER_CERT)); + EXPECT_EQ(unsigned(CERTDB_TRUSTED | CERTDB_TERMINAL_RECORD), + puny_cert->os_cert_handle()->trust->sslFlags); + + scoped_refptr<CertVerifyProc> verify_proc(new CertVerifyProcNSS()); + int flags = 0; + CertVerifyResult verify_result; + int error = verify_proc->Verify(puny_cert, "xn--wgv71a119e.com", flags, + NULL, empty_cert_list_, &verify_result); + EXPECT_EQ(OK, error); + EXPECT_EQ(0U, verify_result.cert_status); +} + +TEST_F(CertDatabaseNSSTest, ImportCaAndServerCert) { + CertificateList ca_certs = CreateCertificateListFromFile( + GetTestCertsDirectory(), "root_ca_cert.crt", + X509Certificate::FORMAT_AUTO); + ASSERT_EQ(1U, ca_certs.size()); + + // Import CA cert and trust it. + NSSCertDatabase::ImportCertFailureList failed; + EXPECT_TRUE(cert_db_->ImportCACerts(ca_certs, NSSCertDatabase::TRUSTED_SSL, + &failed)); + EXPECT_EQ(0U, failed.size()); + + CertificateList certs = CreateCertificateListFromFile( + GetTestCertsDirectory(), "ok_cert.pem", + X509Certificate::FORMAT_AUTO); + ASSERT_EQ(1U, certs.size()); + + // Import server cert with default trust. + EXPECT_TRUE(cert_db_->ImportServerCert(certs, NSSCertDatabase::TRUST_DEFAULT, + &failed)); + EXPECT_EQ(0U, failed.size()); + + // Server cert should verify. + scoped_refptr<CertVerifyProc> verify_proc(new CertVerifyProcNSS()); + int flags = 0; + CertVerifyResult verify_result; + int error = verify_proc->Verify(certs[0], "127.0.0.1", flags, + NULL, empty_cert_list_, &verify_result); + EXPECT_EQ(OK, error); + EXPECT_EQ(0U, verify_result.cert_status); +} + +TEST_F(CertDatabaseNSSTest, ImportCaAndServerCert_DistrustServer) { + // Explicit distrust only works starting in NSS 3.13. + if (!NSS_VersionCheck("3.13")) { + LOG(INFO) << "test skipped on NSS < 3.13"; + return; + } + + CertificateList ca_certs = CreateCertificateListFromFile( + GetTestCertsDirectory(), "root_ca_cert.crt", + X509Certificate::FORMAT_AUTO); + ASSERT_EQ(1U, ca_certs.size()); + + // Import CA cert and trust it. + NSSCertDatabase::ImportCertFailureList failed; + EXPECT_TRUE(cert_db_->ImportCACerts(ca_certs, NSSCertDatabase::TRUSTED_SSL, + &failed)); + EXPECT_EQ(0U, failed.size()); + + CertificateList certs = CreateCertificateListFromFile( + GetTestCertsDirectory(), "ok_cert.pem", + X509Certificate::FORMAT_AUTO); + ASSERT_EQ(1U, certs.size()); + + // Import server cert without inheriting trust from issuer (explicit + // distrust). + EXPECT_TRUE(cert_db_->ImportServerCert( + certs, NSSCertDatabase::DISTRUSTED_SSL, &failed)); + EXPECT_EQ(0U, failed.size()); + EXPECT_EQ(NSSCertDatabase::DISTRUSTED_SSL, + cert_db_->GetCertTrust(certs[0], SERVER_CERT)); + + EXPECT_EQ(unsigned(CERTDB_TERMINAL_RECORD), + certs[0]->os_cert_handle()->trust->sslFlags); + + // Server cert should fail to verify. + scoped_refptr<CertVerifyProc> verify_proc(new CertVerifyProcNSS()); + int flags = 0; + CertVerifyResult verify_result; + int error = verify_proc->Verify(certs[0], "127.0.0.1", flags, + NULL, empty_cert_list_, &verify_result); + EXPECT_EQ(ERR_CERT_REVOKED, error); + EXPECT_EQ(CERT_STATUS_REVOKED, verify_result.cert_status); +} + +TEST_F(CertDatabaseNSSTest, TrustIntermediateCa) { + CertificateList ca_certs = CreateCertificateListFromFile( + GetTestCertsDirectory(), "2048-rsa-root.pem", + X509Certificate::FORMAT_AUTO); + ASSERT_EQ(1U, ca_certs.size()); + + // Import Root CA cert and distrust it. + NSSCertDatabase::ImportCertFailureList failed; + EXPECT_TRUE(cert_db_->ImportCACerts(ca_certs, NSSCertDatabase::DISTRUSTED_SSL, + &failed)); + EXPECT_EQ(0U, failed.size()); + + CertificateList intermediate_certs = CreateCertificateListFromFile( + GetTestCertsDirectory(), "2048-rsa-intermediate.pem", + X509Certificate::FORMAT_AUTO); + ASSERT_EQ(1U, intermediate_certs.size()); + + // Import Intermediate CA cert and trust it. + EXPECT_TRUE(cert_db_->ImportCACerts(intermediate_certs, + NSSCertDatabase::TRUSTED_SSL, &failed)); + EXPECT_EQ(0U, failed.size()); + + CertificateList certs = CreateCertificateListFromFile( + GetTestCertsDirectory(), "2048-rsa-ee-by-2048-rsa-intermediate.pem", + X509Certificate::FORMAT_AUTO); + ASSERT_EQ(1U, certs.size()); + + // Import server cert with default trust. + EXPECT_TRUE(cert_db_->ImportServerCert( + certs, NSSCertDatabase::TRUST_DEFAULT, &failed)); + EXPECT_EQ(0U, failed.size()); + EXPECT_EQ(NSSCertDatabase::TRUST_DEFAULT, + cert_db_->GetCertTrust(certs[0], SERVER_CERT)); + + // Server cert should verify. + scoped_refptr<CertVerifyProc> verify_proc(new CertVerifyProcNSS()); + int flags = 0; + CertVerifyResult verify_result; + int error = verify_proc->Verify(certs[0], "127.0.0.1", flags, + NULL, empty_cert_list_, &verify_result); + EXPECT_EQ(OK, error); + EXPECT_EQ(0U, verify_result.cert_status); + + // Explicit distrust only works starting in NSS 3.13. + if (!NSS_VersionCheck("3.13")) { + LOG(INFO) << "test partially skipped on NSS < 3.13"; + return; + } + + // Trust the root cert and distrust the intermediate. + EXPECT_TRUE(cert_db_->SetCertTrust( + ca_certs[0], CA_CERT, NSSCertDatabase::TRUSTED_SSL)); + EXPECT_TRUE(cert_db_->SetCertTrust( + intermediate_certs[0], CA_CERT, NSSCertDatabase::DISTRUSTED_SSL)); + EXPECT_EQ( + unsigned(CERTDB_VALID_CA | CERTDB_TRUSTED_CA | CERTDB_TRUSTED_CLIENT_CA), + ca_certs[0]->os_cert_handle()->trust->sslFlags); + EXPECT_EQ(unsigned(CERTDB_VALID_CA), + ca_certs[0]->os_cert_handle()->trust->emailFlags); + EXPECT_EQ(unsigned(CERTDB_VALID_CA), + ca_certs[0]->os_cert_handle()->trust->objectSigningFlags); + EXPECT_EQ(unsigned(CERTDB_TERMINAL_RECORD), + intermediate_certs[0]->os_cert_handle()->trust->sslFlags); + EXPECT_EQ(unsigned(CERTDB_VALID_CA), + intermediate_certs[0]->os_cert_handle()->trust->emailFlags); + EXPECT_EQ( + unsigned(CERTDB_VALID_CA), + intermediate_certs[0]->os_cert_handle()->trust->objectSigningFlags); + + // Server cert should fail to verify. + CertVerifyResult verify_result2; + error = verify_proc->Verify(certs[0], "127.0.0.1", flags, + NULL, empty_cert_list_, &verify_result2); + EXPECT_EQ(ERR_CERT_REVOKED, error); + EXPECT_EQ(CERT_STATUS_REVOKED, verify_result2.cert_status); +} + +TEST_F(CertDatabaseNSSTest, TrustIntermediateCa2) { + NSSCertDatabase::ImportCertFailureList failed; + + CertificateList intermediate_certs = CreateCertificateListFromFile( + GetTestCertsDirectory(), "2048-rsa-intermediate.pem", + X509Certificate::FORMAT_AUTO); + ASSERT_EQ(1U, intermediate_certs.size()); + + // Import Intermediate CA cert and trust it. + EXPECT_TRUE(cert_db_->ImportCACerts(intermediate_certs, + NSSCertDatabase::TRUSTED_SSL, &failed)); + EXPECT_EQ(0U, failed.size()); + + CertificateList certs = CreateCertificateListFromFile( + GetTestCertsDirectory(), "2048-rsa-ee-by-2048-rsa-intermediate.pem", + X509Certificate::FORMAT_AUTO); + ASSERT_EQ(1U, certs.size()); + + // Import server cert with default trust. + EXPECT_TRUE(cert_db_->ImportServerCert( + certs, NSSCertDatabase::TRUST_DEFAULT, &failed)); + EXPECT_EQ(0U, failed.size()); + EXPECT_EQ(NSSCertDatabase::TRUST_DEFAULT, + cert_db_->GetCertTrust(certs[0], SERVER_CERT)); + + // Server cert should verify. + scoped_refptr<CertVerifyProc> verify_proc(new CertVerifyProcNSS()); + int flags = 0; + CertVerifyResult verify_result; + int error = verify_proc->Verify(certs[0], "127.0.0.1", flags, + NULL, empty_cert_list_, &verify_result); + EXPECT_EQ(OK, error); + EXPECT_EQ(0U, verify_result.cert_status); + + // Without explicit trust of the intermediate, verification should fail. + EXPECT_TRUE(cert_db_->SetCertTrust( + intermediate_certs[0], CA_CERT, NSSCertDatabase::TRUST_DEFAULT)); + + // Server cert should fail to verify. + CertVerifyResult verify_result2; + error = verify_proc->Verify(certs[0], "127.0.0.1", flags, + NULL, empty_cert_list_, &verify_result2); + EXPECT_EQ(ERR_CERT_AUTHORITY_INVALID, error); + EXPECT_EQ(CERT_STATUS_AUTHORITY_INVALID, verify_result2.cert_status); +} + +TEST_F(CertDatabaseNSSTest, TrustIntermediateCa3) { + NSSCertDatabase::ImportCertFailureList failed; + + CertificateList ca_certs = CreateCertificateListFromFile( + GetTestCertsDirectory(), "2048-rsa-root.pem", + X509Certificate::FORMAT_AUTO); + ASSERT_EQ(1U, ca_certs.size()); + + // Import Root CA cert and default trust it. + EXPECT_TRUE(cert_db_->ImportCACerts(ca_certs, NSSCertDatabase::TRUST_DEFAULT, + &failed)); + EXPECT_EQ(0U, failed.size()); + + CertificateList intermediate_certs = CreateCertificateListFromFile( + GetTestCertsDirectory(), "2048-rsa-intermediate.pem", + X509Certificate::FORMAT_AUTO); + ASSERT_EQ(1U, intermediate_certs.size()); + + // Import Intermediate CA cert and trust it. + EXPECT_TRUE(cert_db_->ImportCACerts(intermediate_certs, + NSSCertDatabase::TRUSTED_SSL, &failed)); + EXPECT_EQ(0U, failed.size()); + + CertificateList certs = CreateCertificateListFromFile( + GetTestCertsDirectory(), "2048-rsa-ee-by-2048-rsa-intermediate.pem", + X509Certificate::FORMAT_AUTO); + ASSERT_EQ(1U, certs.size()); + + // Import server cert with default trust. + EXPECT_TRUE(cert_db_->ImportServerCert( + certs, NSSCertDatabase::TRUST_DEFAULT, &failed)); + EXPECT_EQ(0U, failed.size()); + EXPECT_EQ(NSSCertDatabase::TRUST_DEFAULT, + cert_db_->GetCertTrust(certs[0], SERVER_CERT)); + + // Server cert should verify. + scoped_refptr<CertVerifyProc> verify_proc(new CertVerifyProcNSS()); + int flags = 0; + CertVerifyResult verify_result; + int error = verify_proc->Verify(certs[0], "127.0.0.1", flags, + NULL, empty_cert_list_, &verify_result); + EXPECT_EQ(OK, error); + EXPECT_EQ(0U, verify_result.cert_status); + + // Without explicit trust of the intermediate, verification should fail. + EXPECT_TRUE(cert_db_->SetCertTrust( + intermediate_certs[0], CA_CERT, NSSCertDatabase::TRUST_DEFAULT)); + + // Server cert should fail to verify. + CertVerifyResult verify_result2; + error = verify_proc->Verify(certs[0], "127.0.0.1", flags, + NULL, empty_cert_list_, &verify_result2); + EXPECT_EQ(ERR_CERT_AUTHORITY_INVALID, error); + EXPECT_EQ(CERT_STATUS_AUTHORITY_INVALID, verify_result2.cert_status); +} + +TEST_F(CertDatabaseNSSTest, TrustIntermediateCa4) { + // Explicit distrust only works starting in NSS 3.13. + if (!NSS_VersionCheck("3.13")) { + LOG(INFO) << "test skipped on NSS < 3.13"; + return; + } + + NSSCertDatabase::ImportCertFailureList failed; + + CertificateList ca_certs = CreateCertificateListFromFile( + GetTestCertsDirectory(), "2048-rsa-root.pem", + X509Certificate::FORMAT_AUTO); + ASSERT_EQ(1U, ca_certs.size()); + + // Import Root CA cert and trust it. + EXPECT_TRUE(cert_db_->ImportCACerts(ca_certs, NSSCertDatabase::TRUSTED_SSL, + &failed)); + EXPECT_EQ(0U, failed.size()); + + CertificateList intermediate_certs = CreateCertificateListFromFile( + GetTestCertsDirectory(), "2048-rsa-intermediate.pem", + X509Certificate::FORMAT_AUTO); + ASSERT_EQ(1U, intermediate_certs.size()); + + // Import Intermediate CA cert and distrust it. + EXPECT_TRUE(cert_db_->ImportCACerts( + intermediate_certs, NSSCertDatabase::DISTRUSTED_SSL, &failed)); + EXPECT_EQ(0U, failed.size()); + + CertificateList certs = CreateCertificateListFromFile( + GetTestCertsDirectory(), "2048-rsa-ee-by-2048-rsa-intermediate.pem", + X509Certificate::FORMAT_AUTO); + ASSERT_EQ(1U, certs.size()); + + // Import server cert with default trust. + EXPECT_TRUE(cert_db_->ImportServerCert( + certs, NSSCertDatabase::TRUST_DEFAULT, &failed)); + EXPECT_EQ(0U, failed.size()); + EXPECT_EQ(NSSCertDatabase::TRUST_DEFAULT, + cert_db_->GetCertTrust(certs[0], SERVER_CERT)); + + // Server cert should not verify. + scoped_refptr<CertVerifyProc> verify_proc(new CertVerifyProcNSS()); + int flags = 0; + CertVerifyResult verify_result; + int error = verify_proc->Verify(certs[0], "127.0.0.1", flags, + NULL, empty_cert_list_, &verify_result); + EXPECT_EQ(ERR_CERT_REVOKED, error); + EXPECT_EQ(CERT_STATUS_REVOKED, verify_result.cert_status); + + // Without explicit distrust of the intermediate, verification should succeed. + EXPECT_TRUE(cert_db_->SetCertTrust( + intermediate_certs[0], CA_CERT, NSSCertDatabase::TRUST_DEFAULT)); + + // Server cert should verify. + CertVerifyResult verify_result2; + error = verify_proc->Verify(certs[0], "127.0.0.1", flags, + NULL, empty_cert_list_, &verify_result2); + EXPECT_EQ(OK, error); + EXPECT_EQ(0U, verify_result2.cert_status); +} + +} // namespace net diff --git a/net/cert/pem_tokenizer.cc b/net/cert/pem_tokenizer.cc new file mode 100644 index 0000000..bb64255 --- /dev/null +++ b/net/cert/pem_tokenizer.cc @@ -0,0 +1,105 @@ +// Copyright (c) 2010 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 "net/cert/pem_tokenizer.h" + +#include "base/base64.h" +#include "base/string_util.h" +#include "base/stringprintf.h" + +namespace { + +const char kPEMSearchBlock[] = "-----BEGIN "; +const char kPEMBeginBlock[] = "-----BEGIN %s-----"; +const char kPEMEndBlock[] = "-----END %s-----"; + +} // namespace + +namespace net { + +using base::StringPiece; + +struct PEMTokenizer::PEMType { + std::string type; + std::string header; + std::string footer; +}; + +PEMTokenizer::PEMTokenizer( + const StringPiece& str, + const std::vector<std::string>& allowed_block_types) { + Init(str, allowed_block_types); +} + +PEMTokenizer::~PEMTokenizer() { +} + +bool PEMTokenizer::GetNext() { + while (pos_ != StringPiece::npos) { + // Scan for the beginning of the next PEM encoded block. + pos_ = str_.find(kPEMSearchBlock, pos_); + if (pos_ == StringPiece::npos) + return false; // No more PEM blocks + + std::vector<PEMType>::const_iterator it; + // Check to see if it is of an acceptable block type. + for (it = block_types_.begin(); it != block_types_.end(); ++it) { + if (!str_.substr(pos_).starts_with(it->header)) + continue; + + // Look for a footer matching the header. If none is found, then all + // data following this point is invalid and should not be parsed. + StringPiece::size_type footer_pos = str_.find(it->footer, pos_); + if (footer_pos == StringPiece::npos) { + pos_ = StringPiece::npos; + return false; + } + + // Chop off the header and footer and parse the data in between. + StringPiece::size_type data_begin = pos_ + it->header.size(); + pos_ = footer_pos + it->footer.size(); + block_type_ = it->type; + + StringPiece encoded = str_.substr(data_begin, + footer_pos - data_begin); + if (!base::Base64Decode(CollapseWhitespaceASCII(encoded.as_string(), + true), &data_)) { + // The most likely cause for a decode failure is a datatype that + // includes PEM headers, which are not supported. + break; + } + + return true; + } + + // If the block did not match any acceptable type, move past it and + // continue the search. Otherwise, |pos_| has been updated to the most + // appropriate search position to continue searching from and should not + // be adjusted. + if (it == block_types_.end()) + pos_ += sizeof(kPEMSearchBlock); + } + + return false; +} + +void PEMTokenizer::Init( + const StringPiece& str, + const std::vector<std::string>& allowed_block_types) { + str_ = str; + pos_ = 0; + + // Construct PEM header/footer strings for all the accepted types, to + // reduce parsing later. + for (std::vector<std::string>::const_iterator it = + allowed_block_types.begin(); it != allowed_block_types.end(); ++it) { + PEMType allowed_type; + allowed_type.type = *it; + allowed_type.header = base::StringPrintf(kPEMBeginBlock, it->c_str()); + allowed_type.footer = base::StringPrintf(kPEMEndBlock, it->c_str()); + block_types_.push_back(allowed_type); + } +} + +} // namespace net diff --git a/net/cert/pem_tokenizer.h b/net/cert/pem_tokenizer.h new file mode 100644 index 0000000..13d2817 --- /dev/null +++ b/net/cert/pem_tokenizer.h @@ -0,0 +1,77 @@ +// Copyright (c) 2011 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. + +#ifndef NET_CERT_PEM_TOKENIZER_H_ +#define NET_CERT_PEM_TOKENIZER_H_ + +#include <string> +#include <vector> + +#include "base/string_piece.h" +#include "net/base/net_export.h" + +namespace net { + +// PEMTokenizer is a utility class for the parsing of data encapsulated +// using RFC 1421, Privacy Enhancement for Internet Electronic Mail. It +// does not implement the full specification, most notably it does not +// support the Encapsulated Header Portion described in Section 4.4. +class NET_EXPORT_PRIVATE PEMTokenizer { + public: + // Create a new PEMTokenizer that iterates through |str| searching for + // instances of PEM encoded blocks that are of the |allowed_block_types|. + // |str| must remain valid for the duration of the PEMTokenizer. + PEMTokenizer(const base::StringPiece& str, + const std::vector<std::string>& allowed_block_types); + ~PEMTokenizer(); + + // Attempts to decode the next PEM block in the string. Returns false if no + // PEM blocks can be decoded. The decoded PEM block will be available via + // data(). + bool GetNext(); + + // Returns the PEM block type (eg: CERTIFICATE) of the last successfully + // decoded PEM block. + // GetNext() must have returned true before calling this method. + const std::string& block_type() const { return block_type_; } + + // Returns the raw, Base64-decoded data of the last successfully decoded + // PEM block. + // GetNext() must have returned true before calling this method. + const std::string& data() const { return data_; } + + private: + void Init(const base::StringPiece& str, + const std::vector<std::string>& allowed_block_types); + + // A simple cache of the allowed PEM header and footer for a given PEM + // block type, so that it is only computed once. + struct PEMType; + + // The string to search, which must remain valid for as long as this class + // is around. + base::StringPiece str_; + + // The current position within |str_| that searching should begin from, + // or StringPiece::npos if iteration is complete + base::StringPiece::size_type pos_; + + // The type of data that was encoded, as indicated in the PEM + // Pre-Encapsulation Boundary (eg: CERTIFICATE, PKCS7, or + // PRIVACY-ENHANCED MESSAGE). + std::string block_type_; + + // The types of PEM blocks that are allowed. PEM blocks that are not of + // one of these types will be skipped. + std::vector<PEMType> block_types_; + + // The raw (Base64-decoded) data of the last successfully decoded block. + std::string data_; + + DISALLOW_COPY_AND_ASSIGN(PEMTokenizer); +}; + +} // namespace net + +#endif // NET_CERT_PEM_TOKENIZER_H_ diff --git a/net/cert/pem_tokenizer_unittest.cc b/net/cert/pem_tokenizer_unittest.cc new file mode 100644 index 0000000..d5334db --- /dev/null +++ b/net/cert/pem_tokenizer_unittest.cc @@ -0,0 +1,169 @@ +// Copyright (c) 2010 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 "net/cert/pem_tokenizer.h" + +#include "testing/gtest/include/gtest/gtest.h" + +namespace net { + +TEST(PEMTokenizerTest, BasicParsing) { + const char data[] = + "-----BEGIN EXPECTED-BLOCK-----\n" + "TWF0Y2hlc0FjY2VwdGVkQmxvY2tUeXBl\n" + "-----END EXPECTED-BLOCK-----\n"; + base::StringPiece string_piece(data); + std::vector<std::string> accepted_types; + accepted_types.push_back("EXPECTED-BLOCK"); + + PEMTokenizer tokenizer(string_piece, accepted_types); + EXPECT_TRUE(tokenizer.GetNext()); + + EXPECT_EQ("EXPECTED-BLOCK", tokenizer.block_type()); + EXPECT_EQ("MatchesAcceptedBlockType", tokenizer.data()); + + EXPECT_FALSE(tokenizer.GetNext()); +} + +TEST(PEMTokenizerTest, CarriageReturnLineFeeds) { + const char data[] = + "-----BEGIN EXPECTED-BLOCK-----\r\n" + "TWF0Y2hlc0FjY2VwdGVkQmxvY2tUeXBl\r\n" + "-----END EXPECTED-BLOCK-----\r\n"; + base::StringPiece string_piece(data); + std::vector<std::string> accepted_types; + accepted_types.push_back("EXPECTED-BLOCK"); + + PEMTokenizer tokenizer(string_piece, accepted_types); + EXPECT_TRUE(tokenizer.GetNext()); + + EXPECT_EQ("EXPECTED-BLOCK", tokenizer.block_type()); + EXPECT_EQ("MatchesAcceptedBlockType", tokenizer.data()); + + EXPECT_FALSE(tokenizer.GetNext()); +} + +TEST(PEMTokenizerTest, NoAcceptedBlockTypes) { + const char data[] = + "-----BEGIN UNEXPECTED-BLOCK-----\n" + "SWdub3Jlc1JlamVjdGVkQmxvY2tUeXBl\n" + "-----END UNEXPECTED-BLOCK-----\n"; + base::StringPiece string_piece(data); + std::vector<std::string> accepted_types; + accepted_types.push_back("EXPECTED-BLOCK"); + + PEMTokenizer tokenizer(string_piece, accepted_types); + EXPECT_FALSE(tokenizer.GetNext()); +} + +TEST(PEMTokenizerTest, MultipleAcceptedBlockTypes) { + const char data[] = + "-----BEGIN BLOCK-ONE-----\n" + "RW5jb2RlZERhdGFPbmU=\n" + "-----END BLOCK-ONE-----\n" + "-----BEGIN BLOCK-TWO-----\n" + "RW5jb2RlZERhdGFUd28=\n" + "-----END BLOCK-TWO-----\n"; + base::StringPiece string_piece(data); + std::vector<std::string> accepted_types; + accepted_types.push_back("BLOCK-ONE"); + accepted_types.push_back("BLOCK-TWO"); + + PEMTokenizer tokenizer(string_piece, accepted_types); + EXPECT_TRUE(tokenizer.GetNext()); + + EXPECT_EQ("BLOCK-ONE", tokenizer.block_type()); + EXPECT_EQ("EncodedDataOne", tokenizer.data()); + + EXPECT_TRUE(tokenizer.GetNext()); + + EXPECT_EQ("BLOCK-TWO", tokenizer.block_type()); + EXPECT_EQ("EncodedDataTwo", tokenizer.data()); + + EXPECT_FALSE(tokenizer.GetNext()); +} + +TEST(PEMTokenizerTest, MissingFooter) { + const char data[] = + "-----BEGIN MISSING-FOOTER-----\n" + "RW5jb2RlZERhdGFPbmU=\n" + "-----END MISSING-FOOTER-----\n" + "-----BEGIN MISSING-FOOTER-----\n" + "RW5jb2RlZERhdGFUd28=\n"; + base::StringPiece string_piece(data); + std::vector<std::string> accepted_types; + accepted_types.push_back("MISSING-FOOTER"); + + PEMTokenizer tokenizer(string_piece, accepted_types); + EXPECT_TRUE(tokenizer.GetNext()); + + EXPECT_EQ("MISSING-FOOTER", tokenizer.block_type()); + EXPECT_EQ("EncodedDataOne", tokenizer.data()); + + EXPECT_FALSE(tokenizer.GetNext()); +} + +TEST(PEMTokenizerTest, NestedEncoding) { + const char data[] = + "-----BEGIN BLOCK-ONE-----\n" + "RW5jb2RlZERhdGFPbmU=\n" + "-----BEGIN BLOCK-TWO-----\n" + "RW5jb2RlZERhdGFUd28=\n" + "-----END BLOCK-TWO-----\n" + "-----END BLOCK-ONE-----\n" + "-----BEGIN BLOCK-ONE-----\n" + "RW5jb2RlZERhdGFUaHJlZQ==\n" + "-----END BLOCK-ONE-----\n"; + base::StringPiece string_piece(data); + std::vector<std::string> accepted_types; + accepted_types.push_back("BLOCK-ONE"); + + PEMTokenizer tokenizer(string_piece, accepted_types); + EXPECT_TRUE(tokenizer.GetNext()); + + EXPECT_EQ("BLOCK-ONE", tokenizer.block_type()); + EXPECT_EQ("EncodedDataThree", tokenizer.data()); + + EXPECT_FALSE(tokenizer.GetNext()); +} + +TEST(PEMTokenizerTest, EmptyAcceptedTypes) { + const char data[] = + "-----BEGIN BLOCK-ONE-----\n" + "RW5jb2RlZERhdGFPbmU=\n" + "-----END BLOCK-ONE-----\n"; + base::StringPiece string_piece(data); + std::vector<std::string> accepted_types; + + PEMTokenizer tokenizer(string_piece, accepted_types); + EXPECT_FALSE(tokenizer.GetNext()); +} + +TEST(PEMTokenizerTest, BlockWithHeader) { + const char data[] = + "-----BEGIN BLOCK-ONE-----\n" + "Header-One: Data data data\n" + "Header-Two: \n" + " continuation\n" + "Header-Three: Mix-And,Match\n" + "\n" + "RW5jb2RlZERhdGFPbmU=\n" + "-----END BLOCK-ONE-----\n" + "-----BEGIN BLOCK-ONE-----\n" + "RW5jb2RlZERhdGFUd28=\n" + "-----END BLOCK-ONE-----\n"; + base::StringPiece string_piece(data); + std::vector<std::string> accepted_types; + accepted_types.push_back("BLOCK-ONE"); + + PEMTokenizer tokenizer(string_piece, accepted_types); + EXPECT_TRUE(tokenizer.GetNext()); + + EXPECT_EQ("BLOCK-ONE", tokenizer.block_type()); + EXPECT_EQ("EncodedDataTwo", tokenizer.data()); + + EXPECT_FALSE(tokenizer.GetNext()); +} + +} // namespace net diff --git a/net/cert/single_request_cert_verifier.cc b/net/cert/single_request_cert_verifier.cc new file mode 100644 index 0000000..909af07 --- /dev/null +++ b/net/cert/single_request_cert_verifier.cc @@ -0,0 +1,70 @@ +// Copyright (c) 2012 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 "net/cert/single_request_cert_verifier.h" + +#include "base/bind.h" +#include "base/bind_helpers.h" +#include "net/base/net_errors.h" +#include "net/cert/x509_certificate.h" + +namespace net { + +SingleRequestCertVerifier::SingleRequestCertVerifier( + CertVerifier* cert_verifier) + : cert_verifier_(cert_verifier), + cur_request_(NULL) { + DCHECK(cert_verifier_ != NULL); +} + +SingleRequestCertVerifier::~SingleRequestCertVerifier() { + if (cur_request_) { + cert_verifier_->CancelRequest(cur_request_); + cur_request_ = NULL; + } +} + +int SingleRequestCertVerifier::Verify(X509Certificate* cert, + const std::string& hostname, + int flags, + CRLSet* crl_set, + CertVerifyResult* verify_result, + const CompletionCallback& callback, + const BoundNetLog& net_log) { + // Should not be already in use. + DCHECK(!cur_request_ && cur_request_callback_.is_null()); + + CertVerifier::RequestHandle request = NULL; + + // We need to be notified of completion before |callback| is called, so that + // we can clear out |cur_request_*|. + int rv = cert_verifier_->Verify( + cert, hostname, flags, crl_set, verify_result, + base::Bind(&SingleRequestCertVerifier::OnVerifyCompletion, + base::Unretained(this)), + &request, net_log); + + if (rv == ERR_IO_PENDING) { + // Cleared in OnVerifyCompletion(). + cur_request_ = request; + cur_request_callback_ = callback; + } + + return rv; +} + +void SingleRequestCertVerifier::OnVerifyCompletion(int result) { + DCHECK(cur_request_ && !cur_request_callback_.is_null()); + + CompletionCallback callback = cur_request_callback_; + + // Clear the outstanding request information. + cur_request_ = NULL; + cur_request_callback_.Reset(); + + // Call the user's original callback. + callback.Run(result); +} + +} // namespace net diff --git a/net/cert/single_request_cert_verifier.h b/net/cert/single_request_cert_verifier.h new file mode 100644 index 0000000..6b19281 --- /dev/null +++ b/net/cert/single_request_cert_verifier.h @@ -0,0 +1,52 @@ +// Copyright (c) 2012 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. + +#ifndef NET_CERT_SINGLE_REQUEST_CERT_VERIFIER_H_ +#define NET_CERT_SINGLE_REQUEST_CERT_VERIFIER_H_ + +#include "net/cert/cert_verifier.h" + +namespace net { + +// This class represents the task of verifying a certificate. It wraps +// CertVerifier to verify only a single certificate at a time and cancels this +// request when going out of scope. +class SingleRequestCertVerifier { + public: + // |cert_verifier| must remain valid for the lifetime of |this|. + explicit SingleRequestCertVerifier(CertVerifier* cert_verifier); + + // If a completion callback is pending when the verifier is destroyed, the + // certificate verification is canceled, and the completion callback will + // not be called. + ~SingleRequestCertVerifier(); + + // Verifies the given certificate, filling out the |verify_result| object + // upon success. See CertVerifier::Verify() for details. + int Verify(X509Certificate* cert, + const std::string& hostname, + int flags, + CRLSet* crl_set, + CertVerifyResult* verify_result, + const CompletionCallback& callback, + const BoundNetLog& net_log); + + private: + // Callback for when the request to |cert_verifier_| completes, so we + // dispatch to the user's callback. + void OnVerifyCompletion(int result); + + // The actual certificate verifier that will handle the request. + CertVerifier* const cert_verifier_; + + // The current request (if any). + CertVerifier::RequestHandle cur_request_; + CompletionCallback cur_request_callback_; + + DISALLOW_COPY_AND_ASSIGN(SingleRequestCertVerifier); +}; + +} // namespace net + +#endif // NET_CERT_SINGLE_REQUEST_CERT_VERIFIER_H_ diff --git a/net/cert/test_root_certs.cc b/net/cert/test_root_certs.cc new file mode 100644 index 0000000..3c77ea5 --- /dev/null +++ b/net/cert/test_root_certs.cc @@ -0,0 +1,76 @@ +// Copyright (c) 2012 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 "net/cert/test_root_certs.h" + +#include <string> + +#include "base/file_util.h" +#include "base/files/file_path.h" +#include "base/logging.h" +#include "net/cert/x509_certificate.h" + +namespace net { + +namespace { + +bool g_has_instance = false; + +base::LazyInstance<TestRootCerts>::Leaky + g_test_root_certs = LAZY_INSTANCE_INITIALIZER; + +CertificateList LoadCertificates(const base::FilePath& filename) { + std::string raw_cert; + if (!file_util::ReadFileToString(filename, &raw_cert)) { + LOG(ERROR) << "Can't load certificate " << filename.value(); + return CertificateList(); + } + + return X509Certificate::CreateCertificateListFromBytes( + raw_cert.data(), raw_cert.length(), X509Certificate::FORMAT_AUTO); +} + +} // namespace + +// static +TestRootCerts* TestRootCerts::GetInstance() { + return g_test_root_certs.Pointer(); +} + +bool TestRootCerts::HasInstance() { + return g_has_instance; +} + +bool TestRootCerts::AddFromFile(const base::FilePath& file) { + CertificateList root_certs = LoadCertificates(file); + if (root_certs.empty() || root_certs.size() > 1) + return false; + + return Add(root_certs.front()); +} + +TestRootCerts::TestRootCerts() { + Init(); + g_has_instance = true; +} + +ScopedTestRoot::ScopedTestRoot() {} + +ScopedTestRoot::ScopedTestRoot(X509Certificate* cert) { + Reset(cert); +} + +ScopedTestRoot::~ScopedTestRoot() { + Reset(NULL); +} + +void ScopedTestRoot::Reset(X509Certificate* cert) { + if (cert_) + TestRootCerts::GetInstance()->Clear(); + if (cert) + TestRootCerts::GetInstance()->Add(cert); + cert_ = cert; +} + +} // namespace net diff --git a/net/cert/test_root_certs.h b/net/cert/test_root_certs.h new file mode 100644 index 0000000..543adbd --- /dev/null +++ b/net/cert/test_root_certs.h @@ -0,0 +1,127 @@ +// Copyright (c) 2012 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. + +#ifndef NET_CERT_TEST_ROOT_CERTS_H_ +#define NET_CERT_TEST_ROOT_CERTS_H_ + +#include "base/lazy_instance.h" +#include "base/memory/ref_counted.h" +#include "build/build_config.h" +#include "net/base/net_export.h" + +#if defined(USE_NSS) || defined(OS_IOS) +#include <list> +#elif defined(OS_WIN) +#include <windows.h> +#include <wincrypt.h> +#elif defined(OS_MACOSX) +#include <CoreFoundation/CFArray.h> +#include <Security/SecTrust.h> +#include "base/mac/scoped_cftyperef.h" +#endif + +namespace base { +class FilePath; +} + +namespace net { + +class X509Certificate; + +// TestRootCerts is a helper class for unit tests that is used to +// artificially mark a certificate as trusted, independent of the local +// machine configuration. +class NET_EXPORT_PRIVATE TestRootCerts { + public: + // Obtains the Singleton instance to the trusted certificates. + static TestRootCerts* GetInstance(); + + // Returns true if an instance exists, without forcing an initialization. + static bool HasInstance(); + + // Marks |certificate| as trusted for X509Certificate::Verify(). Returns + // false if the certificate could not be marked trusted. + bool Add(X509Certificate* certificate); + + // Reads a single certificate from |file| and marks it as trusted. Returns + // false if an error is encountered, such as being unable to read |file| + // or more than one certificate existing in |file|. + bool AddFromFile(const base::FilePath& file); + + // Clears the trusted status of any certificates that were previously + // marked trusted via Add(). + void Clear(); + + // Returns true if there are no certificates that have been marked trusted. + bool IsEmpty() const; + +#if defined(OS_MACOSX) && !defined(OS_IOS) + CFArrayRef temporary_roots() const { return temporary_roots_; } + + // Modifies the root certificates of |trust_ref| to include the + // certificates stored in |temporary_roots_|. If IsEmpty() is true, this + // does not modify |trust_ref|. + OSStatus FixupSecTrustRef(SecTrustRef trust_ref) const; +#elif defined(OS_WIN) + HCERTSTORE temporary_roots() const { return temporary_roots_; } + + // Returns an HCERTCHAINENGINE suitable to be used for certificate + // validation routines, or NULL to indicate that the default system chain + // engine is appropriate. The caller is responsible for freeing the + // returned HCERTCHAINENGINE. + HCERTCHAINENGINE GetChainEngine() const; +#endif + + private: + friend struct base::DefaultLazyInstanceTraits<TestRootCerts>; + + TestRootCerts(); + ~TestRootCerts(); + + // Performs platform-dependent initialization. + void Init(); + +#if defined(USE_NSS) || defined(OS_IOS) + // It is necessary to maintain a cache of the original certificate trust + // settings, in order to restore them when Clear() is called. + class TrustEntry; + std::list<TrustEntry*> trust_cache_; +#elif defined(OS_WIN) + HCERTSTORE temporary_roots_; +#elif defined(OS_MACOSX) + base::mac::ScopedCFTypeRef<CFMutableArrayRef> temporary_roots_; +#endif + +#if defined(OS_WIN) || defined(USE_OPENSSL) + // True if there are no temporarily trusted root certificates. + bool empty_; +#endif + + DISALLOW_COPY_AND_ASSIGN(TestRootCerts); +}; + +// Scoped helper for unittests to handle safely managing trusted roots. +class NET_EXPORT_PRIVATE ScopedTestRoot { + public: + ScopedTestRoot(); + // Creates a ScopedTestRoot that will adds|cert| to the TestRootCerts store. + explicit ScopedTestRoot(X509Certificate* cert); + ~ScopedTestRoot(); + + // Assigns |cert| to be the new test root cert. If |cert| is NULL, undoes + // any work the ScopedTestRoot may have previously done. + // If |cert_| contains a certificate (due to a prior call to Reset or due to + // a cert being passed at construction), the existing TestRootCerts store is + // cleared. + void Reset(X509Certificate* cert); + + private: + scoped_refptr<X509Certificate> cert_; + + DISALLOW_COPY_AND_ASSIGN(ScopedTestRoot); +}; + +} // namespace net + +#endif // NET_CERT_TEST_ROOT_CERTS_H_ diff --git a/net/cert/test_root_certs_android.cc b/net/cert/test_root_certs_android.cc new file mode 100644 index 0000000..a7b80c4 --- /dev/null +++ b/net/cert/test_root_certs_android.cc @@ -0,0 +1,42 @@ +// Copyright (c) 2011 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 "net/cert/test_root_certs.h" + +#include "base/location.h" +#include "base/logging.h" +#include "net/android/network_library.h" +#include "net/cert/x509_certificate.h" + +namespace net { + +bool TestRootCerts::Add(X509Certificate* certificate) { + std::string cert_bytes; + if (!X509Certificate::GetDEREncoded(certificate->os_cert_handle(), + &cert_bytes)) + return false; + android::AddTestRootCertificate( + reinterpret_cast<const uint8*>(cert_bytes.data()), cert_bytes.size()); + return true; +} + +void TestRootCerts::Clear() { + if (empty_) + return; + + android::ClearTestRootCertificates(); + empty_ = true; +} + +bool TestRootCerts::IsEmpty() const { + return empty_; +} + +TestRootCerts::~TestRootCerts() {} + +void TestRootCerts::Init() { + empty_ = true; +} + +} // namespace net diff --git a/net/cert/test_root_certs_mac.cc b/net/cert/test_root_certs_mac.cc new file mode 100644 index 0000000..475fb24 --- /dev/null +++ b/net/cert/test_root_certs_mac.cc @@ -0,0 +1,127 @@ +// Copyright (c) 2012 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 "net/cert/test_root_certs.h" + +#include <Security/Security.h> + +#include "base/logging.h" +#include "base/mac/scoped_cftyperef.h" +#include "net/cert/x509_certificate.h" + +namespace net { + +namespace { + +typedef OSStatus (*SecTrustSetAnchorCertificatesOnlyFuncPtr)(SecTrustRef, + Boolean); + +Boolean OurSecCertificateEqual(const void* value1, const void* value2) { + if (CFGetTypeID(value1) != SecCertificateGetTypeID() || + CFGetTypeID(value2) != SecCertificateGetTypeID()) + return CFEqual(value1, value2); + return X509Certificate::IsSameOSCert( + reinterpret_cast<SecCertificateRef>(const_cast<void*>(value1)), + reinterpret_cast<SecCertificateRef>(const_cast<void*>(value2))); +} + +const void* RetainWrapper(CFAllocatorRef unused, const void* value) { + return CFRetain(value); +} + +void ReleaseWrapper(CFAllocatorRef unused, const void* value) { + CFRelease(value); +} + +// CFEqual prior to 10.6 only performed pointer checks on SecCertificateRefs, +// rather than checking if they were the same (logical) certificate, so a +// custom structure is used for the array callbacks. +const CFArrayCallBacks kCertArrayCallbacks = { + 0, // version + RetainWrapper, + ReleaseWrapper, + CFCopyDescription, + OurSecCertificateEqual, +}; + +} // namespace + +bool TestRootCerts::Add(X509Certificate* certificate) { + if (CFArrayContainsValue(temporary_roots_, + CFRangeMake(0, CFArrayGetCount(temporary_roots_)), + certificate->os_cert_handle())) + return true; + CFArrayAppendValue(temporary_roots_, certificate->os_cert_handle()); + return true; +} + +void TestRootCerts::Clear() { + CFArrayRemoveAllValues(temporary_roots_); +} + +bool TestRootCerts::IsEmpty() const { + return CFArrayGetCount(temporary_roots_) == 0; +} + +OSStatus TestRootCerts::FixupSecTrustRef(SecTrustRef trust_ref) const { + if (IsEmpty()) + return noErr; + + CFBundleRef bundle = + CFBundleGetBundleWithIdentifier(CFSTR("com.apple.security")); + SecTrustSetAnchorCertificatesOnlyFuncPtr set_anchor_certificates_only = NULL; + if (bundle) { + set_anchor_certificates_only = + reinterpret_cast<SecTrustSetAnchorCertificatesOnlyFuncPtr>( + CFBundleGetFunctionPointerForName(bundle, + CFSTR("SecTrustSetAnchorCertificatesOnly"))); + } + + OSStatus status = noErr; + if (set_anchor_certificates_only) { + // OS X 10.6 includes a function where the system trusts can be + // preserved while appending application trusts. This is preferable, + // because it preserves any user trust settings (explicit distrust), + // which the naive copy in 10.5 does not. Unfortunately, though the + // function pointer may be available, it is not always implemented. If it + // returns errSecUnimplemented, fall through to the 10.5 behaviour. + status = SecTrustSetAnchorCertificates(trust_ref, temporary_roots_); + if (status) + return status; + status = set_anchor_certificates_only(trust_ref, false); + if (status != errSecUnimplemented) + return status; + + // Restore the original settings before falling back. + status = SecTrustSetAnchorCertificates(trust_ref, NULL); + if (status) + return status; + } + + // On 10.5, the system certificates have to be copied and merged into + // the application trusts, and may override any user trust settings. + CFArrayRef system_roots = NULL; + status = SecTrustCopyAnchorCertificates(&system_roots); + if (status) + return status; + + base::mac::ScopedCFTypeRef<CFArrayRef> scoped_system_roots(system_roots); + base::mac::ScopedCFTypeRef<CFMutableArrayRef> scoped_roots( + CFArrayCreateMutableCopy(kCFAllocatorDefault, 0, + scoped_system_roots)); + DCHECK(scoped_roots.get()); + + CFArrayAppendArray(scoped_roots, temporary_roots_, + CFRangeMake(0, CFArrayGetCount(temporary_roots_))); + return SecTrustSetAnchorCertificates(trust_ref, scoped_roots); +} + +TestRootCerts::~TestRootCerts() {} + +void TestRootCerts::Init() { + temporary_roots_.reset(CFArrayCreateMutable(kCFAllocatorDefault, 0, + &kCertArrayCallbacks)); +} + +} // namespace net diff --git a/net/cert/test_root_certs_nss.cc b/net/cert/test_root_certs_nss.cc new file mode 100644 index 0000000..3a2f88a --- /dev/null +++ b/net/cert/test_root_certs_nss.cc @@ -0,0 +1,125 @@ +// Copyright (c) 2011 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 "net/cert/test_root_certs.h" + +#include <cert.h> + +#include "base/logging.h" +#include "base/stl_util.h" +#include "crypto/nss_util.h" +#include "net/cert/x509_certificate.h" + +#if defined(OS_IOS) +#include "net/cert/x509_util_ios.h" +#endif + +namespace net { + +// TrustEntry is used to store the original CERTCertificate and CERTCertTrust +// for a certificate whose trust status has been changed by the +// TestRootCerts. +class TestRootCerts::TrustEntry { + public: + // Creates a new TrustEntry by incrementing the reference to |certificate| + // and copying |trust|. + TrustEntry(CERTCertificate* certificate, const CERTCertTrust& trust); + ~TrustEntry(); + + CERTCertificate* certificate() const { return certificate_; } + const CERTCertTrust& trust() const { return trust_; } + + private: + // The temporary root certificate. + CERTCertificate* certificate_; + + // The original trust settings, before |certificate_| was manipulated to + // be a temporarily trusted root. + CERTCertTrust trust_; + + DISALLOW_COPY_AND_ASSIGN(TrustEntry); +}; + +TestRootCerts::TrustEntry::TrustEntry(CERTCertificate* certificate, + const CERTCertTrust& trust) + : certificate_(CERT_DupCertificate(certificate)), + trust_(trust) { +} + +TestRootCerts::TrustEntry::~TrustEntry() { + CERT_DestroyCertificate(certificate_); +} + +bool TestRootCerts::Add(X509Certificate* certificate) { +#if defined(OS_IOS) + x509_util_ios::NSSCertificate nss_certificate(certificate->os_cert_handle()); + CERTCertificate* cert_handle = nss_certificate.cert_handle(); +#else + CERTCertificate* cert_handle = certificate->os_cert_handle(); +#endif + // Preserve the original trust bits so that they can be restored when + // the certificate is removed. + CERTCertTrust original_trust; + SECStatus rv = CERT_GetCertTrust(cert_handle, &original_trust); + if (rv != SECSuccess) { + // CERT_GetCertTrust will fail if the certificate does not have any + // particular trust settings associated with it, and attempts to use + // |original_trust| later to restore the original trust settings will not + // cause the trust settings to be revoked. If the certificate has no + // particular trust settings associated with it, mark the certificate as + // a valid CA certificate with no specific trust. + rv = CERT_DecodeTrustString(&original_trust, "c,c,c"); + } + + // Change the trust bits to unconditionally trust this certificate. + CERTCertTrust new_trust; + rv = CERT_DecodeTrustString(&new_trust, "TCu,Cu,Tu"); + if (rv != SECSuccess) { + LOG(ERROR) << "Cannot decode certificate trust string."; + return false; + } + + rv = CERT_ChangeCertTrust(CERT_GetDefaultCertDB(), cert_handle, &new_trust); + if (rv != SECSuccess) { + LOG(ERROR) << "Cannot change certificate trust."; + return false; + } + + trust_cache_.push_back(new TrustEntry(cert_handle, original_trust)); + return true; +} + +void TestRootCerts::Clear() { + // Restore the certificate trusts to what they were originally, before + // Add() was called. Work from the rear first, since if a certificate was + // added twice, the second entry's original trust status will be that of + // the first entry, while the first entry contains the desired resultant + // status. + for (std::list<TrustEntry*>::reverse_iterator it = trust_cache_.rbegin(); + it != trust_cache_.rend(); ++it) { + CERTCertTrust original_trust = (*it)->trust(); + SECStatus rv = CERT_ChangeCertTrust(CERT_GetDefaultCertDB(), + (*it)->certificate(), + &original_trust); + // DCHECK(), rather than LOG(), as a failure to restore the original + // trust can cause flake or hard-to-trace errors in any unit tests that + // occur after Clear() has been called. + DCHECK_EQ(SECSuccess, rv) << "Cannot restore certificate trust."; + } + STLDeleteElements(&trust_cache_); +} + +bool TestRootCerts::IsEmpty() const { + return trust_cache_.empty(); +} + +TestRootCerts::~TestRootCerts() { + Clear(); +} + +void TestRootCerts::Init() { + crypto::EnsureNSSInit(); +} + +} // namespace net diff --git a/net/cert/test_root_certs_openssl.cc b/net/cert/test_root_certs_openssl.cc new file mode 100644 index 0000000..3d5cf3d --- /dev/null +++ b/net/cert/test_root_certs_openssl.cc @@ -0,0 +1,51 @@ +// Copyright (c) 2011 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 "net/cert/test_root_certs.h" + +#include <openssl/err.h> +#include <openssl/x509v3.h> + +#include "base/location.h" +#include "base/logging.h" +#include "crypto/openssl_util.h" +#include "net/cert/x509_certificate.h" + +namespace net { + +bool TestRootCerts::Add(X509Certificate* certificate) { + if (!X509_STORE_add_cert(X509Certificate::cert_store(), + certificate->os_cert_handle())) { + unsigned long error_code = ERR_peek_error(); + if (ERR_GET_LIB(error_code) != ERR_LIB_X509 || + ERR_GET_REASON(error_code) != X509_R_CERT_ALREADY_IN_HASH_TABLE) { + crypto::ClearOpenSSLERRStack(FROM_HERE); + return false; + } + ERR_clear_error(); + } + + empty_ = false; + return true; +} + +void TestRootCerts::Clear() { + if (empty_) + return; + + X509Certificate::ResetCertStore(); + empty_ = true; +} + +bool TestRootCerts::IsEmpty() const { + return empty_; +} + +TestRootCerts::~TestRootCerts() {} + +void TestRootCerts::Init() { + empty_ = true; +} + +} // namespace net diff --git a/net/cert/test_root_certs_win.cc b/net/cert/test_root_certs_win.cc new file mode 100644 index 0000000..90a21d6 --- /dev/null +++ b/net/cert/test_root_certs_win.cc @@ -0,0 +1,213 @@ +// Copyright (c) 2012 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 "net/cert/test_root_certs.h" + +#include <windows.h> +#include <wincrypt.h> + +#include "base/basictypes.h" +#include "base/lazy_instance.h" +#include "base/logging.h" +#include "base/win/win_util.h" +#include "base/win/windows_version.h" +#include "net/cert/x509_certificate.h" + +namespace net { + +namespace { + +// Provides a CertDllOpenStoreProv callback provider function, to be called +// by CertOpenStore when the CERT_STORE_PROV_SYSTEM_W store is opened. See +// http://msdn.microsoft.com/en-us/library/aa376043(VS.85).aspx. +BOOL WINAPI InterceptedOpenStoreW(LPCSTR store_provider, + DWORD encoding, + HCRYPTPROV crypt_provider, + DWORD flags, + const void* extra, + HCERTSTORE memory_store, + PCERT_STORE_PROV_INFO store_info); + +// CryptoAPIInjector is used to inject a store provider function for system +// certificate stores before the one provided internally by Crypt32.dll. +// Once injected, there is no way to remove, so every call to open a system +// store will be redirected to the injected function. +struct CryptoAPIInjector { + // The previous default function for opening system stores. For most + // configurations, this should point to Crypt32's internal + // I_CertDllOpenSystemStoreProvW function. + PFN_CERT_DLL_OPEN_STORE_PROV_FUNC original_function; + + // The handle that CryptoAPI uses to ensure the DLL implementing + // |original_function| remains loaded in memory. + HCRYPTOIDFUNCADDR original_handle; + + private: + friend struct base::DefaultLazyInstanceTraits<CryptoAPIInjector>; + + CryptoAPIInjector() + : original_function(NULL), + original_handle(NULL) { + HCRYPTOIDFUNCSET registered_functions = + CryptInitOIDFunctionSet(CRYPT_OID_OPEN_STORE_PROV_FUNC, 0); + + // Preserve the original handler function in |original_function|. If other + // functions are overridden, they will also need to be preserved. + BOOL ok = CryptGetOIDFunctionAddress( + registered_functions, 0, CERT_STORE_PROV_SYSTEM_W, 0, + reinterpret_cast<void**>(&original_function), &original_handle); + DCHECK(ok); + + // For now, intercept only the numeric form of the system store + // function, CERT_STORE_PROV_SYSTEM_W (0x0A), which is what Crypt32 + // functionality uses exclusively. Depending on the machine that tests + // are being run on, it may prove necessary to also intercept + // sz_CERT_STORE_PROV_SYSTEM_[A/W] and CERT_STORE_PROV_SYSTEM_A, based + // on whether or not any third-party CryptoAPI modules have been + // installed. + const CRYPT_OID_FUNC_ENTRY kFunctionToIntercept = + { CERT_STORE_PROV_SYSTEM_W, &InterceptedOpenStoreW }; + + // Inject kFunctionToIntercept at the front of the linked list that + // crypt32 uses when CertOpenStore is called, replacing the existing + // registered function. + ok = CryptInstallOIDFunctionAddress(NULL, 0, + CRYPT_OID_OPEN_STORE_PROV_FUNC, 1, + &kFunctionToIntercept, + CRYPT_INSTALL_OID_FUNC_BEFORE_FLAG); + DCHECK(ok); + } + + // This is never called, because this object is intentionally leaked. + // Certificate verification happens on a non-joinable worker thread, which + // may still be running when ~AtExitManager is called, so the LazyInstance + // must be leaky. + ~CryptoAPIInjector() { + original_function = NULL; + CryptFreeOIDFunctionAddress(original_handle, NULL); + } +}; + +base::LazyInstance<CryptoAPIInjector>::Leaky + g_capi_injector = LAZY_INSTANCE_INITIALIZER; + +BOOL WINAPI InterceptedOpenStoreW(LPCSTR store_provider, + DWORD encoding, + HCRYPTPROV crypt_provider, + DWORD flags, + const void* store_name, + HCERTSTORE memory_store, + PCERT_STORE_PROV_INFO store_info) { + // If the high word is all zeroes, then |store_provider| is a numeric ID. + // Otherwise, it's a pointer to a null-terminated ASCII string. See the + // documentation for CryptGetOIDFunctionAddress for more information. + uint32 store_as_uint = reinterpret_cast<uint32>(store_provider); + if (store_as_uint > 0xFFFF || store_provider != CERT_STORE_PROV_SYSTEM_W || + !g_capi_injector.Get().original_function) + return FALSE; + + BOOL ok = g_capi_injector.Get().original_function(store_provider, encoding, + crypt_provider, flags, + store_name, memory_store, + store_info); + // Only the Root store should have certificates injected. If + // CERT_SYSTEM_STORE_RELOCATE_FLAG is set, then |store_name| points to a + // CERT_SYSTEM_STORE_RELOCATE_PARA structure, rather than a + // NULL-terminated wide string, so check before making a string + // comparison. + if (!ok || TestRootCerts::GetInstance()->IsEmpty() || + (flags & CERT_SYSTEM_STORE_RELOCATE_FLAG) || + lstrcmpiW(reinterpret_cast<LPCWSTR>(store_name), L"root")) + return ok; + + // The result of CertOpenStore with CERT_STORE_PROV_SYSTEM_W is documented + // to be a collection store, and that appears to hold for |memory_store|. + // Attempting to add an individual certificate to |memory_store| causes + // the request to be forwarded to the first physical store in the + // collection that accepts modifications, which will cause a secure + // confirmation dialog to be displayed, confirming the user wishes to + // trust the certificate. However, appending a store to the collection + // will merely modify the temporary collection store, and will not persist + // any changes to the underlying physical store. When the |memory_store| is + // searched to see if a certificate is in the Root store, all the + // underlying stores in the collection will be searched, and any certificate + // in temporary_roots() will be found and seen as trusted. + return CertAddStoreToCollection( + memory_store, TestRootCerts::GetInstance()->temporary_roots(), 0, 0); +} + +} // namespace + +bool TestRootCerts::Add(X509Certificate* certificate) { + // Ensure that the default CryptoAPI functionality has been intercepted. + // If a test certificate is never added, then no interception should + // happen. + g_capi_injector.Get(); + + BOOL ok = CertAddCertificateContextToStore( + temporary_roots_, certificate->os_cert_handle(), + CERT_STORE_ADD_NEW, NULL); + if (!ok) { + // If the certificate is already added, return successfully. + return GetLastError() == CRYPT_E_EXISTS; + } + + empty_ = false; + return true; +} + +void TestRootCerts::Clear() { + empty_ = true; + + PCCERT_CONTEXT prev_cert = NULL; + while (prev_cert = CertEnumCertificatesInStore(temporary_roots_, NULL)) + CertDeleteCertificateFromStore(prev_cert); +} + +bool TestRootCerts::IsEmpty() const { + return empty_; +} + +HCERTCHAINENGINE TestRootCerts::GetChainEngine() const { + if (IsEmpty()) + return NULL; // Default chain engine will suffice. + + // Windows versions before 7 don't accept the struct size for later versions. + // We report the size of the old struct since we don't need the new members. + static const DWORD kSizeofCertChainEngineConfig = + SIZEOF_STRUCT_WITH_SPECIFIED_LAST_MEMBER( + CERT_CHAIN_ENGINE_CONFIG, CycleDetectionModulus); + + // Each HCERTCHAINENGINE caches both the configured system stores and + // information about each chain that has been built. In order to ensure + // that changes to |temporary_roots_| are properly propagated and that the + // various caches are flushed, when at least one certificate is added, + // return a new chain engine for every call. Each chain engine creation + // should re-open the root store, ensuring the most recent changes are + // visible. + CERT_CHAIN_ENGINE_CONFIG engine_config = { + kSizeofCertChainEngineConfig + }; + engine_config.dwFlags = + CERT_CHAIN_ENABLE_CACHE_AUTO_UPDATE | + CERT_CHAIN_ENABLE_SHARE_STORE; + HCERTCHAINENGINE chain_engine = NULL; + BOOL ok = CertCreateCertificateChainEngine(&engine_config, &chain_engine); + DCHECK(ok); + return chain_engine; +} + +TestRootCerts::~TestRootCerts() { + CertCloseStore(temporary_roots_, 0); +} + +void TestRootCerts::Init() { + empty_ = true; + temporary_roots_ = CertOpenStore( + CERT_STORE_PROV_MEMORY, 0, NULL, + CERT_STORE_DEFER_CLOSE_UNTIL_LAST_FREE_FLAG, NULL); + DCHECK(temporary_roots_); +} + +} // namespace net diff --git a/net/cert/x509_cert_types.cc b/net/cert/x509_cert_types.cc new file mode 100644 index 0000000..04dbd6b --- /dev/null +++ b/net/cert/x509_cert_types.cc @@ -0,0 +1,129 @@ +// Copyright (c) 2012 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 "net/cert/x509_cert_types.h" + +#include <cstdlib> +#include <cstring> + +#include "base/logging.h" +#include "base/string_number_conversions.h" +#include "base/string_piece.h" +#include "base/time.h" +#include "net/cert/x509_certificate.h" + +namespace net { + +namespace { + +// Helper for ParseCertificateDate. |*field| must contain at least +// |field_len| characters. |*field| will be advanced by |field_len| on exit. +// |*ok| is set to false if there is an error in parsing the number, but left +// untouched otherwise. Returns the parsed integer. +int ParseIntAndAdvance(const char** field, size_t field_len, bool* ok) { + int result = 0; + *ok &= base::StringToInt(base::StringPiece(*field, field_len), &result); + *field += field_len; + return result; +} + +} + +CertPrincipal::CertPrincipal() { +} + +CertPrincipal::CertPrincipal(const std::string& name) : common_name(name) {} + +CertPrincipal::~CertPrincipal() { +} + +std::string CertPrincipal::GetDisplayName() const { + if (!common_name.empty()) + return common_name; + if (!organization_names.empty()) + return organization_names[0]; + if (!organization_unit_names.empty()) + return organization_unit_names[0]; + + return std::string(); +} + +CertPolicy::CertPolicy() { +} + +CertPolicy::~CertPolicy() { +} + +CertPolicy::Judgment CertPolicy::Check( + X509Certificate* cert) const { + // It shouldn't matter which set we check first, but we check denied first + // in case something strange has happened. + + if (denied_.find(cert->fingerprint()) != denied_.end()) { + // DCHECK that the order didn't matter. + DCHECK(allowed_.find(cert->fingerprint()) == allowed_.end()); + return DENIED; + } + + if (allowed_.find(cert->fingerprint()) != allowed_.end()) { + // DCHECK that the order didn't matter. + DCHECK(denied_.find(cert->fingerprint()) == denied_.end()); + return ALLOWED; + } + + // We don't have a policy for this cert. + return UNKNOWN; +} + +void CertPolicy::Allow(X509Certificate* cert) { + // Put the cert in the allowed set and (maybe) remove it from the denied set. + denied_.erase(cert->fingerprint()); + allowed_.insert(cert->fingerprint()); +} + +void CertPolicy::Deny(X509Certificate* cert) { + // Put the cert in the denied set and (maybe) remove it from the allowed set. + allowed_.erase(cert->fingerprint()); + denied_.insert(cert->fingerprint()); +} + +bool CertPolicy::HasAllowedCert() const { + return !allowed_.empty(); +} + +bool CertPolicy::HasDeniedCert() const { + return !denied_.empty(); +} + +bool ParseCertificateDate(const base::StringPiece& raw_date, + CertDateFormat format, + base::Time* time) { + size_t year_length = format == CERT_DATE_FORMAT_UTC_TIME ? 2 : 4; + + if (raw_date.length() < 11 + year_length) + return false; + + const char* field = raw_date.data(); + bool valid = true; + base::Time::Exploded exploded = {0}; + + exploded.year = ParseIntAndAdvance(&field, year_length, &valid); + exploded.month = ParseIntAndAdvance(&field, 2, &valid); + exploded.day_of_month = ParseIntAndAdvance(&field, 2, &valid); + exploded.hour = ParseIntAndAdvance(&field, 2, &valid); + exploded.minute = ParseIntAndAdvance(&field, 2, &valid); + exploded.second = ParseIntAndAdvance(&field, 2, &valid); + if (valid && year_length == 2) + exploded.year += exploded.year < 50 ? 2000 : 1900; + + valid &= exploded.HasValidValues(); + + if (!valid) + return false; + + *time = base::Time::FromUTCExploded(exploded); + return true; +} + +} // namespace net diff --git a/net/cert/x509_cert_types.h b/net/cert/x509_cert_types.h new file mode 100644 index 0000000..4b17310 --- /dev/null +++ b/net/cert/x509_cert_types.h @@ -0,0 +1,138 @@ +// Copyright (c) 2012 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. + +#ifndef NET_CERT_X509_CERT_TYPES_H_ +#define NET_CERT_X509_CERT_TYPES_H_ + +#include <string.h> + +#include <set> +#include <string> +#include <vector> + +#include "base/logging.h" +#include "base/string_piece.h" +#include "build/build_config.h" +#include "net/base/hash_value.h" +#include "net/base/net_export.h" + +#if defined(OS_MACOSX) && !defined(OS_IOS) +#include <Security/x509defs.h> +#endif + +namespace base { +class Time; +} // namespace base + +namespace net { + +class X509Certificate; + +// CertPrincipal represents the issuer or subject field of an X.509 certificate. +struct NET_EXPORT CertPrincipal { + CertPrincipal(); + explicit CertPrincipal(const std::string& name); + ~CertPrincipal(); + +#if (defined(OS_MACOSX) && !defined(OS_IOS)) || defined(OS_WIN) + // Parses a BER-format DistinguishedName. + bool ParseDistinguishedName(const void* ber_name_data, size_t length); +#endif + +#if defined(OS_MACOSX) + // Compare this CertPrincipal with |against|, returning true if they're + // equal enough to be a possible match. This should NOT be used for any + // security relevant decisions. + // TODO(rsleevi): Remove once Mac client auth uses NSS for name comparison. + bool Matches(const CertPrincipal& against) const; +#endif + + // Returns a name that can be used to represent the issuer. It tries in this + // order: CN, O and OU and returns the first non-empty one found. + std::string GetDisplayName() const; + + // The different attributes for a principal, stored in UTF-8. They may be "". + // Note that some of them can have several values. + + std::string common_name; + std::string locality_name; + std::string state_or_province_name; + std::string country_name; + + std::vector<std::string> street_addresses; + std::vector<std::string> organization_names; + std::vector<std::string> organization_unit_names; + std::vector<std::string> domain_components; +}; + +// This class is useful for maintaining policies about which certificates are +// permitted or forbidden for a particular purpose. +class NET_EXPORT CertPolicy { + public: + // The judgments this policy can reach. + enum Judgment { + // We don't have policy information for this certificate. + UNKNOWN, + + // This certificate is allowed. + ALLOWED, + + // This certificate is denied. + DENIED, + }; + + CertPolicy(); + ~CertPolicy(); + + // Returns the judgment this policy makes about this certificate. + Judgment Check(X509Certificate* cert) const; + + // Causes the policy to allow this certificate. + void Allow(X509Certificate* cert); + + // Causes the policy to deny this certificate. + void Deny(X509Certificate* cert); + + // Returns true if this policy has allowed at least one certificate. + bool HasAllowedCert() const; + + // Returns true if this policy has denied at least one certificate. + bool HasDeniedCert() const; + + private: + // The set of fingerprints of allowed certificates. + std::set<SHA1HashValue, SHA1HashValueLessThan> allowed_; + + // The set of fingerprints of denied certificates. + std::set<SHA1HashValue, SHA1HashValueLessThan> denied_; +}; + +#if defined(OS_MACOSX) && !defined(OS_IOS) +// Compares two OIDs by value. +inline bool CSSMOIDEqual(const CSSM_OID* oid1, const CSSM_OID* oid2) { + return oid1->Length == oid2->Length && + (memcmp(oid1->Data, oid2->Data, oid1->Length) == 0); +} +#endif + +// A list of ASN.1 date/time formats that ParseCertificateDate() supports, +// encoded in the canonical forms specified in RFC 2459/3280/5280. +enum CertDateFormat { + // UTCTime: Format is YYMMDDHHMMSSZ + CERT_DATE_FORMAT_UTC_TIME, + + // GeneralizedTime: Format is YYYYMMDDHHMMSSZ + CERT_DATE_FORMAT_GENERALIZED_TIME, +}; + +// Attempts to parse |raw_date|, an ASN.1 date/time string encoded as +// |format|, and writes the result into |*time|. If an invalid date is +// specified, or if parsing fails, returns false, and |*time| will not be +// updated. +bool ParseCertificateDate(const base::StringPiece& raw_date, + CertDateFormat format, + base::Time* time); +} // namespace net + +#endif // NET_CERT_X509_CERT_TYPES_H_ diff --git a/net/cert/x509_cert_types_mac.cc b/net/cert/x509_cert_types_mac.cc new file mode 100644 index 0000000..00795ed --- /dev/null +++ b/net/cert/x509_cert_types_mac.cc @@ -0,0 +1,290 @@ +// Copyright (c) 2012 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 "net/cert/x509_cert_types.h" + +#include <CoreServices/CoreServices.h> +#include <Security/SecAsn1Coder.h> +#include <Security/Security.h> + +#include "base/i18n/icu_string_conversions.h" +#include "base/logging.h" +#include "base/mac/mac_logging.h" +#include "base/utf_string_conversions.h" + +namespace net { + +namespace { + +// The BER encoding of 0.9.2342.19200300.100.1.25. +// On 10.6 and later this is available as CSSMOID_DomainComponent, which is an +// external symbol from Security.framework. However, it appears that Apple's +// implementation improperly encoded this on 10.6+, and even still is +// unavailable on 10.5, so simply including the raw BER here. +// +// Note: CSSM is allowed to store CSSM_OIDs in any arbitrary format desired, +// as long as the symbols are properly exposed. The fact that Apple's +// implementation stores it in BER is an internal implementation detail +// observed by studying libsecurity_cssm. +const uint8 kDomainComponentData[] = { + 0x09, 0x92, 0x26, 0x89, 0x93, 0xF2, 0x2C, 0x64, 0x01, 0x19 +}; + +const CSSM_OID kDomainComponentOID = { + arraysize(kDomainComponentData), + const_cast<uint8*>(kDomainComponentData) +}; + +const CSSM_OID* kOIDs[] = { + &CSSMOID_CommonName, + &CSSMOID_LocalityName, + &CSSMOID_StateProvinceName, + &CSSMOID_CountryName, + &CSSMOID_StreetAddress, + &CSSMOID_OrganizationName, + &CSSMOID_OrganizationalUnitName, + &kDomainComponentOID, +}; + +// The following structs and templates work with Apple's very arcane and under- +// documented SecAsn1Parser API, which is apparently the same as NSS's ASN.1 +// decoder: +// http://www.mozilla.org/projects/security/pki/nss/tech-notes/tn1.html + +// These are used to parse the contents of a raw +// BER DistinguishedName structure. + +const SecAsn1Template kStringValueTemplate[] = { + { SEC_ASN1_CHOICE, offsetof(CSSM_X509_TYPE_VALUE_PAIR, valueType), }, + { SEC_ASN1_PRINTABLE_STRING, + offsetof(CSSM_X509_TYPE_VALUE_PAIR, value), 0, + BER_TAG_PRINTABLE_STRING }, + { SEC_ASN1_IA5_STRING, + offsetof(CSSM_X509_TYPE_VALUE_PAIR, value), 0, + BER_TAG_IA5_STRING }, + { SEC_ASN1_T61_STRING, + offsetof(CSSM_X509_TYPE_VALUE_PAIR, value), 0, + BER_TAG_T61_STRING }, + { SEC_ASN1_UTF8_STRING, + offsetof(CSSM_X509_TYPE_VALUE_PAIR, value), 0, + BER_TAG_PKIX_UTF8_STRING }, + { SEC_ASN1_BMP_STRING, + offsetof(CSSM_X509_TYPE_VALUE_PAIR, value), 0, + BER_TAG_PKIX_BMP_STRING }, + { SEC_ASN1_UNIVERSAL_STRING, + offsetof(CSSM_X509_TYPE_VALUE_PAIR, value), 0, + BER_TAG_PKIX_UNIVERSAL_STRING }, + { 0, } +}; + +const SecAsn1Template kKeyValuePairTemplate[] = { + { SEC_ASN1_SEQUENCE, 0, NULL, sizeof(CSSM_X509_TYPE_VALUE_PAIR) }, + { SEC_ASN1_OBJECT_ID, offsetof(CSSM_X509_TYPE_VALUE_PAIR, type), }, + { SEC_ASN1_INLINE, 0, &kStringValueTemplate, }, + { 0, } +}; + +struct KeyValuePairs { + CSSM_X509_TYPE_VALUE_PAIR* pairs; +}; + +const SecAsn1Template kKeyValuePairSetTemplate[] = { + { SEC_ASN1_SET_OF, offsetof(KeyValuePairs, pairs), + kKeyValuePairTemplate, sizeof(KeyValuePairs) } +}; + +struct X509Name { + KeyValuePairs** pairs_list; +}; + +const SecAsn1Template kNameTemplate[] = { + { SEC_ASN1_SEQUENCE_OF, offsetof(X509Name, pairs_list), + kKeyValuePairSetTemplate, sizeof(X509Name) } +}; + +// Converts raw CSSM_DATA to a std::string. (Char encoding is unaltered.) +std::string DataToString(CSSM_DATA data) { + return std::string( + reinterpret_cast<std::string::value_type*>(data.Data), + data.Length); +} + +// Converts raw CSSM_DATA in ISO-8859-1 to a std::string in UTF-8. +std::string Latin1DataToUTF8String(CSSM_DATA data) { + base::string16 utf16; + if (!CodepageToUTF16(DataToString(data), base::kCodepageLatin1, + base::OnStringConversionError::FAIL, &utf16)) + return ""; + return UTF16ToUTF8(utf16); +} + +// Converts big-endian UTF-16 to UTF-8 in a std::string. +// Note: The byte-order flipping is done in place on the input buffer! +bool UTF16BigEndianToUTF8(char16* chars, size_t length, + std::string* out_string) { + for (size_t i = 0; i < length; i++) + chars[i] = EndianU16_BtoN(chars[i]); + return UTF16ToUTF8(chars, length, out_string); +} + +// Converts big-endian UTF-32 to UTF-8 in a std::string. +// Note: The byte-order flipping is done in place on the input buffer! +bool UTF32BigEndianToUTF8(char32* chars, size_t length, + std::string* out_string) { + for (size_t i = 0; i < length; ++i) + chars[i] = EndianS32_BtoN(chars[i]); +#if defined(WCHAR_T_IS_UTF32) + return WideToUTF8(reinterpret_cast<const wchar_t*>(chars), + length, out_string); +#else +#error This code doesn't handle 16-bit wchar_t. +#endif +} + +// Adds a type+value pair to the appropriate vector from a C array. +// The array is keyed by the matching OIDs from kOIDS[]. +void AddTypeValuePair(const CSSM_OID type, + const std::string& value, + std::vector<std::string>* values[]) { + for (size_t oid = 0; oid < arraysize(kOIDs); ++oid) { + if (CSSMOIDEqual(&type, kOIDs[oid])) { + values[oid]->push_back(value); + break; + } + } +} + +// Stores the first string of the vector, if any, to *single_value. +void SetSingle(const std::vector<std::string>& values, + std::string* single_value) { + // We don't expect to have more than one CN, L, S, and C. + LOG_IF(WARNING, values.size() > 1) << "Didn't expect multiple values"; + if (!values.empty()) + *single_value = values[0]; +} + +bool match(const std::string& str, const std::string& against) { + // TODO(snej): Use the full matching rules specified in RFC 5280 sec. 7.1 + // including trimming and case-folding: <http://www.ietf.org/rfc/rfc5280.txt>. + return against == str; +} + +bool match(const std::vector<std::string>& rdn1, + const std::vector<std::string>& rdn2) { + // "Two relative distinguished names RDN1 and RDN2 match if they have the + // same number of naming attributes and for each naming attribute in RDN1 + // there is a matching naming attribute in RDN2." --RFC 5280 sec. 7.1. + if (rdn1.size() != rdn2.size()) + return false; + for (unsigned i1 = 0; i1 < rdn1.size(); ++i1) { + unsigned i2; + for (i2 = 0; i2 < rdn2.size(); ++i2) { + if (match(rdn1[i1], rdn2[i2])) + break; + } + if (i2 == rdn2.size()) + return false; + } + return true; +} + +} // namespace + +bool CertPrincipal::ParseDistinguishedName(const void* ber_name_data, + size_t length) { + DCHECK(ber_name_data); + + // First parse the BER |name_data| into the above structs. + SecAsn1CoderRef coder = NULL; + SecAsn1CoderCreate(&coder); + DCHECK(coder); + X509Name* name = NULL; + OSStatus err = SecAsn1Decode(coder, ber_name_data, length, kNameTemplate, + &name); + if (err) { + OSSTATUS_LOG(ERROR, err) << "SecAsn1Decode"; + SecAsn1CoderRelease(coder); + return false; + } + + // Now scan the structs and add the values to my string vectors. + // I don't store multiple common/locality/state/country names, so use + // temporary vectors for those. + std::vector<std::string> common_names, locality_names, state_names, + country_names; + std::vector<std::string>* values[] = { + &common_names, &locality_names, + &state_names, &country_names, + &this->street_addresses, + &this->organization_names, + &this->organization_unit_names, + &this->domain_components + }; + DCHECK(arraysize(kOIDs) == arraysize(values)); + + for (int rdn = 0; name[rdn].pairs_list; ++rdn) { + CSSM_X509_TYPE_VALUE_PAIR* pair; + for (int pair_index = 0; + NULL != (pair = name[rdn].pairs_list[0][pair_index].pairs); + ++pair_index) { + switch (pair->valueType) { + case BER_TAG_IA5_STRING: // ASCII (that means 7-bit!) + case BER_TAG_PRINTABLE_STRING: // a subset of ASCII + case BER_TAG_PKIX_UTF8_STRING: // UTF-8 + AddTypeValuePair(pair->type, DataToString(pair->value), values); + break; + case BER_TAG_T61_STRING: // T61, pretend it's Latin-1 + AddTypeValuePair(pair->type, + Latin1DataToUTF8String(pair->value), + values); + break; + case BER_TAG_PKIX_BMP_STRING: { // UTF-16, big-endian + std::string value; + UTF16BigEndianToUTF8(reinterpret_cast<char16*>(pair->value.Data), + pair->value.Length / sizeof(char16), + &value); + AddTypeValuePair(pair->type, value, values); + break; + } + case BER_TAG_PKIX_UNIVERSAL_STRING: { // UTF-32, big-endian + std::string value; + UTF32BigEndianToUTF8(reinterpret_cast<char32*>(pair->value.Data), + pair->value.Length / sizeof(char32), + &value); + AddTypeValuePair(pair->type, value, values); + break; + } + default: + DCHECK_EQ(pair->valueType, BER_TAG_UNKNOWN); + // We don't know what data type this is, but we'll store it as a blob. + // Displaying the string may not work, but at least it can be compared + // byte-for-byte by a Matches() call. + AddTypeValuePair(pair->type, DataToString(pair->value), values); + break; + } + } + } + + SetSingle(common_names, &this->common_name); + SetSingle(locality_names, &this->locality_name); + SetSingle(state_names, &this->state_or_province_name); + SetSingle(country_names, &this->country_name); + + // Releasing |coder| frees all the memory pointed to via |name|. + SecAsn1CoderRelease(coder); + return true; +} + +bool CertPrincipal::Matches(const CertPrincipal& against) const { + return match(common_name, against.common_name) && + match(locality_name, against.locality_name) && + match(state_or_province_name, against.state_or_province_name) && + match(country_name, against.country_name) && + match(street_addresses, against.street_addresses) && + match(organization_names, against.organization_names) && + match(organization_unit_names, against.organization_unit_names) && + match(domain_components, against.domain_components); +} + +} // namespace net diff --git a/net/cert/x509_cert_types_unittest.cc b/net/cert/x509_cert_types_unittest.cc new file mode 100644 index 0000000..e0bcc70 --- /dev/null +++ b/net/cert/x509_cert_types_unittest.cc @@ -0,0 +1,139 @@ +// Copyright (c) 2010 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 "base/basictypes.h" +#include "net/cert/x509_cert_types.h" +#include "net/test/test_certificate_data.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace net { + +#if defined(OS_MACOSX) +TEST(X509TypesTest, Matching) { + CertPrincipal spamco; + spamco.common_name = "SpamCo Dept. Of Certificization"; + spamco.country_name = "EB"; + spamco.organization_names.push_back("SpamCo Holding Company, LLC"); + spamco.organization_names.push_back("SpamCo Evil Masterminds"); + spamco.organization_unit_names.push_back("Class Z Obfuscation Authority"); + ASSERT_TRUE(spamco.Matches(spamco)); + + CertPrincipal bogus; + EXPECT_FALSE(bogus.Matches(spamco)); + EXPECT_FALSE(spamco.Matches(bogus)); + + bogus = spamco; + EXPECT_TRUE(bogus.Matches(spamco)); + EXPECT_TRUE(spamco.Matches(bogus)); + + bogus.organization_names.erase(bogus.organization_names.begin(), + bogus.organization_names.end()); + EXPECT_FALSE(bogus.Matches(spamco)); + EXPECT_FALSE(spamco.Matches(bogus)); + + bogus.organization_names.push_back("SpamCo Holding Company, LLC"); + bogus.organization_names.push_back("SpamCo Evil Masterminds"); + EXPECT_TRUE(bogus.Matches(spamco)); + EXPECT_TRUE(spamco.Matches(bogus)); + + bogus.locality_name = "Elbosdorf"; + EXPECT_FALSE(bogus.Matches(spamco)); + EXPECT_FALSE(spamco.Matches(bogus)); + + bogus.locality_name = ""; + bogus.organization_unit_names.push_back("Q Division"); + EXPECT_FALSE(bogus.Matches(spamco)); + EXPECT_FALSE(spamco.Matches(bogus)); +} +#endif + +TEST(X509TypesTest, ParseDNVerisign) { + CertPrincipal verisign; + EXPECT_TRUE(verisign.ParseDistinguishedName(VerisignDN, sizeof(VerisignDN))); + EXPECT_EQ("", verisign.common_name); + EXPECT_EQ("US", verisign.country_name); + ASSERT_EQ(1U, verisign.organization_names.size()); + EXPECT_EQ("VeriSign, Inc.", verisign.organization_names[0]); + ASSERT_EQ(1U, verisign.organization_unit_names.size()); + EXPECT_EQ("Class 1 Public Primary Certification Authority", + verisign.organization_unit_names[0]); +} + +TEST(X509TypesTest, ParseDNStartcom) { + CertPrincipal startcom; + EXPECT_TRUE(startcom.ParseDistinguishedName(StartComDN, sizeof(StartComDN))); + EXPECT_EQ("StartCom Certification Authority", startcom.common_name); + EXPECT_EQ("IL", startcom.country_name); + ASSERT_EQ(1U, startcom.organization_names.size()); + EXPECT_EQ("StartCom Ltd.", startcom.organization_names[0]); + ASSERT_EQ(1U, startcom.organization_unit_names.size()); + EXPECT_EQ("Secure Digital Certificate Signing", + startcom.organization_unit_names[0]); +} + +TEST(X509TypesTest, ParseDNUserTrust) { + CertPrincipal usertrust; + EXPECT_TRUE(usertrust.ParseDistinguishedName(UserTrustDN, + sizeof(UserTrustDN))); + EXPECT_EQ("UTN-USERFirst-Client Authentication and Email", + usertrust.common_name); + EXPECT_EQ("US", usertrust.country_name); + EXPECT_EQ("UT", usertrust.state_or_province_name); + EXPECT_EQ("Salt Lake City", usertrust.locality_name); + ASSERT_EQ(1U, usertrust.organization_names.size()); + EXPECT_EQ("The USERTRUST Network", usertrust.organization_names[0]); + ASSERT_EQ(1U, usertrust.organization_unit_names.size()); + EXPECT_EQ("http://www.usertrust.com", + usertrust.organization_unit_names[0]); +} + +TEST(X509TypesTest, ParseDNTurkTrust) { + // Note: This tests parsing UTF8STRINGs. + CertPrincipal turktrust; + EXPECT_TRUE(turktrust.ParseDistinguishedName(TurkTrustDN, + sizeof(TurkTrustDN))); + EXPECT_EQ("TÃœRKTRUST Elektronik Sertifika Hizmet SaÄŸlayıcısı", + turktrust.common_name); + EXPECT_EQ("TR", turktrust.country_name); + EXPECT_EQ("Ankara", turktrust.locality_name); + ASSERT_EQ(1U, turktrust.organization_names.size()); + EXPECT_EQ("TÃœRKTRUST Bilgi Ä°letiÅŸim ve BiliÅŸim GüvenliÄŸi Hizmetleri A.Åž. (c) Kasım 2005", + turktrust.organization_names[0]); +} + +TEST(X509TypesTest, ParseDNATrust) { + // Note: This tests parsing 16-bit BMPSTRINGs. + CertPrincipal atrust; + EXPECT_TRUE(atrust.ParseDistinguishedName(ATrustQual01DN, + sizeof(ATrustQual01DN))); + EXPECT_EQ("A-Trust-Qual-01", + atrust.common_name); + EXPECT_EQ("AT", atrust.country_name); + ASSERT_EQ(1U, atrust.organization_names.size()); + EXPECT_EQ("A-Trust Ges. für Sicherheitssysteme im elektr. Datenverkehr GmbH", + atrust.organization_names[0]); + ASSERT_EQ(1U, atrust.organization_unit_names.size()); + EXPECT_EQ("A-Trust-Qual-01", + atrust.organization_unit_names[0]); +} + +TEST(X509TypesTest, ParseDNEntrust) { + // Note: This tests parsing T61STRINGs and fields with multiple values. + CertPrincipal entrust; + EXPECT_TRUE(entrust.ParseDistinguishedName(EntrustDN, + sizeof(EntrustDN))); + EXPECT_EQ("Entrust.net Certification Authority (2048)", + entrust.common_name); + EXPECT_EQ("", entrust.country_name); + ASSERT_EQ(1U, entrust.organization_names.size()); + EXPECT_EQ("Entrust.net", + entrust.organization_names[0]); + ASSERT_EQ(2U, entrust.organization_unit_names.size()); + EXPECT_EQ("www.entrust.net/CPS_2048 incorp. by ref. (limits liab.)", + entrust.organization_unit_names[0]); + EXPECT_EQ("(c) 1999 Entrust.net Limited", + entrust.organization_unit_names[1]); +} + +} // namespace net diff --git a/net/cert/x509_cert_types_win.cc b/net/cert/x509_cert_types_win.cc new file mode 100644 index 0000000..7b99bec --- /dev/null +++ b/net/cert/x509_cert_types_win.cc @@ -0,0 +1,139 @@ +// Copyright (c) 2012 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 "net/cert/x509_cert_types.h" + +#include <windows.h> +#include <wincrypt.h> + +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "base/string_util.h" +#include "base/utf_string_conversions.h" +#include "crypto/capi_util.h" + +#pragma comment(lib, "crypt32.lib") + +namespace net { + +namespace { + +// A list of OIDs to decode. Any OID not on this list will be ignored for +// purposes of parsing. +const char* kOIDs[] = { + szOID_COMMON_NAME, + szOID_LOCALITY_NAME, + szOID_STATE_OR_PROVINCE_NAME, + szOID_COUNTRY_NAME, + szOID_STREET_ADDRESS, + szOID_ORGANIZATION_NAME, + szOID_ORGANIZATIONAL_UNIT_NAME, + szOID_DOMAIN_COMPONENT +}; + +// Converts the value for |attribute| to an UTF-8 string, storing the result +// in |value|. Returns false if the string cannot be converted. +bool GetAttributeValue(PCERT_RDN_ATTR attribute, + std::string* value) { + DWORD chars_needed = CertRDNValueToStrW(attribute->dwValueType, + &attribute->Value, NULL, 0); + if (chars_needed == 0) + return false; + if (chars_needed == 1) { + // The value is actually an empty string (chars_needed includes a single + // char for a NULL value). Don't bother converting - just clear the + // string. + value->clear(); + return true; + } + std::wstring wide_name; + DWORD chars_written = CertRDNValueToStrW( + attribute->dwValueType, &attribute->Value, + WriteInto(&wide_name, chars_needed), chars_needed); + if (chars_written <= 1) + return false; + wide_name.resize(chars_written - 1); + *value = WideToUTF8(wide_name); + return true; +} + +// Adds a type+value pair to the appropriate vector from a C array. +// The array is keyed by the matching OIDs from kOIDS[]. +bool AddTypeValuePair(PCERT_RDN_ATTR attribute, + std::vector<std::string>* values[]) { + for (size_t oid = 0; oid < arraysize(kOIDs); ++oid) { + if (strcmp(attribute->pszObjId, kOIDs[oid]) == 0) { + std::string value; + if (!GetAttributeValue(attribute, &value)) + return false; + values[oid]->push_back(value); + break; + } + } + return true; +} + +// Stores the first string of the vector, if any, to *single_value. +void SetSingle(const std::vector<std::string>& values, + std::string* single_value) { + // We don't expect to have more than one CN, L, S, and C. + LOG_IF(WARNING, values.size() > 1) << "Didn't expect multiple values"; + if (!values.empty()) + *single_value = values[0]; +} + +} // namespace + +bool CertPrincipal::ParseDistinguishedName(const void* ber_name_data, + size_t length) { + DCHECK(ber_name_data); + + CRYPT_DECODE_PARA decode_para; + decode_para.cbSize = sizeof(decode_para); + decode_para.pfnAlloc = crypto::CryptAlloc; + decode_para.pfnFree = crypto::CryptFree; + CERT_NAME_INFO* name_info = NULL; + DWORD name_info_size = 0; + BOOL rv; + rv = CryptDecodeObjectEx(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, + X509_NAME, + reinterpret_cast<const BYTE*>(ber_name_data), + length, + CRYPT_DECODE_ALLOC_FLAG | CRYPT_DECODE_NOCOPY_FLAG, + &decode_para, + &name_info, &name_info_size); + if (!rv) + return false; + scoped_ptr_malloc<CERT_NAME_INFO> scoped_name_info(name_info); + + std::vector<std::string> common_names, locality_names, state_names, + country_names; + + std::vector<std::string>* values[] = { + &common_names, &locality_names, + &state_names, &country_names, + &this->street_addresses, + &this->organization_names, + &this->organization_unit_names, + &this->domain_components + }; + DCHECK(arraysize(kOIDs) == arraysize(values)); + + for (DWORD cur_rdn = 0; cur_rdn < name_info->cRDN; ++cur_rdn) { + PCERT_RDN rdn = &name_info->rgRDN[cur_rdn]; + for (DWORD cur_ava = 0; cur_ava < rdn->cRDNAttr; ++cur_ava) { + PCERT_RDN_ATTR ava = &rdn->rgRDNAttr[cur_ava]; + if (!AddTypeValuePair(ava, values)) + return false; + } + } + + SetSingle(common_names, &this->common_name); + SetSingle(locality_names, &this->locality_name); + SetSingle(state_names, &this->state_or_province_name); + SetSingle(country_names, &this->country_name); + return true; +} + +} // namespace net diff --git a/net/cert/x509_certificate.cc b/net/cert/x509_certificate.cc new file mode 100644 index 0000000..cc3b4cd --- /dev/null +++ b/net/cert/x509_certificate.cc @@ -0,0 +1,701 @@ +// Copyright (c) 2012 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 "net/cert/x509_certificate.h" + +#include <stdlib.h> + +#include <algorithm> +#include <map> +#include <string> +#include <vector> + +#include "base/base64.h" +#include "base/lazy_instance.h" +#include "base/logging.h" +#include "base/memory/singleton.h" +#include "base/metrics/histogram.h" +#include "base/pickle.h" +#include "base/sha1.h" +#include "base/string_piece.h" +#include "base/string_util.h" +#include "base/synchronization/lock.h" +#include "base/time.h" +#include "googleurl/src/url_canon_ip.h" +#include "net/base/net_util.h" +#include "net/cert/pem_tokenizer.h" + +namespace net { + +namespace { + +// Indicates the order to use when trying to decode binary data, which is +// based on (speculation) as to what will be most common -> least common +const X509Certificate::Format kFormatDecodePriority[] = { + X509Certificate::FORMAT_SINGLE_CERTIFICATE, + X509Certificate::FORMAT_PKCS7 +}; + +// The PEM block header used for DER certificates +const char kCertificateHeader[] = "CERTIFICATE"; +// The PEM block header used for PKCS#7 data +const char kPKCS7Header[] = "PKCS7"; + +#if !defined(USE_NSS) +// A thread-safe cache for OS certificate handles. +// +// Within each of the supported underlying crypto libraries, a certificate +// handle is represented as a ref-counted object that contains the parsed +// data for the certificate. In addition, the underlying OS handle may also +// contain a copy of the original ASN.1 DER used to constructed the handle. +// +// In order to reduce the memory usage when multiple SSL connections exist, +// with each connection storing the server's identity certificate plus any +// intermediates supplied, the certificate handles are cached. Any two +// X509Certificates that were created from the same ASN.1 DER data, +// regardless of where that data came from, will share the same underlying +// OS certificate handle. +class X509CertificateCache { + public: + // Performs a compare-and-swap like operation. If an OS certificate handle + // for the same certificate data as |*cert_handle| already exists in the + // cache, the original |*cert_handle| will be freed and |cert_handle| + // will be updated to point to a duplicated reference to the existing cached + // certificate, with the caller taking ownership of this duplicated handle. + // If an equivalent OS certificate handle is not found, a duplicated + // reference to |*cert_handle| will be added to the cache. In either case, + // upon return, the caller fully owns |*cert_handle| and is responsible for + // calling FreeOSCertHandle(), after first calling Remove(). + void InsertOrUpdate(X509Certificate::OSCertHandle* cert_handle); + + // Decrements the cache reference count for |cert_handle|, a handle that was + // previously obtained by calling InsertOrUpdate(). If this is the last + // cached reference held, this will remove the handle from the cache. The + // caller retains ownership of |cert_handle| and remains responsible for + // calling FreeOSCertHandle() to release the underlying OS certificate + void Remove(X509Certificate::OSCertHandle cert_handle); + + private: + // A single entry in the cache. Certificates will be keyed by their SHA1 + // fingerprints, but will not be considered equivalent unless the entire + // certificate data matches. + struct Entry { + Entry() : cert_handle(NULL), ref_count(0) {} + + X509Certificate::OSCertHandle cert_handle; + + // Increased by each call to InsertOrUpdate(), and balanced by each call + // to Remove(). When it equals 0, all references created by + // InsertOrUpdate() have been released, so the cache entry will be removed + // the cached OS certificate handle will be freed. + int ref_count; + }; + typedef std::map<SHA1HashValue, Entry, SHA1HashValueLessThan> CertMap; + + // Obtain an instance of X509CertificateCache via a LazyInstance. + X509CertificateCache() {} + ~X509CertificateCache() {} + friend struct base::DefaultLazyInstanceTraits<X509CertificateCache>; + + // You must acquire this lock before using any private data of this object + // You must not block while holding this lock. + base::Lock lock_; + + // The certificate cache. You must acquire |lock_| before using |cache_|. + CertMap cache_; + + DISALLOW_COPY_AND_ASSIGN(X509CertificateCache); +}; + +base::LazyInstance<X509CertificateCache>::Leaky + g_x509_certificate_cache = LAZY_INSTANCE_INITIALIZER; + +void X509CertificateCache::InsertOrUpdate( + X509Certificate::OSCertHandle* cert_handle) { + DCHECK(cert_handle); + SHA1HashValue fingerprint = + X509Certificate::CalculateFingerprint(*cert_handle); + + X509Certificate::OSCertHandle old_handle = NULL; + { + base::AutoLock lock(lock_); + CertMap::iterator pos = cache_.find(fingerprint); + if (pos == cache_.end()) { + // A cached entry was not found, so initialize a new entry. The entry + // assumes ownership of the current |*cert_handle|. + Entry cache_entry; + cache_entry.cert_handle = *cert_handle; + cache_entry.ref_count = 0; + CertMap::value_type cache_value(fingerprint, cache_entry); + pos = cache_.insert(cache_value).first; + } else { + bool is_same_cert = + X509Certificate::IsSameOSCert(*cert_handle, pos->second.cert_handle); + if (!is_same_cert) { + // Two certificates don't match, due to a SHA1 hash collision. Given + // the low probability, the simplest solution is to not cache the + // certificate, which should not affect performance too negatively. + return; + } + // A cached entry was found and will be used instead of the caller's + // handle. Ensure the caller's original handle will be freed, since + // ownership is assumed. + old_handle = *cert_handle; + } + // Whether an existing cached handle or a new handle, increment the + // cache's reference count and return a handle that the caller can own. + ++pos->second.ref_count; + *cert_handle = X509Certificate::DupOSCertHandle(pos->second.cert_handle); + } + // If the caller's handle was replaced with a cached handle, free the + // original handle now. This is done outside of the lock because + // |old_handle| may be the only handle for this particular certificate, so + // freeing it may be complex or resource-intensive and does not need to + // be guarded by the lock. + if (old_handle) { + X509Certificate::FreeOSCertHandle(old_handle); + DHISTOGRAM_COUNTS("X509CertificateReuseCount", 1); + } +} + +void X509CertificateCache::Remove(X509Certificate::OSCertHandle cert_handle) { + SHA1HashValue fingerprint = + X509Certificate::CalculateFingerprint(cert_handle); + base::AutoLock lock(lock_); + + CertMap::iterator pos = cache_.find(fingerprint); + if (pos == cache_.end()) + return; // A hash collision where the winning cert was already freed. + + bool is_same_cert = X509Certificate::IsSameOSCert(cert_handle, + pos->second.cert_handle); + if (!is_same_cert) + return; // A hash collision where the winning cert is still around. + + if (--pos->second.ref_count == 0) { + // The last reference to |cert_handle| has been removed, so release the + // Entry's OS handle and remove the Entry. The caller still holds a + // reference to |cert_handle| and is responsible for freeing it. + X509Certificate::FreeOSCertHandle(pos->second.cert_handle); + cache_.erase(pos); + } +} +#endif // !defined(USE_NSS) + +// See X509CertificateCache::InsertOrUpdate. NSS has a built-in cache, so there +// is no point in wrapping another cache around it. +void InsertOrUpdateCache(X509Certificate::OSCertHandle* cert_handle) { +#if !defined(USE_NSS) + g_x509_certificate_cache.Pointer()->InsertOrUpdate(cert_handle); +#endif +} + +// See X509CertificateCache::Remove. +void RemoveFromCache(X509Certificate::OSCertHandle cert_handle) { +#if !defined(USE_NSS) + g_x509_certificate_cache.Pointer()->Remove(cert_handle); +#endif +} + +// Utility to split |src| on the first occurrence of |c|, if any. |right| will +// either be empty if |c| was not found, or will contain the remainder of the +// string including the split character itself. +void SplitOnChar(const base::StringPiece& src, + char c, + base::StringPiece* left, + base::StringPiece* right) { + size_t pos = src.find(c); + if (pos == base::StringPiece::npos) { + *left = src; + right->clear(); + } else { + *left = src.substr(0, pos); + *right = src.substr(pos); + } +} + +} // namespace + +bool X509Certificate::LessThan::operator()(X509Certificate* lhs, + X509Certificate* rhs) const { + if (lhs == rhs) + return false; + + int rv = memcmp(lhs->fingerprint_.data, rhs->fingerprint_.data, + sizeof(lhs->fingerprint_.data)); + if (rv != 0) + return rv < 0; + + rv = memcmp(lhs->ca_fingerprint_.data, rhs->ca_fingerprint_.data, + sizeof(lhs->ca_fingerprint_.data)); + return rv < 0; +} + +X509Certificate::X509Certificate(const std::string& subject, + const std::string& issuer, + base::Time start_date, + base::Time expiration_date) + : subject_(subject), + issuer_(issuer), + valid_start_(start_date), + valid_expiry_(expiration_date), + cert_handle_(NULL) { + memset(fingerprint_.data, 0, sizeof(fingerprint_.data)); + memset(ca_fingerprint_.data, 0, sizeof(ca_fingerprint_.data)); +} + +// static +X509Certificate* X509Certificate::CreateFromHandle( + OSCertHandle cert_handle, + const OSCertHandles& intermediates) { + DCHECK(cert_handle); + return new X509Certificate(cert_handle, intermediates); +} + +// static +X509Certificate* X509Certificate::CreateFromDERCertChain( + const std::vector<base::StringPiece>& der_certs) { + if (der_certs.empty()) + return NULL; + + X509Certificate::OSCertHandles intermediate_ca_certs; + for (size_t i = 1; i < der_certs.size(); i++) { + OSCertHandle handle = CreateOSCertHandleFromBytes( + const_cast<char*>(der_certs[i].data()), der_certs[i].size()); + if (!handle) + break; + intermediate_ca_certs.push_back(handle); + } + + OSCertHandle handle = NULL; + // Return NULL if we failed to parse any of the certs. + if (der_certs.size() - 1 == intermediate_ca_certs.size()) { + handle = CreateOSCertHandleFromBytes( + const_cast<char*>(der_certs[0].data()), der_certs[0].size()); + } + + X509Certificate* cert = NULL; + if (handle) { + cert = CreateFromHandle(handle, intermediate_ca_certs); + FreeOSCertHandle(handle); + } + + for (size_t i = 0; i < intermediate_ca_certs.size(); i++) + FreeOSCertHandle(intermediate_ca_certs[i]); + + return cert; +} + +// static +X509Certificate* X509Certificate::CreateFromBytes(const char* data, + int length) { + OSCertHandle cert_handle = CreateOSCertHandleFromBytes(data, length); + if (!cert_handle) + return NULL; + + X509Certificate* cert = CreateFromHandle(cert_handle, OSCertHandles()); + FreeOSCertHandle(cert_handle); + return cert; +} + +// static +X509Certificate* X509Certificate::CreateFromPickle(const Pickle& pickle, + PickleIterator* pickle_iter, + PickleType type) { + if (type == PICKLETYPE_CERTIFICATE_CHAIN_V3) { + int chain_length = 0; + if (!pickle_iter->ReadLength(&chain_length)) + return NULL; + + std::vector<base::StringPiece> cert_chain; + const char* data = NULL; + int data_length = 0; + for (int i = 0; i < chain_length; ++i) { + if (!pickle_iter->ReadData(&data, &data_length)) + return NULL; + cert_chain.push_back(base::StringPiece(data, data_length)); + } + return CreateFromDERCertChain(cert_chain); + } + + // Legacy / Migration code. This should eventually be removed once + // sufficient time has passed that all pickles serialized prior to + // PICKLETYPE_CERTIFICATE_CHAIN_V3 have been removed. + OSCertHandle cert_handle = ReadOSCertHandleFromPickle(pickle_iter); + if (!cert_handle) + return NULL; + + OSCertHandles intermediates; + uint32 num_intermediates = 0; + if (type != PICKLETYPE_SINGLE_CERTIFICATE) { + if (!pickle_iter->ReadUInt32(&num_intermediates)) { + FreeOSCertHandle(cert_handle); + return NULL; + } + +#if defined(OS_POSIX) && !defined(OS_MACOSX) && defined(__x86_64__) + // On 64-bit Linux (and any other 64-bit platforms), the intermediate count + // might really be a 64-bit field since we used to use Pickle::WriteSize(), + // which writes either 32 or 64 bits depending on the architecture. Since + // x86-64 is little-endian, if that happens, the next 32 bits will be all + // zeroes (the high bits) and the 32 bits we already read above are the + // correct value (we assume there are never more than 2^32 - 1 intermediate + // certificates in a chain; in practice, more than a dozen or so is + // basically unheard of). Since it's invalid for a certificate to start with + // 32 bits of zeroes, we check for that here and skip it if we find it. We + // save a copy of the pickle iterator to restore in case we don't get 32 + // bits of zeroes. Now we always write 32 bits, so after a while, these old + // cached pickles will all get replaced. + // TODO(mdm): remove this compatibility code in April 2013 or so. + PickleIterator saved_iter = *pickle_iter; + uint32 zero_check = 0; + if (!pickle_iter->ReadUInt32(&zero_check)) { + // This may not be an error. If there are no intermediates, and we're + // reading an old 32-bit pickle, and there's nothing else after this in + // the pickle, we should report success. Note that it is technically + // possible for us to skip over zeroes that should have occurred after + // an empty certificate list; to avoid this going forward, only do this + // backward-compatibility stuff for PICKLETYPE_CERTIFICATE_CHAIN_V1 + // which comes from the pickle version number in http_response_info.cc. + if (num_intermediates) { + FreeOSCertHandle(cert_handle); + return NULL; + } + } + if (zero_check) + *pickle_iter = saved_iter; +#endif // defined(OS_POSIX) && !defined(OS_MACOSX) && defined(__x86_64__) + + for (uint32 i = 0; i < num_intermediates; ++i) { + OSCertHandle intermediate = ReadOSCertHandleFromPickle(pickle_iter); + if (!intermediate) + break; + intermediates.push_back(intermediate); + } + } + + X509Certificate* cert = NULL; + if (intermediates.size() == num_intermediates) + cert = CreateFromHandle(cert_handle, intermediates); + FreeOSCertHandle(cert_handle); + for (size_t i = 0; i < intermediates.size(); ++i) + FreeOSCertHandle(intermediates[i]); + + return cert; +} + +// static +CertificateList X509Certificate::CreateCertificateListFromBytes( + const char* data, int length, int format) { + OSCertHandles certificates; + + // Check to see if it is in a PEM-encoded form. This check is performed + // first, as both OS X and NSS will both try to convert if they detect + // PEM encoding, except they don't do it consistently between the two. + base::StringPiece data_string(data, length); + std::vector<std::string> pem_headers; + + // To maintain compatibility with NSS/Firefox, CERTIFICATE is a universally + // valid PEM block header for any format. + pem_headers.push_back(kCertificateHeader); + if (format & FORMAT_PKCS7) + pem_headers.push_back(kPKCS7Header); + + PEMTokenizer pem_tok(data_string, pem_headers); + while (pem_tok.GetNext()) { + std::string decoded(pem_tok.data()); + + OSCertHandle handle = NULL; + if (format & FORMAT_PEM_CERT_SEQUENCE) + handle = CreateOSCertHandleFromBytes(decoded.c_str(), decoded.size()); + if (handle != NULL) { + // Parsed a DER encoded certificate. All PEM blocks that follow must + // also be DER encoded certificates wrapped inside of PEM blocks. + format = FORMAT_PEM_CERT_SEQUENCE; + certificates.push_back(handle); + continue; + } + + // If the first block failed to parse as a DER certificate, and + // formats other than PEM are acceptable, check to see if the decoded + // data is one of the accepted formats. + if (format & ~FORMAT_PEM_CERT_SEQUENCE) { + for (size_t i = 0; certificates.empty() && + i < arraysize(kFormatDecodePriority); ++i) { + if (format & kFormatDecodePriority[i]) { + certificates = CreateOSCertHandlesFromBytes(decoded.c_str(), + decoded.size(), kFormatDecodePriority[i]); + } + } + } + + // Stop parsing after the first block for any format but a sequence of + // PEM-encoded DER certificates. The case of FORMAT_PEM_CERT_SEQUENCE + // is handled above, and continues processing until a certificate fails + // to parse. + break; + } + + // Try each of the formats, in order of parse preference, to see if |data| + // contains the binary representation of a Format, if it failed to parse + // as a PEM certificate/chain. + for (size_t i = 0; certificates.empty() && + i < arraysize(kFormatDecodePriority); ++i) { + if (format & kFormatDecodePriority[i]) + certificates = CreateOSCertHandlesFromBytes(data, length, + kFormatDecodePriority[i]); + } + + CertificateList results; + // No certificates parsed. + if (certificates.empty()) + return results; + + for (OSCertHandles::iterator it = certificates.begin(); + it != certificates.end(); ++it) { + X509Certificate* result = CreateFromHandle(*it, OSCertHandles()); + results.push_back(scoped_refptr<X509Certificate>(result)); + FreeOSCertHandle(*it); + } + + return results; +} + +void X509Certificate::Persist(Pickle* pickle) { + DCHECK(cert_handle_); + // This would be an absolutely insane number of intermediates. + if (intermediate_ca_certs_.size() > static_cast<size_t>(INT_MAX) - 1) { + NOTREACHED(); + return; + } + if (!pickle->WriteInt( + static_cast<int>(intermediate_ca_certs_.size() + 1)) || + !WriteOSCertHandleToPickle(cert_handle_, pickle)) { + NOTREACHED(); + return; + } + for (size_t i = 0; i < intermediate_ca_certs_.size(); ++i) { + if (!WriteOSCertHandleToPickle(intermediate_ca_certs_[i], pickle)) { + NOTREACHED(); + return; + } + } +} + +void X509Certificate::GetDNSNames(std::vector<std::string>* dns_names) const { + GetSubjectAltName(dns_names, NULL); + if (dns_names->empty()) + dns_names->push_back(subject_.common_name); +} + +bool X509Certificate::HasExpired() const { + return base::Time::Now() > valid_expiry(); +} + +bool X509Certificate::Equals(const X509Certificate* other) const { + return IsSameOSCert(cert_handle_, other->cert_handle_); +} + +// static +bool X509Certificate::VerifyHostname( + const std::string& hostname, + const std::string& cert_common_name, + const std::vector<std::string>& cert_san_dns_names, + const std::vector<std::string>& cert_san_ip_addrs) { + DCHECK(!hostname.empty()); + // Perform name verification following http://tools.ietf.org/html/rfc6125. + // The terminology used in this method is as per that RFC:- + // Reference identifier == the host the local user/agent is intending to + // access, i.e. the thing displayed in the URL bar. + // Presented identifier(s) == name(s) the server knows itself as, in its cert. + + // CanonicalizeHost requires surrounding brackets to parse an IPv6 address. + const std::string host_or_ip = hostname.find(':') != std::string::npos ? + "[" + hostname + "]" : hostname; + url_canon::CanonHostInfo host_info; + std::string reference_name = CanonicalizeHost(host_or_ip, &host_info); + // CanonicalizeHost does not normalize absolute vs relative DNS names. If + // the input name was absolute (included trailing .), normalize it as if it + // was relative. + if (!reference_name.empty() && *reference_name.rbegin() == '.') + reference_name.resize(reference_name.size() - 1); + if (reference_name.empty()) + return false; + + // Allow fallback to Common name matching? + const bool common_name_fallback = cert_san_dns_names.empty() && + cert_san_ip_addrs.empty(); + + // Fully handle all cases where |hostname| contains an IP address. + if (host_info.IsIPAddress()) { + if (common_name_fallback && + host_info.family == url_canon::CanonHostInfo::IPV4) { + // Fallback to Common name matching. As this is deprecated and only + // supported for compatibility refuse it for IPv6 addresses. + return reference_name == cert_common_name; + } + base::StringPiece ip_addr_string( + reinterpret_cast<const char*>(host_info.address), + host_info.AddressLength()); + return std::find(cert_san_ip_addrs.begin(), cert_san_ip_addrs.end(), + ip_addr_string) != cert_san_ip_addrs.end(); + } + + // |reference_domain| is the remainder of |host| after the leading host + // component is stripped off, but includes the leading dot e.g. + // "www.f.com" -> ".f.com". + // If there is no meaningful domain part to |host| (e.g. it contains no dots) + // then |reference_domain| will be empty. + base::StringPiece reference_host, reference_domain; + SplitOnChar(reference_name, '.', &reference_host, &reference_domain); + bool allow_wildcards = false; + if (!reference_domain.empty()) { + DCHECK(reference_domain.starts_with(".")); + // We required at least 3 components (i.e. 2 dots) as a basic protection + // against too-broad wild-carding. + // Also we don't attempt wildcard matching on a purely numerical hostname. + allow_wildcards = reference_domain.rfind('.') != 0 && + reference_name.find_first_not_of("0123456789.") != std::string::npos; + } + + // Now step through the DNS names doing wild card comparison (if necessary) + // on each against the reference name. If subjectAltName is empty, then + // fallback to use the common name instead. + std::vector<std::string> common_name_as_vector; + const std::vector<std::string>* presented_names = &cert_san_dns_names; + if (common_name_fallback) { + // Note: there's a small possibility cert_common_name is an international + // domain name in non-standard encoding (e.g. UTF8String or BMPString + // instead of A-label). As common name fallback is deprecated we're not + // doing anything specific to deal with this. + common_name_as_vector.push_back(cert_common_name); + presented_names = &common_name_as_vector; + } + for (std::vector<std::string>::const_iterator it = + presented_names->begin(); + it != presented_names->end(); ++it) { + // Catch badly corrupt cert names up front. + if (it->empty() || it->find('\0') != std::string::npos) { + DVLOG(1) << "Bad name in cert: " << *it; + continue; + } + std::string presented_name(StringToLowerASCII(*it)); + + // Remove trailing dot, if any. + if (*presented_name.rbegin() == '.') + presented_name.resize(presented_name.length() - 1); + + // The hostname must be at least as long as the cert name it is matching, + // as we require the wildcard (if present) to match at least one character. + if (presented_name.length() > reference_name.length()) + continue; + + base::StringPiece presented_host, presented_domain; + SplitOnChar(presented_name, '.', &presented_host, &presented_domain); + + if (presented_domain != reference_domain) + continue; + + base::StringPiece pattern_begin, pattern_end; + SplitOnChar(presented_host, '*', &pattern_begin, &pattern_end); + + if (pattern_end.empty()) { // No '*' in the presented_host + if (presented_host == reference_host) + return true; + continue; + } + pattern_end.remove_prefix(1); // move past the * + + if (!allow_wildcards) + continue; + + // * must not match a substring of an IDN A label; just a whole fragment. + if (reference_host.starts_with("xn--") && + !(pattern_begin.empty() && pattern_end.empty())) + continue; + + if (reference_host.starts_with(pattern_begin) && + reference_host.ends_with(pattern_end)) + return true; + } + return false; +} + +#if !defined(USE_NSS) +bool X509Certificate::VerifyNameMatch(const std::string& hostname) const { + std::vector<std::string> dns_names, ip_addrs; + GetSubjectAltName(&dns_names, &ip_addrs); + return VerifyHostname(hostname, subject_.common_name, dns_names, ip_addrs); +} +#endif + +// static +bool X509Certificate::GetPEMEncoded(OSCertHandle cert_handle, + std::string* pem_encoded) { + std::string der_encoded; + if (!GetDEREncoded(cert_handle, &der_encoded) || der_encoded.empty()) + return false; + std::string b64_encoded; + if (!base::Base64Encode(der_encoded, &b64_encoded) || b64_encoded.empty()) + return false; + *pem_encoded = "-----BEGIN CERTIFICATE-----\n"; + + // Divide the Base-64 encoded data into 64-character chunks, as per + // 4.3.2.4 of RFC 1421. + static const size_t kChunkSize = 64; + size_t chunks = (b64_encoded.size() + (kChunkSize - 1)) / kChunkSize; + for (size_t i = 0, chunk_offset = 0; i < chunks; + ++i, chunk_offset += kChunkSize) { + pem_encoded->append(b64_encoded, chunk_offset, kChunkSize); + pem_encoded->append("\n"); + } + pem_encoded->append("-----END CERTIFICATE-----\n"); + return true; +} + +bool X509Certificate::GetPEMEncodedChain( + std::vector<std::string>* pem_encoded) const { + std::vector<std::string> encoded_chain; + std::string pem_data; + if (!GetPEMEncoded(os_cert_handle(), &pem_data)) + return false; + encoded_chain.push_back(pem_data); + for (size_t i = 0; i < intermediate_ca_certs_.size(); ++i) { + if (!GetPEMEncoded(intermediate_ca_certs_[i], &pem_data)) + return false; + encoded_chain.push_back(pem_data); + } + pem_encoded->swap(encoded_chain); + return true; +} + +X509Certificate::X509Certificate(OSCertHandle cert_handle, + const OSCertHandles& intermediates) + : cert_handle_(DupOSCertHandle(cert_handle)) { + InsertOrUpdateCache(&cert_handle_); + for (size_t i = 0; i < intermediates.size(); ++i) { + // Duplicate the incoming certificate, as the caller retains ownership + // of |intermediates|. + OSCertHandle intermediate = DupOSCertHandle(intermediates[i]); + // Update the cache, which will assume ownership of the duplicated + // handle and return a suitable equivalent, potentially from the cache. + InsertOrUpdateCache(&intermediate); + intermediate_ca_certs_.push_back(intermediate); + } + // Platform-specific initialization. + Initialize(); +} + +X509Certificate::~X509Certificate() { + if (cert_handle_) { + RemoveFromCache(cert_handle_); + FreeOSCertHandle(cert_handle_); + } + for (size_t i = 0; i < intermediate_ca_certs_.size(); ++i) { + RemoveFromCache(intermediate_ca_certs_[i]); + FreeOSCertHandle(intermediate_ca_certs_[i]); + } +} + +} // namespace net diff --git a/net/cert/x509_certificate.h b/net/cert/x509_certificate.h new file mode 100644 index 0000000..16f4d8d --- /dev/null +++ b/net/cert/x509_certificate.h @@ -0,0 +1,510 @@ +// Copyright (c) 2012 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. + +#ifndef NET_CERT_X509_CERTIFICATE_H_ +#define NET_CERT_X509_CERTIFICATE_H_ + +#include <string.h> + +#include <string> +#include <vector> + +#include "base/gtest_prod_util.h" +#include "base/memory/ref_counted.h" +#include "base/string_piece.h" +#include "base/time.h" +#include "net/base/net_export.h" +#include "net/cert/cert_type.h" +#include "net/cert/x509_cert_types.h" + +#if defined(OS_WIN) +#include <windows.h> +#include <wincrypt.h> +#elif defined(OS_MACOSX) +#include <CoreFoundation/CFArray.h> +#include <Security/SecBase.h> + +#elif defined(USE_OPENSSL) +// Forward declaration; real one in <x509.h> +typedef struct x509_st X509; +typedef struct x509_store_st X509_STORE; +#elif defined(USE_NSS) +// Forward declaration; real one in <cert.h> +struct CERTCertificateStr; +#endif + +class Pickle; +class PickleIterator; + +namespace crypto { +class RSAPrivateKey; +} // namespace crypto + +namespace net { + +class CRLSet; +class CertVerifyResult; + +typedef std::vector<scoped_refptr<X509Certificate> > CertificateList; + +// X509Certificate represents a X.509 certificate, which is comprised a +// particular identity or end-entity certificate, such as an SSL server +// identity or an SSL client certificate, and zero or more intermediate +// certificates that may be used to build a path to a root certificate. +class NET_EXPORT X509Certificate + : public base::RefCountedThreadSafe<X509Certificate> { + public: + // An OSCertHandle is a handle to a certificate object in the underlying + // crypto library. We assume that OSCertHandle is a pointer type on all + // platforms and that NULL represents an invalid OSCertHandle. +#if defined(OS_WIN) + typedef PCCERT_CONTEXT OSCertHandle; +#elif defined(OS_MACOSX) + typedef SecCertificateRef OSCertHandle; +#elif defined(USE_OPENSSL) + typedef X509* OSCertHandle; +#elif defined(USE_NSS) + typedef struct CERTCertificateStr* OSCertHandle; +#else + // TODO(ericroman): not implemented + typedef void* OSCertHandle; +#endif + + typedef std::vector<OSCertHandle> OSCertHandles; + + enum PublicKeyType { + kPublicKeyTypeUnknown, + kPublicKeyTypeRSA, + kPublicKeyTypeDSA, + kPublicKeyTypeECDSA, + kPublicKeyTypeDH, + kPublicKeyTypeECDH + }; + + // Predicate functor used in maps when X509Certificate is used as the key. + class NET_EXPORT LessThan { + public: + bool operator() (X509Certificate* lhs, X509Certificate* rhs) const; + }; + + enum Format { + // The data contains a single DER-encoded certificate, or a PEM-encoded + // DER certificate with the PEM encoding block name of "CERTIFICATE". + // Any subsequent blocks will be ignored. + FORMAT_SINGLE_CERTIFICATE = 1 << 0, + + // The data contains a sequence of one or more PEM-encoded, DER + // certificates, with the PEM encoding block name of "CERTIFICATE". + // All PEM blocks will be parsed, until the first error is encountered. + FORMAT_PEM_CERT_SEQUENCE = 1 << 1, + + // The data contains a PKCS#7 SignedData structure, whose certificates + // member is to be used to initialize the certificate and intermediates. + // The data may further be encoded using PEM, specifying block names of + // either "PKCS7" or "CERTIFICATE". + FORMAT_PKCS7 = 1 << 2, + + // Automatically detect the format. + FORMAT_AUTO = FORMAT_SINGLE_CERTIFICATE | FORMAT_PEM_CERT_SEQUENCE | + FORMAT_PKCS7, + }; + + // PickleType is intended for deserializing certificates that were pickled + // by previous releases as part of a net::HttpResponseInfo. + // When serializing certificates to a new Pickle, + // PICKLETYPE_CERTIFICATE_CHAIN_V3 is always used. + enum PickleType { + // When reading a certificate from a Pickle, the Pickle only contains a + // single certificate. + PICKLETYPE_SINGLE_CERTIFICATE, + + // When reading a certificate from a Pickle, the Pickle contains the + // the certificate plus any certificates that were stored in + // |intermediate_ca_certificates_| at the time it was serialized. + // The count of certificates is stored as a size_t, which is either 32 + // or 64 bits. + PICKLETYPE_CERTIFICATE_CHAIN_V2, + + // The Pickle contains the certificate and any certificates that were + // stored in |intermediate_ca_certs_| at the time it was serialized. + // The format is [int count], [data - this certificate], + // [data - intermediate1], ... [data - intermediateN]. + // All certificates are stored in DER form. + PICKLETYPE_CERTIFICATE_CHAIN_V3, + }; + + // Creates a X509Certificate from the ground up. Used by tests that simulate + // SSL connections. + X509Certificate(const std::string& subject, const std::string& issuer, + base::Time start_date, base::Time expiration_date); + + // Create an X509Certificate from a handle to the certificate object in the + // underlying crypto library. The returned pointer must be stored in a + // scoped_refptr<X509Certificate>. + static X509Certificate* CreateFromHandle(OSCertHandle cert_handle, + const OSCertHandles& intermediates); + + // Create an X509Certificate from a chain of DER encoded certificates. The + // first certificate in the chain is the end-entity certificate to which a + // handle is returned. The other certificates in the chain are intermediate + // certificates. The returned pointer must be stored in a + // scoped_refptr<X509Certificate>. + static X509Certificate* CreateFromDERCertChain( + const std::vector<base::StringPiece>& der_certs); + + // Create an X509Certificate from the DER-encoded representation. + // Returns NULL on failure. + // + // The returned pointer must be stored in a scoped_refptr<X509Certificate>. + static X509Certificate* CreateFromBytes(const char* data, int length); + +#if defined(USE_NSS) + // Create an X509Certificate from the DER-encoded representation. + // |nickname| can be NULL if an auto-generated nickname is desired. + // Returns NULL on failure. The returned pointer must be stored in a + // scoped_refptr<X509Certificate>. + // + // This function differs from CreateFromBytes in that it takes a + // nickname that will be used when the certificate is imported into PKCS#11. + static X509Certificate* CreateFromBytesWithNickname(const char* data, + int length, + const char* nickname); + + // The default nickname of the certificate, based on the certificate type + // passed in. If this object was created using CreateFromBytesWithNickname, + // then this will return the nickname specified upon creation. + std::string GetDefaultNickname(CertType type) const; +#endif + + // Create an X509Certificate from the representation stored in the given + // pickle. The data for this object is found relative to the given + // pickle_iter, which should be passed to the pickle's various Read* methods. + // Returns NULL on failure. + // + // The returned pointer must be stored in a scoped_refptr<X509Certificate>. + static X509Certificate* CreateFromPickle(const Pickle& pickle, + PickleIterator* pickle_iter, + PickleType type); + + // Parses all of the certificates possible from |data|. |format| is a + // bit-wise OR of Format, indicating the possible formats the + // certificates may have been serialized as. If an error occurs, an empty + // collection will be returned. + static CertificateList CreateCertificateListFromBytes(const char* data, + int length, + int format); + + // Create a self-signed certificate containing the public key in |key|. + // Subject, serial number and validity period are given as parameters. + // The certificate is signed by the private key in |key|. The hashing + // algorithm for the signature is SHA-1. + // + // |subject| is a distinguished name defined in RFC4514. + // + // An example: + // CN=Michael Wong,O=FooBar Corporation,DC=foobar,DC=com + // + // SECURITY WARNING + // + // Using self-signed certificates has the following security risks: + // 1. Encryption without authentication and thus vulnerable to + // man-in-the-middle attacks. + // 2. Self-signed certificates cannot be revoked. + // + // Use this certificate only after the above risks are acknowledged. + static X509Certificate* CreateSelfSigned(crypto::RSAPrivateKey* key, + const std::string& subject, + uint32 serial_number, + base::TimeDelta valid_duration); + + // Appends a representation of this object to the given pickle. + void Persist(Pickle* pickle); + + // The serial number, DER encoded, possibly including a leading 00 byte. + const std::string& serial_number() const { return serial_number_; } + + // The subject of the certificate. For HTTPS server certificates, this + // represents the web server. The common name of the subject should match + // the host name of the web server. + const CertPrincipal& subject() const { return subject_; } + + // The issuer of the certificate. + const CertPrincipal& issuer() const { return issuer_; } + + // Time period during which the certificate is valid. More precisely, this + // certificate is invalid before the |valid_start| date and invalid after + // the |valid_expiry| date. + // If we were unable to parse either date from the certificate (or if the cert + // lacks either date), the date will be null (i.e., is_null() will be true). + const base::Time& valid_start() const { return valid_start_; } + const base::Time& valid_expiry() const { return valid_expiry_; } + + // The fingerprint of this certificate. + const SHA1HashValue& fingerprint() const { return fingerprint_; } + + // The fingerprint of the intermediate CA certificates. + const SHA1HashValue& ca_fingerprint() const { + return ca_fingerprint_; + } + + // Gets the DNS names in the certificate. Pursuant to RFC 2818, Section 3.1 + // Server Identity, if the certificate has a subjectAltName extension of + // type dNSName, this method gets the DNS names in that extension. + // Otherwise, it gets the common name in the subject field. + void GetDNSNames(std::vector<std::string>* dns_names) const; + + // Gets the subjectAltName extension field from the certificate, if any. + // For future extension; currently this only returns those name types that + // are required for HTTP certificate name verification - see VerifyHostname. + // Unrequired parameters may be passed as NULL. + void GetSubjectAltName(std::vector<std::string>* dns_names, + std::vector<std::string>* ip_addrs) const; + + // Convenience method that returns whether this certificate has expired as of + // now. + bool HasExpired() const; + + // Returns true if this object and |other| represent the same certificate. + bool Equals(const X509Certificate* other) const; + + // Returns intermediate certificates added via AddIntermediateCertificate(). + // Ownership follows the "get" rule: it is the caller's responsibility to + // retain the elements of the result. + const OSCertHandles& GetIntermediateCertificates() const { + return intermediate_ca_certs_; + } + +#if defined(OS_MACOSX) + // Does this certificate's usage allow SSL client authentication? + bool SupportsSSLClientAuth() const; + + // Creates the chain of certs to use for this client identity cert. + CFArrayRef CreateClientCertificateChain() const; + + // Returns a new CFArrayRef containing this certificate and its intermediate + // certificates in the form expected by Security.framework and Keychain + // Services, or NULL on failure. + // The first item in the array will be this certificate, followed by its + // intermediates, if any. + CFArrayRef CreateOSCertChainForCert() const; +#endif + + // Do any of the given issuer names appear in this cert's chain of trust? + // |valid_issuers| is a list of DER-encoded X.509 DistinguishedNames. + bool IsIssuedByEncoded(const std::vector<std::string>& valid_issuers); + +#if defined(OS_WIN) + // Returns a new PCCERT_CONTEXT containing this certificate and its + // intermediate certificates, or NULL on failure. The returned + // PCCERT_CONTEXT *MUST NOT* be stored in an X509Certificate, as this will + // cause os_cert_handle() to return incorrect results. This function is only + // necessary if the CERT_CONTEXT.hCertStore member will be accessed or + // enumerated, which is generally true for any CryptoAPI functions involving + // certificate chains, including validation or certificate display. + // + // Remarks: + // Depending on the CryptoAPI function, Windows may need to access the + // HCERTSTORE that the passed-in PCCERT_CONTEXT belongs to, such as to + // locate additional intermediates. However, all certificate handles are added + // to a NULL HCERTSTORE, allowing the system to manage the resources. As a + // result, intermediates for |cert_handle_| cannot be located simply via + // |cert_handle_->hCertStore|, as it refers to a magic value indicating + // "only this certificate". + // + // To avoid this problems, a new in-memory HCERTSTORE is created containing + // just this certificate and its intermediates. The handle to the version of + // the current certificate in the new HCERTSTORE is then returned, with the + // PCCERT_CONTEXT's HCERTSTORE set to be automatically freed when the returned + // certificate handle is freed. + // + // This function is only needed when the HCERTSTORE of the os_cert_handle() + // will be accessed, which is generally only during certificate validation + // or display. While the returned PCCERT_CONTEXT and its HCERTSTORE can + // safely be used on multiple threads if no further modifications happen, it + // is generally preferable for each thread that needs such a context to + // obtain its own, rather than risk thread-safety issues by sharing. + // + // Because of how X509Certificate caching is implemented, attempting to + // create an X509Certificate from the returned PCCERT_CONTEXT may result in + // the original handle (and thus the originall HCERTSTORE) being returned by + // os_cert_handle(). For this reason, the returned PCCERT_CONTEXT *MUST NOT* + // be stored in an X509Certificate. + PCCERT_CONTEXT CreateOSCertChainForCert() const; +#endif + +#if defined(USE_OPENSSL) + // Returns a handle to a global, in-memory certificate store. We + // use it for test code, e.g. importing the test server's certificate. + static X509_STORE* cert_store(); +#endif + + // Verifies that |hostname| matches this certificate. + // Does not verify that the certificate is valid, only that the certificate + // matches this host. + // Returns true if it matches. + bool VerifyNameMatch(const std::string& hostname) const; + + // Obtains the DER encoded certificate data for |cert_handle|. On success, + // returns true and writes the DER encoded certificate to |*der_encoded|. + static bool GetDEREncoded(OSCertHandle cert_handle, + std::string* der_encoded); + + // Returns the PEM encoded data from an OSCertHandle. If the return value is + // true, then the PEM encoded certificate is written to |pem_encoded|. + static bool GetPEMEncoded(OSCertHandle cert_handle, + std::string* pem_encoded); + + // Encodes the entire certificate chain (this certificate and any + // intermediate certificates stored in |intermediate_ca_certs_|) as a series + // of PEM encoded strings. Returns true if all certificates were encoded, + // storig the result in |*pem_encoded|, with this certificate stored as + // the first element. + bool GetPEMEncodedChain(std::vector<std::string>* pem_encoded) const; + + // Sets |*size_bits| to be the length of the public key in bits, and sets + // |*type| to one of the |PublicKeyType| values. In case of + // |kPublicKeyTypeUnknown|, |*size_bits| will be set to 0. + static void GetPublicKeyInfo(OSCertHandle cert_handle, + size_t* size_bits, + PublicKeyType* type); + + // Returns the OSCertHandle of this object. Because of caching, this may + // differ from the OSCertHandle originally supplied during initialization. + // Note: On Windows, CryptoAPI may return unexpected results if this handle + // is used across multiple threads. For more details, see + // CreateOSCertChainForCert(). + OSCertHandle os_cert_handle() const { return cert_handle_; } + + // Returns true if two OSCertHandles refer to identical certificates. + static bool IsSameOSCert(OSCertHandle a, OSCertHandle b); + + // Creates an OS certificate handle from the DER-encoded representation. + // Returns NULL on failure. + static OSCertHandle CreateOSCertHandleFromBytes(const char* data, + int length); + +#if defined(USE_NSS) + // Creates an OS certificate handle from the DER-encoded representation. + // Returns NULL on failure. Sets the default nickname if |nickname| is + // non-NULL. + static OSCertHandle CreateOSCertHandleFromBytesWithNickname( + const char* data, + int length, + const char* nickname); +#endif + + // Creates all possible OS certificate handles from |data| encoded in a + // specific |format|. Returns an empty collection on failure. + static OSCertHandles CreateOSCertHandlesFromBytes( + const char* data, + int length, + Format format); + + // Duplicates (or adds a reference to) an OS certificate handle. + static OSCertHandle DupOSCertHandle(OSCertHandle cert_handle); + + // Frees (or releases a reference to) an OS certificate handle. + static void FreeOSCertHandle(OSCertHandle cert_handle); + + // Calculates the SHA-1 fingerprint of the certificate. Returns an empty + // (all zero) fingerprint on failure. + static SHA1HashValue CalculateFingerprint(OSCertHandle cert_handle); + + // Calculates the SHA-1 fingerprint of the intermediate CA certificates. + // Returns an empty (all zero) fingerprint on failure. + static SHA1HashValue CalculateCAFingerprint( + const OSCertHandles& intermediates); + + private: + friend class base::RefCountedThreadSafe<X509Certificate>; + friend class TestRootCerts; // For unit tests + + FRIEND_TEST_ALL_PREFIXES(X509CertificateNameVerifyTest, VerifyHostname); + FRIEND_TEST_ALL_PREFIXES(X509CertificateTest, SerialNumbers); + + // Construct an X509Certificate from a handle to the certificate object + // in the underlying crypto library. + X509Certificate(OSCertHandle cert_handle, + const OSCertHandles& intermediates); + + ~X509Certificate(); + + // Common object initialization code. Called by the constructors only. + void Initialize(); + +#if defined(USE_OPENSSL) + // Resets the store returned by cert_store() to default state. Used by + // TestRootCerts to undo modifications. + static void ResetCertStore(); +#endif + + // Verifies that |hostname| matches one of the certificate names or IP + // addresses supplied, based on TLS name matching rules - specifically, + // following http://tools.ietf.org/html/rfc6125. + // |cert_common_name| is the Subject CN, e.g. from X509Certificate::subject(). + // The members of |cert_san_dns_names| and |cert_san_ipaddrs| must be filled + // from the dNSName and iPAddress components of the subject alternative name + // extension, if present. Note these IP addresses are NOT ascii-encoded: + // they must be 4 or 16 bytes of network-ordered data, for IPv4 and IPv6 + // addresses, respectively. + static bool VerifyHostname(const std::string& hostname, + const std::string& cert_common_name, + const std::vector<std::string>& cert_san_dns_names, + const std::vector<std::string>& cert_san_ip_addrs); + + // Reads a single certificate from |pickle_iter| and returns a + // platform-specific certificate handle. The format of the certificate + // stored in |pickle_iter| is not guaranteed to be the same across different + // underlying cryptographic libraries, nor acceptable to CreateFromBytes(). + // Returns an invalid handle, NULL, on failure. + // NOTE: This should not be used for any new code. It is provided for + // migration purposes and should eventually be removed. + static OSCertHandle ReadOSCertHandleFromPickle(PickleIterator* pickle_iter); + + // Writes a single certificate to |pickle| in DER form. Returns false on + // failure. + static bool WriteOSCertHandleToPickle(OSCertHandle handle, Pickle* pickle); + + // The subject of the certificate. + CertPrincipal subject_; + + // The issuer of the certificate. + CertPrincipal issuer_; + + // This certificate is not valid before |valid_start_| + base::Time valid_start_; + + // This certificate is not valid after |valid_expiry_| + base::Time valid_expiry_; + + // The fingerprint of this certificate. + SHA1HashValue fingerprint_; + + // The fingerprint of the intermediate CA certificates. + SHA1HashValue ca_fingerprint_; + + // The serial number of this certificate, DER encoded. + std::string serial_number_; + + // A handle to the certificate object in the underlying crypto library. + OSCertHandle cert_handle_; + + // Untrusted intermediate certificates associated with this certificate + // that may be needed for chain building. + OSCertHandles intermediate_ca_certs_; + +#if defined(USE_NSS) + // This stores any default nickname that has been set on the certificate + // at creation time with CreateFromBytesWithNickname. + // If this is empty, then GetDefaultNickname will return a generated name + // based on the type of the certificate. + std::string default_nickname_; +#endif + + DISALLOW_COPY_AND_ASSIGN(X509Certificate); +}; + +} // namespace net + +#endif // NET_CERT_X509_CERTIFICATE_H_ diff --git a/net/cert/x509_certificate_ios.cc b/net/cert/x509_certificate_ios.cc new file mode 100644 index 0000000..042f6d5 --- /dev/null +++ b/net/cert/x509_certificate_ios.cc @@ -0,0 +1,245 @@ +// Copyright (c) 2012 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 "net/cert/x509_certificate.h" + +#include <CommonCrypto/CommonDigest.h> +#include <Security/Security.h> +#include <vector> + +#include <cert.h> +#include <cryptohi.h> +#include <keyhi.h> +#include <nss.h> +#include <pk11pub.h> +#include <prerror.h> +#include <prtime.h> +#include <prtypes.h> +#include <secder.h> +#include <secerr.h> +#include <sslerr.h> + +#include "base/logging.h" +#include "base/mac/scoped_cftyperef.h" +#include "base/memory/scoped_ptr.h" +#include "base/pickle.h" +#include "base/time.h" +#include "crypto/nss_util.h" +#include "crypto/scoped_nss_types.h" +#include "net/base/net_errors.h" +#include "net/cert/asn1_util.h" +#include "net/cert/cert_status_flags.h" +#include "net/cert/cert_verify_result.h" +#include "net/cert/ev_root_ca_metadata.h" +#include "net/cert/x509_util_ios.h" +#include "net/cert/x509_util_nss.h" + +using base::mac::ScopedCFTypeRef; + +namespace net { +namespace { +// Returns true if a given |cert_handle| is actually a valid X.509 certificate +// handle. +// +// SecCertificateCreateFromData() does not always force the immediate parsing of +// the certificate, and as such, may return a SecCertificateRef for an +// invalid/unparsable certificate. Force parsing to occur to ensure that the +// SecCertificateRef is correct. On later versions where +// SecCertificateCreateFromData() immediately parses, rather than lazily, this +// call is cheap, as the subject is cached. +bool IsValidOSCertHandle(SecCertificateRef cert_handle) { + ScopedCFTypeRef<CFStringRef> sanity_check( + SecCertificateCopySubjectSummary(cert_handle)); + return sanity_check != NULL; +} +} // namespace + +void X509Certificate::Initialize() { + x509_util_ios::NSSCertificate nss_cert(cert_handle_); + CERTCertificate* cert_handle = nss_cert.cert_handle(); + if (cert_handle) { + x509_util::ParsePrincipal(&cert_handle->subject, &subject_); + x509_util::ParsePrincipal(&cert_handle->issuer, &issuer_); + x509_util::ParseDate(&cert_handle->validity.notBefore, &valid_start_); + x509_util::ParseDate(&cert_handle->validity.notAfter, &valid_expiry_); + serial_number_ = x509_util::ParseSerialNumber(cert_handle); + } + fingerprint_ = CalculateFingerprint(cert_handle_); + ca_fingerprint_ = CalculateCAFingerprint(intermediate_ca_certs_); +} + +bool X509Certificate::IsIssuedByEncoded( + const std::vector<std::string>& valid_issuers) { + x509_util_ios::NSSCertChain nss_chain(this); + // Convert to scoped CERTName* list. + std::vector<CERTName*> issuers; + crypto::ScopedPLArenaPool arena(PORT_NewArena(DER_DEFAULT_CHUNKSIZE)); + if (!x509_util::GetIssuersFromEncodedList(valid_issuers, + arena.get(), + &issuers)) { + return false; + } + return x509_util::IsCertificateIssuedBy( + nss_chain.cert_chain(), issuers); +} + +// static +X509Certificate* X509Certificate::CreateSelfSigned( + crypto::RSAPrivateKey* key, + const std::string& subject, + uint32 serial_number, + base::TimeDelta valid_duration) { + DCHECK(key); + DCHECK(!subject.empty()); + NOTIMPLEMENTED(); + return NULL; +} + +void X509Certificate::GetSubjectAltName( + std::vector<std::string>* dns_names, + std::vector<std::string>* ip_addrs) const { + x509_util_ios::NSSCertificate nss_cert(cert_handle_); + CERTCertificate* cert_handle = nss_cert.cert_handle(); + if (!cert_handle) { + if (dns_names) + dns_names->clear(); + if (ip_addrs) + ip_addrs->clear(); + return; + } + x509_util::GetSubjectAltName(cert_handle, dns_names, ip_addrs); +} + +// static +bool X509Certificate::GetDEREncoded(OSCertHandle cert_handle, + std::string* encoded) { + ScopedCFTypeRef<CFDataRef> der_data(SecCertificateCopyData(cert_handle)); + if (!der_data) + return false; + encoded->assign(reinterpret_cast<const char*>(CFDataGetBytePtr(der_data)), + CFDataGetLength(der_data)); + return true; +} + +// static +bool X509Certificate::IsSameOSCert(X509Certificate::OSCertHandle a, + X509Certificate::OSCertHandle b) { + DCHECK(a && b); + if (a == b) + return true; + if (CFEqual(a, b)) + return true; + ScopedCFTypeRef<CFDataRef> a_data(SecCertificateCopyData(a)); + ScopedCFTypeRef<CFDataRef> b_data(SecCertificateCopyData(b)); + return a_data && b_data && + CFDataGetLength(a_data) == CFDataGetLength(b_data) && + memcmp(CFDataGetBytePtr(a_data), CFDataGetBytePtr(b_data), + CFDataGetLength(a_data)) == 0; +} + +// static +X509Certificate::OSCertHandle X509Certificate::CreateOSCertHandleFromBytes( + const char* data, int length) { + ScopedCFTypeRef<CFDataRef> cert_data(CFDataCreateWithBytesNoCopy( + kCFAllocatorDefault, reinterpret_cast<const UInt8 *>(data), length, + kCFAllocatorNull)); + if (!cert_data) + return NULL; + OSCertHandle cert_handle = SecCertificateCreateWithData(NULL, cert_data); + if (!cert_handle) + return NULL; + if (!IsValidOSCertHandle(cert_handle)) { + CFRelease(cert_handle); + return NULL; + } + return cert_handle; +} + +// static +X509Certificate::OSCertHandles X509Certificate::CreateOSCertHandlesFromBytes( + const char* data, + int length, + Format format) { + return x509_util::CreateOSCertHandlesFromBytes(data, length, format); +} + +// static +X509Certificate::OSCertHandle X509Certificate::DupOSCertHandle( + OSCertHandle handle) { + if (!handle) + return NULL; + return reinterpret_cast<OSCertHandle>(const_cast<void*>(CFRetain(handle))); +} + +// static +void X509Certificate::FreeOSCertHandle(OSCertHandle cert_handle) { + CFRelease(cert_handle); +} + +// static +SHA1HashValue X509Certificate::CalculateFingerprint( + OSCertHandle cert) { + SHA1HashValue sha1; + memset(sha1.data, 0, sizeof(sha1.data)); + + ScopedCFTypeRef<CFDataRef> cert_data(SecCertificateCopyData(cert)); + if (!cert_data) + return sha1; + DCHECK(CFDataGetBytePtr(cert_data)); + DCHECK_NE(0, CFDataGetLength(cert_data)); + CC_SHA1(CFDataGetBytePtr(cert_data), CFDataGetLength(cert_data), sha1.data); + + return sha1; +} + +// static +SHA1HashValue X509Certificate::CalculateCAFingerprint( + const OSCertHandles& intermediates) { + SHA1HashValue sha1; + memset(sha1.data, 0, sizeof(sha1.data)); + + // The CC_SHA(3cc) man page says all CC_SHA1_xxx routines return 1, so + // we don't check their return values. + CC_SHA1_CTX sha1_ctx; + CC_SHA1_Init(&sha1_ctx); + for (size_t i = 0; i < intermediates.size(); ++i) { + ScopedCFTypeRef<CFDataRef> + cert_data(SecCertificateCopyData(intermediates[i])); + if (!cert_data) + return sha1; + CC_SHA1_Update(&sha1_ctx, + CFDataGetBytePtr(cert_data), + CFDataGetLength(cert_data)); + } + CC_SHA1_Final(sha1.data, &sha1_ctx); + return sha1; +} + +// static +X509Certificate::OSCertHandle +X509Certificate::ReadOSCertHandleFromPickle(PickleIterator* pickle_iter) { + return x509_util::ReadOSCertHandleFromPickle(pickle_iter); +} + +// static +bool X509Certificate::WriteOSCertHandleToPickle(OSCertHandle cert_handle, + Pickle* pickle) { + ScopedCFTypeRef<CFDataRef> cert_data(SecCertificateCopyData(cert_handle)); + if (!cert_data) + return false; + + return pickle->WriteData( + reinterpret_cast<const char*>(CFDataGetBytePtr(cert_data)), + CFDataGetLength(cert_data)); +} + +// static +void X509Certificate::GetPublicKeyInfo(OSCertHandle cert_handle, + size_t* size_bits, + PublicKeyType* type) { + x509_util_ios::NSSCertificate nss_cert(cert_handle); + x509_util::GetPublicKeyInfo(nss_cert.cert_handle(), size_bits, type); +} + +} // namespace net diff --git a/net/cert/x509_certificate_known_roots_mac.h b/net/cert/x509_certificate_known_roots_mac.h new file mode 100644 index 0000000..db5403e --- /dev/null +++ b/net/cert/x509_certificate_known_roots_mac.h @@ -0,0 +1,342 @@ +// Copyright (c) 2011 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. + +#ifndef NET_CERT_X509_CERTIFICATE_KNOWN_ROOTS_MAC_H_ +#define NET_CERT_X509_CERTIFICATE_KNOWN_ROOTS_MAC_H_ + +// This is the set of Apple trusted roots from OS X 10.6. +// They were taken by exporting the trusted roots from the Keychain utility. +// +// Note that these *are not* trust anchors for Chromium. They are only used to +// distinguish `real' root CAs from roots that were user-installed. +static uint8 kKnownRootCertSHA1Hashes[][20] = { + {0x01, 0x68, 0x97, 0xe1, 0xa0, 0xb8, 0xf2, 0xc3, 0xb1, 0x34, + 0x66, 0x5c, 0x20, 0xa7, 0x27, 0xb7, 0xa1, 0x58, 0xe2, 0x8f}, + {0x02, 0x72, 0x68, 0x29, 0x3e, 0x5f, 0x5d, 0x17, 0xaa, 0xa4, + 0xb3, 0xc3, 0xe6, 0x36, 0x1e, 0x1f, 0x92, 0x57, 0x5e, 0xaa}, + {0x02, 0xfa, 0xf3, 0xe2, 0x91, 0x43, 0x54, 0x68, 0x60, 0x78, + 0x57, 0x69, 0x4d, 0xf5, 0xe4, 0x5b, 0x68, 0x85, 0x18, 0x68}, + {0x04, 0x83, 0xed, 0x33, 0x99, 0xac, 0x36, 0x08, 0x05, 0x87, + 0x22, 0xed, 0xbc, 0x5e, 0x46, 0x00, 0xe3, 0xbe, 0xf9, 0xd7}, + {0x05, 0x63, 0xb8, 0x63, 0x0d, 0x62, 0xd7, 0x5a, 0xbb, 0xc8, + 0xab, 0x1e, 0x4b, 0xdf, 0xb5, 0xa8, 0x99, 0xb2, 0x4d, 0x43}, + {0x06, 0x08, 0x3f, 0x59, 0x3f, 0x15, 0xa1, 0x04, 0xa0, 0x69, + 0xa4, 0x6b, 0xa9, 0x03, 0xd0, 0x06, 0xb7, 0x97, 0x09, 0x91}, + {0x07, 0x47, 0x22, 0x01, 0x99, 0xce, 0x74, 0xb9, 0x7c, 0xb0, + 0x3d, 0x79, 0xb2, 0x64, 0xa2, 0xc8, 0x55, 0xe9, 0x33, 0xff}, + {0x07, 0xe0, 0x32, 0xe0, 0x20, 0xb7, 0x2c, 0x3f, 0x19, 0x2f, + 0x06, 0x28, 0xa2, 0x59, 0x3a, 0x19, 0xa7, 0x0f, 0x06, 0x9e}, + {0x08, 0x64, 0x18, 0xe9, 0x06, 0xce, 0xe8, 0x9c, 0x23, 0x53, + 0xb6, 0xe2, 0x7f, 0xbd, 0x9e, 0x74, 0x39, 0xf7, 0x63, 0x16}, + {0x0b, 0x77, 0xbe, 0xbb, 0xcb, 0x7a, 0xa2, 0x47, 0x05, 0xde, + 0xcc, 0x0f, 0xbd, 0x6a, 0x02, 0xfc, 0x7a, 0xbd, 0x9b, 0x52}, + {0x10, 0x1d, 0xfa, 0x3f, 0xd5, 0x0b, 0xcb, 0xbb, 0x9b, 0xb5, + 0x60, 0x0c, 0x19, 0x55, 0xa4, 0x1a, 0xf4, 0x73, 0x3a, 0x04}, + {0x10, 0xf1, 0x93, 0xf3, 0x40, 0xac, 0x91, 0xd6, 0xde, 0x5f, + 0x1e, 0xdc, 0x00, 0x62, 0x47, 0xc4, 0xf2, 0x5d, 0x96, 0x71}, + {0x13, 0x2d, 0x0d, 0x45, 0x53, 0x4b, 0x69, 0x97, 0xcd, 0xb2, + 0xd5, 0xc3, 0x39, 0xe2, 0x55, 0x76, 0x60, 0x9b, 0x5c, 0xc6}, + {0x1f, 0x49, 0x14, 0xf7, 0xd8, 0x74, 0x95, 0x1d, 0xdd, 0xae, + 0x02, 0xc0, 0xbe, 0xfd, 0x3a, 0x2d, 0x82, 0x75, 0x51, 0x85}, + {0x20, 0x42, 0x85, 0xdc, 0xf7, 0xeb, 0x76, 0x41, 0x95, 0x57, + 0x8e, 0x13, 0x6b, 0xd4, 0xb7, 0xd1, 0xe9, 0x8e, 0x46, 0xa5}, + {0x20, 0x99, 0x00, 0xb6, 0x3d, 0x95, 0x57, 0x28, 0x14, 0x0c, + 0xd1, 0x36, 0x22, 0xd8, 0xc6, 0x87, 0xa4, 0xeb, 0x00, 0x85}, + {0x21, 0xfc, 0xbd, 0x8e, 0x7f, 0x6c, 0xaf, 0x05, 0x1b, 0xd1, + 0xb3, 0x43, 0xec, 0xa8, 0xe7, 0x61, 0x47, 0xf2, 0x0f, 0x8a}, + {0x23, 0xe5, 0x94, 0x94, 0x51, 0x95, 0xf2, 0x41, 0x48, 0x03, + 0xb4, 0xd5, 0x64, 0xd2, 0xa3, 0xa3, 0xf5, 0xd8, 0x8b, 0x8c}, + {0x25, 0x01, 0x90, 0x19, 0xcf, 0xfb, 0xd9, 0x99, 0x1c, 0xb7, + 0x68, 0x25, 0x74, 0x8d, 0x94, 0x5f, 0x30, 0x93, 0x95, 0x42}, + {0x25, 0x3f, 0x77, 0x5b, 0x0e, 0x77, 0x97, 0xab, 0x64, 0x5f, + 0x15, 0x91, 0x55, 0x97, 0xc3, 0x9e, 0x26, 0x36, 0x31, 0xd1}, + {0x27, 0x0c, 0x50, 0x0c, 0xc6, 0xc8, 0x6e, 0xcb, 0x19, 0x80, + 0xbc, 0x13, 0x05, 0x43, 0x9e, 0xd2, 0x82, 0x48, 0x0b, 0xe3}, + {0x27, 0x3e, 0xe1, 0x24, 0x57, 0xfd, 0xc4, 0xf9, 0x0c, 0x55, + 0xe8, 0x2b, 0x56, 0x16, 0x7f, 0x62, 0xf5, 0x32, 0xe5, 0x47}, + {0x27, 0x96, 0xba, 0xe6, 0x3f, 0x18, 0x01, 0xe2, 0x77, 0x26, + 0x1b, 0xa0, 0xd7, 0x77, 0x70, 0x02, 0x8f, 0x20, 0xee, 0xe4}, + {0x29, 0x36, 0x21, 0x02, 0x8b, 0x20, 0xed, 0x02, 0xf5, 0x66, + 0xc5, 0x32, 0xd1, 0xd6, 0xed, 0x90, 0x9f, 0x45, 0x00, 0x2f}, + {0x29, 0x64, 0xb6, 0x86, 0x13, 0x5b, 0x5d, 0xfd, 0xdd, 0x32, + 0x53, 0xa8, 0x9b, 0xbc, 0x24, 0xd7, 0x4b, 0x08, 0xc6, 0x4d}, + {0x2a, 0xb6, 0x28, 0x48, 0x5e, 0x78, 0xfb, 0xf3, 0xad, 0x9e, + 0x79, 0x10, 0xdd, 0x6b, 0xdf, 0x99, 0x72, 0x2c, 0x96, 0xe5}, + {0x2a, 0xc8, 0xd5, 0x8b, 0x57, 0xce, 0xbf, 0x2f, 0x49, 0xaf, + 0xf2, 0xfc, 0x76, 0x8f, 0x51, 0x14, 0x62, 0x90, 0x7a, 0x41}, + {0x2c, 0xeb, 0x05, 0x34, 0xad, 0x59, 0x27, 0x18, 0x0d, 0x34, + 0x8c, 0x5f, 0x0e, 0x05, 0x6d, 0x38, 0x2b, 0x50, 0x82, 0x87}, + {0x2d, 0xff, 0x63, 0x36, 0xe3, 0x3a, 0x48, 0x29, 0xaa, 0x00, + 0x9f, 0x01, 0xa1, 0x80, 0x1e, 0xe7, 0xeb, 0xa5, 0x82, 0xbb}, + {0x2e, 0xf6, 0x4b, 0xba, 0x77, 0xdd, 0x37, 0x79, 0xe9, 0x1f, + 0xbd, 0x5a, 0x4e, 0xee, 0x63, 0x3c, 0x8a, 0x36, 0xa5, 0xb1}, + {0x2f, 0x17, 0x3f, 0x7d, 0xe9, 0x96, 0x67, 0xaf, 0xa5, 0x7a, + 0xf8, 0x0a, 0xa2, 0xd1, 0xb1, 0x2f, 0xac, 0x83, 0x03, 0x38}, + {0x30, 0x77, 0x9e, 0x93, 0x15, 0x02, 0x2e, 0x94, 0x85, 0x6a, + 0x3f, 0xf8, 0xbc, 0xf8, 0x15, 0xb0, 0x82, 0xf9, 0xae, 0xfd}, + {0x31, 0x7a, 0x2a, 0xd0, 0x7f, 0x2b, 0x33, 0x5e, 0xf5, 0xa1, + 0xc3, 0x4e, 0x4b, 0x57, 0xe8, 0xb7, 0xd8, 0xf1, 0xfc, 0xa6}, + {0x32, 0x3c, 0x11, 0x8e, 0x1b, 0xf7, 0xb8, 0xb6, 0x52, 0x54, + 0xe2, 0xe2, 0x10, 0x0d, 0xd6, 0x02, 0x90, 0x37, 0xf0, 0x96}, + {0x33, 0x9b, 0x6b, 0x14, 0x50, 0x24, 0x9b, 0x55, 0x7a, 0x01, + 0x87, 0x72, 0x84, 0xd9, 0xe0, 0x2f, 0xc3, 0xd2, 0xd8, 0xe9}, + {0x36, 0x86, 0x35, 0x63, 0xfd, 0x51, 0x28, 0xc7, 0xbe, 0xa6, + 0xf0, 0x05, 0xcf, 0xe9, 0xb4, 0x36, 0x68, 0x08, 0x6c, 0xce}, + {0x36, 0xb1, 0x2b, 0x49, 0xf9, 0x81, 0x9e, 0xd7, 0x4c, 0x9e, + 0xbc, 0x38, 0x0f, 0xc6, 0x56, 0x8f, 0x5d, 0xac, 0xb2, 0xf7}, + {0x37, 0xf7, 0x6d, 0xe6, 0x07, 0x7c, 0x90, 0xc5, 0xb1, 0x3e, + 0x93, 0x1a, 0xb7, 0x41, 0x10, 0xb4, 0xf2, 0xe4, 0x9a, 0x27}, + {0x39, 0x21, 0xc1, 0x15, 0xc1, 0x5d, 0x0e, 0xca, 0x5c, 0xcb, + 0x5b, 0xc4, 0xf0, 0x7d, 0x21, 0xd8, 0x05, 0x0b, 0x56, 0x6a}, + {0x39, 0x4f, 0xf6, 0x85, 0x0b, 0x06, 0xbe, 0x52, 0xe5, 0x18, + 0x56, 0xcc, 0x10, 0xe1, 0x80, 0xe8, 0x82, 0xb3, 0x85, 0xcc}, + {0x3a, 0x32, 0xef, 0x7b, 0x9a, 0xb8, 0x36, 0xf8, 0x37, 0x18, + 0x1a, 0x4c, 0xef, 0xa3, 0x55, 0xc6, 0x46, 0x67, 0xac, 0xbf}, + {0x3a, 0x44, 0x73, 0x5a, 0xe5, 0x81, 0x90, 0x1f, 0x24, 0x86, + 0x61, 0x46, 0x1e, 0x3b, 0x9c, 0xc4, 0x5f, 0xf5, 0x3a, 0x1b}, + {0x3e, 0x2b, 0xf7, 0xf2, 0x03, 0x1b, 0x96, 0xf3, 0x8c, 0xe6, + 0xc4, 0xd8, 0xa8, 0x5d, 0x3e, 0x2d, 0x58, 0x47, 0x6a, 0x0f}, + {0x40, 0x54, 0xda, 0x6f, 0x1c, 0x3f, 0x40, 0x74, 0xac, 0xed, + 0x0f, 0xec, 0xcd, 0xdb, 0x79, 0xd1, 0x53, 0xfb, 0x90, 0x1d}, + {0x40, 0x9d, 0x4b, 0xd9, 0x17, 0xb5, 0x5c, 0x27, 0xb6, 0x9b, + 0x64, 0xcb, 0x98, 0x22, 0x44, 0x0d, 0xcd, 0x09, 0xb8, 0x89}, + {0x40, 0xe7, 0x8c, 0x1d, 0x52, 0x3d, 0x1c, 0xd9, 0x95, 0x4f, + 0xac, 0x1a, 0x1a, 0xb3, 0xbd, 0x3c, 0xba, 0xa1, 0x5b, 0xfc}, + {0x43, 0xf9, 0xb1, 0x10, 0xd5, 0xba, 0xfd, 0x48, 0x22, 0x52, + 0x31, 0xb0, 0xd0, 0x08, 0x2b, 0x37, 0x2f, 0xef, 0x9a, 0x54}, + {0x4a, 0x3f, 0x8d, 0x6b, 0xdc, 0x0e, 0x1e, 0xcf, 0xcd, 0x72, + 0xe3, 0x77, 0xde, 0xf2, 0xd7, 0xff, 0x92, 0xc1, 0x9b, 0xc7}, + {0x4a, 0x65, 0xd5, 0xf4, 0x1d, 0xef, 0x39, 0xb8, 0xb8, 0x90, + 0x4a, 0x4a, 0xd3, 0x64, 0x81, 0x33, 0xcf, 0xc7, 0xa1, 0xd1}, + {0x4a, 0xd4, 0x4d, 0x4d, 0x81, 0x2e, 0x42, 0x23, 0x2f, 0xe0, + 0x38, 0x76, 0x4c, 0x7b, 0x0c, 0xeb, 0x46, 0x6e, 0xef, 0x96}, + {0x4c, 0xab, 0x31, 0xa1, 0x28, 0x34, 0x02, 0x52, 0xbc, 0xb4, + 0x67, 0xd6, 0x2a, 0x99, 0x63, 0x1b, 0x21, 0x77, 0x20, 0x50}, + {0x4d, 0x23, 0x78, 0xec, 0x91, 0x95, 0x39, 0xb5, 0x00, 0x7f, + 0x75, 0x8f, 0x03, 0x3b, 0x21, 0x1e, 0xc5, 0x4d, 0x8b, 0xcf}, + {0x4e, 0xb6, 0xd5, 0x78, 0x49, 0x9b, 0x1c, 0xcf, 0x5f, 0x58, + 0x1e, 0xad, 0x56, 0xbe, 0x3d, 0x9b, 0x67, 0x44, 0xa5, 0xe5}, + {0x50, 0x30, 0x06, 0x09, 0x1d, 0x97, 0xd4, 0xf5, 0xae, 0x39, + 0xf7, 0xcb, 0xe7, 0x92, 0x7d, 0x7d, 0x65, 0x2d, 0x34, 0x31}, + {0x51, 0xa4, 0x4c, 0x28, 0xf3, 0x13, 0xe3, 0xf9, 0xcb, 0x5e, + 0x7c, 0x0a, 0x1e, 0x0e, 0x0d, 0xd2, 0x84, 0x37, 0x58, 0xae}, + {0x51, 0xcc, 0xa0, 0x71, 0x0a, 0xf7, 0x73, 0x3d, 0x34, 0xac, + 0xdc, 0x19, 0x45, 0x09, 0x9f, 0x43, 0x5c, 0x7f, 0xc5, 0x9f}, + {0x56, 0x4b, 0x6f, 0x8c, 0x56, 0x38, 0xdc, 0x05, 0x5b, 0xba, + 0x2b, 0xa1, 0x39, 0x0f, 0x7e, 0x31, 0x95, 0x4a, 0x55, 0x50}, + {0x56, 0xe0, 0xfa, 0xc0, 0x3b, 0x8f, 0x18, 0x23, 0x55, 0x18, + 0xe5, 0xd3, 0x11, 0xca, 0xe8, 0xc2, 0x43, 0x31, 0xab, 0x66}, + {0x57, 0xf0, 0x3d, 0xce, 0xfb, 0x45, 0x69, 0x4c, 0x1c, 0x25, + 0xe6, 0xee, 0xa0, 0x2c, 0x43, 0xd7, 0x52, 0x38, 0xd3, 0xc4}, + {0x58, 0x0f, 0x80, 0x47, 0x92, 0xab, 0xc6, 0x3b, 0xbb, 0x80, + 0x15, 0x4d, 0x4d, 0xfd, 0xdd, 0x8b, 0x2e, 0xf2, 0x67, 0x4e}, + {0x58, 0x11, 0x9f, 0x0e, 0x12, 0x82, 0x87, 0xea, 0x50, 0xfd, + 0xd9, 0x87, 0x45, 0x6f, 0x4f, 0x78, 0xdc, 0xfa, 0xd6, 0xd4}, + {0x59, 0x22, 0xa1, 0xe1, 0x5a, 0xea, 0x16, 0x35, 0x21, 0xf8, + 0x98, 0x39, 0x6a, 0x46, 0x46, 0xb0, 0x44, 0x1b, 0x0f, 0xa9}, + {0x59, 0xaf, 0x82, 0x79, 0x91, 0x86, 0xc7, 0xb4, 0x75, 0x07, + 0xcb, 0xcf, 0x03, 0x57, 0x46, 0xeb, 0x04, 0xdd, 0xb7, 0x16}, + {0x5d, 0x98, 0x9c, 0xdb, 0x15, 0x96, 0x11, 0x36, 0x51, 0x65, + 0x64, 0x1b, 0x56, 0x0f, 0xdb, 0xea, 0x2a, 0xc2, 0x3e, 0xf1}, + {0x5f, 0x3a, 0xfc, 0x0a, 0x8b, 0x64, 0xf6, 0x86, 0x67, 0x34, + 0x74, 0xdf, 0x7e, 0xa9, 0xa2, 0xfe, 0xf9, 0xfa, 0x7a, 0x51}, + {0x5f, 0x4e, 0x1f, 0xcf, 0x31, 0xb7, 0x91, 0x3b, 0x85, 0x0b, + 0x54, 0xf6, 0xe5, 0xff, 0x50, 0x1a, 0x2b, 0x6f, 0xc6, 0xcf}, + {0x5f, 0xb7, 0xee, 0x06, 0x33, 0xe2, 0x59, 0xdb, 0xad, 0x0c, + 0x4c, 0x9a, 0xe6, 0xd3, 0x8f, 0x1a, 0x61, 0xc7, 0xdc, 0x25}, + {0x61, 0x1e, 0x5b, 0x66, 0x2c, 0x59, 0x3a, 0x08, 0xff, 0x58, + 0xd1, 0x4a, 0xe2, 0x24, 0x52, 0xd1, 0x98, 0xdf, 0x6c, 0x60}, + {0x61, 0x57, 0x3a, 0x11, 0xdf, 0x0e, 0xd8, 0x7e, 0xd5, 0x92, + 0x65, 0x22, 0xea, 0xd0, 0x56, 0xd7, 0x44, 0xb3, 0x23, 0x71}, + {0x61, 0xef, 0x43, 0xd7, 0x7f, 0xca, 0xd4, 0x61, 0x51, 0xbc, + 0x98, 0xe0, 0xc3, 0x59, 0x12, 0xaf, 0x9f, 0xeb, 0x63, 0x11}, + {0x62, 0x52, 0xdc, 0x40, 0xf7, 0x11, 0x43, 0xa2, 0x2f, 0xde, + 0x9e, 0xf7, 0x34, 0x8e, 0x06, 0x42, 0x51, 0xb1, 0x81, 0x18}, + {0x62, 0x7f, 0x8d, 0x78, 0x27, 0x65, 0x63, 0x99, 0xd2, 0x7d, + 0x7f, 0x90, 0x44, 0xc9, 0xfe, 0xb3, 0xf3, 0x3e, 0xfa, 0x9a}, + {0x66, 0x31, 0xbf, 0x9e, 0xf7, 0x4f, 0x9e, 0xb6, 0xc9, 0xd5, + 0xa6, 0x0c, 0xba, 0x6a, 0xbe, 0xd1, 0xf7, 0xbd, 0xef, 0x7b}, + {0x67, 0x65, 0x0d, 0xf1, 0x7e, 0x8e, 0x7e, 0x5b, 0x82, 0x40, + 0xa4, 0xf4, 0x56, 0x4b, 0xcf, 0xe2, 0x3d, 0x69, 0xc6, 0xf0}, + {0x67, 0x82, 0xaa, 0xe0, 0xed, 0xee, 0xe2, 0x1a, 0x58, 0x39, + 0xd3, 0xc0, 0xcd, 0x14, 0x68, 0x0a, 0x4f, 0x60, 0x14, 0x2a}, + {0x67, 0x9a, 0x4f, 0x81, 0xfc, 0x70, 0x5d, 0xde, 0xc4, 0x19, + 0x77, 0x8d, 0xd2, 0xeb, 0xd8, 0x75, 0xf4, 0xc2, 0x42, 0xc6}, + {0x69, 0xbd, 0x8c, 0xf4, 0x9c, 0xd3, 0x00, 0xfb, 0x59, 0x2e, + 0x17, 0x93, 0xca, 0x55, 0x6a, 0xf3, 0xec, 0xaa, 0x35, 0xfb}, + {0x6a, 0x84, 0xfe, 0x62, 0x7e, 0xcc, 0x49, 0xa1, 0xbe, 0x02, + 0xe9, 0x18, 0xfa, 0xc9, 0xe1, 0xf7, 0x32, 0x80, 0x3a, 0x62}, + {0x6b, 0x2f, 0x34, 0xad, 0x89, 0x58, 0xbe, 0x62, 0xfd, 0xb0, + 0x6b, 0x5c, 0xce, 0xbb, 0x9d, 0xd9, 0x4f, 0x4e, 0x39, 0xf3}, + {0x6b, 0x81, 0x44, 0x6a, 0x5c, 0xdd, 0xf4, 0x74, 0xa0, 0xf8, + 0x00, 0xff, 0xbe, 0x69, 0xfd, 0x0d, 0xb6, 0x28, 0x75, 0x16}, + {0x6e, 0x3a, 0x55, 0xa4, 0x19, 0x0c, 0x19, 0x5c, 0x93, 0x84, + 0x3c, 0xc0, 0xdb, 0x72, 0x2e, 0x31, 0x30, 0x61, 0xf0, 0xb1}, + {0x70, 0x17, 0x9b, 0x86, 0x8c, 0x00, 0xa4, 0xfa, 0x60, 0x91, + 0x52, 0x22, 0x3f, 0x9f, 0x3e, 0x32, 0xbd, 0xe0, 0x05, 0x62}, + {0x74, 0x20, 0x74, 0x41, 0x72, 0x9c, 0xdd, 0x92, 0xec, 0x79, + 0x31, 0xd8, 0x23, 0x10, 0x8d, 0xc2, 0x81, 0x92, 0xe2, 0xbb}, + {0x74, 0x2c, 0x31, 0x92, 0xe6, 0x07, 0xe4, 0x24, 0xeb, 0x45, + 0x49, 0x54, 0x2b, 0xe1, 0xbb, 0xc5, 0x3e, 0x61, 0x74, 0xe2}, + {0x74, 0x54, 0x53, 0x5c, 0x24, 0xa3, 0xa7, 0x58, 0x20, 0x7e, + 0x3e, 0x3e, 0xd3, 0x24, 0xf8, 0x16, 0xfb, 0x21, 0x16, 0x49}, + {0x74, 0xa2, 0x66, 0xf0, 0x95, 0xa9, 0xa4, 0xeb, 0x95, 0x22, + 0x19, 0xd6, 0x05, 0xda, 0x93, 0x63, 0xf5, 0x14, 0xfa, 0xf9}, + {0x74, 0xf8, 0xa3, 0xc3, 0xef, 0xe7, 0xb3, 0x90, 0x06, 0x4b, + 0x83, 0x90, 0x3c, 0x21, 0x64, 0x60, 0x20, 0xe5, 0xdf, 0xce}, + {0x75, 0xe0, 0xab, 0xb6, 0x13, 0x85, 0x12, 0x27, 0x1c, 0x04, + 0xf8, 0x5f, 0xdd, 0xde, 0x38, 0xe4, 0xb7, 0x24, 0x2e, 0xfe}, + {0x79, 0x98, 0xa3, 0x08, 0xe1, 0x4d, 0x65, 0x85, 0xe6, 0xc2, + 0x1e, 0x15, 0x3a, 0x71, 0x9f, 0xba, 0x5a, 0xd3, 0x4a, 0xd9}, + {0x7e, 0x78, 0x4a, 0x10, 0x1c, 0x82, 0x65, 0xcc, 0x2d, 0xe1, + 0xf1, 0x6d, 0x47, 0xb4, 0x40, 0xca, 0xd9, 0x0a, 0x19, 0x45}, + {0x7f, 0x8a, 0xb0, 0xcf, 0xd0, 0x51, 0x87, 0x6a, 0x66, 0xf3, + 0x36, 0x0f, 0x47, 0xc8, 0x8d, 0x8c, 0xd3, 0x35, 0xfc, 0x74}, + {0x80, 0x1d, 0x62, 0xd0, 0x7b, 0x44, 0x9d, 0x5c, 0x5c, 0x03, + 0x5c, 0x98, 0xea, 0x61, 0xfa, 0x44, 0x3c, 0x2a, 0x58, 0xfe}, + {0x80, 0x25, 0xef, 0xf4, 0x6e, 0x70, 0xc8, 0xd4, 0x72, 0x24, + 0x65, 0x84, 0xfe, 0x40, 0x3b, 0x8a, 0x8d, 0x6a, 0xdb, 0xf5}, + {0x85, 0x37, 0x1c, 0xa6, 0xe5, 0x50, 0x14, 0x3d, 0xce, 0x28, + 0x03, 0x47, 0x1b, 0xde, 0x3a, 0x09, 0xe8, 0xf8, 0x77, 0x0f}, + {0x85, 0xa4, 0x08, 0xc0, 0x9c, 0x19, 0x3e, 0x5d, 0x51, 0x58, + 0x7d, 0xcd, 0xd6, 0x13, 0x30, 0xfd, 0x8c, 0xde, 0x37, 0xbf}, + {0x85, 0xb5, 0xff, 0x67, 0x9b, 0x0c, 0x79, 0x96, 0x1f, 0xc8, + 0x6e, 0x44, 0x22, 0x00, 0x46, 0x13, 0xdb, 0x17, 0x92, 0x84}, + {0x87, 0x81, 0xc2, 0x5a, 0x96, 0xbd, 0xc2, 0xfb, 0x4c, 0x65, + 0x06, 0x4f, 0xf9, 0x39, 0x0b, 0x26, 0x04, 0x8a, 0x0e, 0x01}, + {0x87, 0x82, 0xc6, 0xc3, 0x04, 0x35, 0x3b, 0xcf, 0xd2, 0x96, + 0x92, 0xd2, 0x59, 0x3e, 0x7d, 0x44, 0xd9, 0x34, 0xff, 0x11}, + {0x87, 0x9f, 0x4b, 0xee, 0x05, 0xdf, 0x98, 0x58, 0x3b, 0xe3, + 0x60, 0xd6, 0x33, 0xe7, 0x0d, 0x3f, 0xfe, 0x98, 0x71, 0xaf}, + {0x8b, 0xaf, 0x4c, 0x9b, 0x1d, 0xf0, 0x2a, 0x92, 0xf7, 0xda, + 0x12, 0x8e, 0xb9, 0x1b, 0xac, 0xf4, 0x98, 0x60, 0x4b, 0x6f}, + {0x8c, 0x94, 0x1b, 0x34, 0xea, 0x1e, 0xa6, 0xed, 0x9a, 0xe2, + 0xbc, 0x54, 0xcf, 0x68, 0x72, 0x52, 0xb4, 0xc9, 0xb5, 0x61}, + {0x8c, 0x96, 0xba, 0xeb, 0xdd, 0x2b, 0x07, 0x07, 0x48, 0xee, + 0x30, 0x32, 0x66, 0xa0, 0xf3, 0x98, 0x6e, 0x7c, 0xae, 0x58}, + {0x8c, 0xc4, 0x30, 0x7b, 0xc6, 0x07, 0x55, 0xe7, 0xb2, 0x2d, + 0xd9, 0xf7, 0xfe, 0xa2, 0x45, 0x93, 0x6c, 0x7c, 0xf2, 0x88}, + {0x90, 0xae, 0xa2, 0x69, 0x85, 0xff, 0x14, 0x80, 0x4c, 0x43, + 0x49, 0x52, 0xec, 0xe9, 0x60, 0x84, 0x77, 0xaf, 0x55, 0x6f}, + {0x91, 0xc6, 0xd6, 0xee, 0x3e, 0x8a, 0xc8, 0x63, 0x84, 0xe5, + 0x48, 0xc2, 0x99, 0x29, 0x5c, 0x75, 0x6c, 0x81, 0x7b, 0x81}, + {0x93, 0xe6, 0xab, 0x22, 0x03, 0x03, 0xb5, 0x23, 0x28, 0xdc, + 0xda, 0x56, 0x9e, 0xba, 0xe4, 0xd1, 0xd1, 0xcc, 0xfb, 0x65}, + {0x96, 0x56, 0xcd, 0x7b, 0x57, 0x96, 0x98, 0x95, 0xd0, 0xe1, + 0x41, 0x46, 0x68, 0x06, 0xfb, 0xb8, 0xc6, 0x11, 0x06, 0x87}, + {0x96, 0x83, 0x38, 0xf1, 0x13, 0xe3, 0x6a, 0x7b, 0xab, 0xdd, + 0x08, 0xf7, 0x77, 0x63, 0x91, 0xa6, 0x87, 0x36, 0x58, 0x2e}, + {0x97, 0x81, 0x79, 0x50, 0xd8, 0x1c, 0x96, 0x70, 0xcc, 0x34, + 0xd8, 0x09, 0xcf, 0x79, 0x44, 0x31, 0x36, 0x7e, 0xf4, 0x74}, + {0x99, 0xa6, 0x9b, 0xe6, 0x1a, 0xfe, 0x88, 0x6b, 0x4d, 0x2b, + 0x82, 0x00, 0x7c, 0xb8, 0x54, 0xfc, 0x31, 0x7e, 0x15, 0x39}, + {0x9b, 0xaa, 0xe5, 0x9f, 0x56, 0xee, 0x21, 0xcb, 0x43, 0x5a, + 0xbe, 0x25, 0x93, 0xdf, 0xa7, 0xf0, 0x40, 0xd1, 0x1d, 0xcb}, + {0xa0, 0xa1, 0xab, 0x90, 0xc9, 0xfc, 0x84, 0x7b, 0x3b, 0x12, + 0x61, 0xe8, 0x97, 0x7d, 0x5f, 0xd3, 0x22, 0x61, 0xd3, 0xcc}, + {0xa1, 0xdb, 0x63, 0x93, 0x91, 0x6f, 0x17, 0xe4, 0x18, 0x55, + 0x09, 0x40, 0x04, 0x15, 0xc7, 0x02, 0x40, 0xb0, 0xae, 0x6b}, + {0xa6, 0x76, 0xdb, 0xf1, 0x92, 0x48, 0xf5, 0x2c, 0x57, 0x53, + 0xd0, 0xda, 0xc1, 0x4c, 0x53, 0xc4, 0x74, 0xa4, 0x83, 0x5e}, + {0xa6, 0x9a, 0x91, 0xfd, 0x05, 0x7f, 0x13, 0x6a, 0x42, 0x63, + 0x0b, 0xb1, 0x76, 0x0d, 0x2d, 0x51, 0x12, 0x0c, 0x16, 0x50}, + {0xa8, 0x98, 0x5d, 0x3a, 0x65, 0xe5, 0xe5, 0xc4, 0xb2, 0xd7, + 0xd6, 0x6d, 0x40, 0xc6, 0xdd, 0x2f, 0xb1, 0x9c, 0x54, 0x36}, + {0xac, 0xed, 0x5f, 0x65, 0x53, 0xfd, 0x25, 0xce, 0x01, 0x5f, + 0x1f, 0x7a, 0x48, 0x3b, 0x6a, 0x74, 0x9f, 0x61, 0x78, 0xc6}, + {0xad, 0x7e, 0x1c, 0x28, 0xb0, 0x64, 0xef, 0x8f, 0x60, 0x03, + 0x40, 0x20, 0x14, 0xc3, 0xd0, 0xe3, 0x37, 0x0e, 0xb5, 0x8a}, + {0xae, 0x50, 0x83, 0xed, 0x7c, 0xf4, 0x5c, 0xbc, 0x8f, 0x61, + 0xc6, 0x21, 0xfe, 0x68, 0x5d, 0x79, 0x42, 0x21, 0x15, 0x6e}, + {0xb1, 0x2e, 0x13, 0x63, 0x45, 0x86, 0xa4, 0x6f, 0x1a, 0xb2, + 0x60, 0x68, 0x37, 0x58, 0x2d, 0xc4, 0xac, 0xfd, 0x94, 0x97}, + {0xb1, 0x72, 0xb1, 0xa5, 0x6d, 0x95, 0xf9, 0x1f, 0xe5, 0x02, + 0x87, 0xe1, 0x4d, 0x37, 0xea, 0x6a, 0x44, 0x63, 0x76, 0x8a}, + {0xb1, 0xbc, 0x96, 0x8b, 0xd4, 0xf4, 0x9d, 0x62, 0x2a, 0xa8, + 0x9a, 0x81, 0xf2, 0x15, 0x01, 0x52, 0xa4, 0x1d, 0x82, 0x9c}, + {0xb3, 0x1e, 0xb1, 0xb7, 0x40, 0xe3, 0x6c, 0x84, 0x02, 0xda, + 0xdc, 0x37, 0xd4, 0x4d, 0xf5, 0xd4, 0x67, 0x49, 0x52, 0xf9}, + {0xb3, 0xea, 0xc4, 0x47, 0x76, 0xc9, 0xc8, 0x1c, 0xea, 0xf2, + 0x9d, 0x95, 0xb6, 0xcc, 0xa0, 0x08, 0x1b, 0x67, 0xec, 0x9d}, + {0xb4, 0x35, 0xd4, 0xe1, 0x11, 0x9d, 0x1c, 0x66, 0x90, 0xa7, + 0x49, 0xeb, 0xb3, 0x94, 0xbd, 0x63, 0x7b, 0xa7, 0x82, 0xb7}, + {0xb4, 0x57, 0x12, 0x1e, 0x63, 0x45, 0xff, 0x93, 0x5d, 0x6b, + 0x1c, 0xa2, 0xdd, 0xf4, 0x52, 0x3c, 0xc6, 0xd0, 0xef, 0x6b}, + {0xb6, 0xca, 0x21, 0x5b, 0x83, 0x6c, 0x35, 0x10, 0x1d, 0xaf, + 0x74, 0x63, 0x90, 0x0a, 0x93, 0x68, 0x80, 0x76, 0x7a, 0xa6}, + {0xb8, 0x01, 0x86, 0xd1, 0xeb, 0x9c, 0x86, 0xa5, 0x41, 0x04, + 0xcf, 0x30, 0x54, 0xf3, 0x4c, 0x52, 0xb7, 0xe5, 0x58, 0xc6}, + {0xb8, 0x23, 0x6b, 0x00, 0x2f, 0x1d, 0x16, 0x86, 0x53, 0x01, + 0x55, 0x6c, 0x11, 0xa4, 0x37, 0xca, 0xeb, 0xff, 0xc3, 0xbb}, + {0xc0, 0x60, 0xed, 0x44, 0xcb, 0xd8, 0x81, 0xbd, 0x0e, 0xf8, + 0x6c, 0x0b, 0xa2, 0x87, 0xdd, 0xcf, 0x81, 0x67, 0x47, 0x8c}, + {0xc8, 0xec, 0x8c, 0x87, 0x92, 0x69, 0xcb, 0x4b, 0xab, 0x39, + 0xe9, 0x8d, 0x7e, 0x57, 0x67, 0xf3, 0x14, 0x95, 0x73, 0x9d}, + {0xca, 0x3a, 0xfb, 0xcf, 0x12, 0x40, 0x36, 0x4b, 0x44, 0xb2, + 0x16, 0x20, 0x88, 0x80, 0x48, 0x39, 0x19, 0x93, 0x7c, 0xf7}, + {0xcb, 0x44, 0xa0, 0x97, 0x85, 0x7c, 0x45, 0xfa, 0x18, 0x7e, + 0xd9, 0x52, 0x08, 0x6c, 0xb9, 0x84, 0x1f, 0x2d, 0x51, 0xb5}, + {0xcb, 0xa1, 0xc5, 0xf8, 0xb0, 0xe3, 0x5e, 0xb8, 0xb9, 0x45, + 0x12, 0xd3, 0xf9, 0x34, 0xa2, 0xe9, 0x06, 0x10, 0xd3, 0x36}, + {0xcc, 0xab, 0x0e, 0xa0, 0x4c, 0x23, 0x01, 0xd6, 0x69, 0x7b, + 0xdd, 0x37, 0x9f, 0xcd, 0x12, 0xeb, 0x24, 0xe3, 0x94, 0x9d}, + {0xce, 0x6a, 0x64, 0xa3, 0x09, 0xe4, 0x2f, 0xbb, 0xd9, 0x85, + 0x1c, 0x45, 0x3e, 0x64, 0x09, 0xea, 0xe8, 0x7d, 0x60, 0xf1}, + {0xcf, 0x9e, 0x87, 0x6d, 0xd3, 0xeb, 0xfc, 0x42, 0x26, 0x97, + 0xa3, 0xb5, 0xa3, 0x7a, 0xa0, 0x76, 0xa9, 0x06, 0x23, 0x48}, + {0xd1, 0xeb, 0x23, 0xa4, 0x6d, 0x17, 0xd6, 0x8f, 0xd9, 0x25, + 0x64, 0xc2, 0xf1, 0xf1, 0x60, 0x17, 0x64, 0xd8, 0xe3, 0x49}, + {0xd2, 0x32, 0x09, 0xad, 0x23, 0xd3, 0x14, 0x23, 0x21, 0x74, + 0xe4, 0x0d, 0x7f, 0x9d, 0x62, 0x13, 0x97, 0x86, 0x63, 0x3a}, + {0xd3, 0xc0, 0x63, 0xf2, 0x19, 0xed, 0x07, 0x3e, 0x34, 0xad, + 0x5d, 0x75, 0x0b, 0x32, 0x76, 0x29, 0xff, 0xd5, 0x9a, 0xf2}, + {0xd4, 0xde, 0x20, 0xd0, 0x5e, 0x66, 0xfc, 0x53, 0xfe, 0x1a, + 0x50, 0x88, 0x2c, 0x78, 0xdb, 0x28, 0x52, 0xca, 0xe4, 0x74}, + {0xd6, 0x9b, 0x56, 0x11, 0x48, 0xf0, 0x1c, 0x77, 0xc5, 0x45, + 0x78, 0xc1, 0x09, 0x26, 0xdf, 0x5b, 0x85, 0x69, 0x76, 0xad}, + {0xd6, 0xda, 0xa8, 0x20, 0x8d, 0x09, 0xd2, 0x15, 0x4d, 0x24, + 0xb5, 0x2f, 0xcb, 0x34, 0x6e, 0xb2, 0x58, 0xb2, 0x8a, 0x58}, + {0xd8, 0xa6, 0x33, 0x2c, 0xe0, 0x03, 0x6f, 0xb1, 0x85, 0xf6, + 0x63, 0x4f, 0x7d, 0x6a, 0x06, 0x65, 0x26, 0x32, 0x28, 0x27}, + {0xd8, 0xc5, 0x38, 0x8a, 0xb7, 0x30, 0x1b, 0x1b, 0x6e, 0xd4, + 0x7a, 0xe6, 0x45, 0x25, 0x3a, 0x6f, 0x9f, 0x1a, 0x27, 0x61}, + {0xda, 0x40, 0x18, 0x8b, 0x91, 0x89, 0xa3, 0xed, 0xee, 0xae, + 0xda, 0x97, 0xfe, 0x2f, 0x9d, 0xf5, 0xb7, 0xd1, 0x8a, 0x41}, + {0xda, 0xc9, 0x02, 0x4f, 0x54, 0xd8, 0xf6, 0xdf, 0x94, 0x93, + 0x5f, 0xb1, 0x73, 0x26, 0x38, 0xca, 0x6a, 0xd7, 0x7c, 0x13}, + {0xde, 0x28, 0xf4, 0xa4, 0xff, 0xe5, 0xb9, 0x2f, 0xa3, 0xc5, + 0x03, 0xd1, 0xa3, 0x49, 0xa7, 0xf9, 0x96, 0x2a, 0x82, 0x12}, + {0xde, 0x3f, 0x40, 0xbd, 0x50, 0x93, 0xd3, 0x9b, 0x6c, 0x60, + 0xf6, 0xda, 0xbc, 0x07, 0x62, 0x01, 0x00, 0x89, 0x76, 0xc9}, + {0xde, 0x99, 0x0c, 0xed, 0x99, 0xe0, 0x43, 0x1f, 0x60, 0xed, + 0xc3, 0x93, 0x7e, 0x7c, 0xd5, 0xbf, 0x0e, 0xd9, 0xe5, 0xfa}, + {0xdf, 0xdf, 0xac, 0x89, 0x47, 0xbd, 0xf7, 0x52, 0x64, 0xa9, + 0x23, 0x3a, 0xc1, 0x0e, 0xe3, 0xd1, 0x28, 0x33, 0xda, 0xcc}, + {0xe1, 0x2d, 0xfb, 0x4b, 0x41, 0xd7, 0xd9, 0xc3, 0x2b, 0x30, + 0x51, 0x4b, 0xac, 0x1d, 0x81, 0xd8, 0x38, 0x5e, 0x2d, 0x46}, + {0xe1, 0x9f, 0xe3, 0x0e, 0x8b, 0x84, 0x60, 0x9e, 0x80, 0x9b, + 0x17, 0x0d, 0x72, 0xa8, 0xc5, 0xba, 0x6e, 0x14, 0x09, 0xbd}, + {0xe3, 0x92, 0x51, 0x2f, 0x0a, 0xcf, 0xf5, 0x05, 0xdf, 0xf6, + 0xde, 0x06, 0x7f, 0x75, 0x37, 0xe1, 0x65, 0xea, 0x57, 0x4b}, + {0xe5, 0xdf, 0x74, 0x3c, 0xb6, 0x01, 0xc4, 0x9b, 0x98, 0x43, + 0xdc, 0xab, 0x8c, 0xe8, 0x6a, 0x81, 0x10, 0x9f, 0xe4, 0x8e}, + {0xe6, 0x19, 0xd2, 0x5b, 0x38, 0x0b, 0x7b, 0x13, 0xfd, 0xa3, + 0x3e, 0x8a, 0x58, 0xcd, 0x82, 0xd8, 0xa8, 0x8e, 0x05, 0x15}, + {0xe7, 0xb4, 0xf6, 0x9d, 0x61, 0xec, 0x90, 0x69, 0xdb, 0x7e, + 0x90, 0xa7, 0x40, 0x1a, 0x3c, 0xf4, 0x7d, 0x4f, 0xe8, 0xee}, + {0xf1, 0x7f, 0x6f, 0xb6, 0x31, 0xdc, 0x99, 0xe3, 0xa3, 0xc8, + 0x7f, 0xfe, 0x1c, 0xf1, 0x81, 0x10, 0x88, 0xd9, 0x60, 0x33}, + {0xf7, 0x4d, 0xac, 0xb2, 0x14, 0x14, 0xdc, 0xba, 0xab, 0x0b, + 0x94, 0x7c, 0x8a, 0x25, 0x7c, 0x32, 0x5c, 0xa8, 0x85, 0x50}, + {0xf9, 0xb5, 0xb6, 0x32, 0x45, 0x5f, 0x9c, 0xbe, 0xec, 0x57, + 0x5f, 0x80, 0xdc, 0xe9, 0x6e, 0x2c, 0xc7, 0xb2, 0x78, 0xb7}, + {0xfa, 0xa7, 0xd9, 0xfb, 0x31, 0xb7, 0x46, 0xf2, 0x00, 0xa8, + 0x5e, 0x65, 0x79, 0x76, 0x13, 0xd8, 0x16, 0xe0, 0x63, 0xb5}, + {0xfc, 0x21, 0x9a, 0x76, 0x11, 0x2f, 0x76, 0xc1, 0xc5, 0x08, + 0x83, 0x3c, 0x9a, 0x2f, 0xa2, 0xba, 0x84, 0xac, 0x08, 0x7a}, + {0xfe, 0xb8, 0xc4, 0x32, 0xdc, 0xf9, 0x76, 0x9a, 0xce, 0xae, + 0x3d, 0xd8, 0x90, 0x8f, 0xfd, 0x28, 0x86, 0x65, 0x64, 0x7d}, +}; + +#endif // NET_CERT_X509_CERTIFICATE_KNOWN_ROOTS_MAC_H_ diff --git a/net/cert/x509_certificate_known_roots_win.h b/net/cert/x509_certificate_known_roots_win.h new file mode 100644 index 0000000..f872413 --- /dev/null +++ b/net/cert/x509_certificate_known_roots_win.h @@ -0,0 +1,655 @@ +// Copyright (c) 2011 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. + +#ifndef NET_CERT_X509_CERTIFICATE_KNOWN_ROOTS_WIN_H_ +#define NET_CERT_X509_CERTIFICATE_KNOWN_ROOTS_WIN_H_ + +// This is the set of 318 Microsoft trusted roots from 1st April 2011. +// Extracted from +// http://www.download.windowsupdate.com/msdownload/update/v3/ +// static/trustedr/en/authrootstl.cab +// (sha1sum: 62f2d9b91a5a0032be6795bb4b6e2b4bfeb8bbb3) +// +// Note that these *are not* trust anchors for Chromium. They are only used to +// distinguish `real' root CAs from roots that were user-installed. +static uint8 kKnownRootCertSHA1Hashes[][20] = { + {0x00, 0x48, 0xf8, 0xd3, 0x7b, 0x15, 0x3f, 0x6e, 0xa2, 0x79, + 0x8c, 0x32, 0x3e, 0xf4, 0xf3, 0x18, 0xa5, 0x62, 0x4a, 0x9e}, + {0x00, 0xea, 0x52, 0x2c, 0x8a, 0x9c, 0x06, 0xaa, 0x3e, 0xcc, + 0xe0, 0xb4, 0xfa, 0x6c, 0xdc, 0x21, 0xd9, 0x2e, 0x80, 0x99}, + {0x01, 0x68, 0x97, 0xe1, 0xa0, 0xb8, 0xf2, 0xc3, 0xb1, 0x34, + 0x66, 0x5c, 0x20, 0xa7, 0x27, 0xb7, 0xa1, 0x58, 0xe2, 0x8f}, + {0x02, 0x72, 0x68, 0x29, 0x3e, 0x5f, 0x5d, 0x17, 0xaa, 0xa4, + 0xb3, 0xc3, 0xe6, 0x36, 0x1e, 0x1f, 0x92, 0x57, 0x5e, 0xaa}, + {0x02, 0xfa, 0xf3, 0xe2, 0x91, 0x43, 0x54, 0x68, 0x60, 0x78, + 0x57, 0x69, 0x4d, 0xf5, 0xe4, 0x5b, 0x68, 0x85, 0x18, 0x68}, + {0x03, 0x9e, 0xed, 0xb8, 0x0b, 0xe7, 0xa0, 0x3c, 0x69, 0x53, + 0x89, 0x3b, 0x20, 0xd2, 0xd9, 0x32, 0x3a, 0x4c, 0x2a, 0xfd}, + {0x04, 0x09, 0x56, 0x5b, 0x77, 0xda, 0x58, 0x2e, 0x64, 0x95, + 0xac, 0x00, 0x60, 0xa7, 0x23, 0x54, 0xe6, 0x4b, 0x01, 0x92}, + {0x04, 0x46, 0xc8, 0xbb, 0x9a, 0x69, 0x83, 0xc9, 0x5c, 0x8a, + 0x2e, 0x54, 0x64, 0x68, 0x7c, 0x11, 0x15, 0xaa, 0xb7, 0x4a}, + {0x04, 0x56, 0xf2, 0x3d, 0x1e, 0x9c, 0x43, 0xae, 0xcb, 0x0d, + 0x80, 0x7f, 0x1c, 0x06, 0x47, 0x55, 0x1a, 0x05, 0xf4, 0x56}, + {0x04, 0x83, 0xed, 0x33, 0x99, 0xac, 0x36, 0x08, 0x05, 0x87, + 0x22, 0xed, 0xbc, 0x5e, 0x46, 0x00, 0xe3, 0xbe, 0xf9, 0xd7}, + {0x04, 0x98, 0x11, 0x05, 0x6a, 0xfe, 0x9f, 0xd0, 0xf5, 0xbe, + 0x01, 0x68, 0x5a, 0xac, 0xe6, 0xa5, 0xd1, 0xc4, 0x45, 0x4c}, + {0x05, 0x60, 0xa2, 0xc7, 0x38, 0xff, 0x98, 0xd1, 0x17, 0x2a, + 0x94, 0xfe, 0x45, 0xfb, 0x8a, 0x47, 0xd6, 0x65, 0x37, 0x1e}, + {0x05, 0x63, 0xb8, 0x63, 0x0d, 0x62, 0xd7, 0x5a, 0xbb, 0xc8, + 0xab, 0x1e, 0x4b, 0xdf, 0xb5, 0xa8, 0x99, 0xb2, 0x4d, 0x43}, + {0x06, 0x08, 0x3f, 0x59, 0x3f, 0x15, 0xa1, 0x04, 0xa0, 0x69, + 0xa4, 0x6b, 0xa9, 0x03, 0xd0, 0x06, 0xb7, 0x97, 0x09, 0x91}, + {0x07, 0x47, 0x22, 0x01, 0x99, 0xce, 0x74, 0xb9, 0x7c, 0xb0, + 0x3d, 0x79, 0xb2, 0x64, 0xa2, 0xc8, 0x55, 0xe9, 0x33, 0xff}, + {0x07, 0xe0, 0x32, 0xe0, 0x20, 0xb7, 0x2c, 0x3f, 0x19, 0x2f, + 0x06, 0x28, 0xa2, 0x59, 0x3a, 0x19, 0xa7, 0x0f, 0x06, 0x9e}, + {0x08, 0x64, 0x18, 0xe9, 0x06, 0xce, 0xe8, 0x9c, 0x23, 0x53, + 0xb6, 0xe2, 0x7f, 0xbd, 0x9e, 0x74, 0x39, 0xf7, 0x63, 0x16}, + {0x0b, 0x71, 0x99, 0xa1, 0xc7, 0xf3, 0xad, 0xdf, 0x7b, 0xa7, + 0xea, 0xb8, 0xeb, 0x57, 0x4a, 0xe8, 0x0d, 0x60, 0xdd, 0xde}, + {0x0b, 0x77, 0xbe, 0xbb, 0xcb, 0x7a, 0xa2, 0x47, 0x05, 0xde, + 0xcc, 0x0f, 0xbd, 0x6a, 0x02, 0xfc, 0x7a, 0xbd, 0x9b, 0x52}, + {0x0b, 0x97, 0x2c, 0x9e, 0xa6, 0xe7, 0xcc, 0x58, 0xd9, 0x3b, + 0x20, 0xbf, 0x71, 0xec, 0x41, 0x2e, 0x72, 0x09, 0xfa, 0xbf}, + {0x0c, 0xfd, 0x83, 0xdb, 0xae, 0x44, 0xb9, 0xa0, 0xc8, 0xf6, + 0x76, 0xf3, 0xb5, 0x70, 0x65, 0x0b, 0x94, 0xb6, 0x9d, 0xbf}, + {0x10, 0x1d, 0xfa, 0x3f, 0xd5, 0x0b, 0xcb, 0xbb, 0x9b, 0xb5, + 0x60, 0x0c, 0x19, 0x55, 0xa4, 0x1a, 0xf4, 0x73, 0x3a, 0x04}, + {0x11, 0xc5, 0xb5, 0xf7, 0x55, 0x52, 0xb0, 0x11, 0x66, 0x9c, + 0x2e, 0x97, 0x17, 0xde, 0x6d, 0x9b, 0xff, 0x5f, 0xa8, 0x10}, + {0x13, 0x2d, 0x0d, 0x45, 0x53, 0x4b, 0x69, 0x97, 0xcd, 0xb2, + 0xd5, 0xc3, 0x39, 0xe2, 0x55, 0x76, 0x60, 0x9b, 0x5c, 0xc6}, + {0x16, 0xd8, 0x66, 0x35, 0xaf, 0x13, 0x41, 0xcd, 0x34, 0x79, + 0x94, 0x45, 0xeb, 0x60, 0x3e, 0x27, 0x37, 0x02, 0x96, 0x5d}, + {0x18, 0xf7, 0xc1, 0xfc, 0xc3, 0x09, 0x02, 0x03, 0xfd, 0x5b, + 0xaa, 0x2f, 0x86, 0x1a, 0x75, 0x49, 0x76, 0xc8, 0xdd, 0x25}, + {0x1b, 0x4b, 0x39, 0x61, 0x26, 0x27, 0x6b, 0x64, 0x91, 0xa2, + 0x68, 0x6d, 0xd7, 0x02, 0x43, 0x21, 0x2d, 0x1f, 0x1d, 0x96}, + {0x1f, 0x49, 0x14, 0xf7, 0xd8, 0x74, 0x95, 0x1d, 0xdd, 0xae, + 0x02, 0xc0, 0xbe, 0xfd, 0x3a, 0x2d, 0x82, 0x75, 0x51, 0x85}, + {0x20, 0x42, 0x85, 0xdc, 0xf7, 0xeb, 0x76, 0x41, 0x95, 0x57, + 0x8e, 0x13, 0x6b, 0xd4, 0xb7, 0xd1, 0xe9, 0x8e, 0x46, 0xa5}, + {0x20, 0x99, 0x00, 0xb6, 0x3d, 0x95, 0x57, 0x28, 0x14, 0x0c, + 0xd1, 0x36, 0x22, 0xd8, 0xc6, 0x87, 0xa4, 0xeb, 0x00, 0x85}, + {0x20, 0xcb, 0x59, 0x4f, 0xb4, 0xed, 0xd8, 0x95, 0x76, 0x3f, + 0xd5, 0x25, 0x4e, 0x95, 0x9a, 0x66, 0x74, 0xc6, 0xee, 0xb2}, + {0x21, 0x11, 0x65, 0xca, 0x37, 0x9f, 0xbb, 0x5e, 0xd8, 0x01, + 0xe3, 0x1c, 0x43, 0x0a, 0x62, 0xaa, 0xc1, 0x09, 0xbc, 0xb4}, + {0x21, 0x6b, 0x2a, 0x29, 0xe6, 0x2a, 0x00, 0xce, 0x82, 0x01, + 0x46, 0xd8, 0x24, 0x41, 0x41, 0xb9, 0x25, 0x11, 0xb2, 0x79}, + {0x21, 0xfc, 0xbd, 0x8e, 0x7f, 0x6c, 0xaf, 0x05, 0x1b, 0xd1, + 0xb3, 0x43, 0xec, 0xa8, 0xe7, 0x61, 0x47, 0xf2, 0x0f, 0x8a}, + {0x22, 0xd5, 0xd8, 0xdf, 0x8f, 0x02, 0x31, 0xd1, 0x8d, 0xf7, + 0x9d, 0xb7, 0xcf, 0x8a, 0x2d, 0x64, 0xc9, 0x3f, 0x6c, 0x3a}, + {0x23, 0x88, 0xc9, 0xd3, 0x71, 0xcc, 0x9e, 0x96, 0x3d, 0xff, + 0x7d, 0x3c, 0xa7, 0xce, 0xfc, 0xd6, 0x25, 0xec, 0x19, 0x0d}, + {0x23, 0xe5, 0x94, 0x94, 0x51, 0x95, 0xf2, 0x41, 0x48, 0x03, + 0xb4, 0xd5, 0x64, 0xd2, 0xa3, 0xa3, 0xf5, 0xd8, 0x8b, 0x8c}, + {0x23, 0xe8, 0x33, 0x23, 0x3e, 0x7d, 0x0c, 0xc9, 0x2b, 0x7c, + 0x42, 0x79, 0xac, 0x19, 0xc2, 0xf4, 0x74, 0xd6, 0x04, 0xca}, + {0x24, 0x5c, 0x97, 0xdf, 0x75, 0x14, 0xe7, 0xcf, 0x2d, 0xf8, + 0xbe, 0x72, 0xae, 0x95, 0x7b, 0x9e, 0x04, 0x74, 0x1e, 0x85}, + {0x24, 0xa4, 0x0a, 0x1f, 0x57, 0x36, 0x43, 0xa6, 0x7f, 0x0a, + 0x4b, 0x07, 0x49, 0xf6, 0xa2, 0x2b, 0xf2, 0x8a, 0xbb, 0x6b}, + {0x24, 0xba, 0x6d, 0x6c, 0x8a, 0x5b, 0x58, 0x37, 0xa4, 0x8d, + 0xb5, 0xfa, 0xe9, 0x19, 0xea, 0x67, 0x5c, 0x94, 0xd2, 0x17}, + {0x25, 0x01, 0x90, 0x19, 0xcf, 0xfb, 0xd9, 0x99, 0x1c, 0xb7, + 0x68, 0x25, 0x74, 0x8d, 0x94, 0x5f, 0x30, 0x93, 0x95, 0x42}, + {0x25, 0x3f, 0x77, 0x5b, 0x0e, 0x77, 0x97, 0xab, 0x64, 0x5f, + 0x15, 0x91, 0x55, 0x97, 0xc3, 0x9e, 0x26, 0x36, 0x31, 0xd1}, + {0x27, 0x3e, 0xe1, 0x24, 0x57, 0xfd, 0xc4, 0xf9, 0x0c, 0x55, + 0xe8, 0x2b, 0x56, 0x16, 0x7f, 0x62, 0xf5, 0x32, 0xe5, 0x47}, + {0x27, 0x96, 0xba, 0xe6, 0x3f, 0x18, 0x01, 0xe2, 0x77, 0x26, + 0x1b, 0xa0, 0xd7, 0x77, 0x70, 0x02, 0x8f, 0x20, 0xee, 0xe4}, + {0x28, 0x90, 0x3a, 0x63, 0x5b, 0x52, 0x80, 0xfa, 0xe6, 0x77, + 0x4c, 0x0b, 0x6d, 0xa7, 0xd6, 0xba, 0xa6, 0x4a, 0xf2, 0xe8}, + {0x29, 0x36, 0x21, 0x02, 0x8b, 0x20, 0xed, 0x02, 0xf5, 0x66, + 0xc5, 0x32, 0xd1, 0xd6, 0xed, 0x90, 0x9f, 0x45, 0x00, 0x2f}, + {0x29, 0x64, 0xb6, 0x86, 0x13, 0x5b, 0x5d, 0xfd, 0xdd, 0x32, + 0x53, 0xa8, 0x9b, 0xbc, 0x24, 0xd7, 0x4b, 0x08, 0xc6, 0x4d}, + {0x2a, 0xc8, 0xd5, 0x8b, 0x57, 0xce, 0xbf, 0x2f, 0x49, 0xaf, + 0xf2, 0xfc, 0x76, 0x8f, 0x51, 0x14, 0x62, 0x90, 0x7a, 0x41}, + {0x2b, 0x8f, 0x1b, 0x57, 0x33, 0x0d, 0xbb, 0xa2, 0xd0, 0x7a, + 0x6c, 0x51, 0xf7, 0x0e, 0xe9, 0x0d, 0xda, 0xb9, 0xad, 0x8e}, + {0x2e, 0x14, 0xda, 0xec, 0x28, 0xf0, 0xfa, 0x1e, 0x8e, 0x38, + 0x9a, 0x4e, 0xab, 0xeb, 0x26, 0xc0, 0x0a, 0xd3, 0x83, 0xc3}, + {0x30, 0x70, 0xf8, 0x83, 0x3e, 0x4a, 0xa6, 0x80, 0x3e, 0x09, + 0xa6, 0x46, 0xae, 0x3f, 0x7d, 0x8a, 0xe1, 0xfd, 0x16, 0x54}, + {0x30, 0x77, 0x9e, 0x93, 0x15, 0x02, 0x2e, 0x94, 0x85, 0x6a, + 0x3f, 0xf8, 0xbc, 0xf8, 0x15, 0xb0, 0x82, 0xf9, 0xae, 0xfd}, + {0x31, 0x7a, 0x2a, 0xd0, 0x7f, 0x2b, 0x33, 0x5e, 0xf5, 0xa1, + 0xc3, 0x4e, 0x4b, 0x57, 0xe8, 0xb7, 0xd8, 0xf1, 0xfc, 0xa6}, + {0x31, 0xe2, 0xc5, 0x2c, 0xe1, 0x08, 0x9b, 0xef, 0xfd, 0xda, + 0xdb, 0x26, 0xdd, 0x7c, 0x78, 0x2e, 0xbc, 0x40, 0x37, 0xbd}, + {0x32, 0x3c, 0x11, 0x8e, 0x1b, 0xf7, 0xb8, 0xb6, 0x52, 0x54, + 0xe2, 0xe2, 0x10, 0x0d, 0xd6, 0x02, 0x90, 0x37, 0xf0, 0x96}, + {0x33, 0x9b, 0x6b, 0x14, 0x50, 0x24, 0x9b, 0x55, 0x7a, 0x01, + 0x87, 0x72, 0x84, 0xd9, 0xe0, 0x2f, 0xc3, 0xd2, 0xd8, 0xe9}, + {0x34, 0x2c, 0xd9, 0xd3, 0x06, 0x2d, 0xa4, 0x8c, 0x34, 0x69, + 0x65, 0x29, 0x7f, 0x08, 0x1e, 0xbc, 0x2e, 0xf6, 0x8f, 0xdc}, + {0x34, 0xd4, 0x99, 0x42, 0x6f, 0x9f, 0xc2, 0xbb, 0x27, 0xb0, + 0x75, 0xba, 0xb6, 0x82, 0xaa, 0xe5, 0xef, 0xfc, 0xba, 0x74}, + {0x36, 0x79, 0xca, 0x35, 0x66, 0x87, 0x72, 0x30, 0x4d, 0x30, + 0xa5, 0xfb, 0x87, 0x3b, 0x0f, 0xa7, 0x7b, 0xb7, 0x0d, 0x54}, + {0x36, 0x86, 0x35, 0x63, 0xfd, 0x51, 0x28, 0xc7, 0xbe, 0xa6, + 0xf0, 0x05, 0xcf, 0xe9, 0xb4, 0x36, 0x68, 0x08, 0x6c, 0xce}, + {0x36, 0xb1, 0x2b, 0x49, 0xf9, 0x81, 0x9e, 0xd7, 0x4c, 0x9e, + 0xbc, 0x38, 0x0f, 0xc6, 0x56, 0x8f, 0x5d, 0xac, 0xb2, 0xf7}, + {0x37, 0x9a, 0x19, 0x7b, 0x41, 0x85, 0x45, 0x35, 0x0c, 0xa6, + 0x03, 0x69, 0xf3, 0x3c, 0x2e, 0xaf, 0x47, 0x4f, 0x20, 0x79}, + {0x37, 0xf7, 0x6d, 0xe6, 0x07, 0x7c, 0x90, 0xc5, 0xb1, 0x3e, + 0x93, 0x1a, 0xb7, 0x41, 0x10, 0xb4, 0xf2, 0xe4, 0x9a, 0x27}, + {0x39, 0x13, 0x85, 0x3e, 0x45, 0xc4, 0x39, 0xa2, 0xda, 0x71, + 0x8c, 0xdf, 0xb6, 0xf3, 0xe0, 0x33, 0xe0, 0x4f, 0xee, 0x71}, + {0x39, 0x21, 0xc1, 0x15, 0xc1, 0x5d, 0x0e, 0xca, 0x5c, 0xcb, + 0x5b, 0xc4, 0xf0, 0x7d, 0x21, 0xd8, 0x05, 0x0b, 0x56, 0x6a}, + {0x39, 0x4f, 0xf6, 0x85, 0x0b, 0x06, 0xbe, 0x52, 0xe5, 0x18, + 0x56, 0xcc, 0x10, 0xe1, 0x80, 0xe8, 0x82, 0xb3, 0x85, 0xcc}, + {0x39, 0x8e, 0xbe, 0x9c, 0x0f, 0x46, 0xc0, 0x79, 0xc3, 0xc7, + 0xaf, 0xe0, 0x7a, 0x2f, 0xdd, 0x9f, 0xae, 0x5f, 0x8a, 0x5c}, + {0x3a, 0x44, 0x73, 0x5a, 0xe5, 0x81, 0x90, 0x1f, 0x24, 0x86, + 0x61, 0x46, 0x1e, 0x3b, 0x9c, 0xc4, 0x5f, 0xf5, 0x3a, 0x1b}, + {0x3b, 0x1e, 0xfd, 0x3a, 0x66, 0xea, 0x28, 0xb1, 0x66, 0x97, + 0x39, 0x47, 0x03, 0xa7, 0x2c, 0xa3, 0x40, 0xa0, 0x5b, 0xd5}, + {0x3b, 0xc0, 0x38, 0x0b, 0x33, 0xc3, 0xf6, 0xa6, 0x0c, 0x86, + 0x15, 0x22, 0x93, 0xd9, 0xdf, 0xf5, 0x4b, 0x81, 0xc0, 0x04}, + {0x3b, 0xc4, 0x9f, 0x48, 0xf8, 0xf3, 0x73, 0xa0, 0x9c, 0x1e, + 0xbd, 0xf8, 0x5b, 0xb1, 0xc3, 0x65, 0xc7, 0xd8, 0x11, 0xb3}, + {0x3c, 0x71, 0xd7, 0x0e, 0x35, 0xa5, 0xda, 0xa8, 0xb2, 0xe3, + 0x81, 0x2d, 0xc3, 0x67, 0x74, 0x17, 0xf5, 0x99, 0x0d, 0xf3}, + {0x3e, 0x2b, 0xf7, 0xf2, 0x03, 0x1b, 0x96, 0xf3, 0x8c, 0xe6, + 0xc4, 0xd8, 0xa8, 0x5d, 0x3e, 0x2d, 0x58, 0x47, 0x6a, 0x0f}, + {0x3e, 0x42, 0xa1, 0x87, 0x06, 0xbd, 0x0c, 0x9c, 0xcf, 0x59, + 0x47, 0x50, 0xd2, 0xe4, 0xd6, 0xab, 0x00, 0x48, 0xfd, 0xc4}, + {0x3e, 0x84, 0xd3, 0xbc, 0xc5, 0x44, 0xc0, 0xf6, 0xfa, 0x19, + 0x43, 0x5c, 0x85, 0x1f, 0x3f, 0x2f, 0xcb, 0xa8, 0xe8, 0x14}, + {0x3f, 0x85, 0xf2, 0xbb, 0x4a, 0x62, 0xb0, 0xb5, 0x8b, 0xe1, + 0x61, 0x4a, 0xbb, 0x0d, 0x46, 0x31, 0xb4, 0xbe, 0xf8, 0xba}, + {0x40, 0x54, 0xda, 0x6f, 0x1c, 0x3f, 0x40, 0x74, 0xac, 0xed, + 0x0f, 0xec, 0xcd, 0xdb, 0x79, 0xd1, 0x53, 0xfb, 0x90, 0x1d}, + {0x40, 0x9d, 0x4b, 0xd9, 0x17, 0xb5, 0x5c, 0x27, 0xb6, 0x9b, + 0x64, 0xcb, 0x98, 0x22, 0x44, 0x0d, 0xcd, 0x09, 0xb8, 0x89}, + {0x40, 0xe7, 0x8c, 0x1d, 0x52, 0x3d, 0x1c, 0xd9, 0x95, 0x4f, + 0xac, 0x1a, 0x1a, 0xb3, 0xbd, 0x3c, 0xba, 0xa1, 0x5b, 0xfc}, + {0x42, 0xef, 0xdd, 0xe6, 0xbf, 0xf3, 0x5e, 0xd0, 0xba, 0xe6, + 0xac, 0xdd, 0x20, 0x4c, 0x50, 0xae, 0x86, 0xc4, 0xf4, 0xfa}, + {0x43, 0x13, 0xbb, 0x96, 0xf1, 0xd5, 0x86, 0x9b, 0xc1, 0x4e, + 0x6a, 0x92, 0xf6, 0xcf, 0xf6, 0x34, 0x69, 0x87, 0x82, 0x37}, + {0x43, 0xd9, 0xbc, 0xb5, 0x68, 0xe0, 0x39, 0xd0, 0x73, 0xa7, + 0x4a, 0x71, 0xd8, 0x51, 0x1f, 0x74, 0x76, 0x08, 0x9c, 0xc3}, + {0x43, 0xf9, 0xb1, 0x10, 0xd5, 0xba, 0xfd, 0x48, 0x22, 0x52, + 0x31, 0xb0, 0xd0, 0x08, 0x2b, 0x37, 0x2f, 0xef, 0x9a, 0x54}, + {0x44, 0x63, 0xc5, 0x31, 0xd7, 0xcc, 0xc1, 0x00, 0x67, 0x94, + 0x61, 0x2b, 0xb6, 0x56, 0xd3, 0xbf, 0x82, 0x57, 0x84, 0x6f}, + {0x47, 0xbe, 0xab, 0xc9, 0x22, 0xea, 0xe8, 0x0e, 0x78, 0x78, + 0x34, 0x62, 0xa7, 0x9f, 0x45, 0xc2, 0x54, 0xfd, 0xe6, 0x8b}, + {0x4a, 0x05, 0x8f, 0xdf, 0xd7, 0x61, 0xdb, 0x21, 0xb0, 0xc2, + 0xee, 0x48, 0x57, 0x9b, 0xe2, 0x7f, 0x42, 0xa4, 0xda, 0x1c}, + {0x4a, 0x3f, 0x8d, 0x6b, 0xdc, 0x0e, 0x1e, 0xcf, 0xcd, 0x72, + 0xe3, 0x77, 0xde, 0xf2, 0xd7, 0xff, 0x92, 0xc1, 0x9b, 0xc7}, + {0x4a, 0xbd, 0xee, 0xec, 0x95, 0x0d, 0x35, 0x9c, 0x89, 0xae, + 0xc7, 0x52, 0xa1, 0x2c, 0x5b, 0x29, 0xf6, 0xd6, 0xaa, 0x0c}, + {0x4e, 0xb6, 0xd5, 0x78, 0x49, 0x9b, 0x1c, 0xcf, 0x5f, 0x58, + 0x1e, 0xad, 0x56, 0xbe, 0x3d, 0x9b, 0x67, 0x44, 0xa5, 0xe5}, + {0x4e, 0xfc, 0xed, 0x9c, 0x6b, 0xdd, 0x0c, 0x98, 0x5c, 0xa3, + 0xc7, 0xd2, 0x53, 0x06, 0x3c, 0x5b, 0xe6, 0xfc, 0x62, 0x0c}, + {0x4f, 0x55, 0x5c, 0xe2, 0x0d, 0xcd, 0x33, 0x64, 0xe0, 0xdc, + 0x7c, 0x41, 0xef, 0xdd, 0x40, 0xf5, 0x03, 0x56, 0xc1, 0x22}, + {0x4f, 0x65, 0x56, 0x63, 0x36, 0xdb, 0x65, 0x98, 0x58, 0x1d, + 0x58, 0x4a, 0x59, 0x6c, 0x87, 0x93, 0x4d, 0x5f, 0x2a, 0xb4}, + {0x4f, 0x99, 0xaa, 0x93, 0xfb, 0x2b, 0xd1, 0x37, 0x26, 0xa1, + 0x99, 0x4a, 0xce, 0x7f, 0xf0, 0x05, 0xf2, 0x93, 0x5d, 0x1e}, + {0x50, 0x30, 0x06, 0x09, 0x1d, 0x97, 0xd4, 0xf5, 0xae, 0x39, + 0xf7, 0xcb, 0xe7, 0x92, 0x7d, 0x7d, 0x65, 0x2d, 0x34, 0x31}, + {0x51, 0xa4, 0x4c, 0x28, 0xf3, 0x13, 0xe3, 0xf9, 0xcb, 0x5e, + 0x7c, 0x0a, 0x1e, 0x0e, 0x0d, 0xd2, 0x84, 0x37, 0x58, 0xae}, + {0x54, 0xf9, 0xc1, 0x63, 0x75, 0x9f, 0x19, 0x04, 0x51, 0x21, + 0xa3, 0x19, 0xf6, 0x4c, 0x2d, 0x05, 0x55, 0xb7, 0xe0, 0x73}, + {0x55, 0xc8, 0x6f, 0x74, 0x14, 0xac, 0x8b, 0xdd, 0x68, 0x14, + 0xf4, 0xd8, 0x6a, 0xf1, 0x5f, 0x37, 0x10, 0xe1, 0x04, 0xd0}, + {0x56, 0xe0, 0xfa, 0xc0, 0x3b, 0x8f, 0x18, 0x23, 0x55, 0x18, + 0xe5, 0xd3, 0x11, 0xca, 0xe8, 0xc2, 0x43, 0x31, 0xab, 0x66}, + {0x58, 0x11, 0x9f, 0x0e, 0x12, 0x82, 0x87, 0xea, 0x50, 0xfd, + 0xd9, 0x87, 0x45, 0x6f, 0x4f, 0x78, 0xdc, 0xfa, 0xd6, 0xd4}, + {0x59, 0x22, 0xa1, 0xe1, 0x5a, 0xea, 0x16, 0x35, 0x21, 0xf8, + 0x98, 0x39, 0x6a, 0x46, 0x46, 0xb0, 0x44, 0x1b, 0x0f, 0xa9}, + {0x59, 0xaf, 0x82, 0x79, 0x91, 0x86, 0xc7, 0xb4, 0x75, 0x07, + 0xcb, 0xcf, 0x03, 0x57, 0x46, 0xeb, 0x04, 0xdd, 0xb7, 0x16}, + {0x5a, 0x4d, 0x0e, 0x8b, 0x5f, 0xdc, 0xfd, 0xf6, 0x4e, 0x72, + 0x99, 0xa3, 0x6c, 0x06, 0x0d, 0xb2, 0x22, 0xca, 0x78, 0xe4}, + {0x5a, 0x5a, 0x4d, 0xaf, 0x78, 0x61, 0x26, 0x7c, 0x4b, 0x1f, + 0x1e, 0x67, 0x58, 0x6b, 0xae, 0x6e, 0xd4, 0xfe, 0xb9, 0x3f}, + {0x5d, 0x00, 0x38, 0x60, 0xf0, 0x02, 0xed, 0x82, 0x9d, 0xea, + 0xa4, 0x18, 0x68, 0xf7, 0x88, 0x18, 0x6d, 0x62, 0x12, 0x7f}, + {0x5d, 0x98, 0x9c, 0xdb, 0x15, 0x96, 0x11, 0x36, 0x51, 0x65, + 0x64, 0x1b, 0x56, 0x0f, 0xdb, 0xea, 0x2a, 0xc2, 0x3e, 0xf1}, + {0x5f, 0x3a, 0xfc, 0x0a, 0x8b, 0x64, 0xf6, 0x86, 0x67, 0x34, + 0x74, 0xdf, 0x7e, 0xa9, 0xa2, 0xfe, 0xf9, 0xfa, 0x7a, 0x51}, + {0x5f, 0x3b, 0x8c, 0xf2, 0xf8, 0x10, 0xb3, 0x7d, 0x78, 0xb4, + 0xce, 0xec, 0x19, 0x19, 0xc3, 0x73, 0x34, 0xb9, 0xc7, 0x74}, + {0x5f, 0x43, 0xe5, 0xb1, 0xbf, 0xf8, 0x78, 0x8c, 0xac, 0x1c, + 0xc7, 0xca, 0x4a, 0x9a, 0xc6, 0x22, 0x2b, 0xcc, 0x34, 0xc6}, + {0x5f, 0x4e, 0x1f, 0xcf, 0x31, 0xb7, 0x91, 0x3b, 0x85, 0x0b, + 0x54, 0xf6, 0xe5, 0xff, 0x50, 0x1a, 0x2b, 0x6f, 0xc6, 0xcf}, + {0x5f, 0xb7, 0xee, 0x06, 0x33, 0xe2, 0x59, 0xdb, 0xad, 0x0c, + 0x4c, 0x9a, 0xe6, 0xd3, 0x8f, 0x1a, 0x61, 0xc7, 0xdc, 0x25}, + {0x60, 0xd6, 0x89, 0x74, 0xb5, 0xc2, 0x65, 0x9e, 0x8a, 0x0f, + 0xc1, 0x88, 0x7c, 0x88, 0xd2, 0x46, 0x69, 0x1b, 0x18, 0x2c}, + {0x61, 0x57, 0x3a, 0x11, 0xdf, 0x0e, 0xd8, 0x7e, 0xd5, 0x92, + 0x65, 0x22, 0xea, 0xd0, 0x56, 0xd7, 0x44, 0xb3, 0x23, 0x71}, + {0x61, 0xef, 0x43, 0xd7, 0x7f, 0xca, 0xd4, 0x61, 0x51, 0xbc, + 0x98, 0xe0, 0xc3, 0x59, 0x12, 0xaf, 0x9f, 0xeb, 0x63, 0x11}, + {0x62, 0x52, 0xdc, 0x40, 0xf7, 0x11, 0x43, 0xa2, 0x2f, 0xde, + 0x9e, 0xf7, 0x34, 0x8e, 0x06, 0x42, 0x51, 0xb1, 0x81, 0x18}, + {0x62, 0x7f, 0x8d, 0x78, 0x27, 0x65, 0x63, 0x99, 0xd2, 0x7d, + 0x7f, 0x90, 0x44, 0xc9, 0xfe, 0xb3, 0xf3, 0x3e, 0xfa, 0x9a}, + {0x63, 0x4c, 0x3b, 0x02, 0x30, 0xcf, 0x1b, 0x78, 0xb4, 0x56, + 0x9f, 0xec, 0xf2, 0xc0, 0x4a, 0x86, 0x52, 0xef, 0xef, 0x0e}, + {0x64, 0x90, 0x2a, 0xd7, 0x27, 0x7a, 0xf3, 0xe3, 0x2c, 0xd8, + 0xcc, 0x1d, 0xc7, 0x9d, 0xe1, 0xfd, 0x7f, 0x80, 0x69, 0xea}, + {0x66, 0x31, 0xbf, 0x9e, 0xf7, 0x4f, 0x9e, 0xb6, 0xc9, 0xd5, + 0xa6, 0x0c, 0xba, 0x6a, 0xbe, 0xd1, 0xf7, 0xbd, 0xef, 0x7b}, + {0x67, 0x24, 0x89, 0x80, 0xde, 0x77, 0x5d, 0x2c, 0x9b, 0x04, + 0xe4, 0x03, 0x07, 0x94, 0x0b, 0xad, 0xb3, 0x51, 0xf3, 0x95}, + {0x67, 0x65, 0x0d, 0xf1, 0x7e, 0x8e, 0x7e, 0x5b, 0x82, 0x40, + 0xa4, 0xf4, 0x56, 0x4b, 0xcf, 0xe2, 0x3d, 0x69, 0xc6, 0xf0}, + {0x67, 0x82, 0xaa, 0xe0, 0xed, 0xee, 0xe2, 0x1a, 0x58, 0x39, + 0xd3, 0xc0, 0xcd, 0x14, 0x68, 0x0a, 0x4f, 0x60, 0x14, 0x2a}, + {0x67, 0x9a, 0x4f, 0x81, 0xfc, 0x70, 0x5d, 0xde, 0xc4, 0x19, + 0x77, 0x8d, 0xd2, 0xeb, 0xd8, 0x75, 0xf4, 0xc2, 0x42, 0xc6}, + {0x67, 0xeb, 0x33, 0x7b, 0x68, 0x4c, 0xeb, 0x0e, 0xc2, 0xb0, + 0x76, 0x0a, 0xb4, 0x88, 0x27, 0x8c, 0xdd, 0x95, 0x97, 0xdd}, + {0x68, 0x8b, 0x6e, 0xb8, 0x07, 0xe8, 0xed, 0xa5, 0xc7, 0xb1, + 0x7c, 0x43, 0x93, 0xd0, 0x79, 0x5f, 0x0f, 0xae, 0x15, 0x5f}, + {0x68, 0xed, 0x18, 0xb3, 0x09, 0xcd, 0x52, 0x91, 0xc0, 0xd3, + 0x35, 0x7c, 0x1d, 0x11, 0x41, 0xbf, 0x88, 0x38, 0x66, 0xb1}, + {0x69, 0xbd, 0x8c, 0xf4, 0x9c, 0xd3, 0x00, 0xfb, 0x59, 0x2e, + 0x17, 0x93, 0xca, 0x55, 0x6a, 0xf3, 0xec, 0xaa, 0x35, 0xfb}, + {0x6a, 0x17, 0x45, 0x70, 0xa9, 0x16, 0xfb, 0xe8, 0x44, 0x53, + 0xee, 0xd3, 0xd0, 0x70, 0xa1, 0xd8, 0xda, 0x44, 0x28, 0x29}, + {0x6a, 0x6f, 0x2a, 0x8b, 0x6e, 0x26, 0x15, 0x08, 0x8d, 0xf5, + 0x9c, 0xd2, 0x4c, 0x40, 0x24, 0x18, 0xae, 0x42, 0xa3, 0xf1}, + {0x6b, 0x2f, 0x34, 0xad, 0x89, 0x58, 0xbe, 0x62, 0xfd, 0xb0, + 0x6b, 0x5c, 0xce, 0xbb, 0x9d, 0xd9, 0x4f, 0x4e, 0x39, 0xf3}, + {0x6b, 0x81, 0x44, 0x6a, 0x5c, 0xdd, 0xf4, 0x74, 0xa0, 0xf8, + 0x00, 0xff, 0xbe, 0x69, 0xfd, 0x0d, 0xb6, 0x28, 0x75, 0x16}, + {0x6e, 0x3a, 0x55, 0xa4, 0x19, 0x0c, 0x19, 0x5c, 0x93, 0x84, + 0x3c, 0xc0, 0xdb, 0x72, 0x2e, 0x31, 0x30, 0x61, 0xf0, 0xb1}, + {0x70, 0x17, 0x9b, 0x86, 0x8c, 0x00, 0xa4, 0xfa, 0x60, 0x91, + 0x52, 0x22, 0x3f, 0x9f, 0x3e, 0x32, 0xbd, 0xe0, 0x05, 0x62}, + {0x70, 0x30, 0xaa, 0xbf, 0x84, 0x32, 0xa8, 0x00, 0x66, 0x6c, + 0xcc, 0xc4, 0x2a, 0x88, 0x7e, 0x42, 0xb7, 0x55, 0x3e, 0x2b}, + {0x70, 0x5d, 0x2b, 0x45, 0x65, 0xc7, 0x04, 0x7a, 0x54, 0x06, + 0x94, 0xa7, 0x9a, 0xf7, 0xab, 0xb8, 0x42, 0xbd, 0xc1, 0x61}, + {0x72, 0x0f, 0xc1, 0x5d, 0xdc, 0x27, 0xd4, 0x56, 0xd0, 0x98, + 0xfa, 0xbf, 0x3c, 0xdd, 0x78, 0xd3, 0x1e, 0xf5, 0xa8, 0xda}, + {0x74, 0x20, 0x74, 0x41, 0x72, 0x9c, 0xdd, 0x92, 0xec, 0x79, + 0x31, 0xd8, 0x23, 0x10, 0x8d, 0xc2, 0x81, 0x92, 0xe2, 0xbb}, + {0x74, 0x2c, 0x31, 0x92, 0xe6, 0x07, 0xe4, 0x24, 0xeb, 0x45, + 0x49, 0x54, 0x2b, 0xe1, 0xbb, 0xc5, 0x3e, 0x61, 0x74, 0xe2}, + {0x74, 0x2c, 0xdf, 0x15, 0x94, 0x04, 0x9c, 0xbf, 0x17, 0xa2, + 0x04, 0x6c, 0xc6, 0x39, 0xbb, 0x38, 0x88, 0xe0, 0x2e, 0x33}, + {0x74, 0xf8, 0xa3, 0xc3, 0xef, 0xe7, 0xb3, 0x90, 0x06, 0x4b, + 0x83, 0x90, 0x3c, 0x21, 0x64, 0x60, 0x20, 0xe5, 0xdf, 0xce}, + {0x75, 0x02, 0x51, 0xb2, 0xc6, 0x32, 0x53, 0x6f, 0x9d, 0x91, + 0x72, 0x79, 0x54, 0x3c, 0x13, 0x7c, 0xd7, 0x21, 0xc6, 0xe0}, + {0x75, 0xe0, 0xab, 0xb6, 0x13, 0x85, 0x12, 0x27, 0x1c, 0x04, + 0xf8, 0x5f, 0xdd, 0xde, 0x38, 0xe4, 0xb7, 0x24, 0x2e, 0xfe}, + {0x76, 0x39, 0xc7, 0x18, 0x47, 0xe1, 0x51, 0xb5, 0xc7, 0xea, + 0x01, 0xc7, 0x58, 0xfb, 0xf1, 0x2a, 0xba, 0x29, 0x8f, 0x7a}, + {0x76, 0xb7, 0x60, 0x96, 0xdd, 0x14, 0x56, 0x29, 0xac, 0x75, + 0x85, 0xd3, 0x70, 0x63, 0xc1, 0xbc, 0x47, 0x86, 0x1c, 0x8b}, + {0x78, 0x6a, 0x74, 0xac, 0x76, 0xab, 0x14, 0x7f, 0x9c, 0x6a, + 0x30, 0x50, 0xba, 0x9e, 0xa8, 0x7e, 0xfe, 0x9a, 0xce, 0x3c}, + {0x78, 0xe9, 0xdd, 0x06, 0x50, 0x62, 0x4d, 0xb9, 0xcb, 0x36, + 0xb5, 0x07, 0x67, 0xf2, 0x09, 0xb8, 0x43, 0xbe, 0x15, 0xb3}, + {0x79, 0x98, 0xa3, 0x08, 0xe1, 0x4d, 0x65, 0x85, 0xe6, 0xc2, + 0x1e, 0x15, 0x3a, 0x71, 0x9f, 0xba, 0x5a, 0xd3, 0x4a, 0xd9}, + {0x7a, 0x74, 0x41, 0x0f, 0xb0, 0xcd, 0x5c, 0x97, 0x2a, 0x36, + 0x4b, 0x71, 0xbf, 0x03, 0x1d, 0x88, 0xa6, 0x51, 0x0e, 0x9e}, + {0x7a, 0xc5, 0xff, 0xf8, 0xdc, 0xbc, 0x55, 0x83, 0x17, 0x68, + 0x77, 0x07, 0x3b, 0xf7, 0x51, 0x73, 0x5e, 0x9b, 0xd3, 0x58}, + {0x7e, 0x20, 0x69, 0x39, 0xcc, 0x5f, 0xa8, 0x83, 0x63, 0x5f, + 0x64, 0xc7, 0x50, 0xeb, 0xf5, 0xfd, 0xa9, 0xae, 0xe6, 0x53}, + {0x7e, 0x78, 0x4a, 0x10, 0x1c, 0x82, 0x65, 0xcc, 0x2d, 0xe1, + 0xf1, 0x6d, 0x47, 0xb4, 0x40, 0xca, 0xd9, 0x0a, 0x19, 0x45}, + {0x7f, 0x88, 0xcd, 0x72, 0x23, 0xf3, 0xc8, 0x13, 0x81, 0x8c, + 0x99, 0x46, 0x14, 0xa8, 0x9c, 0x99, 0xfa, 0x3b, 0x52, 0x47}, + {0x7f, 0x8a, 0x77, 0x83, 0x6b, 0xdc, 0x6d, 0x06, 0x8f, 0x8b, + 0x07, 0x37, 0xfc, 0xc5, 0x72, 0x54, 0x13, 0x06, 0x8c, 0xa4}, + {0x7f, 0x8a, 0xb0, 0xcf, 0xd0, 0x51, 0x87, 0x6a, 0x66, 0xf3, + 0x36, 0x0f, 0x47, 0xc8, 0x8d, 0x8c, 0xd3, 0x35, 0xfc, 0x74}, + {0x7f, 0xb9, 0xe2, 0xc9, 0x95, 0xc9, 0x7a, 0x93, 0x9f, 0x9e, + 0x81, 0xa0, 0x7a, 0xea, 0x9b, 0x4d, 0x70, 0x46, 0x34, 0x96}, + {0x7f, 0xbb, 0x6a, 0xcd, 0x7e, 0x0a, 0xb4, 0x38, 0xda, 0xaf, + 0x6f, 0xd5, 0x02, 0x10, 0xd0, 0x07, 0xc6, 0xc0, 0x82, 0x9c}, + {0x80, 0x25, 0xef, 0xf4, 0x6e, 0x70, 0xc8, 0xd4, 0x72, 0x24, + 0x65, 0x84, 0xfe, 0x40, 0x3b, 0x8a, 0x8d, 0x6a, 0xdb, 0xf5}, + {0x80, 0xbf, 0x3d, 0xe9, 0xa4, 0x1d, 0x76, 0x8d, 0x19, 0x4b, + 0x29, 0x3c, 0x85, 0x63, 0x2c, 0xdb, 0xc8, 0xea, 0x8c, 0xf7}, + {0x81, 0x96, 0x8b, 0x3a, 0xef, 0x1c, 0xdc, 0x70, 0xf5, 0xfa, + 0x32, 0x69, 0xc2, 0x92, 0xa3, 0x63, 0x5b, 0xd1, 0x23, 0xd3}, + {0x82, 0x50, 0xbe, 0xd5, 0xa2, 0x14, 0x43, 0x3a, 0x66, 0x37, + 0x7c, 0xbc, 0x10, 0xef, 0x83, 0xf6, 0x69, 0xda, 0x3a, 0x67}, + {0x83, 0x8e, 0x30, 0xf7, 0x7f, 0xdd, 0x14, 0xaa, 0x38, 0x5e, + 0xd1, 0x45, 0x00, 0x9c, 0x0e, 0x22, 0x36, 0x49, 0x4f, 0xaa}, + {0x85, 0x37, 0x1c, 0xa6, 0xe5, 0x50, 0x14, 0x3d, 0xce, 0x28, + 0x03, 0x47, 0x1b, 0xde, 0x3a, 0x09, 0xe8, 0xf8, 0x77, 0x0f}, + {0x85, 0xa4, 0x08, 0xc0, 0x9c, 0x19, 0x3e, 0x5d, 0x51, 0x58, + 0x7d, 0xcd, 0xd6, 0x13, 0x30, 0xfd, 0x8c, 0xde, 0x37, 0xbf}, + {0x85, 0xb5, 0xff, 0x67, 0x9b, 0x0c, 0x79, 0x96, 0x1f, 0xc8, + 0x6e, 0x44, 0x22, 0x00, 0x46, 0x13, 0xdb, 0x17, 0x92, 0x84}, + {0x87, 0x81, 0xc2, 0x5a, 0x96, 0xbd, 0xc2, 0xfb, 0x4c, 0x65, + 0x06, 0x4f, 0xf9, 0x39, 0x0b, 0x26, 0x04, 0x8a, 0x0e, 0x01}, + {0x87, 0x82, 0xc6, 0xc3, 0x04, 0x35, 0x3b, 0xcf, 0xd2, 0x96, + 0x92, 0xd2, 0x59, 0x3e, 0x7d, 0x44, 0xd9, 0x34, 0xff, 0x11}, + {0x87, 0x9f, 0x4b, 0xee, 0x05, 0xdf, 0x98, 0x58, 0x3b, 0xe3, + 0x60, 0xd6, 0x33, 0xe7, 0x0d, 0x3f, 0xfe, 0x98, 0x71, 0xaf}, + {0x89, 0xc3, 0x2e, 0x6b, 0x52, 0x4e, 0x4d, 0x65, 0x38, 0x8b, + 0x9e, 0xce, 0xdc, 0x63, 0x71, 0x34, 0xed, 0x41, 0x93, 0xa3}, + {0x89, 0xdf, 0x74, 0xfe, 0x5c, 0xf4, 0x0f, 0x4a, 0x80, 0xf9, + 0xe3, 0x37, 0x7d, 0x54, 0xda, 0x91, 0xe1, 0x01, 0x31, 0x8e}, + {0x8b, 0x1a, 0x11, 0x06, 0xb8, 0xe2, 0x6b, 0x23, 0x29, 0x80, + 0xfd, 0x65, 0x2e, 0x61, 0x81, 0x37, 0x64, 0x41, 0xfd, 0x11}, + {0x8b, 0xaf, 0x4c, 0x9b, 0x1d, 0xf0, 0x2a, 0x92, 0xf7, 0xda, + 0x12, 0x8e, 0xb9, 0x1b, 0xac, 0xf4, 0x98, 0x60, 0x4b, 0x6f}, + {0x8c, 0x96, 0xba, 0xeb, 0xdd, 0x2b, 0x07, 0x07, 0x48, 0xee, + 0x30, 0x32, 0x66, 0xa0, 0xf3, 0x98, 0x6e, 0x7c, 0xae, 0x58}, + {0x8c, 0xc4, 0x30, 0x7b, 0xc6, 0x07, 0x55, 0xe7, 0xb2, 0x2d, + 0xd9, 0xf7, 0xfe, 0xa2, 0x45, 0x93, 0x6c, 0x7c, 0xf2, 0x88}, + {0x8c, 0xf4, 0x27, 0xfd, 0x79, 0x0c, 0x3a, 0xd1, 0x66, 0x06, + 0x8d, 0xe8, 0x1e, 0x57, 0xef, 0xbb, 0x93, 0x22, 0x72, 0xd4}, + {0x8d, 0x08, 0xfc, 0x43, 0xc0, 0x77, 0x0c, 0xa8, 0x4f, 0x4d, + 0xcc, 0xb2, 0xd4, 0x1a, 0x5d, 0x95, 0x6d, 0x78, 0x6d, 0xc4}, + {0x8d, 0x17, 0x84, 0xd5, 0x37, 0xf3, 0x03, 0x7d, 0xec, 0x70, + 0xfe, 0x57, 0x8b, 0x51, 0x9a, 0x99, 0xe6, 0x10, 0xd7, 0xb0}, + {0x8e, 0x10, 0x32, 0xe9, 0x24, 0x59, 0x44, 0xf8, 0x47, 0x91, + 0x98, 0x3e, 0xc9, 0xe8, 0x29, 0xcb, 0x10, 0x59, 0xb4, 0xd3}, + {0x8e, 0xb0, 0x3f, 0xc3, 0xcf, 0x7b, 0xb2, 0x92, 0x86, 0x62, + 0x68, 0xb7, 0x51, 0x22, 0x3d, 0xb5, 0x10, 0x34, 0x05, 0xcb}, + {0x8e, 0xfd, 0xca, 0xbc, 0x93, 0xe6, 0x1e, 0x92, 0x5d, 0x4d, + 0x1d, 0xed, 0x18, 0x1a, 0x43, 0x20, 0xa4, 0x67, 0xa1, 0x39}, + {0x90, 0x5f, 0x94, 0x2f, 0xd9, 0xf2, 0x8f, 0x67, 0x9b, 0x37, + 0x81, 0x80, 0xfd, 0x4f, 0x84, 0x63, 0x47, 0xf6, 0x45, 0xc1}, + {0x90, 0x78, 0xc5, 0xa2, 0x8f, 0x9a, 0x43, 0x25, 0xc2, 0xa7, + 0xc7, 0x38, 0x13, 0xcd, 0xfe, 0x13, 0xc2, 0x0f, 0x93, 0x4e}, + {0x90, 0xae, 0xa2, 0x69, 0x85, 0xff, 0x14, 0x80, 0x4c, 0x43, + 0x49, 0x52, 0xec, 0xe9, 0x60, 0x84, 0x77, 0xaf, 0x55, 0x6f}, + {0x90, 0xde, 0xce, 0x77, 0xf8, 0xc8, 0x25, 0x34, 0x0e, 0x62, + 0xeb, 0xd6, 0x35, 0xe1, 0xbe, 0x20, 0xcf, 0x73, 0x27, 0xdd}, + {0x90, 0xde, 0xde, 0x9e, 0x4c, 0x4e, 0x9f, 0x6f, 0xd8, 0x86, + 0x17, 0x57, 0x9d, 0xd3, 0x91, 0xbc, 0x65, 0xa6, 0x89, 0x64}, + {0x91, 0x21, 0x98, 0xee, 0xf2, 0x3d, 0xca, 0xc4, 0x09, 0x39, + 0x31, 0x2f, 0xee, 0x97, 0xdd, 0x56, 0x0b, 0xae, 0x49, 0xb1}, + {0x91, 0x58, 0xc5, 0xef, 0x98, 0x73, 0x01, 0xa8, 0x90, 0x3c, + 0xfd, 0xab, 0x03, 0xd7, 0x2d, 0xa1, 0xd8, 0x89, 0x09, 0xc9}, + {0x91, 0xc6, 0xd6, 0xee, 0x3e, 0x8a, 0xc8, 0x63, 0x84, 0xe5, + 0x48, 0xc2, 0x99, 0x29, 0x5c, 0x75, 0x6c, 0x81, 0x7b, 0x81}, + {0x92, 0x5a, 0x8f, 0x8d, 0x2c, 0x6d, 0x04, 0xe0, 0x66, 0x5f, + 0x59, 0x6a, 0xff, 0x22, 0xd8, 0x63, 0xe8, 0x25, 0x6f, 0x3f}, + {0x93, 0xe6, 0xab, 0x22, 0x03, 0x03, 0xb5, 0x23, 0x28, 0xdc, + 0xda, 0x56, 0x9e, 0xba, 0xe4, 0xd1, 0xd1, 0xcc, 0xfb, 0x65}, + {0x96, 0x56, 0xcd, 0x7b, 0x57, 0x96, 0x98, 0x95, 0xd0, 0xe1, + 0x41, 0x46, 0x68, 0x06, 0xfb, 0xb8, 0xc6, 0x11, 0x06, 0x87}, + {0x96, 0x83, 0x38, 0xf1, 0x13, 0xe3, 0x6a, 0x7b, 0xab, 0xdd, + 0x08, 0xf7, 0x77, 0x63, 0x91, 0xa6, 0x87, 0x36, 0x58, 0x2e}, + {0x96, 0x97, 0x4c, 0xd6, 0xb6, 0x63, 0xa7, 0x18, 0x45, 0x26, + 0xb1, 0xd6, 0x48, 0xad, 0x81, 0x5c, 0xf5, 0x1e, 0x80, 0x1a}, + {0x97, 0x1d, 0x34, 0x86, 0xfc, 0x1e, 0x8e, 0x63, 0x15, 0xf7, + 0xc6, 0xf2, 0xe1, 0x29, 0x67, 0xc7, 0x24, 0x34, 0x22, 0x14}, + {0x97, 0x22, 0x6a, 0xae, 0x4a, 0x7a, 0x64, 0xa5, 0x9b, 0xd1, + 0x67, 0x87, 0xf2, 0x7f, 0x84, 0x1c, 0x0a, 0x00, 0x1f, 0xd0}, + {0x97, 0x81, 0x79, 0x50, 0xd8, 0x1c, 0x96, 0x70, 0xcc, 0x34, + 0xd8, 0x09, 0xcf, 0x79, 0x44, 0x31, 0x36, 0x7e, 0xf4, 0x74}, + {0x97, 0xe2, 0xe9, 0x96, 0x36, 0xa5, 0x47, 0x55, 0x4f, 0x83, + 0x8f, 0xba, 0x38, 0xb8, 0x2e, 0x74, 0xf8, 0x9a, 0x83, 0x0a}, + {0x99, 0xa6, 0x9b, 0xe6, 0x1a, 0xfe, 0x88, 0x6b, 0x4d, 0x2b, + 0x82, 0x00, 0x7c, 0xb8, 0x54, 0xfc, 0x31, 0x7e, 0x15, 0x39}, + {0x9b, 0xaa, 0xe5, 0x9f, 0x56, 0xee, 0x21, 0xcb, 0x43, 0x5a, + 0xbe, 0x25, 0x93, 0xdf, 0xa7, 0xf0, 0x40, 0xd1, 0x1d, 0xcb}, + {0x9c, 0x61, 0x5c, 0x4d, 0x4d, 0x85, 0x10, 0x3a, 0x53, 0x26, + 0xc2, 0x4d, 0xba, 0xea, 0xe4, 0xa2, 0xd2, 0xd5, 0xcc, 0x97}, + {0x9e, 0xd1, 0x80, 0x28, 0xfb, 0x1e, 0x8a, 0x97, 0x01, 0x48, + 0x0a, 0x78, 0x90, 0xa5, 0x9a, 0xcd, 0x73, 0xdf, 0xf8, 0x71}, + {0x9f, 0x74, 0x4e, 0x9f, 0x2b, 0x4d, 0xba, 0xec, 0x0f, 0x31, + 0x2c, 0x50, 0xb6, 0x56, 0x3b, 0x8e, 0x2d, 0x93, 0xc3, 0x11}, + {0x9f, 0xad, 0x91, 0xa6, 0xce, 0x6a, 0xc6, 0xc5, 0x00, 0x47, + 0xc4, 0x4e, 0xc9, 0xd4, 0xa5, 0x0d, 0x92, 0xd8, 0x49, 0x79}, + {0x9f, 0xc7, 0x96, 0xe8, 0xf8, 0x52, 0x4f, 0x86, 0x3a, 0xe1, + 0x49, 0x6d, 0x38, 0x12, 0x42, 0x10, 0x5f, 0x1b, 0x78, 0xf5}, + {0xa0, 0x73, 0xe5, 0xc5, 0xbd, 0x43, 0x61, 0x0d, 0x86, 0x4c, + 0x21, 0x13, 0x0a, 0x85, 0x58, 0x57, 0xcc, 0x9c, 0xea, 0x46}, + {0xa0, 0xa1, 0xab, 0x90, 0xc9, 0xfc, 0x84, 0x7b, 0x3b, 0x12, + 0x61, 0xe8, 0x97, 0x7d, 0x5f, 0xd3, 0x22, 0x61, 0xd3, 0xcc}, + {0xa0, 0xf8, 0xdb, 0x3f, 0x0b, 0xf4, 0x17, 0x69, 0x3b, 0x28, + 0x2e, 0xb7, 0x4a, 0x6a, 0xd8, 0x6d, 0xf9, 0xd4, 0x48, 0xa3}, + {0xa1, 0xdb, 0x63, 0x93, 0x91, 0x6f, 0x17, 0xe4, 0x18, 0x55, + 0x09, 0x40, 0x04, 0x15, 0xc7, 0x02, 0x40, 0xb0, 0xae, 0x6b}, + {0xa1, 0xe7, 0xc6, 0x00, 0xaa, 0x41, 0x70, 0xe5, 0xb7, 0x4b, + 0xc9, 0x4f, 0x9b, 0x97, 0x03, 0xed, 0xc2, 0x61, 0xb4, 0xb9}, + {0xa3, 0x99, 0xf7, 0x6f, 0x0c, 0xbf, 0x4c, 0x9d, 0xa5, 0x5e, + 0x4a, 0xc2, 0x4e, 0x89, 0x60, 0x98, 0x4b, 0x29, 0x05, 0xb6}, + {0xa3, 0xe3, 0x1e, 0x20, 0xb2, 0xe4, 0x6a, 0x32, 0x85, 0x20, + 0x47, 0x2d, 0x0c, 0xde, 0x95, 0x23, 0xe7, 0x26, 0x0c, 0x6d}, + {0xa4, 0x34, 0x89, 0x15, 0x9a, 0x52, 0x0f, 0x0d, 0x93, 0xd0, + 0x32, 0xcc, 0xaf, 0x37, 0xe7, 0xfe, 0x20, 0xa8, 0xb4, 0x19}, + {0xa5, 0x9c, 0x9b, 0x10, 0xec, 0x73, 0x57, 0x51, 0x5a, 0xbb, + 0x66, 0x0c, 0x4d, 0x94, 0xf7, 0x3b, 0x9e, 0x6e, 0x92, 0x72}, + {0xa5, 0xec, 0x73, 0xd4, 0x8c, 0x34, 0xfc, 0xbe, 0xf1, 0x00, + 0x5a, 0xeb, 0x85, 0x84, 0x35, 0x24, 0xbb, 0xfa, 0xb7, 0x27}, + {0xa6, 0x9a, 0x91, 0xfd, 0x05, 0x7f, 0x13, 0x6a, 0x42, 0x63, + 0x0b, 0xb1, 0x76, 0x0d, 0x2d, 0x51, 0x12, 0x0c, 0x16, 0x50}, + {0xa7, 0xf8, 0x39, 0x0b, 0xa5, 0x77, 0x05, 0x09, 0x6f, 0xd3, + 0x69, 0x41, 0xd4, 0x2e, 0x71, 0x98, 0xc6, 0xd4, 0xd9, 0xd5}, + {0xa8, 0x98, 0x5d, 0x3a, 0x65, 0xe5, 0xe5, 0xc4, 0xb2, 0xd7, + 0xd6, 0x6d, 0x40, 0xc6, 0xdd, 0x2f, 0xb1, 0x9c, 0x54, 0x36}, + {0xa9, 0x62, 0x8f, 0x4b, 0x98, 0xa9, 0x1b, 0x48, 0x35, 0xba, + 0xd2, 0xc1, 0x46, 0x32, 0x86, 0xbb, 0x66, 0x64, 0x6a, 0x8c}, + {0xa9, 0xe9, 0x78, 0x08, 0x14, 0x37, 0x58, 0x88, 0xf2, 0x05, + 0x19, 0xb0, 0x6d, 0x2b, 0x0d, 0x2b, 0x60, 0x16, 0x90, 0x7d}, + {0xaa, 0xdb, 0xbc, 0x22, 0x23, 0x8f, 0xc4, 0x01, 0xa1, 0x27, + 0xbb, 0x38, 0xdd, 0xf4, 0x1d, 0xdb, 0x08, 0x9e, 0xf0, 0x12}, + {0xab, 0x16, 0xdd, 0x14, 0x4e, 0xcd, 0xc0, 0xfc, 0x4b, 0xaa, + 0xb6, 0x2e, 0xcf, 0x04, 0x08, 0x89, 0x6f, 0xde, 0x52, 0xb7}, + {0xab, 0x48, 0xf3, 0x33, 0xdb, 0x04, 0xab, 0xb9, 0xc0, 0x72, + 0xda, 0x5b, 0x0c, 0xc1, 0xd0, 0x57, 0xf0, 0x36, 0x9b, 0x46}, + {0xac, 0xed, 0x5f, 0x65, 0x53, 0xfd, 0x25, 0xce, 0x01, 0x5f, + 0x1f, 0x7a, 0x48, 0x3b, 0x6a, 0x74, 0x9f, 0x61, 0x78, 0xc6}, + {0xad, 0x7e, 0x1c, 0x28, 0xb0, 0x64, 0xef, 0x8f, 0x60, 0x03, + 0x40, 0x20, 0x14, 0xc3, 0xd0, 0xe3, 0x37, 0x0e, 0xb5, 0x8a}, + {0xae, 0x50, 0x83, 0xed, 0x7c, 0xf4, 0x5c, 0xbc, 0x8f, 0x61, + 0xc6, 0x21, 0xfe, 0x68, 0x5d, 0x79, 0x42, 0x21, 0x15, 0x6e}, + {0xae, 0xc5, 0xfb, 0x3f, 0xc8, 0xe1, 0xbf, 0xc4, 0xe5, 0x4f, + 0x03, 0x07, 0x5a, 0x9a, 0xe8, 0x00, 0xb7, 0xf7, 0xb6, 0xfa}, + {0xaf, 0xe5, 0xd2, 0x44, 0xa8, 0xd1, 0x19, 0x42, 0x30, 0xff, + 0x47, 0x9f, 0xe2, 0xf8, 0x97, 0xbb, 0xcd, 0x7a, 0x8c, 0xb4}, + {0xb1, 0x2e, 0x13, 0x63, 0x45, 0x86, 0xa4, 0x6f, 0x1a, 0xb2, + 0x60, 0x68, 0x37, 0x58, 0x2d, 0xc4, 0xac, 0xfd, 0x94, 0x97}, + {0xb1, 0x72, 0xb1, 0xa5, 0x6d, 0x95, 0xf9, 0x1f, 0xe5, 0x02, + 0x87, 0xe1, 0x4d, 0x37, 0xea, 0x6a, 0x44, 0x63, 0x76, 0x8a}, + {0xb1, 0x9d, 0xd0, 0x96, 0xdc, 0xd4, 0xe3, 0xe0, 0xfd, 0x67, + 0x68, 0x85, 0x50, 0x5a, 0x67, 0x2c, 0x43, 0x8d, 0x4e, 0x9c}, + {0xb1, 0xbc, 0x96, 0x8b, 0xd4, 0xf4, 0x9d, 0x62, 0x2a, 0xa8, + 0x9a, 0x81, 0xf2, 0x15, 0x01, 0x52, 0xa4, 0x1d, 0x82, 0x9c}, + {0xb1, 0xea, 0xc3, 0xe5, 0xb8, 0x24, 0x76, 0xe9, 0xd5, 0x0b, + 0x1e, 0xc6, 0x7d, 0x2c, 0xc1, 0x1e, 0x12, 0xe0, 0xb4, 0x91}, + {0xb3, 0x1e, 0xb1, 0xb7, 0x40, 0xe3, 0x6c, 0x84, 0x02, 0xda, + 0xdc, 0x37, 0xd4, 0x4d, 0xf5, 0xd4, 0x67, 0x49, 0x52, 0xf9}, + {0xb3, 0x8f, 0xec, 0xec, 0x0b, 0x14, 0x8a, 0xa6, 0x86, 0xc3, + 0xd0, 0x0f, 0x01, 0xec, 0xc8, 0x84, 0x8e, 0x80, 0x85, 0xeb}, + {0xb3, 0xea, 0xc4, 0x47, 0x76, 0xc9, 0xc8, 0x1c, 0xea, 0xf2, + 0x9d, 0x95, 0xb6, 0xcc, 0xa0, 0x08, 0x1b, 0x67, 0xec, 0x9d}, + {0xb4, 0x35, 0xd4, 0xe1, 0x11, 0x9d, 0x1c, 0x66, 0x90, 0xa7, + 0x49, 0xeb, 0xb3, 0x94, 0xbd, 0x63, 0x7b, 0xa7, 0x82, 0xb7}, + {0xb5, 0x1c, 0x06, 0x7c, 0xee, 0x2b, 0x0c, 0x3d, 0xf8, 0x55, + 0xab, 0x2d, 0x92, 0xf4, 0xfe, 0x39, 0xd4, 0xe7, 0x0f, 0x0e}, + {0xb7, 0x2f, 0xff, 0x92, 0xd2, 0xce, 0x43, 0xde, 0x0a, 0x8d, + 0x4c, 0x54, 0x8c, 0x50, 0x37, 0x26, 0xa8, 0x1e, 0x2b, 0x93}, + {0xb8, 0x01, 0x86, 0xd1, 0xeb, 0x9c, 0x86, 0xa5, 0x41, 0x04, + 0xcf, 0x30, 0x54, 0xf3, 0x4c, 0x52, 0xb7, 0xe5, 0x58, 0xc6}, + {0xb8, 0x23, 0x6b, 0x00, 0x2f, 0x1d, 0x16, 0x86, 0x53, 0x01, + 0x55, 0x6c, 0x11, 0xa4, 0x37, 0xca, 0xeb, 0xff, 0xc3, 0xbb}, + {0xb8, 0x65, 0x13, 0x0b, 0xed, 0xca, 0x38, 0xd2, 0x7f, 0x69, + 0x92, 0x94, 0x20, 0x77, 0x0b, 0xed, 0x86, 0xef, 0xbc, 0x10}, + {0xbc, 0x7b, 0x3c, 0x6f, 0xef, 0x26, 0xb9, 0xf7, 0xab, 0x10, + 0xd7, 0xa1, 0xf6, 0xb6, 0x7c, 0x5e, 0xd2, 0xa1, 0x2d, 0x3d}, + {0xbc, 0x92, 0x19, 0xdd, 0xc9, 0x8e, 0x14, 0xbf, 0x1a, 0x78, + 0x1f, 0x6e, 0x28, 0x0b, 0x04, 0xc2, 0x7f, 0x90, 0x27, 0x12}, + {0xbe, 0x36, 0xa4, 0x56, 0x2f, 0xb2, 0xee, 0x05, 0xdb, 0xb3, + 0xd3, 0x23, 0x23, 0xad, 0xf4, 0x45, 0x08, 0x4e, 0xd6, 0x56}, + {0xbe, 0xb5, 0xa9, 0x95, 0x74, 0x6b, 0x9e, 0xdf, 0x73, 0x8b, + 0x56, 0xe6, 0xdf, 0x43, 0x7a, 0x77, 0xbe, 0x10, 0x6b, 0x81}, + {0xc0, 0x60, 0xed, 0x44, 0xcb, 0xd8, 0x81, 0xbd, 0x0e, 0xf8, + 0x6c, 0x0b, 0xa2, 0x87, 0xdd, 0xcf, 0x81, 0x67, 0x47, 0x8c}, + {0xc0, 0x9a, 0xb0, 0xc8, 0xad, 0x71, 0x14, 0x71, 0x4e, 0xd5, + 0xe2, 0x1a, 0x5a, 0x27, 0x6a, 0xdc, 0xd5, 0xe7, 0xef, 0xcb}, + {0xc0, 0xdb, 0x57, 0x81, 0x57, 0xe9, 0xee, 0x82, 0xb5, 0x91, + 0x7d, 0xf0, 0xdd, 0x6d, 0x82, 0xee, 0x90, 0x39, 0xc4, 0xe2}, + {0xc1, 0x82, 0x11, 0x32, 0x8a, 0x92, 0xb3, 0xb2, 0x38, 0x09, + 0xb9, 0xb5, 0xe2, 0x74, 0x0a, 0x07, 0xfb, 0x12, 0xeb, 0x5e}, + {0xc4, 0x67, 0x4d, 0xdc, 0x6c, 0xe2, 0x96, 0x7f, 0xf9, 0xc9, + 0x2e, 0x07, 0x2e, 0xf8, 0xe8, 0xa7, 0xfb, 0xd6, 0xa1, 0x31}, + {0xc7, 0x30, 0x26, 0xe3, 0x25, 0xfe, 0x21, 0x91, 0x6b, 0x55, + 0xc4, 0xb5, 0x3a, 0x56, 0xb1, 0x3d, 0xca, 0xf3, 0xd6, 0x25}, + {0xc8, 0xec, 0x8c, 0x87, 0x92, 0x69, 0xcb, 0x4b, 0xab, 0x39, + 0xe9, 0x8d, 0x7e, 0x57, 0x67, 0xf3, 0x14, 0x95, 0x73, 0x9d}, + {0xc9, 0x32, 0x1d, 0xe6, 0xb5, 0xa8, 0x26, 0x66, 0xcf, 0x69, + 0x71, 0xa1, 0x8a, 0x56, 0xf2, 0xd3, 0xa8, 0x67, 0x56, 0x02}, + {0xca, 0x3a, 0xfb, 0xcf, 0x12, 0x40, 0x36, 0x4b, 0x44, 0xb2, + 0x16, 0x20, 0x88, 0x80, 0x48, 0x39, 0x19, 0x93, 0x7c, 0xf7}, + {0xca, 0xbb, 0x51, 0x67, 0x24, 0x00, 0x58, 0x8e, 0x64, 0x19, + 0xf1, 0xd4, 0x08, 0x78, 0xd0, 0x40, 0x3a, 0xa2, 0x02, 0x64}, + {0xcb, 0x44, 0xa0, 0x97, 0x85, 0x7c, 0x45, 0xfa, 0x18, 0x7e, + 0xd9, 0x52, 0x08, 0x6c, 0xb9, 0x84, 0x1f, 0x2d, 0x51, 0xb5}, + {0xcb, 0x65, 0x82, 0x64, 0xea, 0x8c, 0xda, 0x18, 0x6e, 0x17, + 0x52, 0xfb, 0x52, 0xc3, 0x97, 0x36, 0x7e, 0xa3, 0x87, 0xbe}, + {0xcb, 0xa1, 0xc5, 0xf8, 0xb0, 0xe3, 0x5e, 0xb8, 0xb9, 0x45, + 0x12, 0xd3, 0xf9, 0x34, 0xa2, 0xe9, 0x06, 0x10, 0xd3, 0x36}, + {0xcd, 0xd4, 0xee, 0xae, 0x60, 0x00, 0xac, 0x7f, 0x40, 0xc3, + 0x80, 0x2c, 0x17, 0x1e, 0x30, 0x14, 0x80, 0x30, 0xc0, 0x72}, + {0xce, 0x6a, 0x64, 0xa3, 0x09, 0xe4, 0x2f, 0xbb, 0xd9, 0x85, + 0x1c, 0x45, 0x3e, 0x64, 0x09, 0xea, 0xe8, 0x7d, 0x60, 0xf1}, + {0xce, 0xa9, 0x89, 0x0d, 0x85, 0xd8, 0x07, 0x53, 0xa6, 0x26, + 0x28, 0x6c, 0xda, 0xd7, 0x8c, 0xb5, 0x66, 0xd7, 0x0c, 0xf2}, + {0xcf, 0x9e, 0x87, 0x6d, 0xd3, 0xeb, 0xfc, 0x42, 0x26, 0x97, + 0xa3, 0xb5, 0xa3, 0x7a, 0xa0, 0x76, 0xa9, 0x06, 0x23, 0x48}, + {0xcf, 0xde, 0xfe, 0x10, 0x2f, 0xda, 0x05, 0xbb, 0xe4, 0xc7, + 0x8d, 0x2e, 0x44, 0x23, 0x58, 0x90, 0x05, 0xb2, 0x57, 0x1d}, + {0xcf, 0xe4, 0x31, 0x3d, 0xba, 0x05, 0xb8, 0xa7, 0xc3, 0x00, + 0x63, 0x99, 0x5a, 0x9e, 0xb7, 0xc2, 0x47, 0xad, 0x8f, 0xd5}, + {0xcf, 0xf3, 0x60, 0xf5, 0x24, 0xcb, 0x20, 0xf1, 0xfe, 0xad, + 0x89, 0x00, 0x6f, 0x7f, 0x58, 0x6a, 0x28, 0x5b, 0x2d, 0x5b}, + {0xcf, 0xf8, 0x10, 0xfb, 0x2c, 0x4f, 0xfc, 0x01, 0x56, 0xbf, + 0xe1, 0xe1, 0xfa, 0xbc, 0xb4, 0x18, 0xc6, 0x8d, 0x31, 0xc5}, + {0xd1, 0xcb, 0xca, 0x5d, 0xb2, 0xd5, 0x2a, 0x7f, 0x69, 0x3b, + 0x67, 0x4d, 0xe5, 0xf0, 0x5a, 0x1d, 0x0c, 0x95, 0x7d, 0xf0}, + {0xd1, 0xeb, 0x23, 0xa4, 0x6d, 0x17, 0xd6, 0x8f, 0xd9, 0x25, + 0x64, 0xc2, 0xf1, 0xf1, 0x60, 0x17, 0x64, 0xd8, 0xe3, 0x49}, + {0xd2, 0x32, 0x09, 0xad, 0x23, 0xd3, 0x14, 0x23, 0x21, 0x74, + 0xe4, 0x0d, 0x7f, 0x9d, 0x62, 0x13, 0x97, 0x86, 0x63, 0x3a}, + {0xd2, 0x44, 0x1a, 0xa8, 0xc2, 0x03, 0xae, 0xca, 0xa9, 0x6e, + 0x50, 0x1f, 0x12, 0x4d, 0x52, 0xb6, 0x8f, 0xe4, 0xc3, 0x75}, + {0xd2, 0x9f, 0x6c, 0x98, 0xbe, 0xfc, 0x6d, 0x98, 0x65, 0x21, + 0x54, 0x3e, 0xe8, 0xbe, 0x56, 0xce, 0xbc, 0x28, 0x8c, 0xf3}, + {0xd2, 0xed, 0xf8, 0x8b, 0x41, 0xb6, 0xfe, 0x01, 0x46, 0x1d, + 0x6e, 0x28, 0x34, 0xec, 0x7c, 0x8f, 0x6c, 0x77, 0x72, 0x1e}, + {0xd3, 0xc0, 0x63, 0xf2, 0x19, 0xed, 0x07, 0x3e, 0x34, 0xad, + 0x5d, 0x75, 0x0b, 0x32, 0x76, 0x29, 0xff, 0xd5, 0x9a, 0xf2}, + {0xd4, 0xde, 0x20, 0xd0, 0x5e, 0x66, 0xfc, 0x53, 0xfe, 0x1a, + 0x50, 0x88, 0x2c, 0x78, 0xdb, 0x28, 0x52, 0xca, 0xe4, 0x74}, + {0xd6, 0x9b, 0x56, 0x11, 0x48, 0xf0, 0x1c, 0x77, 0xc5, 0x45, + 0x78, 0xc1, 0x09, 0x26, 0xdf, 0x5b, 0x85, 0x69, 0x76, 0xad}, + {0xd6, 0xbf, 0x79, 0x94, 0xf4, 0x2b, 0xe5, 0xfa, 0x29, 0xda, + 0x0b, 0xd7, 0x58, 0x7b, 0x59, 0x1f, 0x47, 0xa4, 0x4f, 0x22}, + {0xd6, 0xda, 0xa8, 0x20, 0x8d, 0x09, 0xd2, 0x15, 0x4d, 0x24, + 0xb5, 0x2f, 0xcb, 0x34, 0x6e, 0xb2, 0x58, 0xb2, 0x8a, 0x58}, + {0xd8, 0xa6, 0x33, 0x2c, 0xe0, 0x03, 0x6f, 0xb1, 0x85, 0xf6, + 0x63, 0x4f, 0x7d, 0x6a, 0x06, 0x65, 0x26, 0x32, 0x28, 0x27}, + {0xd8, 0xc5, 0x38, 0x8a, 0xb7, 0x30, 0x1b, 0x1b, 0x6e, 0xd4, + 0x7a, 0xe6, 0x45, 0x25, 0x3a, 0x6f, 0x9f, 0x1a, 0x27, 0x61}, + {0xd9, 0x04, 0x08, 0x0a, 0x49, 0x29, 0xc8, 0x38, 0xe9, 0xf1, + 0x85, 0xec, 0xf7, 0xa2, 0x2d, 0xef, 0x99, 0x34, 0x24, 0x07}, + {0xda, 0x40, 0x18, 0x8b, 0x91, 0x89, 0xa3, 0xed, 0xee, 0xae, + 0xda, 0x97, 0xfe, 0x2f, 0x9d, 0xf5, 0xb7, 0xd1, 0x8a, 0x41}, + {0xda, 0xc9, 0x02, 0x4f, 0x54, 0xd8, 0xf6, 0xdf, 0x94, 0x93, + 0x5f, 0xb1, 0x73, 0x26, 0x38, 0xca, 0x6a, 0xd7, 0x7c, 0x13}, + {0xdb, 0xac, 0x3c, 0x7a, 0xa4, 0x25, 0x4d, 0xa1, 0xaa, 0x5c, + 0xaa, 0xd6, 0x84, 0x68, 0xcb, 0x88, 0xee, 0xdd, 0xee, 0xa8}, + {0xdd, 0x83, 0xc5, 0x19, 0xd4, 0x34, 0x81, 0xfa, 0xd4, 0xc2, + 0x2c, 0x03, 0xd7, 0x02, 0xfe, 0x9f, 0x3b, 0x22, 0xf5, 0x17}, + {0xdd, 0xe1, 0xd2, 0xa9, 0x01, 0x80, 0x2e, 0x1d, 0x87, 0x5e, + 0x84, 0xb3, 0x80, 0x7e, 0x4b, 0xb1, 0xfd, 0x99, 0x41, 0x34}, + {0xde, 0x28, 0xf4, 0xa4, 0xff, 0xe5, 0xb9, 0x2f, 0xa3, 0xc5, + 0x03, 0xd1, 0xa3, 0x49, 0xa7, 0xf9, 0x96, 0x2a, 0x82, 0x12}, + {0xde, 0x3f, 0x40, 0xbd, 0x50, 0x93, 0xd3, 0x9b, 0x6c, 0x60, + 0xf6, 0xda, 0xbc, 0x07, 0x62, 0x01, 0x00, 0x89, 0x76, 0xc9}, + {0xde, 0x99, 0x0c, 0xed, 0x99, 0xe0, 0x43, 0x1f, 0x60, 0xed, + 0xc3, 0x93, 0x7e, 0x7c, 0xd5, 0xbf, 0x0e, 0xd9, 0xe5, 0xfa}, + {0xdf, 0x64, 0x6d, 0xcb, 0x7b, 0x0f, 0xd3, 0xa9, 0x6a, 0xee, + 0x88, 0xc6, 0x4e, 0x2d, 0x67, 0x67, 0x11, 0xff, 0x9d, 0x5f}, + {0xe0, 0x92, 0x5e, 0x18, 0xc7, 0x76, 0x5e, 0x22, 0xda, 0xbd, + 0x94, 0x27, 0x52, 0x9d, 0xa6, 0xaf, 0x4e, 0x06, 0x64, 0x28}, + {0xe0, 0xab, 0x05, 0x94, 0x20, 0x72, 0x54, 0x93, 0x05, 0x60, + 0x62, 0x02, 0x36, 0x70, 0xf7, 0xcd, 0x2e, 0xfc, 0x66, 0x66}, + {0xe0, 0xb4, 0x32, 0x2e, 0xb2, 0xf6, 0xa5, 0x68, 0xb6, 0x54, + 0x53, 0x84, 0x48, 0x18, 0x4a, 0x50, 0x36, 0x87, 0x43, 0x84}, + {0xe1, 0x2d, 0xfb, 0x4b, 0x41, 0xd7, 0xd9, 0xc3, 0x2b, 0x30, + 0x51, 0x4b, 0xac, 0x1d, 0x81, 0xd8, 0x38, 0x5e, 0x2d, 0x46}, + {0xe1, 0xa4, 0x5b, 0x14, 0x1a, 0x21, 0xda, 0x1a, 0x79, 0xf4, + 0x1a, 0x42, 0xa9, 0x61, 0xd6, 0x69, 0xcd, 0x06, 0x34, 0xc1}, + {0xe3, 0x92, 0x51, 0x2f, 0x0a, 0xcf, 0xf5, 0x05, 0xdf, 0xf6, + 0xde, 0x06, 0x7f, 0x75, 0x37, 0xe1, 0x65, 0xea, 0x57, 0x4b}, + {0xe3, 0xd7, 0x36, 0x06, 0x99, 0x6c, 0xdf, 0xef, 0x61, 0xfa, + 0x04, 0xc3, 0x35, 0xe9, 0x8e, 0xa9, 0x61, 0x04, 0x26, 0x4a}, + {0xe5, 0xdf, 0x74, 0x3c, 0xb6, 0x01, 0xc4, 0x9b, 0x98, 0x43, + 0xdc, 0xab, 0x8c, 0xe8, 0x6a, 0x81, 0x10, 0x9f, 0xe4, 0x8e}, + {0xe6, 0x19, 0xd2, 0x5b, 0x38, 0x0b, 0x7b, 0x13, 0xfd, 0xa3, + 0x3e, 0x8a, 0x58, 0xcd, 0x82, 0xd8, 0xa8, 0x8e, 0x05, 0x15}, + {0xe6, 0x21, 0xf3, 0x35, 0x43, 0x79, 0x05, 0x9a, 0x4b, 0x68, + 0x30, 0x9d, 0x8a, 0x2f, 0x74, 0x22, 0x15, 0x87, 0xec, 0x79}, + {0xe7, 0x07, 0x15, 0xf6, 0xf7, 0x28, 0x36, 0x5b, 0x51, 0x90, + 0xe2, 0x71, 0xde, 0xe4, 0xc6, 0x5e, 0xbe, 0xea, 0xca, 0xf3}, + {0xe7, 0xb4, 0xf6, 0x9d, 0x61, 0xec, 0x90, 0x69, 0xdb, 0x7e, + 0x90, 0xa7, 0x40, 0x1a, 0x3c, 0xf4, 0x7d, 0x4f, 0xe8, 0xee}, + {0xec, 0x0c, 0x37, 0x16, 0xea, 0x9e, 0xdf, 0xad, 0xd3, 0x5d, + 0xfb, 0xd5, 0x56, 0x08, 0xe6, 0x0a, 0x05, 0xd3, 0xcb, 0xf3}, + {0xec, 0x93, 0xde, 0x08, 0x3c, 0x93, 0xd9, 0x33, 0xa9, 0x86, + 0xb3, 0xd5, 0xcd, 0xe2, 0x5a, 0xcb, 0x2f, 0xee, 0xcf, 0x8e}, + {0xed, 0x8d, 0xc8, 0x38, 0x6c, 0x48, 0x86, 0xae, 0xee, 0x07, + 0x91, 0x58, 0xaa, 0xc3, 0xbf, 0xe6, 0x58, 0xe3, 0x94, 0xb4}, + {0xee, 0x29, 0xd6, 0xea, 0x98, 0xe6, 0x32, 0xc6, 0xe5, 0x27, + 0xe0, 0x90, 0x6f, 0x02, 0x80, 0x68, 0x8b, 0xdf, 0x44, 0xdc}, + {0xf1, 0x7f, 0x6f, 0xb6, 0x31, 0xdc, 0x99, 0xe3, 0xa3, 0xc8, + 0x7f, 0xfe, 0x1c, 0xf1, 0x81, 0x10, 0x88, 0xd9, 0x60, 0x33}, + {0xf1, 0x8b, 0x53, 0x8d, 0x1b, 0xe9, 0x03, 0xb6, 0xa6, 0xf0, + 0x56, 0x43, 0x5b, 0x17, 0x15, 0x89, 0xca, 0xf3, 0x6b, 0xf2}, + {0xf4, 0x40, 0x95, 0xc2, 0x38, 0xac, 0x73, 0xfc, 0x4f, 0x77, + 0xbf, 0x8f, 0x98, 0xdf, 0x70, 0xf8, 0xf0, 0x91, 0xbc, 0x52}, + {0xf4, 0x8b, 0x11, 0xbf, 0xde, 0xab, 0xbe, 0x94, 0x54, 0x20, + 0x71, 0xe6, 0x41, 0xde, 0x6b, 0xbe, 0x88, 0x2b, 0x40, 0xb9}, + {0xf5, 0xc2, 0x7c, 0xf5, 0xff, 0xf3, 0x02, 0x9a, 0xcf, 0x1a, + 0x1a, 0x4b, 0xec, 0x7e, 0xe1, 0x96, 0x4c, 0x77, 0xd7, 0x84}, + {0xf9, 0xb5, 0xb6, 0x32, 0x45, 0x5f, 0x9c, 0xbe, 0xec, 0x57, + 0x5f, 0x80, 0xdc, 0xe9, 0x6e, 0x2c, 0xc7, 0xb2, 0x78, 0xb7}, + {0xf9, 0xcd, 0x0e, 0x2c, 0xda, 0x76, 0x24, 0xc1, 0x8f, 0xbd, + 0xf0, 0xf0, 0xab, 0xb6, 0x45, 0xb8, 0xf7, 0xfe, 0xd5, 0x7a}, + {0xf9, 0xdd, 0x19, 0x26, 0x6b, 0x20, 0x43, 0xf1, 0xfe, 0x4b, + 0x3d, 0xcb, 0x01, 0x90, 0xaf, 0xf1, 0x1f, 0x31, 0xa6, 0x9d}, + {0xfa, 0x08, 0x82, 0x59, 0x5f, 0x9c, 0xa6, 0xa1, 0x1e, 0xcc, + 0xbe, 0xaf, 0x65, 0xc7, 0x64, 0xc0, 0xcc, 0xc3, 0x11, 0xd0}, + {0xfa, 0xa7, 0xd9, 0xfb, 0x31, 0xb7, 0x46, 0xf2, 0x00, 0xa8, + 0x5e, 0x65, 0x79, 0x76, 0x13, 0xd8, 0x16, 0xe0, 0x63, 0xb5}, + {0xfa, 0xaa, 0x27, 0xb8, 0xca, 0xf5, 0xfd, 0xf5, 0xcd, 0xa9, + 0x8a, 0xc3, 0x37, 0x85, 0x72, 0xe0, 0x4c, 0xe8, 0xf2, 0xe0}, + {0xfa, 0xb7, 0xee, 0x36, 0x97, 0x26, 0x62, 0xfb, 0x2d, 0xb0, + 0x2a, 0xf6, 0xbf, 0x03, 0xfd, 0xe8, 0x7c, 0x4b, 0x2f, 0x9b}, + {0xfd, 0x1e, 0xd1, 0xe2, 0x02, 0x1b, 0x0b, 0x9f, 0x73, 0xe8, + 0xeb, 0x75, 0xce, 0x23, 0x43, 0x6b, 0xbc, 0xc7, 0x46, 0xeb}, + {0xfe, 0xb8, 0xc4, 0x32, 0xdc, 0xf9, 0x76, 0x9a, 0xce, 0xae, + 0x3d, 0xd8, 0x90, 0x8f, 0xfd, 0x28, 0x86, 0x65, 0x64, 0x7d}, +}; + +#endif // NET_CERT_X509_CERTIFICATE_KNOWN_ROOTS_WIN_H_ diff --git a/net/cert/x509_certificate_mac.cc b/net/cert/x509_certificate_mac.cc new file mode 100644 index 0000000..4f4d9ff --- /dev/null +++ b/net/cert/x509_certificate_mac.cc @@ -0,0 +1,834 @@ +// Copyright (c) 2012 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 "net/cert/x509_certificate.h" + +#include <CommonCrypto/CommonDigest.h> +#include <CoreServices/CoreServices.h> +#include <Security/Security.h> +#include <time.h> + +#include <vector> + +#include "base/lazy_instance.h" +#include "base/logging.h" +#include "base/mac/mac_logging.h" +#include "base/mac/scoped_cftyperef.h" +#include "base/memory/singleton.h" +#include "base/pickle.h" +#include "base/sha1.h" +#include "base/string_piece.h" +#include "base/strings/sys_string_conversions.h" +#include "base/synchronization/lock.h" +#include "crypto/cssm_init.h" +#include "crypto/mac_security_services_lock.h" +#include "crypto/nss_util.h" +#include "crypto/rsa_private_key.h" +#include "net/cert/x509_util_mac.h" +#include "third_party/nss/mozilla/security/nss/lib/certdb/cert.h" + +using base::mac::ScopedCFTypeRef; +using base::Time; + +namespace net { + +namespace { + +void GetCertDistinguishedName( + const x509_util::CSSMCachedCertificate& cached_cert, + const CSSM_OID* oid, + CertPrincipal* result) { + x509_util::CSSMFieldValue distinguished_name; + OSStatus status = cached_cert.GetField(oid, &distinguished_name); + if (status || !distinguished_name.field()) + return; + result->ParseDistinguishedName(distinguished_name.field()->Data, + distinguished_name.field()->Length); +} + +bool IsCertIssuerInEncodedList(X509Certificate::OSCertHandle cert_handle, + const std::vector<std::string>& issuers) { + x509_util::CSSMCachedCertificate cached_cert; + if (cached_cert.Init(cert_handle) != CSSM_OK) + return false; + + x509_util::CSSMFieldValue distinguished_name; + OSStatus status = cached_cert.GetField(&CSSMOID_X509V1IssuerNameStd, + &distinguished_name); + if (status || !distinguished_name.field()) + return false; + + base::StringPiece name_piece( + reinterpret_cast<const char*>(distinguished_name.field()->Data), + static_cast<size_t>(distinguished_name.field()->Length)); + + for (std::vector<std::string>::const_iterator it = issuers.begin(); + it != issuers.end(); ++it) { + base::StringPiece issuer_piece(*it); + if (name_piece == issuer_piece) + return true; + } + + return false; +} + +void GetCertDateForOID(const x509_util::CSSMCachedCertificate& cached_cert, + const CSSM_OID* oid, + Time* result) { + *result = Time::Time(); + + x509_util::CSSMFieldValue field; + OSStatus status = cached_cert.GetField(oid, &field); + if (status) + return; + + const CSSM_X509_TIME* x509_time = field.GetAs<CSSM_X509_TIME>(); + if (x509_time->timeType != BER_TAG_UTC_TIME && + x509_time->timeType != BER_TAG_GENERALIZED_TIME) { + LOG(ERROR) << "Unsupported date/time format " + << x509_time->timeType; + return; + } + + base::StringPiece time_string( + reinterpret_cast<const char*>(x509_time->time.Data), + x509_time->time.Length); + CertDateFormat format = x509_time->timeType == BER_TAG_UTC_TIME ? + CERT_DATE_FORMAT_UTC_TIME : CERT_DATE_FORMAT_GENERALIZED_TIME; + if (!ParseCertificateDate(time_string, format, result)) + LOG(ERROR) << "Invalid certificate date/time " << time_string; +} + +std::string GetCertSerialNumber( + const x509_util::CSSMCachedCertificate& cached_cert) { + x509_util::CSSMFieldValue serial_number; + OSStatus status = cached_cert.GetField(&CSSMOID_X509V1SerialNumber, + &serial_number); + if (status || !serial_number.field()) + return std::string(); + + return std::string( + reinterpret_cast<const char*>(serial_number.field()->Data), + serial_number.field()->Length); +} + +// Gets the issuer for a given cert, starting with the cert itself and +// including the intermediate and finally root certificates (if any). +// This function calls SecTrust but doesn't actually pay attention to the trust +// result: it shouldn't be used to determine trust, just to traverse the chain. +// Caller is responsible for releasing the value stored into *out_cert_chain. +OSStatus CopyCertChain(SecCertificateRef cert_handle, + CFArrayRef* out_cert_chain) { + DCHECK(cert_handle); + DCHECK(out_cert_chain); + + // Create an SSL policy ref configured for client cert evaluation. + SecPolicyRef ssl_policy; + OSStatus result = x509_util::CreateSSLClientPolicy(&ssl_policy); + if (result) + return result; + ScopedCFTypeRef<SecPolicyRef> scoped_ssl_policy(ssl_policy); + + // Create a SecTrustRef. + ScopedCFTypeRef<CFArrayRef> input_certs(CFArrayCreate( + NULL, const_cast<const void**>(reinterpret_cast<void**>(&cert_handle)), + 1, &kCFTypeArrayCallBacks)); + SecTrustRef trust_ref = NULL; + { + base::AutoLock lock(crypto::GetMacSecurityServicesLock()); + result = SecTrustCreateWithCertificates(input_certs, ssl_policy, + &trust_ref); + } + if (result) + return result; + ScopedCFTypeRef<SecTrustRef> trust(trust_ref); + + // Evaluate trust, which creates the cert chain. + SecTrustResultType status; + CSSM_TP_APPLE_EVIDENCE_INFO* status_chain; + { + base::AutoLock lock(crypto::GetMacSecurityServicesLock()); + result = SecTrustEvaluate(trust, &status); + } + if (result) + return result; + { + base::AutoLock lock(crypto::GetMacSecurityServicesLock()); + result = SecTrustGetResult(trust, &status, out_cert_chain, &status_chain); + } + return result; +} + +// Returns true if |purpose| is listed as allowed in |usage|. This +// function also considers the "Any" purpose. If the attribute is +// present and empty, we return false. +bool ExtendedKeyUsageAllows(const CE_ExtendedKeyUsage* usage, + const CSSM_OID* purpose) { + for (unsigned p = 0; p < usage->numPurposes; ++p) { + if (CSSMOIDEqual(&usage->purposes[p], purpose)) + return true; + if (CSSMOIDEqual(&usage->purposes[p], &CSSMOID_ExtendedKeyUsageAny)) + return true; + } + return false; +} + +// Test that a given |cert_handle| is actually a valid X.509 certificate, and +// return true if it is. +// +// On OS X, SecCertificateCreateFromData() does not return any errors if +// called with invalid data, as long as data is present. The actual decoding +// of the certificate does not happen until an API that requires a CSSM +// handle is called. While SecCertificateGetCLHandle is the most likely +// candidate, as it performs the parsing, it does not check whether the +// parsing was actually successful. Instead, SecCertificateGetSubject is +// used (supported since 10.3), as a means to check that the certificate +// parsed as a valid X.509 certificate. +bool IsValidOSCertHandle(SecCertificateRef cert_handle) { + const CSSM_X509_NAME* sanity_check = NULL; + OSStatus status = SecCertificateGetSubject(cert_handle, &sanity_check); + return status == noErr && sanity_check; +} + +// Parses |data| of length |length|, attempting to decode it as the specified +// |format|. If |data| is in the specified format, any certificates contained +// within are stored into |output|. +void AddCertificatesFromBytes(const char* data, size_t length, + SecExternalFormat format, + X509Certificate::OSCertHandles* output) { + SecExternalFormat input_format = format; + ScopedCFTypeRef<CFDataRef> local_data(CFDataCreateWithBytesNoCopy( + kCFAllocatorDefault, reinterpret_cast<const UInt8*>(data), length, + kCFAllocatorNull)); + + CFArrayRef items = NULL; + OSStatus status; + { + base::AutoLock lock(crypto::GetMacSecurityServicesLock()); + status = SecKeychainItemImport(local_data, NULL, &input_format, + NULL, 0, NULL, NULL, &items); + } + + if (status) { + OSSTATUS_DLOG(WARNING, status) + << "Unable to import items from data of length " << length; + return; + } + + ScopedCFTypeRef<CFArrayRef> scoped_items(items); + CFTypeID cert_type_id = SecCertificateGetTypeID(); + + for (CFIndex i = 0; i < CFArrayGetCount(items); ++i) { + SecKeychainItemRef item = reinterpret_cast<SecKeychainItemRef>( + const_cast<void*>(CFArrayGetValueAtIndex(items, i))); + + // While inputFormat implies only certificates will be imported, if/when + // other formats (eg: PKCS#12) are supported, this may also include + // private keys or other items types, so filter appropriately. + if (CFGetTypeID(item) == cert_type_id) { + SecCertificateRef cert = reinterpret_cast<SecCertificateRef>(item); + // OS X ignores |input_format| if it detects that |local_data| is PEM + // encoded, attempting to decode data based on internal rules for PEM + // block headers. If a PKCS#7 blob is encoded with a PEM block of + // CERTIFICATE, OS X 10.5 will return a single, invalid certificate + // based on the decoded data. If this happens, the certificate should + // not be included in |output|. Because |output| is empty, + // CreateCertificateListfromBytes will use PEMTokenizer to decode the + // data. When called again with the decoded data, OS X will honor + // |input_format|, causing decode to succeed. On OS X 10.6, the data + // is properly decoded as a PKCS#7, whether PEM or not, which avoids + // the need to fallback to internal decoding. + if (IsValidOSCertHandle(cert)) { + CFRetain(cert); + output->push_back(cert); + } + } + } +} + +struct CSSMOIDString { + const CSSM_OID* oid_; + std::string string_; +}; + +typedef std::vector<CSSMOIDString> CSSMOIDStringVector; + +bool CERTNameToCSSMOIDVector(CERTName* name, CSSMOIDStringVector* out_values) { + struct OIDCSSMMap { + SECOidTag sec_OID_; + const CSSM_OID* cssm_OID_; + }; + + const OIDCSSMMap kOIDs[] = { + { SEC_OID_AVA_COMMON_NAME, &CSSMOID_CommonName }, + { SEC_OID_AVA_COUNTRY_NAME, &CSSMOID_CountryName }, + { SEC_OID_AVA_LOCALITY, &CSSMOID_LocalityName }, + { SEC_OID_AVA_STATE_OR_PROVINCE, &CSSMOID_StateProvinceName }, + { SEC_OID_AVA_STREET_ADDRESS, &CSSMOID_StreetAddress }, + { SEC_OID_AVA_ORGANIZATION_NAME, &CSSMOID_OrganizationName }, + { SEC_OID_AVA_ORGANIZATIONAL_UNIT_NAME, &CSSMOID_OrganizationalUnitName }, + { SEC_OID_AVA_DN_QUALIFIER, &CSSMOID_DNQualifier }, + { SEC_OID_RFC1274_UID, &CSSMOID_UniqueIdentifier }, + { SEC_OID_PKCS9_EMAIL_ADDRESS, &CSSMOID_EmailAddress }, + }; + + CERTRDN** rdns = name->rdns; + for (size_t rdn = 0; rdns[rdn]; ++rdn) { + CERTAVA** avas = rdns[rdn]->avas; + for (size_t pair = 0; avas[pair] != 0; ++pair) { + SECOidTag tag = CERT_GetAVATag(avas[pair]); + if (tag == SEC_OID_UNKNOWN) { + return false; + } + CSSMOIDString oidString; + bool found_oid = false; + for (size_t oid = 0; oid < ARRAYSIZE_UNSAFE(kOIDs); ++oid) { + if (kOIDs[oid].sec_OID_ == tag) { + SECItem* decode_item = CERT_DecodeAVAValue(&avas[pair]->value); + if (!decode_item) + return false; + + // TODO(wtc): Pass decode_item to CERT_RFC1485_EscapeAndQuote. + std::string value(reinterpret_cast<char*>(decode_item->data), + decode_item->len); + oidString.oid_ = kOIDs[oid].cssm_OID_; + oidString.string_ = value; + out_values->push_back(oidString); + SECITEM_FreeItem(decode_item, PR_TRUE); + found_oid = true; + break; + } + } + if (!found_oid) { + DLOG(ERROR) << "Unrecognized OID: " << tag; + } + } + } + return true; +} + +class ScopedCertName { + public: + explicit ScopedCertName(CERTName* name) : name_(name) { } + ~ScopedCertName() { + if (name_) CERT_DestroyName(name_); + } + operator CERTName*() { return name_; } + + private: + CERTName* name_; +}; + +class ScopedEncodedCertResults { + public: + explicit ScopedEncodedCertResults(CSSM_TP_RESULT_SET* results) + : results_(results) { } + ~ScopedEncodedCertResults() { + if (results_) { + CSSM_ENCODED_CERT* encCert = + reinterpret_cast<CSSM_ENCODED_CERT*>(results_->Results); + for (uint32 i = 0; i < results_->NumberOfResults; i++) { + crypto::CSSMFree(encCert[i].CertBlob.Data); + } + } + crypto::CSSMFree(results_->Results); + crypto::CSSMFree(results_); + } + + private: + CSSM_TP_RESULT_SET* results_; +}; + +} // namespace + +void X509Certificate::Initialize() { + x509_util::CSSMCachedCertificate cached_cert; + if (cached_cert.Init(cert_handle_) == CSSM_OK) { + GetCertDistinguishedName(cached_cert, &CSSMOID_X509V1SubjectNameStd, + &subject_); + GetCertDistinguishedName(cached_cert, &CSSMOID_X509V1IssuerNameStd, + &issuer_); + GetCertDateForOID(cached_cert, &CSSMOID_X509V1ValidityNotBefore, + &valid_start_); + GetCertDateForOID(cached_cert, &CSSMOID_X509V1ValidityNotAfter, + &valid_expiry_); + serial_number_ = GetCertSerialNumber(cached_cert); + } + + fingerprint_ = CalculateFingerprint(cert_handle_); + ca_fingerprint_ = CalculateCAFingerprint(intermediate_ca_certs_); +} + +bool X509Certificate::IsIssuedByEncoded( + const std::vector<std::string>& valid_issuers) { + if (IsCertIssuerInEncodedList(cert_handle_, valid_issuers)) + return true; + + for (OSCertHandles::iterator it = intermediate_ca_certs_.begin(); + it != intermediate_ca_certs_.end(); ++it) { + if (IsCertIssuerInEncodedList(*it, valid_issuers)) + return true; + } + return false; +} + +// static +X509Certificate* X509Certificate::CreateSelfSigned( + crypto::RSAPrivateKey* key, + const std::string& subject, + uint32 serial_number, + base::TimeDelta valid_duration) { + DCHECK(key); + DCHECK(!subject.empty()); + + if (valid_duration.InSeconds() > kuint32max) { + LOG(ERROR) << "valid_duration too big " << valid_duration.InSeconds(); + valid_duration = base::TimeDelta::FromSeconds(kuint32max); + } + + // There is a comment in + // http://www.opensource.apple.com/source/security_certtool/security_certtool-31828/src/CertTool.cpp + // that serial_numbers being passed into CSSM_TP_SubmitCredRequest can't have + // their high bit set. We will continue though and mask it out below. + if (serial_number & 0x80000000) + LOG(ERROR) << "serial_number has high bit set " << serial_number; + + // NSS is used to parse the subject string into a set of + // CSSM_OID/string pairs. There doesn't appear to be a system routine for + // parsing Distinguished Name strings. + crypto::EnsureNSSInit(); + + CSSMOIDStringVector subject_name_oids; + ScopedCertName subject_name( + CERT_AsciiToName(const_cast<char*>(subject.c_str()))); + if (!CERTNameToCSSMOIDVector(subject_name, &subject_name_oids)) { + DLOG(ERROR) << "Unable to generate CSSMOIDMap from " << subject; + return NULL; + } + + // Convert the map of oid/string pairs into an array of + // CSSM_APPLE_TP_NAME_OIDs. + std::vector<CSSM_APPLE_TP_NAME_OID> cssm_subject_names; + for (CSSMOIDStringVector::iterator iter = subject_name_oids.begin(); + iter != subject_name_oids.end(); ++iter) { + CSSM_APPLE_TP_NAME_OID cssm_subject_name; + cssm_subject_name.oid = iter->oid_; + cssm_subject_name.string = iter->string_.c_str(); + cssm_subject_names.push_back(cssm_subject_name); + } + + if (cssm_subject_names.empty()) { + DLOG(ERROR) << "cssm_subject_names.size() == 0. Input: " << subject; + return NULL; + } + + // Set up a certificate request. + CSSM_APPLE_TP_CERT_REQUEST certReq; + memset(&certReq, 0, sizeof(certReq)); + certReq.cspHand = crypto::GetSharedCSPHandle(); + certReq.clHand = crypto::GetSharedCLHandle(); + // See comment about serial numbers above. + certReq.serialNumber = serial_number & 0x7fffffff; + certReq.numSubjectNames = cssm_subject_names.size(); + certReq.subjectNames = &cssm_subject_names[0]; + certReq.numIssuerNames = 0; // Root. + certReq.issuerNames = NULL; + certReq.issuerNameX509 = NULL; + certReq.certPublicKey = key->public_key(); + certReq.issuerPrivateKey = key->key(); + // These are the Apple defaults. + certReq.signatureAlg = CSSM_ALGID_SHA1WithRSA; + certReq.signatureOid = CSSMOID_SHA1WithRSA; + certReq.notBefore = 0; + certReq.notAfter = static_cast<uint32>(valid_duration.InSeconds()); + certReq.numExtensions = 0; + certReq.extensions = NULL; + certReq.challengeString = NULL; + + CSSM_TP_REQUEST_SET reqSet; + reqSet.NumberOfRequests = 1; + reqSet.Requests = &certReq; + + CSSM_FIELD policyId; + memset(&policyId, 0, sizeof(policyId)); + policyId.FieldOid = CSSMOID_APPLE_TP_LOCAL_CERT_GEN; + + CSSM_TP_CALLERAUTH_CONTEXT callerAuthContext; + memset(&callerAuthContext, 0, sizeof(callerAuthContext)); + callerAuthContext.Policy.NumberOfPolicyIds = 1; + callerAuthContext.Policy.PolicyIds = &policyId; + + CSSM_TP_HANDLE tp_handle = crypto::GetSharedTPHandle(); + CSSM_DATA refId; + memset(&refId, 0, sizeof(refId)); + sint32 estTime; + CSSM_RETURN crtn = CSSM_TP_SubmitCredRequest(tp_handle, NULL, + CSSM_TP_AUTHORITY_REQUEST_CERTISSUE, &reqSet, &callerAuthContext, + &estTime, &refId); + if (crtn) { + DLOG(ERROR) << "CSSM_TP_SubmitCredRequest failed " << crtn; + return NULL; + } + + CSSM_BOOL confirmRequired; + CSSM_TP_RESULT_SET* resultSet = NULL; + crtn = CSSM_TP_RetrieveCredResult(tp_handle, &refId, NULL, &estTime, + &confirmRequired, &resultSet); + ScopedEncodedCertResults scopedResults(resultSet); + crypto::CSSMFree(refId.Data); + if (crtn) { + DLOG(ERROR) << "CSSM_TP_RetrieveCredResult failed " << crtn; + return NULL; + } + + if (confirmRequired) { + // Potential leak here of resultSet. |confirmRequired| should never be + // true. + DLOG(ERROR) << "CSSM_TP_RetrieveCredResult required confirmation"; + return NULL; + } + + if (resultSet->NumberOfResults != 1) { + DLOG(ERROR) << "Unexpected number of results: " + << resultSet->NumberOfResults; + return NULL; + } + + CSSM_ENCODED_CERT* encCert = + reinterpret_cast<CSSM_ENCODED_CERT*>(resultSet->Results); + ScopedCFTypeRef<SecCertificateRef> scoped_cert; + SecCertificateRef certificate_ref = NULL; + OSStatus os_status = + SecCertificateCreateFromData(&encCert->CertBlob, encCert->CertType, + encCert->CertEncoding, &certificate_ref); + if (os_status != 0) { + OSSTATUS_DLOG(ERROR, os_status) << "SecCertificateCreateFromData failed"; + return NULL; + } + scoped_cert.reset(certificate_ref); + + return CreateFromHandle(scoped_cert, X509Certificate::OSCertHandles()); +} + +void X509Certificate::GetSubjectAltName( + std::vector<std::string>* dns_names, + std::vector<std::string>* ip_addrs) const { + if (dns_names) + dns_names->clear(); + if (ip_addrs) + ip_addrs->clear(); + + x509_util::CSSMCachedCertificate cached_cert; + OSStatus status = cached_cert.Init(cert_handle_); + if (status) + return; + x509_util::CSSMFieldValue subject_alt_name; + status = cached_cert.GetField(&CSSMOID_SubjectAltName, &subject_alt_name); + if (status || !subject_alt_name.field()) + return; + const CSSM_X509_EXTENSION* cssm_ext = + subject_alt_name.GetAs<CSSM_X509_EXTENSION>(); + if (!cssm_ext || !cssm_ext->value.parsedValue) + return; + const CE_GeneralNames* alt_name = + reinterpret_cast<const CE_GeneralNames*>(cssm_ext->value.parsedValue); + + for (size_t name = 0; name < alt_name->numNames; ++name) { + const CE_GeneralName& name_struct = alt_name->generalName[name]; + const CSSM_DATA& name_data = name_struct.name; + // DNSName and IPAddress are encoded as IA5String and OCTET STRINGs + // respectively, both of which can be byte copied from + // CSSM_DATA::data into the appropriate output vector. + if (dns_names && name_struct.nameType == GNT_DNSName) { + dns_names->push_back(std::string( + reinterpret_cast<const char*>(name_data.Data), + name_data.Length)); + } else if (ip_addrs && name_struct.nameType == GNT_IPAddress) { + ip_addrs->push_back(std::string( + reinterpret_cast<const char*>(name_data.Data), + name_data.Length)); + } + } +} + +// static +bool X509Certificate::GetDEREncoded(X509Certificate::OSCertHandle cert_handle, + std::string* encoded) { + CSSM_DATA der_data; + if (SecCertificateGetData(cert_handle, &der_data) != noErr) + return false; + encoded->assign(reinterpret_cast<char*>(der_data.Data), + der_data.Length); + return true; +} + +// static +bool X509Certificate::IsSameOSCert(X509Certificate::OSCertHandle a, + X509Certificate::OSCertHandle b) { + DCHECK(a && b); + if (a == b) + return true; + if (CFEqual(a, b)) + return true; + CSSM_DATA a_data, b_data; + return SecCertificateGetData(a, &a_data) == noErr && + SecCertificateGetData(b, &b_data) == noErr && + a_data.Length == b_data.Length && + memcmp(a_data.Data, b_data.Data, a_data.Length) == 0; +} + +// static +X509Certificate::OSCertHandle X509Certificate::CreateOSCertHandleFromBytes( + const char* data, int length) { + CSSM_DATA cert_data; + cert_data.Data = const_cast<uint8*>(reinterpret_cast<const uint8*>(data)); + cert_data.Length = length; + + OSCertHandle cert_handle = NULL; + OSStatus status = SecCertificateCreateFromData(&cert_data, + CSSM_CERT_X_509v3, + CSSM_CERT_ENCODING_DER, + &cert_handle); + if (status != noErr) + return NULL; + if (!IsValidOSCertHandle(cert_handle)) { + CFRelease(cert_handle); + return NULL; + } + return cert_handle; +} + +// static +X509Certificate::OSCertHandles X509Certificate::CreateOSCertHandlesFromBytes( + const char* data, int length, Format format) { + OSCertHandles results; + + switch (format) { + case FORMAT_SINGLE_CERTIFICATE: { + OSCertHandle handle = CreateOSCertHandleFromBytes(data, length); + if (handle) + results.push_back(handle); + break; + } + case FORMAT_PKCS7: + AddCertificatesFromBytes(data, length, kSecFormatPKCS7, &results); + break; + default: + NOTREACHED() << "Certificate format " << format << " unimplemented"; + break; + } + + return results; +} + +// static +X509Certificate::OSCertHandle X509Certificate::DupOSCertHandle( + OSCertHandle handle) { + if (!handle) + return NULL; + return reinterpret_cast<OSCertHandle>(const_cast<void*>(CFRetain(handle))); +} + +// static +void X509Certificate::FreeOSCertHandle(OSCertHandle cert_handle) { + CFRelease(cert_handle); +} + +// static +SHA1HashValue X509Certificate::CalculateFingerprint( + OSCertHandle cert) { + SHA1HashValue sha1; + memset(sha1.data, 0, sizeof(sha1.data)); + + CSSM_DATA cert_data; + OSStatus status = SecCertificateGetData(cert, &cert_data); + if (status) + return sha1; + + DCHECK(cert_data.Data); + DCHECK_NE(cert_data.Length, 0U); + + CC_SHA1(cert_data.Data, cert_data.Length, sha1.data); + + return sha1; +} + +// static +SHA1HashValue X509Certificate::CalculateCAFingerprint( + const OSCertHandles& intermediates) { + SHA1HashValue sha1; + memset(sha1.data, 0, sizeof(sha1.data)); + + // The CC_SHA(3cc) man page says all CC_SHA1_xxx routines return 1, so + // we don't check their return values. + CC_SHA1_CTX sha1_ctx; + CC_SHA1_Init(&sha1_ctx); + CSSM_DATA cert_data; + for (size_t i = 0; i < intermediates.size(); ++i) { + OSStatus status = SecCertificateGetData(intermediates[i], &cert_data); + if (status) + return sha1; + CC_SHA1_Update(&sha1_ctx, cert_data.Data, cert_data.Length); + } + CC_SHA1_Final(sha1.data, &sha1_ctx); + + return sha1; +} + +bool X509Certificate::SupportsSSLClientAuth() const { + x509_util::CSSMCachedCertificate cached_cert; + OSStatus status = cached_cert.Init(cert_handle_); + if (status) + return false; + + // RFC5280 says to take the intersection of the two extensions. + // + // Our underlying crypto libraries don't expose + // ClientCertificateType, so for now we will not support fixed + // Diffie-Hellman mechanisms. For rsa_sign, we need the + // digitalSignature bit. + // + // In particular, if a key has the nonRepudiation bit and not the + // digitalSignature one, we will not offer it to the user. + x509_util::CSSMFieldValue key_usage; + status = cached_cert.GetField(&CSSMOID_KeyUsage, &key_usage); + if (status == CSSM_OK && key_usage.field()) { + const CSSM_X509_EXTENSION* ext = key_usage.GetAs<CSSM_X509_EXTENSION>(); + const CE_KeyUsage* key_usage_value = + reinterpret_cast<const CE_KeyUsage*>(ext->value.parsedValue); + if (!((*key_usage_value) & CE_KU_DigitalSignature)) + return false; + } + + status = cached_cert.GetField(&CSSMOID_ExtendedKeyUsage, &key_usage); + if (status == CSSM_OK && key_usage.field()) { + const CSSM_X509_EXTENSION* ext = key_usage.GetAs<CSSM_X509_EXTENSION>(); + const CE_ExtendedKeyUsage* ext_key_usage = + reinterpret_cast<const CE_ExtendedKeyUsage*>(ext->value.parsedValue); + if (!ExtendedKeyUsageAllows(ext_key_usage, &CSSMOID_ClientAuth)) + return false; + } + return true; +} + +CFArrayRef X509Certificate::CreateClientCertificateChain() const { + // Initialize the result array with just the IdentityRef of the receiver: + SecIdentityRef identity; + OSStatus result; + { + base::AutoLock lock(crypto::GetMacSecurityServicesLock()); + result = SecIdentityCreateWithCertificate(NULL, cert_handle_, &identity); + } + if (result) { + OSSTATUS_LOG(ERROR, result) << "SecIdentityCreateWithCertificate error"; + return NULL; + } + ScopedCFTypeRef<CFMutableArrayRef> chain( + CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks)); + CFArrayAppendValue(chain, identity); + + CFArrayRef cert_chain = NULL; + result = CopyCertChain(cert_handle_, &cert_chain); + ScopedCFTypeRef<CFArrayRef> scoped_cert_chain(cert_chain); + if (result) { + OSSTATUS_LOG(ERROR, result) << "CreateIdentityCertificateChain error"; + return chain.release(); + } + + // Append the intermediate certs from SecTrust to the result array: + if (cert_chain) { + int chain_count = CFArrayGetCount(cert_chain); + if (chain_count > 1) { + CFArrayAppendArray(chain, + cert_chain, + CFRangeMake(1, chain_count - 1)); + } + } + + return chain.release(); +} + +CFArrayRef X509Certificate::CreateOSCertChainForCert() const { + CFMutableArrayRef cert_list = + CFArrayCreateMutable(kCFAllocatorDefault, 0, + &kCFTypeArrayCallBacks); + if (!cert_list) + return NULL; + + CFArrayAppendValue(cert_list, os_cert_handle()); + for (size_t i = 0; i < intermediate_ca_certs_.size(); ++i) + CFArrayAppendValue(cert_list, intermediate_ca_certs_[i]); + + return cert_list; +} + +// static +X509Certificate::OSCertHandle +X509Certificate::ReadOSCertHandleFromPickle(PickleIterator* pickle_iter) { + const char* data; + int length; + if (!pickle_iter->ReadData(&data, &length)) + return NULL; + + return CreateOSCertHandleFromBytes(data, length); +} + +// static +bool X509Certificate::WriteOSCertHandleToPickle(OSCertHandle cert_handle, + Pickle* pickle) { + CSSM_DATA cert_data; + OSStatus status = SecCertificateGetData(cert_handle, &cert_data); + if (status) + return false; + + return pickle->WriteData(reinterpret_cast<char*>(cert_data.Data), + cert_data.Length); +} + +// static +void X509Certificate::GetPublicKeyInfo(OSCertHandle cert_handle, + size_t* size_bits, + PublicKeyType* type) { + // Since we might fail, set the output parameters to default values first. + *type = kPublicKeyTypeUnknown; + *size_bits = 0; + + SecKeyRef key; + OSStatus status = SecCertificateCopyPublicKey(cert_handle, &key); + if (status) { + NOTREACHED() << "SecCertificateCopyPublicKey failed: " << status; + return; + } + ScopedCFTypeRef<SecKeyRef> scoped_key(key); + + const CSSM_KEY* cssm_key; + status = SecKeyGetCSSMKey(key, &cssm_key); + if (status) { + NOTREACHED() << "SecKeyGetCSSMKey failed: " << status; + return; + } + + *size_bits = cssm_key->KeyHeader.LogicalKeySizeInBits; + + switch (cssm_key->KeyHeader.AlgorithmId) { + case CSSM_ALGID_RSA: + *type = kPublicKeyTypeRSA; + break; + case CSSM_ALGID_DSA: + *type = kPublicKeyTypeDSA; + break; + case CSSM_ALGID_ECDSA: + *type = kPublicKeyTypeECDSA; + break; + case CSSM_ALGID_DH: + *type = kPublicKeyTypeDH; + break; + default: + *type = kPublicKeyTypeUnknown; + *size_bits = 0; + break; + } +} + +} // namespace net diff --git a/net/cert/x509_certificate_net_log_param.cc b/net/cert/x509_certificate_net_log_param.cc new file mode 100644 index 0000000..3c52b96 --- /dev/null +++ b/net/cert/x509_certificate_net_log_param.cc @@ -0,0 +1,27 @@ +// Copyright (c) 2012 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 "net/cert/x509_certificate_net_log_param.h" + +#include <string> +#include <vector> + +#include "base/values.h" +#include "net/cert/x509_certificate.h" + +namespace net { + +base::Value* NetLogX509CertificateCallback(const X509Certificate* certificate, + NetLog::LogLevel log_level) { + base::DictionaryValue* dict = new base::DictionaryValue(); + base::ListValue* certs = new base::ListValue(); + std::vector<std::string> encoded_chain; + certificate->GetPEMEncodedChain(&encoded_chain); + for (size_t i = 0; i < encoded_chain.size(); ++i) + certs->Append(new base::StringValue(encoded_chain[i])); + dict->Set("certificates", certs); + return dict; +} + +} // namespace net diff --git a/net/cert/x509_certificate_net_log_param.h b/net/cert/x509_certificate_net_log_param.h new file mode 100644 index 0000000..36ae8f5 --- /dev/null +++ b/net/cert/x509_certificate_net_log_param.h @@ -0,0 +1,21 @@ +// Copyright (c) 2012 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. + +#ifndef NET_BASE_X509_CERT_NET_LOG_PARAM_H_ +#define NET_BASE_X509_CERT_NET_LOG_PARAM_H_ + +#include "net/base/net_log.h" + +namespace net { + +class X509Certificate; + +// Creates NetLog parameter to describe an X509Certificate. +base::Value* NetLogX509CertificateCallback( + const X509Certificate* certificate, + NetLog::LogLevel log_level); + +} // namespace net + +#endif // NET_BASE_X509_CERT_NET_LOG_PARAM_H_ diff --git a/net/cert/x509_certificate_nss.cc b/net/cert/x509_certificate_nss.cc new file mode 100644 index 0000000..f6fdd94 --- /dev/null +++ b/net/cert/x509_certificate_nss.cc @@ -0,0 +1,306 @@ +// Copyright (c) 2012 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 "net/cert/x509_certificate.h" + +#include <cert.h> +#include <cryptohi.h> +#include <keyhi.h> +#include <nss.h> +#include <pk11pub.h> +#include <prtime.h> +#include <seccomon.h> +#include <secder.h> +#include <sechash.h> + +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "base/pickle.h" +#include "base/time.h" +#include "crypto/nss_util.h" +#include "crypto/rsa_private_key.h" +#include "crypto/scoped_nss_types.h" +#include "net/cert/x509_util_nss.h" + +namespace net { + +void X509Certificate::Initialize() { + x509_util::ParsePrincipal(&cert_handle_->subject, &subject_); + x509_util::ParsePrincipal(&cert_handle_->issuer, &issuer_); + + x509_util::ParseDate(&cert_handle_->validity.notBefore, &valid_start_); + x509_util::ParseDate(&cert_handle_->validity.notAfter, &valid_expiry_); + + fingerprint_ = CalculateFingerprint(cert_handle_); + ca_fingerprint_ = CalculateCAFingerprint(intermediate_ca_certs_); + + serial_number_ = x509_util::ParseSerialNumber(cert_handle_); +} + +// static +X509Certificate* X509Certificate::CreateFromBytesWithNickname( + const char* data, + int length, + const char* nickname) { + OSCertHandle cert_handle = CreateOSCertHandleFromBytesWithNickname(data, + length, + nickname); + if (!cert_handle) + return NULL; + + X509Certificate* cert = CreateFromHandle(cert_handle, OSCertHandles()); + FreeOSCertHandle(cert_handle); + + if (nickname) + cert->default_nickname_ = nickname; + + return cert; +} + +std::string X509Certificate::GetDefaultNickname(CertType type) const { + if (!default_nickname_.empty()) + return default_nickname_; + + std::string result; + if (type == USER_CERT && cert_handle_->slot) { + // Find the private key for this certificate and see if it has a + // nickname. If there is a private key, and it has a nickname, then + // we return that nickname. + SECKEYPrivateKey* private_key = PK11_FindPrivateKeyFromCert( + cert_handle_->slot, + cert_handle_, + NULL); // wincx + if (private_key) { + char* private_key_nickname = PK11_GetPrivateKeyNickname(private_key); + if (private_key_nickname) { + result = private_key_nickname; + PORT_Free(private_key_nickname); + SECKEY_DestroyPrivateKey(private_key); + return result; + } + SECKEY_DestroyPrivateKey(private_key); + } + } + + switch (type) { + case CA_CERT: { + char* nickname = CERT_MakeCANickname(cert_handle_); + result = nickname; + PORT_Free(nickname); + break; + } + case USER_CERT: { + // Create a nickname for a user certificate. + // We use the scheme used by Firefox: + // --> <subject's common name>'s <issuer's common name> ID. + // TODO(gspencer): internationalize this: it's wrong to + // hard code English. + + std::string username, ca_name; + char* temp_username = CERT_GetCommonName( + &cert_handle_->subject); + char* temp_ca_name = CERT_GetCommonName(&cert_handle_->issuer); + if (temp_username) { + username = temp_username; + PORT_Free(temp_username); + } + if (temp_ca_name) { + ca_name = temp_ca_name; + PORT_Free(temp_ca_name); + } + result = username + "'s " + ca_name + " ID"; + break; + } + case SERVER_CERT: + result = subject_.GetDisplayName(); + break; + case UNKNOWN_CERT: + default: + break; + } + return result; +} + +// static +X509Certificate* X509Certificate::CreateSelfSigned( + crypto::RSAPrivateKey* key, + const std::string& subject, + uint32 serial_number, + base::TimeDelta valid_duration) { + DCHECK(key); + base::Time not_valid_before = base::Time::Now(); + base::Time not_valid_after = not_valid_before + valid_duration; + CERTCertificate* cert = x509_util::CreateSelfSignedCert(key->public_key(), + key->key(), + subject, + serial_number, + not_valid_before, + not_valid_after); + if (!cert) + return NULL; + + X509Certificate* x509_cert = X509Certificate::CreateFromHandle( + cert, X509Certificate::OSCertHandles()); + CERT_DestroyCertificate(cert); + return x509_cert; +} + +void X509Certificate::GetSubjectAltName( + std::vector<std::string>* dns_names, + std::vector<std::string>* ip_addrs) const { + x509_util::GetSubjectAltName(cert_handle_, dns_names, ip_addrs); +} + +bool X509Certificate::VerifyNameMatch(const std::string& hostname) const { + return CERT_VerifyCertName(cert_handle_, hostname.c_str()) == SECSuccess; +} + +bool X509Certificate::IsIssuedByEncoded( + const std::vector<std::string>& valid_issuers) { + // Get certificate chain as scoped list of CERTCertificate objects. + std::vector<CERTCertificate*> cert_chain; + cert_chain.push_back(cert_handle_); + for (size_t n = 0; n < intermediate_ca_certs_.size(); ++n) { + cert_chain.push_back(intermediate_ca_certs_[n]); + } + // Convert encoded issuers to scoped CERTName* list. + std::vector<CERTName*> issuers; + crypto::ScopedPLArenaPool arena(PORT_NewArena(DER_DEFAULT_CHUNKSIZE)); + if (!x509_util::GetIssuersFromEncodedList(valid_issuers, + arena.get(), + &issuers)) { + return false; + } + return x509_util::IsCertificateIssuedBy(cert_chain, issuers); +} + +// static +bool X509Certificate::GetDEREncoded(X509Certificate::OSCertHandle cert_handle, + std::string* encoded) { + if (!cert_handle->derCert.len) + return false; + encoded->assign(reinterpret_cast<char*>(cert_handle->derCert.data), + cert_handle->derCert.len); + return true; +} + +// static +bool X509Certificate::IsSameOSCert(X509Certificate::OSCertHandle a, + X509Certificate::OSCertHandle b) { + DCHECK(a && b); + if (a == b) + return true; + return a->derCert.len == b->derCert.len && + memcmp(a->derCert.data, b->derCert.data, a->derCert.len) == 0; +} + +// static +X509Certificate::OSCertHandle X509Certificate::CreateOSCertHandleFromBytes( + const char* data, int length) { + return CreateOSCertHandleFromBytesWithNickname(data, length, NULL); +} + +// static +X509Certificate::OSCertHandle +X509Certificate::CreateOSCertHandleFromBytesWithNickname( + const char* data, + int length, + const char* nickname) { + if (length < 0) + return NULL; + + crypto::EnsureNSSInit(); + + if (!NSS_IsInitialized()) + return NULL; + + SECItem der_cert; + der_cert.data = reinterpret_cast<unsigned char*>(const_cast<char*>(data)); + der_cert.len = length; + der_cert.type = siDERCertBuffer; + + // Parse into a certificate structure. + return CERT_NewTempCertificate(CERT_GetDefaultCertDB(), &der_cert, + const_cast<char*>(nickname), + PR_FALSE, PR_TRUE); +} + +// static +X509Certificate::OSCertHandles X509Certificate::CreateOSCertHandlesFromBytes( + const char* data, + int length, + Format format) { + return x509_util::CreateOSCertHandlesFromBytes(data, length, format); +} + +// static +X509Certificate::OSCertHandle X509Certificate::DupOSCertHandle( + OSCertHandle cert_handle) { + return CERT_DupCertificate(cert_handle); +} + +// static +void X509Certificate::FreeOSCertHandle(OSCertHandle cert_handle) { + CERT_DestroyCertificate(cert_handle); +} + +// static +SHA1HashValue X509Certificate::CalculateFingerprint( + OSCertHandle cert) { + SHA1HashValue sha1; + memset(sha1.data, 0, sizeof(sha1.data)); + + DCHECK(NULL != cert->derCert.data); + DCHECK_NE(0U, cert->derCert.len); + + SECStatus rv = HASH_HashBuf(HASH_AlgSHA1, sha1.data, + cert->derCert.data, cert->derCert.len); + DCHECK_EQ(SECSuccess, rv); + + return sha1; +} + +// static +SHA1HashValue X509Certificate::CalculateCAFingerprint( + const OSCertHandles& intermediates) { + SHA1HashValue sha1; + memset(sha1.data, 0, sizeof(sha1.data)); + + HASHContext* sha1_ctx = HASH_Create(HASH_AlgSHA1); + if (!sha1_ctx) + return sha1; + HASH_Begin(sha1_ctx); + for (size_t i = 0; i < intermediates.size(); ++i) { + CERTCertificate* ca_cert = intermediates[i]; + HASH_Update(sha1_ctx, ca_cert->derCert.data, ca_cert->derCert.len); + } + unsigned int result_len; + HASH_End(sha1_ctx, sha1.data, &result_len, HASH_ResultLenContext(sha1_ctx)); + HASH_Destroy(sha1_ctx); + + return sha1; +} + +// static +X509Certificate::OSCertHandle +X509Certificate::ReadOSCertHandleFromPickle(PickleIterator* pickle_iter) { + return x509_util::ReadOSCertHandleFromPickle(pickle_iter); +} + +// static +bool X509Certificate::WriteOSCertHandleToPickle(OSCertHandle cert_handle, + Pickle* pickle) { + return pickle->WriteData( + reinterpret_cast<const char*>(cert_handle->derCert.data), + cert_handle->derCert.len); +} + +// static +void X509Certificate::GetPublicKeyInfo(OSCertHandle cert_handle, + size_t* size_bits, + PublicKeyType* type) { + x509_util::GetPublicKeyInfo(cert_handle, size_bits, type); +} + +} // namespace net diff --git a/net/cert/x509_certificate_openssl.cc b/net/cert/x509_certificate_openssl.cc new file mode 100644 index 0000000..7f77d55 --- /dev/null +++ b/net/cert/x509_certificate_openssl.cc @@ -0,0 +1,531 @@ +// Copyright (c) 2012 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 "net/cert/x509_certificate.h" + +#include <openssl/asn1.h> +#include <openssl/crypto.h> +#include <openssl/obj_mac.h> +#include <openssl/pem.h> +#include <openssl/pkcs7.h> +#include <openssl/sha.h> +#include <openssl/ssl.h> +#include <openssl/x509v3.h> + +#include "base/memory/singleton.h" +#include "base/pickle.h" +#include "base/sha1.h" +#include "base/string_number_conversions.h" +#include "base/string_util.h" +#include "crypto/openssl_util.h" +#include "net/base/net_errors.h" +#include "net/base/net_util.h" +#include "net/cert/x509_util_openssl.h" + +#if defined(OS_ANDROID) +#include "base/logging.h" +#include "net/android/network_library.h" +#endif + +namespace net { + +namespace { + +void CreateOSCertHandlesFromPKCS7Bytes( + const char* data, int length, + X509Certificate::OSCertHandles* handles) { + crypto::EnsureOpenSSLInit(); + const unsigned char* der_data = reinterpret_cast<const unsigned char*>(data); + crypto::ScopedOpenSSL<PKCS7, PKCS7_free> pkcs7_cert( + d2i_PKCS7(NULL, &der_data, length)); + if (!pkcs7_cert.get()) + return; + + STACK_OF(X509)* certs = NULL; + int nid = OBJ_obj2nid(pkcs7_cert.get()->type); + if (nid == NID_pkcs7_signed) { + certs = pkcs7_cert.get()->d.sign->cert; + } else if (nid == NID_pkcs7_signedAndEnveloped) { + certs = pkcs7_cert.get()->d.signed_and_enveloped->cert; + } + + if (certs) { + for (int i = 0; i < sk_X509_num(certs); ++i) { + X509* x509_cert = + X509Certificate::DupOSCertHandle(sk_X509_value(certs, i)); + handles->push_back(x509_cert); + } + } +} + +void ParsePrincipalValues(X509_NAME* name, + int nid, + std::vector<std::string>* fields) { + for (int index = -1; + (index = X509_NAME_get_index_by_NID(name, nid, index)) != -1;) { + std::string field; + if (!x509_util::ParsePrincipalValueByIndex(name, index, &field)) + break; + fields->push_back(field); + } +} + +void ParsePrincipal(X509Certificate::OSCertHandle cert, + X509_NAME* x509_name, + CertPrincipal* principal) { + if (!x509_name) + return; + + ParsePrincipalValues(x509_name, NID_streetAddress, + &principal->street_addresses); + ParsePrincipalValues(x509_name, NID_organizationName, + &principal->organization_names); + ParsePrincipalValues(x509_name, NID_organizationalUnitName, + &principal->organization_unit_names); + ParsePrincipalValues(x509_name, NID_domainComponent, + &principal->domain_components); + + x509_util::ParsePrincipalValueByNID(x509_name, NID_commonName, + &principal->common_name); + x509_util::ParsePrincipalValueByNID(x509_name, NID_localityName, + &principal->locality_name); + x509_util::ParsePrincipalValueByNID(x509_name, NID_stateOrProvinceName, + &principal->state_or_province_name); + x509_util::ParsePrincipalValueByNID(x509_name, NID_countryName, + &principal->country_name); +} + +void ParseSubjectAltName(X509Certificate::OSCertHandle cert, + std::vector<std::string>* dns_names, + std::vector<std::string>* ip_addresses) { + DCHECK(dns_names || ip_addresses); + int index = X509_get_ext_by_NID(cert, NID_subject_alt_name, -1); + X509_EXTENSION* alt_name_ext = X509_get_ext(cert, index); + if (!alt_name_ext) + return; + + crypto::ScopedOpenSSL<GENERAL_NAMES, GENERAL_NAMES_free> alt_names( + reinterpret_cast<GENERAL_NAMES*>(X509V3_EXT_d2i(alt_name_ext))); + if (!alt_names.get()) + return; + + for (int i = 0; i < sk_GENERAL_NAME_num(alt_names.get()); ++i) { + const GENERAL_NAME* name = sk_GENERAL_NAME_value(alt_names.get(), i); + if (name->type == GEN_DNS && dns_names) { + const unsigned char* dns_name = ASN1_STRING_data(name->d.dNSName); + if (!dns_name) + continue; + int dns_name_len = ASN1_STRING_length(name->d.dNSName); + dns_names->push_back( + std::string(reinterpret_cast<const char*>(dns_name), dns_name_len)); + } else if (name->type == GEN_IPADD && ip_addresses) { + const unsigned char* ip_addr = name->d.iPAddress->data; + if (!ip_addr) + continue; + int ip_addr_len = name->d.iPAddress->length; + if (ip_addr_len != static_cast<int>(kIPv4AddressSize) && + ip_addr_len != static_cast<int>(kIPv6AddressSize)) { + // http://www.ietf.org/rfc/rfc3280.txt requires subjectAltName iPAddress + // to have 4 or 16 bytes, whereas in a name constraint it includes a + // net mask hence 8 or 32 bytes. Logging to help diagnose any mixup. + LOG(WARNING) << "Bad sized IP Address in cert: " << ip_addr_len; + continue; + } + ip_addresses->push_back( + std::string(reinterpret_cast<const char*>(ip_addr), ip_addr_len)); + } + } +} + +struct DERCache { + unsigned char* data; + int data_length; +}; + +void DERCache_free(void* parent, void* ptr, CRYPTO_EX_DATA* ad, int idx, + long argl, void* argp) { + DERCache* der_cache = static_cast<DERCache*>(ptr); + if (!der_cache) + return; + if (der_cache->data) + OPENSSL_free(der_cache->data); + OPENSSL_free(der_cache); +} + +class X509InitSingleton { + public: + static X509InitSingleton* GetInstance() { + // We allow the X509 store to leak, because it is used from a non-joinable + // worker that is not stopped on shutdown, hence may still be using + // OpenSSL library after the AtExit runner has completed. + return Singleton<X509InitSingleton, + LeakySingletonTraits<X509InitSingleton> >::get(); + } + int der_cache_ex_index() const { return der_cache_ex_index_; } + X509_STORE* store() const { return store_.get(); } + + void ResetCertStore() { + store_.reset(X509_STORE_new()); + DCHECK(store_.get()); + X509_STORE_set_default_paths(store_.get()); + // TODO(joth): Enable CRL (see X509_STORE_set_flags(X509_V_FLAG_CRL_CHECK)). + } + + private: + friend struct DefaultSingletonTraits<X509InitSingleton>; + X509InitSingleton() { + crypto::EnsureOpenSSLInit(); + der_cache_ex_index_ = X509_get_ex_new_index(0, 0, 0, 0, DERCache_free); + DCHECK_NE(der_cache_ex_index_, -1); + ResetCertStore(); + } + + int der_cache_ex_index_; + crypto::ScopedOpenSSL<X509_STORE, X509_STORE_free> store_; + + DISALLOW_COPY_AND_ASSIGN(X509InitSingleton); +}; + +// Takes ownership of |data| (which must have been allocated by OpenSSL). +DERCache* SetDERCache(X509Certificate::OSCertHandle cert, + int x509_der_cache_index, + unsigned char* data, + int data_length) { + DERCache* internal_cache = static_cast<DERCache*>( + OPENSSL_malloc(sizeof(*internal_cache))); + if (!internal_cache) { + // We took ownership of |data|, so we must free if we can't add it to + // |cert|. + OPENSSL_free(data); + return NULL; + } + + internal_cache->data = data; + internal_cache->data_length = data_length; + X509_set_ex_data(cert, x509_der_cache_index, internal_cache); + return internal_cache; +} + +// Returns true if |der_cache| points to valid data, false otherwise. +// (note: the DER-encoded data in |der_cache| is owned by |cert|, callers should +// not free it). +bool GetDERAndCacheIfNeeded(X509Certificate::OSCertHandle cert, + DERCache* der_cache) { + int x509_der_cache_index = + X509InitSingleton::GetInstance()->der_cache_ex_index(); + + // Re-encoding the DER data via i2d_X509 is an expensive operation, but it's + // necessary for comparing two certificates. We re-encode at most once per + // certificate and cache the data within the X509 cert using X509_set_ex_data. + DERCache* internal_cache = static_cast<DERCache*>( + X509_get_ex_data(cert, x509_der_cache_index)); + if (!internal_cache) { + unsigned char* data = NULL; + int data_length = i2d_X509(cert, &data); + if (data_length <= 0 || !data) + return false; + internal_cache = SetDERCache(cert, x509_der_cache_index, data, data_length); + if (!internal_cache) + return false; + } + *der_cache = *internal_cache; + return true; +} + +// Used to free a list of X509_NAMEs and the objects it points to. +void sk_X509_NAME_free_all(STACK_OF(X509_NAME)* sk) { + sk_X509_NAME_pop_free(sk, X509_NAME_free); +} + +} // namespace + +// static +X509Certificate::OSCertHandle X509Certificate::DupOSCertHandle( + OSCertHandle cert_handle) { + DCHECK(cert_handle); + // Using X509_dup causes the entire certificate to be reparsed. This + // conversion, besides being non-trivial, drops any associated + // application-specific data set by X509_set_ex_data. Using CRYPTO_add + // just bumps up the ref-count for the cert, without causing any allocations + // or deallocations. + CRYPTO_add(&cert_handle->references, 1, CRYPTO_LOCK_X509); + return cert_handle; +} + +// static +void X509Certificate::FreeOSCertHandle(OSCertHandle cert_handle) { + // Decrement the ref-count for the cert and, if all references are gone, + // free the memory and any application-specific data associated with the + // certificate. + X509_free(cert_handle); +} + +void X509Certificate::Initialize() { + crypto::EnsureOpenSSLInit(); + fingerprint_ = CalculateFingerprint(cert_handle_); + ca_fingerprint_ = CalculateCAFingerprint(intermediate_ca_certs_); + + ASN1_INTEGER* serial_num = X509_get_serialNumber(cert_handle_); + if (serial_num) { + // ASN1_INTEGERS represent the decoded number, in a format internal to + // OpenSSL. Most notably, this may have leading zeroes stripped off for + // numbers whose first byte is >= 0x80. Thus, it is necessary to + // re-encoded the integer back into DER, which is what the interface + // of X509Certificate exposes, to ensure callers get the proper (DER) + // value. + int bytes_required = i2c_ASN1_INTEGER(serial_num, NULL); + unsigned char* buffer = reinterpret_cast<unsigned char*>( + WriteInto(&serial_number_, bytes_required + 1)); + int bytes_written = i2c_ASN1_INTEGER(serial_num, &buffer); + DCHECK_EQ(static_cast<size_t>(bytes_written), serial_number_.size()); + } + + ParsePrincipal(cert_handle_, X509_get_subject_name(cert_handle_), &subject_); + ParsePrincipal(cert_handle_, X509_get_issuer_name(cert_handle_), &issuer_); + x509_util::ParseDate(X509_get_notBefore(cert_handle_), &valid_start_); + x509_util::ParseDate(X509_get_notAfter(cert_handle_), &valid_expiry_); +} + +// static +void X509Certificate::ResetCertStore() { + X509InitSingleton::GetInstance()->ResetCertStore(); +} + +// static +SHA1HashValue X509Certificate::CalculateFingerprint(OSCertHandle cert) { + SHA1HashValue sha1; + unsigned int sha1_size = static_cast<unsigned int>(sizeof(sha1.data)); + int ret = X509_digest(cert, EVP_sha1(), sha1.data, &sha1_size); + CHECK(ret); + CHECK_EQ(sha1_size, sizeof(sha1.data)); + return sha1; +} + +// static +SHA1HashValue X509Certificate::CalculateCAFingerprint( + const OSCertHandles& intermediates) { + SHA1HashValue sha1; + memset(sha1.data, 0, sizeof(sha1.data)); + + SHA_CTX sha1_ctx; + SHA1_Init(&sha1_ctx); + DERCache der_cache; + for (size_t i = 0; i < intermediates.size(); ++i) { + if (!GetDERAndCacheIfNeeded(intermediates[i], &der_cache)) + return sha1; + SHA1_Update(&sha1_ctx, der_cache.data, der_cache.data_length); + } + SHA1_Final(sha1.data, &sha1_ctx); + + return sha1; +} + +// static +X509Certificate::OSCertHandle X509Certificate::CreateOSCertHandleFromBytes( + const char* data, int length) { + if (length < 0) + return NULL; + crypto::EnsureOpenSSLInit(); + const unsigned char* d2i_data = + reinterpret_cast<const unsigned char*>(data); + // Don't cache this data via SetDERCache as this wire format may be not be + // identical from the i2d_X509 roundtrip. + X509* cert = d2i_X509(NULL, &d2i_data, length); + return cert; +} + +// static +X509Certificate::OSCertHandles X509Certificate::CreateOSCertHandlesFromBytes( + const char* data, int length, Format format) { + OSCertHandles results; + if (length < 0) + return results; + + switch (format) { + case FORMAT_SINGLE_CERTIFICATE: { + OSCertHandle handle = CreateOSCertHandleFromBytes(data, length); + if (handle) + results.push_back(handle); + break; + } + case FORMAT_PKCS7: { + CreateOSCertHandlesFromPKCS7Bytes(data, length, &results); + break; + } + default: { + NOTREACHED() << "Certificate format " << format << " unimplemented"; + break; + } + } + + return results; +} + +// static +X509Certificate* X509Certificate::CreateSelfSigned( + crypto::RSAPrivateKey* key, + const std::string& subject, + uint32 serial_number, + base::TimeDelta valid_duration) { + // TODO(port): Implement. See http://crbug.com/91512. + NOTIMPLEMENTED(); + return NULL; +} + +void X509Certificate::GetSubjectAltName( + std::vector<std::string>* dns_names, + std::vector<std::string>* ip_addrs) const { + if (dns_names) + dns_names->clear(); + if (ip_addrs) + ip_addrs->clear(); + + ParseSubjectAltName(cert_handle_, dns_names, ip_addrs); +} + +// static +X509_STORE* X509Certificate::cert_store() { + return X509InitSingleton::GetInstance()->store(); +} + +// static +bool X509Certificate::GetDEREncoded(X509Certificate::OSCertHandle cert_handle, + std::string* encoded) { + DERCache der_cache; + if (!GetDERAndCacheIfNeeded(cert_handle, &der_cache)) + return false; + encoded->assign(reinterpret_cast<const char*>(der_cache.data), + der_cache.data_length); + return true; +} + +// static +bool X509Certificate::IsSameOSCert(X509Certificate::OSCertHandle a, + X509Certificate::OSCertHandle b) { + DCHECK(a && b); + if (a == b) + return true; + + // X509_cmp only checks the fingerprint, but we want to compare the whole + // DER data. Encoding it from OSCertHandle is an expensive operation, so we + // cache the DER (if not already cached via X509_set_ex_data). + DERCache der_cache_a, der_cache_b; + + return GetDERAndCacheIfNeeded(a, &der_cache_a) && + GetDERAndCacheIfNeeded(b, &der_cache_b) && + der_cache_a.data_length == der_cache_b.data_length && + memcmp(der_cache_a.data, der_cache_b.data, der_cache_a.data_length) == 0; +} + +// static +X509Certificate::OSCertHandle +X509Certificate::ReadOSCertHandleFromPickle(PickleIterator* pickle_iter) { + const char* data; + int length; + if (!pickle_iter->ReadData(&data, &length)) + return NULL; + + return CreateOSCertHandleFromBytes(data, length); +} + +// static +bool X509Certificate::WriteOSCertHandleToPickle(OSCertHandle cert_handle, + Pickle* pickle) { + DERCache der_cache; + if (!GetDERAndCacheIfNeeded(cert_handle, &der_cache)) + return false; + + return pickle->WriteData( + reinterpret_cast<const char*>(der_cache.data), + der_cache.data_length); +} + +// static +void X509Certificate::GetPublicKeyInfo(OSCertHandle cert_handle, + size_t* size_bits, + PublicKeyType* type) { + *type = kPublicKeyTypeUnknown; + *size_bits = 0; + + crypto::ScopedOpenSSL<EVP_PKEY, EVP_PKEY_free> scoped_key( + X509_get_pubkey(cert_handle)); + if (!scoped_key.get()) + return; + + CHECK(scoped_key.get()); + EVP_PKEY* key = scoped_key.get(); + + switch (key->type) { + case EVP_PKEY_RSA: + *type = kPublicKeyTypeRSA; + *size_bits = EVP_PKEY_size(key) * 8; + break; + case EVP_PKEY_DSA: + *type = kPublicKeyTypeDSA; + *size_bits = EVP_PKEY_size(key) * 8; + break; + case EVP_PKEY_EC: + *type = kPublicKeyTypeECDSA; + *size_bits = EVP_PKEY_size(key); + break; + case EVP_PKEY_DH: + *type = kPublicKeyTypeDH; + *size_bits = EVP_PKEY_size(key) * 8; + break; + } +} + +bool X509Certificate::IsIssuedByEncoded( + const std::vector<std::string>& valid_issuers) { + if (valid_issuers.empty()) + return false; + + // Convert to a temporary list of X509_NAME objects. + // It will own the objects it points to. + crypto::ScopedOpenSSL<STACK_OF(X509_NAME), sk_X509_NAME_free_all> + issuer_names(sk_X509_NAME_new_null()); + if (!issuer_names.get()) + return false; + + for (std::vector<std::string>::const_iterator it = valid_issuers.begin(); + it != valid_issuers.end(); ++it) { + const unsigned char* p = + reinterpret_cast<const unsigned char*>(it->data()); + long len = static_cast<long>(it->length()); + X509_NAME* ca_name = d2i_X509_NAME(NULL, &p, len); + if (ca_name == NULL) + return false; + sk_X509_NAME_push(issuer_names.get(), ca_name); + } + + // Create a temporary list of X509_NAME objects corresponding + // to the certificate chain. It doesn't own the object it points to. + std::vector<X509_NAME*> cert_names; + X509_NAME* issuer = X509_get_issuer_name(cert_handle_); + if (issuer == NULL) + return false; + + cert_names.push_back(issuer); + for (OSCertHandles::iterator it = intermediate_ca_certs_.begin(); + it != intermediate_ca_certs_.end(); ++it) { + issuer = X509_get_issuer_name(*it); + if (issuer == NULL) + return false; + cert_names.push_back(issuer); + } + + // and 'cert_names'. + for (size_t n = 0; n < cert_names.size(); ++n) { + for (int m = 0; m < sk_X509_NAME_num(issuer_names.get()); ++m) { + X509_NAME* issuer = sk_X509_NAME_value(issuer_names.get(), m); + if (X509_NAME_cmp(issuer, cert_names[n]) == 0) { + return true; + } + } + } + + return false; +} + +} // namespace net diff --git a/net/cert/x509_certificate_unittest.cc b/net/cert/x509_certificate_unittest.cc new file mode 100644 index 0000000..6e10439 --- /dev/null +++ b/net/cert/x509_certificate_unittest.cc @@ -0,0 +1,1170 @@ +// Copyright (c) 2012 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 "net/cert/x509_certificate.h" + +#include "base/basictypes.h" +#include "base/files/file_path.h" +#include "base/memory/scoped_ptr.h" +#include "base/pickle.h" +#include "base/sha1.h" +#include "base/string_number_conversions.h" +#include "base/strings/string_split.h" +#include "crypto/rsa_private_key.h" +#include "net/base/net_errors.h" +#include "net/base/test_data_directory.h" +#include "net/cert/asn1_util.h" +#include "net/test/cert_test_util.h" +#include "net/test/test_certificate_data.h" +#include "testing/gtest/include/gtest/gtest.h" + +#if defined(USE_NSS) +#include <cert.h> +#endif + +using base::HexEncode; +using base::Time; + +namespace net { + +// Certificates for test data. They're obtained with: +// +// $ openssl s_client -connect [host]:443 -showcerts > /tmp/host.pem < /dev/null +// $ openssl x509 -inform PEM -outform DER < /tmp/host.pem > /tmp/host.der +// +// For fingerprint +// $ openssl x509 -inform DER -fingerprint -noout < /tmp/host.der + +// For valid_start, valid_expiry +// $ openssl x509 -inform DER -text -noout < /tmp/host.der | +// grep -A 2 Validity +// $ date +%s -d '<date str>' + +// Google's cert. +uint8 google_fingerprint[] = { + 0xab, 0xbe, 0x5e, 0xb4, 0x93, 0x88, 0x4e, 0xe4, 0x60, 0xc6, 0xef, 0xf8, + 0xea, 0xd4, 0xb1, 0x55, 0x4b, 0xc9, 0x59, 0x3c +}; + +// webkit.org's cert. +uint8 webkit_fingerprint[] = { + 0xa1, 0x4a, 0x94, 0x46, 0x22, 0x8e, 0x70, 0x66, 0x2b, 0x94, 0xf9, 0xf8, + 0x57, 0x83, 0x2d, 0xa2, 0xff, 0xbc, 0x84, 0xc2 +}; + +// thawte.com's cert (it's EV-licious!). +uint8 thawte_fingerprint[] = { + 0x85, 0x04, 0x2d, 0xfd, 0x2b, 0x0e, 0xc6, 0xc8, 0xaf, 0x2d, 0x77, 0xd6, + 0xa1, 0x3a, 0x64, 0x04, 0x27, 0x90, 0x97, 0x37 +}; + +// A certificate for https://www.unosoft.hu/, whose AIA extension contains +// an LDAP URL without a host name. +uint8 unosoft_hu_fingerprint[] = { + 0x32, 0xff, 0xe3, 0xbe, 0x2c, 0x3b, 0xc7, 0xca, 0xbf, 0x2d, 0x64, 0xbd, + 0x25, 0x66, 0xf2, 0xec, 0x8b, 0x0f, 0xbf, 0xd8 +}; + +// The fingerprint of the Google certificate used in the parsing tests, +// which is newer than the one included in the x509_certificate_data.h +uint8 google_parse_fingerprint[] = { + 0x40, 0x50, 0x62, 0xe5, 0xbe, 0xfd, 0xe4, 0xaf, 0x97, 0xe9, 0x38, 0x2a, + 0xf1, 0x6c, 0xc8, 0x7c, 0x8f, 0xb7, 0xc4, 0xe2 +}; + +// The fingerprint for the Thawte SGC certificate +uint8 thawte_parse_fingerprint[] = { + 0xec, 0x07, 0x10, 0x03, 0xd8, 0xf5, 0xa3, 0x7f, 0x42, 0xc4, 0x55, 0x7f, + 0x65, 0x6a, 0xae, 0x86, 0x65, 0xfa, 0x4b, 0x02 +}; + +// Dec 18 00:00:00 2009 GMT +const double kGoogleParseValidFrom = 1261094400; +// Dec 18 23:59:59 2011 GMT +const double kGoogleParseValidTo = 1324252799; + +struct CertificateFormatTestData { + const char* file_name; + X509Certificate::Format format; + uint8* chain_fingerprints[3]; +}; + +const CertificateFormatTestData FormatTestData[] = { + // DER Parsing - single certificate, DER encoded + { "google.single.der", X509Certificate::FORMAT_SINGLE_CERTIFICATE, + { google_parse_fingerprint, + NULL, } }, + // DER parsing - single certificate, PEM encoded + { "google.single.pem", X509Certificate::FORMAT_SINGLE_CERTIFICATE, + { google_parse_fingerprint, + NULL, } }, + // PEM parsing - single certificate, PEM encoded with a PEB of + // "CERTIFICATE" + { "google.single.pem", X509Certificate::FORMAT_PEM_CERT_SEQUENCE, + { google_parse_fingerprint, + NULL, } }, + // PEM parsing - sequence of certificates, PEM encoded with a PEB of + // "CERTIFICATE" + { "google.chain.pem", X509Certificate::FORMAT_PEM_CERT_SEQUENCE, + { google_parse_fingerprint, + thawte_parse_fingerprint, + NULL, } }, + // PKCS#7 parsing - "degenerate" SignedData collection of certificates, DER + // encoding + { "google.binary.p7b", X509Certificate::FORMAT_PKCS7, + { google_parse_fingerprint, + thawte_parse_fingerprint, + NULL, } }, + // PKCS#7 parsing - "degenerate" SignedData collection of certificates, PEM + // encoded with a PEM PEB of "CERTIFICATE" + { "google.pem_cert.p7b", X509Certificate::FORMAT_PKCS7, + { google_parse_fingerprint, + thawte_parse_fingerprint, + NULL, } }, + // PKCS#7 parsing - "degenerate" SignedData collection of certificates, PEM + // encoded with a PEM PEB of "PKCS7" + { "google.pem_pkcs7.p7b", X509Certificate::FORMAT_PKCS7, + { google_parse_fingerprint, + thawte_parse_fingerprint, + NULL, } }, + // All of the above, this time using auto-detection + { "google.single.der", X509Certificate::FORMAT_AUTO, + { google_parse_fingerprint, + NULL, } }, + { "google.single.pem", X509Certificate::FORMAT_AUTO, + { google_parse_fingerprint, + NULL, } }, + { "google.chain.pem", X509Certificate::FORMAT_AUTO, + { google_parse_fingerprint, + thawte_parse_fingerprint, + NULL, } }, + { "google.binary.p7b", X509Certificate::FORMAT_AUTO, + { google_parse_fingerprint, + thawte_parse_fingerprint, + NULL, } }, + { "google.pem_cert.p7b", X509Certificate::FORMAT_AUTO, + { google_parse_fingerprint, + thawte_parse_fingerprint, + NULL, } }, + { "google.pem_pkcs7.p7b", X509Certificate::FORMAT_AUTO, + { google_parse_fingerprint, + thawte_parse_fingerprint, + NULL, } }, +}; + +void CheckGoogleCert(const scoped_refptr<X509Certificate>& google_cert, + uint8* expected_fingerprint, + double valid_from, double valid_to) { + ASSERT_NE(static_cast<X509Certificate*>(NULL), google_cert); + + const CertPrincipal& subject = google_cert->subject(); + EXPECT_EQ("www.google.com", subject.common_name); + EXPECT_EQ("Mountain View", subject.locality_name); + EXPECT_EQ("California", subject.state_or_province_name); + EXPECT_EQ("US", subject.country_name); + EXPECT_EQ(0U, subject.street_addresses.size()); + ASSERT_EQ(1U, subject.organization_names.size()); + EXPECT_EQ("Google Inc", subject.organization_names[0]); + EXPECT_EQ(0U, subject.organization_unit_names.size()); + EXPECT_EQ(0U, subject.domain_components.size()); + + const CertPrincipal& issuer = google_cert->issuer(); + EXPECT_EQ("Thawte SGC CA", issuer.common_name); + EXPECT_EQ("", issuer.locality_name); + EXPECT_EQ("", issuer.state_or_province_name); + EXPECT_EQ("ZA", issuer.country_name); + EXPECT_EQ(0U, issuer.street_addresses.size()); + ASSERT_EQ(1U, issuer.organization_names.size()); + EXPECT_EQ("Thawte Consulting (Pty) Ltd.", issuer.organization_names[0]); + EXPECT_EQ(0U, issuer.organization_unit_names.size()); + EXPECT_EQ(0U, issuer.domain_components.size()); + + // Use DoubleT because its epoch is the same on all platforms + const Time& valid_start = google_cert->valid_start(); + EXPECT_EQ(valid_from, valid_start.ToDoubleT()); + + const Time& valid_expiry = google_cert->valid_expiry(); + EXPECT_EQ(valid_to, valid_expiry.ToDoubleT()); + + const SHA1HashValue& fingerprint = google_cert->fingerprint(); + for (size_t i = 0; i < 20; ++i) + EXPECT_EQ(expected_fingerprint[i], fingerprint.data[i]); + + std::vector<std::string> dns_names; + google_cert->GetDNSNames(&dns_names); + ASSERT_EQ(1U, dns_names.size()); + EXPECT_EQ("www.google.com", dns_names[0]); +} + +TEST(X509CertificateTest, GoogleCertParsing) { + scoped_refptr<X509Certificate> google_cert( + X509Certificate::CreateFromBytes( + reinterpret_cast<const char*>(google_der), sizeof(google_der))); + + CheckGoogleCert(google_cert, google_fingerprint, + 1238192407, // Mar 27 22:20:07 2009 GMT + 1269728407); // Mar 27 22:20:07 2010 GMT +} + +TEST(X509CertificateTest, WebkitCertParsing) { + scoped_refptr<X509Certificate> webkit_cert(X509Certificate::CreateFromBytes( + reinterpret_cast<const char*>(webkit_der), sizeof(webkit_der))); + + ASSERT_NE(static_cast<X509Certificate*>(NULL), webkit_cert); + + const CertPrincipal& subject = webkit_cert->subject(); + EXPECT_EQ("Cupertino", subject.locality_name); + EXPECT_EQ("California", subject.state_or_province_name); + EXPECT_EQ("US", subject.country_name); + EXPECT_EQ(0U, subject.street_addresses.size()); + ASSERT_EQ(1U, subject.organization_names.size()); + EXPECT_EQ("Apple Inc.", subject.organization_names[0]); + ASSERT_EQ(1U, subject.organization_unit_names.size()); + EXPECT_EQ("Mac OS Forge", subject.organization_unit_names[0]); + EXPECT_EQ(0U, subject.domain_components.size()); + + const CertPrincipal& issuer = webkit_cert->issuer(); + EXPECT_EQ("Go Daddy Secure Certification Authority", issuer.common_name); + EXPECT_EQ("Scottsdale", issuer.locality_name); + EXPECT_EQ("Arizona", issuer.state_or_province_name); + EXPECT_EQ("US", issuer.country_name); + EXPECT_EQ(0U, issuer.street_addresses.size()); + ASSERT_EQ(1U, issuer.organization_names.size()); + EXPECT_EQ("GoDaddy.com, Inc.", issuer.organization_names[0]); + ASSERT_EQ(1U, issuer.organization_unit_names.size()); + EXPECT_EQ("http://certificates.godaddy.com/repository", + issuer.organization_unit_names[0]); + EXPECT_EQ(0U, issuer.domain_components.size()); + + // Use DoubleT because its epoch is the same on all platforms + const Time& valid_start = webkit_cert->valid_start(); + EXPECT_EQ(1205883319, valid_start.ToDoubleT()); // Mar 18 23:35:19 2008 GMT + + const Time& valid_expiry = webkit_cert->valid_expiry(); + EXPECT_EQ(1300491319, valid_expiry.ToDoubleT()); // Mar 18 23:35:19 2011 GMT + + const SHA1HashValue& fingerprint = webkit_cert->fingerprint(); + for (size_t i = 0; i < 20; ++i) + EXPECT_EQ(webkit_fingerprint[i], fingerprint.data[i]); + + std::vector<std::string> dns_names; + webkit_cert->GetDNSNames(&dns_names); + ASSERT_EQ(2U, dns_names.size()); + EXPECT_EQ("*.webkit.org", dns_names[0]); + EXPECT_EQ("webkit.org", dns_names[1]); + + // Test that the wildcard cert matches properly. + EXPECT_TRUE(webkit_cert->VerifyNameMatch("www.webkit.org")); + EXPECT_TRUE(webkit_cert->VerifyNameMatch("foo.webkit.org")); + EXPECT_TRUE(webkit_cert->VerifyNameMatch("webkit.org")); + EXPECT_FALSE(webkit_cert->VerifyNameMatch("www.webkit.com")); + EXPECT_FALSE(webkit_cert->VerifyNameMatch("www.foo.webkit.com")); +} + +TEST(X509CertificateTest, ThawteCertParsing) { + scoped_refptr<X509Certificate> thawte_cert(X509Certificate::CreateFromBytes( + reinterpret_cast<const char*>(thawte_der), sizeof(thawte_der))); + + ASSERT_NE(static_cast<X509Certificate*>(NULL), thawte_cert); + + const CertPrincipal& subject = thawte_cert->subject(); + EXPECT_EQ("www.thawte.com", subject.common_name); + EXPECT_EQ("Mountain View", subject.locality_name); + EXPECT_EQ("California", subject.state_or_province_name); + EXPECT_EQ("US", subject.country_name); + EXPECT_EQ(0U, subject.street_addresses.size()); + ASSERT_EQ(1U, subject.organization_names.size()); + EXPECT_EQ("Thawte Inc", subject.organization_names[0]); + EXPECT_EQ(0U, subject.organization_unit_names.size()); + EXPECT_EQ(0U, subject.domain_components.size()); + + const CertPrincipal& issuer = thawte_cert->issuer(); + EXPECT_EQ("thawte Extended Validation SSL CA", issuer.common_name); + EXPECT_EQ("", issuer.locality_name); + EXPECT_EQ("", issuer.state_or_province_name); + EXPECT_EQ("US", issuer.country_name); + EXPECT_EQ(0U, issuer.street_addresses.size()); + ASSERT_EQ(1U, issuer.organization_names.size()); + EXPECT_EQ("thawte, Inc.", issuer.organization_names[0]); + ASSERT_EQ(1U, issuer.organization_unit_names.size()); + EXPECT_EQ("Terms of use at https://www.thawte.com/cps (c)06", + issuer.organization_unit_names[0]); + EXPECT_EQ(0U, issuer.domain_components.size()); + + // Use DoubleT because its epoch is the same on all platforms + const Time& valid_start = thawte_cert->valid_start(); + EXPECT_EQ(1227052800, valid_start.ToDoubleT()); // Nov 19 00:00:00 2008 GMT + + const Time& valid_expiry = thawte_cert->valid_expiry(); + EXPECT_EQ(1263772799, valid_expiry.ToDoubleT()); // Jan 17 23:59:59 2010 GMT + + const SHA1HashValue& fingerprint = thawte_cert->fingerprint(); + for (size_t i = 0; i < 20; ++i) + EXPECT_EQ(thawte_fingerprint[i], fingerprint.data[i]); + + std::vector<std::string> dns_names; + thawte_cert->GetDNSNames(&dns_names); + ASSERT_EQ(1U, dns_names.size()); + EXPECT_EQ("www.thawte.com", dns_names[0]); +} + +// Test that all desired AttributeAndValue pairs can be extracted when only +// a single RelativeDistinguishedName is present. "Normally" there is only +// one AVA per RDN, but some CAs place all AVAs within a single RDN. +// This is a regression test for http://crbug.com/101009 +TEST(X509CertificateTest, MultivalueRDN) { + base::FilePath certs_dir = GetTestCertsDirectory(); + + scoped_refptr<X509Certificate> multivalue_rdn_cert = + ImportCertFromFile(certs_dir, "multivalue_rdn.pem"); + ASSERT_NE(static_cast<X509Certificate*>(NULL), multivalue_rdn_cert); + + const CertPrincipal& subject = multivalue_rdn_cert->subject(); + EXPECT_EQ("Multivalue RDN Test", subject.common_name); + EXPECT_EQ("", subject.locality_name); + EXPECT_EQ("", subject.state_or_province_name); + EXPECT_EQ("US", subject.country_name); + EXPECT_EQ(0U, subject.street_addresses.size()); + ASSERT_EQ(1U, subject.organization_names.size()); + EXPECT_EQ("Chromium", subject.organization_names[0]); + ASSERT_EQ(1U, subject.organization_unit_names.size()); + EXPECT_EQ("Chromium net_unittests", subject.organization_unit_names[0]); + ASSERT_EQ(1U, subject.domain_components.size()); + EXPECT_EQ("Chromium", subject.domain_components[0]); +} + +// Test that characters which would normally be escaped in the string form, +// such as '=' or '"', are not escaped when parsed as individual components. +// This is a regression test for http://crbug.com/102839 +TEST(X509CertificateTest, UnescapedSpecialCharacters) { + base::FilePath certs_dir = GetTestCertsDirectory(); + + scoped_refptr<X509Certificate> unescaped_cert = + ImportCertFromFile(certs_dir, "unescaped.pem"); + ASSERT_NE(static_cast<X509Certificate*>(NULL), unescaped_cert); + + const CertPrincipal& subject = unescaped_cert->subject(); + EXPECT_EQ("127.0.0.1", subject.common_name); + EXPECT_EQ("Mountain View", subject.locality_name); + EXPECT_EQ("California", subject.state_or_province_name); + EXPECT_EQ("US", subject.country_name); + ASSERT_EQ(1U, subject.street_addresses.size()); + EXPECT_EQ("1600 Amphitheatre Parkway", subject.street_addresses[0]); + ASSERT_EQ(1U, subject.organization_names.size()); + EXPECT_EQ("Chromium = \"net_unittests\"", subject.organization_names[0]); + ASSERT_EQ(2U, subject.organization_unit_names.size()); + EXPECT_EQ("net_unittests", subject.organization_unit_names[0]); + EXPECT_EQ("Chromium", subject.organization_unit_names[1]); + EXPECT_EQ(0U, subject.domain_components.size()); +} + +TEST(X509CertificateTest, SerialNumbers) { + scoped_refptr<X509Certificate> google_cert( + X509Certificate::CreateFromBytes( + reinterpret_cast<const char*>(google_der), sizeof(google_der))); + + static const uint8 google_serial[16] = { + 0x01,0x2a,0x39,0x76,0x0d,0x3f,0x4f,0xc9, + 0x0b,0xe7,0xbd,0x2b,0xcf,0x95,0x2e,0x7a, + }; + + ASSERT_EQ(sizeof(google_serial), google_cert->serial_number().size()); + EXPECT_TRUE(memcmp(google_cert->serial_number().data(), google_serial, + sizeof(google_serial)) == 0); + + // We also want to check a serial number where the first byte is >= 0x80 in + // case the underlying library tries to pad it. + scoped_refptr<X509Certificate> paypal_null_cert( + X509Certificate::CreateFromBytes( + reinterpret_cast<const char*>(paypal_null_der), + sizeof(paypal_null_der))); + + static const uint8 paypal_null_serial[3] = {0x00, 0xf0, 0x9b}; + ASSERT_EQ(sizeof(paypal_null_serial), + paypal_null_cert->serial_number().size()); + EXPECT_TRUE(memcmp(paypal_null_cert->serial_number().data(), + paypal_null_serial, sizeof(paypal_null_serial)) == 0); +} + +TEST(X509CertificateTest, CAFingerprints) { + base::FilePath certs_dir = GetTestCertsDirectory(); + + scoped_refptr<X509Certificate> server_cert = + ImportCertFromFile(certs_dir, "salesforce_com_test.pem"); + ASSERT_NE(static_cast<X509Certificate*>(NULL), server_cert); + + scoped_refptr<X509Certificate> intermediate_cert1 = + ImportCertFromFile(certs_dir, "verisign_intermediate_ca_2011.pem"); + ASSERT_NE(static_cast<X509Certificate*>(NULL), intermediate_cert1); + + scoped_refptr<X509Certificate> intermediate_cert2 = + ImportCertFromFile(certs_dir, "verisign_intermediate_ca_2016.pem"); + ASSERT_NE(static_cast<X509Certificate*>(NULL), intermediate_cert2); + + X509Certificate::OSCertHandles intermediates; + intermediates.push_back(intermediate_cert1->os_cert_handle()); + scoped_refptr<X509Certificate> cert_chain1 = + X509Certificate::CreateFromHandle(server_cert->os_cert_handle(), + intermediates); + + intermediates.clear(); + intermediates.push_back(intermediate_cert2->os_cert_handle()); + scoped_refptr<X509Certificate> cert_chain2 = + X509Certificate::CreateFromHandle(server_cert->os_cert_handle(), + intermediates); + + // No intermediate CA certicates. + intermediates.clear(); + scoped_refptr<X509Certificate> cert_chain3 = + X509Certificate::CreateFromHandle(server_cert->os_cert_handle(), + intermediates); + + static const uint8 cert_chain1_ca_fingerprint[20] = { + 0xc2, 0xf0, 0x08, 0x7d, 0x01, 0xe6, 0x86, 0x05, 0x3a, 0x4d, + 0x63, 0x3e, 0x7e, 0x70, 0xd4, 0xef, 0x65, 0xc2, 0xcc, 0x4f + }; + static const uint8 cert_chain2_ca_fingerprint[20] = { + 0xd5, 0x59, 0xa5, 0x86, 0x66, 0x9b, 0x08, 0xf4, 0x6a, 0x30, + 0xa1, 0x33, 0xf8, 0xa9, 0xed, 0x3d, 0x03, 0x8e, 0x2e, 0xa8 + }; + // The SHA-1 hash of nothing. + static const uint8 cert_chain3_ca_fingerprint[20] = { + 0xda, 0x39, 0xa3, 0xee, 0x5e, 0x6b, 0x4b, 0x0d, 0x32, 0x55, + 0xbf, 0xef, 0x95, 0x60, 0x18, 0x90, 0xaf, 0xd8, 0x07, 0x09 + }; + EXPECT_TRUE(memcmp(cert_chain1->ca_fingerprint().data, + cert_chain1_ca_fingerprint, 20) == 0); + EXPECT_TRUE(memcmp(cert_chain2->ca_fingerprint().data, + cert_chain2_ca_fingerprint, 20) == 0); + EXPECT_TRUE(memcmp(cert_chain3->ca_fingerprint().data, + cert_chain3_ca_fingerprint, 20) == 0); +} + +TEST(X509CertificateTest, ParseSubjectAltNames) { + base::FilePath certs_dir = GetTestCertsDirectory(); + + scoped_refptr<X509Certificate> san_cert = + ImportCertFromFile(certs_dir, "subjectAltName_sanity_check.pem"); + ASSERT_NE(static_cast<X509Certificate*>(NULL), san_cert); + + std::vector<std::string> dns_names; + std::vector<std::string> ip_addresses; + san_cert->GetSubjectAltName(&dns_names, &ip_addresses); + + // Ensure that DNS names are correctly parsed. + ASSERT_EQ(1U, dns_names.size()); + EXPECT_EQ("test.example", dns_names[0]); + + // Ensure that both IPv4 and IPv6 addresses are correctly parsed. + ASSERT_EQ(2U, ip_addresses.size()); + + static const uint8 kIPv4Address[] = { + 0x7F, 0x00, 0x00, 0x02 + }; + ASSERT_EQ(arraysize(kIPv4Address), ip_addresses[0].size()); + EXPECT_EQ(0, memcmp(ip_addresses[0].data(), kIPv4Address, + arraysize(kIPv4Address))); + + static const uint8 kIPv6Address[] = { + 0xFE, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01 + }; + ASSERT_EQ(arraysize(kIPv6Address), ip_addresses[1].size()); + EXPECT_EQ(0, memcmp(ip_addresses[1].data(), kIPv6Address, + arraysize(kIPv6Address))); + + // Ensure the subjectAltName dirName has not influenced the handling of + // the subject commonName. + EXPECT_EQ("127.0.0.1", san_cert->subject().common_name); +} + +TEST(X509CertificateTest, ExtractSPKIFromDERCert) { + base::FilePath certs_dir = GetTestCertsDirectory(); + scoped_refptr<X509Certificate> cert = + ImportCertFromFile(certs_dir, "nist.der"); + ASSERT_NE(static_cast<X509Certificate*>(NULL), cert); + + std::string derBytes; + EXPECT_TRUE(X509Certificate::GetDEREncoded(cert->os_cert_handle(), + &derBytes)); + + base::StringPiece spkiBytes; + EXPECT_TRUE(asn1::ExtractSPKIFromDERCert(derBytes, &spkiBytes)); + + uint8 hash[base::kSHA1Length]; + base::SHA1HashBytes(reinterpret_cast<const uint8*>(spkiBytes.data()), + spkiBytes.size(), hash); + + EXPECT_EQ(0, memcmp(hash, kNistSPKIHash, sizeof(hash))); +} + +TEST(X509CertificateTest, ExtractCRLURLsFromDERCert) { + base::FilePath certs_dir = GetTestCertsDirectory(); + scoped_refptr<X509Certificate> cert = + ImportCertFromFile(certs_dir, "nist.der"); + ASSERT_NE(static_cast<X509Certificate*>(NULL), cert); + + std::string derBytes; + EXPECT_TRUE(X509Certificate::GetDEREncoded(cert->os_cert_handle(), + &derBytes)); + + std::vector<base::StringPiece> crl_urls; + EXPECT_TRUE(asn1::ExtractCRLURLsFromDERCert(derBytes, &crl_urls)); + + EXPECT_EQ(1u, crl_urls.size()); + if (crl_urls.size() > 0) { + EXPECT_EQ("http://SVRSecure-G3-crl.verisign.com/SVRSecureG3.crl", + crl_urls[0].as_string()); + } +} + +// Tests X509CertificateCache via X509Certificate::CreateFromHandle. We +// call X509Certificate::CreateFromHandle several times and observe whether +// it returns a cached or new OSCertHandle. +TEST(X509CertificateTest, Cache) { + X509Certificate::OSCertHandle google_cert_handle; + X509Certificate::OSCertHandle thawte_cert_handle; + + // Add a single certificate to the certificate cache. + google_cert_handle = X509Certificate::CreateOSCertHandleFromBytes( + reinterpret_cast<const char*>(google_der), sizeof(google_der)); + scoped_refptr<X509Certificate> cert1(X509Certificate::CreateFromHandle( + google_cert_handle, X509Certificate::OSCertHandles())); + X509Certificate::FreeOSCertHandle(google_cert_handle); + + // Add the same certificate, but as a new handle. + google_cert_handle = X509Certificate::CreateOSCertHandleFromBytes( + reinterpret_cast<const char*>(google_der), sizeof(google_der)); + scoped_refptr<X509Certificate> cert2(X509Certificate::CreateFromHandle( + google_cert_handle, X509Certificate::OSCertHandles())); + X509Certificate::FreeOSCertHandle(google_cert_handle); + + // A new X509Certificate should be returned. + EXPECT_NE(cert1.get(), cert2.get()); + // But both instances should share the underlying OS certificate handle. + EXPECT_EQ(cert1->os_cert_handle(), cert2->os_cert_handle()); + EXPECT_EQ(0u, cert1->GetIntermediateCertificates().size()); + EXPECT_EQ(0u, cert2->GetIntermediateCertificates().size()); + + // Add the same certificate, but this time with an intermediate. This + // should result in the intermediate being cached. Note that this is not + // a legitimate chain, but is suitable for testing. + google_cert_handle = X509Certificate::CreateOSCertHandleFromBytes( + reinterpret_cast<const char*>(google_der), sizeof(google_der)); + thawte_cert_handle = X509Certificate::CreateOSCertHandleFromBytes( + reinterpret_cast<const char*>(thawte_der), sizeof(thawte_der)); + X509Certificate::OSCertHandles intermediates; + intermediates.push_back(thawte_cert_handle); + scoped_refptr<X509Certificate> cert3(X509Certificate::CreateFromHandle( + google_cert_handle, intermediates)); + X509Certificate::FreeOSCertHandle(google_cert_handle); + X509Certificate::FreeOSCertHandle(thawte_cert_handle); + + // Test that the new certificate, even with intermediates, results in the + // same underlying handle being used. + EXPECT_EQ(cert1->os_cert_handle(), cert3->os_cert_handle()); + // Though they use the same OS handle, the intermediates should be different. + EXPECT_NE(cert1->GetIntermediateCertificates().size(), + cert3->GetIntermediateCertificates().size()); +} + +TEST(X509CertificateTest, Pickle) { + X509Certificate::OSCertHandle google_cert_handle = + X509Certificate::CreateOSCertHandleFromBytes( + reinterpret_cast<const char*>(google_der), sizeof(google_der)); + X509Certificate::OSCertHandle thawte_cert_handle = + X509Certificate::CreateOSCertHandleFromBytes( + reinterpret_cast<const char*>(thawte_der), sizeof(thawte_der)); + + X509Certificate::OSCertHandles intermediates; + intermediates.push_back(thawte_cert_handle); + scoped_refptr<X509Certificate> cert = X509Certificate::CreateFromHandle( + google_cert_handle, intermediates); + ASSERT_NE(static_cast<X509Certificate*>(NULL), cert.get()); + + X509Certificate::FreeOSCertHandle(google_cert_handle); + X509Certificate::FreeOSCertHandle(thawte_cert_handle); + + Pickle pickle; + cert->Persist(&pickle); + + PickleIterator iter(pickle); + scoped_refptr<X509Certificate> cert_from_pickle = + X509Certificate::CreateFromPickle( + pickle, &iter, X509Certificate::PICKLETYPE_CERTIFICATE_CHAIN_V3); + ASSERT_NE(static_cast<X509Certificate*>(NULL), cert_from_pickle); + EXPECT_TRUE(X509Certificate::IsSameOSCert( + cert->os_cert_handle(), cert_from_pickle->os_cert_handle())); + const X509Certificate::OSCertHandles& cert_intermediates = + cert->GetIntermediateCertificates(); + const X509Certificate::OSCertHandles& pickle_intermediates = + cert_from_pickle->GetIntermediateCertificates(); + ASSERT_EQ(cert_intermediates.size(), pickle_intermediates.size()); + for (size_t i = 0; i < cert_intermediates.size(); ++i) { + EXPECT_TRUE(X509Certificate::IsSameOSCert(cert_intermediates[i], + pickle_intermediates[i])); + } +} + +TEST(X509CertificateTest, Policy) { + scoped_refptr<X509Certificate> google_cert(X509Certificate::CreateFromBytes( + reinterpret_cast<const char*>(google_der), sizeof(google_der))); + + scoped_refptr<X509Certificate> webkit_cert(X509Certificate::CreateFromBytes( + reinterpret_cast<const char*>(webkit_der), sizeof(webkit_der))); + + CertPolicy policy; + + EXPECT_EQ(policy.Check(google_cert.get()), CertPolicy::UNKNOWN); + EXPECT_EQ(policy.Check(webkit_cert.get()), CertPolicy::UNKNOWN); + EXPECT_FALSE(policy.HasAllowedCert()); + EXPECT_FALSE(policy.HasDeniedCert()); + + policy.Allow(google_cert.get()); + + EXPECT_EQ(policy.Check(google_cert.get()), CertPolicy::ALLOWED); + EXPECT_EQ(policy.Check(webkit_cert.get()), CertPolicy::UNKNOWN); + EXPECT_TRUE(policy.HasAllowedCert()); + EXPECT_FALSE(policy.HasDeniedCert()); + + policy.Deny(google_cert.get()); + + EXPECT_EQ(policy.Check(google_cert.get()), CertPolicy::DENIED); + EXPECT_EQ(policy.Check(webkit_cert.get()), CertPolicy::UNKNOWN); + EXPECT_FALSE(policy.HasAllowedCert()); + EXPECT_TRUE(policy.HasDeniedCert()); + + policy.Allow(webkit_cert.get()); + + EXPECT_EQ(policy.Check(google_cert.get()), CertPolicy::DENIED); + EXPECT_EQ(policy.Check(webkit_cert.get()), CertPolicy::ALLOWED); + EXPECT_TRUE(policy.HasAllowedCert()); + EXPECT_TRUE(policy.HasDeniedCert()); +} + +TEST(X509CertificateTest, IntermediateCertificates) { + scoped_refptr<X509Certificate> webkit_cert( + X509Certificate::CreateFromBytes( + reinterpret_cast<const char*>(webkit_der), sizeof(webkit_der))); + + scoped_refptr<X509Certificate> thawte_cert( + X509Certificate::CreateFromBytes( + reinterpret_cast<const char*>(thawte_der), sizeof(thawte_der))); + + X509Certificate::OSCertHandle google_handle; + // Create object with no intermediates: + google_handle = X509Certificate::CreateOSCertHandleFromBytes( + reinterpret_cast<const char*>(google_der), sizeof(google_der)); + X509Certificate::OSCertHandles intermediates1; + scoped_refptr<X509Certificate> cert1; + cert1 = X509Certificate::CreateFromHandle(google_handle, intermediates1); + EXPECT_EQ(0u, cert1->GetIntermediateCertificates().size()); + + // Create object with 2 intermediates: + X509Certificate::OSCertHandles intermediates2; + intermediates2.push_back(webkit_cert->os_cert_handle()); + intermediates2.push_back(thawte_cert->os_cert_handle()); + scoped_refptr<X509Certificate> cert2; + cert2 = X509Certificate::CreateFromHandle(google_handle, intermediates2); + + // Verify it has all the intermediates: + const X509Certificate::OSCertHandles& cert2_intermediates = + cert2->GetIntermediateCertificates(); + ASSERT_EQ(2u, cert2_intermediates.size()); + EXPECT_TRUE(X509Certificate::IsSameOSCert(cert2_intermediates[0], + webkit_cert->os_cert_handle())); + EXPECT_TRUE(X509Certificate::IsSameOSCert(cert2_intermediates[1], + thawte_cert->os_cert_handle())); + + // Cleanup + X509Certificate::FreeOSCertHandle(google_handle); +} + +TEST(X509CertificateTest, IsIssuedByEncoded) { + base::FilePath certs_dir = GetTestCertsDirectory(); + + // Test a client certificate from MIT. + scoped_refptr<X509Certificate> mit_davidben_cert( + ImportCertFromFile(certs_dir, "mit.davidben.der")); + ASSERT_NE(static_cast<X509Certificate*>(NULL), mit_davidben_cert); + + std::string mit_issuer(reinterpret_cast<const char*>(MITDN), + sizeof(MITDN)); + + // Test a certificate from Google, issued by Thawte + scoped_refptr<X509Certificate> google_cert( + ImportCertFromFile(certs_dir, "google.single.der")); + ASSERT_NE(static_cast<X509Certificate*>(NULL), google_cert); + + std::string thawte_issuer(reinterpret_cast<const char*>(ThawteDN), + sizeof(ThawteDN)); + + // Check that the David Ben certificate is issued by MIT, but not + // by Thawte. + std::vector<std::string> issuers; + issuers.clear(); + issuers.push_back(mit_issuer); + EXPECT_TRUE(mit_davidben_cert->IsIssuedByEncoded(issuers)); + EXPECT_FALSE(google_cert->IsIssuedByEncoded(issuers)); + + // Check that the Google certificate is issued by Thawte and not + // by MIT. + issuers.clear(); + issuers.push_back(thawte_issuer); + EXPECT_FALSE(mit_davidben_cert->IsIssuedByEncoded(issuers)); + EXPECT_TRUE(google_cert->IsIssuedByEncoded(issuers)); + + // Check that they both pass when given a list of the two issuers. + issuers.clear(); + issuers.push_back(mit_issuer); + issuers.push_back(thawte_issuer); + EXPECT_TRUE(mit_davidben_cert->IsIssuedByEncoded(issuers)); + EXPECT_TRUE(google_cert->IsIssuedByEncoded(issuers)); +} + +TEST(X509CertificateTest, IsIssuedByEncodedWithIntermediates) { + base::FilePath certs_dir = GetTestCertsDirectory(); + + scoped_refptr<X509Certificate> server_cert = + ImportCertFromFile(certs_dir, "www_us_army_mil_cert.der"); + ASSERT_NE(static_cast<X509Certificate*>(NULL), server_cert); + + // The intermediate CA certificate's policyConstraints extension has a + // requireExplicitPolicy field with SkipCerts=0. + scoped_refptr<X509Certificate> intermediate_cert = + ImportCertFromFile(certs_dir, "dod_ca_17_cert.der"); + ASSERT_NE(static_cast<X509Certificate*>(NULL), intermediate_cert); + + std::string dod_ca_17_issuer(reinterpret_cast<const char*>(DodCA17DN), + sizeof(DodCA17DN)); + + scoped_refptr<X509Certificate> root_cert = + ImportCertFromFile(certs_dir, "dod_root_ca_2_cert.der"); + + std::string dod_root_ca_2_issuer( + reinterpret_cast<const char*>(DodRootCA2DN), sizeof(DodRootCA2DN)); + + X509Certificate::OSCertHandles intermediates; + intermediates.push_back(intermediate_cert->os_cert_handle()); + scoped_refptr<X509Certificate> cert_chain = + X509Certificate::CreateFromHandle(server_cert->os_cert_handle(), + intermediates); + + std::vector<std::string> issuers; + + // Check that the chain is issued by DOD CA-17. + issuers.clear(); + issuers.push_back(dod_ca_17_issuer); + EXPECT_TRUE(cert_chain->IsIssuedByEncoded(issuers)); + + // Check that the chain is also issued by DoD Root CA 2. + issuers.clear(); + issuers.push_back(dod_root_ca_2_issuer); + EXPECT_TRUE(cert_chain->IsIssuedByEncoded(issuers)); + + // Check that the chain is issued by either one of the two DOD issuers. + issuers.clear(); + issuers.push_back(dod_ca_17_issuer); + issuers.push_back(dod_root_ca_2_issuer); + EXPECT_TRUE(cert_chain->IsIssuedByEncoded(issuers)); + + // Check that an empty issuers list returns false. + issuers.clear(); + EXPECT_FALSE(cert_chain->IsIssuedByEncoded(issuers)); + + // Check that the chain is not issued by MIT + std::string mit_issuer(reinterpret_cast<const char*>(MITDN), + sizeof(MITDN)); + issuers.clear(); + issuers.push_back(mit_issuer); + EXPECT_FALSE(cert_chain->IsIssuedByEncoded(issuers)); +} + +#if !defined(OS_IOS) // TODO(ios): Unable to create certificates. +#if defined(USE_NSS) || defined(OS_WIN) || defined(OS_MACOSX) +// This test creates a self-signed cert from a private key and then verify the +// content of the certificate. +TEST(X509CertificateTest, CreateSelfSigned) { + scoped_ptr<crypto::RSAPrivateKey> private_key( + crypto::RSAPrivateKey::Create(1024)); + scoped_refptr<X509Certificate> cert = + X509Certificate::CreateSelfSigned( + private_key.get(), "CN=subject", 1, base::TimeDelta::FromDays(1)); + + EXPECT_EQ("subject", cert->subject().GetDisplayName()); + EXPECT_FALSE(cert->HasExpired()); + + const uint8 private_key_info[] = { + 0x30, 0x82, 0x02, 0x78, 0x02, 0x01, 0x00, 0x30, + 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, + 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x04, 0x82, + 0x02, 0x62, 0x30, 0x82, 0x02, 0x5e, 0x02, 0x01, + 0x00, 0x02, 0x81, 0x81, 0x00, 0xb8, 0x7f, 0x2b, + 0x20, 0xdc, 0x7c, 0x9b, 0x0c, 0xdc, 0x51, 0x61, + 0x99, 0x0d, 0x36, 0x0f, 0xd4, 0x66, 0x88, 0x08, + 0x55, 0x84, 0xd5, 0x3a, 0xbf, 0x2b, 0xa4, 0x64, + 0x85, 0x7b, 0x0c, 0x04, 0x13, 0x3f, 0x8d, 0xf4, + 0xbc, 0x38, 0x0d, 0x49, 0xfe, 0x6b, 0xc4, 0x5a, + 0xb0, 0x40, 0x53, 0x3a, 0xd7, 0x66, 0x09, 0x0f, + 0x9e, 0x36, 0x74, 0x30, 0xda, 0x8a, 0x31, 0x4f, + 0x1f, 0x14, 0x50, 0xd7, 0xc7, 0x20, 0x94, 0x17, + 0xde, 0x4e, 0xb9, 0x57, 0x5e, 0x7e, 0x0a, 0xe5, + 0xb2, 0x65, 0x7a, 0x89, 0x4e, 0xb6, 0x47, 0xff, + 0x1c, 0xbd, 0xb7, 0x38, 0x13, 0xaf, 0x47, 0x85, + 0x84, 0x32, 0x33, 0xf3, 0x17, 0x49, 0xbf, 0xe9, + 0x96, 0xd0, 0xd6, 0x14, 0x6f, 0x13, 0x8d, 0xc5, + 0xfc, 0x2c, 0x72, 0xba, 0xac, 0xea, 0x7e, 0x18, + 0x53, 0x56, 0xa6, 0x83, 0xa2, 0xce, 0x93, 0x93, + 0xe7, 0x1f, 0x0f, 0xe6, 0x0f, 0x02, 0x03, 0x01, + 0x00, 0x01, 0x02, 0x81, 0x80, 0x03, 0x61, 0x89, + 0x37, 0xcb, 0xf2, 0x98, 0xa0, 0xce, 0xb4, 0xcb, + 0x16, 0x13, 0xf0, 0xe6, 0xaf, 0x5c, 0xc5, 0xa7, + 0x69, 0x71, 0xca, 0xba, 0x8d, 0xe0, 0x4d, 0xdd, + 0xed, 0xb8, 0x48, 0x8b, 0x16, 0x93, 0x36, 0x95, + 0xc2, 0x91, 0x40, 0x65, 0x17, 0xbd, 0x7f, 0xd6, + 0xad, 0x9e, 0x30, 0x28, 0x46, 0xe4, 0x3e, 0xcc, + 0x43, 0x78, 0xf9, 0xfe, 0x1f, 0x33, 0x23, 0x1e, + 0x31, 0x12, 0x9d, 0x3c, 0xa7, 0x08, 0x82, 0x7b, + 0x7d, 0x25, 0x4e, 0x5e, 0x19, 0xa8, 0x9b, 0xed, + 0x86, 0xb2, 0xcb, 0x3c, 0xfe, 0x4e, 0xa1, 0xfa, + 0x62, 0x87, 0x3a, 0x17, 0xf7, 0x60, 0xec, 0x38, + 0x29, 0xe8, 0x4f, 0x34, 0x9f, 0x76, 0x9d, 0xee, + 0xa3, 0xf6, 0x85, 0x6b, 0x84, 0x43, 0xc9, 0x1e, + 0x01, 0xff, 0xfd, 0xd0, 0x29, 0x4c, 0xfa, 0x8e, + 0x57, 0x0c, 0xc0, 0x71, 0xa5, 0xbb, 0x88, 0x46, + 0x29, 0x5c, 0xc0, 0x4f, 0x01, 0x02, 0x41, 0x00, + 0xf5, 0x83, 0xa4, 0x64, 0x4a, 0xf2, 0xdd, 0x8c, + 0x2c, 0xed, 0xa8, 0xd5, 0x60, 0x5a, 0xe4, 0xc7, + 0xcc, 0x61, 0xcd, 0x38, 0x42, 0x20, 0xd3, 0x82, + 0x18, 0xf2, 0x35, 0x00, 0x72, 0x2d, 0xf7, 0x89, + 0x80, 0x67, 0xb5, 0x93, 0x05, 0x5f, 0xdd, 0x42, + 0xba, 0x16, 0x1a, 0xea, 0x15, 0xc6, 0xf0, 0xb8, + 0x8c, 0xbc, 0xbf, 0x54, 0x9e, 0xf1, 0xc1, 0xb2, + 0xb3, 0x8b, 0xb6, 0x26, 0x02, 0x30, 0xc4, 0x81, + 0x02, 0x41, 0x00, 0xc0, 0x60, 0x62, 0x80, 0xe1, + 0x22, 0x78, 0xf6, 0x9d, 0x83, 0x18, 0xeb, 0x72, + 0x45, 0xd7, 0xc8, 0x01, 0x7f, 0xa9, 0xca, 0x8f, + 0x7d, 0xd6, 0xb8, 0x31, 0x2b, 0x84, 0x7f, 0x62, + 0xd9, 0xa9, 0x22, 0x17, 0x7d, 0x06, 0x35, 0x6c, + 0xf3, 0xc1, 0x94, 0x17, 0x85, 0x5a, 0xaf, 0x9c, + 0x5c, 0x09, 0x3c, 0xcf, 0x2f, 0x44, 0x9d, 0xb6, + 0x52, 0x68, 0x5f, 0xf9, 0x59, 0xc8, 0x84, 0x2b, + 0x39, 0x22, 0x8f, 0x02, 0x41, 0x00, 0xb2, 0x04, + 0xe2, 0x0e, 0x56, 0xca, 0x03, 0x1a, 0xc0, 0xf9, + 0x12, 0x92, 0xa5, 0x6b, 0x42, 0xb8, 0x1c, 0xda, + 0x4d, 0x93, 0x9d, 0x5f, 0x6f, 0xfd, 0xc5, 0x58, + 0xda, 0x55, 0x98, 0x74, 0xfc, 0x28, 0x17, 0x93, + 0x1b, 0x75, 0x9f, 0x50, 0x03, 0x7f, 0x7e, 0xae, + 0xc8, 0x95, 0x33, 0x75, 0x2c, 0xd6, 0xa4, 0x35, + 0xb8, 0x06, 0x03, 0xba, 0x08, 0x59, 0x2b, 0x17, + 0x02, 0xdc, 0x4c, 0x7a, 0x50, 0x01, 0x02, 0x41, + 0x00, 0x9d, 0xdb, 0x39, 0x59, 0x09, 0xe4, 0x30, + 0xa0, 0x24, 0xf5, 0xdb, 0x2f, 0xf0, 0x2f, 0xf1, + 0x75, 0x74, 0x0d, 0x5e, 0xb5, 0x11, 0x73, 0xb0, + 0x0a, 0xaa, 0x86, 0x4c, 0x0d, 0xff, 0x7e, 0x1d, + 0xb4, 0x14, 0xd4, 0x09, 0x91, 0x33, 0x5a, 0xfd, + 0xa0, 0x58, 0x80, 0x9b, 0xbe, 0x78, 0x2e, 0x69, + 0x82, 0x15, 0x7c, 0x72, 0xf0, 0x7b, 0x18, 0x39, + 0xff, 0x6e, 0xeb, 0xc6, 0x86, 0xf5, 0xb4, 0xc7, + 0x6f, 0x02, 0x41, 0x00, 0x8d, 0x1a, 0x37, 0x0f, + 0x76, 0xc4, 0x82, 0xfa, 0x5c, 0xc3, 0x79, 0x35, + 0x3e, 0x70, 0x8a, 0xbf, 0x27, 0x49, 0xb0, 0x99, + 0x63, 0xcb, 0x77, 0x5f, 0xa8, 0x82, 0x65, 0xf6, + 0x03, 0x52, 0x51, 0xf1, 0xae, 0x2e, 0x05, 0xb3, + 0xc6, 0xa4, 0x92, 0xd1, 0xce, 0x6c, 0x72, 0xfb, + 0x21, 0xb3, 0x02, 0x87, 0xe4, 0xfd, 0x61, 0xca, + 0x00, 0x42, 0x19, 0xf0, 0xda, 0x5a, 0x53, 0xe3, + 0xb1, 0xc5, 0x15, 0xf3 + }; + + std::vector<uint8> input; + input.resize(sizeof(private_key_info)); + memcpy(&input.front(), private_key_info, sizeof(private_key_info)); + + private_key.reset(crypto::RSAPrivateKey::CreateFromPrivateKeyInfo(input)); + ASSERT_TRUE(private_key.get()); + + cert = X509Certificate::CreateSelfSigned( + private_key.get(), "CN=subject", 1, base::TimeDelta::FromDays(1)); + + EXPECT_EQ("subject", cert->subject().GetDisplayName()); + EXPECT_FALSE(cert->HasExpired()); +} + +TEST(X509CertificateTest, GetDEREncoded) { + scoped_ptr<crypto::RSAPrivateKey> private_key( + crypto::RSAPrivateKey::Create(1024)); + scoped_refptr<X509Certificate> cert = + X509Certificate::CreateSelfSigned( + private_key.get(), "CN=subject", 0, base::TimeDelta::FromDays(1)); + + std::string der_cert; + EXPECT_TRUE(X509Certificate::GetDEREncoded(cert->os_cert_handle(), + &der_cert)); + EXPECT_FALSE(der_cert.empty()); +} +#endif +#endif // !defined(OS_IOS) + +class X509CertificateParseTest + : public testing::TestWithParam<CertificateFormatTestData> { + public: + virtual ~X509CertificateParseTest() {} + virtual void SetUp() { + test_data_ = GetParam(); + } + virtual void TearDown() {} + + protected: + CertificateFormatTestData test_data_; +}; + +TEST_P(X509CertificateParseTest, CanParseFormat) { + base::FilePath certs_dir = GetTestCertsDirectory(); + CertificateList certs = CreateCertificateListFromFile( + certs_dir, test_data_.file_name, test_data_.format); + ASSERT_FALSE(certs.empty()); + ASSERT_LE(certs.size(), arraysize(test_data_.chain_fingerprints)); + CheckGoogleCert(certs.front(), google_parse_fingerprint, + kGoogleParseValidFrom, kGoogleParseValidTo); + + size_t i; + for (i = 0; i < arraysize(test_data_.chain_fingerprints); ++i) { + if (test_data_.chain_fingerprints[i] == NULL) { + // No more test certificates expected - make sure no more were + // returned before marking this test a success. + EXPECT_EQ(i, certs.size()); + break; + } + + // A cert is expected - make sure that one was parsed. + ASSERT_LT(i, certs.size()); + + // Compare the parsed certificate with the expected certificate, by + // comparing fingerprints. + const X509Certificate* cert = certs[i]; + const SHA1HashValue& actual_fingerprint = cert->fingerprint(); + uint8* expected_fingerprint = test_data_.chain_fingerprints[i]; + + for (size_t j = 0; j < 20; ++j) + EXPECT_EQ(expected_fingerprint[j], actual_fingerprint.data[j]); + } +} + +INSTANTIATE_TEST_CASE_P(, X509CertificateParseTest, + testing::ValuesIn(FormatTestData)); + +struct CertificateNameVerifyTestData { + // true iff we expect hostname to match an entry in cert_names. + bool expected; + // The hostname to match. + const char* hostname; + // Common name, may be used if |dns_names| or |ip_addrs| are empty. + const char* common_name; + // Comma separated list of certificate names to match against. Any occurrence + // of '#' will be replaced with a null character before processing. + const char* dns_names; + // Comma separated list of certificate IP Addresses to match against. Each + // address is x prefixed 16 byte hex code for v6 or dotted-decimals for v4. + const char* ip_addrs; +}; + +// GTest 'magic' pretty-printer, so that if/when a test fails, it knows how +// to output the parameter that was passed. Without this, it will simply +// attempt to print out the first twenty bytes of the object, which depending +// on platform and alignment, may result in an invalid read. +void PrintTo(const CertificateNameVerifyTestData& data, std::ostream* os) { + ASSERT_TRUE(data.hostname && data.common_name); + // Using StringPiece to allow for optional fields being NULL. + *os << " expected: " << data.expected + << "; hostname: " << data.hostname + << "; common_name: " << data.common_name + << "; dns_names: " << base::StringPiece(data.dns_names) + << "; ip_addrs: " << base::StringPiece(data.ip_addrs); +} + +const CertificateNameVerifyTestData kNameVerifyTestData[] = { + { true, "foo.com", "foo.com" }, + { true, "f", "f" }, + { false, "h", "i" }, + { true, "bar.foo.com", "*.foo.com" }, + { true, "www.test.fr", "common.name", + "*.test.com,*.test.co.uk,*.test.de,*.test.fr" }, + { true, "wwW.tESt.fr", "common.name", + ",*.*,*.test.de,*.test.FR,www" }, + { false, "f.uk", ".uk" }, + { false, "w.bar.foo.com", "?.bar.foo.com" }, + { false, "www.foo.com", "(www|ftp).foo.com" }, + { false, "www.foo.com", "www.foo.com#" }, // # = null char. + { false, "www.foo.com", "", "www.foo.com#*.foo.com,#,#" }, + { false, "www.house.example", "ww.house.example" }, + { false, "test.org", "", "www.test.org,*.test.org,*.org" }, + { false, "w.bar.foo.com", "w*.bar.foo.com" }, + { false, "www.bar.foo.com", "ww*ww.bar.foo.com" }, + { false, "wwww.bar.foo.com", "ww*ww.bar.foo.com" }, + { true, "wwww.bar.foo.com", "w*w.bar.foo.com" }, + { false, "wwww.bar.foo.com", "w*w.bar.foo.c0m" }, + { true, "WALLY.bar.foo.com", "wa*.bar.foo.com" }, + { true, "wally.bar.foo.com", "*Ly.bar.foo.com" }, + { true, "ww%57.foo.com", "", "www.foo.com" }, + { true, "www&.foo.com", "www%26.foo.com" }, + // Common name must not be used if subject alternative name was provided. + { false, "www.test.co.jp", "www.test.co.jp", + "*.test.de,*.jp,www.test.co.uk,www.*.co.jp" }, + { false, "www.bar.foo.com", "www.bar.foo.com", + "*.foo.com,*.*.foo.com,*.*.bar.foo.com,*..bar.foo.com," }, + { false, "www.bath.org", "www.bath.org", "", "20.30.40.50" }, + { false, "66.77.88.99", "www.bath.org", "www.bath.org" }, + // IDN tests + { true, "xn--poema-9qae5a.com.br", "xn--poema-9qae5a.com.br" }, + { true, "www.xn--poema-9qae5a.com.br", "*.xn--poema-9qae5a.com.br" }, + { false, "xn--poema-9qae5a.com.br", "", "*.xn--poema-9qae5a.com.br," + "xn--poema-*.com.br," + "xn--*-9qae5a.com.br," + "*--poema-9qae5a.com.br" }, + { true, "xn--poema-9qae5a.com.br", "*.com.br" }, + // The following are adapted from the examples quoted from + // http://tools.ietf.org/html/rfc6125#section-6.4.3 + // (e.g., *.example.com would match foo.example.com but + // not bar.foo.example.com or example.com). + { true, "foo.example.com", "*.example.com" }, + { false, "bar.foo.example.com", "*.example.com" }, + { false, "example.com", "*.example.com" }, + // (e.g., baz*.example.net and *baz.example.net and b*z.example.net would + // be taken to match baz1.example.net and foobaz.example.net and + // buzz.example.net, respectively + { true, "baz1.example.net", "baz*.example.net" }, + { true, "foobaz.example.net", "*baz.example.net" }, + { true, "buzz.example.net", "b*z.example.net" }, + // Wildcards should not be valid unless there are at least three name + // components. + { true, "h.co.uk", "*.co.uk" }, + { false, "foo.com", "*.com" }, + { false, "foo.us", "*.us" }, + { false, "foo", "*" }, + // Multiple wildcards are not valid. + { false, "foo.example.com", "*.*.com" }, + { false, "foo.bar.example.com", "*.bar.*.com" }, + // Absolute vs relative DNS name tests. Although not explicitly specified + // in RFC 6125, absolute reference names (those ending in a .) should + // match either absolute or relative presented names. + { true, "foo.com", "foo.com." }, + { true, "foo.com.", "foo.com" }, + { true, "foo.com.", "foo.com." }, + { true, "f", "f." }, + { true, "f.", "f" }, + { true, "f.", "f." }, + { true, "www-3.bar.foo.com", "*.bar.foo.com." }, + { true, "www-3.bar.foo.com.", "*.bar.foo.com" }, + { true, "www-3.bar.foo.com.", "*.bar.foo.com." }, + { false, ".", "." }, + { false, "example.com", "*.com." }, + { false, "example.com.", "*.com" }, + { false, "example.com.", "*.com." }, + { false, "foo.", "*." }, + // IP addresses in common name; IPv4 only. + { true, "127.0.0.1", "127.0.0.1" }, + { true, "192.168.1.1", "192.168.1.1" }, + { true, "676768", "0.10.83.160" }, + { true, "1.2.3", "1.2.0.3" }, + { false, "192.169.1.1", "192.168.1.1" }, + { false, "12.19.1.1", "12.19.1.1/255.255.255.0" }, + { false, "FEDC:ba98:7654:3210:FEDC:BA98:7654:3210", + "FEDC:BA98:7654:3210:FEDC:ba98:7654:3210" }, + { false, "1111:2222:3333:4444:5555:6666:7777:8888", + "1111:2222:3333:4444:5555:6666:7777:8888" }, + { false, "::192.9.5.5", "[::192.9.5.5]" }, + // No wildcard matching in valid IP addresses + { false, "::192.9.5.5", "*.9.5.5" }, + { false, "2010:836B:4179::836B:4179", "*:836B:4179::836B:4179" }, + { false, "192.168.1.11", "*.168.1.11" }, + { false, "FEDC:BA98:7654:3210:FEDC:BA98:7654:3210", "*.]" }, + // IP addresses in subject alternative name (common name ignored) + { true, "10.1.2.3", "", "", "10.1.2.3" }, + { true, "14.15", "", "", "14.0.0.15" }, + { false, "10.1.2.7", "10.1.2.7", "", "10.1.2.6,10.1.2.8" }, + { false, "10.1.2.8", "10.20.2.8", "foo" }, + { true, "::4.5.6.7", "", "", "x00000000000000000000000004050607" }, + { false, "::6.7.8.9", "::6.7.8.9", "::6.7.8.9", + "x00000000000000000000000006070808,x0000000000000000000000000607080a," + "xff000000000000000000000006070809,6.7.8.9" }, + { true, "FE80::200:f8ff:fe21:67cf", "no.common.name", "", + "x00000000000000000000000006070808,xfe800000000000000200f8fffe2167cf," + "xff0000000000000000000000060708ff,10.0.0.1" }, + // Numeric only hostnames (none of these are considered valid IP addresses). + { false, "12345.6", "12345.6" }, + { false, "121.2.3.512", "", "1*1.2.3.512,*1.2.3.512,1*.2.3.512,*.2.3.512", + "121.2.3.0"}, + { false, "1.2.3.4.5.6", "*.2.3.4.5.6" }, + { true, "1.2.3.4.5", "", "1.2.3.4.5" }, + // Invalid host names. + { false, "junk)(£)$*!@~#", "junk)(£)$*!@~#" }, + { false, "www.*.com", "www.*.com" }, + { false, "w$w.f.com", "w$w.f.com" }, + { false, "nocolonallowed:example", "", "nocolonallowed:example" }, + { false, "www-1.[::FFFF:129.144.52.38]", "*.[::FFFF:129.144.52.38]" }, + { false, "[::4.5.6.9]", "", "", "x00000000000000000000000004050609" }, +}; + +class X509CertificateNameVerifyTest + : public testing::TestWithParam<CertificateNameVerifyTestData> { +}; + +TEST_P(X509CertificateNameVerifyTest, VerifyHostname) { + CertificateNameVerifyTestData test_data = GetParam(); + + std::string common_name(test_data.common_name); + ASSERT_EQ(std::string::npos, common_name.find(',')); + std::replace(common_name.begin(), common_name.end(), '#', '\0'); + + std::vector<std::string> dns_names, ip_addressses; + if (test_data.dns_names) { + // Build up the certificate DNS names list. + std::string dns_name_line(test_data.dns_names); + std::replace(dns_name_line.begin(), dns_name_line.end(), '#', '\0'); + base::SplitString(dns_name_line, ',', &dns_names); + } + + if (test_data.ip_addrs) { + // Build up the certificate IP address list. + std::string ip_addrs_line(test_data.ip_addrs); + std::vector<std::string> ip_addressses_ascii; + base::SplitString(ip_addrs_line, ',', &ip_addressses_ascii); + for (size_t i = 0; i < ip_addressses_ascii.size(); ++i) { + std::string& addr_ascii = ip_addressses_ascii[i]; + ASSERT_NE(0U, addr_ascii.length()); + if (addr_ascii[0] == 'x') { // Hex encoded address + addr_ascii.erase(0, 1); + std::vector<uint8> bytes; + EXPECT_TRUE(base::HexStringToBytes(addr_ascii, &bytes)) + << "Could not parse hex address " << addr_ascii << " i = " << i; + ip_addressses.push_back(std::string(reinterpret_cast<char*>(&bytes[0]), + bytes.size())); + ASSERT_EQ(16U, ip_addressses.back().size()) << i; + } else { // Decimal groups + std::vector<std::string> decimals_ascii; + base::SplitString(addr_ascii, '.', &decimals_ascii); + EXPECT_EQ(4U, decimals_ascii.size()) << i; + std::string addr_bytes; + for (size_t j = 0; j < decimals_ascii.size(); ++j) { + int decimal_value; + EXPECT_TRUE(base::StringToInt(decimals_ascii[j], &decimal_value)); + EXPECT_GE(decimal_value, 0); + EXPECT_LE(decimal_value, 255); + addr_bytes.push_back(static_cast<char>(decimal_value)); + } + ip_addressses.push_back(addr_bytes); + ASSERT_EQ(4U, ip_addressses.back().size()) << i; + } + } + } + + EXPECT_EQ(test_data.expected, X509Certificate::VerifyHostname( + test_data.hostname, common_name, dns_names, ip_addressses)); +} + +INSTANTIATE_TEST_CASE_P(, X509CertificateNameVerifyTest, + testing::ValuesIn(kNameVerifyTestData)); + +} // namespace net diff --git a/net/cert/x509_certificate_win.cc b/net/cert/x509_certificate_win.cc new file mode 100644 index 0000000..6a72328 --- /dev/null +++ b/net/cert/x509_certificate_win.cc @@ -0,0 +1,505 @@ +// Copyright (c) 2012 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 "net/cert/x509_certificate.h" + +#include <blapi.h> // Implement CalculateChainFingerprint() with NSS. + +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "base/pickle.h" +#include "base/sha1.h" +#include "base/string_util.h" +#include "base/utf_string_conversions.h" +#include "crypto/capi_util.h" +#include "crypto/rsa_private_key.h" +#include "crypto/scoped_capi_types.h" +#include "net/base/net_errors.h" + +#pragma comment(lib, "crypt32.lib") + +using base::Time; + +namespace net { + +namespace { + +typedef crypto::ScopedCAPIHandle< + HCERTSTORE, + crypto::CAPIDestroyerWithFlags<HCERTSTORE, + CertCloseStore, 0> > ScopedHCERTSTORE; + +void ExplodedTimeToSystemTime(const base::Time::Exploded& exploded, + SYSTEMTIME* system_time) { + system_time->wYear = exploded.year; + system_time->wMonth = exploded.month; + system_time->wDayOfWeek = exploded.day_of_week; + system_time->wDay = exploded.day_of_month; + system_time->wHour = exploded.hour; + system_time->wMinute = exploded.minute; + system_time->wSecond = exploded.second; + system_time->wMilliseconds = exploded.millisecond; +} + +//----------------------------------------------------------------------------- + +// Decodes the cert's subjectAltName extension into a CERT_ALT_NAME_INFO +// structure and stores it in *output. +void GetCertSubjectAltName(PCCERT_CONTEXT cert, + scoped_ptr_malloc<CERT_ALT_NAME_INFO>* output) { + PCERT_EXTENSION extension = CertFindExtension(szOID_SUBJECT_ALT_NAME2, + cert->pCertInfo->cExtension, + cert->pCertInfo->rgExtension); + if (!extension) + return; + + CRYPT_DECODE_PARA decode_para; + decode_para.cbSize = sizeof(decode_para); + decode_para.pfnAlloc = crypto::CryptAlloc; + decode_para.pfnFree = crypto::CryptFree; + CERT_ALT_NAME_INFO* alt_name_info = NULL; + DWORD alt_name_info_size = 0; + BOOL rv; + rv = CryptDecodeObjectEx(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, + szOID_SUBJECT_ALT_NAME2, + extension->Value.pbData, + extension->Value.cbData, + CRYPT_DECODE_ALLOC_FLAG | CRYPT_DECODE_NOCOPY_FLAG, + &decode_para, + &alt_name_info, + &alt_name_info_size); + if (rv) + output->reset(alt_name_info); +} + +void AddCertsFromStore(HCERTSTORE store, + X509Certificate::OSCertHandles* results) { + PCCERT_CONTEXT cert = NULL; + + while ((cert = CertEnumCertificatesInStore(store, cert)) != NULL) { + PCCERT_CONTEXT to_add = NULL; + if (CertAddCertificateContextToStore( + NULL, // The cert won't be persisted in any cert store. This breaks + // any association the context currently has to |store|, which + // allows us, the caller, to safely close |store| without + // releasing the cert handles. + cert, + CERT_STORE_ADD_USE_EXISTING, + &to_add) && to_add != NULL) { + // When processing stores generated from PKCS#7/PKCS#12 files, it + // appears that the order returned is the inverse of the order that it + // appeared in the file. + // TODO(rsleevi): Ensure this order is consistent across all Win + // versions + results->insert(results->begin(), to_add); + } + } +} + +X509Certificate::OSCertHandles ParsePKCS7(const char* data, size_t length) { + X509Certificate::OSCertHandles results; + CERT_BLOB data_blob; + data_blob.cbData = length; + data_blob.pbData = reinterpret_cast<BYTE*>(const_cast<char*>(data)); + + HCERTSTORE out_store = NULL; + + DWORD expected_types = CERT_QUERY_CONTENT_FLAG_PKCS7_SIGNED | + CERT_QUERY_CONTENT_FLAG_PKCS7_SIGNED_EMBED | + CERT_QUERY_CONTENT_FLAG_PKCS7_UNSIGNED; + + if (!CryptQueryObject(CERT_QUERY_OBJECT_BLOB, &data_blob, expected_types, + CERT_QUERY_FORMAT_FLAG_BINARY, 0, NULL, NULL, NULL, + &out_store, NULL, NULL) || out_store == NULL) { + return results; + } + + AddCertsFromStore(out_store, &results); + CertCloseStore(out_store, CERT_CLOSE_STORE_CHECK_FLAG); + + return results; +} + +// Given a CERT_NAME_BLOB, returns true if it appears in a given list, +// formatted as a vector of strings holding DER-encoded X.509 +// DistinguishedName entries. +bool IsCertNameBlobInIssuerList( + CERT_NAME_BLOB* name_blob, + const std::vector<std::string>& issuer_names) { + for (std::vector<std::string>::const_iterator it = issuer_names.begin(); + it != issuer_names.end(); ++it) { + CERT_NAME_BLOB issuer_blob; + issuer_blob.pbData = + reinterpret_cast<BYTE*>(const_cast<char*>(it->data())); + issuer_blob.cbData = static_cast<DWORD>(it->length()); + + BOOL rb = CertCompareCertificateName( + X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, &issuer_blob, name_blob); + if (rb) + return true; + } + return false; +} + +} // namespace + +void X509Certificate::Initialize() { + DCHECK(cert_handle_); + subject_.ParseDistinguishedName(cert_handle_->pCertInfo->Subject.pbData, + cert_handle_->pCertInfo->Subject.cbData); + issuer_.ParseDistinguishedName(cert_handle_->pCertInfo->Issuer.pbData, + cert_handle_->pCertInfo->Issuer.cbData); + + valid_start_ = Time::FromFileTime(cert_handle_->pCertInfo->NotBefore); + valid_expiry_ = Time::FromFileTime(cert_handle_->pCertInfo->NotAfter); + + fingerprint_ = CalculateFingerprint(cert_handle_); + ca_fingerprint_ = CalculateCAFingerprint(intermediate_ca_certs_); + + const CRYPT_INTEGER_BLOB* serial = &cert_handle_->pCertInfo->SerialNumber; + scoped_array<uint8> serial_bytes(new uint8[serial->cbData]); + for (unsigned i = 0; i < serial->cbData; i++) + serial_bytes[i] = serial->pbData[serial->cbData - i - 1]; + serial_number_ = std::string( + reinterpret_cast<char*>(serial_bytes.get()), serial->cbData); +} + +// static +X509Certificate* X509Certificate::CreateSelfSigned( + crypto::RSAPrivateKey* key, + const std::string& subject, + uint32 serial_number, + base::TimeDelta valid_duration) { + // Get the ASN.1 encoding of the certificate subject. + std::wstring w_subject = ASCIIToWide(subject); + DWORD encoded_subject_length = 0; + if (!CertStrToName( + X509_ASN_ENCODING, + w_subject.c_str(), + CERT_X500_NAME_STR, NULL, NULL, &encoded_subject_length, NULL)) { + return NULL; + } + + scoped_array<BYTE> encoded_subject(new BYTE[encoded_subject_length]); + if (!CertStrToName( + X509_ASN_ENCODING, + w_subject.c_str(), + CERT_X500_NAME_STR, NULL, + encoded_subject.get(), + &encoded_subject_length, NULL)) { + return NULL; + } + + CERT_NAME_BLOB subject_name; + memset(&subject_name, 0, sizeof(subject_name)); + subject_name.cbData = encoded_subject_length; + subject_name.pbData = encoded_subject.get(); + + CRYPT_ALGORITHM_IDENTIFIER sign_algo; + memset(&sign_algo, 0, sizeof(sign_algo)); + sign_algo.pszObjId = szOID_RSA_SHA1RSA; + + base::Time not_before = base::Time::Now(); + base::Time not_after = not_before + valid_duration; + base::Time::Exploded exploded; + + // Create the system time structs representing our exploded times. + not_before.UTCExplode(&exploded); + SYSTEMTIME start_time; + ExplodedTimeToSystemTime(exploded, &start_time); + not_after.UTCExplode(&exploded); + SYSTEMTIME end_time; + ExplodedTimeToSystemTime(exploded, &end_time); + + PCCERT_CONTEXT cert_handle = + CertCreateSelfSignCertificate(key->provider(), &subject_name, + CERT_CREATE_SELFSIGN_NO_KEY_INFO, NULL, + &sign_algo, &start_time, &end_time, NULL); + DCHECK(cert_handle) << "Failed to create self-signed certificate: " + << GetLastError(); + if (!cert_handle) + return NULL; + + X509Certificate* cert = CreateFromHandle(cert_handle, OSCertHandles()); + FreeOSCertHandle(cert_handle); + return cert; +} + +void X509Certificate::GetSubjectAltName( + std::vector<std::string>* dns_names, + std::vector<std::string>* ip_addrs) const { + if (dns_names) + dns_names->clear(); + if (ip_addrs) + ip_addrs->clear(); + + if (!cert_handle_) + return; + + scoped_ptr_malloc<CERT_ALT_NAME_INFO> alt_name_info; + GetCertSubjectAltName(cert_handle_, &alt_name_info); + CERT_ALT_NAME_INFO* alt_name = alt_name_info.get(); + if (alt_name) { + int num_entries = alt_name->cAltEntry; + for (int i = 0; i < num_entries; i++) { + // dNSName is an ASN.1 IA5String representing a string of ASCII + // characters, so we can use WideToASCII here. + const CERT_ALT_NAME_ENTRY& entry = alt_name->rgAltEntry[i]; + + if (dns_names && entry.dwAltNameChoice == CERT_ALT_NAME_DNS_NAME) { + dns_names->push_back(WideToASCII(entry.pwszDNSName)); + } else if (ip_addrs && + entry.dwAltNameChoice == CERT_ALT_NAME_IP_ADDRESS) { + ip_addrs->push_back(std::string( + reinterpret_cast<const char*>(entry.IPAddress.pbData), + entry.IPAddress.cbData)); + } + } + } +} + +PCCERT_CONTEXT X509Certificate::CreateOSCertChainForCert() const { + // Create an in-memory certificate store to hold this certificate and + // any intermediate certificates in |intermediate_ca_certs_|. The store + // will be referenced in the returned PCCERT_CONTEXT, and will not be freed + // until the PCCERT_CONTEXT is freed. + ScopedHCERTSTORE store(CertOpenStore( + CERT_STORE_PROV_MEMORY, 0, NULL, + CERT_STORE_DEFER_CLOSE_UNTIL_LAST_FREE_FLAG, NULL)); + if (!store.get()) + return NULL; + + // NOTE: This preserves all of the properties of |os_cert_handle()| except + // for CERT_KEY_PROV_HANDLE_PROP_ID and CERT_KEY_CONTEXT_PROP_ID - the two + // properties that hold access to already-opened private keys. If a handle + // has already been unlocked (eg: PIN prompt), then the first time that the + // identity is used for client auth, it may prompt the user again. + PCCERT_CONTEXT primary_cert; + BOOL ok = CertAddCertificateContextToStore(store.get(), os_cert_handle(), + CERT_STORE_ADD_ALWAYS, + &primary_cert); + if (!ok || !primary_cert) + return NULL; + + for (size_t i = 0; i < intermediate_ca_certs_.size(); ++i) { + CertAddCertificateContextToStore(store.get(), intermediate_ca_certs_[i], + CERT_STORE_ADD_ALWAYS, NULL); + } + + // Note: |store| is explicitly not released, as the call to CertCloseStore() + // when |store| goes out of scope will not actually free the store. Instead, + // the store will be freed when |primary_cert| is freed. + return primary_cert; +} + +// static +bool X509Certificate::GetDEREncoded(X509Certificate::OSCertHandle cert_handle, + std::string* encoded) { + if (!cert_handle->pbCertEncoded || !cert_handle->cbCertEncoded) + return false; + encoded->assign(reinterpret_cast<char*>(cert_handle->pbCertEncoded), + cert_handle->cbCertEncoded); + return true; +} + +// static +bool X509Certificate::IsSameOSCert(X509Certificate::OSCertHandle a, + X509Certificate::OSCertHandle b) { + DCHECK(a && b); + if (a == b) + return true; + return a->cbCertEncoded == b->cbCertEncoded && + memcmp(a->pbCertEncoded, b->pbCertEncoded, a->cbCertEncoded) == 0; +} + +// static +X509Certificate::OSCertHandle X509Certificate::CreateOSCertHandleFromBytes( + const char* data, int length) { + OSCertHandle cert_handle = NULL; + if (!CertAddEncodedCertificateToStore( + NULL, X509_ASN_ENCODING, reinterpret_cast<const BYTE*>(data), + length, CERT_STORE_ADD_USE_EXISTING, &cert_handle)) + return NULL; + + return cert_handle; +} + +X509Certificate::OSCertHandles X509Certificate::CreateOSCertHandlesFromBytes( + const char* data, int length, Format format) { + OSCertHandles results; + switch (format) { + case FORMAT_SINGLE_CERTIFICATE: { + OSCertHandle handle = CreateOSCertHandleFromBytes(data, length); + if (handle != NULL) + results.push_back(handle); + break; + } + case FORMAT_PKCS7: + results = ParsePKCS7(data, length); + break; + default: + NOTREACHED() << "Certificate format " << format << " unimplemented"; + break; + } + + return results; +} + +// static +X509Certificate::OSCertHandle X509Certificate::DupOSCertHandle( + OSCertHandle cert_handle) { + return CertDuplicateCertificateContext(cert_handle); +} + +// static +void X509Certificate::FreeOSCertHandle(OSCertHandle cert_handle) { + CertFreeCertificateContext(cert_handle); +} + +// static +SHA1HashValue X509Certificate::CalculateFingerprint( + OSCertHandle cert) { + DCHECK(NULL != cert->pbCertEncoded); + DCHECK_NE(static_cast<DWORD>(0), cert->cbCertEncoded); + + BOOL rv; + SHA1HashValue sha1; + DWORD sha1_size = sizeof(sha1.data); + rv = CryptHashCertificate(NULL, CALG_SHA1, 0, cert->pbCertEncoded, + cert->cbCertEncoded, sha1.data, &sha1_size); + DCHECK(rv && sha1_size == sizeof(sha1.data)); + if (!rv) + memset(sha1.data, 0, sizeof(sha1.data)); + return sha1; +} + +// TODO(wtc): This function is implemented with NSS low-level hash +// functions to ensure it is fast. Reimplement this function with +// CryptoAPI. May need to cache the HCRYPTPROV to reduce the overhead. +// static +SHA1HashValue X509Certificate::CalculateCAFingerprint( + const OSCertHandles& intermediates) { + SHA1HashValue sha1; + memset(sha1.data, 0, sizeof(sha1.data)); + + SHA1Context* sha1_ctx = SHA1_NewContext(); + if (!sha1_ctx) + return sha1; + SHA1_Begin(sha1_ctx); + for (size_t i = 0; i < intermediates.size(); ++i) { + PCCERT_CONTEXT ca_cert = intermediates[i]; + SHA1_Update(sha1_ctx, ca_cert->pbCertEncoded, ca_cert->cbCertEncoded); + } + unsigned int result_len; + SHA1_End(sha1_ctx, sha1.data, &result_len, SHA1_LENGTH); + SHA1_DestroyContext(sha1_ctx, PR_TRUE); + + return sha1; +} + +// static +X509Certificate::OSCertHandle +X509Certificate::ReadOSCertHandleFromPickle(PickleIterator* pickle_iter) { + const char* data; + int length; + if (!pickle_iter->ReadData(&data, &length)) + return NULL; + + // Legacy serialized certificates were serialized with extended attributes, + // rather than as DER only. As a result, these serialized certificates are + // not portable across platforms and may have side-effects on Windows due + // to extended attributes being serialized/deserialized - + // http://crbug.com/118706. To avoid deserializing these attributes, write + // the deserialized cert into a temporary cert store and then create a new + // cert from the DER - that is, without attributes. + ScopedHCERTSTORE store( + CertOpenStore(CERT_STORE_PROV_MEMORY, 0, NULL, 0, NULL)); + if (!store.get()) + return NULL; + + OSCertHandle cert_handle = NULL; + if (!CertAddSerializedElementToStore( + store.get(), reinterpret_cast<const BYTE*>(data), length, + CERT_STORE_ADD_NEW, 0, CERT_STORE_CERTIFICATE_CONTEXT_FLAG, + NULL, reinterpret_cast<const void **>(&cert_handle))) { + return NULL; + } + + std::string encoded; + bool ok = GetDEREncoded(cert_handle, &encoded); + FreeOSCertHandle(cert_handle); + cert_handle = NULL; + + if (ok) + cert_handle = CreateOSCertHandleFromBytes(encoded.data(), encoded.size()); + return cert_handle; +} + +// static +bool X509Certificate::WriteOSCertHandleToPickle(OSCertHandle cert_handle, + Pickle* pickle) { + return pickle->WriteData( + reinterpret_cast<char*>(cert_handle->pbCertEncoded), + cert_handle->cbCertEncoded); +} + +// static +void X509Certificate::GetPublicKeyInfo(OSCertHandle cert_handle, + size_t* size_bits, + PublicKeyType* type) { + *type = kPublicKeyTypeUnknown; + *size_bits = 0; + + PCCRYPT_OID_INFO oid_info = CryptFindOIDInfo( + CRYPT_OID_INFO_OID_KEY, + cert_handle->pCertInfo->SubjectPublicKeyInfo.Algorithm.pszObjId, + CRYPT_PUBKEY_ALG_OID_GROUP_ID); + if (!oid_info) + return; + + CHECK_EQ(oid_info->dwGroupId, + static_cast<DWORD>(CRYPT_PUBKEY_ALG_OID_GROUP_ID)); + + *size_bits = CertGetPublicKeyLength( + X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, + &cert_handle->pCertInfo->SubjectPublicKeyInfo); + + switch (oid_info->Algid) { + case CALG_RSA_SIGN: + case CALG_RSA_KEYX: + *type = kPublicKeyTypeRSA; + break; + case CALG_DSS_SIGN: + *type = kPublicKeyTypeDSA; + break; + case CALG_ECDSA: + *type = kPublicKeyTypeECDSA; + break; + case CALG_ECDH: + *type = kPublicKeyTypeECDH; + break; + } +} + +bool X509Certificate::IsIssuedByEncoded( + const std::vector<std::string>& valid_issuers) { + + // If the certificate's issuer in the list? + if (IsCertNameBlobInIssuerList(&cert_handle_->pCertInfo->Issuer, + valid_issuers)) { + return true; + } + // Otherwise, is any of the intermediate CA subjects in the list? + for (OSCertHandles::iterator it = intermediate_ca_certs_.begin(); + it != intermediate_ca_certs_.end(); ++it) { + if (IsCertNameBlobInIssuerList(&(*it)->pCertInfo->Issuer, + valid_issuers)) { + return true; + } + } + + return false; +} + +} // namespace net diff --git a/net/cert/x509_util.cc b/net/cert/x509_util.cc new file mode 100644 index 0000000..4dbadb1 --- /dev/null +++ b/net/cert/x509_util.cc @@ -0,0 +1,49 @@ +// Copyright (c) 2012 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 "net/cert/x509_util.h" + +#include "base/time.h" +#include "net/cert/x509_certificate.h" + +namespace net { + +namespace x509_util { + +ClientCertSorter::ClientCertSorter() : now_(base::Time::Now()) {} + +bool ClientCertSorter::operator()( + const scoped_refptr<X509Certificate>& a, + const scoped_refptr<X509Certificate>& b) const { + // Certificates that are null are sorted last. + if (!a.get() || !b.get()) + return a.get() && !b.get(); + + // Certificates that are expired/not-yet-valid are sorted last. + bool a_is_valid = now_ >= a->valid_start() && now_ <= a->valid_expiry(); + bool b_is_valid = now_ >= b->valid_start() && now_ <= b->valid_expiry(); + if (a_is_valid != b_is_valid) + return a_is_valid && !b_is_valid; + + // Certificates with longer expirations appear as higher priority (less + // than) certificates with shorter expirations. + if (a->valid_expiry() != b->valid_expiry()) + return a->valid_expiry() > b->valid_expiry(); + + // If the expiration dates are equivalent, certificates that were issued + // more recently should be prioritized over older certificates. + if (a->valid_start() != b->valid_start()) + return a->valid_start() > b->valid_start(); + + // Otherwise, prefer client certificates with shorter chains. + const X509Certificate::OSCertHandles& a_intermediates = + a->GetIntermediateCertificates(); + const X509Certificate::OSCertHandles& b_intermediates = + b->GetIntermediateCertificates(); + return a_intermediates.size() < b_intermediates.size(); +} + +} // namespace x509_util + +} // namespace net diff --git a/net/cert/x509_util.h b/net/cert/x509_util.h new file mode 100644 index 0000000..50ffc7f --- /dev/null +++ b/net/cert/x509_util.h @@ -0,0 +1,73 @@ +// Copyright (c) 2012 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. + +#ifndef NET_CERT_X509_UTIL_H_ +#define NET_CERT_X509_UTIL_H_ + +#include <string> + +#include "base/memory/ref_counted.h" +#include "base/time.h" +#include "net/base/net_export.h" + +namespace crypto { +class ECPrivateKey; +} + +namespace net { + +class X509Certificate; + +namespace x509_util { + +// Returns true if the times can be used to create an X.509 certificate. +// Certificates can accept dates from Jan 1st, 1 to Dec 31, 9999. A bug in NSS +// limited the range to 1950-9999 +// (https://bugzilla.mozilla.org/show_bug.cgi?id=786531). This function will +// return whether it is supported by the currently used crypto library. +NET_EXPORT_PRIVATE bool IsSupportedValidityRange(base::Time not_valid_before, + base::Time not_valid_after); + +// Creates a server bound certificate containing the public key in |key|. +// Domain, serial number and validity period are given as +// parameters. The certificate is signed by the private key in |key|. +// The hashing algorithm for the signature is SHA-1. +// +// See Internet Draft draft-balfanz-tls-obc-00 for more details: +// http://tools.ietf.org/html/draft-balfanz-tls-obc-00 +NET_EXPORT_PRIVATE bool CreateDomainBoundCertEC( + crypto::ECPrivateKey* key, + const std::string& domain, + uint32 serial_number, + base::Time not_valid_before, + base::Time not_valid_after, + std::string* der_cert); + +// Comparator for use in STL algorithms that will sort client certificates by +// order of preference. +// Returns true if |a| is more preferable than |b|, allowing it to be used +// with any algorithm that compares according to strict weak ordering. +// +// Criteria include: +// - Prefer certificates that have a longer validity period (later +// expiration dates) +// - If equal, prefer certificates that were issued more recently +// - If equal, prefer shorter chains (if available) +class NET_EXPORT_PRIVATE ClientCertSorter { + public: + ClientCertSorter(); + + bool operator()( + const scoped_refptr<X509Certificate>& a, + const scoped_refptr<X509Certificate>& b) const; + + private: + base::Time now_; +}; + +} // namespace x509_util + +} // namespace net + +#endif // NET_CERT_X509_UTIL_H_ diff --git a/net/cert/x509_util_ios.cc b/net/cert/x509_util_ios.cc new file mode 100644 index 0000000..5de6d19 --- /dev/null +++ b/net/cert/x509_util_ios.cc @@ -0,0 +1,141 @@ +// Copyright (c) 2012 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 "net/cert/x509_util_ios.h" + +#include <cert.h> +#include <CommonCrypto/CommonDigest.h> +#include <nss.h> +#include <prtypes.h> + +#include "base/mac/scoped_cftyperef.h" +#include "crypto/nss_util.h" +#include "net/cert/x509_certificate.h" +#include "net/cert/x509_util_nss.h" + +using base::mac::ScopedCFTypeRef; + +namespace net { +namespace x509_util_ios { + +namespace { + +// Creates an NSS certificate handle from |data|, which is |length| bytes in +// size. +CERTCertificate* CreateNSSCertHandleFromBytes(const char* data, + int length) { + if (length < 0) + return NULL; + + crypto::EnsureNSSInit(); + + if (!NSS_IsInitialized()) + return NULL; + + SECItem der_cert; + der_cert.data = reinterpret_cast<unsigned char*>(const_cast<char*>(data)); + der_cert.len = length; + der_cert.type = siDERCertBuffer; + + // Parse into a certificate structure. + return CERT_NewTempCertificate(CERT_GetDefaultCertDB(), &der_cert, NULL, + PR_FALSE, PR_TRUE); +} + +} // namespace + +CERTCertificate* CreateNSSCertHandleFromOSHandle( + SecCertificateRef cert_handle) { + ScopedCFTypeRef<CFDataRef> cert_data(SecCertificateCopyData(cert_handle)); + return CreateNSSCertHandleFromBytes( + reinterpret_cast<const char*>(CFDataGetBytePtr(cert_data)), + CFDataGetLength(cert_data)); +} + +SecCertificateRef CreateOSCertHandleFromNSSHandle( + CERTCertificate* nss_cert_handle) { + return X509Certificate::CreateOSCertHandleFromBytes( + reinterpret_cast<const char*>(nss_cert_handle->derCert.data), + nss_cert_handle->derCert.len); +} + +X509Certificate* CreateCertFromNSSHandles( + CERTCertificate* cert_handle, + const std::vector<CERTCertificate*>& intermediates) { + ScopedCFTypeRef<SecCertificateRef> os_server_cert( + CreateOSCertHandleFromNSSHandle(cert_handle)); + if (!os_server_cert) + return NULL; + std::vector<SecCertificateRef> os_intermediates; + for (size_t i = 0; i < intermediates.size(); ++i) { + SecCertificateRef intermediate = + CreateOSCertHandleFromNSSHandle(intermediates[i]); + if (!intermediate) + break; + os_intermediates.push_back(intermediate); + } + + X509Certificate* cert = NULL; + if (intermediates.size() == os_intermediates.size()) { + cert = X509Certificate::CreateFromHandle(os_server_cert, + os_intermediates); + } + + for (size_t i = 0; i < os_intermediates.size(); ++i) + CFRelease(os_intermediates[i]); + return cert; +} + +SHA1HashValue CalculateFingerprintNSS(CERTCertificate* cert) { + DCHECK(cert->derCert.data); + DCHECK_NE(0U, cert->derCert.len); + SHA1HashValue sha1; + memset(sha1.data, 0, sizeof(sha1.data)); + CC_SHA1(cert->derCert.data, cert->derCert.len, sha1.data); + return sha1; +} + +// NSSCertificate implementation. + +NSSCertificate::NSSCertificate(SecCertificateRef cert_handle) { + nss_cert_handle_ = CreateNSSCertHandleFromOSHandle(cert_handle); + DLOG_IF(INFO, cert_handle && !nss_cert_handle_) + << "Could not convert SecCertificateRef to CERTCertificate*"; +} + +NSSCertificate::~NSSCertificate() { + CERT_DestroyCertificate(nss_cert_handle_); +} + +CERTCertificate* NSSCertificate::cert_handle() const { + return nss_cert_handle_; +} + +// NSSCertChain implementation + +NSSCertChain::NSSCertChain(X509Certificate* certificate) { + DCHECK(certificate); + certs_.push_back(CreateNSSCertHandleFromOSHandle( + certificate->os_cert_handle())); + const X509Certificate::OSCertHandles& cert_intermediates = + certificate->GetIntermediateCertificates(); + for (size_t i = 0; i < cert_intermediates.size(); ++i) + certs_.push_back(CreateNSSCertHandleFromOSHandle(cert_intermediates[i])); +} + +NSSCertChain::~NSSCertChain() { + for (size_t i = 0; i < certs_.size(); ++i) + CERT_DestroyCertificate(certs_[i]); +} + +CERTCertificate* NSSCertChain::cert_handle() const { + return certs_.empty() ? NULL : certs_.front(); +} + +const std::vector<CERTCertificate*>& NSSCertChain::cert_chain() const { + return certs_; +} + +} // namespace x509_util_ios +} // namespace net diff --git a/net/cert/x509_util_ios.h b/net/cert/x509_util_ios.h new file mode 100644 index 0000000..5a8a576 --- /dev/null +++ b/net/cert/x509_util_ios.h @@ -0,0 +1,72 @@ +// Copyright (c) 2012 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. + +// This file contains functions for iOS to glue NSS and Security.framework +// together. + +#ifndef NET_CERT_X509_UTIL_IOS_H_ +#define NET_CERT_X509_UTIL_IOS_H_ + +#include <Security/Security.h> +#include <vector> + +#include "net/cert/x509_cert_types.h" + +// Forward declaration; real one in <cert.h> +typedef struct CERTCertificateStr CERTCertificate; + +namespace net { + +class X509Certificate; + +namespace x509_util_ios { + +// Converts a Security.framework certificate handle (SecCertificateRef) into +// an NSS certificate handle (CERTCertificate*). +CERTCertificate* CreateNSSCertHandleFromOSHandle(SecCertificateRef cert_handle); + +// Converts an NSS certificate handle (CERTCertificate*) into a +// Security.framework handle (SecCertificateRef) +SecCertificateRef CreateOSCertHandleFromNSSHandle( + CERTCertificate* nss_cert_handle); + +// Create a new X509Certificate from the specified NSS server cert and +// intermediates. This is functionally equivalent to +// X509Certificate::CreateFromHandle(), except it supports receiving +// NSS CERTCertificate*s rather than iOS SecCertificateRefs. +X509Certificate* CreateCertFromNSSHandles( + CERTCertificate* cert_handle, + const std::vector<CERTCertificate*>& intermediates); + +SHA1HashValue CalculateFingerprintNSS(CERTCertificate* cert); + +// This is a wrapper class around the native NSS certificate handle. +// The constructor copies the certificate data from |cert_handle| and +// uses the NSS library to parse it. +class NSSCertificate { + public: + explicit NSSCertificate(SecCertificateRef cert_handle); + ~NSSCertificate(); + CERTCertificate* cert_handle() const; + private: + CERTCertificate* nss_cert_handle_; +}; + +// A wrapper class that loads a certificate and all of its intermediates into +// NSS. This is necessary for libpkix path building to be able to locate +// needed intermediates. +class NSSCertChain { + public: + explicit NSSCertChain(X509Certificate* certificate); + ~NSSCertChain(); + CERTCertificate* cert_handle() const; + const std::vector<CERTCertificate*>& cert_chain() const; + private: + std::vector<CERTCertificate*> certs_; +}; + +} // namespace x509_util_ios +} // namespace net + +#endif // NET_CERT_X509_UTIL_IOS_H_ diff --git a/net/cert/x509_util_mac.cc b/net/cert/x509_util_mac.cc new file mode 100644 index 0000000..c9aa37b --- /dev/null +++ b/net/cert/x509_util_mac.cc @@ -0,0 +1,231 @@ +// Copyright (c) 2012 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 "net/cert/x509_util_mac.h" + +#include "base/logging.h" +#include "third_party/apple_apsl/cssmapplePriv.h" + +namespace net { + +namespace x509_util { + +namespace { + +// Creates a SecPolicyRef for the given OID, with optional value. +OSStatus CreatePolicy(const CSSM_OID* policy_oid, + void* option_data, + size_t option_length, + SecPolicyRef* policy) { + SecPolicySearchRef search; + OSStatus err = SecPolicySearchCreate(CSSM_CERT_X_509v3, policy_oid, NULL, + &search); + if (err) + return err; + err = SecPolicySearchCopyNext(search, policy); + CFRelease(search); + if (err) + return err; + + if (option_data) { + CSSM_DATA options_data = { + option_length, + reinterpret_cast<uint8_t*>(option_data) + }; + err = SecPolicySetValue(*policy, &options_data); + if (err) { + CFRelease(*policy); + return err; + } + } + return noErr; +} + +} // namespace + + +OSStatus CreateSSLClientPolicy(SecPolicyRef* policy) { + CSSM_APPLE_TP_SSL_OPTIONS tp_ssl_options; + memset(&tp_ssl_options, 0, sizeof(tp_ssl_options)); + tp_ssl_options.Version = CSSM_APPLE_TP_SSL_OPTS_VERSION; + tp_ssl_options.Flags |= CSSM_APPLE_TP_SSL_CLIENT; + + return CreatePolicy(&CSSMOID_APPLE_TP_SSL, &tp_ssl_options, + sizeof(tp_ssl_options), policy); +} + +OSStatus CreateSSLServerPolicy(const std::string& hostname, + SecPolicyRef* policy) { + CSSM_APPLE_TP_SSL_OPTIONS tp_ssl_options; + memset(&tp_ssl_options, 0, sizeof(tp_ssl_options)); + tp_ssl_options.Version = CSSM_APPLE_TP_SSL_OPTS_VERSION; + if (!hostname.empty()) { + tp_ssl_options.ServerName = hostname.data(); + tp_ssl_options.ServerNameLen = hostname.size(); + } + + return CreatePolicy(&CSSMOID_APPLE_TP_SSL, &tp_ssl_options, + sizeof(tp_ssl_options), policy); +} + +OSStatus CreateBasicX509Policy(SecPolicyRef* policy) { + return CreatePolicy(&CSSMOID_APPLE_X509_BASIC, NULL, 0, policy); +} + +OSStatus CreateRevocationPolicies(bool enable_revocation_checking, + bool enable_ev_checking, + CFMutableArrayRef policies) { + OSStatus status = noErr; + + // In order to bypass the system revocation checking settings, the + // SecTrustRef must have at least one revocation policy associated with it. + // Since it is not known prior to verification whether the Apple TP will + // consider a certificate as an EV candidate, the default policy used is a + // CRL policy, since it does not communicate over the network. + // If the TP believes the leaf is an EV cert, it will explicitly add an + // OCSP policy to perform the online checking, and if it doesn't believe + // that the leaf is EV, then the default CRL policy will effectively no-op. + // This behaviour is used to implement EV-only revocation checking. + if (enable_ev_checking || enable_revocation_checking) { + CSSM_APPLE_TP_CRL_OPTIONS tp_crl_options; + memset(&tp_crl_options, 0, sizeof(tp_crl_options)); + tp_crl_options.Version = CSSM_APPLE_TP_CRL_OPTS_VERSION; + // Only allow network CRL fetches if the caller explicitly requests + // online revocation checking. Note that, as of OS X 10.7.2, the system + // will set force this flag on according to system policies, so + // online revocation checks cannot be completely disabled. + if (enable_revocation_checking) + tp_crl_options.CrlFlags = CSSM_TP_ACTION_FETCH_CRL_FROM_NET; + + SecPolicyRef crl_policy; + status = CreatePolicy(&CSSMOID_APPLE_TP_REVOCATION_CRL, &tp_crl_options, + sizeof(tp_crl_options), &crl_policy); + if (status) + return status; + CFArrayAppendValue(policies, crl_policy); + CFRelease(crl_policy); + } + + // If revocation checking is explicitly enabled, then add an OCSP policy + // and allow network access. If both revocation checking and EV checking + // are disabled, then the added OCSP policy will be prevented from + // accessing the network. This is done because the TP will force an OCSP + // policy to be present when it believes the certificate is EV. If network + // fetching was not explicitly disabled, then it would be as if + // enable_ev_checking was always set to true. + if (enable_revocation_checking || !enable_ev_checking) { + CSSM_APPLE_TP_OCSP_OPTIONS tp_ocsp_options; + memset(&tp_ocsp_options, 0, sizeof(tp_ocsp_options)); + tp_ocsp_options.Version = CSSM_APPLE_TP_OCSP_OPTS_VERSION; + + if (enable_revocation_checking) { + // The default for the OCSP policy is to fetch responses via the network, + // unlike the CRL policy default. The policy is further modified to + // prefer OCSP over CRLs, if both are specified on the certificate. This + // is because an OCSP response is both sufficient and typically + // significantly smaller than the CRL counterpart. + tp_ocsp_options.Flags = CSSM_TP_ACTION_OCSP_SUFFICIENT; + } else { + // Effectively disable OCSP checking by making it impossible to get an + // OCSP response. Even if the Apple TP forces OCSP, no checking will + // be able to succeed. If this happens, the Apple TP will report an error + // that OCSP was unavailable, but this will be handled and suppressed in + // X509Certificate::Verify(). + tp_ocsp_options.Flags = CSSM_TP_ACTION_OCSP_DISABLE_NET | + CSSM_TP_ACTION_OCSP_CACHE_READ_DISABLE; + } + + SecPolicyRef ocsp_policy; + status = CreatePolicy(&CSSMOID_APPLE_TP_REVOCATION_OCSP, &tp_ocsp_options, + sizeof(tp_ocsp_options), &ocsp_policy); + if (status) + return status; + CFArrayAppendValue(policies, ocsp_policy); + CFRelease(ocsp_policy); + } + + return status; +} + +CSSMFieldValue::CSSMFieldValue() + : cl_handle_(CSSM_INVALID_HANDLE), + oid_(NULL), + field_(NULL) { +} +CSSMFieldValue::CSSMFieldValue(CSSM_CL_HANDLE cl_handle, + const CSSM_OID* oid, + CSSM_DATA_PTR field) + : cl_handle_(cl_handle), + oid_(const_cast<CSSM_OID_PTR>(oid)), + field_(field) { +} + +CSSMFieldValue::~CSSMFieldValue() { + Reset(CSSM_INVALID_HANDLE, NULL, NULL); +} + +void CSSMFieldValue::Reset(CSSM_CL_HANDLE cl_handle, + CSSM_OID_PTR oid, + CSSM_DATA_PTR field) { + if (cl_handle_ && oid_ && field_) + CSSM_CL_FreeFieldValue(cl_handle_, oid_, field_); + cl_handle_ = cl_handle; + oid_ = oid; + field_ = field; +} + +CSSMCachedCertificate::CSSMCachedCertificate() + : cl_handle_(CSSM_INVALID_HANDLE), + cached_cert_handle_(CSSM_INVALID_HANDLE) { +} +CSSMCachedCertificate::~CSSMCachedCertificate() { + if (cl_handle_ && cached_cert_handle_) + CSSM_CL_CertAbortCache(cl_handle_, cached_cert_handle_); +} + +OSStatus CSSMCachedCertificate::Init(SecCertificateRef os_cert_handle) { + DCHECK(!cl_handle_ && !cached_cert_handle_); + DCHECK(os_cert_handle); + CSSM_DATA cert_data; + OSStatus status = SecCertificateGetData(os_cert_handle, &cert_data); + if (status) + return status; + status = SecCertificateGetCLHandle(os_cert_handle, &cl_handle_); + if (status) { + DCHECK(!cl_handle_); + return status; + } + + status = CSSM_CL_CertCache(cl_handle_, &cert_data, &cached_cert_handle_); + if (status) + DCHECK(!cached_cert_handle_); + return status; +} + +OSStatus CSSMCachedCertificate::GetField(const CSSM_OID* field_oid, + CSSMFieldValue* field) const { + DCHECK(cl_handle_); + DCHECK(cached_cert_handle_); + + CSSM_OID_PTR oid = const_cast<CSSM_OID_PTR>(field_oid); + CSSM_DATA_PTR field_ptr = NULL; + CSSM_HANDLE results_handle = CSSM_INVALID_HANDLE; + uint32 field_value_count = 0; + CSSM_RETURN status = CSSM_CL_CertGetFirstCachedFieldValue( + cl_handle_, cached_cert_handle_, oid, &results_handle, + &field_value_count, &field_ptr); + if (status) + return status; + + // Note: |field_value_count| may be > 1, indicating that more than one + // value is present. This may happen with extensions, but for current + // usages, only the first value is returned. + CSSM_CL_CertAbortQuery(cl_handle_, results_handle); + field->Reset(cl_handle_, oid, field_ptr); + return CSSM_OK; +} + +} // namespace x509_util + +} // namespace net diff --git a/net/cert/x509_util_mac.h b/net/cert/x509_util_mac.h new file mode 100644 index 0000000..caf7a28 --- /dev/null +++ b/net/cert/x509_util_mac.h @@ -0,0 +1,139 @@ +// Copyright (c) 2012 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. + +#ifndef NET_CERT_X509_UTIL_MAC_H_ +#define NET_CERT_X509_UTIL_MAC_H_ + +#include <CoreFoundation/CFArray.h> +#include <Security/Security.h> + +#include <string> + +#include "base/basictypes.h" +#include "net/base/net_export.h" + +namespace net { + +namespace x509_util { + +// Creates a security policy for certificates used as client certificates +// in SSL. +// If a policy is successfully created, it will be stored in +// |*policy| and ownership transferred to the caller. +OSStatus NET_EXPORT CreateSSLClientPolicy(SecPolicyRef* policy); + +// Create an SSL server policy. While certificate name validation will be +// performed by SecTrustEvaluate(), it has the following limitations: +// - Doesn't support IP addresses in dotted-quad literals (127.0.0.1) +// - Doesn't support IPv6 addresses +// - Doesn't support the iPAddress subjectAltName +// Providing the hostname is necessary in order to locate certain user or +// system trust preferences, such as those created by Safari. Preferences +// created by Keychain Access do not share this requirement. +// On success, stores the resultant policy in |*policy| and returns noErr. +OSStatus NET_EXPORT CreateSSLServerPolicy(const std::string& hostname, + SecPolicyRef* policy); + +// Creates a security policy for basic X.509 validation. If the policy is +// successfully created, it will be stored in |*policy| and ownership +// transferred to the caller. +OSStatus NET_EXPORT CreateBasicX509Policy(SecPolicyRef* policy); + +// Creates security policies to control revocation checking (OCSP and CRL). +// If |enable_revocation_checking| is true, revocation checking will be +// explicitly enabled. +// If |enable_revocation_checking| is false, but |enable_ev_checking| is +// true, then the system policies for EV checking (which include checking +// for an online OCSP response) will be permitted. However, if the OS +// does not believe the certificate is EV, no revocation checking will be +// performed. +// If both are false, then the policies returned will be explicitly +// prohibited from accessing the network or the local cache, regardless of +// system settings. +// If the policies are successfully created, they will be appended to +// |policies|. +OSStatus NET_EXPORT CreateRevocationPolicies(bool enable_revocation_checking, + bool enable_ev_checking, + CFMutableArrayRef policies); + +// Wrapper for a CSSM_DATA_PTR that was obtained via one of the CSSM field +// accessors (such as CSSM_CL_CertGet[First/Next]Value or +// CSSM_CL_CertGet[First/Next]CachedValue). +class CSSMFieldValue { + public: + CSSMFieldValue(); + CSSMFieldValue(CSSM_CL_HANDLE cl_handle, + const CSSM_OID* oid, + CSSM_DATA_PTR field); + ~CSSMFieldValue(); + + CSSM_OID_PTR oid() const { return oid_; } + CSSM_DATA_PTR field() const { return field_; } + + // Returns the field as if it was an arbitrary type - most commonly, by + // interpreting the field as a specific CSSM/CDSA parsed type, such as + // CSSM_X509_SUBJECT_PUBLIC_KEY_INFO or CSSM_X509_ALGORITHM_IDENTIFIER. + // An added check is applied to ensure that the current field is large + // enough to actually contain the requested type. + template <typename T> const T* GetAs() const { + if (!field_ || field_->Length < sizeof(T)) + return NULL; + return reinterpret_cast<const T*>(field_->Data); + } + + void Reset(CSSM_CL_HANDLE cl_handle, + CSSM_OID_PTR oid, + CSSM_DATA_PTR field); + + private: + CSSM_CL_HANDLE cl_handle_; + CSSM_OID_PTR oid_; + CSSM_DATA_PTR field_; + + DISALLOW_COPY_AND_ASSIGN(CSSMFieldValue); +}; + +// CSSMCachedCertificate is a container class that is used to wrap the +// CSSM_CL_CertCache APIs and provide safe and efficient access to +// certificate fields in their CSSM form. +// +// To provide efficient access to certificate/CRL fields, CSSM provides an +// API/SPI to "cache" a certificate/CRL. The exact meaning of a cached +// certificate is not defined by CSSM, but is documented to generally be some +// intermediate or parsed form of the certificate. In the case of Apple's +// CSSM CL implementation, the intermediate form is the parsed certificate +// stored in an internal format (which happens to be NSS). By caching the +// certificate, callers that wish to access multiple fields (such as subject, +// issuer, and validity dates) do not need to repeatedly parse the entire +// certificate, nor are they forced to convert all fields from their NSS types +// to their CSSM equivalents. This latter point is especially helpful when +// running on OS X 10.5, as it will fail to convert some fields that reference +// unsupported algorithms, such as ECC. +class CSSMCachedCertificate { + public: + CSSMCachedCertificate(); + ~CSSMCachedCertificate(); + + // Initializes the CSSMCachedCertificate by caching the specified + // |os_cert_handle|. On success, returns noErr. + // Note: Once initialized, the cached certificate should only be accessed + // from a single thread. + OSStatus Init(SecCertificateRef os_cert_handle); + + // Fetches the first value for the field associated with |field_oid|. + // If |field_oid| is a valid OID and is present in the current certificate, + // returns CSSM_OK and stores the first value in |field|. If additional + // values are associated with |field_oid|, they are ignored. + OSStatus GetField(const CSSM_OID* field_oid, CSSMFieldValue* field) const; + + private: + CSSM_CL_HANDLE cl_handle_; + CSSM_HANDLE cached_cert_handle_; +}; + +} // namespace x509_util + +} // namespace net + +#endif // NET_CERT_X509_UTIL_MAC_H_ diff --git a/net/cert/x509_util_nss.cc b/net/cert/x509_util_nss.cc new file mode 100644 index 0000000..90b7487 --- /dev/null +++ b/net/cert/x509_util_nss.cc @@ -0,0 +1,600 @@ +// Copyright (c) 2012 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 "net/cert/x509_util.h" +#include "net/cert/x509_util_nss.h" + +#include <cert.h> +#include <cryptohi.h> +#include <nss.h> +#include <pk11pub.h> +#include <prerror.h> +#include <secder.h> +#include <secmod.h> +#include <secport.h> + +#include "base/debug/leak_annotations.h" +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "base/memory/singleton.h" +#include "base/pickle.h" +#include "crypto/ec_private_key.h" +#include "crypto/nss_util.h" +#include "crypto/nss_util_internal.h" +#include "crypto/scoped_nss_types.h" +#include "crypto/third_party/nss/chromium-nss.h" +#include "net/cert/x509_certificate.h" + +namespace net { + +namespace { + +class DomainBoundCertOIDWrapper { + public: + static DomainBoundCertOIDWrapper* GetInstance() { + // Instantiated as a leaky singleton to allow the singleton to be + // constructed on a worker thead that is not joined when a process + // shuts down. + return Singleton<DomainBoundCertOIDWrapper, + LeakySingletonTraits<DomainBoundCertOIDWrapper> >::get(); + } + + SECOidTag domain_bound_cert_oid_tag() const { + return domain_bound_cert_oid_tag_; + } + + private: + friend struct DefaultSingletonTraits<DomainBoundCertOIDWrapper>; + + DomainBoundCertOIDWrapper(); + + SECOidTag domain_bound_cert_oid_tag_; + + DISALLOW_COPY_AND_ASSIGN(DomainBoundCertOIDWrapper); +}; + +DomainBoundCertOIDWrapper::DomainBoundCertOIDWrapper() + : domain_bound_cert_oid_tag_(SEC_OID_UNKNOWN) { + // 1.3.6.1.4.1.11129.2.1.6 + // (iso.org.dod.internet.private.enterprises.google.googleSecurity. + // certificateExtensions.originBoundCertificate) + static const uint8 kObCertOID[] = { + 0x2b, 0x06, 0x01, 0x04, 0x01, 0xd6, 0x79, 0x02, 0x01, 0x06 + }; + SECOidData oid_data; + memset(&oid_data, 0, sizeof(oid_data)); + oid_data.oid.data = const_cast<uint8*>(kObCertOID); + oid_data.oid.len = sizeof(kObCertOID); + oid_data.offset = SEC_OID_UNKNOWN; + oid_data.desc = "Origin Bound Certificate"; + oid_data.mechanism = CKM_INVALID_MECHANISM; + oid_data.supportedExtension = SUPPORTED_CERT_EXTENSION; + domain_bound_cert_oid_tag_ = SECOID_AddEntry(&oid_data); + if (domain_bound_cert_oid_tag_ == SEC_OID_UNKNOWN) + LOG(ERROR) << "OB_CERT OID tag creation failed"; +} + +// Creates a Certificate object that may be passed to the SignCertificate +// method to generate an X509 certificate. +// Returns NULL if an error is encountered in the certificate creation +// process. +// Caller responsible for freeing returned certificate object. +CERTCertificate* CreateCertificate( + SECKEYPublicKey* public_key, + const std::string& subject, + uint32 serial_number, + base::Time not_valid_before, + base::Time not_valid_after) { + // Create info about public key. + CERTSubjectPublicKeyInfo* spki = + SECKEY_CreateSubjectPublicKeyInfo(public_key); + if (!spki) + return NULL; + + // Create the certificate request. + CERTName* subject_name = + CERT_AsciiToName(const_cast<char*>(subject.c_str())); + CERTCertificateRequest* cert_request = + CERT_CreateCertificateRequest(subject_name, spki, NULL); + SECKEY_DestroySubjectPublicKeyInfo(spki); + + if (!cert_request) { + PRErrorCode prerr = PR_GetError(); + LOG(ERROR) << "Failed to create certificate request: " << prerr; + CERT_DestroyName(subject_name); + return NULL; + } + + CERTValidity* validity = CERT_CreateValidity( + crypto::BaseTimeToPRTime(not_valid_before), + crypto::BaseTimeToPRTime(not_valid_after)); + if (!validity) { + PRErrorCode prerr = PR_GetError(); + LOG(ERROR) << "Failed to create certificate validity object: " << prerr; + CERT_DestroyName(subject_name); + CERT_DestroyCertificateRequest(cert_request); + return NULL; + } + CERTCertificate* cert = CERT_CreateCertificate(serial_number, subject_name, + validity, cert_request); + if (!cert) { + PRErrorCode prerr = PR_GetError(); + LOG(ERROR) << "Failed to create certificate: " << prerr; + } + + // Cleanup for resources used to generate the cert. + CERT_DestroyName(subject_name); + CERT_DestroyValidity(validity); + CERT_DestroyCertificateRequest(cert_request); + + return cert; +} + +// Signs a certificate object, with |key| generating a new X509Certificate +// and destroying the passed certificate object (even when NULL is returned). +// The logic of this method references SignCert() in NSS utility certutil: +// http://mxr.mozilla.org/security/ident?i=SignCert. +// Returns true on success or false if an error is encountered in the +// certificate signing process. +bool SignCertificate( + CERTCertificate* cert, + SECKEYPrivateKey* key) { + // |arena| is used to encode the cert. + PLArenaPool* arena = cert->arena; + SECOidTag algo_id = SEC_GetSignatureAlgorithmOidTag(key->keyType, + SEC_OID_SHA1); + if (algo_id == SEC_OID_UNKNOWN) + return false; + + SECStatus rv = SECOID_SetAlgorithmID(arena, &cert->signature, algo_id, 0); + if (rv != SECSuccess) + return false; + + // Generate a cert of version 3. + *(cert->version.data) = 2; + cert->version.len = 1; + + SECItem der = { siBuffer, NULL, 0 }; + + // Use ASN1 DER to encode the cert. + void* encode_result = SEC_ASN1EncodeItem( + NULL, &der, cert, SEC_ASN1_GET(CERT_CertificateTemplate)); + if (!encode_result) + return false; + + // Allocate space to contain the signed cert. + SECItem result = { siBuffer, NULL, 0 }; + + // Sign the ASN1 encoded cert and save it to |result|. + rv = DerSignData(arena, &result, &der, key, algo_id); + PORT_Free(der.data); + if (rv != SECSuccess) { + DLOG(ERROR) << "DerSignData: " << PORT_GetError(); + return false; + } + + // Save the signed result to the cert. + cert->derCert = result; + + return true; +} + +bool CreateDomainBoundCertInternal( + SECKEYPublicKey* public_key, + SECKEYPrivateKey* private_key, + const std::string& domain, + uint32 serial_number, + base::Time not_valid_before, + base::Time not_valid_after, + std::string* der_cert) { + CERTCertificate* cert = CreateCertificate(public_key, + "CN=anonymous.invalid", + serial_number, + not_valid_before, + not_valid_after); + + if (!cert) + return false; + + // Create opaque handle used to add extensions later. + void* cert_handle; + if ((cert_handle = CERT_StartCertExtensions(cert)) == NULL) { + LOG(ERROR) << "Unable to get opaque handle for adding extensions"; + CERT_DestroyCertificate(cert); + return false; + } + + // Create SECItem for IA5String encoding. + SECItem domain_string_item = { + siAsciiString, + (unsigned char*)domain.data(), + static_cast<unsigned>(domain.size()) + }; + + // IA5Encode and arena allocate SECItem + SECItem* asn1_domain_string = SEC_ASN1EncodeItem( + cert->arena, NULL, &domain_string_item, + SEC_ASN1_GET(SEC_IA5StringTemplate)); + if (asn1_domain_string == NULL) { + LOG(ERROR) << "Unable to get ASN1 encoding for domain in domain_bound_cert" + " extension"; + CERT_DestroyCertificate(cert); + return false; + } + + // Add the extension to the opaque handle + if (CERT_AddExtension( + cert_handle, + DomainBoundCertOIDWrapper::GetInstance()->domain_bound_cert_oid_tag(), + asn1_domain_string, PR_TRUE, PR_TRUE) != SECSuccess){ + LOG(ERROR) << "Unable to add domain bound cert extension to opaque handle"; + CERT_DestroyCertificate(cert); + return false; + } + + // Copy extension into x509 cert + if (CERT_FinishExtensions(cert_handle) != SECSuccess){ + LOG(ERROR) << "Unable to copy extension to X509 cert"; + CERT_DestroyCertificate(cert); + return false; + } + + if (!SignCertificate(cert, private_key)) { + CERT_DestroyCertificate(cert); + return false; + } + + DCHECK(cert->derCert.len); + // XXX copied from X509Certificate::GetDEREncoded + der_cert->clear(); + der_cert->append(reinterpret_cast<char*>(cert->derCert.data), + cert->derCert.len); + CERT_DestroyCertificate(cert); + return true; +} + +#if defined(USE_NSS) || defined(OS_IOS) +// Callback for CERT_DecodeCertPackage(), used in +// CreateOSCertHandlesFromBytes(). +SECStatus PR_CALLBACK CollectCertsCallback(void* arg, + SECItem** certs, + int num_certs) { + X509Certificate::OSCertHandles* results = + reinterpret_cast<X509Certificate::OSCertHandles*>(arg); + + for (int i = 0; i < num_certs; ++i) { + X509Certificate::OSCertHandle handle = + X509Certificate::CreateOSCertHandleFromBytes( + reinterpret_cast<char*>(certs[i]->data), certs[i]->len); + if (handle) + results->push_back(handle); + } + + return SECSuccess; +} + +typedef scoped_ptr_malloc< + CERTName, + crypto::NSSDestroyer<CERTName, CERT_DestroyName> > ScopedCERTName; + +// Create a new CERTName object from its encoded representation. +// |arena| is the allocation pool to use. +// |data| points to a DER-encoded X.509 DistinguishedName. +// Return a new CERTName pointer on success, or NULL. +CERTName* CreateCertNameFromEncoded(PLArenaPool* arena, + const base::StringPiece& data) { + if (!arena) + return NULL; + + ScopedCERTName name(PORT_ArenaZNew(arena, CERTName)); + if (!name.get()) + return NULL; + + SECItem item; + item.len = static_cast<unsigned int>(data.length()); + item.data = reinterpret_cast<unsigned char*>( + const_cast<char*>(data.data())); + + SECStatus rv = SEC_ASN1DecodeItem( + arena, name.get(), SEC_ASN1_GET(CERT_NameTemplate), &item); + if (rv != SECSuccess) + return NULL; + + return name.release(); +} + +#endif // defined(USE_NSS) || defined(OS_IOS) + +} // namespace + +namespace x509_util { + +CERTCertificate* CreateSelfSignedCert( + SECKEYPublicKey* public_key, + SECKEYPrivateKey* private_key, + const std::string& subject, + uint32 serial_number, + base::Time not_valid_before, + base::Time not_valid_after) { + CERTCertificate* cert = CreateCertificate(public_key, + subject, + serial_number, + not_valid_before, + not_valid_after); + if (!cert) + return NULL; + + if (!SignCertificate(cert, private_key)) { + CERT_DestroyCertificate(cert); + return NULL; + } + + return cert; +} + +bool IsSupportedValidityRange(base::Time not_valid_before, + base::Time not_valid_after) { + CERTValidity* validity = CERT_CreateValidity( + crypto::BaseTimeToPRTime(not_valid_before), + crypto::BaseTimeToPRTime(not_valid_after)); + + if (!validity) + return false; + + CERT_DestroyValidity(validity); + return true; +} + +bool CreateDomainBoundCertEC( + crypto::ECPrivateKey* key, + const std::string& domain, + uint32 serial_number, + base::Time not_valid_before, + base::Time not_valid_after, + std::string* der_cert) { + DCHECK(key); + return CreateDomainBoundCertInternal(key->public_key(), + key->key(), + domain, + serial_number, + not_valid_before, + not_valid_after, + der_cert); +} + +#if defined(USE_NSS) || defined(OS_IOS) +void ParsePrincipal(CERTName* name, CertPrincipal* principal) { + typedef char* (*CERTGetNameFunc)(CERTName* name); + + // TODO(jcampan): add business_category and serial_number. + // TODO(wtc): NSS has the CERT_GetOrgName, CERT_GetOrgUnitName, and + // CERT_GetDomainComponentName functions, but they return only the most + // general (the first) RDN. NSS doesn't have a function for the street + // address. + static const SECOidTag kOIDs[] = { + SEC_OID_AVA_STREET_ADDRESS, + SEC_OID_AVA_ORGANIZATION_NAME, + SEC_OID_AVA_ORGANIZATIONAL_UNIT_NAME, + SEC_OID_AVA_DC }; + + std::vector<std::string>* values[] = { + &principal->street_addresses, + &principal->organization_names, + &principal->organization_unit_names, + &principal->domain_components }; + DCHECK_EQ(arraysize(kOIDs), arraysize(values)); + + CERTRDN** rdns = name->rdns; + for (size_t rdn = 0; rdns[rdn]; ++rdn) { + CERTAVA** avas = rdns[rdn]->avas; + for (size_t pair = 0; avas[pair] != 0; ++pair) { + SECOidTag tag = CERT_GetAVATag(avas[pair]); + for (size_t oid = 0; oid < arraysize(kOIDs); ++oid) { + if (kOIDs[oid] == tag) { + SECItem* decode_item = CERT_DecodeAVAValue(&avas[pair]->value); + if (!decode_item) + break; + // TODO(wtc): Pass decode_item to CERT_RFC1485_EscapeAndQuote. + std::string value(reinterpret_cast<char*>(decode_item->data), + decode_item->len); + values[oid]->push_back(value); + SECITEM_FreeItem(decode_item, PR_TRUE); + break; + } + } + } + } + + // Get CN, L, S, and C. + CERTGetNameFunc get_name_funcs[4] = { + CERT_GetCommonName, CERT_GetLocalityName, + CERT_GetStateName, CERT_GetCountryName }; + std::string* single_values[4] = { + &principal->common_name, &principal->locality_name, + &principal->state_or_province_name, &principal->country_name }; + for (size_t i = 0; i < arraysize(get_name_funcs); ++i) { + char* value = get_name_funcs[i](name); + if (value) { + single_values[i]->assign(value); + PORT_Free(value); + } + } +} + +void ParseDate(const SECItem* der_date, base::Time* result) { + PRTime prtime; + SECStatus rv = DER_DecodeTimeChoice(&prtime, der_date); + DCHECK_EQ(SECSuccess, rv); + *result = crypto::PRTimeToBaseTime(prtime); +} + +std::string ParseSerialNumber(const CERTCertificate* certificate) { + return std::string(reinterpret_cast<char*>(certificate->serialNumber.data), + certificate->serialNumber.len); +} + +void GetSubjectAltName(CERTCertificate* cert_handle, + std::vector<std::string>* dns_names, + std::vector<std::string>* ip_addrs) { + if (dns_names) + dns_names->clear(); + if (ip_addrs) + ip_addrs->clear(); + + SECItem alt_name; + SECStatus rv = CERT_FindCertExtension(cert_handle, + SEC_OID_X509_SUBJECT_ALT_NAME, + &alt_name); + if (rv != SECSuccess) + return; + + PLArenaPool* arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); + DCHECK(arena != NULL); + + CERTGeneralName* alt_name_list; + alt_name_list = CERT_DecodeAltNameExtension(arena, &alt_name); + SECITEM_FreeItem(&alt_name, PR_FALSE); + + CERTGeneralName* name = alt_name_list; + while (name) { + // DNSName and IPAddress are encoded as IA5String and OCTET STRINGs + // respectively, both of which can be byte copied from + // SECItemType::data into the appropriate output vector. + if (dns_names && name->type == certDNSName) { + dns_names->push_back(std::string( + reinterpret_cast<char*>(name->name.other.data), + name->name.other.len)); + } else if (ip_addrs && name->type == certIPAddress) { + ip_addrs->push_back(std::string( + reinterpret_cast<char*>(name->name.other.data), + name->name.other.len)); + } + name = CERT_GetNextGeneralName(name); + if (name == alt_name_list) + break; + } + PORT_FreeArena(arena, PR_FALSE); +} + +X509Certificate::OSCertHandles CreateOSCertHandlesFromBytes( + const char* data, + int length, + X509Certificate::Format format) { + X509Certificate::OSCertHandles results; + if (length < 0) + return results; + + crypto::EnsureNSSInit(); + + if (!NSS_IsInitialized()) + return results; + + switch (format) { + case X509Certificate::FORMAT_SINGLE_CERTIFICATE: { + X509Certificate::OSCertHandle handle = + X509Certificate::CreateOSCertHandleFromBytes(data, length); + if (handle) + results.push_back(handle); + break; + } + case X509Certificate::FORMAT_PKCS7: { + // Make a copy since CERT_DecodeCertPackage may modify it + std::vector<char> data_copy(data, data + length); + + SECStatus result = CERT_DecodeCertPackage(&data_copy[0], + length, CollectCertsCallback, &results); + if (result != SECSuccess) + results.clear(); + break; + } + default: + NOTREACHED() << "Certificate format " << format << " unimplemented"; + break; + } + + return results; +} + +X509Certificate::OSCertHandle ReadOSCertHandleFromPickle( + PickleIterator* pickle_iter) { + const char* data; + int length; + if (!pickle_iter->ReadData(&data, &length)) + return NULL; + + return X509Certificate::CreateOSCertHandleFromBytes(data, length); +} + +void GetPublicKeyInfo(CERTCertificate* handle, + size_t* size_bits, + X509Certificate::PublicKeyType* type) { + // Since we might fail, set the output parameters to default values first. + *type = X509Certificate::kPublicKeyTypeUnknown; + *size_bits = 0; + + crypto::ScopedSECKEYPublicKey key(CERT_ExtractPublicKey(handle)); + if (!key.get()) + return; + + *size_bits = SECKEY_PublicKeyStrengthInBits(key.get()); + + switch (key->keyType) { + case rsaKey: + *type = X509Certificate::kPublicKeyTypeRSA; + break; + case dsaKey: + *type = X509Certificate::kPublicKeyTypeDSA; + break; + case dhKey: + *type = X509Certificate::kPublicKeyTypeDH; + break; + case ecKey: + *type = X509Certificate::kPublicKeyTypeECDSA; + break; + default: + *type = X509Certificate::kPublicKeyTypeUnknown; + *size_bits = 0; + break; + } +} + +bool GetIssuersFromEncodedList( + const std::vector<std::string>& encoded_issuers, + PLArenaPool* arena, + std::vector<CERTName*>* out) { + std::vector<CERTName*> result; + for (size_t n = 0; n < encoded_issuers.size(); ++n) { + CERTName* name = CreateCertNameFromEncoded(arena, encoded_issuers[n]); + if (name != NULL) + result.push_back(name); + } + + if (result.size() == encoded_issuers.size()) { + out->swap(result); + return true; + } + + for (size_t n = 0; n < result.size(); ++n) + CERT_DestroyName(result[n]); + return false; +} + + +bool IsCertificateIssuedBy(const std::vector<CERTCertificate*>& cert_chain, + const std::vector<CERTName*>& valid_issuers) { + for (size_t n = 0; n < cert_chain.size(); ++n) { + CERTName* cert_issuer = &cert_chain[n]->issuer; + for (size_t i = 0; i < valid_issuers.size(); ++i) { + if (CERT_CompareName(valid_issuers[i], cert_issuer) == SECEqual) + return true; + } + } + return false; +} + +#endif // defined(USE_NSS) || defined(OS_IOS) + +} // namespace x509_util + +} // namespace net diff --git a/net/cert/x509_util_nss.h b/net/cert/x509_util_nss.h new file mode 100644 index 0000000..66c634c --- /dev/null +++ b/net/cert/x509_util_nss.h @@ -0,0 +1,101 @@ +// Copyright (c) 2011 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. + +#ifndef NET_CERT_X509_UTIL_NSS_H_ +#define NET_CERT_X509_UTIL_NSS_H_ + +#include <string> +#include <vector> + +#include "base/time.h" +#include "net/cert/x509_certificate.h" + +class PickleIterator; + +typedef struct CERTCertificateStr CERTCertificate; +typedef struct CERTNameStr CERTName; +typedef struct PLArenaPool PLArenaPool; +typedef struct SECKEYPrivateKeyStr SECKEYPrivateKey; +typedef struct SECItemStr SECItem; +typedef struct SECKEYPublicKeyStr SECKEYPublicKey; + +namespace net { + +namespace x509_util { + +// Creates a self-signed certificate containing |public_key|. Subject, serial +// number and validity period are given as parameters. The certificate is +// signed by |private_key|. The hashing algorithm for the signature is SHA-1. +// |subject| is a distinguished name defined in RFC4514. +CERTCertificate* CreateSelfSignedCert( + SECKEYPublicKey* public_key, + SECKEYPrivateKey* private_key, + const std::string& subject, + uint32 serial_number, + base::Time not_valid_before, + base::Time not_valid_after); + +#if defined(USE_NSS) || defined(OS_IOS) +// Parses the Principal attribute from |name| and outputs the result in +// |principal|. +void ParsePrincipal(CERTName* name, + CertPrincipal* principal); + +// Parses the date from |der_date| and outputs the result in |result|. +void ParseDate(const SECItem* der_date, base::Time* result); + +// Parses the serial number from |certificate|. +std::string ParseSerialNumber(const CERTCertificate* certificate); + +// Gets the subjectAltName extension field from the certificate, if any. +void GetSubjectAltName(CERTCertificate* cert_handle, + std::vector<std::string>* dns_names, + std::vector<std::string>* ip_addrs); + +// Creates all possible OS certificate handles from |data| encoded in a specific +// |format|. Returns an empty collection on failure. +X509Certificate::OSCertHandles CreateOSCertHandlesFromBytes( + const char* data, + int length, + X509Certificate::Format format); + +// Reads a single certificate from |pickle_iter| and returns a platform-specific +// certificate handle. Returns an invalid handle, NULL, on failure. +X509Certificate::OSCertHandle ReadOSCertHandleFromPickle( + PickleIterator* pickle_iter); + +// Sets |*size_bits| to be the length of the public key in bits, and sets +// |*type| to one of the |PublicKeyType| values. In case of +// |kPublicKeyTypeUnknown|, |*size_bits| will be set to 0. +void GetPublicKeyInfo(CERTCertificate* handle, + size_t* size_bits, + X509Certificate::PublicKeyType* type); + +// Create a list of CERTName objects from a list of DER-encoded X.509 +// DistinguishedName items. All objects are created in a given arena. +// |encoded_issuers| is the list of encoded DNs. +// |arena| is the arena used for all allocations. +// |out| will receive the result list on success. +// Return true on success. On failure, the caller must free the +// intermediate CERTName objects pushed to |out|. +bool GetIssuersFromEncodedList( + const std::vector<std::string>& issuers, + PLArenaPool* arena, + std::vector<CERTName*>* out); + +// Returns true iff a certificate is issued by any of the issuers listed +// by name in |valid_issuers|. +// |cert_chain| is the certificate's chain. +// |valid_issuers| is a list of strings, where each string contains +// a DER-encoded X.509 Distinguished Name. +bool IsCertificateIssuedBy(const std::vector<CERTCertificate*>& cert_chain, + const std::vector<CERTName*>& valid_issuers); + +#endif // defined(USE_NSS) || defined(OS_IOS) + +} // namespace x509_util + +} // namespace net + +#endif // NET_CERT_X509_UTIL_NSS_H_ diff --git a/net/cert/x509_util_nss_unittest.cc b/net/cert/x509_util_nss_unittest.cc new file mode 100644 index 0000000..968cc14 --- /dev/null +++ b/net/cert/x509_util_nss_unittest.cc @@ -0,0 +1,171 @@ +// Copyright (c) 2012 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 "net/cert/x509_util.h" +#include "net/cert/x509_util_nss.h" + +#include <cert.h> +#include <secoid.h> + +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "crypto/ec_private_key.h" +#include "crypto/scoped_nss_types.h" +#include "crypto/signature_verifier.h" +#include "net/cert/x509_certificate.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace net { + +namespace { + +CERTCertificate* CreateNSSCertHandleFromBytes(const char* data, size_t length) { + SECItem der_cert; + der_cert.data = reinterpret_cast<unsigned char*>(const_cast<char*>(data)); + der_cert.len = length; + der_cert.type = siDERCertBuffer; + + // Parse into a certificate structure. + return CERT_NewTempCertificate(CERT_GetDefaultCertDB(), &der_cert, NULL, + PR_FALSE, PR_TRUE); +} + +#if !defined(OS_WIN) && !defined(OS_MACOSX) +void VerifyCertificateSignature(const std::string& der_cert, + const std::vector<uint8>& der_spki) { + crypto::ScopedPLArenaPool arena(PORT_NewArena(DER_DEFAULT_CHUNKSIZE)); + + CERTSignedData sd; + memset(&sd, 0, sizeof(sd)); + + SECItem der_cert_item = { + siDERCertBuffer, + reinterpret_cast<unsigned char*>(const_cast<char*>(der_cert.data())), + static_cast<unsigned int>(der_cert.size()) + }; + SECStatus rv = SEC_ASN1DecodeItem(arena.get(), &sd, + SEC_ASN1_GET(CERT_SignedDataTemplate), + &der_cert_item); + ASSERT_EQ(SECSuccess, rv); + + // The CERTSignedData.signatureAlgorithm is decoded, but SignatureVerifier + // wants the DER encoded form, so re-encode it again. + SECItem* signature_algorithm = SEC_ASN1EncodeItem( + arena.get(), + NULL, + &sd.signatureAlgorithm, + SEC_ASN1_GET(SECOID_AlgorithmIDTemplate)); + ASSERT_TRUE(signature_algorithm); + + crypto::SignatureVerifier verifier; + bool ok = verifier.VerifyInit( + signature_algorithm->data, + signature_algorithm->len, + sd.signature.data, + sd.signature.len / 8, // Signature is a BIT STRING, convert to bytes. + &der_spki[0], + der_spki.size()); + + ASSERT_TRUE(ok); + verifier.VerifyUpdate(sd.data.data, + sd.data.len); + + ok = verifier.VerifyFinal(); + EXPECT_TRUE(ok); +} +#endif // !defined(OS_WIN) && !defined(OS_MACOSX) + +void VerifyDomainBoundCert(const std::string& domain, + const std::string& der_cert) { + // Origin Bound Cert OID. + static const char oid_string[] = "1.3.6.1.4.1.11129.2.1.6"; + + // Create object neccessary for extension lookup call. + SECItem extension_object = { + siAsciiString, + (unsigned char*)domain.data(), + static_cast<unsigned int>(domain.size()) + }; + + // IA5Encode and arena allocate SECItem. + PLArenaPool* arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); + SECItem* expected = SEC_ASN1EncodeItem(arena, + NULL, + &extension_object, + SEC_ASN1_GET(SEC_IA5StringTemplate)); + + ASSERT_NE(static_cast<SECItem*>(NULL), expected); + + // Create OID SECItem. + SECItem ob_cert_oid = { siDEROID, NULL, 0 }; + SECStatus ok = SEC_StringToOID(arena, &ob_cert_oid, + oid_string, 0); + + ASSERT_EQ(SECSuccess, ok); + + SECOidTag ob_cert_oid_tag = SECOID_FindOIDTag(&ob_cert_oid); + + ASSERT_NE(SEC_OID_UNKNOWN, ob_cert_oid_tag); + + // This test is run on Mac and Win where X509Certificate::os_cert_handle isn't + // an NSS type, so we have to manually create a NSS certificate object so we + // can use CERT_FindCertExtension. We also check the subject and validity + // times using NSS since X509Certificate will fail with EC certs on OSX 10.5 + // (http://crbug.com/101231). + CERTCertificate* nss_cert = CreateNSSCertHandleFromBytes( + der_cert.data(), der_cert.size()); + + char* common_name = CERT_GetCommonName(&nss_cert->subject); + ASSERT_TRUE(common_name); + EXPECT_STREQ("anonymous.invalid", common_name); + PORT_Free(common_name); + EXPECT_EQ(SECSuccess, CERT_CertTimesValid(nss_cert)); + + // Lookup Origin Bound Cert extension in generated cert. + SECItem actual = { siBuffer, NULL, 0 }; + ok = CERT_FindCertExtension(nss_cert, + ob_cert_oid_tag, + &actual); + CERT_DestroyCertificate(nss_cert); + ASSERT_EQ(SECSuccess, ok); + + // Compare expected and actual extension values. + PRBool result = SECITEM_ItemsAreEqual(expected, &actual); + ASSERT_TRUE(result); + + // Do Cleanup. + SECITEM_FreeItem(&actual, PR_FALSE); + PORT_FreeArena(arena, PR_FALSE); +} + +} // namespace + +// This test creates a domain-bound cert from an EC private key and +// then verifies the content of the certificate. +TEST(X509UtilNSSTest, CreateDomainBoundCertEC) { + // Create a sample ASCII weborigin. + std::string domain = "weborigin.com"; + base::Time now = base::Time::Now(); + + scoped_ptr<crypto::ECPrivateKey> private_key( + crypto::ECPrivateKey::Create()); + std::string der_cert; + ASSERT_TRUE(x509_util::CreateDomainBoundCertEC( + private_key.get(), + domain, 1, + now, + now + base::TimeDelta::FromDays(1), + &der_cert)); + + VerifyDomainBoundCert(domain, der_cert); + +#if !defined(OS_WIN) && !defined(OS_MACOSX) + // signature_verifier_win and signature_verifier_mac can't handle EC certs. + std::vector<uint8> spki; + ASSERT_TRUE(private_key->ExportPublicKey(&spki)); + VerifyCertificateSignature(der_cert, spki); +#endif +} + +} // namespace net diff --git a/net/cert/x509_util_openssl.cc b/net/cert/x509_util_openssl.cc new file mode 100644 index 0000000..5853b64 --- /dev/null +++ b/net/cert/x509_util_openssl.cc @@ -0,0 +1,119 @@ +// Copyright (c) 2012 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 "net/cert/x509_util.h" +#include "net/cert/x509_util_openssl.h" + +#include <algorithm> + +#include "base/logging.h" +#include "base/string_piece.h" +#include "net/cert/x509_cert_types.h" + +namespace net { + +namespace x509_util { + +bool IsSupportedValidityRange(base::Time not_valid_before, + base::Time not_valid_after) { + if (not_valid_before > not_valid_after) + return false; + + // The validity field of a certificate can only encode years 1-9999. + + // Compute the base::Time values corresponding to Jan 1st,0001 and + // Jan 1st, 10000 respectively. Done by using the pre-computed numbers + // of days between these dates and the Unix epoch, i.e. Jan 1st, 1970, + // using the following Python script: + // + // from datetime import date as D + // print (D(1970,1,1)-D(1,1,1)) # -> 719162 days + // print (D(9999,12,31)-D(1970,1,1)) # -> 2932896 days + // + // Note: This ignores leap seconds, but should be enough in practice. + // + const int64 kDaysFromYear0001ToUnixEpoch = 719162; + const int64 kDaysFromUnixEpochToYear10000 = 2932896 + 1; + const base::Time kEpoch = base::Time::UnixEpoch(); + const base::Time kYear0001 = kEpoch - + base::TimeDelta::FromDays(kDaysFromYear0001ToUnixEpoch); + const base::Time kYear10000 = kEpoch + + base::TimeDelta::FromDays(kDaysFromUnixEpochToYear10000); + + if (not_valid_before < kYear0001 || not_valid_before >= kYear10000 || + not_valid_after < kYear0001 || not_valid_after >= kYear10000) + return false; + + return true; +} + +bool CreateDomainBoundCertEC( + crypto::ECPrivateKey* key, + const std::string& domain, + uint32 serial_number, + base::Time not_valid_before, + base::Time not_valid_after, + std::string* der_cert) { + NOTIMPLEMENTED(); + return false; +} + +bool ParsePrincipalKeyAndValueByIndex(X509_NAME* name, + int index, + std::string* key, + std::string* value) { + X509_NAME_ENTRY* entry = X509_NAME_get_entry(name, index); + if (!entry) + return false; + + if (key) { + ASN1_OBJECT* object = X509_NAME_ENTRY_get_object(entry); + key->assign(OBJ_nid2sn(OBJ_obj2nid(object))); + } + + ASN1_STRING* data = X509_NAME_ENTRY_get_data(entry); + if (!data) + return false; + + unsigned char* buf = NULL; + int len = ASN1_STRING_to_UTF8(&buf, data); + if (len <= 0) + return false; + + value->assign(reinterpret_cast<const char*>(buf), len); + OPENSSL_free(buf); + return true; +} + +bool ParsePrincipalValueByIndex(X509_NAME* name, + int index, + std::string* value) { + return ParsePrincipalKeyAndValueByIndex(name, index, NULL, value); +} + +bool ParsePrincipalValueByNID(X509_NAME* name, int nid, std::string* value) { + int index = X509_NAME_get_index_by_NID(name, nid, -1); + if (index < 0) + return false; + + return ParsePrincipalValueByIndex(name, index, value); +} + +bool ParseDate(ASN1_TIME* x509_time, base::Time* time) { + if (!x509_time || + (x509_time->type != V_ASN1_UTCTIME && + x509_time->type != V_ASN1_GENERALIZEDTIME)) + return false; + + base::StringPiece str_date(reinterpret_cast<const char*>(x509_time->data), + x509_time->length); + + CertDateFormat format = x509_time->type == V_ASN1_UTCTIME ? + CERT_DATE_FORMAT_UTC_TIME : CERT_DATE_FORMAT_GENERALIZED_TIME; + return ParseCertificateDate(str_date, format, time); +} + +} // namespace x509_util + +} // namespace net diff --git a/net/cert/x509_util_openssl.h b/net/cert/x509_util_openssl.h new file mode 100644 index 0000000..ec45380 --- /dev/null +++ b/net/cert/x509_util_openssl.h @@ -0,0 +1,45 @@ +// Copyright (c) 2011 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. + +#ifndef NET_CERT_X509_UTIL_OPENSSL_H_ +#define NET_CERT_X509_UTIL_OPENSSL_H_ + +#include <openssl/asn1.h> +#include <openssl/x509v3.h> + +#include <string> +#include <vector> + +#include "net/base/net_export.h" + +namespace base { +class Time; +} // namespace base + +namespace net { + +// A collection of helper functions to fetch data from OpenSSL X509 certificates +// into more convenient std / base datatypes. +namespace x509_util { + +bool NET_EXPORT ParsePrincipalKeyAndValueByIndex(X509_NAME* name, + int index, + std::string* key, + std::string* value); + +bool NET_EXPORT ParsePrincipalValueByIndex(X509_NAME* name, + int index, + std::string* value); + +bool NET_EXPORT ParsePrincipalValueByNID(X509_NAME* name, + int nid, + std::string* value); + +bool NET_EXPORT ParseDate(ASN1_TIME* x509_time, base::Time* time); + +} // namespace x509_util + +} // namespace net + +#endif // NET_CERT_X509_UTIL_OPENSSL_H_ diff --git a/net/cert/x509_util_openssl_unittest.cc b/net/cert/x509_util_openssl_unittest.cc new file mode 100644 index 0000000..f237602 --- /dev/null +++ b/net/cert/x509_util_openssl_unittest.cc @@ -0,0 +1,57 @@ +// Copyright (c) 2012 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 "base/memory/scoped_ptr.h" +#include "crypto/ec_private_key.h" +#include "net/cert/x509_util.h" +#include "net/cert/x509_util_openssl.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace net { + +TEST(X509UtilOpenSSLTest, IsSupportedValidityRange) { + base::Time now = base::Time::Now(); + EXPECT_TRUE(x509_util::IsSupportedValidityRange(now, now)); + EXPECT_FALSE(x509_util::IsSupportedValidityRange( + now, now - base::TimeDelta::FromSeconds(1))); + + // See x509_util_openssl.cc to see how these were computed. + const int64 kDaysFromYear0001ToUnixEpoch = 719162; + const int64 kDaysFromUnixEpochToYear10000 = 2932896 + 1; + + // When computing too_old / too_late, add one day to account for + // possible leap seconds. + base::Time too_old = base::Time::UnixEpoch() - + base::TimeDelta::FromDays(kDaysFromYear0001ToUnixEpoch + 1); + + base::Time too_late = base::Time::UnixEpoch() + + base::TimeDelta::FromDays(kDaysFromUnixEpochToYear10000 + 1); + + EXPECT_FALSE(x509_util::IsSupportedValidityRange(too_old, too_old)); + EXPECT_FALSE(x509_util::IsSupportedValidityRange(too_old, now)); + + EXPECT_FALSE(x509_util::IsSupportedValidityRange(now, too_late)); + EXPECT_FALSE(x509_util::IsSupportedValidityRange(too_late, too_late)); +} + +// For OpenSSL, x509_util::CreateDomainBoundCertEC() is not yet implemented +// and should return false. This unit test ensures that a stub implementation +// is present. +TEST(X509UtilOpenSSLTest, CreateDomainBoundCertNotImplemented) { + std::string domain = "weborigin.com"; + base::Time now = base::Time::Now(); + scoped_ptr<crypto::ECPrivateKey> private_key( + crypto::ECPrivateKey::Create()); + std::string der_cert; + EXPECT_FALSE(x509_util::CreateDomainBoundCertEC( + private_key.get(), + domain, 1, + now, + now + base::TimeDelta::FromDays(1), + &der_cert)); + EXPECT_TRUE(der_cert.empty()); + +} + +} // namespace net diff --git a/net/cert/x509_util_unittest.cc b/net/cert/x509_util_unittest.cc new file mode 100644 index 0000000..ee42f1c --- /dev/null +++ b/net/cert/x509_util_unittest.cc @@ -0,0 +1,54 @@ +// Copyright (c) 2012 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 "net/cert/x509_util.h" + +#include <algorithm> + +#include "base/time.h" +#include "net/cert/x509_certificate.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace net { + +namespace x509_util { + +TEST(X509UtilTest, SortClientCertificates) { + CertificateList certs; + + const base::Time now = base::Time::Now(); + const base::TimeDelta five_days = base::TimeDelta::FromDays(5); + + certs.push_back(scoped_refptr<X509Certificate>(NULL)); + certs.push_back(new X509Certificate( + "expired", "expired", + base::Time::UnixEpoch(), base::Time::UnixEpoch())); + certs.push_back(new X509Certificate( + "not yet valid", "not yet valid", + base::Time::Max(), base::Time::Max())); + certs.push_back(new X509Certificate( + "older cert", "older cert", + now - five_days, now + five_days)); + certs.push_back(scoped_refptr<X509Certificate>(NULL)); + certs.push_back(new X509Certificate( + "newer cert", "newer cert", + now - base::TimeDelta::FromDays(3), now + five_days)); + + std::sort(certs.begin(), certs.end(), ClientCertSorter()); + + ASSERT_TRUE(certs[0].get()); + EXPECT_EQ("newer cert", certs[0]->subject().common_name); + ASSERT_TRUE(certs[1].get()); + EXPECT_EQ("older cert", certs[1]->subject().common_name); + ASSERT_TRUE(certs[2].get()); + EXPECT_EQ("not yet valid", certs[2]->subject().common_name); + ASSERT_TRUE(certs[3].get()); + EXPECT_EQ("expired", certs[3]->subject().common_name); + ASSERT_FALSE(certs[4].get()); + ASSERT_FALSE(certs[5].get()); +} + +} // namespace x509_util + +} // namespace net |