nsSiteSecurityService.cpp 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712
  1. /* This Source Code Form is subject to the terms of the Mozilla Public
  2. * License, v. 2.0. If a copy of the MPL was not distributed with this
  3. * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
  4. #include "nsSiteSecurityService.h"
  5. #include "mozilla/LinkedList.h"
  6. #include "mozilla/Preferences.h"
  7. #include "mozilla/Base64.h"
  8. #include "base64.h"
  9. #include "CertVerifier.h"
  10. #include "nsCRTGlue.h"
  11. #include "nsISSLStatus.h"
  12. #include "nsISocketProvider.h"
  13. #include "nsIURI.h"
  14. #include "nsIX509Cert.h"
  15. #include "nsNetUtil.h"
  16. #include "nsNSSComponent.h"
  17. #include "nsSecurityHeaderParser.h"
  18. #include "nsString.h"
  19. #include "nsThreadUtils.h"
  20. #include "nsXULAppAPI.h"
  21. #include "pkix/pkixtypes.h"
  22. #include "plstr.h"
  23. #include "mozilla/Logging.h"
  24. #include "prnetdb.h"
  25. #include "prprf.h"
  26. #include "ScopedNSSTypes.h"
  27. #include "SharedCertVerifier.h"
  28. using namespace mozilla;
  29. using namespace mozilla::psm;
  30. static LazyLogModule gSSSLog("nsSSService");
  31. #define SSSLOG(args) MOZ_LOG(gSSSLog, mozilla::LogLevel::Debug, args)
  32. ////////////////////////////////////////////////////////////////////////////////
  33. SiteHSTSState::SiteHSTSState(nsCString& aStateString)
  34. : mHSTSExpireTime(0)
  35. , mHSTSState(SecurityPropertyUnset)
  36. , mHSTSIncludeSubdomains(false)
  37. {
  38. uint32_t hstsState = 0;
  39. uint32_t hstsIncludeSubdomains = 0; // PR_sscanf doesn't handle bools.
  40. int32_t matches = PR_sscanf(aStateString.get(), "%lld,%lu,%lu",
  41. &mHSTSExpireTime, &hstsState,
  42. &hstsIncludeSubdomains);
  43. bool valid = (matches == 3 &&
  44. (hstsIncludeSubdomains == 0 || hstsIncludeSubdomains == 1) &&
  45. ((SecurityPropertyState)hstsState == SecurityPropertyUnset ||
  46. (SecurityPropertyState)hstsState == SecurityPropertySet ||
  47. (SecurityPropertyState)hstsState == SecurityPropertyKnockout ||
  48. (SecurityPropertyState)hstsState == SecurityPropertyNegative));
  49. if (valid) {
  50. mHSTSState = (SecurityPropertyState)hstsState;
  51. mHSTSIncludeSubdomains = (hstsIncludeSubdomains == 1);
  52. } else {
  53. SSSLOG(("%s is not a valid SiteHSTSState", aStateString.get()));
  54. mHSTSExpireTime = 0;
  55. mHSTSState = SecurityPropertyUnset;
  56. mHSTSIncludeSubdomains = false;
  57. }
  58. }
  59. SiteHSTSState::SiteHSTSState(PRTime aHSTSExpireTime,
  60. SecurityPropertyState aHSTSState,
  61. bool aHSTSIncludeSubdomains)
  62. : mHSTSExpireTime(aHSTSExpireTime)
  63. , mHSTSState(aHSTSState)
  64. , mHSTSIncludeSubdomains(aHSTSIncludeSubdomains)
  65. {
  66. }
  67. void
  68. SiteHSTSState::ToString(nsCString& aString)
  69. {
  70. aString.Truncate();
  71. aString.AppendInt(mHSTSExpireTime);
  72. aString.Append(',');
  73. aString.AppendInt(mHSTSState);
  74. aString.Append(',');
  75. aString.AppendInt(static_cast<uint32_t>(mHSTSIncludeSubdomains));
  76. }
  77. ////////////////////////////////////////////////////////////////////////////////
  78. static bool
  79. HostIsIPAddress(const char *hostname)
  80. {
  81. PRNetAddr hostAddr;
  82. return (PR_StringToNetAddr(hostname, &hostAddr) == PR_SUCCESS);
  83. }
  84. nsAutoCString CanonicalizeHostname(const char* hostname)
  85. {
  86. nsAutoCString canonicalizedHostname(hostname);
  87. ToLowerCase(canonicalizedHostname);
  88. while (canonicalizedHostname.Length() > 0 &&
  89. canonicalizedHostname.Last() == '.') {
  90. canonicalizedHostname.Truncate(canonicalizedHostname.Length() - 1);
  91. }
  92. return canonicalizedHostname;
  93. }
  94. nsSiteSecurityService::nsSiteSecurityService()
  95. : mUseStsService(true)
  96. , mPreloadListTimeOffset(0)
  97. {
  98. }
  99. nsSiteSecurityService::~nsSiteSecurityService()
  100. {
  101. }
  102. NS_IMPL_ISUPPORTS(nsSiteSecurityService,
  103. nsIObserver,
  104. nsISiteSecurityService)
  105. nsresult
  106. nsSiteSecurityService::Init()
  107. {
  108. // Don't access Preferences off the main thread.
  109. if (!NS_IsMainThread()) {
  110. NS_NOTREACHED("nsSiteSecurityService initialized off main thread");
  111. return NS_ERROR_NOT_SAME_THREAD;
  112. }
  113. mUseStsService = mozilla::Preferences::GetBool(
  114. "network.stricttransportsecurity.enabled", true);
  115. mozilla::Preferences::AddStrongObserver(this,
  116. "network.stricttransportsecurity.enabled");
  117. mPreloadListTimeOffset = mozilla::Preferences::GetInt(
  118. "test.currentTimeOffsetSeconds", 0);
  119. mozilla::Preferences::AddStrongObserver(this,
  120. "test.currentTimeOffsetSeconds");
  121. mSiteStateStorage =
  122. mozilla::DataStorage::Get(NS_LITERAL_STRING("SiteSecurityServiceState.txt"));
  123. bool storageWillPersist = false;
  124. nsresult rv = mSiteStateStorage->Init(storageWillPersist);
  125. if (NS_WARN_IF(NS_FAILED(rv))) {
  126. return rv;
  127. }
  128. // This is not fatal. There are some cases where there won't be a
  129. // profile directory (e.g. running xpcshell). There isn't the
  130. // expectation that site information will be presisted in those cases.
  131. if (!storageWillPersist) {
  132. NS_WARNING("site security information will not be persisted");
  133. }
  134. return NS_OK;
  135. }
  136. nsresult
  137. nsSiteSecurityService::GetHost(nsIURI* aURI, nsACString& aResult)
  138. {
  139. nsCOMPtr<nsIURI> innerURI = NS_GetInnermostURI(aURI);
  140. if (!innerURI) {
  141. return NS_ERROR_FAILURE;
  142. }
  143. nsAutoCString host;
  144. nsresult rv = innerURI->GetAsciiHost(host);
  145. if (NS_FAILED(rv)) {
  146. return rv;
  147. }
  148. aResult.Assign(CanonicalizeHostname(host.get()));
  149. if (aResult.IsEmpty()) {
  150. return NS_ERROR_UNEXPECTED;
  151. }
  152. return NS_OK;
  153. }
  154. static void
  155. SetStorageKey(nsAutoCString& storageKey, nsCString& hostname, uint32_t aType)
  156. {
  157. storageKey = hostname;
  158. switch (aType) {
  159. case nsISiteSecurityService::HEADER_HSTS:
  160. storageKey.AppendLiteral(":HSTS");
  161. break;
  162. default:
  163. NS_ASSERTION(false, "SSS:SetStorageKey got invalid type");
  164. }
  165. }
  166. // Expire times are in millis. Since Headers max-age is in seconds, and
  167. // PR_Now() is in micros, normalize the units at milliseconds.
  168. static int64_t
  169. ExpireTimeFromMaxAge(uint64_t maxAge)
  170. {
  171. return (PR_Now() / PR_USEC_PER_MSEC) + ((int64_t)maxAge * PR_MSEC_PER_SEC);
  172. }
  173. nsresult
  174. nsSiteSecurityService::SetHSTSState(uint32_t aType,
  175. nsIURI* aSourceURI,
  176. int64_t maxage,
  177. bool includeSubdomains,
  178. uint32_t flags,
  179. SecurityPropertyState aHSTSState)
  180. {
  181. // Exit early if STS not enabled
  182. if (!mUseStsService) {
  183. return NS_OK;
  184. }
  185. // If max-age is zero, the host is no longer considered HSTS.
  186. if (maxage == 0) {
  187. return RemoveState(aType, aSourceURI, flags);
  188. }
  189. MOZ_ASSERT((aHSTSState == SecurityPropertySet ||
  190. aHSTSState == SecurityPropertyNegative),
  191. "HSTS State must be SecurityPropertySet or SecurityPropertyNegative");
  192. int64_t expiretime = ExpireTimeFromMaxAge(maxage);
  193. SiteHSTSState siteState(expiretime, aHSTSState, includeSubdomains);
  194. nsAutoCString stateString;
  195. siteState.ToString(stateString);
  196. nsAutoCString hostname;
  197. nsresult rv = GetHost(aSourceURI, hostname);
  198. NS_ENSURE_SUCCESS(rv, rv);
  199. SSSLOG(("SSS: setting state for %s", hostname.get()));
  200. bool isPrivate = flags & nsISocketProvider::NO_PERMANENT_STORAGE;
  201. mozilla::DataStorageType storageType = isPrivate
  202. ? mozilla::DataStorage_Private
  203. : mozilla::DataStorage_Persistent;
  204. nsAutoCString storageKey;
  205. SetStorageKey(storageKey, hostname, aType);
  206. rv = mSiteStateStorage->Put(storageKey, stateString, storageType);
  207. NS_ENSURE_SUCCESS(rv, rv);
  208. return NS_OK;
  209. }
  210. NS_IMETHODIMP
  211. nsSiteSecurityService::RemoveState(uint32_t aType, nsIURI* aURI, uint32_t aFlags)
  212. {
  213. // Child processes are not allowed direct access to this.
  214. if (!XRE_IsParentProcess()) {
  215. MOZ_CRASH("Child process: no direct access to nsISiteSecurityService::RemoveState");
  216. }
  217. // Only HSTS is supported at the moment.
  218. NS_ENSURE_TRUE(aType == nsISiteSecurityService::HEADER_HSTS,
  219. NS_ERROR_NOT_IMPLEMENTED);
  220. nsAutoCString hostname;
  221. nsresult rv = GetHost(aURI, hostname);
  222. NS_ENSURE_SUCCESS(rv, rv);
  223. bool isPrivate = aFlags & nsISocketProvider::NO_PERMANENT_STORAGE;
  224. mozilla::DataStorageType storageType = isPrivate
  225. ? mozilla::DataStorage_Private
  226. : mozilla::DataStorage_Persistent;
  227. SSSLOG(("SSS: removing entry for %s", hostname.get()));
  228. nsAutoCString storageKey;
  229. SetStorageKey(storageKey, hostname, aType);
  230. mSiteStateStorage->Remove(storageKey, storageType);
  231. return NS_OK;
  232. }
  233. NS_IMETHODIMP
  234. nsSiteSecurityService::ProcessHeader(uint32_t aType,
  235. nsIURI* aSourceURI,
  236. const char* aHeader,
  237. nsISSLStatus* aSSLStatus,
  238. uint32_t aFlags,
  239. uint64_t* aMaxAge,
  240. bool* aIncludeSubdomains,
  241. uint32_t* aFailureResult)
  242. {
  243. if (aFailureResult) {
  244. *aFailureResult = nsISiteSecurityService::ERROR_UNKNOWN;
  245. }
  246. NS_ENSURE_TRUE(aType == nsISiteSecurityService::HEADER_HSTS ||
  247. aType == nsISiteSecurityService::HEADER_HPKP,
  248. NS_ERROR_NOT_IMPLEMENTED);
  249. NS_ENSURE_ARG(aSSLStatus);
  250. return ProcessHeaderInternal(aType, aSourceURI, aHeader, aSSLStatus, aFlags,
  251. aMaxAge, aIncludeSubdomains, aFailureResult);
  252. }
  253. NS_IMETHODIMP
  254. nsSiteSecurityService::UnsafeProcessHeader(uint32_t aType,
  255. nsIURI* aSourceURI,
  256. const char* aHeader,
  257. uint32_t aFlags,
  258. uint64_t* aMaxAge,
  259. bool* aIncludeSubdomains,
  260. uint32_t* aFailureResult)
  261. {
  262. return ProcessHeaderInternal(aType, aSourceURI, aHeader, nullptr, aFlags,
  263. aMaxAge, aIncludeSubdomains, aFailureResult);
  264. }
  265. nsresult
  266. nsSiteSecurityService::ProcessHeaderInternal(uint32_t aType,
  267. nsIURI* aSourceURI,
  268. const char* aHeader,
  269. nsISSLStatus* aSSLStatus,
  270. uint32_t aFlags,
  271. uint64_t* aMaxAge,
  272. bool* aIncludeSubdomains,
  273. uint32_t* aFailureResult)
  274. {
  275. if (aFailureResult) {
  276. *aFailureResult = nsISiteSecurityService::ERROR_UNKNOWN;
  277. }
  278. // Only HSTS is supported at the moment.
  279. NS_ENSURE_TRUE(aType == nsISiteSecurityService::HEADER_HSTS,
  280. NS_ERROR_NOT_IMPLEMENTED);
  281. if (aMaxAge != nullptr) {
  282. *aMaxAge = 0;
  283. }
  284. if (aIncludeSubdomains != nullptr) {
  285. *aIncludeSubdomains = false;
  286. }
  287. if (aSSLStatus) {
  288. bool tlsIsBroken = false;
  289. bool trustcheck;
  290. nsresult rv;
  291. rv = aSSLStatus->GetIsDomainMismatch(&trustcheck);
  292. NS_ENSURE_SUCCESS(rv, rv);
  293. tlsIsBroken = tlsIsBroken || trustcheck;
  294. rv = aSSLStatus->GetIsNotValidAtThisTime(&trustcheck);
  295. NS_ENSURE_SUCCESS(rv, rv);
  296. tlsIsBroken = tlsIsBroken || trustcheck;
  297. rv = aSSLStatus->GetIsUntrusted(&trustcheck);
  298. NS_ENSURE_SUCCESS(rv, rv);
  299. tlsIsBroken = tlsIsBroken || trustcheck;
  300. if (tlsIsBroken) {
  301. SSSLOG(("SSS: discarding header from untrustworthy connection"));
  302. if (aFailureResult) {
  303. *aFailureResult = nsISiteSecurityService::ERROR_UNTRUSTWORTHY_CONNECTION;
  304. }
  305. return NS_ERROR_FAILURE;
  306. }
  307. }
  308. nsAutoCString host;
  309. nsresult rv = GetHost(aSourceURI, host);
  310. NS_ENSURE_SUCCESS(rv, rv);
  311. if (HostIsIPAddress(host.get())) {
  312. /* Don't process headers if a site is accessed by IP address. */
  313. return NS_OK;
  314. }
  315. switch (aType) {
  316. case nsISiteSecurityService::HEADER_HSTS:
  317. rv = ProcessSTSHeader(aSourceURI, aHeader, aFlags, aMaxAge,
  318. aIncludeSubdomains, aFailureResult);
  319. break;
  320. default:
  321. MOZ_CRASH("unexpected header type");
  322. }
  323. return rv;
  324. }
  325. static uint32_t
  326. ParseSSSHeaders(uint32_t aType,
  327. const char* aHeader,
  328. bool& foundIncludeSubdomains,
  329. bool& foundMaxAge,
  330. bool& foundUnrecognizedDirective,
  331. uint64_t& maxAge,
  332. nsTArray<nsCString>& sha256keys)
  333. {
  334. // "Strict-Transport-Security" ":" OWS
  335. // STS-d *( OWS ";" OWS STS-d OWS)
  336. //
  337. // ; STS directive
  338. // STS-d = maxAge / includeSubDomains
  339. //
  340. // maxAge = "max-age" "=" delta-seconds v-ext
  341. //
  342. // includeSubDomains = [ "includeSubDomains" ]
  343. //
  344. // All directives must appear only once.
  345. // Directive names are case-insensitive.
  346. // The entire header is invalid if a directive not conforming to the
  347. // syntax is encountered.
  348. // Unrecognized directives (that are otherwise syntactically valid) are
  349. // ignored, and the rest of the header is parsed as normal.
  350. NS_NAMED_LITERAL_CSTRING(max_age_var, "max-age");
  351. NS_NAMED_LITERAL_CSTRING(include_subd_var, "includesubdomains");
  352. nsSecurityHeaderParser parser(aHeader);
  353. nsresult rv = parser.Parse();
  354. if (NS_FAILED(rv)) {
  355. SSSLOG(("SSS: could not parse header"));
  356. return nsISiteSecurityService::ERROR_COULD_NOT_PARSE_HEADER;
  357. }
  358. mozilla::LinkedList<nsSecurityHeaderDirective>* directives = parser.GetDirectives();
  359. for (nsSecurityHeaderDirective* directive = directives->getFirst();
  360. directive != nullptr; directive = directive->getNext()) {
  361. SSSLOG(("SSS: found directive %s\n", directive->mName.get()));
  362. if (directive->mName.Length() == max_age_var.Length() &&
  363. directive->mName.EqualsIgnoreCase(max_age_var.get(),
  364. max_age_var.Length())) {
  365. if (foundMaxAge) {
  366. SSSLOG(("SSS: found two max-age directives"));
  367. return nsISiteSecurityService::ERROR_MULTIPLE_MAX_AGES;
  368. }
  369. SSSLOG(("SSS: found max-age directive"));
  370. foundMaxAge = true;
  371. size_t len = directive->mValue.Length();
  372. for (size_t i = 0; i < len; i++) {
  373. char chr = directive->mValue.CharAt(i);
  374. if (chr < '0' || chr > '9') {
  375. SSSLOG(("SSS: invalid value for max-age directive"));
  376. return nsISiteSecurityService::ERROR_INVALID_MAX_AGE;
  377. }
  378. }
  379. if (PR_sscanf(directive->mValue.get(), "%llu", &maxAge) != 1) {
  380. SSSLOG(("SSS: could not parse delta-seconds"));
  381. return nsISiteSecurityService::ERROR_INVALID_MAX_AGE;
  382. }
  383. SSSLOG(("SSS: parsed delta-seconds: %llu", maxAge));
  384. } else if (directive->mName.Length() == include_subd_var.Length() &&
  385. directive->mName.EqualsIgnoreCase(include_subd_var.get(),
  386. include_subd_var.Length())) {
  387. if (foundIncludeSubdomains) {
  388. SSSLOG(("SSS: found two includeSubdomains directives"));
  389. return nsISiteSecurityService::ERROR_MULTIPLE_INCLUDE_SUBDOMAINS;
  390. }
  391. SSSLOG(("SSS: found includeSubdomains directive"));
  392. foundIncludeSubdomains = true;
  393. if (directive->mValue.Length() != 0) {
  394. SSSLOG(("SSS: includeSubdomains directive unexpectedly had value '%s'",
  395. directive->mValue.get()));
  396. return nsISiteSecurityService::ERROR_INVALID_INCLUDE_SUBDOMAINS;
  397. }
  398. } else {
  399. SSSLOG(("SSS: ignoring unrecognized directive '%s'",
  400. directive->mName.get()));
  401. foundUnrecognizedDirective = true;
  402. }
  403. }
  404. return nsISiteSecurityService::Success;
  405. }
  406. nsresult
  407. nsSiteSecurityService::ProcessSTSHeader(nsIURI* aSourceURI,
  408. const char* aHeader,
  409. uint32_t aFlags,
  410. uint64_t* aMaxAge,
  411. bool* aIncludeSubdomains,
  412. uint32_t* aFailureResult)
  413. {
  414. if (aFailureResult) {
  415. *aFailureResult = nsISiteSecurityService::ERROR_UNKNOWN;
  416. }
  417. SSSLOG(("SSS: processing HSTS header '%s'", aHeader));
  418. const uint32_t aType = nsISiteSecurityService::HEADER_HSTS;
  419. bool foundMaxAge = false;
  420. bool foundIncludeSubdomains = false;
  421. bool foundUnrecognizedDirective = false;
  422. uint64_t maxAge = 0;
  423. nsTArray<nsCString> unusedSHA256keys; // Required for sane internal interface
  424. uint32_t sssrv = ParseSSSHeaders(aType, aHeader, foundIncludeSubdomains,
  425. foundMaxAge, foundUnrecognizedDirective,
  426. maxAge, unusedSHA256keys);
  427. if (sssrv != nsISiteSecurityService::Success) {
  428. if (aFailureResult) {
  429. *aFailureResult = sssrv;
  430. }
  431. return NS_ERROR_FAILURE;
  432. }
  433. // after processing all the directives, make sure we came across max-age
  434. // somewhere.
  435. if (!foundMaxAge) {
  436. SSSLOG(("SSS: did not encounter required max-age directive"));
  437. if (aFailureResult) {
  438. *aFailureResult = nsISiteSecurityService::ERROR_NO_MAX_AGE;
  439. }
  440. return NS_ERROR_FAILURE;
  441. }
  442. // record the successfully parsed header data.
  443. nsresult rv = SetHSTSState(aType, aSourceURI, maxAge, foundIncludeSubdomains,
  444. aFlags, SecurityPropertySet);
  445. if (NS_FAILED(rv)) {
  446. SSSLOG(("SSS: failed to set STS state"));
  447. if (aFailureResult) {
  448. *aFailureResult = nsISiteSecurityService::ERROR_COULD_NOT_SAVE_STATE;
  449. }
  450. return rv;
  451. }
  452. if (aMaxAge != nullptr) {
  453. *aMaxAge = maxAge;
  454. }
  455. if (aIncludeSubdomains != nullptr) {
  456. *aIncludeSubdomains = foundIncludeSubdomains;
  457. }
  458. return foundUnrecognizedDirective
  459. ? NS_SUCCESS_LOSS_OF_INSIGNIFICANT_DATA
  460. : NS_OK;
  461. }
  462. NS_IMETHODIMP
  463. nsSiteSecurityService::IsSecureURI(uint32_t aType, nsIURI* aURI,
  464. uint32_t aFlags, bool* aCached,
  465. bool* aResult)
  466. {
  467. NS_ENSURE_ARG(aURI);
  468. NS_ENSURE_ARG(aResult);
  469. // Only HSTS is supported at the moment.
  470. NS_ENSURE_TRUE(aType == nsISiteSecurityService::HEADER_HSTS,
  471. NS_ERROR_NOT_IMPLEMENTED);
  472. nsAutoCString hostname;
  473. nsresult rv = GetHost(aURI, hostname);
  474. NS_ENSURE_SUCCESS(rv, rv);
  475. // Exit early if STS not enabled
  476. if (!mUseStsService) {
  477. *aResult = false;
  478. return NS_OK;
  479. }
  480. /* An IP address never qualifies as a secure URI. */
  481. if (HostIsIPAddress(hostname.get())) {
  482. *aResult = false;
  483. return NS_OK;
  484. }
  485. return IsSecureHost(aType, hostname.get(), aFlags, aCached, aResult);
  486. }
  487. NS_IMETHODIMP
  488. nsSiteSecurityService::IsSecureHost(uint32_t aType, const char* aHost,
  489. uint32_t aFlags, bool* aCached,
  490. bool* aResult)
  491. {
  492. NS_ENSURE_ARG(aHost);
  493. NS_ENSURE_ARG(aResult);
  494. // Only HSTS is supported at the moment.
  495. NS_ENSURE_TRUE(aType == nsISiteSecurityService::HEADER_HSTS,
  496. NS_ERROR_NOT_IMPLEMENTED);
  497. // set default in case if we can't find any STS information
  498. *aResult = false;
  499. if (aCached) {
  500. *aCached = false;
  501. }
  502. // Exit early if checking HSTS and STS not enabled
  503. if (!mUseStsService && aType == nsISiteSecurityService::HEADER_HSTS) {
  504. return NS_OK;
  505. }
  506. // An IP address never qualifies as a secure URI.
  507. if (HostIsIPAddress(aHost)) {
  508. return NS_OK;
  509. }
  510. // Canonicalize the passed host name
  511. nsAutoCString host(CanonicalizeHostname(aHost));
  512. // First check the exact host. This involves first checking for an entry in
  513. // site security storage. If that entry exists, we don't want to check
  514. // in the preload list. We only want to use the stored value if it is not a
  515. // knockout entry, however.
  516. // Additionally, if it is a knockout entry, we want to stop looking for data
  517. // on the host, because the knockout entry indicates "we have no information
  518. // regarding the security status of this host".
  519. bool isPrivate = aFlags & nsISocketProvider::NO_PERMANENT_STORAGE;
  520. mozilla::DataStorageType storageType = isPrivate
  521. ? mozilla::DataStorage_Private
  522. : mozilla::DataStorage_Persistent;
  523. nsAutoCString storageKey;
  524. SetStorageKey(storageKey, host, aType);
  525. nsCString value = mSiteStateStorage->Get(storageKey, storageType);
  526. SiteHSTSState siteState(value);
  527. if (siteState.mHSTSState != SecurityPropertyUnset) {
  528. SSSLOG(("Found entry for %s", host.get()));
  529. bool expired = siteState.IsExpired(aType);
  530. if (!expired) {
  531. if (aCached) {
  532. *aCached = true;
  533. }
  534. if (siteState.mHSTSState == SecurityPropertySet) {
  535. *aResult = true;
  536. return NS_OK;
  537. } else if (siteState.mHSTSState == SecurityPropertyNegative) {
  538. *aResult = false;
  539. return NS_OK;
  540. }
  541. }
  542. // If the entry is expired we can remove it.
  543. if (expired) {
  544. mSiteStateStorage->Remove(storageKey, storageType);
  545. }
  546. }
  547. SSSLOG(("no HSTS data for %s found, walking up domain", host.get()));
  548. const char *subdomain;
  549. uint32_t offset = 0;
  550. for (offset = host.FindChar('.', offset) + 1;
  551. offset > 0;
  552. offset = host.FindChar('.', offset) + 1) {
  553. subdomain = host.get() + offset;
  554. // If we get an empty string, don't continue.
  555. if (strlen(subdomain) < 1) {
  556. break;
  557. }
  558. // Do the same thing as with the exact host, except now we're looking at
  559. // ancestor domains of the original host. So, we have to look at the
  560. // include subdomains flag (although we still have to check for a
  561. // SecurityPropertySet flag first to check that this is a secure host and
  562. // not a knockout entry - and again, if it is a knockout entry, we stop
  563. // looking for data on it and skip to the next higher up ancestor domain).
  564. nsCString subdomainString(subdomain);
  565. nsAutoCString storageKey;
  566. SetStorageKey(storageKey, subdomainString, aType);
  567. value = mSiteStateStorage->Get(storageKey, storageType);
  568. SiteHSTSState siteState(value);
  569. if (siteState.mHSTSState != SecurityPropertyUnset) {
  570. SSSLOG(("Found entry for %s", subdomain));
  571. bool expired = siteState.IsExpired(aType);
  572. if (!expired) {
  573. if (aCached) {
  574. *aCached = true;
  575. }
  576. if (siteState.mHSTSState == SecurityPropertySet) {
  577. *aResult = siteState.mHSTSIncludeSubdomains;
  578. break;
  579. } else if (siteState.mHSTSState == SecurityPropertyNegative) {
  580. *aResult = false;
  581. break;
  582. }
  583. }
  584. // If the entry is expired we can remove it.
  585. if (expired) {
  586. mSiteStateStorage->Remove(storageKey, storageType);
  587. }
  588. }
  589. SSSLOG(("no HSTS data for %s found, walking up domain", subdomain));
  590. }
  591. // Use whatever we ended up with, which defaults to false.
  592. return NS_OK;
  593. }
  594. NS_IMETHODIMP
  595. nsSiteSecurityService::ClearAll()
  596. {
  597. return mSiteStateStorage->Clear();
  598. }
  599. //------------------------------------------------------------
  600. // nsSiteSecurityService::nsIObserver
  601. //------------------------------------------------------------
  602. NS_IMETHODIMP
  603. nsSiteSecurityService::Observe(nsISupports *subject,
  604. const char *topic,
  605. const char16_t *data)
  606. {
  607. // Don't access Preferences off the main thread.
  608. if (!NS_IsMainThread()) {
  609. NS_NOTREACHED("Preferences accessed off main thread");
  610. return NS_ERROR_NOT_SAME_THREAD;
  611. }
  612. if (strcmp(topic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID) == 0) {
  613. mUseStsService = mozilla::Preferences::GetBool(
  614. "network.stricttransportsecurity.enabled", true);
  615. mPreloadListTimeOffset =
  616. mozilla::Preferences::GetInt("test.currentTimeOffsetSeconds", 0);
  617. }
  618. return NS_OK;
  619. }