diff -r dfcd763ab174 jni/native/include/ocsp_sock.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/jni/native/include/ocsp_sock.h Fri Sep 19 17:04:40 2008 +0300 @@ -0,0 +1,22 @@ +/* Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define OCSP_STATUS_OK 0 +#define OCSP_STATUS_REVOKED 1 +#define OCSP_STATUS_UNKNOWN 2 + + +int ocspRequest(X509 *cert, X509 *issuer); diff -r dfcd763ab174 jni/native/src/sslutils.c --- a/jni/native/src/sslutils.c Thu Sep 11 16:34:31 2008 +0300 +++ b/jni/native/src/sslutils.c Fri Sep 19 17:04:40 2008 +0300 @@ -31,6 +31,27 @@ extern int WIN32_SSL_password_prompt(tcn_pass_cb_t *data); #endif +#ifdef HAVE_SSL_OCSP +#include +#include +#include +#include + +#include +#include + +#include "apr_network_io.h" + +#include "ocsp_sock.h" + +/* defines with the values as seen by the asn1parse -dump openssl command */ +#define ASN1_SEQUENCE 0x30 +#define ASN1_OID 0x06 +#define ASN1_STRING 0x86 + +int ssl_verify_OCSP(int ok, X509_STORE_CTX *ctx); + +#endif /* HAVE_SSL_OCSP */ /* _________________________________________________________________ ** ** Additional High-Level Functions for OpenSSL @@ -621,6 +642,8 @@ * This OpenSSL callback function is called when OpenSSL * does client authentication and verifies the certificate chain. */ + + int SSL_callback_SSL_verify(int ok, X509_STORE_CTX *ctx) { /* Get Apache context back through OpenSSL context */ @@ -632,6 +655,8 @@ int errdepth = X509_STORE_CTX_get_error_depth(ctx); int verify = con->ctx->verify_mode; int depth = con->ctx->verify_depth; + int ocsp_response; + int skip_crl = 0; if (verify == SSL_CVERIFY_UNSET || verify == SSL_CVERIFY_NONE) @@ -642,10 +667,26 @@ ok = 1; SSL_set_verify_result(ssl, X509_V_OK); } + +#ifdef HAVE_SSL_OCSP + /* First perform OCSP validation if possible */ + if(ok) { + ocsp_response = ssl_verify_OCSP(ok, ctx); + if (ocsp_response == OCSP_STATUS_OK ) { + skip_crl = 1; /* we know it is valid we skip crl evaluation */ + } + else if(ocsp_response == OCSP_STATUS_REVOKED ) { + ok = 0 ; + errnum = X509_STORE_CTX_get_error(ctx); + } + else if (ocsp_response == OCSP_STATUS_UNKNOWN) + ; /* do nothing for time being, continue with CRL */ + } +#endif /* HAVE_SSL_OCSP */ /* * Additionally perform CRL-based revocation checks */ - if (ok && con->ctx->crl) { + if (ok && con->ctx->crl && !skip_crl) { if (!(ok = ssl_verify_CRL(ok, ctx, con))) { errnum = X509_STORE_CTX_get_error(ctx); /* TODO: Log something */ @@ -672,4 +713,522 @@ return ok; } +#ifdef HAVE_SSL_OCSP + +/* Function that is used to do the OCSP verification */ +int ssl_verify_OCSP(int ok, X509_STORE_CTX *ctx) +{ + X509 *cert, *issuer; + int r = OCSP_STATUS_UNKNOWN, o; + + cert = X509_STORE_CTX_get_current_cert(ctx); + + o = X509_STORE_CTX_get1_issuer(&issuer, ctx, cert); + /* if we can't get the issuer, we cannot perform OCSP verification */ + if( o == 1 ) { + r = ocspRequest(cert, issuer); + if (r == OCSP_STATUS_REVOKED ) +/* we set the error if we know that it is revoked */ + X509_STORE_CTX_set_error(ctx, X509_V_ERR_CERT_REVOKED); + else + r = OCSP_STATUS_UNKNOWN; /* else we return unknown, so that we can continue with the crl */ + X509_free(issuer); /* It appears that we should free issuer since + X509_STORE_CTX_get1_issuer() calls X509_OBJECT_up_ref_count() + on the issuer object (unline X509_STORE_CTX_get_current_cert() + that just returns the pointer */ + + } + + return r; +} + + +/* Helps with error handling or realloc */ +static void *apr_xrealloc(void *buf, size_t oldlen, size_t len, apr_pool_t *p) +{ + void *newp; + newp = apr_palloc(p, len); + if(newp) + memcpy(newp, buf, oldlen); + + return newp; +} + +/* parses the ocsp url and updates the ocsp_urls and nocsp_urls variables + returns 0 on success, 1 on failure */ +static int parse_ocsp_url(unsigned char *asn1, char ***ocsp_urls, int *nocsp_urls, apr_pool_t *p) +{ + char **new_ocsp_urls, *ocsp_url; + int len, err = 0, new_nocsp_urls; + + if(*asn1 == ASN1_STRING) { + len = *++asn1; + asn1++; + new_nocsp_urls = *nocsp_urls+1; + if((new_ocsp_urls = apr_xrealloc(*ocsp_urls,*nocsp_urls, new_nocsp_urls, p)) == NULL) + err = 1; + if(!err) { + *ocsp_urls = new_ocsp_urls; + *nocsp_urls = new_nocsp_urls; + *(*ocsp_urls + *nocsp_urls) = NULL; + if((ocsp_url = apr_palloc(p, (len+1)*sizeof(char))) == NULL) { + err = 1; + } + else { + memcpy(ocsp_url, asn1, len); + *(ocsp_url+len) = '\0'; + *(*ocsp_urls + *nocsp_urls -1 ) = ocsp_url; + } + } + } + return err; + +} + +/* parses the ANS1 OID and if it is an OCSP OID then calls the parse_ocsp_url function */ +static int parse_ASN1_OID(unsigned char *asn1, char ***ocsp_urls, int *nocsp_urls, apr_pool_t *p) +{ + int len, err = 0 ; + const unsigned char OCSP_OID[] = {0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x01}; + + len = *++asn1; + asn1++; + if(memcmp(asn1, OCSP_OID, len) == 0 ) { + asn1+=len; + err = parse_ocsp_url(asn1, ocsp_urls, nocsp_urls, p); + } + return err; +} + + +/* Parses an ASN1 Sequence. It is a recursive function, since if it finds a sequence + within the sequence it calls recursively itself. This function stops when it finds + the end of the ASN1 sequence (marked by '\0'), so if there are other sequences within + the same sequence the while loop parses the sequences */ + +/* This algo was developped with AIA in mind so it was tested only with this extension */ +static int parse_ASN1_Sequence(unsigned char *asn1, char ***ocsp_urls, int *nocsp_urls, apr_pool_t *p) +{ + int len = 0 , err = 0; + while( !err && *asn1 != '\0') { + switch(*asn1) { + case ASN1_SEQUENCE: + len = *++asn1; + asn1++; + err = parse_ASN1_Sequence(asn1, ocsp_urls, nocsp_urls, p); + break; + case ASN1_OID: + err = parse_ASN1_OID(asn1,ocsp_urls,nocsp_urls, p); + return 0; + break; + default: + err = 1; /* we shouldn't have any errors */ + break; + } + asn1+=len; + } + return err; +} + +/* the main function that gets the ASN1 encoding string and returns + a pointer to a NULL terminated "array" of char *, that contains + the ocsp_urls */ +static char **decode_OCSP_url(ASN1_OCTET_STRING *os, apr_pool_t *p) +{ + char **response = NULL; + unsigned char *ocsp_urls; + int i, len, err = 0, numofresponses = 0 ; + + len = ASN1_STRING_length(os); + + ocsp_urls = apr_palloc(p, ((size_t)(len+1)*sizeof(char))); + memcpy(ocsp_urls,os->data, (size_t) len); + ocsp_urls[len] = '\0'; + + if ((response = apr_palloc(p, sizeof(char *))) == NULL) + return NULL; + *response = NULL; + + err = parse_ASN1_Sequence(ocsp_urls, &response, &numofresponses, p); + if (err) { + response = NULL; + } + + return response; +} + + + +/* stolen from openssl ocsp command */ +static int add_ocsp_cert(OCSP_REQUEST **req, X509 *cert, X509 *issuer, + STACK_OF(OCSP_CERTID) *ids) +{ + OCSP_CERTID *id; + if(!issuer) + return 0; + if(!*req) *req = OCSP_REQUEST_new(); + if(!*req) goto err; + id = OCSP_cert_to_id(NULL, cert, issuer); + if(!id || !sk_OCSP_CERTID_push(ids, id)) goto err; + if(!OCSP_request_add0_id(*req, id)) goto err; + return 1; + +err: + return 0; +} + + +/* Creates the APR socket and connect to the hostname. Returns the + socket or NULL if there is an error. +*/ +static apr_socket_t *make_socket(char *hostname, int port, apr_pool_t *mp) +{ + int r = 0; + apr_sockaddr_t *sa_in; + apr_status_t status; + apr_socket_t *sock = NULL; + + + status = apr_sockaddr_info_get(&sa_in, hostname, APR_INET, port, 0, mp); + + if(status == APR_SUCCESS) + status = apr_socket_create(&sock, sa_in->family, SOCK_STREAM, APR_PROTO_TCP, mp); + if(status == APR_SUCCESS) + status = apr_socket_connect(sock, sa_in); + + if(status == APR_SUCCESS) + return sock; + return NULL; + +} + + +/* Creates the request in a memory BIO in order to send it to the OCSP server. + Most parts of this function are taken from mod_ssl support for OCSP (with some + minor modifications +*/ +static BIO *serialize_request(OCSP_REQUEST *req, char *host, int port, char *path) +{ + BIO *bio; + int len; + + len = i2d_OCSP_REQUEST(req, NULL); + + bio = BIO_new(BIO_s_mem()); + + BIO_printf(bio, "POST %s HTTP/1.0\r\n" + "Host: %s:%d\r\n" + "Content-Type: application/ocsp-request\r\n" + "Content-Length: %d\r\n" + "\r\n", + path, host, port, len); + + if (i2d_OCSP_REQUEST_bio(bio, req) != 1) { + BIO_free(bio); + return NULL; + } + + return bio; +} + + +/* Send the OCSP request to the OCSP server. Taken from mod_ssl OCSP support */ +#define HUGE_STRING_LENGTH 8192 +static int ocsp_send_req(apr_socket_t *sock, BIO *req) +{ + int len; + char buf[HUGE_STRING_LENGTH]; + apr_status_t rv; + int ok = 1; + + while ((len = BIO_read(req, buf, sizeof buf)) > 0) { + char *wbuf = buf; + apr_size_t remain = len; + + do { + apr_size_t wlen = remain; + rv = apr_socket_send(sock, wbuf, &wlen); + wbuf += remain; + remain -= wlen; + } while (rv == APR_SUCCESS && remain > 0); + + if (rv != APR_SUCCESS) { + apr_socket_close(sock); + ok = 0; + } + } + + return ok; +} + + + +/* Parses the buffer from the response and extracts the OCSP response. + Taken from openssl library */ +static OCSP_RESPONSE *parse_ocsp_resp(char *buf, int len) +{ + BIO *mem = NULL; + char tmpbuf[1024]; + OCSP_RESPONSE *resp = NULL; + char *p, *q, *r; + int retcode; + + mem = BIO_new(BIO_s_mem()); + if(mem == NULL) + return NULL; + + BIO_write(mem, buf, len); /* write the buffer to the bio */ + if(BIO_gets(mem, tmpbuf, 512) <= 0) { + OCSPerr(OCSP_F_OCSP_SENDREQ_BIO,OCSP_R_SERVER_RESPONSE_PARSE_ERROR); + goto err; + } + /* Parse the HTTP response. This will look like this: + * "HTTP/1.0 200 OK". We need to obtain the numeric code and + * (optional) informational message. + */ + + /* Skip to first white space (passed protocol info) */ + for(p = tmpbuf; *p && !isspace((unsigned char)*p); p++) continue; + if(!*p) { + goto err; + } + /* Skip past white space to start of response code */ + while(isspace((unsigned char)*p)) p++; + if(!*p) { + goto err; + } + /* Find end of response code: first whitespace after start of code */ + for(q = p; *q && !isspace((unsigned char)*q); q++) continue; + if(!*q) { + goto err; + } + /* Set end of response code and start of message */ + *q++ = 0; + /* Attempt to parse numeric code */ + retcode = strtoul(p, &r, 10); + if(*r) goto err; + /* Skip over any leading white space in message */ + while(isspace((unsigned char)*q)) q++; + if(*q) { + /* Finally zap any trailing white space in message (include CRLF) */ + /* We know q has a non white space character so this is OK */ + for(r = q + strlen(q) - 1; isspace((unsigned char)*r); r--) *r = 0; + } + if(retcode != 200) { + goto err; + } + /* Find blank line marking beginning of content */ + while(BIO_gets(mem, tmpbuf, 512) > 0) + { + for(p = tmpbuf; isspace((unsigned char)*p); p++) continue; + if(!*p) break; + } + if(*p) { + goto err; + } + if(!(resp = d2i_OCSP_RESPONSE_bio(mem, NULL))) { + goto err; + } +err: + BIO_free(mem); + return resp; + + +} + + +/* Reads the respnse from the APR socket to a buffer, and parses the buffer to + return the OCSP response */ +#define ADDLEN 512 +static OCSP_RESPONSE *ocsp_get_resp(apr_socket_t *sock) +{ + int buflen = 0, totalread = 0; + apr_size_t readlen; + char *buf, tmpbuf[ADDLEN]; + apr_status_t rv = APR_SUCCESS; + apr_pool_t *p; + + OCSP_RESPONSE *resp; + + apr_pool_create(&p, NULL); + + buflen = ADDLEN; + buf = apr_palloc(p, buflen); + if(buf == NULL) { + apr_pool_destroy(p); + return NULL; + } + + while(rv == APR_SUCCESS ) { + readlen = sizeof(tmpbuf); + rv = apr_socket_recv(sock, tmpbuf, &readlen); + if(rv == APR_SUCCESS) { /* if we have read something .. we can put it in the buffer*/ + if((totalread + readlen) >= buflen) { + buf = apr_xrealloc(buf,buflen,buflen+ADDLEN, p); + if (buf == NULL) { + apr_pool_destroy(p); + return NULL; + } + buflen+=ADDLEN; /* if needed we enlarge the buffer */ + } + memcpy(buf+totalread, tmpbuf, readlen); /* the copy to the buffer */ + totalread+=readlen; /* update the total bytes read */ + } + else { + if (rv == APR_EOF && readlen == 0 ) + ; /* EOF, normal situation */ + else if ( readlen == 0 ) { + /* Not success, and readlen == 0 .. some error */ + apr_pool_destroy(p); + return NULL; + } + } + } + + + resp = parse_ocsp_resp(buf, buflen); + + apr_pool_destroy(p); + + return resp; +} + +/* Creates and OCSP request and returns the OCSP_RESPONSE */ +static OCSP_RESPONSE *get_ocsp_response(X509 *cert, X509 *issuer, char *url) +{ + OCSP_RESPONSE *ocsp_resp = NULL; + OCSP_REQUEST *ocsp_req = NULL; + BIO *bio_req; + char *hostname, *path, *c_port; + int port, use_ssl; + STACK_OF(OCSP_CERTID) *ids = NULL; + int ok = 0; + + apr_socket_t *apr_sock=NULL; + + apr_pool_t *mp; + + apr_pool_create(&mp, NULL); + + ids = sk_OCSP_CERTID_new_null(); + + /* problem parsing the URL */ + if(OCSP_parse_url(url,&hostname, &c_port, &path, &use_ssl) == 0 ) { + sk_OCSP_CERTID_free(ids); + return NULL; + } + + /* Create the OCSP request */ + if(sscanf(c_port, "%d", &port) != 1) + goto end; + ocsp_req = OCSP_REQUEST_new(); + if(ocsp_req == NULL) + return NULL; + if(add_ocsp_cert(&ocsp_req,cert,issuer,ids) == 0 ) + goto free_req; + + /* create the BIO with the request to send */ + bio_req = serialize_request(ocsp_req, hostname, port, path); + if(bio_req == NULL) { + goto free_req; + } + + apr_sock = make_socket(hostname, port, mp); + if (apr_sock == NULL) { + ocsp_resp = NULL; + goto free_bio; + } + + ok = ocsp_send_req(apr_sock, bio_req); + if(ok) + ocsp_resp = ocsp_get_resp(apr_sock); + +free_bio: + BIO_free(bio_req); + +free_req: + if(apr_sock && ok) /* if ok == 0 we have already closed the socket */ + apr_socket_close(apr_sock); + + apr_pool_destroy(mp); + + sk_OCSP_CERTID_free(ids); + OCSP_REQUEST_free(ocsp_req); + +end: + return ocsp_resp; +} + +/* Process the OCSP_RESPONSE and returns the corresponding + answert according to the status. +*/ +static int processOCSPResponse(OCSP_RESPONSE *ocsp_resp) +{ + int r, o = V_OCSP_CERTSTATUS_UNKNOWN, i; + OCSP_BASICRESP *bs; + OCSP_SINGLERESP *ss; + + r = OCSP_response_status(ocsp_resp); + + if(r != OCSP_RESPONSE_STATUS_SUCCESSFUL) { + OCSP_RESPONSE_free(ocsp_resp); + return OCSP_STATUS_UNKNOWN; + } + bs = OCSP_response_get1_basic(ocsp_resp); + + ss = OCSP_resp_get0(bs,0); /* we know we have only 1 request */ + + i = OCSP_single_get0_status(ss, NULL, NULL, NULL, NULL); + if ( i == V_OCSP_CERTSTATUS_GOOD ) + o = OCSP_STATUS_OK; + else if ( i == V_OCSP_CERTSTATUS_REVOKED ) + o = OCSP_STATUS_REVOKED; + else if ( i == V_OCSP_CERTSTATUS_UNKNOWN) + o = OCSP_STATUS_UNKNOWN; + + /* we clean up */ + OCSP_RESPONSE_free(ocsp_resp); + + return o; +} + + +int ocspRequest(X509 *cert, X509 *issuer) +{ + char **ocsp_urls = NULL; + int nid, i; + X509_EXTENSION *ext; + ASN1_OCTET_STRING *os; + + apr_pool_t *p; + + apr_pool_create(&p, NULL); + + /* Get the proper extension */ + nid = X509_get_ext_by_NID(cert,NID_info_access,-1); + if(nid >= 0 ) { + ext = X509_get_ext(cert,nid); + os = X509_EXTENSION_get_data(ext); + + ocsp_urls = decode_OCSP_url(os, p); + } + + /* if we find the extensions and we can parse it check + the ocsp status. Otherwise, return OCSP_STATUS_UNKNOWN */ + if(ocsp_urls != NULL) { + OCSP_RESPONSE *resp; + /* for the time being just check for the fist response .. a better + approach is to iterate for all the possible ocsp urls */ + resp = get_ocsp_response(cert, issuer, ocsp_urls[0]); + + apr_pool_destroy(p); + if(resp != NULL) + return processOCSPResponse(resp); + } + apr_pool_destroy(p); + return OCSP_STATUS_UNKNOWN; +} + +#endif /* HAVE_SSL_OCSP */ + #endif