1 /* ***** BEGIN LICENSE BLOCK *****
2 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
3 *
4 * The contents of this file are subject to the Mozilla Public License Version
5 * 1.1 (the "License"); you may not use this file except in compliance with
6 * the License. You may obtain a copy of the License at
7 * http://www.mozilla.org/MPL/
8 *
9 * Software distributed under the License is distributed on an "AS IS" basis,
10 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
11 * for the specific language governing rights and limitations under the
12 * License.
13 *
14 * The Original Code is the Netscape security libraries.
15 *
16 * The Initial Developer of the Original Code is
17 * Netscape Communications Corporation.
18 * Portions created by the Initial Developer are Copyright (C) 2000
19 * the Initial Developer. All Rights Reserved.
20 *
21 * Contributor(s):
22 *   Ian McGreer <mcgreer@netscape.com>
23 *   Javier Delgadillo <javi@netscape.com>
24 *
25 * Alternatively, the contents of this file may be used under the terms of
26 * either the GNU General Public License Version 2 or later (the "GPL"), or
27 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
28 * in which case the provisions of the GPL or the LGPL are applicable instead
29 * of those above. If you wish to allow use of your version of this file only
30 * under the terms of either the GPL or the LGPL, and not to allow others to
31 * use your version of this file under the terms of the MPL, indicate your
32 * decision by deleting the provisions above and replace them with the notice
33 * and other provisions required by the GPL or the LGPL. If you do not delete
34 * the provisions above, a recipient may use your version of this file under
35 * the terms of any one of the MPL, the GPL or the LGPL.
36 *
37 * ***** END LICENSE BLOCK ***** */
38
39#include "net/third_party/mozilla_security_manager/nsNSSCertificateDB.h"
40
41#include <cert.h>
42#include <certdb.h>
43#include <pk11pub.h>
44#include <secerr.h>
45
46#include "base/logging.h"
47#include "net/base/net_errors.h"
48#include "net/cert/x509_certificate.h"
49#include "net/cert/x509_util_nss.h"
50
51#if !defined(CERTDB_TERMINAL_RECORD)
52/* NSS 3.13 renames CERTDB_VALID_PEER to CERTDB_TERMINAL_RECORD
53 * and marks CERTDB_VALID_PEER as deprecated.
54 * If we're using an older version, rename it ourselves.
55 */
56#define CERTDB_TERMINAL_RECORD CERTDB_VALID_PEER
57#endif
58
59namespace mozilla_security_manager {
60
61// Based on nsNSSCertificateDB::handleCACertDownload, minus the UI bits.
62bool ImportCACerts(PK11SlotInfo* slot,
63                   const net::CertificateList& certificates,
64                   net::X509Certificate* root,
65                   net::NSSCertDatabase::TrustBits trustBits,
66                   net::NSSCertDatabase::ImportCertFailureList* not_imported) {
67  if (!slot || certificates.empty() || !root)
68    return false;
69
70  // Mozilla had some code here to check if a perm version of the cert exists
71  // already and use that, but CERT_NewTempCertificate actually does that
72  // itself, so we skip it here.
73
74  if (!CERT_IsCACert(root->os_cert_handle(), NULL)) {
75    not_imported->push_back(net::NSSCertDatabase::ImportCertFailure(
76        root, net::ERR_IMPORT_CA_CERT_NOT_CA));
77  } else if (root->os_cert_handle()->isperm) {
78    // Mozilla just returns here, but we continue in case there are other certs
79    // in the list which aren't already imported.
80    // TODO(mattm): should we set/add trust if it differs from the present
81    // settings?
82    not_imported->push_back(net::NSSCertDatabase::ImportCertFailure(
83        root, net::ERR_IMPORT_CERT_ALREADY_EXISTS));
84  } else {
85    // Mozilla uses CERT_AddTempCertToPerm, however it is privately exported,
86    // and it doesn't take the slot as an argument either.  Instead, we use
87    // PK11_ImportCert and CERT_ChangeCertTrust.
88    SECStatus srv = PK11_ImportCert(
89        slot,
90        root->os_cert_handle(),
91        CK_INVALID_HANDLE,
92        net::x509_util::GetUniqueNicknameForSlot(
93            root->GetDefaultNickname(net::CA_CERT),
94            &root->os_cert_handle()->derSubject,
95            slot).c_str(),
96        PR_FALSE /* includeTrust (unused) */);
97    if (srv != SECSuccess) {
98      LOG(ERROR) << "PK11_ImportCert failed with error " << PORT_GetError();
99      return false;
100    }
101    if (!SetCertTrust(root, net::CA_CERT, trustBits))
102      return false;
103  }
104
105  PRTime now = PR_Now();
106  // Import additional delivered certificates that can be verified.
107  // This is sort of merged in from Mozilla's ImportValidCACertsInList.  Mozilla
108  // uses CERT_FilterCertListByUsage to filter out non-ca certs, but we want to
109  // keep using X509Certificates, so that we can use them to build the
110  // |not_imported| result.  So, we keep using our net::CertificateList and
111  // filter it ourself.
112  for (size_t i = 0; i < certificates.size(); i++) {
113    const scoped_refptr<net::X509Certificate>& cert = certificates[i];
114    if (cert == root) {
115      // we already processed that one
116      continue;
117    }
118
119    // Mozilla uses CERT_FilterCertListByUsage(certList, certUsageAnyCA,
120    // PR_TRUE).  Afaict, checking !CERT_IsCACert on each cert is equivalent.
121    if (!CERT_IsCACert(cert->os_cert_handle(), NULL)) {
122      not_imported->push_back(net::NSSCertDatabase::ImportCertFailure(
123          cert, net::ERR_IMPORT_CA_CERT_NOT_CA));
124      VLOG(1) << "skipping cert (non-ca)";
125      continue;
126    }
127
128    if (cert->os_cert_handle()->isperm) {
129      not_imported->push_back(net::NSSCertDatabase::ImportCertFailure(
130          cert, net::ERR_IMPORT_CERT_ALREADY_EXISTS));
131      VLOG(1) << "skipping cert (perm)";
132      continue;
133    }
134
135    if (CERT_VerifyCert(CERT_GetDefaultCertDB(), cert->os_cert_handle(),
136        PR_TRUE, certUsageVerifyCA, now, NULL, NULL) != SECSuccess) {
137      // TODO(mattm): use better error code (map PORT_GetError to an appropriate
138      // error value).  (maybe make MapSecurityError or MapCertErrorToCertStatus
139      // public.)
140      not_imported->push_back(net::NSSCertDatabase::ImportCertFailure(
141          cert, net::ERR_FAILED));
142      VLOG(1) << "skipping cert (verify) " << PORT_GetError();
143      continue;
144    }
145
146    // Mozilla uses CERT_ImportCerts, which doesn't take a slot arg.  We use
147    // PK11_ImportCert instead.
148    SECStatus srv = PK11_ImportCert(
149        slot,
150        cert->os_cert_handle(),
151        CK_INVALID_HANDLE,
152        net::x509_util::GetUniqueNicknameForSlot(
153            cert->GetDefaultNickname(net::CA_CERT),
154            &cert->os_cert_handle()->derSubject,
155            slot).c_str(),
156        PR_FALSE /* includeTrust (unused) */);
157    if (srv != SECSuccess) {
158      LOG(ERROR) << "PK11_ImportCert failed with error " << PORT_GetError();
159      // TODO(mattm): Should we bail or continue on error here?  Mozilla doesn't
160      // check error code at all.
161      not_imported->push_back(net::NSSCertDatabase::ImportCertFailure(
162          cert, net::ERR_IMPORT_CA_CERT_FAILED));
163    }
164  }
165
166  // Any errors importing individual certs will be in listed in |not_imported|.
167  return true;
168}
169
170// Based on nsNSSCertificateDB::ImportServerCertificate.
171bool ImportServerCert(
172    PK11SlotInfo* slot,
173    const net::CertificateList& certificates,
174    net::NSSCertDatabase::TrustBits trustBits,
175    net::NSSCertDatabase::ImportCertFailureList* not_imported) {
176  if (!slot || certificates.empty())
177    return false;
178
179  for (size_t i = 0; i < certificates.size(); ++i) {
180    const scoped_refptr<net::X509Certificate>& cert = certificates[i];
181
182    // Mozilla uses CERT_ImportCerts, which doesn't take a slot arg.  We use
183    // PK11_ImportCert instead.
184    SECStatus srv = PK11_ImportCert(
185        slot,
186        cert->os_cert_handle(),
187        CK_INVALID_HANDLE,
188        net::x509_util::GetUniqueNicknameForSlot(
189            cert->GetDefaultNickname(net::SERVER_CERT),
190            &cert->os_cert_handle()->derSubject,
191            slot).c_str(),
192        PR_FALSE /* includeTrust (unused) */);
193    if (srv != SECSuccess) {
194      LOG(ERROR) << "PK11_ImportCert failed with error " << PORT_GetError();
195      not_imported->push_back(net::NSSCertDatabase::ImportCertFailure(
196          cert, net::ERR_IMPORT_SERVER_CERT_FAILED));
197      continue;
198    }
199  }
200
201  SetCertTrust(certificates[0].get(), net::SERVER_CERT, trustBits);
202  // TODO(mattm): Report SetCertTrust result?  Putting in not_imported
203  // wouldn't quite match up since it was imported...
204
205  // Any errors importing individual certs will be in listed in |not_imported|.
206  return true;
207}
208
209// Based on nsNSSCertificateDB::SetCertTrust.
210bool
211SetCertTrust(const net::X509Certificate* cert,
212             net::CertType type,
213             net::NSSCertDatabase::TrustBits trustBits)
214{
215  const unsigned kSSLTrustBits = net::NSSCertDatabase::TRUSTED_SSL |
216      net::NSSCertDatabase::DISTRUSTED_SSL;
217  const unsigned kEmailTrustBits = net::NSSCertDatabase::TRUSTED_EMAIL |
218      net::NSSCertDatabase::DISTRUSTED_EMAIL;
219  const unsigned kObjSignTrustBits = net::NSSCertDatabase::TRUSTED_OBJ_SIGN |
220      net::NSSCertDatabase::DISTRUSTED_OBJ_SIGN;
221  if ((trustBits & kSSLTrustBits) == kSSLTrustBits ||
222      (trustBits & kEmailTrustBits) == kEmailTrustBits ||
223      (trustBits & kObjSignTrustBits) == kObjSignTrustBits) {
224    LOG(ERROR) << "SetCertTrust called with conflicting trust bits "
225               << trustBits;
226    NOTREACHED();
227    return false;
228  }
229
230  SECStatus srv;
231  CERTCertificate *nsscert = cert->os_cert_handle();
232  if (type == net::CA_CERT) {
233    // Note that we start with CERTDB_VALID_CA for default trust and explicit
234    // trust, but explicitly distrusted usages will be set to
235    // CERTDB_TERMINAL_RECORD only.
236    CERTCertTrust trust = {CERTDB_VALID_CA, CERTDB_VALID_CA, CERTDB_VALID_CA};
237
238    if (trustBits & net::NSSCertDatabase::DISTRUSTED_SSL)
239      trust.sslFlags = CERTDB_TERMINAL_RECORD;
240    else if (trustBits & net::NSSCertDatabase::TRUSTED_SSL)
241      trust.sslFlags |= CERTDB_TRUSTED_CA | CERTDB_TRUSTED_CLIENT_CA;
242
243    if (trustBits & net::NSSCertDatabase::DISTRUSTED_EMAIL)
244      trust.emailFlags = CERTDB_TERMINAL_RECORD;
245    else if (trustBits & net::NSSCertDatabase::TRUSTED_EMAIL)
246      trust.emailFlags |= CERTDB_TRUSTED_CA | CERTDB_TRUSTED_CLIENT_CA;
247
248    if (trustBits & net::NSSCertDatabase::DISTRUSTED_OBJ_SIGN)
249      trust.objectSigningFlags = CERTDB_TERMINAL_RECORD;
250    else if (trustBits & net::NSSCertDatabase::TRUSTED_OBJ_SIGN)
251      trust.objectSigningFlags |= CERTDB_TRUSTED_CA | CERTDB_TRUSTED_CLIENT_CA;
252
253    srv = CERT_ChangeCertTrust(CERT_GetDefaultCertDB(), nsscert, &trust);
254  } else if (type == net::SERVER_CERT) {
255    CERTCertTrust trust = {0};
256    // We only modify the sslFlags, so copy the other flags.
257    CERT_GetCertTrust(nsscert, &trust);
258    trust.sslFlags = 0;
259
260    if (trustBits & net::NSSCertDatabase::DISTRUSTED_SSL)
261      trust.sslFlags |= CERTDB_TERMINAL_RECORD;
262    else if (trustBits & net::NSSCertDatabase::TRUSTED_SSL)
263      trust.sslFlags |= CERTDB_TRUSTED | CERTDB_TERMINAL_RECORD;
264
265    srv = CERT_ChangeCertTrust(CERT_GetDefaultCertDB(), nsscert, &trust);
266  } else {
267    // ignore user and email/unknown certs
268    return true;
269  }
270  if (srv != SECSuccess)
271    LOG(ERROR) << "SetCertTrust failed with error " << PORT_GetError();
272  return srv == SECSuccess;
273}
274
275}  // namespace mozilla_security_manager
276