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 }