Home | History | Annotate | Download | only in mozilla_security_manager
      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 (at) netscape.com>
     23  *   Javier Delgadillo <javi (at) 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 
     59 namespace mozilla_security_manager {
     60 
     61 // Based on nsNSSCertificateDB::handleCACertDownload, minus the UI bits.
     62 bool 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.
    171 bool 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.
    210 bool
    211 SetCertTrust(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