diff options
Diffstat (limited to 'src/ne_openssl.c')
-rw-r--r-- | src/ne_openssl.c | 790 |
1 files changed, 790 insertions, 0 deletions
diff --git a/src/ne_openssl.c b/src/ne_openssl.c new file mode 100644 index 0000000..5d21ef1 --- /dev/null +++ b/src/ne_openssl.c @@ -0,0 +1,790 @@ +/* + neon SSL/TLS support using OpenSSL + Copyright (C) 2002-2003, 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" + +struct 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); + ne_buffer_append(dump, ent->value->data, ent->value->length); + flag = 1; + } + } + + 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). TODO: could do more advanced wildcard + * matching using fnmatch() here, if fnmatch is present. */ +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. */ +static int check_identity(const char *hostname, X509 *cert, char **identity) +{ + STACK_OF(GENERAL_NAME) *names; + int match = 0, found = 0; + + names = X509_get_ext_d2i(cert, NID_subject_alt_name, NULL, NULL); + if (names) { + /* Got a subject alt. name extension. */ + int n; + + for (n = 0; n < sk_GENERAL_NAME_num(names) && !match; n++) { + GENERAL_NAME *nm = sk_GENERAL_NAME_value(names, n); + + /* only care about this if it is a DNS name. */ + 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; + } + } + /* 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 RFC2818. */ + 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); + 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. */ + ret = check_identity(sess->server.hostname, cert, NULL); + 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; +} + +/* Callback invoked when the SSL server requests a client certificate. */ +static int provide_client_cert(SSL *ssl, X509 **cert, EVP_PKEY **pkey) +{ + ne_ssl_context *ctx = SSL_get_app_data(ssl); + ne_session *sess = SSL_CTX_get_app_data(ctx->ctx); + + if (!sess->client_cert && sess->ssl_provide_fn) { + ne_ssl_dname **dnames = NULL; + int n, count = 0; + STACK_OF(X509_NAME) *ca_list = SSL_get_client_CA_list(ssl); + + if (ca_list == NULL) { + /* no list of acceptable CA names provided. */ + dnames = NULL; + count = 0; + } else { + count = sk_X509_NAME_num(ca_list); + + dnames = ne_malloc(count * sizeof(ne_ssl_dname *)); + + for (n = 0; n < count; n++) { + dnames[n] = ne_malloc(sizeof(ne_ssl_dname)); + dnames[n]->dn = sk_X509_NAME_value(ca_list, n); + } + } + + NE_DEBUG(NE_DBG_SSL, "Calling client certificate provider...\n"); + sess->ssl_provide_fn(sess->ssl_provide_ud, sess, + (const ne_ssl_dname *const *)dnames, count); + if (count) { + for (n = 0; n < count; n++) + ne_free(dnames[n]); + ne_free(dnames); + } + } + + if (sess->client_cert) { + ne_ssl_client_cert *const cc = sess->client_cert; + NE_DEBUG(NE_DBG_SSL, "Supplying client certificate.\n"); + cc->pkey->references++; + cc->cert.subject->references++; + *cert = cc->cert.subject; + *pkey = cc->pkey; + return 1; + } else { + NE_DEBUG(NE_DBG_SSL, "No client certificate supplied.\n"); + return 0; + } +} + +void ne_ssl_set_clicert(ne_session *sess, const ne_ssl_client_cert *cc) +{ + sess->client_cert = dup_client_cert(cc); +} + +ne_ssl_context *ne_ssl_context_create(void) +{ + ne_ssl_context *ctx = ne_malloc(sizeof *ctx); + ctx->ctx = SSL_CTX_new(SSLv23_client_method()); + ctx->sess = NULL; + /* set client cert callback. */ + SSL_CTX_set_client_cert_cb(ctx->ctx, provide_client_cert); + /* enable workarounds for buggy SSL server implementations */ + SSL_CTX_set_options(ctx->ctx, SSL_OP_ALL); + return ctx; +} + +void ne_ssl_context_destroy(ne_ssl_context *ctx) +{ + SSL_CTX_free(ctx->ctx); + if (ctx->sess) + SSL_SESSION_free(ctx->sess); + ne_free(ctx); +} + +/* For internal use only. */ +int ne_negotiate_ssl(ne_request *req) +{ + ne_session *sess = ne_get_session(req); + ne_ssl_context *ctx = sess->ssl_context; + ne_ssl_socket *sock; + STACK_OF(X509) *chain; + + NE_DEBUG(NE_DBG_SSL, "Doing SSL negotiation.\n"); + + /* Rather a hack: link the ssl_context back to the ne_session, so + * provide_client_cert can get to the ne_session. */ + SSL_CTX_set_app_data(ctx->ctx, sess); + + if (ne_sock_connect_ssl(sess->socket, ctx)) { + if (ctx->sess) { + /* remove cached session. */ + SSL_SESSION_free(ctx->sess); + ctx->sess = NULL; + } + ne_set_error(sess, _("SSL negotiation failed: %s"), + ne_sock_error(sess->socket)); + return NE_ERROR; + } + + sock = ne_sock_sslsock(sess->socket); + + chain = SSL_get_peer_cert_chain(sock->ssl); + if (chain == NULL || sk_X509_num(chain) == 0) { + ne_set_error(sess, _("SSL server did not present certificate")); + return NE_ERROR; + } + + if (sess->server_cert) { + if (X509_cmp(sk_X509_value(chain, 0), sess->server_cert->subject)) { + /* This could be a MITM attack: fail the request. */ + ne_set_error(sess, _("Server certificate changed: " + "connection intercepted?")); + return NE_ERROR; + } + /* certificate has already passed verification: no need to + * verify it again. */ + } else { + /* new connection: create the chain. */ + ne_ssl_certificate *cert = make_chain(chain); + + if (check_certificate(sess, sock->ssl, cert)) { + NE_DEBUG(NE_DBG_SSL, "SSL certificate checks failed: %s\n", + sess->error); + ne_ssl_cert_free(cert); + return NE_ERROR; + } + /* remember the chain. */ + sess->server_cert = cert; + } + + if (!ctx->sess) { + /* store the session. */ + ctx->sess = SSL_get1_session(sock->ssl); + } + + if (sess->notify_cb) { + sess->notify_cb(sess->notify_ud, ne_conn_secure, + SSL_get_version(sock->ssl)); + } + + return NE_OK; +} + +const ne_ssl_dname *ne_ssl_cert_issuer(const ne_ssl_certificate *cert) +{ + return &cert->issuer_dn; +} + +const ne_ssl_dname *ne_ssl_cert_subject(const ne_ssl_certificate *cert) +{ + return &cert->subj_dn; +} + +const ne_ssl_certificate *ne_ssl_cert_signedby(const ne_ssl_certificate *cert) +{ + return cert->issuer; +} + +const char *ne_ssl_cert_identity(const ne_ssl_certificate *cert) +{ + return cert->identity; +} + +void ne_ssl_ctx_trustcert(ne_ssl_context *ctx, const ne_ssl_certificate *cert) +{ + X509_STORE *store = SSL_CTX_get_cert_store(ctx->ctx); + + X509_STORE_add_cert(store, cert->subject); +} + +void ne_ssl_trust_default_ca(ne_session *sess) +{ + X509_STORE *store = SSL_CTX_get_cert_store(sess->ssl_context->ctx); + + X509_STORE_set_default_paths(store); +} + +/* Find a friendly name in a PKCS12 structure the hard way, without + * decrypting the parts which are encrypted.. */ +static char *find_friendly_name(PKCS12 *p12) +{ + STACK_OF(PKCS7) *safes = PKCS12_unpack_authsafes(p12); + int n, m; + char *name = NULL; + + if (safes == NULL) return NULL; + + /* Iterate over the unpacked authsafes: */ + for (n = 0; n < sk_PKCS7_num(safes) && !name; n++) { + PKCS7 *safe = sk_PKCS7_value(safes, n); + STACK_OF(PKCS12_SAFEBAG) *bags; + + /* Only looking for unencrypted authsafes. */ + if (OBJ_obj2nid(safe->type) != NID_pkcs7_data) continue; + + bags = PKCS12_unpack_p7data(safe); + if (!bags) continue; + + /* Iterate through the bags, picking out a friendly name */ + for (m = 0; m < sk_PKCS12_SAFEBAG_num(bags) && !name; m++) { + PKCS12_SAFEBAG *bag = sk_PKCS12_SAFEBAG_value(bags, m); + name = PKCS12_get_friendlyname(bag); + } + + sk_PKCS12_SAFEBAG_pop_free(bags, PKCS12_SAFEBAG_free); + } + + sk_PKCS7_pop_free(safes, PKCS7_free); + return name; +} + +ne_ssl_client_cert *ne_ssl_clicert_read(const char *filename) +{ + PKCS12 *p12; + FILE *fp; + X509 *cert; + EVP_PKEY *pkey; + ne_ssl_client_cert *cc; + + fp = fopen(filename, "rb"); + if (fp == NULL) + return NULL; + + p12 = d2i_PKCS12_fp(fp, NULL); + + fclose(fp); + + if (p12 == NULL) { + ERR_clear_error(); + return NULL; + } + + /* Try parsing with no password. */ + if (PKCS12_parse(p12, NULL, &pkey, &cert, NULL) == 1) { + /* Success - no password needed for decryption. */ + unsigned int len = 0; + unsigned char *name = X509_alias_get0(cert, &len); + + cc = ne_calloc(sizeof *cc); + cc->pkey = pkey; + cc->decrypted = 1; + if (name && len) + cc->friendly_name = ne_strndup((char *)name, len); + populate_cert(&cc->cert, cert); + PKCS12_free(p12); + return cc; + } else { + /* Failed to parse the file */ + int err = ERR_get_error(); + ERR_clear_error(); + if (ERR_GET_LIB(err) == ERR_LIB_PKCS12 && + ERR_GET_REASON(err) == PKCS12_R_MAC_VERIFY_FAILURE) { + /* Decryption error due to bad password. */ + cc = ne_calloc(sizeof *cc); + cc->friendly_name = find_friendly_name(p12); + cc->p12 = p12; + return cc; + } else { + /* Some parse error, give up. */ + PKCS12_free(p12); + return NULL; + } + } +} + +int ne_ssl_clicert_encrypted(const ne_ssl_client_cert *cc) +{ + return !cc->decrypted; +} + +int ne_ssl_clicert_decrypt(ne_ssl_client_cert *cc, const char *password) +{ + X509 *cert; + EVP_PKEY *pkey; + + if (PKCS12_parse(cc->p12, password, &pkey, &cert, NULL) != 1) { + ERR_clear_error(); + return -1; + } + + PKCS12_free(cc->p12); + populate_cert(&cc->cert, cert); + cc->pkey = pkey; + cc->decrypted = 1; + cc->p12 = NULL; + return 0; +} + +const ne_ssl_certificate *ne_ssl_clicert_owner(const ne_ssl_client_cert *cc) +{ + return &cc->cert; +} + +const char *ne_ssl_clicert_name(ne_ssl_client_cert *ccert) +{ + return ccert->friendly_name; +} + +ne_ssl_certificate *ne_ssl_cert_read(const char *filename) +{ + FILE *fp = fopen(filename, "r"); + X509 *cert; + + if (fp == NULL) + return NULL; + + cert = PEM_read_X509(fp, NULL, NULL, NULL); + fclose(fp); + + if (cert == NULL) { + NE_DEBUG(NE_DBG_SSL, "d2i_X509_fp failed: %s\n", + ERR_reason_error_string(ERR_get_error())); + ERR_clear_error(); + return NULL; + } + + return populate_cert(ne_calloc(sizeof(struct ne_ssl_certificate_s)), cert); +} + +int ne_ssl_cert_write(const ne_ssl_certificate *cert, const char *filename) +{ + FILE *fp = fopen(filename, "w"); + + if (fp == NULL) return -1; + + if (PEM_write_X509(fp, cert->subject) != 1) { + fclose(fp); + return -1; + } + + if (fclose(fp) != 0) + return -1; + + return 0; +} + +void ne_ssl_cert_free(ne_ssl_certificate *cert) +{ + X509_free(cert->subject); + if (cert->issuer) + ne_ssl_cert_free(cert->issuer); + if (cert->identity) + ne_free(cert->identity); + ne_free(cert); +} + +int ne_ssl_cert_cmp(const ne_ssl_certificate *c1, const ne_ssl_certificate *c2) +{ + return X509_cmp(c1->subject, c2->subject); +} + +/* The certificate import/export format is the base64 encoding of the + * raw DER; PEM without the newlines and wrapping. */ + +ne_ssl_certificate *ne_ssl_cert_import(const char *data) +{ + unsigned char *der, *p; + size_t len; + X509 *x5; + + /* decode the base64 to get the raw DER representation */ + len = ne_unbase64(data, &der); + if (len == 0) return NULL; + + p = der; + x5 = d2i_X509(NULL, &p, len); /* p is incremented */ + ne_free(der); + if (x5 == NULL) { + ERR_clear_error(); + return NULL; + } + + return populate_cert(ne_calloc(sizeof(struct ne_ssl_certificate_s)), x5); +} + +char *ne_ssl_cert_export(const ne_ssl_certificate *cert) +{ + int len; + unsigned char *der, *p; + + /* find the length of the DER encoding. */ + len = i2d_X509(cert->subject, NULL); + + p = der = ne_malloc(len); + i2d_X509(cert->subject, &p); /* p is incremented */ + + p = ne_base64(der, len); + ne_free(der); + return p; +} + +#if SHA_DIGEST_LENGTH != 20 +# error SHA digest length is not 20 bytes +#endif + +int ne_ssl_cert_digest(const ne_ssl_certificate *cert, char *digest) +{ + unsigned char sha1[EVP_MAX_MD_SIZE]; + unsigned int len, j; + char *p; + + if (!X509_digest(cert->subject, EVP_sha1(), sha1, &len) || len != 20) { + ERR_clear_error(); + return -1; + } + + for (j = 0, p = digest; j < 20; j++) { + *p++ = NE_HEX2ASC((sha1[j] >> 4) & 0x0f); + *p++ = NE_HEX2ASC(sha1[j] & 0x0f); + *p++ = ':'; + } + + *--p = '\0'; + return 0; +} |