parsing basic ocsp response
This commit is contained in:
+269
-26
@@ -23,6 +23,7 @@
|
|||||||
|
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
#include <stdio.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <time.h>
|
#include <time.h>
|
||||||
#include <sys/types.h>
|
#include <sys/types.h>
|
||||||
@@ -85,32 +86,32 @@ struct private_ocsp_t {
|
|||||||
chunk_t authNameID;
|
chunk_t authNameID;
|
||||||
};
|
};
|
||||||
|
|
||||||
static const char *const response_status_names[] = {
|
ENUM(response_status_names, STATUS_SUCCESSFUL, STATUS_UNAUTHORIZED,
|
||||||
"successful",
|
"successful",
|
||||||
"malformed request",
|
"malformed request",
|
||||||
"internal error",
|
"internal error",
|
||||||
"try later",
|
"try later",
|
||||||
"signature required",
|
"signature required",
|
||||||
"unauthorized"
|
"unauthorized"
|
||||||
};
|
);
|
||||||
|
|
||||||
/* response container */
|
/* response container */
|
||||||
typedef struct response_t response_t;
|
typedef struct response_t response_t;
|
||||||
|
|
||||||
struct response_t {
|
struct response_t {
|
||||||
chunk_t tbs;
|
chunk_t tbs;
|
||||||
chunk_t responder_id_name;
|
identification_t *responder_id_name;
|
||||||
chunk_t responder_id_key;
|
chunk_t responder_id_key;
|
||||||
time_t produced_at;
|
time_t produced_at;
|
||||||
chunk_t responses;
|
chunk_t responses;
|
||||||
chunk_t nonce;
|
chunk_t nonce;
|
||||||
int algorithm;
|
int algorithm;
|
||||||
chunk_t signature;
|
chunk_t signature;
|
||||||
};
|
};
|
||||||
|
|
||||||
const response_t empty_response = {
|
const response_t empty_response = {
|
||||||
{ NULL, 0 } , /* tbs */
|
{ NULL, 0 } , /* tbs */
|
||||||
{ NULL, 0 } , /* responder_id_name */
|
NULL , /* responder_id_name */
|
||||||
{ NULL, 0 } , /* responder_id_key */
|
{ NULL, 0 } , /* responder_id_key */
|
||||||
UNDEFINED_TIME, /* produced_at */
|
UNDEFINED_TIME, /* produced_at */
|
||||||
{ NULL, 0 } , /* single_response */
|
{ NULL, 0 } , /* single_response */
|
||||||
@@ -218,7 +219,7 @@ static const asn1Object_t basicResponseObjects[] = {
|
|||||||
{ 1, "signature", ASN1_BIT_STRING, ASN1_BODY }, /* 21 */
|
{ 1, "signature", ASN1_BIT_STRING, ASN1_BODY }, /* 21 */
|
||||||
{ 1, "certsContext", ASN1_CONTEXT_C_0, ASN1_OPT }, /* 22 */
|
{ 1, "certsContext", ASN1_CONTEXT_C_0, ASN1_OPT }, /* 22 */
|
||||||
{ 2, "certs", ASN1_SEQUENCE, ASN1_LOOP }, /* 23 */
|
{ 2, "certs", ASN1_SEQUENCE, ASN1_LOOP }, /* 23 */
|
||||||
{ 3, "certificate", ASN1_SEQUENCE, ASN1_OBJ }, /* 24 */
|
{ 3, "certificate", ASN1_SEQUENCE, ASN1_RAW }, /* 24 */
|
||||||
{ 2, "end loop", ASN1_EOC, ASN1_END }, /* 25 */
|
{ 2, "end loop", ASN1_EOC, ASN1_END }, /* 25 */
|
||||||
{ 1, "end opt", ASN1_EOC, ASN1_END } /* 26 */
|
{ 1, "end opt", ASN1_EOC, ASN1_END } /* 26 */
|
||||||
};
|
};
|
||||||
@@ -416,6 +417,7 @@ static chunk_t build_tbs_request(private_ocsp_t *this, bool has_requestor_cert)
|
|||||||
*/
|
*/
|
||||||
static chunk_t build_signature(private_ocsp_t *this, chunk_t tbsRequest)
|
static chunk_t build_signature(private_ocsp_t *this, chunk_t tbsRequest)
|
||||||
{
|
{
|
||||||
|
/* TODO */
|
||||||
return chunk_empty;
|
return chunk_empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -435,28 +437,267 @@ static chunk_t ocsp_build_request(private_ocsp_t *this)
|
|||||||
/* looks for requestor cert and matching private key */
|
/* looks for requestor cert and matching private key */
|
||||||
has_requestor_cert = FALSE;
|
has_requestor_cert = FALSE;
|
||||||
|
|
||||||
/* has_requestor_cert = get_ocsp_requestor_cert(location); */
|
/* TODO has_requestor_cert = get_ocsp_requestor_cert(location); */
|
||||||
|
|
||||||
/* build content */
|
/* build content */
|
||||||
tbsRequest = build_tbs_request(this, has_requestor_cert);
|
tbsRequest = build_tbs_request(this, has_requestor_cert);
|
||||||
|
|
||||||
/* sign tbsReuqest */
|
/* sign tbsReuqest */
|
||||||
signature = (has_requestor_cert)? build_signature(this, tbsRequest): chunk_empty;
|
signature = (has_requestor_cert)? build_signature(this, tbsRequest): chunk_empty;
|
||||||
|
|
||||||
return asn1_wrap(ASN1_SEQUENCE, "mm",
|
return asn1_wrap(ASN1_SEQUENCE, "mm",
|
||||||
tbsRequest,
|
tbsRequest,
|
||||||
signature);
|
signature);
|
||||||
|
|
||||||
return signature;
|
return signature;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the OCSP response has a valid signature
|
||||||
|
*/
|
||||||
|
static bool ocsp_valid_response(response_t *res)
|
||||||
|
{
|
||||||
|
/* TODO */
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* parse a basic OCSP response
|
||||||
|
*/
|
||||||
|
static bool ocsp_parse_basic_response(chunk_t blob, int level0, response_t *res)
|
||||||
|
{
|
||||||
|
u_int level, version;
|
||||||
|
u_int extn_oid = OID_UNKNOWN;
|
||||||
|
u_char buf[BUF_LEN];
|
||||||
|
asn1_ctx_t ctx;
|
||||||
|
bool critical;
|
||||||
|
chunk_t object;
|
||||||
|
int objectID = 0;
|
||||||
|
|
||||||
|
asn1_init(&ctx, blob, level0, FALSE, FALSE);
|
||||||
|
|
||||||
|
while (objectID < BASIC_RESPONSE_ROOF)
|
||||||
|
{
|
||||||
|
if (!extract_object(basicResponseObjects, &objectID, &object, &level, &ctx))
|
||||||
|
{
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (objectID)
|
||||||
|
{
|
||||||
|
case BASIC_RESPONSE_TBS_DATA:
|
||||||
|
res->tbs = object;
|
||||||
|
break;
|
||||||
|
case BASIC_RESPONSE_VERSION:
|
||||||
|
version = (object.len)? (1 + (u_int)*object.ptr) : 1;
|
||||||
|
if (version != OCSP_BASIC_RESPONSE_VERSION)
|
||||||
|
{
|
||||||
|
DBG1("wrong ocsp basic response version (version= %i)", version);
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case BASIC_RESPONSE_ID_BY_NAME:
|
||||||
|
res->responder_id_name = identification_create_from_encoding(ID_DER_ASN1_DN, object);
|
||||||
|
DBG2(" '%D'", res->responder_id_name);
|
||||||
|
break;
|
||||||
|
case BASIC_RESPONSE_ID_BY_KEY:
|
||||||
|
res->responder_id_key = object;
|
||||||
|
break;
|
||||||
|
case BASIC_RESPONSE_PRODUCED_AT:
|
||||||
|
res->produced_at = asn1totime(&object, ASN1_GENERALIZEDTIME);
|
||||||
|
break;
|
||||||
|
case BASIC_RESPONSE_RESPONSES:
|
||||||
|
res->responses = object;
|
||||||
|
break;
|
||||||
|
case BASIC_RESPONSE_EXT_ID:
|
||||||
|
extn_oid = known_oid(object);
|
||||||
|
break;
|
||||||
|
case BASIC_RESPONSE_CRITICAL:
|
||||||
|
critical = object.len && *object.ptr;
|
||||||
|
DBG2(" %s", critical? "TRUE" : "FALSE");
|
||||||
|
break;
|
||||||
|
case BASIC_RESPONSE_EXT_VALUE:
|
||||||
|
if (extn_oid == OID_NONCE)
|
||||||
|
res->nonce = object;
|
||||||
|
break;
|
||||||
|
case BASIC_RESPONSE_ALGORITHM:
|
||||||
|
res->algorithm = parse_algorithmIdentifier(object, level+1, NULL);
|
||||||
|
break;
|
||||||
|
case BASIC_RESPONSE_SIGNATURE:
|
||||||
|
res->signature = object;
|
||||||
|
break;
|
||||||
|
case BASIC_RESPONSE_CERTIFICATE:
|
||||||
|
{
|
||||||
|
chunk_t blob = chunk_clone(object);
|
||||||
|
x509_t *cert = x509_create_from_chunk(blob, level+1);
|
||||||
|
|
||||||
|
if (cert == NULL)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (cert->is_ocsp_signer(cert))
|
||||||
|
{
|
||||||
|
DBG2("received OCSP signer certificate");
|
||||||
|
cert->destroy(cert);
|
||||||
|
/* TODO trust_authcert_candidate(cert, NULL))
|
||||||
|
add_authcert(cert, AUTH_OCSP); */
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
DBG1("embedded ocsp certificate rejected");
|
||||||
|
cert->destroy(cert);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
objectID++;
|
||||||
|
}
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* parse an ocsp response and return the result as a response_t struct
|
||||||
|
*/
|
||||||
|
static response_status ocsp_parse_response(chunk_t blob, response_t * res)
|
||||||
|
{
|
||||||
|
asn1_ctx_t ctx;
|
||||||
|
chunk_t object;
|
||||||
|
u_int level;
|
||||||
|
int objectID = 0;
|
||||||
|
|
||||||
|
response_status rStatus = STATUS_INTERNALERROR;
|
||||||
|
u_int ocspResponseType = OID_UNKNOWN;
|
||||||
|
|
||||||
|
asn1_init(&ctx, blob, 0, FALSE, FALSE);
|
||||||
|
|
||||||
|
while (objectID < OCSP_RESPONSE_ROOF)
|
||||||
|
{
|
||||||
|
if (!extract_object(ocspResponseObjects, &objectID, &object, &level, &ctx))
|
||||||
|
{
|
||||||
|
return STATUS_INTERNALERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (objectID)
|
||||||
|
{
|
||||||
|
case OCSP_RESPONSE_STATUS:
|
||||||
|
rStatus = (response_status) *object.ptr;
|
||||||
|
DBG2(" '%N'", response_status_names, rStatus);
|
||||||
|
|
||||||
|
switch (rStatus)
|
||||||
|
{
|
||||||
|
case STATUS_SUCCESSFUL:
|
||||||
|
break;
|
||||||
|
case STATUS_MALFORMEDREQUEST:
|
||||||
|
case STATUS_INTERNALERROR:
|
||||||
|
case STATUS_TRYLATER:
|
||||||
|
case STATUS_SIGREQUIRED:
|
||||||
|
case STATUS_UNAUTHORIZED:
|
||||||
|
DBG1("unsuccessful ocsp response: server said '%N'",
|
||||||
|
response_status_names, rStatus);
|
||||||
|
return rStatus;
|
||||||
|
default:
|
||||||
|
return STATUS_INTERNALERROR;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case OCSP_RESPONSE_TYPE:
|
||||||
|
ocspResponseType = known_oid(object);
|
||||||
|
break;
|
||||||
|
case OCSP_RESPONSE:
|
||||||
|
{
|
||||||
|
switch (ocspResponseType)
|
||||||
|
{
|
||||||
|
case OID_BASIC:
|
||||||
|
if (!ocsp_parse_basic_response(object, level+1, res))
|
||||||
|
{
|
||||||
|
return STATUS_INTERNALERROR;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
DBG1("ocsp response is not of type BASIC");
|
||||||
|
DBG1("ocsp response OID: %#B", &object);
|
||||||
|
return STATUS_INTERNALERROR;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
objectID++;
|
||||||
|
}
|
||||||
|
return rStatus;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* verify and process ocsp response and update the ocsp cache
|
||||||
|
*/
|
||||||
|
void ocsp_process_response(private_ocsp_t *this, chunk_t reply)
|
||||||
|
{
|
||||||
|
response_t res = empty_response;
|
||||||
|
|
||||||
|
/* parse the ocsp response without looking at the single responses yet */
|
||||||
|
response_status status = ocsp_parse_response(reply, &res);
|
||||||
|
|
||||||
|
if (status != STATUS_SUCCESSFUL)
|
||||||
|
{
|
||||||
|
DBG1("error in ocsp response");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* check if there was a nonce in the request */
|
||||||
|
if (this->nonce.ptr != NULL && res.nonce.ptr == NULL)
|
||||||
|
{
|
||||||
|
DBG1("ocsp response contains no nonce, replay attack possible");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* check if the nonces are identical */
|
||||||
|
if (res.nonce.ptr != NULL && !chunk_equals(res.nonce, this->nonce))
|
||||||
|
{
|
||||||
|
DBG1("invalid nonce in ocsp response");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* check if the response is signed by a trusted key */
|
||||||
|
if (!ocsp_valid_response(&res))
|
||||||
|
{
|
||||||
|
DBG1("invalid ocsp response");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
DBG2("valid ocsp response");
|
||||||
|
|
||||||
|
/* now parse the single responses one at a time */
|
||||||
|
{
|
||||||
|
u_int level;
|
||||||
|
asn1_ctx_t ctx;
|
||||||
|
chunk_t object;
|
||||||
|
int objectID = 0;
|
||||||
|
|
||||||
|
asn1_init(&ctx, res.responses, 0, FALSE, FALSE);
|
||||||
|
|
||||||
|
while (objectID < RESPONSES_ROOF)
|
||||||
|
{
|
||||||
|
if (!extract_object(responsesObjects, &objectID, &object, &level, &ctx))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (objectID == RESPONSES_SINGLE_RESPONSE)
|
||||||
|
{
|
||||||
|
single_response_t sres = empty_single_response;
|
||||||
|
|
||||||
|
if (parse_ocsp_single_response(object, level+1, &sres))
|
||||||
|
{
|
||||||
|
process_single_response(this, &sres);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
objectID++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Implements ocsp_t.fetch.
|
* Implements ocsp_t.fetch.
|
||||||
*/
|
*/
|
||||||
static void fetch(private_ocsp_t *this, certinfo_t *certinfo)
|
static void fetch(private_ocsp_t *this, certinfo_t *certinfo)
|
||||||
{
|
{
|
||||||
chunk_t request;
|
chunk_t request;
|
||||||
chunk_t reply;
|
chunk_t response;
|
||||||
bool fetched = FALSE;
|
bool fetched = FALSE;
|
||||||
|
|
||||||
if (this->uris->get_count(this->uris) == 0)
|
if (this->uris->get_count(this->uris) == 0)
|
||||||
@@ -480,9 +721,9 @@ static void fetch(private_ocsp_t *this, certinfo_t *certinfo)
|
|||||||
snprintf(uri_string, BUF_LEN, "%.*s", uri_chunk.len, uri_chunk.ptr);
|
snprintf(uri_string, BUF_LEN, "%.*s", uri_chunk.len, uri_chunk.ptr);
|
||||||
fetcher = fetcher_create(uri_string);
|
fetcher = fetcher_create(uri_string);
|
||||||
|
|
||||||
reply = fetcher->post(fetcher, "application/ocsp-request", request);
|
response = fetcher->post(fetcher, "application/ocsp-request", request);
|
||||||
fetcher->destroy(fetcher);
|
fetcher->destroy(fetcher);
|
||||||
if (reply.ptr != NULL)
|
if (response.ptr != NULL)
|
||||||
{
|
{
|
||||||
fetched = TRUE;
|
fetched = TRUE;
|
||||||
break;
|
break;
|
||||||
@@ -496,7 +737,9 @@ static void fetch(private_ocsp_t *this, certinfo_t *certinfo)
|
|||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
DBG3("ocsp reply: %B", &reply);
|
DBG3("ocsp response: %B", &response);
|
||||||
|
ocsp_process_response(this, response);
|
||||||
|
free(response.ptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user