2
0
mirror of https://github.com/xcat2/xNBA.git synced 2024-11-23 01:51:58 +00:00

[crypto] Add framework for OCSP

Add support for constructing OCSP queries and parsing OCSP responses.
(There is no support yet for actually issuing an OCSP query via an
HTTP POST.)

Signed-off-by: Michael Brown <mcb30@ipxe.org>
This commit is contained in:
Michael Brown 2012-05-14 18:22:38 +01:00
parent deac4ea1ba
commit 39ac285a8a
6 changed files with 877 additions and 3 deletions

749
src/crypto/ocsp.c Normal file
View File

@ -0,0 +1,749 @@
/*
* Copyright (C) 2012 Michael Brown <mbrown@fensystems.co.uk>.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA.
*/
FILE_LICENCE ( GPL2_OR_LATER );
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <ipxe/asn1.h>
#include <ipxe/x509.h>
#include <ipxe/sha1.h>
#include <ipxe/ocsp.h>
/** @file
*
* Online Certificate Status Protocol
*
*/
/* Disambiguate the various error causes */
#define EACCES_CERT_STATUS \
__einfo_error ( EINFO_EACCES_CERT_STATUS )
#define EINFO_EACCES_CERT_STATUS \
__einfo_uniqify ( EINFO_EACCES, 0x01, \
"Certificate status not good" )
#define EACCES_CERT_MISMATCH \
__einfo_error ( EINFO_EACCES_CERT_MISMATCH )
#define EINFO_EACCES_CERT_MISMATCH \
__einfo_uniqify ( EINFO_EACCES, 0x02, \
"Certificate ID mismatch" )
#define EACCES_NON_OCSP_SIGNING \
__einfo_error ( EINFO_EACCES_NON_OCSP_SIGNING )
#define EINFO_EACCES_NON_OCSP_SIGNING \
__einfo_uniqify ( EINFO_EACCES, 0x03, \
"Not an OCSP signing certificate" )
#define EACCES_STALE \
__einfo_error ( EINFO_EACCES_STALE )
#define EINFO_EACCES_STALE \
__einfo_uniqify ( EINFO_EACCES, 0x04, \
"Stale (or premature) OCSP repsonse" )
#define EPROTO_MALFORMED_REQUEST \
__einfo_error ( EINFO_EPROTO_MALFORMED_REQUEST )
#define EINFO_EPROTO_MALFORMED_REQUEST \
__einfo_uniqify ( EINFO_EPROTO, OCSP_STATUS_MALFORMED_REQUEST, \
"Illegal confirmation request" )
#define EPROTO_INTERNAL_ERROR \
__einfo_error ( EINFO_EPROTO_INTERNAL_ERROR )
#define EINFO_EPROTO_INTERNAL_ERROR \
__einfo_uniqify ( EINFO_EPROTO, OCSP_STATUS_INTERNAL_ERROR, \
"Internal error in issuer" )
#define EPROTO_TRY_LATER \
__einfo_error ( EINFO_EPROTO_TRY_LATER )
#define EINFO_EPROTO_TRY_LATER \
__einfo_uniqify ( EINFO_EPROTO, OCSP_STATUS_TRY_LATER, \
"Try again later" )
#define EPROTO_SIG_REQUIRED \
__einfo_error ( EINFO_EPROTO_SIG_REQUIRED )
#define EINFO_EPROTO_SIG_REQUIRED \
__einfo_uniqify ( EINFO_EPROTO, OCSP_STATUS_SIG_REQUIRED, \
"Must sign the request" )
#define EPROTO_UNAUTHORIZED \
__einfo_error ( EINFO_EPROTO_UNAUTHORIZED )
#define EINFO_EPROTO_UNAUTHORIZED \
__einfo_uniqify ( EINFO_EPROTO, OCSP_STATUS_UNAUTHORIZED, \
"Request unauthorized" )
#define EPROTO_STATUS( status ) \
EUNIQ ( EPROTO, (status), EPROTO_MALFORMED_REQUEST, \
EPROTO_INTERNAL_ERROR, EPROTO_TRY_LATER, \
EPROTO_SIG_REQUIRED, EPROTO_UNAUTHORIZED )
/** OCSP digest algorithm */
#define ocsp_digest_algorithm sha1_algorithm
/** OCSP digest algorithm identifier */
static const uint8_t ocsp_algorithm_id[] =
{ OCSP_ALGORITHM_IDENTIFIER ( ASN1_OID_SHA1 ) };
/** OCSP basic response type */
static const uint8_t oid_basic_response_type[] = { ASN1_OID_OCSP_BASIC };
/** OCSP basic response type cursor */
static struct asn1_cursor oid_basic_response_type_cursor =
ASN1_OID_CURSOR ( oid_basic_response_type );
/**
* Free OCSP check
*
* @v refcnt Reference count
*/
static void ocsp_free ( struct refcnt *refcnt ) {
struct ocsp_check *ocsp =
container_of ( refcnt, struct ocsp_check, refcnt );
x509_put ( ocsp->cert );
x509_put ( ocsp->issuer );
free ( ocsp->request.builder.data );
free ( ocsp->response.data );
x509_put ( ocsp->response.signer );
free ( ocsp );
}
/**
* Build OCSP request
*
* @v ocsp OCSP check
* @ret rc Return status code
*/
static int ocsp_request ( struct ocsp_check *ocsp ) {
struct digest_algorithm *digest = &ocsp_digest_algorithm;
struct asn1_builder *builder = &ocsp->request.builder;
struct asn1_cursor *cert_id = &ocsp->request.cert_id;
uint8_t digest_ctx[digest->ctxsize];
uint8_t name_digest[digest->digestsize];
uint8_t pubkey_digest[digest->digestsize];
int rc;
/* Generate digests */
digest_init ( digest, digest_ctx );
digest_update ( digest, digest_ctx, ocsp->cert->issuer.raw.data,
ocsp->cert->issuer.raw.len );
digest_final ( digest, digest_ctx, name_digest );
digest_init ( digest, digest_ctx );
digest_update ( digest, digest_ctx,
ocsp->issuer->subject.public_key.raw_bits.data,
ocsp->issuer->subject.public_key.raw_bits.len );
digest_final ( digest, digest_ctx, pubkey_digest );
/* Construct request */
if ( ( rc = ( asn1_prepend_raw ( builder, ocsp->cert->serial.raw.data,
ocsp->cert->serial.raw.len ),
asn1_prepend ( builder, ASN1_OCTET_STRING,
pubkey_digest, sizeof ( pubkey_digest ) ),
asn1_prepend ( builder, ASN1_OCTET_STRING,
name_digest, sizeof ( name_digest ) ),
asn1_prepend ( builder, ASN1_SEQUENCE,
ocsp_algorithm_id,
sizeof ( ocsp_algorithm_id ) ),
asn1_wrap ( builder, ASN1_SEQUENCE ),
asn1_wrap ( builder, ASN1_SEQUENCE ),
asn1_wrap ( builder, ASN1_SEQUENCE ),
asn1_wrap ( builder, ASN1_SEQUENCE ),
asn1_wrap ( builder, ASN1_SEQUENCE ) ) ) != 0 ) {
DBGC ( ocsp, "OCSP %p \"%s\" could not build request: %s\n",
ocsp, ocsp->cert->subject.name, strerror ( rc ) );
return rc;
}
DBGC2 ( ocsp, "OCSP %p \"%s\" request is:\n",
ocsp, ocsp->cert->subject.name );
DBGC2_HDA ( ocsp, 0, builder->data, builder->len );
/* Parse certificate ID for comparison with response */
cert_id->data = builder->data;
cert_id->len = builder->len;
if ( ( rc = ( asn1_enter ( cert_id, ASN1_SEQUENCE ),
asn1_enter ( cert_id, ASN1_SEQUENCE ),
asn1_enter ( cert_id, ASN1_SEQUENCE ),
asn1_enter ( cert_id, ASN1_SEQUENCE ) ) ) != 0 ) {
DBGC ( ocsp, "OCSP %p \"%s\" could not locate certID: %s\n",
ocsp, ocsp->cert->subject.name, strerror ( rc ) );
return rc;
}
return 0;
}
/**
* Create OCSP check
*
* @v cert Certificate to check
* @v issuer Issuing certificate
* @ret ocsp OCSP check
* @ret rc Return status code
*/
int ocsp_check ( struct x509_certificate *cert,
struct x509_certificate *issuer,
struct ocsp_check **ocsp ) {
int rc;
/* Sanity checks */
assert ( cert != NULL );
assert ( issuer != NULL );
assert ( issuer->valid );
/* Allocate and initialise check */
*ocsp = zalloc ( sizeof ( **ocsp ) );
if ( ! *ocsp ) {
rc = -ENOMEM;
goto err_alloc;
}
ref_init ( &(*ocsp)->refcnt, ocsp_free );
(*ocsp)->cert = x509_get ( cert );
(*ocsp)->issuer = x509_get ( issuer );
/* Build request */
if ( ( rc = ocsp_request ( *ocsp ) ) != 0 )
goto err_request;
return 0;
err_request:
ocsp_put ( *ocsp );
err_alloc:
return rc;
}
/**
* Parse OCSP response status
*
* @v ocsp OCSP check
* @v raw ASN.1 cursor
* @ret rc Return status code
*/
static int ocsp_parse_response_status ( struct ocsp_check *ocsp,
const struct asn1_cursor *raw ) {
struct asn1_cursor cursor;
uint8_t status;
int rc;
/* Enter responseStatus */
memcpy ( &cursor, raw, sizeof ( cursor ) );
if ( ( rc = asn1_enter ( &cursor, ASN1_ENUMERATED ) ) != 0 ) {
DBGC ( ocsp, "OCSP %p \"%s\" could not locate responseStatus: "
"%s\n", ocsp, ocsp->cert->subject.name, strerror ( rc ));
return rc;
}
/* Extract response status */
if ( cursor.len != sizeof ( status ) ) {
DBGC ( ocsp, "OCSP %p \"%s\" invalid status:\n",
ocsp, ocsp->cert->subject.name );
DBGC_HDA ( ocsp, 0, cursor.data, cursor.len );
return -EINVAL;
}
memcpy ( &status, cursor.data, sizeof ( status ) );
/* Check response status */
if ( status != OCSP_STATUS_SUCCESSFUL ) {
DBGC ( ocsp, "OCSP %p \"%s\" response status %d\n",
ocsp, ocsp->cert->subject.name, status );
return EPROTO_STATUS ( status );
}
return 0;
}
/**
* Parse OCSP response type
*
* @v ocsp OCSP check
* @v raw ASN.1 cursor
* @ret rc Return status code
*/
static int ocsp_parse_response_type ( struct ocsp_check *ocsp,
const struct asn1_cursor *raw ) {
struct asn1_cursor cursor;
/* Enter responseType */
memcpy ( &cursor, raw, sizeof ( cursor ) );
asn1_enter ( &cursor, ASN1_OID );
/* Check responseType is "basic" */
if ( asn1_compare ( &oid_basic_response_type_cursor, &cursor ) != 0 ) {
DBGC ( ocsp, "OCSP %p \"%s\" response type not supported:\n",
ocsp, ocsp->cert->subject.name );
DBGC_HDA ( ocsp, 0, cursor.data, cursor.len );
return -ENOTSUP;
}
return 0;
}
/**
* Parse OCSP certificate ID
*
* @v ocsp OCSP check
* @v raw ASN.1 cursor
* @ret rc Return status code
*/
static int ocsp_parse_cert_id ( struct ocsp_check *ocsp,
const struct asn1_cursor *raw ) {
struct asn1_cursor cursor;
/* Check certID matches request */
memcpy ( &cursor, raw, sizeof ( cursor ) );
asn1_shrink_any ( &cursor );
if ( asn1_compare ( &cursor, &ocsp->request.cert_id ) != 0 ) {
DBGC ( ocsp, "OCSP %p \"%s\" certID mismatch:\n",
ocsp, ocsp->cert->subject.name );
DBGC_HDA ( ocsp, 0, ocsp->request.cert_id.data,
ocsp->request.cert_id.len );
DBGC_HDA ( ocsp, 0, cursor.data, cursor.len );
return -EACCES_CERT_MISMATCH;
}
return 0;
}
/**
* Parse OCSP responses
*
* @v ocsp OCSP check
* @v raw ASN.1 cursor
* @ret rc Return status code
*/
static int ocsp_parse_responses ( struct ocsp_check *ocsp,
const struct asn1_cursor *raw ) {
struct ocsp_response *response = &ocsp->response;
struct asn1_cursor cursor;
int rc;
/* Enter responses */
memcpy ( &cursor, raw, sizeof ( cursor ) );
asn1_enter ( &cursor, ASN1_SEQUENCE );
/* Enter first singleResponse */
asn1_enter ( &cursor, ASN1_SEQUENCE );
/* Parse certID */
if ( ( rc = ocsp_parse_cert_id ( ocsp, &cursor ) ) != 0 )
return rc;
asn1_skip_any ( &cursor );
/* Check certStatus */
if ( asn1_type ( &cursor ) != ASN1_IMPLICIT_TAG ( 0 ) ) {
DBGC ( ocsp, "OCSP %p \"%s\" non-good certStatus:\n",
ocsp, ocsp->cert->subject.name );
DBGC_HDA ( ocsp, 0, cursor.data, cursor.len );
return -EACCES_CERT_STATUS;
}
asn1_skip_any ( &cursor );
/* Parse thisUpdate */
if ( ( rc = asn1_generalized_time ( &cursor,
&response->this_update ) ) != 0 ) {
DBGC ( ocsp, "OCSP %p \"%s\" could not parse thisUpdate: %s\n",
ocsp, ocsp->cert->subject.name, strerror ( rc ) );
return rc;
}
DBGC2 ( ocsp, "OCSP %p \"%s\" this update was at time %lld\n",
ocsp, ocsp->cert->subject.name, response->this_update );
asn1_skip_any ( &cursor );
/* Parse nextUpdate, if present */
if ( asn1_type ( &cursor ) == ASN1_EXPLICIT_TAG ( 0 ) ) {
asn1_enter ( &cursor, ASN1_EXPLICIT_TAG ( 0 ) );
if ( ( rc = asn1_generalized_time ( &cursor,
&response->next_update ) ) != 0 ) {
DBGC ( ocsp, "OCSP %p \"%s\" could not parse "
"nextUpdate: %s\n", ocsp,
ocsp->cert->subject.name, strerror ( rc ) );
return rc;
}
DBGC2 ( ocsp, "OCSP %p \"%s\" next update is at time %lld\n",
ocsp, ocsp->cert->subject.name, response->next_update );
} else {
/* If no nextUpdate is present, this indicates that
* "newer revocation information is available all the
* time". Actually, this indicates that there is no
* point to performing the OCSP check, since an
* attacker could replay the response at any future
* time and it would still be valid.
*/
DBGC ( ocsp, "OCSP %p \"%s\" responder is a moron\n",
ocsp, ocsp->cert->subject.name );
response->next_update = time ( NULL );
}
return 0;
}
/**
* Parse OCSP response data
*
* @v ocsp OCSP check
* @v raw ASN.1 cursor
* @ret rc Return status code
*/
static int ocsp_parse_tbs_response_data ( struct ocsp_check *ocsp,
const struct asn1_cursor *raw ) {
struct ocsp_response *response = &ocsp->response;
struct asn1_cursor cursor;
int rc;
/* Record raw tbsResponseData */
memcpy ( &cursor, raw, sizeof ( cursor ) );
asn1_shrink_any ( &cursor );
memcpy ( &response->tbs, &cursor, sizeof ( response->tbs ) );
/* Enter tbsResponseData */
asn1_enter ( &cursor, ASN1_SEQUENCE );
/* Skip version, if present */
asn1_skip_if_exists ( &cursor, ASN1_EXPLICIT_TAG ( 0 ) );
/* Skip responderID */
asn1_skip_any ( &cursor );
/* Skip producedAt */
asn1_skip_any ( &cursor );
/* Parse responses */
if ( ( rc = ocsp_parse_responses ( ocsp, &cursor ) ) != 0 )
return rc;
return 0;
}
/**
* Parse OCSP certificates
*
* @v ocsp OCSP check
* @v raw ASN.1 cursor
* @ret rc Return status code
*/
static int ocsp_parse_certs ( struct ocsp_check *ocsp,
const struct asn1_cursor *raw ) {
struct ocsp_response *response = &ocsp->response;
struct asn1_cursor cursor;
int rc;
/* Enter certs */
memcpy ( &cursor, raw, sizeof ( cursor ) );
asn1_enter ( &cursor, ASN1_EXPLICIT_TAG ( 0 ) );
asn1_enter ( &cursor, ASN1_SEQUENCE );
/* Parse certificate, if present. The data structure permits
* multiple certificates, but the protocol requires that the
* OCSP signing certificate must either be the issuer itself,
* or must be directly issued by the issuer (see RFC2560
* section 4.2.2.2 "Authorized Responders").
*/
if ( ( cursor.len != 0 ) &&
( ( rc = x509_certificate ( cursor.data, cursor.len,
&response->signer ) ) != 0 ) ) {
DBGC ( ocsp, "OCSP %p \"%s\" could not parse certificate: "
"%s\n", ocsp, ocsp->cert->subject.name, strerror ( rc ));
DBGC_HDA ( ocsp, 0, cursor.data, cursor.len );
return rc;
}
DBGC2 ( ocsp, "OCSP %p \"%s\" response is signed by \"%s\"\n", ocsp,
ocsp->cert->subject.name, response->signer->subject.name );
return 0;
}
/**
* Parse OCSP basic response
*
* @v ocsp OCSP check
* @v raw ASN.1 cursor
* @ret rc Return status code
*/
static int ocsp_parse_basic_response ( struct ocsp_check *ocsp,
const struct asn1_cursor *raw ) {
struct ocsp_response *response = &ocsp->response;
struct asn1_algorithm **algorithm = &response->algorithm;
struct asn1_bit_string *signature = &response->signature;
struct asn1_cursor cursor;
int rc;
/* Enter BasicOCSPResponse */
memcpy ( &cursor, raw, sizeof ( cursor ) );
asn1_enter ( &cursor, ASN1_SEQUENCE );
/* Parse tbsResponseData */
if ( ( rc = ocsp_parse_tbs_response_data ( ocsp, &cursor ) ) != 0 )
return rc;
asn1_skip_any ( &cursor );
/* Parse signatureAlgorithm */
if ( ( rc = asn1_signature_algorithm ( &cursor, algorithm ) ) != 0 ) {
DBGC ( ocsp, "OCSP %p \"%s\" cannot parse signature "
"algorithm: %s\n",
ocsp, ocsp->cert->subject.name, strerror ( rc ) );
return rc;
}
DBGC2 ( ocsp, "OCSP %p \"%s\" signature algorithm is %s\n",
ocsp, ocsp->cert->subject.name, (*algorithm)->name );
asn1_skip_any ( &cursor );
/* Parse signature */
if ( ( rc = asn1_integral_bit_string ( &cursor, signature ) ) != 0 ) {
DBGC ( ocsp, "OCSP %p \"%s\" cannot parse signature: %s\n",
ocsp, ocsp->cert->subject.name, strerror ( rc ) );
return rc;
}
asn1_skip_any ( &cursor );
/* Parse certs, if present */
if ( ( asn1_type ( &cursor ) == ASN1_EXPLICIT_TAG ( 0 ) ) &&
( ( rc = ocsp_parse_certs ( ocsp, &cursor ) ) != 0 ) )
return rc;
return 0;
}
/**
* Parse OCSP response bytes
*
* @v ocsp OCSP check
* @v raw ASN.1 cursor
* @ret rc Return status code
*/
static int ocsp_parse_response_bytes ( struct ocsp_check *ocsp,
const struct asn1_cursor *raw ) {
struct asn1_cursor cursor;
int rc;
/* Enter responseBytes */
memcpy ( &cursor, raw, sizeof ( cursor ) );
asn1_enter ( &cursor, ASN1_EXPLICIT_TAG ( 0 ) );
asn1_enter ( &cursor, ASN1_SEQUENCE );
/* Parse responseType */
if ( ( rc = ocsp_parse_response_type ( ocsp, &cursor ) ) != 0 )
return rc;
asn1_skip_any ( &cursor );
/* Enter response */
asn1_enter ( &cursor, ASN1_OCTET_STRING );
/* Parse response */
if ( ( rc = ocsp_parse_basic_response ( ocsp, &cursor ) ) != 0 )
return rc;
return 0;
}
/**
* Parse OCSP response
*
* @v ocsp OCSP check
* @v raw ASN.1 cursor
* @ret rc Return status code
*/
static int ocsp_parse_response ( struct ocsp_check *ocsp,
const struct asn1_cursor *raw ) {
struct asn1_cursor cursor;
int rc;
/* Enter OCSPResponse */
memcpy ( &cursor, raw, sizeof ( cursor ) );
asn1_enter ( &cursor, ASN1_SEQUENCE );
/* Parse responseStatus */
if ( ( rc = ocsp_parse_response_status ( ocsp, &cursor ) ) != 0 )
return rc;
asn1_skip_any ( &cursor );
/* Parse responseBytes */
if ( ( rc = ocsp_parse_response_bytes ( ocsp, &cursor ) ) != 0 )
return rc;
return 0;
}
/**
* Receive OCSP response
*
* @v ocsp OCSP check
* @v data Response data
* @v len Length of response data
* @ret rc Return status code
*/
int ocsp_response ( struct ocsp_check *ocsp, const void *data, size_t len ) {
struct ocsp_response *response = &ocsp->response;
struct asn1_cursor cursor;
int rc;
/* Duplicate data */
x509_put ( response->signer );
response->signer = NULL;
free ( response->data );
response->data = malloc ( len );
if ( ! response->data )
return -ENOMEM;
memcpy ( response->data, data, len );
cursor.data = response->data;
cursor.len = len;
/* Parse response */
if ( ( rc = ocsp_parse_response ( ocsp, &cursor ) ) != 0 )
return rc;
return 0;
}
/**
* OCSP dummy root certificate store
*
* OCSP validation uses no root certificates, since it takes place
* only when there already exists a validated issuer certificate.
*/
static struct x509_root ocsp_root = {
.digest = &ocsp_digest_algorithm,
.count = 0,
.fingerprints = NULL,
};
/**
* Check OCSP response signature
*
* @v ocsp OCSP check
* @v signer Signing certificate
* @ret rc Return status code
*/
static int ocsp_check_signature ( struct ocsp_check *ocsp,
struct x509_certificate *signer ) {
struct ocsp_response *response = &ocsp->response;
struct digest_algorithm *digest = response->algorithm->digest;
struct pubkey_algorithm *pubkey = response->algorithm->pubkey;
struct x509_public_key *public_key = &signer->subject.public_key;
uint8_t digest_ctx[ digest->ctxsize ];
uint8_t digest_out[ digest->digestsize ];
uint8_t pubkey_ctx[ pubkey->ctxsize ];
int rc;
/* Generate digest */
digest_init ( digest, digest_ctx );
digest_update ( digest, digest_ctx, response->tbs.data,
response->tbs.len );
digest_final ( digest, digest_ctx, digest_out );
/* Initialise public-key algorithm */
if ( ( rc = pubkey_init ( pubkey, pubkey_ctx, public_key->raw.data,
public_key->raw.len ) ) != 0 ) {
DBGC ( ocsp, "OCSP %p \"%s\" could not initialise public key: "
"%s\n", ocsp, ocsp->cert->subject.name, strerror ( rc ));
goto err_init;
}
/* Verify digest */
if ( ( rc = pubkey_verify ( pubkey, pubkey_ctx, digest, digest_out,
response->signature.data,
response->signature.len ) ) != 0 ) {
DBGC ( ocsp, "OCSP %p \"%s\" signature verification failed: "
"%s\n", ocsp, ocsp->cert->subject.name, strerror ( rc ));
goto err_verify;
}
DBGC2 ( ocsp, "OCSP %p \"%s\" signature is correct\n",
ocsp, ocsp->cert->subject.name );
err_verify:
pubkey_final ( pubkey, pubkey_ctx );
err_init:
return rc;
}
/**
* Validate OCSP response
*
* @v ocsp OCSP check
* @v time Time at which to validate response
* @ret rc Return status code
*/
int ocsp_validate ( struct ocsp_check *ocsp, time_t time ) {
struct ocsp_response *response = &ocsp->response;
struct x509_certificate *signer = response->signer;
int rc;
/* Sanity checks */
assert ( response->data != NULL );
assert ( signer != NULL );
/* Validate signer, if applicable. If the signer is not the
* issuer, then it must be signed directly by the issuer.
*/
if ( signer != ocsp->issuer ) {
/* Forcibly invalidate the signer, since we need to
* ensure that it was signed by our issuer (and not
* some other issuer). This prevents a sub-CA's OCSP
* certificate from fraudulently signing OCSP
* responses from the parent CA.
*/
x509_invalidate ( signer );
if ( ( rc = x509_validate ( signer, ocsp->issuer, time,
&ocsp_root ) ) != 0 ) {
DBGC ( ocsp, "OCSP %p \"%s\" could not validate "
"signer \"%s\": %s\n", ocsp,
ocsp->cert->subject.name, signer->subject.name,
strerror ( rc ) );
return rc;
}
/* If signer is not the issuer, then it must have the
* extendedKeyUsage id-kp-OCSPSigning.
*/
if ( ! ( signer->extensions.ext_usage.bits &
X509_OCSP_SIGNING ) ) {
DBGC ( ocsp, "OCSP %p \"%s\" signer \"%s\" is "
"not an OCSP-signing certificate\n", ocsp,
ocsp->cert->subject.name, signer->subject.name );
return -EACCES_NON_OCSP_SIGNING;
}
}
/* Check OCSP response signature */
if ( ( rc = ocsp_check_signature ( ocsp, signer ) ) != 0 )
return rc;
/* Check OCSP response is valid at the specified time
* (allowing for some margin of error).
*/
if ( response->this_update > ( time - OCSP_ERROR_MARGIN_TIME ) ) {
DBGC ( ocsp, "OCSP %p \"%s\" response is not yet valid (at "
"time %lld)\n", ocsp, ocsp->cert->subject.name, time );
return -EACCES_STALE;
}
if ( response->next_update < ( time + OCSP_ERROR_MARGIN_TIME ) ) {
DBGC ( ocsp, "OCSP %p \"%s\" response is stale (at time "
"%lld)\n", ocsp, ocsp->cert->subject.name, time );
return -EACCES_STALE;
}
DBGC2 ( ocsp, "OCSP %p \"%s\" response is valid (at time %lld)\n",
ocsp, ocsp->cert->subject.name, time );
/* Mark certificate as passing OCSP verification */
ocsp->cert->extensions.auth_info.ocsp.good = 1;
/* Validate certificate against issuer */
if ( ( rc = x509_validate ( ocsp->cert, ocsp->issuer, time,
&ocsp_root ) ) != 0 ) {
DBGC ( ocsp, "OCSP %p \"%s\" could not validate certificate: "
"%s\n", ocsp, ocsp->cert->subject.name, strerror ( rc ));
return rc;
}
DBGC ( ocsp, "OCSP %p \"%s\" successfully validated using \"%s\"\n",
ocsp, ocsp->cert->subject.name, signer->subject.name );
return 0;
}

