]> git.ipfire.org Git - thirdparty/ipxe.git/commitdiff
[crypto] Add framework for OCSP
authorMichael Brown <mcb30@ipxe.org>
Mon, 14 May 2012 17:22:38 +0000 (18:22 +0100)
committerMichael Brown <mcb30@ipxe.org>
Tue, 15 May 2012 12:24:23 +0000 (13:24 +0100)
Add support for constructing OCSP queries and parsing OCSP responses.
(There is no support yet for actually issuing an OCSP query via an
HTTP POST.)

Signed-off-by: Michael Brown <mcb30@ipxe.org>
src/crypto/ocsp.c [new file with mode: 0644]
src/crypto/x509.c
src/include/ipxe/asn1.h
src/include/ipxe/errfile.h
src/include/ipxe/ocsp.h [new file with mode: 0644]
src/include/ipxe/x509.h

diff --git a/src/crypto/ocsp.c b/src/crypto/ocsp.c
new file mode 100644 (file)
index 0000000..7dca281
--- /dev/null
@@ -0,0 +1,749 @@
+/*
+ * Copyright (C) 2012 Michael Brown <mbrown@fensystems.co.uk>.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER );
+
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <ipxe/asn1.h>
+#include <ipxe/x509.h>
+#include <ipxe/sha1.h>
+#include <ipxe/ocsp.h>
+
+/** @file
+ *
+ * Online Certificate Status Protocol
+ *
+ */
+
+/* Disambiguate the various error causes */
+#define EACCES_CERT_STATUS                                             \
+       __einfo_error ( EINFO_EACCES_CERT_STATUS )
+#define EINFO_EACCES_CERT_STATUS                                       \
+       __einfo_uniqify ( EINFO_EACCES, 0x01,                           \
+                         "Certificate status not good" )
+#define EACCES_CERT_MISMATCH                                           \
+       __einfo_error ( EINFO_EACCES_CERT_MISMATCH )
+#define EINFO_EACCES_CERT_MISMATCH                                     \
+       __einfo_uniqify ( EINFO_EACCES, 0x02,                           \
+                         "Certificate ID mismatch" )
+#define EACCES_NON_OCSP_SIGNING                                                \
+       __einfo_error ( EINFO_EACCES_NON_OCSP_SIGNING )
+#define EINFO_EACCES_NON_OCSP_SIGNING                                  \
+       __einfo_uniqify ( EINFO_EACCES, 0x03,                           \
+                         "Not an OCSP signing certificate" )
+#define EACCES_STALE                                                   \
+       __einfo_error ( EINFO_EACCES_STALE )
+#define EINFO_EACCES_STALE                                             \
+       __einfo_uniqify ( EINFO_EACCES, 0x04,                           \
+                         "Stale (or premature) OCSP repsonse" )
+#define EPROTO_MALFORMED_REQUEST                                       \
+       __einfo_error ( EINFO_EPROTO_MALFORMED_REQUEST )
+#define EINFO_EPROTO_MALFORMED_REQUEST                                 \
+       __einfo_uniqify ( EINFO_EPROTO, OCSP_STATUS_MALFORMED_REQUEST,  \
+                         "Illegal confirmation request" )
+#define EPROTO_INTERNAL_ERROR                                          \
+       __einfo_error ( EINFO_EPROTO_INTERNAL_ERROR )
+#define EINFO_EPROTO_INTERNAL_ERROR                                    \
+       __einfo_uniqify ( EINFO_EPROTO, OCSP_STATUS_INTERNAL_ERROR,     \
+                         "Internal error in issuer" )
+#define EPROTO_TRY_LATER                                               \
+       __einfo_error ( EINFO_EPROTO_TRY_LATER )
+#define EINFO_EPROTO_TRY_LATER                                         \
+       __einfo_uniqify ( EINFO_EPROTO, OCSP_STATUS_TRY_LATER,          \
+                         "Try again later" )
+#define EPROTO_SIG_REQUIRED                                            \
+       __einfo_error ( EINFO_EPROTO_SIG_REQUIRED )
+#define EINFO_EPROTO_SIG_REQUIRED                                      \
+       __einfo_uniqify ( EINFO_EPROTO, OCSP_STATUS_SIG_REQUIRED,       \
+                         "Must sign the request" )
+#define EPROTO_UNAUTHORIZED                                            \
+       __einfo_error ( EINFO_EPROTO_UNAUTHORIZED )
+#define EINFO_EPROTO_UNAUTHORIZED                                      \
+       __einfo_uniqify ( EINFO_EPROTO, OCSP_STATUS_UNAUTHORIZED,       \
+                         "Request unauthorized" )
+#define EPROTO_STATUS( status )                                                \
+       EUNIQ ( EPROTO, (status), EPROTO_MALFORMED_REQUEST,             \
+               EPROTO_INTERNAL_ERROR, EPROTO_TRY_LATER,                \
+               EPROTO_SIG_REQUIRED, EPROTO_UNAUTHORIZED )
+
+/** OCSP digest algorithm */
+#define ocsp_digest_algorithm sha1_algorithm
+
+/** OCSP digest algorithm identifier */
+static const uint8_t ocsp_algorithm_id[] =
+       { OCSP_ALGORITHM_IDENTIFIER ( ASN1_OID_SHA1 ) };
+
+/** OCSP basic response type */
+static const uint8_t oid_basic_response_type[] = { ASN1_OID_OCSP_BASIC };
+
+/** OCSP basic response type cursor */
+static struct asn1_cursor oid_basic_response_type_cursor =
+       ASN1_OID_CURSOR ( oid_basic_response_type );
+
+/**
+ * Free OCSP check
+ *
+ * @v refcnt           Reference count
+ */
+static void ocsp_free ( struct refcnt *refcnt ) {
+       struct ocsp_check *ocsp =
+               container_of ( refcnt, struct ocsp_check, refcnt );
+
+       x509_put ( ocsp->cert );
+       x509_put ( ocsp->issuer );
+       free ( ocsp->request.builder.data );
+       free ( ocsp->response.data );
+       x509_put ( ocsp->response.signer );
+       free ( ocsp );
+}
+
+/**
+ * Build OCSP request
+ *
+ * @v ocsp             OCSP check
+ * @ret rc             Return status code
+ */
+static int ocsp_request ( struct ocsp_check *ocsp ) {
+       struct digest_algorithm *digest = &ocsp_digest_algorithm;
+       struct asn1_builder *builder = &ocsp->request.builder;
+       struct asn1_cursor *cert_id = &ocsp->request.cert_id;
+       uint8_t digest_ctx[digest->ctxsize];
+       uint8_t name_digest[digest->digestsize];
+       uint8_t pubkey_digest[digest->digestsize];
+       int rc;
+
+       /* Generate digests */
+       digest_init ( digest, digest_ctx );
+       digest_update ( digest, digest_ctx, ocsp->cert->issuer.raw.data,
+                       ocsp->cert->issuer.raw.len );
+       digest_final ( digest, digest_ctx, name_digest );
+       digest_init ( digest, digest_ctx );
+       digest_update ( digest, digest_ctx,
+                       ocsp->issuer->subject.public_key.raw_bits.data,
+                       ocsp->issuer->subject.public_key.raw_bits.len );
+       digest_final ( digest, digest_ctx, pubkey_digest );
+
+       /* Construct request */
+       if ( ( rc = ( asn1_prepend_raw ( builder, ocsp->cert->serial.raw.data,
+                                        ocsp->cert->serial.raw.len ),
+                     asn1_prepend ( builder, ASN1_OCTET_STRING,
+                                    pubkey_digest, sizeof ( pubkey_digest ) ),
+                     asn1_prepend ( builder, ASN1_OCTET_STRING,
+                                    name_digest, sizeof ( name_digest ) ),
+                     asn1_prepend ( builder, ASN1_SEQUENCE,
+                                    ocsp_algorithm_id,
+                                    sizeof ( ocsp_algorithm_id ) ),
+                     asn1_wrap ( builder, ASN1_SEQUENCE ),
+                     asn1_wrap ( builder, ASN1_SEQUENCE ),
+                     asn1_wrap ( builder, ASN1_SEQUENCE ),
+                     asn1_wrap ( builder, ASN1_SEQUENCE ),
+                     asn1_wrap ( builder, ASN1_SEQUENCE ) ) ) != 0 ) {
+               DBGC ( ocsp, "OCSP %p \"%s\" could not build request: %s\n",
+                      ocsp, ocsp->cert->subject.name, strerror ( rc ) );
+               return rc;
+       }
+       DBGC2 ( ocsp, "OCSP %p \"%s\" request is:\n",
+               ocsp, ocsp->cert->subject.name );
+       DBGC2_HDA ( ocsp, 0, builder->data, builder->len );
+
+       /* Parse certificate ID for comparison with response */
+       cert_id->data = builder->data;
+       cert_id->len = builder->len;
+       if ( ( rc = ( asn1_enter ( cert_id, ASN1_SEQUENCE ),
+                     asn1_enter ( cert_id, ASN1_SEQUENCE ),
+                     asn1_enter ( cert_id, ASN1_SEQUENCE ),
+                     asn1_enter ( cert_id, ASN1_SEQUENCE ) ) ) != 0 ) {
+               DBGC ( ocsp, "OCSP %p \"%s\" could not locate certID: %s\n",
+                      ocsp, ocsp->cert->subject.name, strerror ( rc ) );
+               return rc;
+       }
+
+       return 0;
+}
+
+/**
+ * Create OCSP check
+ *
+ * @v cert             Certificate to check
+ * @v issuer           Issuing certificate
+ * @ret ocsp           OCSP check
+ * @ret rc             Return status code
+ */
+int ocsp_check ( struct x509_certificate *cert,
+                struct x509_certificate *issuer,
+                struct ocsp_check **ocsp ) {
+       int rc;
+
+       /* Sanity checks */
+       assert ( cert != NULL );
+       assert ( issuer != NULL );
+       assert ( issuer->valid );
+
+       /* Allocate and initialise check */
+       *ocsp = zalloc ( sizeof ( **ocsp ) );
+       if ( ! *ocsp ) {
+               rc = -ENOMEM;
+               goto err_alloc;
+       }
+       ref_init ( &(*ocsp)->refcnt, ocsp_free );
+       (*ocsp)->cert = x509_get ( cert );
+       (*ocsp)->issuer = x509_get ( issuer );
+
+       /* Build request */
+       if ( ( rc = ocsp_request ( *ocsp ) ) != 0 )
+               goto err_request;
+
+       return 0;
+
+ err_request:
+       ocsp_put ( *ocsp );
+ err_alloc:
+       return rc;
+}
+
+/**
+ * Parse OCSP response status
+ *
+ * @v ocsp             OCSP check
+ * @v raw              ASN.1 cursor
+ * @ret rc             Return status code
+ */
+static int ocsp_parse_response_status ( struct ocsp_check *ocsp,
+                                       const struct asn1_cursor *raw ) {
+       struct asn1_cursor cursor;
+       uint8_t status;
+       int rc;
+
+       /* Enter responseStatus */
+       memcpy ( &cursor, raw, sizeof ( cursor ) );
+       if ( ( rc = asn1_enter ( &cursor, ASN1_ENUMERATED ) ) != 0 ) {
+               DBGC ( ocsp, "OCSP %p \"%s\" could not locate responseStatus: "
+                      "%s\n", ocsp, ocsp->cert->subject.name, strerror ( rc ));
+               return rc;
+       }
+
+       /* Extract response status */
+       if ( cursor.len != sizeof ( status ) ) {
+               DBGC ( ocsp, "OCSP %p \"%s\" invalid status:\n",
+                      ocsp, ocsp->cert->subject.name );
+               DBGC_HDA ( ocsp, 0, cursor.data, cursor.len );
+               return -EINVAL;
+       }
+       memcpy ( &status, cursor.data, sizeof ( status ) );
+
+       /* Check response status */
+       if ( status != OCSP_STATUS_SUCCESSFUL ) {
+               DBGC ( ocsp, "OCSP %p \"%s\" response status %d\n",
+                      ocsp, ocsp->cert->subject.name, status );
+               return EPROTO_STATUS ( status );
+       }
+
+       return 0;
+}
+
+/**
+ * Parse OCSP response type
+ *
+ * @v ocsp             OCSP check
+ * @v raw              ASN.1 cursor
+ * @ret rc             Return status code
+ */
+static int ocsp_parse_response_type ( struct ocsp_check *ocsp,
+                                     const struct asn1_cursor *raw ) {
+       struct asn1_cursor cursor;
+
+       /* Enter responseType */
+       memcpy ( &cursor, raw, sizeof ( cursor ) );
+       asn1_enter ( &cursor, ASN1_OID );
+
+       /* Check responseType is "basic" */
+       if ( asn1_compare ( &oid_basic_response_type_cursor, &cursor ) != 0 ) {
+               DBGC ( ocsp, "OCSP %p \"%s\" response type not supported:\n",
+                      ocsp, ocsp->cert->subject.name );
+               DBGC_HDA ( ocsp, 0, cursor.data, cursor.len );
+               return -ENOTSUP;
+       }
+
+       return 0;
+}
+
+/**
+ * Parse OCSP certificate ID
+ *
+ * @v ocsp             OCSP check
+ * @v raw              ASN.1 cursor
+ * @ret rc             Return status code
+ */
+static int ocsp_parse_cert_id ( struct ocsp_check *ocsp,
+                               const struct asn1_cursor *raw ) {
+       struct asn1_cursor cursor;
+
+       /* Check certID matches request */
+       memcpy ( &cursor, raw, sizeof ( cursor ) );
+       asn1_shrink_any ( &cursor );
+       if ( asn1_compare ( &cursor, &ocsp->request.cert_id ) != 0 ) {
+               DBGC ( ocsp, "OCSP %p \"%s\" certID mismatch:\n",
+                      ocsp, ocsp->cert->subject.name );
+               DBGC_HDA ( ocsp, 0, ocsp->request.cert_id.data,
+                          ocsp->request.cert_id.len );
+               DBGC_HDA ( ocsp, 0, cursor.data, cursor.len );
+               return -EACCES_CERT_MISMATCH;
+       }
+
+       return 0;
+}
+
+/**
+ * Parse OCSP responses
+ *
+ * @v ocsp             OCSP check
+ * @v raw              ASN.1 cursor
+ * @ret rc             Return status code
+ */
+static int ocsp_parse_responses ( struct ocsp_check *ocsp,
+                                 const struct asn1_cursor *raw ) {
+       struct ocsp_response *response = &ocsp->response;
+       struct asn1_cursor cursor;
+       int rc;
+
+       /* Enter responses */
+       memcpy ( &cursor, raw, sizeof ( cursor ) );
+       asn1_enter ( &cursor, ASN1_SEQUENCE );
+
+       /* Enter first singleResponse */
+       asn1_enter ( &cursor, ASN1_SEQUENCE );
+
+       /* Parse certID */
+       if ( ( rc = ocsp_parse_cert_id ( ocsp, &cursor ) ) != 0 )
+               return rc;
+       asn1_skip_any ( &cursor );
+
+       /* Check certStatus */
+       if ( asn1_type ( &cursor ) != ASN1_IMPLICIT_TAG ( 0 ) ) {
+               DBGC ( ocsp, "OCSP %p \"%s\" non-good certStatus:\n",
+                      ocsp, ocsp->cert->subject.name );
+               DBGC_HDA ( ocsp, 0, cursor.data, cursor.len );
+               return -EACCES_CERT_STATUS;
+       }
+       asn1_skip_any ( &cursor );
+
+       /* Parse thisUpdate */
+       if ( ( rc = asn1_generalized_time ( &cursor,
+                                           &response->this_update ) ) != 0 ) {
+               DBGC ( ocsp, "OCSP %p \"%s\" could not parse thisUpdate: %s\n",
+                      ocsp, ocsp->cert->subject.name, strerror ( rc ) );
+               return rc;
+       }
+       DBGC2 ( ocsp, "OCSP %p \"%s\" this update was at time %lld\n",
+               ocsp, ocsp->cert->subject.name, response->this_update );
+       asn1_skip_any ( &cursor );
+
+       /* Parse nextUpdate, if present */
+       if ( asn1_type ( &cursor ) == ASN1_EXPLICIT_TAG ( 0 ) ) {
+               asn1_enter ( &cursor, ASN1_EXPLICIT_TAG ( 0 ) );
+               if ( ( rc = asn1_generalized_time ( &cursor,
+                                            &response->next_update ) ) != 0 ) {
+                       DBGC ( ocsp, "OCSP %p \"%s\" could not parse "
+                              "nextUpdate: %s\n", ocsp,
+                              ocsp->cert->subject.name, strerror ( rc ) );
+                       return rc;
+               }
+               DBGC2 ( ocsp, "OCSP %p \"%s\" next update is at time %lld\n",
+                       ocsp, ocsp->cert->subject.name, response->next_update );
+       } else {
+               /* If no nextUpdate is present, this indicates that
+                * "newer revocation information is available all the
+                * time".  Actually, this indicates that there is no
+                * point to performing the OCSP check, since an
+                * attacker could replay the response at any future
+                * time and it would still be valid.
+                */
+               DBGC ( ocsp, "OCSP %p \"%s\" responder is a moron\n",
+                      ocsp, ocsp->cert->subject.name );
+               response->next_update = time ( NULL );
+       }
+
+       return 0;
+}
+
+/**
+ * Parse OCSP response data
+ *
+ * @v ocsp             OCSP check
+ * @v raw              ASN.1 cursor
+ * @ret rc             Return status code
+ */
+static int ocsp_parse_tbs_response_data ( struct ocsp_check *ocsp,
+                                         const struct asn1_cursor *raw ) {
+       struct ocsp_response *response = &ocsp->response;
+       struct asn1_cursor cursor;
+       int rc;
+
+       /* Record raw tbsResponseData */
+       memcpy ( &cursor, raw, sizeof ( cursor ) );
+       asn1_shrink_any ( &cursor );
+       memcpy ( &response->tbs, &cursor, sizeof ( response->tbs ) );
+
+       /* Enter tbsResponseData */
+       asn1_enter ( &cursor, ASN1_SEQUENCE );
+
+       /* Skip version, if present */
+       asn1_skip_if_exists ( &cursor, ASN1_EXPLICIT_TAG ( 0 ) );
+
+       /* Skip responderID */
+       asn1_skip_any ( &cursor );
+
+       /* Skip producedAt */
+       asn1_skip_any ( &cursor );
+
+       /* Parse responses */
+       if ( ( rc = ocsp_parse_responses ( ocsp, &cursor ) ) != 0 )
+               return rc;
+
+       return 0;
+}
+
+/**
+ * Parse OCSP certificates
+ *
+ * @v ocsp             OCSP check
+ * @v raw              ASN.1 cursor
+ * @ret rc             Return status code
+ */
+static int ocsp_parse_certs ( struct ocsp_check *ocsp,
+                             const struct asn1_cursor *raw ) {
+       struct ocsp_response *response = &ocsp->response;
+       struct asn1_cursor cursor;
+       int rc;
+
+       /* Enter certs */
+       memcpy ( &cursor, raw, sizeof ( cursor ) );
+       asn1_enter ( &cursor, ASN1_EXPLICIT_TAG ( 0 ) );
+       asn1_enter ( &cursor, ASN1_SEQUENCE );
+
+       /* Parse certificate, if present.  The data structure permits
+        * multiple certificates, but the protocol requires that the
+        * OCSP signing certificate must either be the issuer itself,
+        * or must be directly issued by the issuer (see RFC2560
+        * section 4.2.2.2 "Authorized Responders").
+        */
+       if ( ( cursor.len != 0 ) &&
+            ( ( rc = x509_certificate ( cursor.data, cursor.len,
+                                        &response->signer ) ) != 0 ) ) {
+               DBGC ( ocsp, "OCSP %p \"%s\" could not parse certificate: "
+                      "%s\n", ocsp, ocsp->cert->subject.name, strerror ( rc ));
+               DBGC_HDA ( ocsp, 0, cursor.data, cursor.len );
+               return rc;
+       }
+       DBGC2 ( ocsp, "OCSP %p \"%s\" response is signed by \"%s\"\n", ocsp,
+               ocsp->cert->subject.name, response->signer->subject.name );
+
+       return 0;
+}
+
+/**
+ * Parse OCSP basic response
+ *
+ * @v ocsp             OCSP check
+ * @v raw              ASN.1 cursor
+ * @ret rc             Return status code
+ */
+static int ocsp_parse_basic_response ( struct ocsp_check *ocsp,
+                                      const struct asn1_cursor *raw ) {
+       struct ocsp_response *response = &ocsp->response;
+       struct asn1_algorithm **algorithm = &response->algorithm;
+       struct asn1_bit_string *signature = &response->signature;
+       struct asn1_cursor cursor;
+       int rc;
+
+       /* Enter BasicOCSPResponse */
+       memcpy ( &cursor, raw, sizeof ( cursor ) );
+       asn1_enter ( &cursor, ASN1_SEQUENCE );
+
+       /* Parse tbsResponseData */
+       if ( ( rc = ocsp_parse_tbs_response_data ( ocsp, &cursor ) ) != 0 )
+               return rc;
+       asn1_skip_any ( &cursor );
+
+       /* Parse signatureAlgorithm */
+       if ( ( rc = asn1_signature_algorithm ( &cursor, algorithm ) ) != 0 ) {
+               DBGC ( ocsp, "OCSP %p \"%s\" cannot parse signature "
+                      "algorithm: %s\n",
+                      ocsp, ocsp->cert->subject.name, strerror ( rc ) );
+               return rc;
+       }
+       DBGC2 ( ocsp, "OCSP %p \"%s\" signature algorithm is %s\n",
+               ocsp, ocsp->cert->subject.name, (*algorithm)->name );
+       asn1_skip_any ( &cursor );
+
+       /* Parse signature */
+       if ( ( rc = asn1_integral_bit_string ( &cursor, signature ) ) != 0 ) {
+               DBGC ( ocsp, "OCSP %p \"%s\" cannot parse signature: %s\n",
+                      ocsp, ocsp->cert->subject.name, strerror ( rc ) );
+               return rc;
+       }
+       asn1_skip_any ( &cursor );
+
+       /* Parse certs, if present */
+       if ( ( asn1_type ( &cursor ) == ASN1_EXPLICIT_TAG ( 0 ) ) &&
+            ( ( rc = ocsp_parse_certs ( ocsp, &cursor ) ) != 0 ) )
+               return rc;
+
+       return 0;
+}
+
+/**
+ * Parse OCSP response bytes
+ *
+ * @v ocsp             OCSP check
+ * @v raw              ASN.1 cursor
+ * @ret rc             Return status code
+ */
+static int ocsp_parse_response_bytes ( struct ocsp_check *ocsp,
+                                      const struct asn1_cursor *raw ) {
+       struct asn1_cursor cursor;
+       int rc;
+
+       /* Enter responseBytes */
+       memcpy ( &cursor, raw, sizeof ( cursor ) );
+       asn1_enter ( &cursor, ASN1_EXPLICIT_TAG ( 0 ) );
+       asn1_enter ( &cursor, ASN1_SEQUENCE );
+
+       /* Parse responseType */
+       if ( ( rc = ocsp_parse_response_type ( ocsp, &cursor ) ) != 0 )
+               return rc;
+       asn1_skip_any ( &cursor );
+
+       /* Enter response */
+       asn1_enter ( &cursor, ASN1_OCTET_STRING );
+
+       /* Parse response */
+       if ( ( rc = ocsp_parse_basic_response ( ocsp, &cursor ) ) != 0 )
+               return rc;
+
+       return 0;
+}
+
+/**
+ * Parse OCSP response
+ *
+ * @v ocsp             OCSP check
+ * @v raw              ASN.1 cursor
+ * @ret rc             Return status code
+ */
+static int ocsp_parse_response ( struct ocsp_check *ocsp,
+                                const struct asn1_cursor *raw ) {
+       struct asn1_cursor cursor;
+       int rc;
+
+       /* Enter OCSPResponse */
+       memcpy ( &cursor, raw, sizeof ( cursor ) );
+       asn1_enter ( &cursor, ASN1_SEQUENCE );
+
+       /* Parse responseStatus */
+       if ( ( rc = ocsp_parse_response_status ( ocsp, &cursor ) ) != 0 )
+               return rc;
+       asn1_skip_any ( &cursor );
+
+       /* Parse responseBytes */
+       if ( ( rc = ocsp_parse_response_bytes ( ocsp, &cursor ) ) != 0 )
+               return rc;
+
+       return 0;
+}
+
+/**
+ * Receive OCSP response
+ *
+ * @v ocsp             OCSP check
+ * @v data             Response data
+ * @v len              Length of response data
+ * @ret rc             Return status code
+ */
+int ocsp_response ( struct ocsp_check *ocsp, const void *data, size_t len ) {
+       struct ocsp_response *response = &ocsp->response;
+       struct asn1_cursor cursor;
+       int rc;
+
+       /* Duplicate data */
+       x509_put ( response->signer );
+       response->signer = NULL;
+       free ( response->data );
+       response->data = malloc ( len );
+       if ( ! response->data )
+               return -ENOMEM;
+       memcpy ( response->data, data, len );
+       cursor.data = response->data;
+       cursor.len = len;
+
+       /* Parse response */
+       if ( ( rc = ocsp_parse_response ( ocsp, &cursor ) ) != 0 )
+               return rc;
+
+       return 0;
+}
+
+/**
+ * OCSP dummy root certificate store
+ *
+ * OCSP validation uses no root certificates, since it takes place
+ * only when there already exists a validated issuer certificate.
+ */
+static struct x509_root ocsp_root = {
+       .digest = &ocsp_digest_algorithm,
+       .count = 0,
+       .fingerprints = NULL,
+};
+
+/**
+ * Check OCSP response signature
+ *
+ * @v ocsp             OCSP check
+ * @v signer           Signing certificate
+ * @ret rc             Return status code
+ */
+static int ocsp_check_signature ( struct ocsp_check *ocsp,
+                                 struct x509_certificate *signer ) {
+       struct ocsp_response *response = &ocsp->response;
+       struct digest_algorithm *digest = response->algorithm->digest;
+       struct pubkey_algorithm *pubkey = response->algorithm->pubkey;
+       struct x509_public_key *public_key = &signer->subject.public_key;
+       uint8_t digest_ctx[ digest->ctxsize ];
+       uint8_t digest_out[ digest->digestsize ];
+       uint8_t pubkey_ctx[ pubkey->ctxsize ];
+       int rc;
+
+       /* Generate digest */
+       digest_init ( digest, digest_ctx );
+       digest_update ( digest, digest_ctx, response->tbs.data,
+                       response->tbs.len );
+       digest_final ( digest, digest_ctx, digest_out );
+
+       /* Initialise public-key algorithm */
+       if ( ( rc = pubkey_init ( pubkey, pubkey_ctx, public_key->raw.data,
+                                 public_key->raw.len ) ) != 0 ) {
+               DBGC ( ocsp, "OCSP %p \"%s\" could not initialise public key: "
+                      "%s\n", ocsp, ocsp->cert->subject.name, strerror ( rc ));
+               goto err_init;
+       }
+
+       /* Verify digest */
+       if ( ( rc = pubkey_verify ( pubkey, pubkey_ctx, digest, digest_out,
+                                   response->signature.data,
+                                   response->signature.len ) ) != 0 ) {
+               DBGC ( ocsp, "OCSP %p \"%s\" signature verification failed: "
+                      "%s\n", ocsp, ocsp->cert->subject.name, strerror ( rc ));
+               goto err_verify;
+       }
+
+       DBGC2 ( ocsp, "OCSP %p \"%s\" signature is correct\n",
+               ocsp, ocsp->cert->subject.name );
+
+ err_verify:
+       pubkey_final ( pubkey, pubkey_ctx );
+ err_init:
+       return rc;
+}
+
+/**
+ * Validate OCSP response
+ *
+ * @v ocsp             OCSP check
+ * @v time             Time at which to validate response
+ * @ret rc             Return status code
+ */
+int ocsp_validate ( struct ocsp_check *ocsp, time_t time ) {
+       struct ocsp_response *response = &ocsp->response;
+       struct x509_certificate *signer = response->signer;
+       int rc;
+
+       /* Sanity checks */
+       assert ( response->data != NULL );
+       assert ( signer != NULL );
+
+       /* Validate signer, if applicable.  If the signer is not the
+        * issuer, then it must be signed directly by the issuer.
+        */
+       if ( signer != ocsp->issuer ) {
+               /* Forcibly invalidate the signer, since we need to
+                * ensure that it was signed by our issuer (and not
+                * some other issuer).  This prevents a sub-CA's OCSP
+                * certificate from fraudulently signing OCSP
+                * responses from the parent CA.
+                */
+               x509_invalidate ( signer );
+               if ( ( rc = x509_validate ( signer, ocsp->issuer, time,
+                                           &ocsp_root ) ) != 0 ) {
+                       DBGC ( ocsp, "OCSP %p \"%s\" could not validate "
+                              "signer \"%s\": %s\n", ocsp,
+                              ocsp->cert->subject.name, signer->subject.name,
+                              strerror ( rc ) );
+                       return rc;
+               }
+
+               /* If signer is not the issuer, then it must have the
+                * extendedKeyUsage id-kp-OCSPSigning.
+                */
+               if ( ! ( signer->extensions.ext_usage.bits &
+                        X509_OCSP_SIGNING ) ) {
+                       DBGC ( ocsp, "OCSP %p \"%s\" signer \"%s\" is "
+                              "not an OCSP-signing certificate\n", ocsp,
+                              ocsp->cert->subject.name, signer->subject.name );
+                       return -EACCES_NON_OCSP_SIGNING;
+               }
+       }
+
+       /* Check OCSP response signature */
+       if ( ( rc = ocsp_check_signature ( ocsp, signer ) ) != 0 )
+               return rc;
+
+       /* Check OCSP response is valid at the specified time
+        * (allowing for some margin of error).
+        */
+       if ( response->this_update > ( time - OCSP_ERROR_MARGIN_TIME ) ) {
+               DBGC ( ocsp, "OCSP %p \"%s\" response is not yet valid (at "
+                      "time %lld)\n", ocsp, ocsp->cert->subject.name, time );
+               return -EACCES_STALE;
+       }
+       if ( response->next_update < ( time + OCSP_ERROR_MARGIN_TIME ) ) {
+               DBGC ( ocsp, "OCSP %p \"%s\" response is stale (at time "
+                      "%lld)\n", ocsp, ocsp->cert->subject.name, time );
+               return -EACCES_STALE;
+       }
+       DBGC2 ( ocsp, "OCSP %p \"%s\" response is valid (at time %lld)\n",
+               ocsp, ocsp->cert->subject.name, time );
+
+       /* Mark certificate as passing OCSP verification */
+       ocsp->cert->extensions.auth_info.ocsp.good = 1;
+
+       /* Validate certificate against issuer */
+       if ( ( rc = x509_validate ( ocsp->cert, ocsp->issuer, time,
+                                   &ocsp_root ) ) != 0 ) {
+               DBGC ( ocsp, "OCSP %p \"%s\" could not validate certificate: "
+                      "%s\n", ocsp, ocsp->cert->subject.name, strerror ( rc ));
+               return rc;
+       }
+       DBGC ( ocsp, "OCSP %p \"%s\" successfully validated using \"%s\"\n",
+              ocsp, ocsp->cert->subject.name, signer->subject.name );
+
+       return 0;
+}
index a8660934007964fad7548ee7fc2cac58a397b35e..18a8cebe0d36dcef008293ebb5b84ca2dfcdde12 100644 (file)
@@ -1290,9 +1290,9 @@ int x509_check_time ( struct x509_certificate *cert, time_t time ) {
  * successfully validated then @c issuer, @c time, and @c root will be
  * ignored.
  */
-static int x509_validate ( struct x509_certificate *cert,
-                          struct x509_certificate *issuer,
-                          time_t time, struct x509_root *root ) {
+int x509_validate ( struct x509_certificate *cert,
+                   struct x509_certificate *issuer,
+                   time_t time, struct x509_root *root ) {
        unsigned int max_path_remaining;
        int rc;
 
index 3fbd09f48121e5246d1dacbb608ecfe4e973c092..3e73b59c7817fbeeeb90a07252d4d06ba6fdf582 100644 (file)
@@ -70,6 +70,9 @@ struct asn1_builder_header {
 /** ASN.1 object identifier */
 #define ASN1_OID 0x06
 
+/** ASN.1 enumeration */
+#define ASN1_ENUMERATED 0x0a
+
 /** ASN.1 UTC time */
 #define ASN1_UTC_TIME 0x17
 
@@ -204,6 +207,14 @@ struct asn1_builder_header {
        ASN1_OID_SINGLE ( 5 ), ASN1_OID_SINGLE ( 7 ),           \
        ASN1_OID_SINGLE ( 48 ), ASN1_OID_SINGLE ( 1 )
 
+/** ASN.1 OID for id-pkix-ocsp-basic ( 1.3.6.1.5.5.7.48.1.1) */
+#define ASN1_OID_OCSP_BASIC                                    \
+       ASN1_OID_INITIAL ( 1, 3 ), ASN1_OID_SINGLE ( 6 ),       \
+       ASN1_OID_SINGLE ( 1 ), ASN1_OID_SINGLE ( 5 ),           \
+       ASN1_OID_SINGLE ( 5 ), ASN1_OID_SINGLE ( 7 ),           \
+       ASN1_OID_SINGLE ( 48 ), ASN1_OID_SINGLE ( 1 ),          \
+       ASN1_OID_SINGLE ( 1 )
+
 /** ASN.1 OID for id-kp-OCSPSigning (1.3.6.1.5.5.7.3.9) */
 #define ASN1_OID_OCSPSIGNING                                   \
        ASN1_OID_INITIAL ( 1, 3 ), ASN1_OID_SINGLE ( 6 ),       \
index 2109cf2f95a0e3958e71c34321c79fac5f150f76..108efc7af28650af1fb75984bfdafb9cce457ee4 100644 (file)
@@ -260,6 +260,7 @@ FILE_LICENCE ( GPL2_OR_LATER );
 #define ERRFILE_menu_ui                      ( ERRFILE_OTHER | 0x002c0000 )
 #define ERRFILE_menu_cmd             ( ERRFILE_OTHER | 0x002d0000 )
 #define ERRFILE_validator            ( ERRFILE_OTHER | 0x002e0000 )
+#define ERRFILE_ocsp                 ( ERRFILE_OTHER | 0x002f0000 )
 
 /** @} */
 
diff --git a/src/include/ipxe/ocsp.h b/src/include/ipxe/ocsp.h
new file mode 100644 (file)
index 0000000..e841492
--- /dev/null
@@ -0,0 +1,108 @@
+#ifndef _IPXE_OCSP_H
+#define _IPXE_OCSP_H
+
+/** @file
+ *
+ * Online Certificate Status Protocol
+ *
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER );
+
+#include <stdarg.h>
+#include <time.h>
+#include <ipxe/asn1.h>
+#include <ipxe/x509.h>
+#include <ipxe/refcnt.h>
+
+/** OCSP algorithm identifier */
+#define OCSP_ALGORITHM_IDENTIFIER( ... )                               \
+       ASN1_OID, VA_ARG_COUNT ( __VA_ARGS__ ), __VA_ARGS__,            \
+       ASN1_NULL, 0x00
+
+/* OCSP response statuses */
+#define OCSP_STATUS_SUCCESSFUL         0x00
+#define OCSP_STATUS_MALFORMED_REQUEST  0x01
+#define OCSP_STATUS_INTERNAL_ERROR     0x02
+#define OCSP_STATUS_TRY_LATER          0x03
+#define OCSP_STATUS_SIG_REQUIRED       0x05
+#define OCSP_STATUS_UNAUTHORIZED       0x06
+
+/** Margin of error allowed in OCSP response times
+ *
+ * We allow a generous margin of error: 12 hours to allow for the
+ * local time zone being non-GMT, plus 30 minutes to allow for general
+ * clock drift.
+ */
+#define OCSP_ERROR_MARGIN_TIME ( ( 12 * 60 + 30 ) * 60 )
+
+/** An OCSP request */
+struct ocsp_request {
+       /** Request builder */
+       struct asn1_builder builder;
+       /** Certificate ID */
+       struct asn1_cursor cert_id;
+};
+
+/** An OCSP response */
+struct ocsp_response {
+       /** Raw response */
+       void *data;
+       /** Raw tbsResponseData */
+       struct asn1_cursor tbs;
+       /** Time at which status is known to be correct */
+       time_t this_update;
+       /** Time at which newer status information will be available */
+       time_t next_update;
+       /** Signature algorithm */
+       struct asn1_algorithm *algorithm;
+       /** Signature value */
+       struct asn1_bit_string signature;
+       /** Signing certificate */
+       struct x509_certificate *signer;
+};
+
+/** An OCSP check */
+struct ocsp_check {
+       /** Reference count */
+       struct refcnt refcnt;
+       /** Certificate being checked */
+       struct x509_certificate *cert;
+       /** Issuing certificate */
+       struct x509_certificate *issuer;
+       /** Request */
+       struct ocsp_request request;
+       /** Response */
+       struct ocsp_response response;
+};
+
+/**
+ * Get reference to OCSP check
+ *
+ * @v ocsp             OCSP check
+ * @ret ocsp           OCSP check
+ */
+static inline __attribute__ (( always_inline )) struct ocsp_check *
+ocsp_get ( struct ocsp_check *ocsp ) {
+       ref_get ( &ocsp->refcnt );
+       return ocsp;
+}
+
+/**
+ * Drop reference to OCSP check
+ *
+ * @v ocsp             OCSP check
+ */
+static inline __attribute__ (( always_inline )) void
+ocsp_put ( struct ocsp_check *ocsp ) {
+       ref_put ( &ocsp->refcnt );
+}
+
+extern int ocsp_check ( struct x509_certificate *cert,
+                       struct x509_certificate *issuer,
+                       struct ocsp_check **ocsp );
+extern int ocsp_response ( struct ocsp_check *ocsp, const void *data,
+                          size_t len );
+extern int ocsp_validate ( struct ocsp_check *check, time_t time );
+
+#endif /* _IPXE_OCSP_H */
index 6dc31b45e9ab333e4cd2d042a7a326e3292573a3..a5626c8a8b59adf7f9d7cb34309f22192ac42729 100644 (file)
@@ -126,6 +126,8 @@ enum x509_extended_key_usage_bits {
 struct x509_ocsp_responder {
        /** URI */
        char *uri;
+       /** OCSP status is good */
+       int good;
 };
 
 /** X.509 certificate authority information access */
@@ -322,6 +324,9 @@ struct x509_root {
 
 extern int x509_certificate ( const void *data, size_t len,
                              struct x509_certificate **cert );
+extern int x509_validate ( struct x509_certificate *cert,
+                          struct x509_certificate *issuer,
+                          time_t time, struct x509_root *root );
 
 extern struct x509_chain * x509_alloc_chain ( void );
 extern int x509_append ( struct x509_chain *chain,