Wed, 31 Dec 2014 06:09:35 +0100
Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.
michael@0 | 1 | #if 0 |
michael@0 | 2 | /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
michael@0 | 3 | /* This Source Code Form is subject to the terms of the Mozilla Public |
michael@0 | 4 | * License, v. 2.0. If a copy of the MPL was not distributed with this |
michael@0 | 5 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
michael@0 | 6 | #endif |
michael@0 | 7 | this.EXPORTED_SYMBOLS = [ "BadCertHandler", "checkCert", "readCertPrefs", "validateCert" ]; |
michael@0 | 8 | |
michael@0 | 9 | const Ce = Components.Exception; |
michael@0 | 10 | const Ci = Components.interfaces; |
michael@0 | 11 | const Cr = Components.results; |
michael@0 | 12 | const Cu = Components.utils; |
michael@0 | 13 | |
michael@0 | 14 | Components.utils.import("resource://gre/modules/Services.jsm"); |
michael@0 | 15 | |
michael@0 | 16 | /** |
michael@0 | 17 | * Reads a set of expected certificate attributes from preferences. The returned |
michael@0 | 18 | * array can be passed to validateCert or checkCert to validate that a |
michael@0 | 19 | * certificate matches the expected attributes. The preferences should look like |
michael@0 | 20 | * this: |
michael@0 | 21 | * prefix.1.attribute1 |
michael@0 | 22 | * prefix.1.attribute2 |
michael@0 | 23 | * prefix.2.attribute1 |
michael@0 | 24 | * etc. |
michael@0 | 25 | * Each numeric branch contains a set of required attributes for a single |
michael@0 | 26 | * certificate. Having multiple numeric branches means that multiple |
michael@0 | 27 | * certificates would be accepted by validateCert. |
michael@0 | 28 | * |
michael@0 | 29 | * @param aPrefBranch |
michael@0 | 30 | * The prefix for all preferences, should end with a ".". |
michael@0 | 31 | * @return An array of JS objects with names / values corresponding to the |
michael@0 | 32 | * expected certificate's attribute names / values. |
michael@0 | 33 | */ |
michael@0 | 34 | this.readCertPrefs = |
michael@0 | 35 | function readCertPrefs(aPrefBranch) { |
michael@0 | 36 | if (Services.prefs.getBranch(aPrefBranch).getChildList("").length == 0) |
michael@0 | 37 | return null; |
michael@0 | 38 | |
michael@0 | 39 | let certs = []; |
michael@0 | 40 | let counter = 1; |
michael@0 | 41 | while (true) { |
michael@0 | 42 | let prefBranchCert = Services.prefs.getBranch(aPrefBranch + counter + "."); |
michael@0 | 43 | let prefCertAttrs = prefBranchCert.getChildList(""); |
michael@0 | 44 | if (prefCertAttrs.length == 0) |
michael@0 | 45 | break; |
michael@0 | 46 | |
michael@0 | 47 | let certAttrs = {}; |
michael@0 | 48 | for each (let prefCertAttr in prefCertAttrs) |
michael@0 | 49 | certAttrs[prefCertAttr] = prefBranchCert.getCharPref(prefCertAttr); |
michael@0 | 50 | |
michael@0 | 51 | certs.push(certAttrs); |
michael@0 | 52 | counter++; |
michael@0 | 53 | } |
michael@0 | 54 | |
michael@0 | 55 | return certs; |
michael@0 | 56 | } |
michael@0 | 57 | |
michael@0 | 58 | /** |
michael@0 | 59 | * Verifies that an nsIX509Cert matches the expected certificate attribute |
michael@0 | 60 | * values. |
michael@0 | 61 | * |
michael@0 | 62 | * @param aCertificate |
michael@0 | 63 | * The nsIX509Cert to compare to the expected attributes. |
michael@0 | 64 | * @param aCerts |
michael@0 | 65 | * An array of JS objects with names / values corresponding to the |
michael@0 | 66 | * expected certificate's attribute names / values. If this is null or |
michael@0 | 67 | * an empty array then no checks are performed. |
michael@0 | 68 | * @throws NS_ERROR_ILLEGAL_VALUE if a certificate attribute name from the |
michael@0 | 69 | * aCerts param does not exist or the value for a certificate attribute |
michael@0 | 70 | * from the aCerts param is different than the expected value or |
michael@0 | 71 | * aCertificate wasn't specified and aCerts is not null or an empty |
michael@0 | 72 | * array. |
michael@0 | 73 | */ |
michael@0 | 74 | this.validateCert = |
michael@0 | 75 | function validateCert(aCertificate, aCerts) { |
michael@0 | 76 | // If there are no certificate requirements then just exit |
michael@0 | 77 | if (!aCerts || aCerts.length == 0) |
michael@0 | 78 | return; |
michael@0 | 79 | |
michael@0 | 80 | if (!aCertificate) { |
michael@0 | 81 | const missingCertErr = "A required certificate was not present."; |
michael@0 | 82 | Cu.reportError(missingCertErr); |
michael@0 | 83 | throw new Ce(missingCertErr, Cr.NS_ERROR_ILLEGAL_VALUE); |
michael@0 | 84 | } |
michael@0 | 85 | |
michael@0 | 86 | var errors = []; |
michael@0 | 87 | for (var i = 0; i < aCerts.length; ++i) { |
michael@0 | 88 | var error = false; |
michael@0 | 89 | var certAttrs = aCerts[i]; |
michael@0 | 90 | for (var name in certAttrs) { |
michael@0 | 91 | if (!(name in aCertificate)) { |
michael@0 | 92 | error = true; |
michael@0 | 93 | errors.push("Expected attribute '" + name + "' not present in " + |
michael@0 | 94 | "certificate."); |
michael@0 | 95 | break; |
michael@0 | 96 | } |
michael@0 | 97 | if (aCertificate[name] != certAttrs[name]) { |
michael@0 | 98 | error = true; |
michael@0 | 99 | errors.push("Expected certificate attribute '" + name + "' " + |
michael@0 | 100 | "value incorrect, expected: '" + certAttrs[name] + |
michael@0 | 101 | "', got: '" + aCertificate[name] + "'."); |
michael@0 | 102 | break; |
michael@0 | 103 | } |
michael@0 | 104 | } |
michael@0 | 105 | |
michael@0 | 106 | if (!error) |
michael@0 | 107 | break; |
michael@0 | 108 | } |
michael@0 | 109 | |
michael@0 | 110 | if (error) { |
michael@0 | 111 | errors.forEach(Cu.reportError.bind(Cu)); |
michael@0 | 112 | const certCheckErr = "Certificate checks failed. See previous errors " + |
michael@0 | 113 | "for details."; |
michael@0 | 114 | Cu.reportError(certCheckErr); |
michael@0 | 115 | throw new Ce(certCheckErr, Cr.NS_ERROR_ILLEGAL_VALUE); |
michael@0 | 116 | } |
michael@0 | 117 | } |
michael@0 | 118 | |
michael@0 | 119 | /** |
michael@0 | 120 | * Checks if the connection must be HTTPS and if so, only allows built-in |
michael@0 | 121 | * certificates and validates application specified certificate attribute |
michael@0 | 122 | * values. |
michael@0 | 123 | * See bug 340198 and bug 544442. |
michael@0 | 124 | * |
michael@0 | 125 | * @param aChannel |
michael@0 | 126 | * The nsIChannel that will have its certificate checked. |
michael@0 | 127 | * @param aAllowNonBuiltInCerts (optional) |
michael@0 | 128 | * When true certificates that aren't builtin are allowed. When false |
michael@0 | 129 | * or not specified the certificate must be a builtin certificate. |
michael@0 | 130 | * @param aCerts (optional) |
michael@0 | 131 | * An array of JS objects with names / values corresponding to the |
michael@0 | 132 | * channel's expected certificate's attribute names / values. If it |
michael@0 | 133 | * isn't null or not specified the the scheme for the channel's |
michael@0 | 134 | * originalURI must be https. |
michael@0 | 135 | * @throws NS_ERROR_UNEXPECTED if a certificate is expected and the URI scheme |
michael@0 | 136 | * is not https. |
michael@0 | 137 | * NS_ERROR_ILLEGAL_VALUE if a certificate attribute name from the |
michael@0 | 138 | * aCerts param does not exist or the value for a certificate attribute |
michael@0 | 139 | * from the aCerts param is different than the expected value. |
michael@0 | 140 | * NS_ERROR_ABORT if the certificate issuer is not built-in. |
michael@0 | 141 | */ |
michael@0 | 142 | this.checkCert = |
michael@0 | 143 | function checkCert(aChannel, aAllowNonBuiltInCerts, aCerts) { |
michael@0 | 144 | if (!aChannel.originalURI.schemeIs("https")) { |
michael@0 | 145 | // Require https if there are certificate values to verify |
michael@0 | 146 | if (aCerts) { |
michael@0 | 147 | throw new Ce("SSL is required and URI scheme is not https.", |
michael@0 | 148 | Cr.NS_ERROR_UNEXPECTED); |
michael@0 | 149 | } |
michael@0 | 150 | return; |
michael@0 | 151 | } |
michael@0 | 152 | |
michael@0 | 153 | var cert = |
michael@0 | 154 | aChannel.securityInfo.QueryInterface(Ci.nsISSLStatusProvider). |
michael@0 | 155 | SSLStatus.QueryInterface(Ci.nsISSLStatus).serverCert; |
michael@0 | 156 | |
michael@0 | 157 | validateCert(cert, aCerts); |
michael@0 | 158 | |
michael@0 | 159 | if (aAllowNonBuiltInCerts === true) |
michael@0 | 160 | return; |
michael@0 | 161 | |
michael@0 | 162 | var issuerCert = cert; |
michael@0 | 163 | while (issuerCert.issuer && !issuerCert.issuer.equals(issuerCert)) |
michael@0 | 164 | issuerCert = issuerCert.issuer; |
michael@0 | 165 | |
michael@0 | 166 | const certNotBuiltInErr = "Certificate issuer is not built-in."; |
michael@0 | 167 | if (!issuerCert) |
michael@0 | 168 | throw new Ce(certNotBuiltInErr, Cr.NS_ERROR_ABORT); |
michael@0 | 169 | |
michael@0 | 170 | issuerCert = issuerCert.QueryInterface(Ci.nsIX509Cert3); |
michael@0 | 171 | var tokenNames = issuerCert.getAllTokenNames({}); |
michael@0 | 172 | |
michael@0 | 173 | if (!tokenNames || !tokenNames.some(isBuiltinToken)) |
michael@0 | 174 | throw new Ce(certNotBuiltInErr, Cr.NS_ERROR_ABORT); |
michael@0 | 175 | } |
michael@0 | 176 | |
michael@0 | 177 | function isBuiltinToken(tokenName) { |
michael@0 | 178 | return tokenName == "Builtin Object Token"; |
michael@0 | 179 | } |
michael@0 | 180 | |
michael@0 | 181 | /** |
michael@0 | 182 | * This class implements nsIBadCertListener. Its job is to prevent "bad cert" |
michael@0 | 183 | * security dialogs from being shown to the user. It is better to simply fail |
michael@0 | 184 | * if the certificate is bad. See bug 304286. |
michael@0 | 185 | * |
michael@0 | 186 | * @param aAllowNonBuiltInCerts (optional) |
michael@0 | 187 | * When true certificates that aren't builtin are allowed. When false |
michael@0 | 188 | * or not specified the certificate must be a builtin certificate. |
michael@0 | 189 | */ |
michael@0 | 190 | this.BadCertHandler = |
michael@0 | 191 | function BadCertHandler(aAllowNonBuiltInCerts) { |
michael@0 | 192 | this.allowNonBuiltInCerts = aAllowNonBuiltInCerts; |
michael@0 | 193 | } |
michael@0 | 194 | BadCertHandler.prototype = { |
michael@0 | 195 | |
michael@0 | 196 | // nsIChannelEventSink |
michael@0 | 197 | asyncOnChannelRedirect: function(oldChannel, newChannel, flags, callback) { |
michael@0 | 198 | if (this.allowNonBuiltInCerts) { |
michael@0 | 199 | callback.onRedirectVerifyCallback(Components.results.NS_OK); |
michael@0 | 200 | return; |
michael@0 | 201 | } |
michael@0 | 202 | |
michael@0 | 203 | // make sure the certificate of the old channel checks out before we follow |
michael@0 | 204 | // a redirect from it. See bug 340198. |
michael@0 | 205 | // Don't call checkCert for internal redirects. See bug 569648. |
michael@0 | 206 | if (!(flags & Ci.nsIChannelEventSink.REDIRECT_INTERNAL)) |
michael@0 | 207 | checkCert(oldChannel); |
michael@0 | 208 | |
michael@0 | 209 | callback.onRedirectVerifyCallback(Components.results.NS_OK); |
michael@0 | 210 | }, |
michael@0 | 211 | |
michael@0 | 212 | // nsIInterfaceRequestor |
michael@0 | 213 | getInterface: function(iid) { |
michael@0 | 214 | return this.QueryInterface(iid); |
michael@0 | 215 | }, |
michael@0 | 216 | |
michael@0 | 217 | // nsISupports |
michael@0 | 218 | QueryInterface: function(iid) { |
michael@0 | 219 | if (!iid.equals(Ci.nsIChannelEventSink) && |
michael@0 | 220 | !iid.equals(Ci.nsIInterfaceRequestor) && |
michael@0 | 221 | !iid.equals(Ci.nsISupports)) |
michael@0 | 222 | throw Cr.NS_ERROR_NO_INTERFACE; |
michael@0 | 223 | return this; |
michael@0 | 224 | } |
michael@0 | 225 | }; |