aboutsummaryrefslogtreecommitdiff
path: root/lib/ssluse.c
diff options
context:
space:
mode:
authorDaniel Stenberg <daniel@haxx.se>2008-09-05 14:29:21 +0000
committerDaniel Stenberg <daniel@haxx.se>2008-09-05 14:29:21 +0000
commit4c9768565ec3a9baf26ac8a547bca6e42cc64fa5 (patch)
tree9713affdba06e0ff43abe1f1bdb01bdc3b4cf5a7 /lib/ssluse.c
parent873e734c3971fd620c09bdc31c2e7e4cfc4a0a67 (diff)
- Introducing CURLOPT_CERTINFO and the corresponding CURLINFO_CERTINFO. By
enabling this feature with CURLOPT_CERTINFO for a request using SSL (HTTPS or FTPS), libcurl will gather lots of server certificate info and that info can then get extracted by a client after the request has completed with curl_easy_getinfo()'s CURLINFO_CERTINFO option. Linus Nielsen Feltzing helped me test and smoothen out this feature. Unfortunately, this feature currently only works with libcurl built to use OpenSSL. This feature was sponsored by networking4all.com - thanks!
Diffstat (limited to 'lib/ssluse.c')
-rw-r--r--lib/ssluse.c434
1 files changed, 410 insertions, 24 deletions
diff --git a/lib/ssluse.c b/lib/ssluse.c
index cee78bbcc..cfa761e62 100644
--- a/lib/ssluse.c
+++ b/lib/ssluse.c
@@ -554,6 +554,33 @@ int cert_stuff(struct connectdata *conn,
return(1);
}
+/* returns non-zero on failure */
+static int x509_name_oneline(X509_NAME *a, char *buf, int size)
+{
+#if 0
+ return X509_NAME_oneline(a, buf, size);
+#else
+ BIO *bio_out = BIO_new(BIO_s_mem());
+ BUF_MEM *biomem;
+ int rc;
+
+ rc = X509_NAME_print_ex(bio_out, a, 0, XN_FLAG_SEP_CPLUS_SPC);
+ BIO_get_mem_ptr(bio_out, &biomem);
+
+ if(biomem->length < size)
+ size = biomem->length;
+ else
+ size--; /* don't overwrite the buffer end */
+
+ memcpy(buf, biomem->data, size);
+ buf[size]=0;
+
+ BIO_free(bio_out);
+
+ return !rc;
+#endif
+}
+
static
int cert_verify_callback(int ok, X509_STORE_CTX *ctx)
{
@@ -561,7 +588,7 @@ int cert_verify_callback(int ok, X509_STORE_CTX *ctx)
char buf[256];
err_cert=X509_STORE_CTX_get_current_cert(ctx);
- X509_NAME_oneline(X509_get_subject_name(err_cert), buf, sizeof(buf));
+ (void)x509_name_oneline(X509_get_subject_name(err_cert), buf, sizeof(buf));
return ok;
}
@@ -886,23 +913,19 @@ int Curl_ossl_close_all(struct SessionHandle *data)
return 0;
}
-static int asn1_output(struct connectdata *conn,
- const char *prefix,
- const ASN1_UTCTIME *tm)
+static int asn1_output(const ASN1_UTCTIME *tm,
+ char *buf,
+ size_t sizeofbuf)
{
const char *asn1_string;
int gmt=FALSE;
int i;
int year=0,month=0,day=0,hour=0,minute=0,second=0;
- struct SessionHandle *data = conn->data;
#ifdef CURL_DISABLE_VERBOSE_STRINGS
(void)prefix;
#endif
- if(!data->set.verbose)
- return 0;
-
i=tm->length;
asn1_string=(const char *)tm->data;
@@ -930,9 +953,9 @@ static int asn1_output(struct connectdata *conn,
(asn1_string[11] >= '0') && (asn1_string[11] <= '9'))
second= (asn1_string[10]-'0')*10+(asn1_string[11]-'0');
- infof(data,
- "%s%04d-%02d-%02d %02d:%02d:%02d %s\n",
- prefix, year+1900, month, day, hour, minute, second, (gmt?"GMT":""));
+ snprintf(buf, sizeofbuf,
+ "%04d-%02d-%02d %02d:%02d:%02d %s",
+ year+1900, month, day, hour, minute, second, (gmt?"GMT":""));
return 0;
}
@@ -1619,6 +1642,364 @@ ossl_connect_step2(struct connectdata *conn, int sockindex)
}
}
+static int asn1_object_dump(ASN1_OBJECT *a, char *buf, size_t len)
+{
+ int i = i2t_ASN1_OBJECT(buf, len, a);
+ if (i >= (int)len)
+ return 1; /* too small buffer! */
+ return 0;
+}
+
+static CURLcode push_certinfo_len(struct SessionHandle *data,
+ int certnum,
+ const char *label,
+ const char *value,
+ size_t valuelen)
+{
+ struct curl_certinfo *ci = &data->info.certs;
+ char *outp;
+ struct curl_slist *nl;
+ CURLcode res = CURLE_OK;
+ size_t labellen = strlen(label);
+ size_t outlen = labellen + 1 + valuelen + 1; /* label:value\0 */
+
+ outp = malloc(outlen);
+ if(!outp)
+ return CURLE_OUT_OF_MEMORY;
+
+ /* sprintf the label and colon */
+ snprintf(outp, outlen, "%s:", label);
+
+ /* memcpy the value (it might not be zero terminated) */
+ memcpy(&outp[labellen+1], value, valuelen);
+
+ /* zero terminate the output */
+ outp[labellen + 1 + valuelen] = 0;
+
+ /* TODO: we should rather introduce an internal API that can do the
+ equivalent of curl_slist_append but doesn't strdup() the given data as
+ like in this place the extra malloc/free is totally pointless */
+ nl = curl_slist_append(ci->certinfo[certnum], outp);
+ if(!nl) {
+ curl_slist_free_all(ci->certinfo[certnum]);
+ res = CURLE_OUT_OF_MEMORY;
+ }
+ else
+ ci->certinfo[certnum] = nl;
+
+ free(outp);
+
+ return res;
+}
+
+/* this is a convenience function for push_certinfo_len that takes a zero
+ terminated value */
+static CURLcode push_certinfo(struct SessionHandle *data,
+ int certnum,
+ const char *label,
+ const char *value)
+{
+ size_t valuelen = strlen(value);
+
+ return push_certinfo_len(data, certnum, label, value, valuelen);
+}
+
+static void pubkey_show(struct SessionHandle *data,
+ int num,
+ const char *type,
+ const char *name,
+ unsigned char *raw,
+ int len)
+{
+ char buffer[1024];
+ size_t left = sizeof(buffer);
+ int i;
+ char *ptr=buffer;
+ char namebuf[32];
+
+ snprintf(namebuf, sizeof(namebuf), "%s(%s)", type, name);
+
+ for(i=0; i< len; i++) {
+ snprintf(ptr, left, "%02x:", raw[i]);
+ ptr += 3;
+ left -= 3;
+ }
+ infof(data, " %s: %s\n", namebuf, buffer);
+ push_certinfo(data, num, namebuf, buffer);
+}
+
+#define print_pubkey_BN(_type, _name, _num) \
+do { \
+ if (pubkey->pkey._type->_name != NULL) { \
+ int len = BN_num_bytes(pubkey->pkey._type->_name); \
+ if(len < (int)sizeof(buf)) { \
+ BN_bn2bin(pubkey->pkey._type->_name, (unsigned char*)buf); \
+ buf[len] = 0; \
+ pubkey_show(data, _num, #_type, #_name, (unsigned char*)buf, len); \
+ } \
+ } \
+} while (0)
+
+static int X509V3_ext(struct SessionHandle *data,
+ int certnum,
+ STACK_OF(X509_EXTENSION) *exts)
+{
+ int i, j;
+
+ if(sk_X509_EXTENSION_num(exts) <= 0)
+ /* no extensions, bail out */
+ return 1;
+
+ for (i=0; i<sk_X509_EXTENSION_num(exts); i++) {
+ ASN1_OBJECT *obj;
+ X509_EXTENSION *ext = sk_X509_EXTENSION_value(exts, i);
+ BIO *bio_out = BIO_new(BIO_s_mem());
+ BUF_MEM *biomem;
+ char buf[512];
+ char *ptr=buf;
+ char namebuf[128];
+
+ obj = X509_EXTENSION_get_object(ext);
+
+ asn1_object_dump(obj, namebuf, sizeof(namebuf));
+
+ infof(data, "%s: %s\n", namebuf,
+ X509_EXTENSION_get_critical(ext)?"(critical)":"");
+
+ if(!X509V3_EXT_print(bio_out, ext, 0, 0))
+ M_ASN1_OCTET_STRING_print(bio_out, ext->value);
+
+ BIO_get_mem_ptr(bio_out, &biomem);
+
+ /* biomem->length bytes at biomem->data, this little loop here is only
+ done for the infof() call, we send the "raw" data to the certinfo
+ function */
+ for(j=0; j<biomem->length; j++) {
+ const char *sep="";
+ if(biomem->data[j] == '\n') {
+ sep=", ";
+ j++; /* skip the newline */
+ };
+ while((biomem->data[j] == ' ') && (j<biomem->length))
+ j++;
+ if(j<biomem->length)
+ ptr+=snprintf(ptr, sizeof(buf)-(ptr-buf), "%s%c", sep, biomem->data[j]);
+ }
+ infof(data, " %s\n", buf);
+
+ push_certinfo(data, certnum, namebuf, buf);
+
+ BIO_free(bio_out);
+
+ }
+ return 0; /* all is fine */
+}
+
+
+static void X509_signature(struct SessionHandle *data,
+ int numcert,
+ ASN1_STRING *sig)
+{
+ char buf[1024];
+ char *ptr = buf;
+ int i;
+ for (i=0; i<sig->length; i++)
+ ptr+=snprintf(ptr, sizeof(buf)-(ptr-buf), "%02x:", sig->data[i]);
+
+ infof(data, " Signature: %s\n", buf);
+ push_certinfo(data, numcert, "Signature", buf);
+}
+
+static void dumpcert(struct SessionHandle *data, X509 *x, int numcert)
+{
+ BIO *bio_out = BIO_new(BIO_s_mem());
+ BUF_MEM *biomem;
+
+ /* this outputs the cert in this 64 column wide style with newlines and
+ -----BEGIN CERTIFICATE----- texts and more */
+ PEM_write_bio_X509(bio_out, x);
+
+ BIO_get_mem_ptr(bio_out, &biomem);
+
+ infof(data, "%s\n", biomem->data);
+
+ push_certinfo_len(data, numcert, "Cert", biomem->data, biomem->length);
+
+ BIO_free(bio_out);
+
+}
+
+
+static int init_certinfo(struct SessionHandle *data,
+ int num)
+{
+ struct curl_certinfo *ci = &data->info.certs;
+ struct curl_slist **table;
+
+ Curl_ssl_free_certinfo(data);
+
+ ci->num_of_certs = num;
+ table = calloc(sizeof(struct curl_slist *) * num, 1);
+ if(!table)
+ return 1;
+
+ ci->certinfo = table;
+ return 0;
+}
+
+static CURLcode get_cert_chain(struct connectdata *conn,
+ struct ssl_connect_data *connssl)
+
+{
+ STACK_OF(X509) *sk;
+ int i;
+ char buf[512];
+ struct SessionHandle *data = conn->data;
+ int numcerts;
+
+ sk = SSL_get_peer_cert_chain(connssl->handle);
+
+ if(!sk)
+ return CURLE_OUT_OF_MEMORY;
+
+ numcerts = sk_X509_num(sk);
+
+ if(init_certinfo(data, numcerts))
+ return CURLE_OUT_OF_MEMORY;
+
+ infof(data, "--- Certificate chain\n");
+ for (i=0; i<numcerts; i++) {
+ long value;
+ ASN1_INTEGER *num;
+ ASN1_TIME *certdate;
+
+ /* get the certs in "importance order" */
+#if 0
+ X509 *x = sk_X509_value(sk, numcerts - i - 1);
+#else
+ X509 *x = sk_X509_value(sk, i);
+#endif
+
+ X509_CINF *cinf;
+ EVP_PKEY *pubkey=NULL;
+ int j;
+ char *ptr;
+
+ (void)x509_name_oneline(X509_get_subject_name(x), buf, sizeof(buf));
+ infof(data, "%2d Subject: %s\n",i,buf);
+ push_certinfo(data, i, "Subject", buf);
+
+ (void)x509_name_oneline(X509_get_issuer_name(x), buf, sizeof(buf));
+ infof(data, " Issuer: %s\n",buf);
+ push_certinfo(data, i, "Issuer", buf);
+
+ value = X509_get_version(x);
+ infof(data, " Version: %lu (0x%lx)\n", value+1, value);
+ snprintf(buf, sizeof(buf), "%lx", value);
+ push_certinfo(data, i, "Version", buf); /* hex */
+
+ num=X509_get_serialNumber(x);
+ if (num->length <= 4) {
+ value = ASN1_INTEGER_get(num);
+ infof(data," Serial Number: %ld (0x%lx)\n", value, value);
+ snprintf(buf, sizeof(buf), "%lx", value);
+ }
+ else {
+
+ ptr = buf;
+ *ptr++ = 0;
+ if(num->type == V_ASN1_NEG_INTEGER)
+ *ptr++='-';
+
+ for (j=0; j<num->length; j++) {
+ /* TODO: length restrictions */
+ snprintf(ptr, 3, "%02x%c",num->data[j],
+ ((j+1 == num->length)?'\n':':'));
+ ptr += 3;
+ }
+ if(num->length)
+ infof(data," Serial Number: %s\n", buf);
+ else
+ buf[0]=0;
+ }
+ if(buf[0])
+ push_certinfo(data, i, "Serial Number", buf); /* hex */
+
+ cinf = x->cert_info;
+
+ j = asn1_object_dump(cinf->signature->algorithm, buf, sizeof(buf));
+ if(!j) {
+ infof(data, " Signature Algorithm: %s\n", buf);
+ push_certinfo(data, i, "Signature Algorithm", buf);
+ }
+
+ certdate = X509_get_notBefore(x);
+ asn1_output(certdate, buf, sizeof(buf));
+ infof(data, " Start date: %s\n", buf);
+ push_certinfo(data, i, "Start date", buf);
+
+ certdate = X509_get_notAfter(x);
+ asn1_output(certdate, buf, sizeof(buf));
+ infof(data, " Expire date: %s\n", buf);
+ push_certinfo(data, i, "Expire date", buf);
+
+ j = asn1_object_dump(cinf->key->algor->algorithm, buf, sizeof(buf));
+ if(!j) {
+ infof(data, " Public Key Algorithm: %s\n", buf);
+ push_certinfo(data, i, "Public Key Algorithm", buf);
+ }
+
+ pubkey = X509_get_pubkey(x);
+ if(!pubkey)
+ infof(data, " Unable to load public key\n");
+ else {
+ switch(pubkey->type) {
+ case EVP_PKEY_RSA:
+ infof(data, " RSA Public Key (%d bits)\n",
+ BN_num_bits(pubkey->pkey.rsa->n));
+ snprintf(buf, sizeof(buf), "%d", BN_num_bits(pubkey->pkey.rsa->n));
+ push_certinfo(data, i, "RSA Public Key", buf);
+
+ print_pubkey_BN(rsa, n, i);
+ print_pubkey_BN(rsa, e, i);
+ print_pubkey_BN(rsa, d, i);
+ print_pubkey_BN(rsa, p, i);
+ print_pubkey_BN(rsa, q, i);
+ print_pubkey_BN(rsa, dmp1, i);
+ print_pubkey_BN(rsa, dmq1, i);
+ print_pubkey_BN(rsa, iqmp, i);
+ break;
+ case EVP_PKEY_DSA:
+ print_pubkey_BN(dsa, p, i);
+ print_pubkey_BN(dsa, q, i);
+ print_pubkey_BN(dsa, g, i);
+ print_pubkey_BN(dsa, priv_key, i);
+ print_pubkey_BN(dsa, pub_key, i);
+ break;
+ case EVP_PKEY_DH:
+ print_pubkey_BN(dh, p, i);
+ print_pubkey_BN(dh, g, i);
+ print_pubkey_BN(dh, priv_key, i);
+ print_pubkey_BN(dh, pub_key, i);
+ break;
+#if 0
+ case EVP_PKEY_EC: /* symbol not present in OpenSSL 0.9.6 */
+ /* left TODO */
+ break;
+#endif
+ }
+ }
+
+ X509V3_ext(data, i, cinf->extensions);
+
+ X509_signature(data, i, x->signature);
+
+ dumpcert(data, x, i);
+ }
+
+ return CURLE_OK;
+}
+
/*
* Get the server cert, verify it and show it etc, only call failf() if the
* 'strict' argument is TRUE as otherwise all this is for informational
@@ -1632,12 +2013,17 @@ static CURLcode servercert(struct connectdata *conn,
bool strict)
{
CURLcode retcode = CURLE_OK;
- char *str;
+ int rc;
long lerr;
ASN1_TIME *certdate;
struct SessionHandle *data = conn->data;
X509 *issuer;
FILE *fp;
+ char buffer[256];
+
+ if(data->set.ssl.certinfo)
+ /* we've been asked to gather certificate info! */
+ (void)get_cert_chain(conn, connssl);
data->set.ssl.certverifyresult = !X509_V_OK;
@@ -1649,23 +2035,24 @@ static CURLcode servercert(struct connectdata *conn,
}
infof (data, "Server certificate:\n");
- str = X509_NAME_oneline(X509_get_subject_name(connssl->server_cert),
- NULL, 0);
- if(!str) {
+ rc = x509_name_oneline(X509_get_subject_name(connssl->server_cert),
+ buffer, sizeof(buffer));
+ if(rc) {
if(strict)
failf(data, "SSL: couldn't get X509-subject!");
X509_free(connssl->server_cert);
connssl->server_cert = NULL;
return CURLE_SSL_CONNECT_ERROR;
}
- infof(data, "\t subject: %s\n", str);
- CRYPTO_free(str);
+ infof(data, "\t subject: %s\n", buffer);
certdate = X509_get_notBefore(connssl->server_cert);
- asn1_output(conn, "\t start date: ", certdate);
+ asn1_output(certdate, buffer, sizeof(buffer));
+ infof(data, "\t start date: %s\n", buffer);
certdate = X509_get_notAfter(connssl->server_cert);
- asn1_output(conn, "\t expire date: ", certdate);
+ asn1_output(certdate, buffer, sizeof(buffer));
+ infof(data, "\t expire date: %s\n", buffer);
if(data->set.ssl.verifyhost) {
retcode = verifyhost(conn, connssl->server_cert);
@@ -1676,16 +2063,15 @@ static CURLcode servercert(struct connectdata *conn,
}
}
- str = X509_NAME_oneline(X509_get_issuer_name(connssl->server_cert),
- NULL, 0);
- if(!str) {
+ rc = x509_name_oneline(X509_get_issuer_name(connssl->server_cert),
+ buffer, sizeof(buffer));
+ if(rc) {
if(strict)
failf(data, "SSL: couldn't get X509-issuer name!");
retcode = CURLE_SSL_CONNECT_ERROR;
}
else {
- infof(data, "\t issuer: %s\n", str);
- CRYPTO_free(str);
+ infof(data, "\t issuer: %s\n", buffer);
/* We could do all sorts of certificate verification stuff here before
deallocating the certificate. */