/*
* Copyright (C) 2017-2019 Tobias Brunner
* Copyright (C) 2008-2009 Martin Willi
- * Copyright (C) 2007-2022 Andreas Steffen
+ * Copyright (C) 2007-2023 Andreas Steffen, strongSec GmbH
* Copyright (C) 2003 Christoph Gysin, Simon Zwahlen
*
* Copyright (C) secunet Security Networks AG
#include <library.h>
#include <asn1/oid.h>
#include <asn1/asn1.h>
+#include <asn1/asn1_parser.h>
#include <utils/identification.h>
#include <collections/linked_list.h>
#include <utils/debug.h>
#include <credentials/certificates/x509.h>
#include <credentials/keys/private_key.h>
-#define NONCE_LEN 16
+/* RFC 8954 OCSP Nonce Extension */
+#define NONCE_LEN 32
typedef struct private_x509_ocsp_request_t private_x509_ocsp_request_t;
x509_ocsp_request_t public;
/**
- * CA the candidates belong to
+ * CA the certificates where issued by
*/
- x509_t *ca;
+ certificate_t *cacert;
/**
* Requestor name, subject of cert used if not set
private_key_t *key;
/**
- * list of certificates to check, x509_t
+ * list of X.509 certificates to check
*/
- linked_list_t *candidates;
+ linked_list_t *reqCerts;
/**
* nonce used in request
*/
chunk_t encoding;
+ /**
+ * data for signature verification
+ */
+ chunk_t tbsRequest;
+
+ /**
+ * signature scheme
+ */
+ signature_params_t *scheme;
+
+ /**
+ * signature
+ */
+ chunk_t signature;
+
/**
* reference count
*/
refcount_t ref;
};
+/**
+ * Single reqCert object sent in OCSP request
+ */
+typedef struct {
+ /** hash algorithm for the two hashes */
+ hash_algorithm_t hashAlgorithm;
+ /** hash of issuer DN */
+ chunk_t issuerNameHash;
+ /** issuerKeyID */
+ chunk_t issuerKeyHash;
+ /** serial number of certificate */
+ chunk_t serialNumber;
+} req_cert_t;
+
+/**
+ * Clean up a reqCert object
+ */
+CALLBACK(req_cert_destroy, void,
+ req_cert_t *reqCert)
+{
+ chunk_free(&reqCert->issuerNameHash);
+ chunk_free(&reqCert->issuerKeyHash);
+ chunk_free(&reqCert->serialNumber);
+ free(reqCert);
+}
+
static const chunk_t ASN1_nonce_oid = chunk_from_chars(
0x06, 0x09,
0x2B, 0x06,
* build Request, not using singleRequestExtensions
*/
static chunk_t build_Request(private_x509_ocsp_request_t *this,
- chunk_t issuerNameHash, chunk_t issuerKeyHash,
- chunk_t serialNumber)
+ req_cert_t *reqCert)
{
return asn1_wrap(ASN1_SEQUENCE, "m",
- asn1_wrap(ASN1_SEQUENCE, "mmmm",
- asn1_algorithmIdentifier(OID_SHA1),
- asn1_simple_object(ASN1_OCTET_STRING, issuerNameHash),
- asn1_simple_object(ASN1_OCTET_STRING, issuerKeyHash),
- asn1_integer("c", serialNumber)));
+ asn1_wrap(ASN1_SEQUENCE, "mmmm",
+ asn1_algorithmIdentifier(
+ hasher_algorithm_to_oid(reqCert->hashAlgorithm)),
+ asn1_simple_object(ASN1_OCTET_STRING, reqCert->issuerNameHash),
+ asn1_simple_object(ASN1_OCTET_STRING, reqCert->issuerKeyHash),
+ asn1_integer("c", reqCert->serialNumber)));
}
/**
*/
static chunk_t build_requestList(private_x509_ocsp_request_t *this)
{
- chunk_t issuerNameHash, issuerKeyHash;
- identification_t *issuer;
- x509_t *x509;
- certificate_t *cert;
- chunk_t list = chunk_empty;
- public_key_t *public;
-
- cert = (certificate_t*)this->ca;
- public = cert->get_public_key(cert);
- if (public)
- {
- hasher_t *hasher = lib->crypto->create_hasher(lib->crypto, HASH_SHA1);
- if (hasher)
- {
- if (public->get_fingerprint(public, KEYID_PUBKEY_SHA1,
- &issuerKeyHash))
- {
- enumerator_t *enumerator;
+ chunk_t list = chunk_empty, request;
+ enumerator_t *enumerator;
+ req_cert_t *reqCert;
- issuer = cert->get_subject(cert);
- if (hasher->allocate_hash(hasher, issuer->get_encoding(issuer),
- &issuerNameHash))
- {
- enumerator = this->candidates->create_enumerator(
- this->candidates);
- while (enumerator->enumerate(enumerator, &x509))
- {
- chunk_t request, serialNumber;
-
- serialNumber = x509->get_serial(x509);
- request = build_Request(this, issuerNameHash,
- issuerKeyHash, serialNumber);
- list = chunk_cat("mm", list, request);
- }
- enumerator->destroy(enumerator);
- chunk_free(&issuerNameHash);
- }
- hasher->destroy(hasher);
- }
- }
- else
- {
- DBG1(DBG_LIB, "creating OCSP request failed, SHA1 not supported");
- }
- public->destroy(public);
- }
- else
+ enumerator = this->reqCerts->create_enumerator(this->reqCerts);
+ while (enumerator->enumerate(enumerator, &reqCert))
{
- DBG1(DBG_LIB, "creating OCSP request failed, CA certificate has "
- "no public key");
+ request = build_Request(this, reqCert);
+ list = chunk_cat("mm", list, request);
}
+ enumerator->destroy(enumerator);
+
return asn1_wrap(ASN1_SEQUENCE, "m", list);
}
rng = lib->crypto->create_rng(lib->crypto, RNG_WEAK);
if (!rng || !rng->allocate_bytes(rng, NONCE_LEN, &this->nonce))
{
- DBG1(DBG_LIB, "creating OCSP request nonce failed, no RNG found");
+ DBG1(DBG_LIB, "failed to create RNG");
DESTROY_IF(rng);
return chunk_empty;
}
}
/**
- * build tbsRequest
+ * Build tbsRequest
*/
static chunk_t build_tbsRequest(private_x509_ocsp_request_t *this)
{
/**
* Build the OCSPRequest data
- *
*/
static chunk_t build_OCSPRequest(private_x509_ocsp_request_t *this)
{
return asn1_wrap(ASN1_SEQUENCE, "mm", tbsRequest, optionalSignature);
}
+/**
+ * ASN.1 definition of ocspRequest
+ */
+static const asn1Object_t ocspRequestObjects[] = {
+ { 0, "OCSPRequest", ASN1_SEQUENCE, ASN1_NONE }, /* 0 */
+ { 1, "tbsRequest", ASN1_SEQUENCE, ASN1_OBJ }, /* 1 */
+ { 2, "versionContext", ASN1_CONTEXT_C_0, ASN1_NONE |
+ ASN1_DEF }, /* 2 */
+ { 3, "version", ASN1_INTEGER, ASN1_BODY }, /* 3 */
+ { 2, "requestorNameContext", ASN1_CONTEXT_C_1, ASN1_OPT }, /* 4 */
+ { 3, "requestorName", ASN1_CONTEXT_C_4, ASN1_BODY }, /* 5 */
+ { 2, "end opt", ASN1_EOC, ASN1_END }, /* 6 */
+ { 2, "requestList", ASN1_SEQUENCE, ASN1_LOOP }, /* 7 */
+ { 3, "request", ASN1_SEQUENCE, ASN1_BODY }, /* 8 */
+ { 4, "reqCert", ASN1_SEQUENCE, ASN1_NONE }, /* 9 */
+ { 5, "hashAlgorithm", ASN1_EOC, ASN1_RAW }, /* 10 */
+ { 5, "issuerNameHash", ASN1_OCTET_STRING, ASN1_BODY }, /* 11 */
+ { 5, "issuerKeyHash", ASN1_OCTET_STRING, ASN1_BODY }, /* 12 */
+ { 5, "serialNumber", ASN1_INTEGER, ASN1_BODY }, /* 13 */
+ { 4, "singleRequestExtensions", ASN1_CONTEXT_C_0, ASN1_OPT }, /* 14 */
+ { 4, "end opt", ASN1_EOC, ASN1_END }, /* 15 */
+ { 2, "end loop", ASN1_EOC, ASN1_END }, /* 16 */
+ { 2, "requestExtensions", ASN1_CONTEXT_C_2, ASN1_OPT }, /* 17 */
+ { 3, "Extensions", ASN1_SEQUENCE, ASN1_LOOP }, /* 18 */
+ { 4, "Extension", ASN1_SEQUENCE, ASN1_NONE }, /* 19 */
+ { 5, "extnID", ASN1_OID, ASN1_BODY }, /* 20 */
+ { 5, "critical", ASN1_BOOLEAN, ASN1_BODY |
+ ASN1_DEF }, /* 21 */
+ { 5, "extnValue", ASN1_OCTET_STRING, ASN1_BODY }, /* 22 */
+ { 3, "end loop", ASN1_EOC, ASN1_END }, /* 23 */
+ { 2, "end opt", ASN1_EOC, ASN1_END }, /* 24 */
+ { 1, "optionalSignature", ASN1_CONTEXT_C_0, ASN1_OPT }, /* 25 */
+ { 2, "signature", ASN1_SEQUENCE, ASN1_NONE }, /* 26 */
+ { 3, "signatureAlgorithm", ASN1_EOC, ASN1_RAW }, /* 27 */
+ { 3, "signature", ASN1_BIT_STRING, ASN1_BODY }, /* 28 */
+ { 3, "certsContext", ASN1_CONTEXT_C_0, ASN1_OPT }, /* 29 */
+ { 4, "certs", ASN1_SEQUENCE, ASN1_LOOP }, /* 30 */
+ { 5, "certificate", ASN1_EOC, ASN1_RAW }, /* 31 */
+ { 4, "end loop", ASN1_EOC, ASN1_END }, /* 32 */
+ { 3, "end opt", ASN1_EOC, ASN1_END }, /* 33 */
+ { 1, "end opt", ASN1_EOC, ASN1_END }, /* 34 */
+ { 0, "exit", ASN1_EOC, ASN1_EXIT }
+};
+
+#define OCSP_REQ_TBS_REQUEST 1
+#define OCSP_REQ_REQUESTOR 5
+#define OCSP_REQ_REQ_CERT 9
+#define OCSP_REQ_HASH_ALG 10
+#define OCSP_REQ_ISSUER_NAME_HASH 11
+#define OCSP_REQ_ISSUER_KEY_HASH 12
+#define OCSP_REQ_SERIAL_NUMBER 13
+#define OCSP_REQ_EXTN_ID 20
+#define OCSP_REQ_CRITICAL 21
+#define OCSP_REQ_EXTN_VALUE 22
+#define OCSP_REQ_SIG_ALG 27
+#define OCSP_REQ_SIGNATURE 28
+#define OCSP_REQ_CERTIFICATE 31
+
+/**
+ * Parse the OCSPRequest data
+ *
+ */
+static bool parse_OCSPRequest(private_x509_ocsp_request_t *this)
+{
+ asn1_parser_t *parser;
+ req_cert_t *reqCert = NULL;
+ chunk_t object;
+ int extn_oid = OID_UNKNOWN;
+ int objectID;
+ bool critical = FALSE, success = FALSE;
+
+ parser = asn1_parser_create(ocspRequestObjects, this->encoding);
+
+ while (parser->iterate(parser, &objectID, &object))
+ {
+ u_int level = parser->get_level(parser)+1;
+
+ switch (objectID)
+ {
+
+ case OCSP_REQ_TBS_REQUEST:
+ this->tbsRequest = object;
+ break;
+ case OCSP_REQ_REQUESTOR:
+ this->requestor = identification_create_from_encoding(ID_DER_ASN1_DN, object);
+ break;
+ case OCSP_REQ_REQ_CERT:
+ INIT(reqCert);
+ this->reqCerts->insert_last(this->reqCerts, reqCert);
+ break;
+ case OCSP_REQ_HASH_ALG:
+ reqCert->hashAlgorithm = hasher_algorithm_from_oid(
+ asn1_parse_algorithmIdentifier(object, level, NULL));
+ if (reqCert->hashAlgorithm == HASH_UNKNOWN)
+ {
+ DBG1(DBG_ASN, "unknowm hash algorithm");
+ goto end;
+ }
+ break;
+ case OCSP_REQ_ISSUER_NAME_HASH:
+ reqCert->issuerNameHash = chunk_clone(object);
+ break;
+ case OCSP_REQ_ISSUER_KEY_HASH:
+ reqCert->issuerKeyHash = chunk_clone(object);
+ break;
+ case OCSP_REQ_SERIAL_NUMBER:
+ reqCert->serialNumber = chunk_clone(chunk_skip_zero(object));
+ break;
+ case OCSP_REQ_EXTN_ID:
+ extn_oid = asn1_known_oid(object);
+ break;
+ case OCSP_REQ_CRITICAL:
+ critical = object.len && *object.ptr;
+ DBG2(DBG_ASN, " %s", critical ? "TRUE" : "FALSE");
+ break;
+ case OCSP_REQ_EXTN_VALUE:
+ {
+ switch (extn_oid)
+ {
+ case OID_NONCE:
+ if (!asn1_parse_simple_object(&object, ASN1_OCTET_STRING,
+ level, "nonce"))
+ {
+ goto end;
+ }
+ this->nonce = chunk_clone(object);
+ break;
+ case OID_RESPONSE:
+ if (!asn1_parse_simple_object(&object, ASN1_SEQUENCE,
+ level, "acceptableResponses"))
+ {
+ goto end;
+ }
+ break;
+ default:
+ if (critical && lib->settings->get_bool(lib->settings,
+ "%s.x509.enforce_critical", TRUE, lib->ns))
+ {
+ DBG1(DBG_ASN, "critical '%s' extension not supported",
+ (extn_oid == OID_UNKNOWN) ? "unknown" :
+ (char*)oid_names[extn_oid].name);
+ goto end;
+ }
+ break;
+ }
+ break;
+ }
+ case OCSP_REQ_SIG_ALG:
+ INIT(this->scheme);
+ if (!signature_params_parse(object, level, this->scheme))
+ {
+ DBG1(DBG_ASN, " unable to parse signature algorithm");
+ goto end;
+ }
+
+ break;
+ case OCSP_REQ_SIGNATURE:
+ this->signature = chunk_skip(object, 1);
+ break;
+ case OCSP_REQ_CERTIFICATE:
+ if (this->cert)
+ {
+ DBG1(DBG_LIB, " skipping additional signing certificate");
+ break;
+ }
+ this->cert = lib->creds->create(lib->creds,
+ CRED_CERTIFICATE,CERT_X509,
+ BUILD_BLOB_ASN1_DER, object, BUILD_END);
+ if (!this->cert)
+ {
+ goto end;
+ }
+ break;
+ }
+ }
+ success = parser->success(parser);
+
+end:
+ parser->destroy(parser);
+ return success;
+}
METHOD(certificate_t, get_type, certificate_type_t,
private_x509_ocsp_request_t *this)
METHOD(certificate_t, get_subject, identification_t*,
private_x509_ocsp_request_t *this)
{
- certificate_t *ca = (certificate_t*)this->ca;
-
if (this->requestor)
{
return this->requestor;
}
- if (this->cert)
- {
- return this->cert->get_subject(this->cert);
- }
- return ca->get_subject(ca);
+ return (this->cert) ? this->cert->get_subject(this->cert) : NULL;
}
METHOD(certificate_t, get_issuer, identification_t*,
private_x509_ocsp_request_t *this)
{
- certificate_t *ca = (certificate_t*)this->ca;
-
- return ca->get_subject(ca);
+ return this->cacert ? this->cacert->get_subject(this->cacert) : NULL;
}
METHOD(certificate_t, has_subject, id_match_t,
private_x509_ocsp_request_t *this, identification_t *subject)
{
- certificate_t *current;
- enumerator_t *enumerator;
- id_match_t match, best = ID_MATCH_NONE;
-
- enumerator = this->candidates->create_enumerator(this->candidates);
- while (enumerator->enumerate(enumerator, ¤t))
- {
- match = current->has_subject(current, subject);
- if (match > best)
- {
- best = match;
- }
- }
- enumerator->destroy(enumerator);
- return best;
+ return ID_MATCH_NONE;
}
METHOD(certificate_t, has_issuer, id_match_t,
private_x509_ocsp_request_t *this,
identification_t *issuer)
{
- certificate_t *ca = (certificate_t*)this->ca;
-
- return ca->has_subject(ca, issuer);
+ return this->cacert ? this->cacert->has_subject(this->cacert, issuer) :
+ ID_MATCH_NONE;
}
METHOD(certificate_t, issued_by, bool,
private_x509_ocsp_request_t *this, certificate_t *issuer,
signature_params_t **scheme)
{
- DBG1(DBG_LIB, "OCSP request validation not implemented!");
- return FALSE;
+ public_key_t *key;
+ bool valid;
+
+ if (issuer->get_type(issuer) != CERT_X509 || this->cert == NULL ||
+ !issuer->equals(issuer, this->cert))
+ {
+ return FALSE;
+ }
+
+ key = issuer->get_public_key(issuer);
+ if (!key)
+ {
+ return FALSE;
+ }
+ valid = key->verify(key, this->scheme->scheme, this->scheme->params,
+ this->tbsRequest, this->signature);
+ key->destroy(key);
+
+ if (valid && scheme)
+ {
+ *scheme = signature_params_clone(this->scheme);
+ }
+ return valid;
}
METHOD(certificate_t, get_public_key, public_key_t*,
private_x509_ocsp_request_t *this, time_t *when, time_t *not_before,
time_t *not_after)
{
- certificate_t *cert;
-
- if (this->cert)
- {
- cert = this->cert;
- }
- else
- {
- cert = (certificate_t*)this->ca;
- }
- return cert->get_validity(cert, when, not_before, not_after);
+ return FALSE;
}
METHOD(certificate_t, get_encoding, bool,
{
if (ref_put(&this->ref))
{
- DESTROY_IF((certificate_t*)this->ca);
+ DESTROY_IF(this->cacert);
DESTROY_IF(this->requestor);
DESTROY_IF(this->cert);
DESTROY_IF(this->key);
- this->candidates->destroy_offset(this->candidates, offsetof(certificate_t, destroy));
+ signature_params_destroy(this->scheme);
+ this->reqCerts->destroy_function(this->reqCerts, req_cert_destroy);
chunk_free(&this->nonce);
chunk_free(&this->encoding);
free(this);
return this->nonce;
}
+METHOD(ocsp_request_t, get_signer_cert, certificate_t*,
+ private_x509_ocsp_request_t *this)
+{
+ return this->cert;
+}
+
+CALLBACK(filter, bool,
+ void *data, enumerator_t *orig, va_list args)
+{
+ req_cert_t *reqCert;
+ hash_algorithm_t *hashAlgorithm;
+ chunk_t *issuerNameHash, *issuerKeyHash, *serialNumber;
+
+ VA_ARGS_VGET(args, hashAlgorithm, issuerNameHash, issuerKeyHash, serialNumber);
+
+ if (orig->enumerate(orig, &reqCert))
+ {
+ if (hashAlgorithm)
+ {
+ *hashAlgorithm = reqCert->hashAlgorithm;
+ }
+ if (issuerNameHash)
+ {
+ *issuerNameHash = reqCert->issuerNameHash;
+ }
+ if (issuerKeyHash)
+ {
+ *issuerKeyHash = reqCert->issuerKeyHash;
+ }
+ if (serialNumber)
+ {
+ *serialNumber = reqCert->serialNumber;
+ }
+ return TRUE;
+ }
+ return FALSE;
+}
+
+METHOD(ocsp_request_t, create_request_enumerator, enumerator_t*,
+ private_x509_ocsp_request_t *this)
+{
+ return enumerator_create_filter(
+ this->reqCerts->create_enumerator(this->reqCerts),
+ filter, NULL, NULL);
+}
+
/**
* create an empty but initialized OCSP request
*/
.destroy = _destroy,
},
.get_nonce = _get_nonce,
+ .get_signer_cert = _get_signer_cert,
+ .create_request_enumerator = _create_request_enumerator,
},
},
- .candidates = linked_list_create(),
+ .reqCerts = linked_list_create(),
.ref = 1,
);
*/
x509_ocsp_request_t *x509_ocsp_request_gen(certificate_type_t type, va_list args)
{
- private_x509_ocsp_request_t *req;
- certificate_t *cert;
+ private_x509_ocsp_request_t *this;
private_key_t *private;
identification_t *subject;
+ certificate_t *cert;
+ x509_t *x509;
+ req_cert_t *reqCert;
+
+ this = create_empty();
- req = create_empty();
while (TRUE)
{
switch (va_arg(args, builder_part_t))
cert = va_arg(args, certificate_t*);
if (cert->get_type(cert) == CERT_X509)
{
- req->ca = (x509_t*)cert->get_ref(cert);
+ this->cacert = cert->get_ref(cert);
}
continue;
case BUILD_CERT:
cert = va_arg(args, certificate_t*);
if (cert->get_type(cert) == CERT_X509)
{
- req->candidates->insert_last(req->candidates,
- cert->get_ref(cert));
+ x509 = (x509_t*)cert;
+ INIT(reqCert,
+ .serialNumber = chunk_clone(x509->get_serial(x509)),
+ );
+ this->reqCerts->insert_last(this->reqCerts, reqCert);
}
continue;
case BUILD_SIGNING_CERT:
cert = va_arg(args, certificate_t*);
- req->cert = cert->get_ref(cert);
+ this->cert = cert->get_ref(cert);
continue;
case BUILD_SIGNING_KEY:
private = va_arg(args, private_key_t*);
- req->key = private->get_ref(private);
+ this->key = private->get_ref(private);
continue;
case BUILD_SUBJECT:
subject = va_arg(args, identification_t*);
- req->requestor = subject->clone(subject);
+ this->requestor = subject->clone(subject);
continue;
case BUILD_END:
break;
default:
- destroy(req);
- return NULL;
+ goto error;
}
break;
}
- if (req->ca)
+
+ if (this->cacert)
{
- req->encoding = build_OCSPRequest(req);
- return &req->public;
+ chunk_t issuerNameHash, issuerKeyHash;
+ enumerator_t *enumerator;
+ identification_t *issuer;
+ public_key_t *public;
+ req_cert_t *reqCert;
+ hasher_t *hasher;
+
+ public = this->cacert->get_public_key(this->cacert);
+ if (!public->get_fingerprint(public, KEYID_PUBKEY_SHA1, &issuerKeyHash))
+ {
+ DBG1(DBG_LIB, "failed to compute SHA1 issuerKeyHash");
+ public->destroy(public);
+ goto error;
+ }
+ public->destroy(public);
+
+ hasher = lib->crypto->create_hasher(lib->crypto, HASH_SHA1);
+ if (!hasher)
+ {
+ DBG1(DBG_LIB, "failed to create SHA1 hasher");
+ goto error;
+ }
+
+ issuer = this->cacert->get_subject(this->cacert);
+ if (!hasher->allocate_hash(hasher, issuer->get_encoding(issuer),
+ &issuerNameHash))
+ {
+ DBG1(DBG_LIB, "failed to compute SHA1 issuerNameHash");
+ hasher->destroy(hasher);
+ goto error;
+ }
+ hasher->destroy(hasher);
+
+ enumerator = this->reqCerts->create_enumerator(this->reqCerts);
+ while (enumerator->enumerate(enumerator, &reqCert))
+ {
+ reqCert->hashAlgorithm = HASH_SHA1;
+ reqCert->issuerNameHash = chunk_clone(issuerNameHash);
+ reqCert->issuerKeyHash = chunk_clone(issuerKeyHash);
+ }
+ enumerator->destroy(enumerator);
+ chunk_free(&issuerNameHash);
+
+ this->encoding = build_OCSPRequest(this);
+
+ return &this->public;
}
- destroy(req);
+
+error:
+ destroy(this);
return NULL;
}
+/**
+ * load an OCSP request
+ */
+static x509_ocsp_request_t *load(chunk_t blob)
+{
+ private_x509_ocsp_request_t *this;
+
+ this = create_empty();
+ this->encoding = chunk_clone(blob);
+
+ if (!parse_OCSPRequest(this))
+ {
+ destroy(this);
+ return NULL;
+ }
+ return &this->public;
+}
+
+/**
+ * See header.
+ */
+x509_ocsp_request_t *x509_ocsp_request_load(certificate_type_t type, va_list args)
+{
+ chunk_t blob = chunk_empty;
+
+ while (TRUE)
+ {
+ switch (va_arg(args, builder_part_t))
+ {
+ case BUILD_BLOB_ASN1_DER:
+ blob = va_arg(args, chunk_t);
+ continue;
+ case BUILD_END:
+ break;
+ default:
+ return NULL;
+ }
+ break;
+ }
+ if (blob.ptr)
+ {
+ return load(blob);
+ }
+ return NULL;
+}