View File

@ -1290,9 +1290,9 @@ int x509_check_time ( struct x509_certificate *cert, time_t time ) {
* successfully validated then @c issuer, @c time, and @c root will be
* ignored.
*/
static int x509_validate ( struct x509_certificate *cert,
struct x509_certificate *issuer,
time_t time, struct x509_root *root ) {
int x509_validate ( struct x509_certificate *cert,
struct x509_certificate *issuer,
time_t time, struct x509_root *root ) {
unsigned int max_path_remaining;
int rc;

View File

@ -70,6 +70,9 @@ struct asn1_builder_header {
/** ASN.1 object identifier */
#define ASN1_OID 0x06
/** ASN.1 enumeration */
#define ASN1_ENUMERATED 0x0a
/** ASN.1 UTC time */
#define ASN1_UTC_TIME 0x17
@ -204,6 +207,14 @@ struct asn1_builder_header {
ASN1_OID_SINGLE ( 5 ), ASN1_OID_SINGLE ( 7 ), \
ASN1_OID_SINGLE ( 48 ), ASN1_OID_SINGLE ( 1 )
/** ASN.1 OID for id-pkix-ocsp-basic ( 1.3.6.1.5.5.7.48.1.1) */
#define ASN1_OID_OCSP_BASIC \
ASN1_OID_INITIAL ( 1, 3 ), ASN1_OID_SINGLE ( 6 ), \
ASN1_OID_SINGLE ( 1 ), ASN1_OID_SINGLE ( 5 ), \
ASN1_OID_SINGLE ( 5 ), ASN1_OID_SINGLE ( 7 ), \
ASN1_OID_SINGLE ( 48 ), ASN1_OID_SINGLE ( 1 ), \
ASN1_OID_SINGLE ( 1 )
/** ASN.1 OID for id-kp-OCSPSigning (1.3.6.1.5.5.7.3.9) */
#define ASN1_OID_OCSPSIGNING \
ASN1_OID_INITIAL ( 1, 3 ), ASN1_OID_SINGLE ( 6 ), \

View File

@ -260,6 +260,7 @@ FILE_LICENCE ( GPL2_OR_LATER );
#define ERRFILE_menu_ui ( ERRFILE_OTHER | 0x002c0000 )
#define ERRFILE_menu_cmd ( ERRFILE_OTHER | 0x002d0000 )
#define ERRFILE_validator ( ERRFILE_OTHER | 0x002e0000 )
#define ERRFILE_ocsp ( ERRFILE_OTHER | 0x002f0000 )
/** @} */

108
src/include/ipxe/ocsp.h Normal file
View File

@ -0,0 +1,108 @@
#ifndef _IPXE_OCSP_H
#define _IPXE_OCSP_H
/** @file
*
* Online Certificate Status Protocol
*
*/
FILE_LICENCE ( GPL2_OR_LATER );
#include <stdarg.h>
#include <time.h>
#include <ipxe/asn1.h>
#include <ipxe/x509.h>
#include <ipxe/refcnt.h>
/** OCSP algorithm identifier */
#define OCSP_ALGORITHM_IDENTIFIER( ... ) \
ASN1_OID, VA_ARG_COUNT ( __VA_ARGS__ ), __VA_ARGS__, \
ASN1_NULL, 0x00
/* OCSP response statuses */
#define OCSP_STATUS_SUCCESSFUL 0x00
#define OCSP_STATUS_MALFORMED_REQUEST 0x01
#define OCSP_STATUS_INTERNAL_ERROR 0x02
#define OCSP_STATUS_TRY_LATER 0x03
#define OCSP_STATUS_SIG_REQUIRED 0x05
#define OCSP_STATUS_UNAUTHORIZED 0x06
/** Margin of error allowed in OCSP response times
*
* We allow a generous margin of error: 12 hours to allow for the
* local time zone being non-GMT, plus 30 minutes to allow for general
* clock drift.
*/
#define OCSP_ERROR_MARGIN_TIME ( ( 12 * 60 + 30 ) * 60 )
/** An OCSP request */
struct ocsp_request {
/** Request builder */
struct asn1_builder builder;
/** Certificate ID */
struct asn1_cursor cert_id;
};
/** An OCSP response */
struct ocsp_response {
/** Raw response */
void *data;
/** Raw tbsResponseData */
struct asn1_cursor tbs;
/** Time at which status is known to be correct */
time_t this_update;
/** Time at which newer status information will be available */
time_t next_update;
/** Signature algorithm */
struct asn1_algorithm *algorithm;
/** Signature value */
struct asn1_bit_string signature;
/** Signing certificate */
struct x509_certificate *signer;
};
/** An OCSP check */
struct ocsp_check {
/** Reference count */
struct refcnt refcnt;
/** Certificate being checked */
struct x509_certificate *cert;
/** Issuing certificate */
struct x509_certificate *issuer;
/** Request */
struct ocsp_request request;
/** Response */
struct ocsp_response response;
};
/**
* Get reference to OCSP check
*
* @v ocsp OCSP check
* @ret ocsp OCSP check
*/
static inline __attribute__ (( always_inline )) struct ocsp_check *
ocsp_get ( struct ocsp_check *ocsp ) {
ref_get ( &ocsp->refcnt );
return ocsp;
}
/**
* Drop reference to OCSP check
*
* @v ocsp OCSP check
*/
static inline __attribute__ (( always_inline )) void
ocsp_put ( struct ocsp_check *ocsp ) {
ref_put ( &ocsp->refcnt );
}
extern int ocsp_check ( struct x509_certificate *cert,
struct x509_certificate *issuer,
struct ocsp_check **ocsp );
extern int ocsp_response ( struct ocsp_check *ocsp, const void *data,
size_t len );
extern int ocsp_validate ( struct ocsp_check *check, time_t time );
#endif /* _IPXE_OCSP_H */

View File

@ -126,6 +126,8 @@ enum x509_extended_key_usage_bits {
struct x509_ocsp_responder {
/** URI */
char *uri;
/** OCSP status is good */
int good;
};
/** X.509 certificate authority information access */
@ -322,6 +324,9 @@ struct x509_root {
extern int x509_certificate ( const void *data, size_t len,
struct x509_certificate **cert );
extern int x509_validate ( struct x509_certificate *cert,
struct x509_certificate *issuer,
time_t time, struct x509_root *root );
extern struct x509_chain * x509_alloc_chain ( void );
extern int x509_append ( struct x509_chain *chain,