📄 ne_openssl.c
字号:
/* neon SSL/TLS support using OpenSSL Copyright (C) 2002-2004, Joe Orton <joe@manyfish.co.uk> Portions are: Copyright (C) 1999-2000 Tommi Komulainen <Tommi.Komulainen@iki.fi> This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA*/#include "config.h"#include <sys/types.h>#ifdef HAVE_STRING_H#include <string.h>#endif#include <stdio.h>#include <openssl/ssl.h>#include <openssl/err.h>#include <openssl/pkcs12.h>#include <openssl/x509v3.h>#include <openssl/rand.h>#include "ne_ssl.h"#include "ne_string.h"#include "ne_session.h"#include "ne_i18n.h"#include "ne_private.h"#include "ne_privssl.h"/* OpenSSL 0.9.6 compatibility */#if OPENSSL_VERSION_NUMBER < 0x0090700fL#define PKCS12_unpack_authsafes M_PKCS12_unpack_authsafes#define PKCS12_unpack_p7data M_PKCS12_unpack_p7data/* cast away lack of const-ness */#define OBJ_cmp(a,b) OBJ_cmp((ASN1_OBJECT *)(a), (ASN1_OBJECT *)(b))#endifstruct ne_ssl_dname_s { X509_NAME *dn;};struct ne_ssl_certificate_s { ne_ssl_dname subj_dn, issuer_dn; X509 *subject; ne_ssl_certificate *issuer; char *identity;};struct ne_ssl_client_cert_s { PKCS12 *p12; int decrypted; /* non-zero if successfully decrypted. */ ne_ssl_certificate cert; EVP_PKEY *pkey; char *friendly_name;};char *ne_ssl_readable_dname(const ne_ssl_dname *name){ int n, flag = 0; ne_buffer *dump = ne_buffer_create(); const ASN1_OBJECT * const cname = OBJ_nid2obj(NID_commonName), * const email = OBJ_nid2obj(NID_pkcs9_emailAddress); for (n = X509_NAME_entry_count(name->dn); n > 0; n--) { X509_NAME_ENTRY *ent = X509_NAME_get_entry(name->dn, n-1); /* Skip commonName or emailAddress except if there is no other * attribute in dname. */ if ((OBJ_cmp(ent->object, cname) && OBJ_cmp(ent->object, email)) || (!flag && n == 1)) { if (flag++) ne_buffer_append(dump, ", ", 2); switch (ent->value->type) { case V_ASN1_UTF8STRING: case V_ASN1_IA5STRING: /* definitely ASCII */ case V_ASN1_VISIBLESTRING: /* probably ASCII */ case V_ASN1_PRINTABLESTRING: /* subset of ASCII */ ne_buffer_append(dump, ent->value->data, ent->value->length); break; case V_ASN1_UNIVERSALSTRING: case V_ASN1_T61STRING: /* let OpenSSL convert it as ISO-8859-1 */ case V_ASN1_BMPSTRING: { unsigned char *tmp = ""; /* initialize to workaround 0.9.6 bug */ int len; len = ASN1_STRING_to_UTF8(&tmp, ent->value); if (len > 0) { ne_buffer_append(dump, tmp, len); OPENSSL_free(tmp); break; } else { ERR_clear_error(); /* and fall through */ } } default: ne_buffer_zappend(dump, "???"); break; } } } return ne_buffer_finish(dump);}int ne_ssl_dname_cmp(const ne_ssl_dname *dn1, const ne_ssl_dname *dn2){ return X509_NAME_cmp(dn1->dn, dn2->dn);}void ne_ssl_clicert_free(ne_ssl_client_cert *cc){ if (cc->p12) PKCS12_free(cc->p12); if (cc->decrypted) { if (cc->cert.identity) ne_free(cc->cert.identity); EVP_PKEY_free(cc->pkey); X509_free(cc->cert.subject); } if (cc->friendly_name) ne_free(cc->friendly_name); ne_free(cc);}/* Map a server cert verification into a string. */static void verify_err(ne_session *sess, int failures){ struct { int bit; const char *str; } reasons[] = { { NE_SSL_NOTYETVALID, N_("certificate is not yet valid") }, { NE_SSL_EXPIRED, N_("certificate has expired") }, { NE_SSL_IDMISMATCH, N_("certificate issued for a different hostname") }, { NE_SSL_UNTRUSTED, N_("issuer is not trusted") }, { 0, NULL } }; int n, flag = 0; strcpy(sess->error, _("Server certificate verification failed: ")); for (n = 0; reasons[n].bit; n++) { if (failures & reasons[n].bit) { if (flag) strncat(sess->error, ", ", sizeof sess->error); strncat(sess->error, _(reasons[n].str), sizeof sess->error); flag = 1; } }}/* Format an ASN1 time to a string. 'buf' must be at least of size * 'NE_SSL_VDATELEN'. */static void asn1time_to_string(ASN1_TIME *tm, char *buf){ BIO *bio; strncpy(buf, _("[invalid date]"), NE_SSL_VDATELEN-1); bio = BIO_new(BIO_s_mem()); if (bio) { if (ASN1_TIME_print(bio, tm)) BIO_read(bio, buf, NE_SSL_VDATELEN-1); BIO_free(bio); }}void ne_ssl_cert_validity(const ne_ssl_certificate *cert, char *from, char *until){ ASN1_TIME *notBefore = X509_get_notBefore(cert->subject); ASN1_TIME *notAfter = X509_get_notAfter(cert->subject); if (from) asn1time_to_string(notBefore, from); if (until) asn1time_to_string(notAfter, until);}/* Return non-zero if hostname from certificate (cn) matches hostname * used for session (hostname). (Wildcard matching is no longer * mandated by RFC3280, but certs are deployed which use wildcards) */static int match_hostname(char *cn, const char *hostname){ const char *dot; NE_DEBUG(NE_DBG_SSL, "Match %s on %s...\n", cn, hostname); dot = strchr(hostname, '.'); if (dot == NULL) { char *pnt = strchr(cn, '.'); /* hostname is not fully-qualified; unqualify the cn. */ if (pnt != NULL) { *pnt = '\0'; } } else if (strncmp(cn, "*.", 2) == 0) { hostname = dot + 1; cn += 2; } return !strcasecmp(cn, hostname);}/* Check certificate identity. Returns zero if identity matches; 1 if * identity does not match, or <0 if the certificate had no identity. * If 'identity' is non-NULL, store the malloc-allocated identity in * *identity. If 'server' is non-NULL, it must be the network address * of the server in use, and identity must be NULL. */static int check_identity(const char *hostname, X509 *cert, char **identity, const ne_inet_addr *server){ STACK_OF(GENERAL_NAME) *names; int match = 0, found = 0; names = X509_get_ext_d2i(cert, NID_subject_alt_name, NULL, NULL); if (names) { int n; /* subjectAltName contains a sequence of GeneralNames */ for (n = 0; n < sk_GENERAL_NAME_num(names) && !match; n++) { GENERAL_NAME *nm = sk_GENERAL_NAME_value(names, n); /* handle dNSName and iPAddress name extensions only. */ if (nm->type == GEN_DNS) { char *name = ne_strndup(nm->d.ia5->data, nm->d.ia5->length); if (identity && !found) *identity = ne_strdup(name); match = match_hostname(name, hostname); ne_free(name); found = 1; } else if (nm->type == GEN_IPADD && server) { /* compare IP address with server IP address. */ ne_inet_addr *ia; if (nm->d.ip->length == 4) ia = ne_iaddr_make(ne_iaddr_ipv4, nm->d.ip->data); else if (nm->d.ip->length == 16) ia = ne_iaddr_make(ne_iaddr_ipv6, nm->d.ip->data); else ia = NULL; /* ne_iaddr_make returns NULL if address type is unsupported */ if (ia != NULL) { /* address type was supported. */ match = ne_iaddr_cmp(server, ia) == 0; found = 1; ne_iaddr_free(ia); } else { NE_DEBUG(NE_DBG_SSL, "iPAddress name with unsupported " "address type (length %d), skipped.\n", nm->d.ip->length); } } /* TODO: handle uniformResourceIdentifier too */ } /* free the whole stack. */ sk_GENERAL_NAME_pop_free(names, GENERAL_NAME_free); } /* Check against the commonName if no DNS alt. names were found, * as per RFC3280. */ if (!found) { X509_NAME *subj = X509_get_subject_name(cert); X509_NAME_ENTRY *entry; ASN1_STRING *str; int idx = -1, lastidx; char *name; /* find the most specific commonName attribute. */ do { lastidx = idx; idx = X509_NAME_get_index_by_NID(subj, NID_commonName, lastidx); } while (idx >= 0); if (lastidx < 0) return -1; /* extract the string from the entry */ entry = X509_NAME_get_entry(subj, lastidx); str = X509_NAME_ENTRY_get_data(entry); name = ne_strndup(str->data, str->length); if (identity) *identity = ne_strdup(name); match = match_hostname(name, hostname); ne_free(name); } NE_DEBUG(NE_DBG_SSL, "Identity match: %s\n", match ? "good" : "bad"); return match ? 0 : 1;}/* Populate an ne_ssl_certificate structure from an X509 object. */static ne_ssl_certificate *populate_cert(ne_ssl_certificate *cert, X509 *x5){ cert->subj_dn.dn = X509_get_subject_name(x5); cert->issuer_dn.dn = X509_get_issuer_name(x5); cert->issuer = NULL; cert->subject = x5; /* Retrieve the cert identity; pass a dummy hostname to match. */ cert->identity = NULL; check_identity("", x5, &cert->identity, NULL); return cert;}/* Return a linked list of certificate objects from an OpenSSL chain. */static ne_ssl_certificate *make_chain(STACK_OF(X509) *chain){ int n, count = sk_X509_num(chain); ne_ssl_certificate *top = NULL, *current = NULL; NE_DEBUG(NE_DBG_SSL, "Chain depth: %d\n", count); for (n = 0; n < count; n++) { ne_ssl_certificate *cert = ne_malloc(sizeof *cert); populate_cert(cert, X509_dup(sk_X509_value(chain, n)));#if NE_DEBUGGING if (ne_debug_mask & NE_DBG_SSL) { fprintf(ne_debug_stream, "Cert #%d:\n", n); X509_print_fp(ne_debug_stream, cert->subject); }#endif if (top == NULL) { current = top = cert; } else { current->issuer = cert; current = cert; } } return top;}/* Verifies an SSL server certificate. */static int check_certificate(ne_session *sess, SSL *ssl, ne_ssl_certificate *chain){ X509 *cert = chain->subject; ASN1_TIME *notBefore = X509_get_notBefore(cert); ASN1_TIME *notAfter = X509_get_notAfter(cert); int ret, failures = 0; long result; /* check expiry dates */ if (X509_cmp_current_time(notBefore) >= 0) failures |= NE_SSL_NOTYETVALID; else if (X509_cmp_current_time(notAfter) <= 0) failures |= NE_SSL_EXPIRED; /* Check certificate was issued to this server; pass network * address of server if a proxy is not in use. */ ret = check_identity(sess->server.hostname, cert, NULL, sess->use_proxy ? NULL : sess->server.current); if (ret < 0) { ne_set_error(sess, _("Server certificate was missing commonName " "attribute in subject name")); return NE_ERROR; } else if (ret > 0) failures |= NE_SSL_IDMISMATCH; /* get the result of the cert verification out of OpenSSL */ result = SSL_get_verify_result(ssl); NE_DEBUG(NE_DBG_SSL, "Verify result: %ld = %s\n", result, X509_verify_cert_error_string(result)); switch (result) { case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY: case X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN: case X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT: /* TODO: and probably more result codes here... */ failures |= NE_SSL_UNTRUSTED; break; /* ignore these, since we've already noticed them: */ case X509_V_ERR_CERT_NOT_YET_VALID: case X509_V_ERR_CERT_HAS_EXPIRED: /* cert was trusted: */ case X509_V_OK: break; default: /* TODO: tricky to handle the 30-odd failure cases OpenSSL * presents here (see x509_vfy.h), and present a useful API to * the application so it in turn can then present a meaningful * UI to the user. The only thing to do really would be to * pass back the error string, but that's not localisable. So * just fail the verification here - better safe than * sorry. */ ne_set_error(sess, _("Certificate verification error: %s"), X509_verify_cert_error_string(result)); return NE_ERROR; } if (failures == 0) { /* verified OK! */ ret = NE_OK; } else { /* Set up the error string. */ verify_err(sess, failures); ret = NE_ERROR; /* Allow manual override */ if (sess->ssl_verify_fn && sess->ssl_verify_fn(sess->ssl_verify_ud, failures, chain) == 0) ret = NE_OK; } return ret;}/* Duplicate a client certificate, which must be in the decrypted state. */static ne_ssl_client_cert *dup_client_cert(const ne_ssl_client_cert *cc){ ne_ssl_client_cert *newcc = ne_calloc(sizeof *newcc); newcc->decrypted = 1; newcc->pkey = cc->pkey; if (cc->friendly_name) newcc->friendly_name = ne_strdup(cc->friendly_name); populate_cert(&newcc->cert, cc->cert.subject); cc->cert.subject->references++; cc->pkey->references++; return newcc;
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -