base
This commit is contained in:
@@ -0,0 +1,853 @@
|
||||
/*
|
||||
neon SSL/TLS support using GNU TLS
|
||||
Copyright (C) 2002-2005, Joe Orton <joe@manyfish.co.uk>
|
||||
Copyright (C) 2004, Aleix Conchillo Flaque <aleix@member.fsf.org>
|
||||
|
||||
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/mman.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#ifdef HAVE_STRING_H
|
||||
#include <string.h>
|
||||
#endif
|
||||
|
||||
#include <stdio.h>
|
||||
//#include <unistd.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
//#include <gnutls/gnutls.h>
|
||||
//#include <gnutls/pkcs12.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 {
|
||||
int subject; /* non-zero if this is the subject DN object */
|
||||
gnutls_x509_crt cert;
|
||||
};
|
||||
|
||||
struct ne_ssl_certificate_s {
|
||||
ne_ssl_dname subj_dn, issuer_dn;
|
||||
gnutls_x509_crt subject;
|
||||
ne_ssl_certificate *issuer;
|
||||
char *identity;
|
||||
};
|
||||
|
||||
struct ne_ssl_client_cert_s {
|
||||
gnutls_pkcs12 p12;
|
||||
int decrypted; /* non-zero if successfully decrypted. */
|
||||
ne_ssl_certificate cert;
|
||||
gnutls_x509_privkey pkey;
|
||||
char *friendly_name;
|
||||
};
|
||||
|
||||
/* Returns the highest used index in subject (or issuer) DN of
|
||||
* certificate CERT for OID, or -1 if no RDNs are present in the DN
|
||||
* using that OID. */
|
||||
static int oid_find_highest_index(gnutls_x509_crt cert, int subject, const char *oid)
|
||||
{
|
||||
int ret, idx = -1;
|
||||
|
||||
do {
|
||||
size_t len = 0;
|
||||
|
||||
if (subject)
|
||||
ret = gnutls_x509_crt_get_dn_by_oid(cert, oid, ++idx, 0,
|
||||
NULL, &len);
|
||||
else
|
||||
ret = gnutls_x509_crt_get_issuer_dn_by_oid(cert, oid, ++idx, 0,
|
||||
NULL, &len);
|
||||
} while (ret == GNUTLS_E_SHORT_MEMORY_BUFFER);
|
||||
|
||||
return idx - 1;
|
||||
}
|
||||
|
||||
/* Appends the value of RDN with given oid from certitifcate x5
|
||||
* subject (if subject is non-zero), or issuer DN to buffer 'buf': */
|
||||
static void append_rdn(ne_buffer *buf, gnutls_x509_crt x5, int subject, const char *oid)
|
||||
{
|
||||
int idx, top, ret;
|
||||
char rdn[50];
|
||||
|
||||
top = oid_find_highest_index(x5, subject, oid);
|
||||
|
||||
for (idx = top; idx >= 0; idx--) {
|
||||
size_t rdnlen = sizeof rdn;
|
||||
|
||||
if (subject)
|
||||
ret = gnutls_x509_crt_get_dn_by_oid(x5, oid, idx, 0, rdn, &rdnlen);
|
||||
else
|
||||
ret = gnutls_x509_crt_get_issuer_dn_by_oid(x5, oid, idx, 0, rdn, &rdnlen);
|
||||
|
||||
if (ret < 0)
|
||||
return;
|
||||
|
||||
if (buf->used > 1) {
|
||||
ne_buffer_append(buf, ", ", 2);
|
||||
}
|
||||
|
||||
ne_buffer_append(buf, rdn, rdnlen);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
char *ne_ssl_readable_dname(const ne_ssl_dname *name)
|
||||
{
|
||||
ne_buffer *buf = ne_buffer_create();
|
||||
#if 0
|
||||
/* this code can be used once there is a released version of GnuTLS
|
||||
* with fixed _get_dn_oid functions */
|
||||
int ret, idx = 0;
|
||||
|
||||
do {
|
||||
char oid[32] = {0};
|
||||
size_t oidlen = sizeof oid;
|
||||
|
||||
ret = name->subject
|
||||
? gnutls_x509_crt_get_dn_oid(name->cert, idx, oid, &oidlen)
|
||||
: gnutls_x509_crt_get_issuer_dn_oid(name->cert, idx, oid, &oidlen);
|
||||
|
||||
if (ret == 0) {
|
||||
append_rdn(buf, name->cert, name->subject, oid);
|
||||
idx++;
|
||||
}
|
||||
} while (ret != GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE);
|
||||
#else
|
||||
|
||||
#define APPEND_RDN(x) append_rdn(buf, name->cert, name->subject, GNUTLS_OID_##x)
|
||||
|
||||
APPEND_RDN(X520_ORGANIZATIONAL_UNIT_NAME);
|
||||
APPEND_RDN(X520_ORGANIZATION_NAME);
|
||||
APPEND_RDN(X520_LOCALITY_NAME);
|
||||
APPEND_RDN(X520_STATE_OR_PROVINCE_NAME);
|
||||
APPEND_RDN(X520_COUNTRY_NAME);
|
||||
|
||||
if (buf->used == 1) APPEND_RDN(X520_COMMON_NAME);
|
||||
if (buf->used == 1) APPEND_RDN(PKCS9_EMAIL);
|
||||
|
||||
#undef APPEND_RDN
|
||||
#endif
|
||||
|
||||
return ne_buffer_finish(buf);
|
||||
}
|
||||
|
||||
int ne_ssl_dname_cmp(const ne_ssl_dname *dn1, const ne_ssl_dname *dn2)
|
||||
{
|
||||
#warning incomplete
|
||||
return 1;
|
||||
}
|
||||
|
||||
void ne_ssl_clicert_free(ne_ssl_client_cert *cc)
|
||||
{
|
||||
if (cc->p12)
|
||||
gnutls_pkcs12_deinit(cc->p12);
|
||||
if (cc->decrypted) {
|
||||
if (cc->cert.identity) ne_free(cc->cert.identity);
|
||||
if (cc->pkey) gnutls_x509_privkey_deinit(cc->pkey);
|
||||
if (cc->cert.subject) gnutls_x509_crt_deinit(cc->cert.subject);
|
||||
}
|
||||
if (cc->friendly_name) ne_free(cc->friendly_name);
|
||||
ne_free(cc);
|
||||
}
|
||||
|
||||
void ne_ssl_cert_validity(const ne_ssl_certificate *cert,
|
||||
char *from, char *until)
|
||||
{
|
||||
#warning FIXME strftime not portable
|
||||
if (from) {
|
||||
time_t t = gnutls_x509_crt_get_activation_time(cert->subject);
|
||||
strftime(from, NE_SSL_VDATELEN, "%b %d %H:%M:%S %Y %Z", localtime(&t));
|
||||
}
|
||||
if (until) {
|
||||
time_t t = gnutls_x509_crt_get_expiration_time(cert->subject);
|
||||
strftime(until, NE_SSL_VDATELEN, "%b %d %H:%M:%S %Y %Z", localtime(&t));
|
||||
}
|
||||
}
|
||||
|
||||
/* 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, gnutls_x509_crt cert,
|
||||
char **identity)
|
||||
{
|
||||
char name[255];
|
||||
unsigned int critical;
|
||||
int ret, seq = 0;
|
||||
int match = 0, found = 0;
|
||||
size_t len;
|
||||
|
||||
do {
|
||||
len = sizeof name;
|
||||
ret = gnutls_x509_crt_get_subject_alt_name(cert, seq, name, &len,
|
||||
&critical);
|
||||
switch (ret) {
|
||||
case GNUTLS_SAN_DNSNAME:
|
||||
if (identity && !found) *identity = ne_strdup(name);
|
||||
match = match_hostname(name, hostname);
|
||||
found = 1;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
seq++;
|
||||
} while (!match && ret != GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE);
|
||||
|
||||
/* Check against the commonName if no DNS alt. names were found,
|
||||
* as per RFC3280. */
|
||||
if (!found) {
|
||||
seq = oid_find_highest_index(cert, 1, GNUTLS_OID_X520_COMMON_NAME);
|
||||
|
||||
if (seq >= 0) {
|
||||
len = sizeof name;
|
||||
name[0] = '\0';
|
||||
ret = gnutls_x509_crt_get_dn_by_oid(cert, GNUTLS_OID_X520_COMMON_NAME,
|
||||
seq, 0, name, &len);
|
||||
if (ret == 0) {
|
||||
if (identity) *identity = ne_strdup(name);
|
||||
match = match_hostname(name, hostname);
|
||||
}
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
gnutls_x509_crt x5)
|
||||
{
|
||||
cert->subj_dn.cert = x5;
|
||||
cert->subj_dn.subject = 1;
|
||||
cert->issuer_dn.cert = x5;
|
||||
cert->issuer_dn.subject = 0;
|
||||
cert->issuer = NULL;
|
||||
cert->subject = x5;
|
||||
cert->identity = NULL;
|
||||
check_identity("", x5, &cert->identity);
|
||||
return cert;
|
||||
}
|
||||
|
||||
/* Returns a copy certificate of certificate SRC. */
|
||||
static gnutls_x509_crt x509_crt_copy(gnutls_x509_crt src)
|
||||
{
|
||||
int ret;
|
||||
size_t size;
|
||||
gnutls_datum tmp;
|
||||
gnutls_x509_crt dest;
|
||||
|
||||
if (gnutls_x509_crt_init(&dest) == 0) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (gnutls_x509_crt_export(src, GNUTLS_X509_FMT_DER, NULL, &size)
|
||||
!= GNUTLS_E_SHORT_MEMORY_BUFFER) {
|
||||
gnutls_x509_crt_deinit(dest);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
tmp.data = ne_malloc(size);
|
||||
ret = gnutls_x509_crt_export(src, GNUTLS_X509_FMT_DER, tmp.data, &size);
|
||||
if (ret == 0) {
|
||||
tmp.size = size;
|
||||
ret = gnutls_x509_crt_import(dest, &tmp, GNUTLS_X509_FMT_DER);
|
||||
}
|
||||
|
||||
if (ret) {
|
||||
gnutls_x509_crt_deinit(dest);
|
||||
dest = NULL;
|
||||
}
|
||||
|
||||
ne_free(tmp.data);
|
||||
return dest;
|
||||
}
|
||||
|
||||
/* 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)
|
||||
{
|
||||
int ret;
|
||||
ne_ssl_client_cert *newcc = ne_calloc(sizeof *newcc);
|
||||
|
||||
newcc->decrypted = 1;
|
||||
|
||||
ret = gnutls_x509_privkey_init(&newcc->pkey);
|
||||
if (ret != 0) goto dup_error;
|
||||
|
||||
ret = gnutls_x509_privkey_cpy(newcc->pkey, cc->pkey);
|
||||
if (ret != 0) goto dup_error;
|
||||
|
||||
newcc->cert.subject = x509_crt_copy(cc->cert.subject);
|
||||
if (!newcc->cert.subject) goto dup_error;
|
||||
|
||||
if (cc->friendly_name) newcc->friendly_name = ne_strdup(cc->friendly_name);
|
||||
|
||||
populate_cert(&newcc->cert, newcc->cert.subject);
|
||||
return newcc;
|
||||
|
||||
dup_error:
|
||||
if (newcc->pkey) gnutls_x509_privkey_deinit(newcc->pkey);
|
||||
if (newcc->cert.subject) gnutls_x509_crt_deinit(newcc->cert.subject);
|
||||
ne_free(newcc);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
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(int flags)
|
||||
{
|
||||
ne_ssl_context *ctx = ne_malloc(sizeof *ctx);
|
||||
gnutls_certificate_allocate_credentials(&ctx->cred);
|
||||
return ctx;
|
||||
}
|
||||
|
||||
int ne_ssl_context_keypair(ne_ssl_context *ctx,
|
||||
const char *cert, const char *key)
|
||||
{
|
||||
gnutls_certificate_set_x509_key_file(ctx->cred, cert, key,
|
||||
GNUTLS_X509_FMT_PEM);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ne_ssl_context_set_verify(ne_ssl_context *ctx, int required,
|
||||
const char *ca_names, const char *verify_cas)
|
||||
{
|
||||
if (verify_cas) {
|
||||
gnutls_certificate_set_x509_trust_file(ctx->cred, verify_cas,
|
||||
GNUTLS_X509_FMT_PEM);
|
||||
}
|
||||
#warning argh
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
void ne_ssl_context_destroy(ne_ssl_context *ctx)
|
||||
{
|
||||
gnutls_certificate_free_credentials(ctx->cred);
|
||||
ne_free(ctx);
|
||||
}
|
||||
|
||||
/* Return the certificate chain sent by the peer, or NULL on error. */
|
||||
static ne_ssl_certificate *make_peers_chain(gnutls_session sock)
|
||||
{
|
||||
ne_ssl_certificate *current = NULL, *top = NULL;
|
||||
const gnutls_datum *certs;
|
||||
unsigned int n, count;
|
||||
|
||||
certs = gnutls_certificate_get_peers(sock, &count);
|
||||
if (!certs) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
for (n = 0; n < count; n++) {
|
||||
ne_ssl_certificate *cert = ne_malloc(sizeof *cert);
|
||||
gnutls_x509_crt x5;
|
||||
|
||||
if (gnutls_x509_crt_init(&x5) ||
|
||||
gnutls_x509_crt_import(x5, &certs[n], GNUTLS_X509_FMT_DER)) {
|
||||
/* leak! */
|
||||
return NULL;
|
||||
}
|
||||
|
||||
populate_cert(cert, x5);
|
||||
|
||||
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, gnutls_session sock,
|
||||
ne_ssl_certificate *chain)
|
||||
{
|
||||
time_t before, after, now = time(NULL);
|
||||
int ret, failures = 0;
|
||||
|
||||
before = gnutls_x509_crt_get_activation_time(chain->subject);
|
||||
after = gnutls_x509_crt_get_expiration_time(chain->subject);
|
||||
|
||||
if (now < before)
|
||||
failures |= NE_SSL_NOTYETVALID;
|
||||
else if (now > after)
|
||||
failures |= NE_SSL_EXPIRED;
|
||||
|
||||
ret = check_identity(sess->server.hostname, chain->subject, 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;
|
||||
}
|
||||
|
||||
if (gnutls_certificate_verify_peers(sock)) {
|
||||
failures |= NE_SSL_UNTRUSTED;
|
||||
}
|
||||
|
||||
NE_DEBUG(NE_DBG_SSL, "Failures = %d\n", failures);
|
||||
|
||||
if (failures == 0) {
|
||||
ret = NE_OK;
|
||||
} else {
|
||||
#warning TODO: set up error string
|
||||
ret = NE_ERROR;
|
||||
if (sess->ssl_verify_fn
|
||||
&& sess->ssl_verify_fn(sess->ssl_verify_ud, failures, chain) == 0)
|
||||
ret = NE_OK;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Negotiate an SSL connection. */
|
||||
int ne__negotiate_ssl(ne_request *req)
|
||||
{
|
||||
ne_session *const sess = ne_get_session(req);
|
||||
ne_ssl_context *const ctx = sess->ssl_context;
|
||||
ne_ssl_certificate *chain;
|
||||
gnutls_session sock;
|
||||
|
||||
NE_DEBUG(NE_DBG_SSL, "Negotiating SSL connection.\n");
|
||||
|
||||
if (ne_sock_connect_ssl(sess->socket, ctx, sess)) {
|
||||
ne_set_error(sess, _("SSL negotiation failed: %s"),
|
||||
ne_sock_error(sess->socket));
|
||||
return NE_ERROR;
|
||||
}
|
||||
|
||||
sock = ne__sock_sslsock(sess->socket);
|
||||
|
||||
chain = make_peers_chain(sock);
|
||||
if (chain == NULL) {
|
||||
ne_set_error(sess, _("Server did not send certificate chain"));
|
||||
return NE_ERROR;
|
||||
}
|
||||
|
||||
if (check_certificate(sess, sock, chain)) {
|
||||
ne_ssl_cert_free(chain);
|
||||
return NE_ERROR;
|
||||
}
|
||||
|
||||
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_context_trustcert(ne_ssl_context *ctx, const ne_ssl_certificate *cert)
|
||||
{
|
||||
gnutls_x509_crt certs = cert->subject;
|
||||
gnutls_certificate_set_x509_trust(ctx->cred, &certs, 1);
|
||||
}
|
||||
|
||||
void ne_ssl_trust_default_ca(ne_session *sess)
|
||||
{
|
||||
#warning incomplete
|
||||
}
|
||||
|
||||
/* Read the contents of file FILENAME into *DATUM. */
|
||||
static int read_to_datum(const char *filename, gnutls_datum *datum)
|
||||
{
|
||||
FILE *f = fopen(filename, "r");
|
||||
ne_buffer *buf;
|
||||
char tmp[4192];
|
||||
size_t len;
|
||||
|
||||
if (!f) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
buf = ne_buffer_ncreate(8192);
|
||||
while ((len = fread(tmp, 1, sizeof tmp, f)) > 0) {
|
||||
ne_buffer_append(buf, tmp, len);
|
||||
}
|
||||
|
||||
if (!feof(f)) {
|
||||
ne_buffer_destroy(buf);
|
||||
return -1;
|
||||
}
|
||||
|
||||
datum->size = ne_buffer_size(buf);
|
||||
datum->data = ne_buffer_finish(buf);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Parses a PKCS#12 structure and loads the certificate, private key
|
||||
* and friendly name if possible. Returns zero on success, non-zero
|
||||
* on error. */
|
||||
static int pkcs12_parse(gnutls_pkcs12 p12, gnutls_x509_privkey *pkey,
|
||||
gnutls_x509_crt *x5, char **friendly_name,
|
||||
const char *password)
|
||||
{
|
||||
gnutls_pkcs12_bag bag = NULL;
|
||||
int i, j, ret = 0;
|
||||
|
||||
for (i = 0; ret == 0; ++i) {
|
||||
if (bag) gnutls_pkcs12_bag_deinit(bag);
|
||||
|
||||
ret = gnutls_pkcs12_bag_init(&bag);
|
||||
if (ret < 0) continue;
|
||||
|
||||
ret = gnutls_pkcs12_get_bag(p12, i, bag);
|
||||
if (ret < 0) continue;
|
||||
|
||||
gnutls_pkcs12_bag_decrypt(bag, password == NULL ? "" : password);
|
||||
|
||||
for (j = 0; ret == 0 && j < gnutls_pkcs12_bag_get_count(bag); ++j) {
|
||||
gnutls_pkcs12_bag_type type;
|
||||
gnutls_datum data;
|
||||
|
||||
if (friendly_name && *friendly_name == NULL) {
|
||||
char *name;
|
||||
gnutls_pkcs12_bag_get_friendly_name(bag, j, &name);
|
||||
if (name) {
|
||||
if (name[0] == '.') name++; /* weird GnuTLS bug? */
|
||||
*friendly_name = ne_strdup(name);
|
||||
}
|
||||
}
|
||||
|
||||
type = gnutls_pkcs12_bag_get_type(bag, j);
|
||||
switch (type) {
|
||||
case GNUTLS_BAG_PKCS8_KEY:
|
||||
case GNUTLS_BAG_PKCS8_ENCRYPTED_KEY:
|
||||
gnutls_x509_privkey_init(pkey);
|
||||
|
||||
ret = gnutls_pkcs12_bag_get_data(bag, j, &data);
|
||||
if (ret < 0) continue;
|
||||
|
||||
ret = gnutls_x509_privkey_import_pkcs8(*pkey, &data,
|
||||
GNUTLS_X509_FMT_DER,
|
||||
password,
|
||||
0);
|
||||
if (ret < 0) continue;
|
||||
break;
|
||||
case GNUTLS_BAG_CERTIFICATE:
|
||||
gnutls_x509_crt_init(x5);
|
||||
|
||||
ret = gnutls_pkcs12_bag_get_data(bag, j, &data);
|
||||
if (ret < 0) continue;
|
||||
|
||||
ret = gnutls_x509_crt_import(*x5, &data, GNUTLS_X509_FMT_DER);
|
||||
if (ret < 0) continue;
|
||||
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Make sure last bag is freed */
|
||||
if (bag) gnutls_pkcs12_bag_deinit(bag);
|
||||
|
||||
/* Free in case of error */
|
||||
if (ret < 0 && ret != GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE) {
|
||||
if (*x5) gnutls_x509_crt_deinit(*x5);
|
||||
if (*pkey) gnutls_x509_privkey_deinit(*pkey);
|
||||
if (friendly_name && *friendly_name) ne_free(*friendly_name);
|
||||
}
|
||||
|
||||
if (ret == GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE) ret = 0;
|
||||
return ret;
|
||||
}
|
||||
|
||||
ne_ssl_client_cert *ne_ssl_clicert_read(const char *filename)
|
||||
{
|
||||
int ret;
|
||||
gnutls_datum data;
|
||||
gnutls_pkcs12 p12;
|
||||
ne_ssl_client_cert *cc;
|
||||
char *friendly_name = NULL;
|
||||
gnutls_x509_crt cert = NULL;
|
||||
gnutls_x509_privkey pkey = NULL;
|
||||
|
||||
if (read_to_datum(filename, &data))
|
||||
return NULL;
|
||||
|
||||
if (gnutls_pkcs12_init(&p12) != 0) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ret = gnutls_pkcs12_import(p12, &data, GNUTLS_X509_FMT_DER, 0);
|
||||
ne_free(data.data);
|
||||
if (ret < 0) {
|
||||
gnutls_pkcs12_deinit(p12);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ret = pkcs12_parse(p12, &pkey, &cert, &friendly_name, NULL);
|
||||
|
||||
if (gnutls_pkcs12_verify_mac(p12, "") == 0) {
|
||||
cc = ne_calloc(sizeof *cc);
|
||||
cc->pkey = pkey;
|
||||
cc->decrypted = 1;
|
||||
cc->friendly_name = friendly_name;
|
||||
populate_cert(&cc->cert, cert);
|
||||
gnutls_pkcs12_deinit(p12);
|
||||
cc->p12 = NULL;
|
||||
return cc;
|
||||
} else {
|
||||
if (ret == 0) {
|
||||
cc = ne_calloc(sizeof *cc);
|
||||
cc->friendly_name = friendly_name;
|
||||
cc->p12 = p12;
|
||||
return cc;
|
||||
} else {
|
||||
gnutls_pkcs12_deinit(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)
|
||||
{
|
||||
int ret;
|
||||
gnutls_x509_crt cert = NULL;
|
||||
gnutls_x509_privkey pkey = NULL;
|
||||
|
||||
if (gnutls_pkcs12_verify_mac(cc->p12, password) != 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
ret = pkcs12_parse(cc->p12, &pkey, &cert, NULL, password);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
gnutls_pkcs12_deinit(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(const ne_ssl_client_cert *ccert)
|
||||
{
|
||||
return ccert->friendly_name;
|
||||
}
|
||||
|
||||
ne_ssl_certificate *ne_ssl_cert_read(const char *filename)
|
||||
{
|
||||
int ret;
|
||||
gnutls_datum data;
|
||||
gnutls_x509_crt x5;
|
||||
|
||||
if (read_to_datum(filename, &data))
|
||||
return NULL;
|
||||
|
||||
if (gnutls_x509_crt_init(&x5) != 0)
|
||||
return NULL;
|
||||
|
||||
ret = gnutls_x509_crt_import(x5, &data, GNUTLS_X509_FMT_PEM);
|
||||
ne_free(data.data);
|
||||
if (ret < 0) {
|
||||
gnutls_x509_crt_deinit(x5);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return populate_cert(ne_calloc(sizeof(struct ne_ssl_certificate_s)), x5);
|
||||
}
|
||||
|
||||
int ne_ssl_cert_write(const ne_ssl_certificate *cert, const char *filename)
|
||||
{
|
||||
unsigned char buffer[10*1024];
|
||||
int len = sizeof buffer;
|
||||
|
||||
FILE *fp = fopen(filename, "w");
|
||||
|
||||
if (fp == NULL) return -1;
|
||||
|
||||
if (gnutls_x509_crt_export(cert->subject, GNUTLS_X509_FMT_PEM, buffer,
|
||||
&len) < 0) {
|
||||
fclose(fp);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (fwrite(buffer, len, 1, fp) != 1) {
|
||||
fclose(fp);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (fclose(fp) != 0)
|
||||
return -1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void ne_ssl_cert_free(ne_ssl_certificate *cert)
|
||||
{
|
||||
gnutls_x509_crt_deinit(cert->subject);
|
||||
if (cert->identity) ne_free(cert->identity);
|
||||
if (cert->issuer) ne_ssl_cert_free(cert->issuer);
|
||||
ne_free(cert);
|
||||
}
|
||||
|
||||
int ne_ssl_cert_cmp(const ne_ssl_certificate *c1, const ne_ssl_certificate *c2)
|
||||
{
|
||||
char digest1[NE_SSL_DIGESTLEN], digest2[NE_SSL_DIGESTLEN];
|
||||
|
||||
if (ne_ssl_cert_digest(c1, digest1) || ne_ssl_cert_digest(c2, digest2)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return strcmp(digest1, digest2);
|
||||
}
|
||||
|
||||
/* 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)
|
||||
{
|
||||
int ret;
|
||||
size_t len;
|
||||
unsigned char *der;
|
||||
gnutls_datum buffer = { NULL, 0 };
|
||||
gnutls_x509_crt x5;
|
||||
|
||||
if (gnutls_x509_crt_init(&x5) != 0)
|
||||
return NULL;
|
||||
|
||||
/* decode the base64 to get the raw DER representation */
|
||||
len = ne_unbase64(data, &der);
|
||||
if (len == 0) return NULL;
|
||||
|
||||
buffer.data = der;
|
||||
buffer.size = len;
|
||||
|
||||
ret = gnutls_x509_crt_import(x5, &buffer, GNUTLS_X509_FMT_DER);
|
||||
ne_free(der);
|
||||
|
||||
if (ret < 0) {
|
||||
gnutls_x509_crt_deinit(x5);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return populate_cert(ne_calloc(sizeof(struct ne_ssl_certificate_s)), x5);
|
||||
}
|
||||
|
||||
char *ne_ssl_cert_export(const ne_ssl_certificate *cert)
|
||||
{
|
||||
unsigned char *der;
|
||||
size_t len = 0;
|
||||
char *ret;
|
||||
|
||||
/* find the length of the DER encoding. */
|
||||
if (gnutls_x509_crt_export(cert->subject, GNUTLS_X509_FMT_DER, NULL, &len) !=
|
||||
GNUTLS_E_SHORT_MEMORY_BUFFER) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
der = ne_malloc(len);
|
||||
if (gnutls_x509_crt_export(cert->subject, GNUTLS_X509_FMT_DER, der, &len)) {
|
||||
ne_free(der);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ret = ne_base64(der, len);
|
||||
ne_free(der);
|
||||
return ret;
|
||||
}
|
||||
|
||||
int ne_ssl_cert_digest(const ne_ssl_certificate *cert, char *digest)
|
||||
{
|
||||
int j, len = 20;
|
||||
char sha1[20], *p;
|
||||
|
||||
if (gnutls_x509_crt_get_fingerprint(cert->subject, GNUTLS_DIG_SHA,
|
||||
sha1, &len) < 0)
|
||||
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;
|
||||
}
|
||||
Reference in New Issue
Block a user