summaryrefslogtreecommitdiffstats
path: root/dom/webauthn/WebAuthnUtil.cpp
blob: 941ff0a85bb0a18a87e21a79b2e8d847f27bf114 (plain)
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
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "mozilla/dom/WebAuthnUtil.h"
#include "nsComponentManagerUtils.h"
#include "nsICryptoHash.h"
#include "nsIEffectiveTLDService.h"
#include "nsNetUtil.h"
#include "mozpkix/pkixutil.h"
#include "nsHTMLDocument.h"
#include "hasht.h"

namespace mozilla::dom {

// Bug #1436078 - Permit Google Accounts. Remove in Bug #1436085 in Jan 2023.
constexpr auto kGoogleAccountsAppId1 =
    u"https://www.gstatic.com/securitykey/origins.json"_ns;
constexpr auto kGoogleAccountsAppId2 =
    u"https://www.gstatic.com/securitykey/a/google.com/origins.json"_ns;

bool EvaluateAppID(nsPIDOMWindowInner* aParent, const nsString& aOrigin,
                   /* in/out */ nsString& aAppId) {
  // Facet is the specification's way of referring to the web origin.
  nsAutoCString facetString = NS_ConvertUTF16toUTF8(aOrigin);
  nsCOMPtr<nsIURI> facetUri;
  if (NS_FAILED(NS_NewURI(getter_AddRefs(facetUri), facetString))) {
    return false;
  }

  // If the facetId (origin) is not HTTPS, reject
  if (!facetUri->SchemeIs("https")) {
    return false;
  }

  // If the appId is empty or null, overwrite it with the facetId and accept
  if (aAppId.IsEmpty() || aAppId.EqualsLiteral("null")) {
    aAppId.Assign(aOrigin);
    return true;
  }

  // AppID is user-supplied. It's quite possible for this parse to fail.
  nsAutoCString appIdString = NS_ConvertUTF16toUTF8(aAppId);
  nsCOMPtr<nsIURI> appIdUri;
  if (NS_FAILED(NS_NewURI(getter_AddRefs(appIdUri), appIdString))) {
    return false;
  }

  // if the appId URL is not HTTPS, reject.
  if (!appIdUri->SchemeIs("https")) {
    return false;
  }

  nsAutoCString appIdHost;
  if (NS_FAILED(appIdUri->GetAsciiHost(appIdHost))) {
    return false;
  }

  // Allow localhost.
  if (appIdHost.EqualsLiteral("localhost")) {
    nsAutoCString facetHost;
    if (NS_FAILED(facetUri->GetAsciiHost(facetHost))) {
      return false;
    }

    if (facetHost.EqualsLiteral("localhost")) {
      return true;
    }
  }

  // Run the HTML5 algorithm to relax the same-origin policy, copied from W3C
  // Web Authentication. See Bug 1244959 comment #8 for context on why we are
  // doing this instead of implementing the external-fetch FacetID logic.
  nsCOMPtr<Document> document = aParent->GetDoc();
  if (!document || !document->IsHTMLDocument()) {
    return false;
  }

  nsHTMLDocument* html = document->AsHTMLDocument();
  // Use the base domain as the facet for evaluation. This lets this algorithm
  // relax the whole eTLD+1.
  nsCOMPtr<nsIEffectiveTLDService> tldService =
      do_GetService(NS_EFFECTIVETLDSERVICE_CONTRACTID);
  if (!tldService) {
    return false;
  }

  nsAutoCString lowestFacetHost;
  if (NS_FAILED(tldService->GetBaseDomain(facetUri, 0, lowestFacetHost))) {
    return false;
  }

  if (html->IsRegistrableDomainSuffixOfOrEqualTo(
          NS_ConvertUTF8toUTF16(lowestFacetHost), appIdHost)) {
    return true;
  }

  // Bug #1436078 - Permit Google Accounts. Remove in Bug #1436085 in Jan 2023.
  if (lowestFacetHost.EqualsLiteral("google.com") &&
      (aAppId.Equals(kGoogleAccountsAppId1) ||
       aAppId.Equals(kGoogleAccountsAppId2))) {
    return true;
  }

  return false;
}

static nsresult HashCString(nsICryptoHash* aHashService, const nsACString& aIn,
                            /* out */ nsTArray<uint8_t>& aOut) {
  MOZ_ASSERT(aHashService);

  nsresult rv = aHashService->Init(nsICryptoHash::SHA256);
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }

  rv = aHashService->Update(
      reinterpret_cast<const uint8_t*>(aIn.BeginReading()), aIn.Length());
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }

  nsAutoCString fullHash;
  // Passing false below means we will get a binary result rather than a
  // base64-encoded string.
  rv = aHashService->Finish(false, fullHash);
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }

  aOut.Clear();
  aOut.AppendElements(reinterpret_cast<uint8_t const*>(fullHash.BeginReading()),
                      fullHash.Length());

  return NS_OK;
}

nsresult HashCString(const nsACString& aIn, /* out */ nsTArray<uint8_t>& aOut) {
  nsresult srv;
  nsCOMPtr<nsICryptoHash> hashService =
      do_CreateInstance(NS_CRYPTO_HASH_CONTRACTID, &srv);
  if (NS_FAILED(srv)) {
    return srv;
  }

  srv = HashCString(hashService, aIn, aOut);
  if (NS_WARN_IF(NS_FAILED(srv))) {
    return NS_ERROR_FAILURE;
  }

  return NS_OK;
}

}  // namespace mozilla::dom