| /* |
| * 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 asn1_cursor *key = &signer->subject.public_key.raw; |
| uint8_t digest_ctx[ digest->ctxsize ]; |
| uint8_t digest_out[ digest->digestsize ]; |
| 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 ); |
| |
| /* Verify digest */ |
| if ( ( rc = pubkey_verify ( pubkey, key, 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 )); |
| return rc; |
| } |
| |
| DBGC2 ( ocsp, "OCSP %p \"%s\" signature is correct\n", |
| ocsp, x509_name ( ocsp->cert ) ); |
| return 0; |
| } |
| |
| /** |
| * 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; |
| } |