tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

authcert.c (8175B)


      1 /*
      2 * NSS utility functions
      3 *
      4 * This Source Code Form is subject to the terms of the Mozilla Public
      5 * License, v. 2.0. If a copy of the MPL was not distributed with this
      6 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      7 
      8 #include <stdio.h>
      9 #include <string.h>
     10 #include "prerror.h"
     11 #include "secitem.h"
     12 #include "prnetdb.h"
     13 #include "cert.h"
     14 #include "nspr.h"
     15 #include "secder.h"
     16 #include "keyhi.h"
     17 #include "nss.h"
     18 #include "ssl.h"
     19 #include "pk11func.h" /* for PK11_ function calls */
     20 #include "sslimpl.h"
     21 
     22 /* convert a CERTDistNameStr to an array ascii strings.
     23 * we ignore caNames which we can't convert, so n could be less than nnames
     24 * n is always set, even on failure.
     25 * This function allows us to use the existing CERT_FilterCertListByCANames. */
     26 static char **
     27 ssl_DistNamesToStrings(struct CERTDistNamesStr *caNames, int *n)
     28 {
     29    char **names;
     30    int i;
     31    SECStatus rv;
     32    PLArenaPool *arena;
     33 
     34    *n = 0;
     35    names = PORT_ZNewArray(char *, caNames->nnames);
     36    if (names == NULL) {
     37        return NULL;
     38    }
     39    arena = PORT_NewArena(2048);
     40    if (arena == NULL) {
     41        PORT_Free(names);
     42        return NULL;
     43    }
     44    for (i = 0; i < caNames->nnames; ++i) {
     45        CERTName dn;
     46        rv = SEC_QuickDERDecodeItem(arena, &dn, SEC_ASN1_GET(CERT_NameTemplate),
     47                                    caNames->names + i);
     48        if (rv != SECSuccess) {
     49            continue;
     50        }
     51        names[*n] = CERT_NameToAscii(&dn);
     52        if (names[*n])
     53            (*n)++;
     54    }
     55    PORT_FreeArena(arena, PR_FALSE);
     56    return names;
     57 }
     58 
     59 /* free the dist names we allocated in the above function. n must be the
     60 * returned n from that function. */
     61 static void
     62 ssl_FreeDistNamesStrings(char **strings, int n)
     63 {
     64    int i;
     65    for (i = 0; i < n; i++) {
     66        PORT_Free(strings[i]);
     67    }
     68    PORT_Free(strings);
     69 }
     70 
     71 PRBool
     72 ssl_CertIsUsable(sslSocket *ss, CERTCertificate *cert)
     73 {
     74    SECStatus rv;
     75    SSLSignatureScheme scheme;
     76 
     77    if ((ss == NULL) || (cert == NULL)) {
     78        return PR_FALSE;
     79    }
     80    /* There are two ways of handling the old style handshake:
     81     * 1) check the actual record we are using and return true,
     82     *   if (!ss->ssl3.hs.hashType == handshake_hash_record  &&
     83     *           ss->ssl3.hs.hashType == handshake_hash_single) {
     84     *   return PR_TRUE;
     85     * 2) assume if ss->ss->ssl3.hs.clientAuthSignatureSchemesLen == 0 we are using the
     86     *    old handshake.
     87     * There is one case where using 2 will be wrong: we somehow call this
     88     * function outside the case where of out GetClientAuthData context.
     89     * In that case we don't know that the 'real' peerScheme list is, so the
     90     * best we can do is either always assume good or always assume bad.
     91     * I think the best results is to always assume good, so we use
     92     * option 2 here to handle that case as well.*/
     93    if (ss->ssl3.hs.clientAuthSignatureSchemesLen == 0) {
     94        return PR_TRUE;
     95    }
     96    if (ss->ssl3.hs.clientAuthSignatureSchemes == NULL) {
     97        return PR_FALSE; /* should this really be an assert? */
     98    }
     99    rv = ssl_PickClientSignatureScheme(ss, cert, NULL,
    100                                       ss->ssl3.hs.clientAuthSignatureSchemes,
    101                                       ss->ssl3.hs.clientAuthSignatureSchemesLen,
    102                                       &scheme);
    103    if (rv != SECSuccess) {
    104        return PR_FALSE;
    105    }
    106    return PR_TRUE;
    107 }
    108 
    109 SECStatus
    110 ssl_FilterClientCertListBySSLSocket(sslSocket *ss, CERTCertList *certList)
    111 {
    112    CERTCertListNode *node;
    113    CERTCertificate *cert;
    114 
    115    if (!certList) {
    116        return SECFailure;
    117    }
    118 
    119    node = CERT_LIST_HEAD(certList);
    120 
    121    while (!CERT_LIST_END(node, certList)) {
    122        cert = node->cert;
    123        if (PR_TRUE != ssl_CertIsUsable(ss, cert)) {
    124            /* cert doesn't match the socket criteria, remove it */
    125            CERTCertListNode *freenode = node;
    126            node = CERT_LIST_NEXT(node);
    127            CERT_RemoveCertListNode(freenode);
    128        } else {
    129            /* this cert is good, go to the next cert */
    130            node = CERT_LIST_NEXT(node);
    131        }
    132    }
    133 
    134    return (SECSuccess);
    135 }
    136 
    137 /* This function can be called by the application's custom GetClientAuthHook
    138 * to filter out any certs in the cert list that doesn't match the negotiated
    139 * requirements of the current SSL connection.
    140 */
    141 SECStatus
    142 SSL_FilterClientCertListBySocket(PRFileDesc *fd, CERTCertList *certList)
    143 {
    144    sslSocket *ss = ssl_FindSocket(fd);
    145    if (ss == NULL) {
    146        return SECFailure;
    147    }
    148    return ssl_FilterClientCertListBySSLSocket(ss, certList);
    149 }
    150 
    151 /* This function can be called by the application's custom GetClientAuthHook
    152 * to determine if a single certificate matches the negotiated requirements of
    153 * the current SSL connection.
    154 */
    155 PRBool
    156 SSL_CertIsUsable(PRFileDesc *fd, CERTCertificate *cert)
    157 {
    158    sslSocket *ss = ssl_FindSocket(fd);
    159    if (ss == NULL) {
    160        return PR_FALSE;
    161    }
    162    return ssl_CertIsUsable(ss, cert);
    163 }
    164 
    165 /*
    166 * This callback used by SSL to pull client certificate upon
    167 * server request
    168 */
    169 SECStatus
    170 NSS_GetClientAuthData(void *arg,
    171                      PRFileDesc *fd,
    172                      struct CERTDistNamesStr *caNames,
    173                      struct CERTCertificateStr **pRetCert,
    174                      struct SECKEYPrivateKeyStr **pRetKey)
    175 {
    176    CERTCertificate *cert = NULL;
    177    CERTCertList *certList = NULL;
    178    SECKEYPrivateKey *privkey = NULL;
    179    char *chosenNickName = (char *)arg; /* CONST */
    180    SECStatus rv = SECFailure;
    181 
    182    sslSocket *ss = ssl_FindSocket(fd);
    183    if (!ss) {
    184        return SECFailure;
    185    }
    186    void *pw_arg = SSL_RevealPinArg(fd);
    187 
    188    /* first, handle any token authentication that may be needed */
    189    if (chosenNickName && pw_arg) {
    190        certList = PK11_FindCertsFromNickname(chosenNickName, pw_arg);
    191        if (certList) {
    192            CERT_FilterCertListForUserCerts(certList);
    193            rv = CERT_FilterCertListByUsage(certList, certUsageSSLClient,
    194                                            PR_FALSE);
    195            if ((rv != SECSuccess) || CERT_LIST_EMPTY(certList)) {
    196                CERT_DestroyCertList(certList);
    197                certList = NULL;
    198            }
    199        }
    200    }
    201 
    202    /* otherwise look through the cache based on usage
    203     * if chosenNickname is set, we ignore the expiration date */
    204    if (certList == NULL) {
    205        certList = CERT_FindUserCertsByUsage(CERT_GetDefaultCertDB(),
    206                                             certUsageSSLClient,
    207                                             PR_FALSE, chosenNickName == NULL,
    208                                             pw_arg);
    209        if (certList == NULL) {
    210            return SECFailure;
    211        }
    212        /* filter only the certs that meet the nickname requirements */
    213        if (chosenNickName) {
    214            rv = CERT_FilterCertListByNickname(certList, chosenNickName,
    215                                               pw_arg);
    216        } else {
    217            int nnames = 0;
    218            char **names = ssl_DistNamesToStrings(caNames, &nnames);
    219            rv = CERT_FilterCertListByCANames(certList, nnames, names,
    220                                              certUsageSSLClient);
    221            ssl_FreeDistNamesStrings(names, nnames);
    222        }
    223        if ((rv != SECSuccess) || CERT_LIST_EMPTY(certList)) {
    224            CERT_DestroyCertList(certList);
    225            return SECFailure;
    226        }
    227    }
    228 
    229    /* now remove any certs that can't meet the connection requirements */
    230    rv = ssl_FilterClientCertListBySSLSocket(ss, certList);
    231    if ((rv != SECSuccess) || CERT_LIST_EMPTY(certList)) {
    232        // no certs left.
    233        CERT_DestroyCertList(certList);
    234        return SECFailure;
    235    }
    236 
    237    /* now return the top cert in the list. We've strived to make the
    238     * list ordered by the most likely usable cert, so it should be the best
    239     * match. */
    240    cert = CERT_DupCertificate(CERT_LIST_HEAD(certList)->cert);
    241    CERT_DestroyCertList(certList);
    242    privkey = PK11_FindKeyByAnyCert(cert, pw_arg);
    243    if (privkey == NULL) {
    244        CERT_DestroyCertificate(cert);
    245        return SECFailure;
    246    }
    247    *pRetCert = cert;
    248    *pRetKey = privkey;
    249    return SECSuccess;
    250 }