|  | /* | 
|  | * 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; | 
|  | } |