1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
|
// 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 <map>
#include <string>
#include <utility>
#include <vector>
#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;
// Tracks which hosts have enabled strict transport security and/or public
// key pins.
//
// This object manages the in-memory store. Register a Delegate with
// |SetDelegate| to persist the state to disk.
//
// HTTP strict transport security (HSTS) is defined in
// http://tools.ietf.org/html/ietf-websec-strict-transport-sec, and
// HTTP-based dynamic public key pinning (HPKP) is defined in
// http://tools.ietf.org/html/ietf-websec-key-pinning.
class NET_EXPORT TransportSecurityState
: NON_EXPORTED_BASE(public base::NonThreadSafe) {
public:
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() {}
};
TransportSecurityState();
~TransportSecurityState();
// A DomainState describes the transport security state (required upgrade
// to HTTPS, and/or any public key pins).
class NET_EXPORT DomainState {
public:
enum UpgradeMode {
// These numbers must match those in hsts_view.js, function modeToString.
MODE_FORCE_HTTPS = 0,
MODE_DEFAULT = 1,
};
DomainState();
~DomainState();
// Parses |value| as a Public-Key-Pins header. If successful, returns true
// and updates the |dynamic_spki_hashes| and |dynamic_spki_hashes_expiry|
// fields; otherwise, returns false without updating any fields.
// Interprets the max-age directive relative to |now|.
bool ParsePinsHeader(const base::Time& now,
const std::string& value,
const SSLInfo& ssl_info);
// Parses |value| as a Strict-Transport-Security header. If successful,
// returns true and updates the |upgrade_mode|, |upgrade_expiry| and
// |include_subdomains| fields; otherwise, returns false without updating
// any fields. Interprets the max-age directive relative to |now|.
bool ParseSTSHeader(const base::Time& now, const std::string& value);
// Takes a set of SubjectPublicKeyInfo |hashes| and returns true if:
// 1) |bad_static_spki_hashes| does not intersect |hashes|; AND
// 2) Both |static_spki_hashes| and |dynamic_spki_hashes| are empty
// or at least one of them intersects |hashes|.
//
// |{dynamic,static}_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_static_spki_hashes| contains public keys that we don't want to
// trust.
bool IsChainOfPublicKeysPermitted(const FingerprintVector& hashes) const;
// Returns true if any of the FingerprintVectors |static_spki_hashes|,
// |bad_static_spki_hashes|, or |dynamic_spki_hashes| contains any
// items.
bool HasPins() const;
// ShouldRedirectHTTPToHTTPS returns true iff, given the |mode| of this
// DomainState, HTTP requests should be internally redirected to HTTPS.
bool ShouldRedirectHTTPToHTTPS() const;
bool Equals(const DomainState& other) const;
UpgradeMode upgrade_mode;
// The absolute time (UTC) when this DomainState was first created.
//
// Static entries do not have a created time.
base::Time created;
// The absolute time (UTC) when the |upgrade_mode|, if set to
// UPGRADE_ALWAYS, downgrades to UPGRADE_NEVER.
base::Time upgrade_expiry;
// Are subdomains subject to this DomainState?
//
// TODO(palmer): Decide if we should have separate |pin_subdomains| and
// |upgrade_subdomains|. Alternately, and perhaps better, is to separate
// DomainState into UpgradeState and PinState (requiring also changing the
// serialization format?).
bool include_subdomains;
// Optional; hashes of static pinned SubjectPublicKeyInfos. Unless both
// are empty, at least one of |static_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 |static_spki_hashes|.
// That is, |IsChainOfPublicKeysPermitted| first checks dynamic pins and
// then checks static pins.
FingerprintVector static_spki_hashes;
// Optional; hashes of dynamically pinned SubjectPublicKeyInfos.
FingerprintVector dynamic_spki_hashes;
// The absolute time (UTC) when the |dynamic_spki_hashes| expire.
base::Time dynamic_spki_hashes_expiry;
// Optional; hashes of static known-bad SubjectPublicKeyInfos which
// MUST NOT intersect with the set of SPKIs in the TLS server's
// certificate chain.
FingerprintVector bad_static_spki_hashes;
// The following members are not valid when stored in |enabled_hosts_|:
// The domain which matched during a search for this DomainState entry.
// Updated by |GetDomainState| and |GetStaticDomainState|.
std::string domain;
};
class Iterator {
public:
explicit Iterator(const TransportSecurityState& state)
: iterator_(state.enabled_hosts_.begin()),
end_(state.enabled_hosts_.end()) {
}
~Iterator() {}
bool HasNext() const { return iterator_ != end_; }
void Advance() { ++iterator_; }
const std::string& hostname() const { return iterator_->first; }
const DomainState& domain_state() const { return iterator_->second; }
private:
std::map<std::string, DomainState>::const_iterator iterator_;
std::map<std::string, DomainState>::const_iterator end_;
};
// Assign a |Delegate| for persisting the transport security state. If
// |NULL|, state will not be persisted. Caller owns |delegate|.
void SetDelegate(Delegate* delegate);
// Enable TransportSecurity for |host|. |state| supercedes any previous
// state for the |host|, including static entries.
//
// The new state for |host| is persisted using the Delegate (if any).
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. Does not delete static entries. Returns true iff an
// entry was deleted.
//
// The new state for |host| is persisted using the Delegate (if any).
bool DeleteHost(const std::string& host);
// Deletes all records created since a given time.
void DeleteSince(const base::Time& time);
// Returns true and updates |*result| iff there is a DomainState for
// |host|.
//
// If |sni_enabled| is true, searches the static pins defined for
// SNI-using hosts as well as the rest of the pins.
//
// If |host| matches both an exact entry and is a subdomain of another
// entry, the exact match determines the return value.
//
// Note that this method is not const because it opportunistically removes
// entries that have expired.
bool GetDomainState(const std::string& host,
bool sni_enabled,
DomainState* result);
// Returns true and updates |*result| iff there is a static DomainState for
// |host|.
//
// |GetStaticDomainState| is identical to |GetDomainState| except that it
// searches only the statically-defined transport security state, ignoring
// all dynamically-added DomainStates.
//
// If |sni_enabled| is true, searches the static pins defined for
// SNI-using hosts as well as the rest of the pins.
//
// If |host| matches both an exact entry and is a subdomain of another
// entry, the exact match determines the return value.
//
// Note that this method is not const because it opportunistically removes
// entries that have expired.
bool GetStaticDomainState(const std::string& host,
bool sni_enabled,
DomainState* result);
// Removed all DomainState records.
void Clear() { enabled_hosts_.clear(); }
// Inserts |state| into |enabled_hosts_| under the key |hashed_host|.
// |hashed_host| is already in the internal representation
// HashHost(CanonicalizeHost(host)); thus, most callers will use
// |EnableHost|.
void AddOrUpdateEnabledHosts(const std::string& hashed_host,
const DomainState& state);
// Inserts |state| into |forced_hosts_| under the key |hashed_host|.
// |hashed_host| is already in the internal representation
// HashHost(CanonicalizeHost(host)); thus, most callers will use
// |EnableHost|.
void AddOrUpdateForcedHosts(const std::string& hashed_host,
const DomainState& state);
// Returns true iff we have any static public key pins for the |host| and
// iff its set of required pins is the set we expect for Google
// properties.
//
// If |sni_enabled| is true, searches the static pins defined for
// SNI-using hosts as well as the rest of the pins.
//
// 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_enabled);
// Decodes a pin string |value| (e.g. "sha1/hvfkN/qlp/zhXR3cuerq6jd2Z7g=").
// If parsing succeeded, updates |*out| and returns true; otherwise returns
// false without updating |*out|.
static bool ParsePin(const std::string& value, Fingerprint* out);
// The maximum number of seconds for which we'll cache an HSTS request.
static const long int kMaxHSTSAgeSecs;
// Converts |hostname| from dotted form ("www.google.com") to the form
// used in DNS: "\x03www\x06google\x03com", lowercases that, and returns
// the result.
static std::string CanonicalizeHost(const std::string& hostname);
// Send an UMA report on pin validation failure, if the host is in a
// statically-defined list of domains.
//
// TODO(palmer): This doesn't really belong here, and should be moved into
// the exactly one call site. This requires unifying |struct HSTSPreload|
// (an implementation detail of this class) with a more generic
// representation of first-class DomainStates, and exposing the preloads
// to the caller with |GetStaticDomainState|.
static void ReportUMAOnPinFailure(const std::string& host);
private:
// If a Delegate is present, notify it that the internal state has
// changed.
void DirtyNotify();
// The set of hosts that have enabled TransportSecurity.
std::map<std::string, DomainState> enabled_hosts_;
// Extra entries, provided by the user at run-time, to treat as if they
// were static.
std::map<std::string, DomainState> forced_hosts_;
Delegate* delegate_;
DISALLOW_COPY_AND_ASSIGN(TransportSecurityState);
};
} // namespace net
#endif // NET_BASE_TRANSPORT_SECURITY_STATE_H_
|