diff options
Diffstat (limited to 'src/VBox/Devices/PC/ipxe/src/crypto/ocsp.c')
-rw-r--r-- | src/VBox/Devices/PC/ipxe/src/crypto/ocsp.c | 974 |
1 files changed, 974 insertions, 0 deletions
diff --git a/src/VBox/Devices/PC/ipxe/src/crypto/ocsp.c b/src/VBox/Devices/PC/ipxe/src/crypto/ocsp.c new file mode 100644 index 00000000..cc957b40 --- /dev/null +++ b/src/VBox/Devices/PC/ipxe/src/crypto/ocsp.c @@ -0,0 +1,974 @@ +/* + * 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 <stdio.h> +#include <string.h> +#include <errno.h> +#include <ipxe/asn1.h> +#include <ipxe/x509.h> +#include <ipxe/sha1.h> +#include <ipxe/base64.h> +#include <ipxe/uri.h> +#include <ipxe/ocsp.h> +#include <config/crypto.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 EACCES_NO_RESPONDER \ + __einfo_error ( EINFO_EACCES_NO_RESPONDER ) +#define EINFO_EACCES_NO_RESPONDER \ + __einfo_uniqify ( EINFO_EACCES, 0x05, \ + "Missing OCSP responder certificate" ) +#define ENOTSUP_RESPONSE_TYPE \ + __einfo_error ( EINFO_ENOTSUP_RESPONSE_TYPE ) +#define EINFO_ENOTSUP_RESPONSE_TYPE \ + __einfo_uniqify ( EINFO_ENOTSUP, 0x01, \ + "Unsupported OCSP response type" ) +#define ENOTSUP_RESPONDER_ID \ + __einfo_error ( EINFO_ENOTSUP_RESPONDER_ID ) +#define EINFO_ENOTSUP_RESPONDER_ID \ + __einfo_uniqify ( EINFO_ENOTSUP, 0x02, \ + "Unsupported OCSP responder ID" ) +#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 ( EINFO_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_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->uri_string ); + 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_tail = &ocsp->request.cert_id_tail; + 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, x509_name ( ocsp->cert ), strerror ( rc ) ); + return rc; + } + DBGC2 ( ocsp, "OCSP %p \"%s\" request is:\n", + ocsp, x509_name ( ocsp->cert ) ); + DBGC2_HDA ( ocsp, 0, builder->data, builder->len ); + + /* Parse certificate ID for comparison with response */ + cert_id_tail->data = builder->data; + cert_id_tail->len = builder->len; + if ( ( rc = ( asn1_enter ( cert_id_tail, ASN1_SEQUENCE ), + asn1_enter ( cert_id_tail, ASN1_SEQUENCE ), + asn1_enter ( cert_id_tail, ASN1_SEQUENCE ), + asn1_enter ( cert_id_tail, ASN1_SEQUENCE ), + asn1_enter ( cert_id_tail, ASN1_SEQUENCE ), + asn1_skip ( cert_id_tail, ASN1_SEQUENCE ) ) ) != 0 ) { + DBGC ( ocsp, "OCSP %p \"%s\" could not locate certID: %s\n", + ocsp, x509_name ( ocsp->cert ), strerror ( rc ) ); + return rc; + } + + return 0; +} + +/** + * Build OCSP URI string + * + * @v ocsp OCSP check + * @ret rc Return status code + */ +static int ocsp_uri_string ( struct ocsp_check *ocsp ) { + struct x509_ocsp_responder *responder = + &ocsp->cert->extensions.auth_info.ocsp; + char *base64; + char *sep; + size_t base64_len; + size_t uri_len; + size_t len; + int rc; + + /* Sanity check */ + if ( ! responder->uri.len ) { + DBGC ( ocsp, "OCSP %p \"%s\" has no OCSP URI\n", + ocsp, x509_name ( ocsp->cert ) ); + rc = -ENOTTY; + goto err_no_uri; + } + + /* Calculate base64-encoded request length */ + base64_len = ( base64_encoded_len ( ocsp->request.builder.len ) + + 1 /* NUL */ ); + + /* Allocate and construct the base64-encoded request */ + base64 = malloc ( base64_len ); + if ( ! base64 ) { + rc = -ENOMEM; + goto err_alloc_base64; + } + base64_encode ( ocsp->request.builder.data, ocsp->request.builder.len, + base64, base64_len ); + + /* Calculate URI-encoded base64-encoded request length */ + uri_len = ( uri_encode ( URI_PATH, base64, ( base64_len - 1 /* NUL */ ), + NULL, 0 ) + 1 /* NUL */ ); + + /* Allocate and construct the URI string */ + len = ( responder->uri.len + 1 /* possible "/" */ + uri_len ); + ocsp->uri_string = zalloc ( len ); + if ( ! ocsp->uri_string ) { + rc = -ENOMEM; + goto err_alloc_uri; + } + memcpy ( ocsp->uri_string, responder->uri.data, responder->uri.len ); + sep = &ocsp->uri_string[ responder->uri.len - 1 ]; + if ( *sep != '/' ) + *(++sep) = '/'; + uri_encode ( URI_PATH, base64, base64_len, ( sep + 1 ), uri_len ); + DBGC2 ( ocsp, "OCSP %p \"%s\" URI is %s\n", + ocsp, x509_name ( ocsp->cert ), ocsp->uri_string ); + + /* Success */ + rc = 0; + + err_alloc_uri: + free ( base64 ); + err_alloc_base64: + err_no_uri: + return rc; +} + +/** + * 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->root != NULL ); + + /* 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; + + /* Build URI string */ + if ( ( rc = ocsp_uri_string ( *ocsp ) ) != 0 ) + goto err_uri_string; + + return 0; + + err_uri_string: + err_request: + ocsp_put ( *ocsp ); + err_alloc: + *ocsp = NULL; + 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, x509_name ( ocsp->cert ), strerror ( rc )); + return rc; + } + + /* Extract response status */ + if ( cursor.len != sizeof ( status ) ) { + DBGC ( ocsp, "OCSP %p \"%s\" invalid status:\n", + ocsp, x509_name ( ocsp->cert ) ); + 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, x509_name ( ocsp->cert ), 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, x509_name ( ocsp->cert ) ); + DBGC_HDA ( ocsp, 0, cursor.data, cursor.len ); + return -ENOTSUP_RESPONSE_TYPE; + } + + return 0; +} + +/** + * Compare responder's certificate name + * + * @v ocsp OCSP check + * @v cert Certificate + * @ret difference Difference as returned by memcmp() + */ +static int ocsp_compare_responder_name ( struct ocsp_check *ocsp, + struct x509_certificate *cert ) { + struct ocsp_responder *responder = &ocsp->response.responder; + + /* Compare responder ID with certificate's subject */ + return asn1_compare ( &responder->id, &cert->subject.raw ); +} + +/** + * Compare responder's certificate public key hash + * + * @v ocsp OCSP check + * @v cert Certificate + * @ret difference Difference as returned by memcmp() + */ +static int ocsp_compare_responder_key_hash ( struct ocsp_check *ocsp, + struct x509_certificate *cert ) { + struct ocsp_responder *responder = &ocsp->response.responder; + struct asn1_cursor key_hash; + uint8_t ctx[SHA1_CTX_SIZE]; + uint8_t digest[SHA1_DIGEST_SIZE]; + int difference; + + /* Enter responder key hash */ + memcpy ( &key_hash, &responder->id, sizeof ( key_hash ) ); + asn1_enter ( &key_hash, ASN1_OCTET_STRING ); + + /* Sanity check */ + difference = ( sizeof ( digest ) - key_hash.len ); + if ( difference ) + return difference; + + /* Generate SHA1 hash of certificate's public key */ + digest_init ( &sha1_algorithm, ctx ); + digest_update ( &sha1_algorithm, ctx, + cert->subject.public_key.raw_bits.data, + cert->subject.public_key.raw_bits.len ); + digest_final ( &sha1_algorithm, ctx, digest ); + + /* Compare responder key hash with hash of certificate's public key */ + return memcmp ( digest, key_hash.data, sizeof ( digest ) ); +} + +/** + * Parse OCSP responder ID + * + * @v ocsp OCSP check + * @v raw ASN.1 cursor + * @ret rc Return status code + */ +static int ocsp_parse_responder_id ( struct ocsp_check *ocsp, + const struct asn1_cursor *raw ) { + struct ocsp_responder *responder = &ocsp->response.responder; + struct asn1_cursor *responder_id = &responder->id; + unsigned int type; + + /* Enter responder ID */ + memcpy ( responder_id, raw, sizeof ( *responder_id ) ); + type = asn1_type ( responder_id ); + asn1_enter_any ( responder_id ); + + /* Identify responder ID type */ + switch ( type ) { + case ASN1_EXPLICIT_TAG ( 1 ) : + DBGC2 ( ocsp, "OCSP %p \"%s\" responder identified by name\n", + ocsp, x509_name ( ocsp->cert ) ); + responder->compare = ocsp_compare_responder_name; + return 0; + case ASN1_EXPLICIT_TAG ( 2 ) : + DBGC2 ( ocsp, "OCSP %p \"%s\" responder identified by key " + "hash\n", ocsp, x509_name ( ocsp->cert ) ); + responder->compare = ocsp_compare_responder_key_hash; + return 0; + default: + DBGC ( ocsp, "OCSP %p \"%s\" unsupported responder ID type " + "%d\n", ocsp, x509_name ( ocsp->cert ), type ); + return -ENOTSUP_RESPONDER_ID; + } +} + +/** + * 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 ) { + static struct asn1_cursor algorithm = { + .data = ocsp_algorithm_id, + .len = sizeof ( ocsp_algorithm_id ), + }; + struct asn1_cursor cert_id; + struct asn1_cursor cursor; + int rc; + + /* Enter cert ID */ + memcpy ( &cert_id, raw, sizeof ( cert_id ) ); + asn1_enter ( &cert_id, ASN1_SEQUENCE ); + + /* Check certID algorithm (but not parameters) */ + memcpy ( &cursor, &cert_id, sizeof ( cursor ) ); + if ( ( rc = ( asn1_enter ( &cursor, ASN1_SEQUENCE ), + asn1_shrink ( &cursor, ASN1_OID ), + asn1_shrink ( &algorithm, ASN1_OID ) ) ) != 0 ) { + DBGC ( ocsp, "OCSP %p \"%s\" certID missing algorithm:\n", + ocsp, x509_name ( ocsp->cert ) ); + DBGC_HDA ( ocsp, 0, cursor.data, cursor.len ); + return -EACCES_CERT_MISMATCH; + } + if ( asn1_compare ( &cursor, &algorithm ) != 0 ) { + DBGC ( ocsp, "OCSP %p \"%s\" certID wrong algorithm:\n", + ocsp, x509_name ( ocsp->cert ) ); + DBGC_HDA ( ocsp, 0, cursor.data, cursor.len ); + return -EACCES_CERT_MISMATCH; + } + + /* Check remaining certID fields */ + asn1_skip ( &cert_id, ASN1_SEQUENCE ); + if ( asn1_compare ( &cert_id, &ocsp->request.cert_id_tail ) != 0 ) { + DBGC ( ocsp, "OCSP %p \"%s\" certID mismatch:\n", + ocsp, x509_name ( ocsp->cert ) ); + DBGC_HDA ( ocsp, 0, ocsp->request.cert_id_tail.data, + ocsp->request.cert_id_tail.len ); + DBGC_HDA ( ocsp, 0, cert_id.data, cert_id.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, x509_name ( ocsp->cert ) ); + 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, x509_name ( ocsp->cert ), strerror ( rc ) ); + return rc; + } + DBGC2 ( ocsp, "OCSP %p \"%s\" this update was at time %lld\n", + ocsp, x509_name ( ocsp->cert ), 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, + x509_name ( ocsp->cert ), strerror ( rc ) ); + return rc; + } + DBGC2 ( ocsp, "OCSP %p \"%s\" next update is at time %lld\n", + ocsp, x509_name ( ocsp->cert ), 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, x509_name ( ocsp->cert ) ); + 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 ) ); + + /* Parse responderID */ + if ( ( rc = ocsp_parse_responder_id ( ocsp, &cursor ) ) != 0 ) + return rc; + 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; + struct x509_certificate *cert; + 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"). We therefore + * need to identify only the single certificate matching the + * Responder ID. + */ + while ( cursor.len ) { + + /* Parse certificate */ + if ( ( rc = x509_certificate ( cursor.data, cursor.len, + &cert ) ) != 0 ) { + DBGC ( ocsp, "OCSP %p \"%s\" could not parse " + "certificate: %s\n", ocsp, + x509_name ( ocsp->cert ), strerror ( rc ) ); + DBGC_HDA ( ocsp, 0, cursor.data, cursor.len ); + return rc; + } + + /* Use if this certificate matches the responder ID */ + if ( response->responder.compare ( ocsp, cert ) == 0 ) { + response->signer = cert; + DBGC2 ( ocsp, "OCSP %p \"%s\" response is signed by ", + ocsp, x509_name ( ocsp->cert ) ); + DBGC2 ( ocsp, "\"%s\"\n", + x509_name ( response->signer ) ); + return 0; + } + + /* Otherwise, discard this certificate */ + x509_put ( cert ); + asn1_skip_any ( &cursor ); + } + + DBGC ( ocsp, "OCSP %p \"%s\" missing responder certificate\n", + ocsp, x509_name ( ocsp->cert ) ); + return -EACCES_NO_RESPONDER; +} + +/** + * 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, x509_name ( ocsp->cert ), strerror ( rc ) ); + return rc; + } + DBGC2 ( ocsp, "OCSP %p \"%s\" signature algorithm is %s\n", + ocsp, x509_name ( ocsp->cert ), (*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, x509_name ( ocsp->cert ), 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; +} + +/** + * 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, x509_name ( ocsp->cert ), 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, x509_name ( ocsp->cert ), strerror ( rc )); + goto err_verify; + } + + DBGC2 ( ocsp, "OCSP %p \"%s\" signature is correct\n", + ocsp, x509_name ( ocsp->cert ) ); + + 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; + int rc; + + /* Sanity checks */ + assert ( response->data != NULL ); + + /* The response may include a signer certificate; if this is + * not present then the response must have been signed + * directly by the issuer. + */ + signer = ( response->signer ? response->signer : ocsp->issuer ); + + /* 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->issuer->root ) ) != 0 ) { + DBGC ( ocsp, "OCSP %p \"%s\" could not validate ", + ocsp, x509_name ( ocsp->cert ) ); + DBGC ( ocsp, "signer \"%s\": %s\n", + x509_name ( signer ), 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\" ", + ocsp, x509_name ( ocsp->cert ) ); + DBGC ( ocsp, "signer \"%s\" is not an OCSP-signing " + "certificate\n", x509_name ( signer ) ); + 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 + TIMESTAMP_ERROR_MARGIN ) ) { + DBGC ( ocsp, "OCSP %p \"%s\" response is not yet valid (at " + "time %lld)\n", ocsp, x509_name ( ocsp->cert ), time ); + return -EACCES_STALE; + } + if ( response->next_update < ( time - TIMESTAMP_ERROR_MARGIN ) ) { + DBGC ( ocsp, "OCSP %p \"%s\" response is stale (at time " + "%lld)\n", ocsp, x509_name ( ocsp->cert ), time ); + return -EACCES_STALE; + } + DBGC2 ( ocsp, "OCSP %p \"%s\" response is valid (at time %lld)\n", + ocsp, x509_name ( ocsp->cert ), 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->issuer->root ) ) != 0 ) { + DBGC ( ocsp, "OCSP %p \"%s\" could not validate certificate: " + "%s\n", ocsp, x509_name ( ocsp->cert ), strerror ( rc )); + return rc; + } + DBGC ( ocsp, "OCSP %p \"%s\" successfully validated ", + ocsp, x509_name ( ocsp->cert ) ); + DBGC ( ocsp, "using \"%s\"\n", x509_name ( signer ) ); + + return 0; +} |