// 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_TRANSPORT_SECURITY_STATE_H_ #define NET_BASE_TRANSPORT_SECURITY_STATE_H_ #pragma once #include #include #include #include "base/basictypes.h" #include "base/gtest_prod_util.h" #include "base/threading/non_thread_safe.h" #include "base/time.h" #include "net/base/net_export.h" #include "net/base/x509_certificate.h" #include "net/base/x509_cert_types.h" namespace net { class SSLInfo; typedef std::vector FingerprintVector; // TransportSecurityState // // Tracks which hosts have enabled *-Transport-Security. This object manages // the in-memory store. A separate object must register itself with this object // in order to persist the state to disk. class NET_EXPORT TransportSecurityState : NON_EXPORTED_BASE(public base::NonThreadSafe) { public: // If non-empty, |hsts_hosts| is a JSON-formatted string to treat as if it // were a built-in entry (same format as persisted metadata in the // TransportSecurityState file). explicit TransportSecurityState(const std::string& hsts_hosts); ~TransportSecurityState(); // A DomainState is the information that we persist about a given domain. struct NET_EXPORT DomainState { enum Mode { // Strict mode implies: // * We generate internal redirects from HTTP -> HTTPS. // * Certificate issues are fatal. MODE_STRICT = 0, // This used to be opportunistic HTTPS, but we removed support. MODE_OPPORTUNISTIC_REMOVED = 1, // SPDY_ONLY (aka X-Bodge-Transport-Security) is a hopefully temporary // measure. It implies: // * We'll request HTTP URLs over HTTPS iff we have SPDY support. // * Certificate issues are fatal. MODE_SPDY_ONLY = 2, // Pinning means there are no HTTP -> HTTPS redirects, however certificate // issues are still fatal and there may be public key pins. MODE_PINNING_ONLY = 3, }; DomainState(); ~DomainState(); // Takes a set of SubjectPublicKeyInfo |hashes| and returns true if: // 1) |bad_preloaded_spki_hashes| does not intersect |hashes|; AND // 2) Both |preloaded_spki_hashes| and |dynamic_spki_hashes| are empty // or at least one of them intersects |hashes|. // // |{dynamic,preloaded}_spki_hashes| contain trustworthy public key // hashes, any one of which is sufficient to validate the certificate // chain in question. The public keys could be of a root CA, intermediate // CA, or leaf certificate, depending on the security vs. disaster // recovery tradeoff selected. (Pinning only to leaf certifiates increases // security because you no longer trust any CAs, but it hampers disaster // recovery because you can't just get a new certificate signed by the // CA.) // // |bad_preloaded_spki_hashes| contains public keys that we don't want to // trust. bool IsChainOfPublicKeysPermitted(const FingerprintVector& hashes); // Returns true if |this| describes a more strict policy than |other|. // Used to see if a dynamic DomainState should override a preloaded one. bool IsMoreStrict(const DomainState& other); // ShouldRedirectHTTPToHTTPS returns true iff, given the |mode| of this // DomainState, HTTP requests should be internally redirected to HTTPS. bool ShouldRedirectHTTPToHTTPS() const; Mode mode; base::Time created; // when this host entry was first created base::Time expiry; // the absolute time (UTC) when this record expires bool include_subdomains; // subdomains included? // Optional; hashes of preloaded "pinned" SubjectPublicKeyInfos. Unless // both are empty, at least one of |preloaded_spki_hashes| and // |dynamic_spki_hashes| MUST intersect with the set of SPKIs in the TLS // server's certificate chain. // // |dynamic_spki_hashes| take precedence over |preloaded_spki_hashes|. // That is, when performing pin validation, first check dynamic and then // check preloaded. FingerprintVector preloaded_spki_hashes; // Optional; hashes of dynamically pinned SubjectPublicKeyInfos. (They // could be set e.g. by an HTTP header or by a superfluous certificate.) FingerprintVector dynamic_spki_hashes; // The absolute time (UTC) when the |dynamic_spki_hashes| expire. base::Time dynamic_spki_hashes_expiry; // The max-age directive of the Public-Key-Pins header as parsed. Do not // persist this; it is only for testing. TODO(palmer): Therefore, get rid // of it and find a better way to test. int max_age; // Optional; hashes of preloaded known-bad SubjectPublicKeyInfos which // MUST NOT intersect with the set of SPKIs in the TLS server's // certificate chain. FingerprintVector bad_preloaded_spki_hashes; // The following members are not valid when stored in |enabled_hosts_|. bool preloaded; // is this a preloaded entry? std::string domain; // the domain which matched }; class Delegate { public: // This function may not block and may be called with internal locks held. // Thus it must not reenter the TransportSecurityState object. virtual void StateIsDirty(TransportSecurityState* state) = 0; protected: virtual ~Delegate() {} }; void SetDelegate(Delegate* delegate); // Enable TransportSecurity for |host|. void EnableHost(const std::string& host, const DomainState& state); // Delete any entry for |host|. If |host| doesn't have an exact entry then no // action is taken. Returns true iff an entry was deleted. bool DeleteHost(const std::string& host); // Returns true if |host| has TransportSecurity enabled. Before operating // on this result, consult |result->mode|, as the expected behavior of // TransportSecurity can significantly differ based on mode. // // If |sni_available| is true, searches the preloads defined for SNI-using // hosts as well as the usual preload list. // // Note that |*result| is always overwritten on every call. // TODO(palmer): Only update |*result| on success. bool GetDomainState(DomainState* result, const std::string& host, bool sni_available); // Returns true if there are any certificates pinned for |host|. // If so, updates the |preloaded_spki_hashes|, |dynamic_spki_hashes|, and // |bad_preloaded_spki_hashes| fields of |*result| with the pins. // // Note that |*result| is always overwritten on every call, regardless of // whether or not pins are enabled. // // If |sni_available| is true, searches the preloads defined for SNI-using // hosts as well as the usual preload list. // // TODO(palmer): Only update |*result| if pins exist. bool HasPinsForHost(DomainState* result, const std::string& host, bool sni_available); // Returns true and updates |*result| if there is any |DomainState| // metadata for |host| in the local TransportSecurityState database; // returns false otherwise. TODO(palmer): Unlike the other // TransportSecurityState lookup functions in this class (e.g // |HasPinsForHost|, |GetDomainState|), |*result| is updated iff metadata // is found. The other functions are buggy and will be fixed to behave // like this one. // // If |sni_available| is true, searches the preloads defined for SNI-using // hosts as well as the usual preload list. bool HasMetadata(DomainState* result, const std::string& host, bool sni_available); // Returns true if we have a preloaded certificate pin for the |host| and if // its set of required certificates is the set we expect for Google // properties. // // If |sni_available| is true, searches the preloads defined for SNI-using // hosts as well as the usual preload list. // // Note that like HasMetadata, if |host| matches both an exact entry and is a // subdomain of another entry, the exact match determines the return value. static bool IsGooglePinnedProperty(const std::string& host, bool sni_available); // Reports UMA statistics upon public key pin failure. Reports only down to // the second-level domain of |host| (e.g. google.com if |host| is // mail.google.com), and only if |host| is a preloaded STS host. static void ReportUMAOnPinFailure(const std::string& host); // Parses |cert|'s Subject Public Key Info structure, hashes it, and writes // the hash into |spki_hash|. Returns true on parse success, false on // failure. static bool GetPublicKeyHash(const X509Certificate& cert, SHA1Fingerprint* spki_hash); // Decodes a pin string |value| (e.g. "sha1/hvfkN/qlp/zhXR3cuerq6jd2Z7g=") // and populates |out|. static bool ParsePin(const std::string& value, SHA1Fingerprint* out); // Deletes all records created since a given time. void DeleteSince(const base::Time& time); // Parses |value| as a Public-Key-Pins header. If successful, returns |true| // and updates the |dynamic_spki_hashes| and |dynamic_spki_hashes_expiry| // fields of |*state|; otherwise, returns |false| without updating |*state|. static bool ParsePinsHeader(const std::string& value, const SSLInfo& ssl_info, DomainState* state); // Returns |true| if |value| parses as a valid *-Transport-Security // header value. The values of max-age and and includeSubDomains are // returned in |max_age| and |include_subdomains|, respectively. The out // parameters are not modified if the function returns |false|. static bool ParseHeader(const std::string& value, int* max_age, bool* include_subdomains); // ParseSidePin attempts to parse a side pin from |side_info| which signs the // SubjectPublicKeyInfo in |leaf_spki|. A side pin is a way for a site to // sign their public key with a key that is offline but still controlled by // them. If successful, the hash of the public key used to sign |leaf_spki| // is put into |out_pub_key_hash|. static bool ParseSidePin(const base::StringPiece& leaf_spki, const base::StringPiece& side_info, FingerprintVector* out_pub_key_hash); bool Serialise(std::string* output); // Existing non-preloaded entries are cleared and repopulated from the // passed JSON string. bool LoadEntries(const std::string& state, bool* dirty); // The maximum number of seconds for which we'll cache an HSTS request. static const long int kMaxHSTSAgeSecs; private: FRIEND_TEST_ALL_PREFIXES(TransportSecurityStateTest, IsPreloaded); // If we have a callback configured, call it to let our serialiser know that // our state is dirty. void DirtyNotify(); bool IsPreloadedSTS(const std::string& canonicalized_host, bool sni_available, DomainState* out); static std::string CanonicalizeHost(const std::string& host); static bool Deserialise(const std::string& state, bool* dirty, std::map* out); // The set of hosts that have enabled TransportSecurity. The keys here // are SHA256(DNSForm(domain)) where DNSForm converts from dotted form // ('www.google.com') to the form used in DNS: "\x03www\x06google\x03com" std::map enabled_hosts_; // These hosts are extra rules to treat as built-in, passed in the // constructor (typically originating from the command line). std::map forced_hosts_; // Our delegate who gets notified when we are dirtied, or NULL. Delegate* delegate_; DISALLOW_COPY_AND_ASSIGN(TransportSecurityState); }; } // namespace net #endif // NET_BASE_TRANSPORT_SECURITY_STATE_H_