move ocsp related routines to the new file
authorMarcel Poul <marcel.poul@cern.ch>
Wed, 11 Jul 2012 21:58:27 +0000 (21:58 +0000)
committerMarcel Poul <marcel.poul@cern.ch>
Wed, 11 Jul 2012 21:58:27 +0000 (21:58 +0000)
emi.canl.canl-c/src/canl_ocsp.c [new file with mode: 0644]

diff --git a/emi.canl.canl-c/src/canl_ocsp.c b/emi.canl.canl-c/src/canl_ocsp.c
new file mode 100644 (file)
index 0000000..f7ff97b
--- /dev/null
@@ -0,0 +1,516 @@
+#include "canl_locl.h"
+#include "canl_mech_ssl.h"
+#include <openssl/ocsp.h>
+
+#define USENONCE 0
+
+typedef enum {
+    CANL_OCSPRESULT_ERROR_NOTCONFIGURED     = -14,
+    CANL_OCSPRESULT_ERROR_NOAIAOCSPURI      = -13,
+    CANL_OCSPRESULT_ERROR_INVALIDRESPONSE   = -12,
+    CANL_OCSPRESULT_ERROR_CONNECTFAILURE    = -11,
+    CANL_OCSPRESULT_ERROR_SIGNFAILURE       = -10,
+    CANL_OCSPRESULT_ERROR_BADOCSPADDRESS    = -9,
+    CANL_OCSPRESULT_ERROR_OUTOFMEMORY       = -8,
+    CANL_OCSPRESULT_ERROR_UNKNOWN           = -7,
+    CANL_OCSPRESULT_ERROR_UNAUTHORIZED      = -6,
+    CANL_OCSPRESULT_ERROR_SIGREQUIRED       = -5,
+    CANL_OCSPRESULT_ERROR_TRYLATER          = -3,
+    CANL_OCSPRESULT_ERROR_INTERNALERROR     = -2,
+    CANL_OCSPRESULT_ERROR_MALFORMEDREQUEST  = -1,
+    CANL_OCSPRESULT_CERTIFICATE_VALID       = 0,
+    CANL_OCSPRESULT_CERTIFICATE_REVOKED     = 1
+} canl_ocspresult_t;
+
+static int set_ocsp_sign_cert(canl_ocsprequest_t *ocspreq, X509 *sign_cert);
+static int set_ocsp_sign_key(canl_ocsprequest_t *ocspreq, EVP_PKEY *sign_key);
+static int set_ocsp_cert(canl_ocsprequest_t *ocspreq, X509 *cert);
+static int set_ocsp_skew(canl_ocsprequest_t *ocspreq, int skew);
+static int set_ocsp_maxage(canl_ocsprequest_t *ocspreq, int maxage);
+static int set_ocsp_url(canl_ocsprequest_t *ocspreq, char *url);
+static int set_ocsp_issuer(canl_ocsprequest_t *ocspreq, X509 *issuer);
+static canl_x509store_t * store_dup(canl_x509store_t *store_from);
+static X509_STORE * canl_create_x509store(canl_x509store_t *store);
+
+static OCSP_RESPONSE *send_request(OCSP_REQUEST *req, char *host, char *path,
+        int port, int ssl, int req_timeout);
+
+static OCSP_RESPONSE *
+query_responder(BIO *conn, char *path, OCSP_REQUEST *req, int req_timeout);
+
+static int
+set_ocsp_cert(canl_ocsprequest_t *ocspreq, X509 *cert)
+{
+
+    if (!ocspreq)
+        ocspreq = calloc(1, sizeof(*ocspreq));
+    if (!ocspreq)
+        return 1;
+
+    if (cert) {
+        if (!ocspreq->cert) {
+            X509_free(ocspreq->cert);
+            ocspreq->cert = NULL;
+        }
+        ocspreq->cert = X509_dup(cert);
+        if (!ocspreq->cert)
+            return 1;
+    }
+    return 0;
+}
+
+    static int 
+set_ocsp_url(canl_ocsprequest_t *ocspreq, char *url)
+{
+
+    int len = 0;
+    if (!ocspreq)
+        ocspreq = calloc(1, sizeof(*ocspreq));
+    if (!ocspreq)
+        return 1;
+
+    if (url) {
+        if (!ocspreq->url) {
+            free (ocspreq->url);
+            ocspreq->url = NULL;
+        }
+        len = strlen(url);
+        ocspreq->url = (char *) malloc((len +1) * sizeof (char));
+        if (!ocspreq->url)
+            return 1;
+        strncpy(ocspreq->url, url, len + 1);
+    }
+    return 0;
+}
+
+    static int 
+set_ocsp_issuer(canl_ocsprequest_t *ocspreq, X509 *issuer)
+{
+
+    if (!ocspreq)
+        ocspreq = calloc(1, sizeof(*ocspreq));
+    if (!ocspreq)
+        return 1;
+    if (issuer) {
+        if (!ocspreq->issuer) {
+            X509_free (ocspreq->issuer);
+            ocspreq->issuer = NULL;
+        }
+        ocspreq->issuer = X509_dup(issuer);
+        if (!ocspreq->issuer)
+            return 1;
+    }
+    return 0;
+}
+
+    static int 
+set_ocsp_sign_cert(canl_ocsprequest_t *ocspreq, X509 *sign_cert)
+{
+
+    if (!ocspreq)
+        ocspreq = calloc(1, sizeof(*ocspreq));
+    if (!ocspreq)
+        return 1;
+    if (sign_cert) {
+        if (!ocspreq->sign_cert) {
+            X509_free (ocspreq->sign_cert);
+            ocspreq->sign_cert = NULL;
+        }
+        ocspreq->sign_cert = X509_dup(sign_cert);
+        if (!ocspreq->sign_cert)
+            return 1;
+    }
+    return 0;
+}
+
+    static int
+set_ocsp_sign_key(canl_ocsprequest_t *ocspreq, EVP_PKEY *sign_key)
+{
+
+    if (!ocspreq)
+        ocspreq = calloc(1, sizeof(*ocspreq));
+    if (!ocspreq)
+        return 1;
+    if (sign_key) {
+        if (!ocspreq->sign_key) {
+            EVP_PKEY_free (ocspreq->sign_key);
+            ocspreq->sign_key = NULL;
+        }
+        pkey_dup(&ocspreq->sign_key, sign_key);
+        if (!ocspreq->sign_key)
+            return 1;
+    }
+    return 0;
+}
+    static int
+set_ocsp_skew(canl_ocsprequest_t *ocspreq, int skew)
+{
+
+    if (!ocspreq)
+        ocspreq = calloc(1, sizeof(*ocspreq));
+    if (!ocspreq)
+        return 1;
+    if (skew)
+        ocspreq->skew = skew;
+    return 0;
+}
+    static int
+set_ocsp_maxage(canl_ocsprequest_t *ocspreq, int maxage)
+{
+
+    if (!ocspreq)
+        ocspreq = calloc(1, sizeof(*ocspreq));
+    if (!ocspreq)
+        return 1;
+    if (maxage)
+        ocspreq->maxage = maxage;
+    return 0;
+}
+
+static canl_x509store_t * 
+store_dup(canl_x509store_t *store_from)
+{
+    canl_x509store_t *store_to = NULL;
+    if (!store_from)
+        return NULL;
+
+    store_to = calloc(1, sizeof(*store_to));
+    if (!store_to)
+        return NULL;
+
+    if (store_from->ca_dir) {
+        int len = strlen(store_from->ca_dir);
+        store_to->ca_dir = (char *) malloc((len + 1) * sizeof (char));    
+        if (store_to->ca_dir)
+            return NULL;
+        strncpy (store_to->ca_dir, store_from->ca_dir, len + 1);
+    }
+    if (store_from->crl_dir) {
+        int len = strlen(store_from->crl_dir);
+        store_to->crl_dir = (char *) malloc((len + 1) * sizeof (char));    
+        if (store_to->crl_dir)
+            return NULL;
+        strncpy (store_to->crl_dir, store_from->crl_dir, len + 1);
+    }
+    return store_to;
+}
+
+    static int
+set_ocsp_store(canl_ocsprequest_t *ocspreq, canl_x509store_t *store)
+{
+
+    if (!ocspreq)
+        ocspreq = calloc(1, sizeof(*ocspreq));
+    if (!ocspreq)
+        return 1;
+    if (store){
+        ocspreq->store = store_dup(store);
+        if (!ocspreq->store)
+            return 1;
+    }
+    return 0;
+}
+
+static X509_STORE *
+canl_create_x509store(canl_x509store_t *store)
+{
+    return NULL;
+}
+
+/*TODO error codes in this function has to be passed to canl_ctx somehow*/
+/*Timeout shoult be in data structure*/
+int do_ocsp_verify (canl_ocsprequest_t *data)
+{
+    OCSP_REQUEST *req = NULL;
+    OCSP_RESPONSE *resp = NULL;
+    OCSP_BASICRESP *basic = NULL;
+    X509_STORE *store = 0;
+    int rc = 0, reason = 0, ssl = 0, status = 0;
+    char *host = NULL, *path = NULL, *port = NULL;
+    OCSP_CERTID *id = NULL;
+    char *chosenurl = NULL;
+    canl_ocspresult_t result = 0;
+    ASN1_GENERALIZEDTIME  *producedAt, *thisUpdate, *nextUpdate;
+    int timeout = -1; // -1 means no timeout - use blocking I/O
+
+    if (!data || !data->cert) { // TODO || !data->issuer ?
+        result = EINVAL; //TODO error code
+        return result;
+    }
+
+    /*get url from cert or use some implicit value*/
+
+    /*get connection parameters out of the chosenurl.
+     Determine whether to use encrypted (ssl) connection (based on the url
+     format). Url is http[s]://host where host consists of 
+     DN [:port] and [path]*/
+    if (!OCSP_parse_url(chosenurl, &host, &port, &path, &ssl)) {
+        result = CANL_OCSPRESULT_ERROR_BADOCSPADDRESS;
+        goto end;
+    }
+    /*Make new OCSP_REQUEST*/
+    if (!(req = OCSP_REQUEST_new())) {
+        result = CANL_OCSPRESULT_ERROR_OUTOFMEMORY;
+        goto end;
+    }
+
+    /*map a cert and its issuer to an ID*/
+    id = OCSP_cert_to_id(0, data->cert, data->issuer);
+
+    /* Add an id and nonce to the request*/
+    if (!id || !OCSP_request_add0_id(req, id))
+        goto end;
+    if (USENONCE)
+        OCSP_request_add1_nonce(req, 0, -1);
+
+    /* sign the request
+       Default hash algorithm is sha1(), might be changed.
+       Do not add additional certificates to request
+       Do not use flags (e.g. like -no_certs for command line ) now */
+    if (data->sign_cert && data->sign_key &&
+            !OCSP_request_sign(req, data->sign_cert, data->sign_key, 
+                EVP_sha1(), 0, 0)) {
+        result = CANL_OCSPRESULT_ERROR_SIGNFAILURE;
+        goto end;
+    }
+
+
+    /* establish a connection to the OCSP responder */
+    if (!(resp = send_request(req, host, path, atoi(port), ssl, timeout))) {
+        result = CANL_OCSPRESULT_ERROR_CONNECTFAILURE;
+        goto end;
+    }
+
+    /* send the request and get a response */
+    if ((rc = OCSP_response_status(resp)) != OCSP_RESPONSE_STATUS_SUCCESSFUL) {
+        switch (rc) {
+            case OCSP_RESPONSE_STATUS_MALFORMEDREQUEST:
+                result = CANL_OCSPRESULT_ERROR_MALFORMEDREQUEST; break;
+            case OCSP_RESPONSE_STATUS_INTERNALERROR:
+                result = CANL_OCSPRESULT_ERROR_INTERNALERROR;    break;
+            case OCSP_RESPONSE_STATUS_TRYLATER:
+                result = CANL_OCSPRESULT_ERROR_TRYLATER;         break;
+            case OCSP_RESPONSE_STATUS_SIGREQUIRED:
+                result = CANL_OCSPRESULT_ERROR_SIGREQUIRED;      break;
+            case OCSP_RESPONSE_STATUS_UNAUTHORIZED:
+                result = CANL_OCSPRESULT_ERROR_UNAUTHORIZED;     break;
+        }
+        goto end;
+    }
+
+    /* verify the response */
+    result = CANL_OCSPRESULT_ERROR_INVALIDRESPONSE;
+    if (!(basic = OCSP_response_get1_basic(resp))) 
+        goto end;
+    if (USENONCE && OCSP_check_nonce(req, basic) <= 0) 
+        goto end;
+    /*TODO make the store*/ 
+    if (data->store && !(store = canl_create_x509store(data->store)))
+        goto end;
+    /*TODO check the second parametr (responder_cert) and the last one*/
+    if ((rc = OCSP_basic_verify(basic, 0, store, 0)) <= 0)
+        if ((rc = OCSP_basic_verify(basic, NULL, store, 0)) <= 0)
+            goto end;
+
+    if (!OCSP_resp_find_status(basic, id, &status, &reason, &producedAt,
+                &thisUpdate, &nextUpdate))
+        goto end;
+    if (!OCSP_check_validity(thisUpdate, nextUpdate, data->skew, data->maxage))
+        goto end;
+
+    /* All done.  Set the return code based on the status from the response. */
+    if (status == V_OCSP_CERTSTATUS_REVOKED) {
+        result = CANL_OCSPRESULT_CERTIFICATE_REVOKED;
+        /*TODO myproxy_log("OCSP status revoked!"); */
+    } else {
+        result = CANL_OCSPRESULT_CERTIFICATE_VALID;
+        /*TODO myproxy_log("OCSP status valid"); */
+    }
+end:
+    /*TODO check what's this 
+      if (result < 0 && result != CANL_OCSPRESULT_ERROR_NOTCONFIGURED) {
+      ssl_error_to_verror();
+      TODO myproxy_log("OCSP check failed");
+      myproxy_log_verror();
+      } */
+
+    if (host) OPENSSL_free(host);
+    if (port) OPENSSL_free(port);
+    if (path) OPENSSL_free(path);
+    if (req) OCSP_REQUEST_free(req);
+    if (resp) OCSP_RESPONSE_free(resp);
+    if (basic) OCSP_BASICRESP_free(basic);
+
+    return result;
+}
+
+static OCSP_RESPONSE *
+send_request(OCSP_REQUEST *req, char *host, char *path,  int port, int ssl,
+        int req_timeout) {
+    BIO *conn;
+    SSL_CTX *ctx_in = NULL;
+    OCSP_RESPONSE *resp = NULL;
+
+    if (!(conn = BIO_new_connect(host)))
+        goto end;
+    BIO_set_conn_int_port(conn, &port);
+
+    if (ssl){
+        BIO *sbio;
+        /*TODO what method to use? default is SSLv3 for now*/
+        ctx_in = SSL_CTX_new(SSLv3_client_method());
+        if (ctx_in == NULL) {
+            goto end;
+        }
+        //SSL_CTX_set_cert_store(ctx_in, store);
+        /*TODO verify using OCSP? Infinite loop
+           !!!!!!!!!!!!!!!!!!!!!!!ยง
+           SSL_CTX_set_mode(ctx_in, SSL_MODE_AUTO_RETRY); ? - return only after
+          the handshake and successful completion*/
+        SSL_CTX_set_verify(ctx_in, SSL_VERIFY_PEER, NULL);
+
+        sbio = BIO_new_ssl(ctx_in, 1);
+        conn = BIO_push(sbio, conn);
+        /*
+           BIO_get_ssl(conn, &ssl_ptr);
+
+           TODO figure out, how to check cert without canl_ctx
+
+           if (!check_hostname_cert(SSL_get_peer_certificate(ssl_ptr), host))
+           goto end;
+
+           TODO verify certs in OCSP at this place? openssl CL tool does not do
+           that.
+           if (SSL_get_verify_result(ssl_ptr) != X509_V_OK)
+           goto end;
+         */
+    }
+
+    resp = query_responder(conn, path, req, req_timeout);
+
+end:
+    if (conn)
+        BIO_free_all(conn);
+    if (ctx_in)
+        SSL_CTX_free(ctx_in);
+    return resp;
+}
+
+/*TODO the timeout variable should be modified if TO is reached.
+  Somehow retur error codes! */
+    static OCSP_RESPONSE *
+query_responder(BIO *conn, char *path, OCSP_REQUEST *req, int req_timeout)
+{
+    OCSP_RESPONSE *rsp = NULL;
+    
+/*openssl does support non blocking BIO for OCSP_send_request*/
+#if SSLEAY_VERSION_NUMBER >=  0x0090808fL
+    int fd;
+    int rv;
+    OCSP_REQ_CTX *ctx = NULL;
+    fd_set confds;
+    struct timeval tv;
+    
+    /*If timeout is set, the nonblocking I/O flag is set*/
+    if (req_timeout != -1)
+        BIO_set_nbio(conn, 1);
+
+    rv = BIO_do_connect(conn);
+    if ((rv <= 0) && ((req_timeout == -1) || !BIO_should_retry(conn)))
+    {
+        /*connect failed*/
+        return NULL;
+    }
+
+    if (BIO_get_fd(conn, &fd) <= 0)
+    {
+        /*Cannot get the socket*/
+        goto err;
+    }
+
+    if (req_timeout != -1 && rv <= 0)
+    {
+        /*try connecting untill timeout is reached*/
+        FD_ZERO(&confds);
+        FD_SET(fd, &confds);
+        tv.tv_usec = 0;
+        tv.tv_sec = req_timeout;
+        rv = select(fd + 1, NULL, (void *)&confds, NULL, &tv);
+        if (rv == 0)
+        {
+            /*Timeout reached*/
+            return NULL;
+        }
+    }
+
+
+    /*Prepare OCSP_REQ_CTX*/
+    ctx = OCSP_sendreq_new(conn, path, NULL, -1);
+    if (!ctx)
+        return NULL;
+    if (!OCSP_REQ_CTX_set1_req(ctx, req))
+        goto err;
+
+    /*send the OCSP request and wait for the response*/
+    for (;;)
+    {
+        rv = OCSP_sendreq_nbio(&rsp, ctx);
+        if (rv != -1)
+            break;
+
+        /* Blocking I/O flag set
+         TODO - might end in an infinite loop? - what about
+         SSL_MODE_AUTO_RETRY ?? */
+        if (req_timeout == -1) 
+            continue;
+        FD_ZERO(&confds);
+        openssl_fdset(fd, &confds);
+        tv.tv_usec = 0;
+        tv.tv_sec = req_timeout;
+        if (BIO_should_read(conn))
+            rv = select(fd + 1, (void *)&confds, NULL, NULL, &tv);
+        else if (BIO_should_write(conn))
+            rv = select(fd + 1, NULL, (void *)&confds, NULL, &tv);
+        else
+        {
+            /* Unexpected retry condition */
+            goto err;
+        }
+        if (rv == 0)
+        {
+            /*Timeout on request */
+            break;
+        }
+        if (rv == -1)
+        {
+            /*Select error*/
+            break;
+        }
+
+    }
+
+#else
+    /*openssl does not support non blocking BIO for OCSP_send_request*/
+
+    if (BIO_do_connect(conn) <= 0)
+    {
+        /*Error connecting BIO*/
+        goto err;
+    }
+
+    rsp = OCSP_sendreq_bio(conn, path, req);
+    if (!rsp) {
+        /*no response from the server*/
+        goto err;
+    }
+
+#endif
+
+err:
+#if SSLEAY_VERSION_NUMBER >=  0x0090808fL
+    if (ctx)
+        OCSP_REQ_CTX_free(ctx);
+#endif 
+    return rsp;
+}
+