revocation: Move CRL/OCSP fetch operations to a dedicated fetcher helper
Before adding stateful CRL fetching extensions, refactor CRL fetching to a helper class for better separation. While there are currently no plans to extend OCSP fetching, move it as well for consistency.
This commit is contained in:
committed by
Tobias Brunner
parent
f739f7a075
commit
119dfc2c38
@@ -12,6 +12,7 @@ endif
|
|||||||
|
|
||||||
libstrongswan_revocation_la_SOURCES = \
|
libstrongswan_revocation_la_SOURCES = \
|
||||||
revocation_plugin.h revocation_plugin.c \
|
revocation_plugin.h revocation_plugin.c \
|
||||||
|
revocation_fetcher.h revocation_fetcher.c \
|
||||||
revocation_validator.h revocation_validator.c
|
revocation_validator.h revocation_validator.c
|
||||||
|
|
||||||
libstrongswan_revocation_la_LDFLAGS = -module -avoid-version
|
libstrongswan_revocation_la_LDFLAGS = -module -avoid-version
|
||||||
|
|||||||
@@ -0,0 +1,159 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2025 Martin Willi
|
||||||
|
* Copyright (C) 2015-2018 Tobias Brunner
|
||||||
|
* Copyright (C) 2009-2022 Andreas Steffen
|
||||||
|
*
|
||||||
|
* Copyright (C) secunet Security Networks AG
|
||||||
|
*
|
||||||
|
* 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. See <http://www.fsf.org/copyleft/gpl.txt>.
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "revocation_fetcher.h"
|
||||||
|
|
||||||
|
#include <utils/debug.h>
|
||||||
|
#include <credentials/certificates/crl.h>
|
||||||
|
#include <credentials/certificates/ocsp_request.h>
|
||||||
|
#include <credentials/certificates/ocsp_response.h>
|
||||||
|
|
||||||
|
typedef struct private_revocation_fetcher_t private_revocation_fetcher_t;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Private data of an revocation_fetcher_t object.
|
||||||
|
*/
|
||||||
|
struct private_revocation_fetcher_t {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Public revocation_fetcher_t interface.
|
||||||
|
*/
|
||||||
|
revocation_fetcher_t public;
|
||||||
|
};
|
||||||
|
|
||||||
|
METHOD(revocation_fetcher_t, fetch_crl, certificate_t *,
|
||||||
|
private_revocation_fetcher_t *this, char *url, u_int timeout)
|
||||||
|
{
|
||||||
|
certificate_t *crl;
|
||||||
|
chunk_t chunk = chunk_empty;
|
||||||
|
|
||||||
|
DBG1(DBG_CFG, " fetching crl from '%s' ...", url);
|
||||||
|
if (lib->fetcher->fetch(lib->fetcher, url, &chunk,
|
||||||
|
FETCH_TIMEOUT, timeout,
|
||||||
|
FETCH_END) != SUCCESS)
|
||||||
|
{
|
||||||
|
DBG1(DBG_CFG, "crl fetching failed");
|
||||||
|
chunk_free(&chunk);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
crl = lib->creds->create(lib->creds, CRED_CERTIFICATE, CERT_X509_CRL,
|
||||||
|
BUILD_BLOB_PEM, chunk, BUILD_END);
|
||||||
|
chunk_free(&chunk);
|
||||||
|
if (!crl)
|
||||||
|
{
|
||||||
|
DBG1(DBG_CFG, "crl fetched successfully but parsing failed");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
return crl;
|
||||||
|
}
|
||||||
|
|
||||||
|
METHOD(revocation_fetcher_t, fetch_ocsp, certificate_t*,
|
||||||
|
private_revocation_fetcher_t *this, char *url,
|
||||||
|
certificate_t *subject, certificate_t *issuer, u_int timeout)
|
||||||
|
{
|
||||||
|
certificate_t *request, *response;
|
||||||
|
ocsp_request_t *ocsp_request;
|
||||||
|
ocsp_response_t *ocsp_response;
|
||||||
|
chunk_t send, receive = chunk_empty;
|
||||||
|
|
||||||
|
/* TODO: requestor name, signature */
|
||||||
|
request = lib->creds->create(lib->creds,
|
||||||
|
CRED_CERTIFICATE, CERT_X509_OCSP_REQUEST,
|
||||||
|
BUILD_CA_CERT, issuer,
|
||||||
|
BUILD_CERT, subject, BUILD_END);
|
||||||
|
if (!request)
|
||||||
|
{
|
||||||
|
DBG1(DBG_CFG, "generating ocsp request failed");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!request->get_encoding(request, CERT_ASN1_DER, &send))
|
||||||
|
{
|
||||||
|
DBG1(DBG_CFG, "encoding ocsp request failed");
|
||||||
|
request->destroy(request);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
DBG1(DBG_CFG, " requesting ocsp status from '%s' ...", url);
|
||||||
|
if (lib->fetcher->fetch(lib->fetcher, url, &receive,
|
||||||
|
FETCH_REQUEST_DATA, send,
|
||||||
|
FETCH_REQUEST_TYPE, "application/ocsp-request",
|
||||||
|
FETCH_TIMEOUT, timeout,
|
||||||
|
FETCH_END) != SUCCESS)
|
||||||
|
{
|
||||||
|
DBG1(DBG_CFG, "ocsp request to %s failed", url);
|
||||||
|
request->destroy(request);
|
||||||
|
chunk_free(&receive);
|
||||||
|
chunk_free(&send);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
chunk_free(&send);
|
||||||
|
|
||||||
|
response = lib->creds->create(lib->creds,
|
||||||
|
CRED_CERTIFICATE, CERT_X509_OCSP_RESPONSE,
|
||||||
|
BUILD_BLOB_ASN1_DER, receive, BUILD_END);
|
||||||
|
chunk_free(&receive);
|
||||||
|
if (!response)
|
||||||
|
{
|
||||||
|
DBG1(DBG_CFG, "parsing ocsp response failed");
|
||||||
|
request->destroy(request);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
ocsp_response = (ocsp_response_t*)response;
|
||||||
|
if (ocsp_response->get_ocsp_status(ocsp_response) != OCSP_SUCCESSFUL)
|
||||||
|
{
|
||||||
|
response->destroy(response);
|
||||||
|
request->destroy(request);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
ocsp_request = (ocsp_request_t*)request;
|
||||||
|
if (ocsp_response->get_nonce(ocsp_response).len &&
|
||||||
|
!chunk_equals_const(ocsp_request->get_nonce(ocsp_request),
|
||||||
|
ocsp_response->get_nonce(ocsp_response)))
|
||||||
|
{
|
||||||
|
DBG1(DBG_CFG, "nonce in ocsp response doesn't match");
|
||||||
|
request->destroy(request);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
request->destroy(request);
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
METHOD(revocation_fetcher_t, destroy, void,
|
||||||
|
private_revocation_fetcher_t *this)
|
||||||
|
{
|
||||||
|
free(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See header
|
||||||
|
*/
|
||||||
|
revocation_fetcher_t *revocation_fetcher_create()
|
||||||
|
{
|
||||||
|
private_revocation_fetcher_t *this;
|
||||||
|
|
||||||
|
INIT(this,
|
||||||
|
.public = {
|
||||||
|
.fetch_crl = _fetch_crl,
|
||||||
|
.fetch_ocsp = _fetch_ocsp,
|
||||||
|
.destroy = _destroy,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
return &this->public;
|
||||||
|
}
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2025 Martin Willi
|
||||||
|
*
|
||||||
|
* Copyright (C) secunet Security Networks AG
|
||||||
|
*
|
||||||
|
* 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. See <http://www.fsf.org/copyleft/gpl.txt>.
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @defgroup revocation_fetcher revocation_fetcher
|
||||||
|
* @{ @ingroup revocation
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef REVOCATION_FETCHER_H_
|
||||||
|
#define REVOCATION_FETCHER_H_
|
||||||
|
|
||||||
|
#include <credentials/certificates/certificate.h>
|
||||||
|
|
||||||
|
typedef struct revocation_fetcher_t revocation_fetcher_t;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Certificate fetcher performing the CRL/OCSP transfer.
|
||||||
|
*/
|
||||||
|
struct revocation_fetcher_t {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch a CRL from given URL.
|
||||||
|
*
|
||||||
|
* @param this revocation fetcher
|
||||||
|
* @param url URL to retrieve the CRL from
|
||||||
|
* @param timeout timeout in seconds for the fetch operation
|
||||||
|
* @return fetched CRL or NULL on error
|
||||||
|
*/
|
||||||
|
certificate_t *(*fetch_crl)(revocation_fetcher_t *this, char *url,
|
||||||
|
u_int timeout);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch an OCSP response from given URL.
|
||||||
|
*
|
||||||
|
* @param this revocation fetcher
|
||||||
|
* @param url URL to retrieve the OCSP response from
|
||||||
|
* @param subject subject to request OSCP status for
|
||||||
|
* @param issuer issuer of the subject
|
||||||
|
* @param timeout timeout in seconds for the fetch operation
|
||||||
|
* @return fetched OCSP response or NULL on error
|
||||||
|
*/
|
||||||
|
certificate_t *(*fetch_ocsp)(revocation_fetcher_t *this, char *url,
|
||||||
|
certificate_t *subject, certificate_t *issuer,
|
||||||
|
u_int timeout);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Destroy a revocation_fetcher_t.
|
||||||
|
*/
|
||||||
|
void (*destroy)(revocation_fetcher_t *this);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a revocation_fetcher instance.
|
||||||
|
*/
|
||||||
|
revocation_fetcher_t *revocation_fetcher_create();
|
||||||
|
|
||||||
|
#endif /** REVOCATION_FETCHER_H_ @}*/
|
||||||
@@ -23,12 +23,13 @@
|
|||||||
#include <utils/debug.h>
|
#include <utils/debug.h>
|
||||||
#include <credentials/certificates/x509.h>
|
#include <credentials/certificates/x509.h>
|
||||||
#include <credentials/certificates/crl.h>
|
#include <credentials/certificates/crl.h>
|
||||||
#include <credentials/certificates/ocsp_request.h>
|
|
||||||
#include <credentials/certificates/ocsp_response.h>
|
#include <credentials/certificates/ocsp_response.h>
|
||||||
#include <credentials/sets/ocsp_response_wrapper.h>
|
#include <credentials/sets/ocsp_response_wrapper.h>
|
||||||
#include <selectors/traffic_selector.h>
|
#include <selectors/traffic_selector.h>
|
||||||
#include <threading/spinlock.h>
|
#include <threading/spinlock.h>
|
||||||
|
|
||||||
|
#include "revocation_fetcher.h"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Default timeout in seconds when fetching OCSP/CRL.
|
* Default timeout in seconds when fetching OCSP/CRL.
|
||||||
*/
|
*/
|
||||||
@@ -46,6 +47,11 @@ struct private_revocation_validator_t {
|
|||||||
*/
|
*/
|
||||||
revocation_validator_t public;
|
revocation_validator_t public;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch helper for CRL/OCSP.
|
||||||
|
*/
|
||||||
|
revocation_fetcher_t *fetcher;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Enable OCSP validation
|
* Enable OCSP validation
|
||||||
*/
|
*/
|
||||||
@@ -67,80 +73,6 @@ struct private_revocation_validator_t {
|
|||||||
spinlock_t *lock;
|
spinlock_t *lock;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Do an OCSP request
|
|
||||||
*/
|
|
||||||
static certificate_t *fetch_ocsp(char *url, certificate_t *subject,
|
|
||||||
certificate_t *issuer, u_int timeout)
|
|
||||||
{
|
|
||||||
certificate_t *request, *response;
|
|
||||||
ocsp_request_t *ocsp_request;
|
|
||||||
ocsp_response_t *ocsp_response;
|
|
||||||
chunk_t send, receive = chunk_empty;
|
|
||||||
|
|
||||||
/* TODO: requestor name, signature */
|
|
||||||
request = lib->creds->create(lib->creds,
|
|
||||||
CRED_CERTIFICATE, CERT_X509_OCSP_REQUEST,
|
|
||||||
BUILD_CA_CERT, issuer,
|
|
||||||
BUILD_CERT, subject, BUILD_END);
|
|
||||||
if (!request)
|
|
||||||
{
|
|
||||||
DBG1(DBG_CFG, "generating ocsp request failed");
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!request->get_encoding(request, CERT_ASN1_DER, &send))
|
|
||||||
{
|
|
||||||
DBG1(DBG_CFG, "encoding ocsp request failed");
|
|
||||||
request->destroy(request);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
DBG1(DBG_CFG, " requesting ocsp status from '%s' ...", url);
|
|
||||||
if (lib->fetcher->fetch(lib->fetcher, url, &receive,
|
|
||||||
FETCH_REQUEST_DATA, send,
|
|
||||||
FETCH_REQUEST_TYPE, "application/ocsp-request",
|
|
||||||
FETCH_TIMEOUT, timeout,
|
|
||||||
FETCH_END) != SUCCESS)
|
|
||||||
{
|
|
||||||
DBG1(DBG_CFG, "ocsp request to %s failed", url);
|
|
||||||
request->destroy(request);
|
|
||||||
chunk_free(&receive);
|
|
||||||
chunk_free(&send);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
chunk_free(&send);
|
|
||||||
|
|
||||||
response = lib->creds->create(lib->creds,
|
|
||||||
CRED_CERTIFICATE, CERT_X509_OCSP_RESPONSE,
|
|
||||||
BUILD_BLOB_ASN1_DER, receive, BUILD_END);
|
|
||||||
chunk_free(&receive);
|
|
||||||
if (!response)
|
|
||||||
{
|
|
||||||
DBG1(DBG_CFG, "parsing ocsp response failed");
|
|
||||||
request->destroy(request);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
ocsp_response = (ocsp_response_t*)response;
|
|
||||||
if (ocsp_response->get_ocsp_status(ocsp_response) != OCSP_SUCCESSFUL)
|
|
||||||
{
|
|
||||||
response->destroy(response);
|
|
||||||
request->destroy(request);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
ocsp_request = (ocsp_request_t*)request;
|
|
||||||
if (ocsp_response->get_nonce(ocsp_response).len &&
|
|
||||||
!chunk_equals_const(ocsp_request->get_nonce(ocsp_request),
|
|
||||||
ocsp_response->get_nonce(ocsp_response)))
|
|
||||||
{
|
|
||||||
DBG1(DBG_CFG, "nonce in ocsp response doesn't match");
|
|
||||||
request->destroy(request);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
request->destroy(request);
|
|
||||||
return response;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Verify OCSP response signature
|
* Verify OCSP response signature
|
||||||
*/
|
*/
|
||||||
@@ -369,8 +301,9 @@ static cert_validation_t check_ocsp(private_revocation_validator_t *this,
|
|||||||
CERT_X509_OCSP_RESPONSE, keyid);
|
CERT_X509_OCSP_RESPONSE, keyid);
|
||||||
while (enumerator->enumerate(enumerator, &uri))
|
while (enumerator->enumerate(enumerator, &uri))
|
||||||
{
|
{
|
||||||
current = fetch_ocsp(uri, &subject->interface, &issuer->interface,
|
current = this->fetcher->fetch_ocsp(this->fetcher, uri,
|
||||||
timeout);
|
&subject->interface,
|
||||||
|
&issuer->interface, timeout);
|
||||||
if (current)
|
if (current)
|
||||||
{
|
{
|
||||||
best = get_better_ocsp(current, best, subject, issuer,
|
best = get_better_ocsp(current, best, subject, issuer,
|
||||||
@@ -392,8 +325,9 @@ static cert_validation_t check_ocsp(private_revocation_validator_t *this,
|
|||||||
enumerator = subject->create_ocsp_uri_enumerator(subject);
|
enumerator = subject->create_ocsp_uri_enumerator(subject);
|
||||||
while (enumerator->enumerate(enumerator, &uri))
|
while (enumerator->enumerate(enumerator, &uri))
|
||||||
{
|
{
|
||||||
current = fetch_ocsp(uri, &subject->interface, &issuer->interface,
|
current = this->fetcher->fetch_ocsp(this->fetcher, uri,
|
||||||
timeout);
|
&subject->interface,
|
||||||
|
&issuer->interface, timeout);
|
||||||
if (current)
|
if (current)
|
||||||
{
|
{
|
||||||
best = get_better_ocsp(current, best, subject, issuer,
|
best = get_better_ocsp(current, best, subject, issuer,
|
||||||
@@ -428,34 +362,6 @@ static cert_validation_t check_ocsp(private_revocation_validator_t *this,
|
|||||||
return valid;
|
return valid;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* fetch a CRL from an URL
|
|
||||||
*/
|
|
||||||
static certificate_t* fetch_crl(char *url, u_int timeout)
|
|
||||||
{
|
|
||||||
certificate_t *crl;
|
|
||||||
chunk_t chunk = chunk_empty;
|
|
||||||
|
|
||||||
DBG1(DBG_CFG, " fetching crl from '%s' ...", url);
|
|
||||||
if (lib->fetcher->fetch(lib->fetcher, url, &chunk,
|
|
||||||
FETCH_TIMEOUT, timeout,
|
|
||||||
FETCH_END) != SUCCESS)
|
|
||||||
{
|
|
||||||
DBG1(DBG_CFG, "crl fetching failed");
|
|
||||||
chunk_free(&chunk);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
crl = lib->creds->create(lib->creds, CRED_CERTIFICATE, CERT_X509_CRL,
|
|
||||||
BUILD_BLOB_PEM, chunk, BUILD_END);
|
|
||||||
chunk_free(&chunk);
|
|
||||||
if (!crl)
|
|
||||||
{
|
|
||||||
DBG1(DBG_CFG, "crl fetched successfully but parsing failed");
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
return crl;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* check the signature of an CRL
|
* check the signature of an CRL
|
||||||
*/
|
*/
|
||||||
@@ -649,7 +555,7 @@ static cert_validation_t find_crl(private_revocation_validator_t *this,
|
|||||||
while (enumerator->enumerate(enumerator, &uri))
|
while (enumerator->enumerate(enumerator, &uri))
|
||||||
{
|
{
|
||||||
*uri_found = TRUE;
|
*uri_found = TRUE;
|
||||||
current = fetch_crl(uri, timeout);
|
current = this->fetcher->fetch_crl(this->fetcher, uri, timeout);
|
||||||
if (current)
|
if (current)
|
||||||
{
|
{
|
||||||
if (!current->has_issuer(current, issuer))
|
if (!current->has_issuer(current, issuer))
|
||||||
@@ -741,7 +647,7 @@ static cert_validation_t check_delta_crl(private_revocation_validator_t *this,
|
|||||||
while (valid != VALIDATION_GOOD && valid != VALIDATION_REVOKED &&
|
while (valid != VALIDATION_GOOD && valid != VALIDATION_REVOKED &&
|
||||||
enumerator->enumerate(enumerator, &cdp))
|
enumerator->enumerate(enumerator, &cdp))
|
||||||
{
|
{
|
||||||
current = fetch_crl(cdp->uri, timeout);
|
current = this->fetcher->fetch_crl(this->fetcher, cdp->uri, timeout);
|
||||||
if (current)
|
if (current)
|
||||||
{
|
{
|
||||||
if (!check_issuer(current, issuer, cdp))
|
if (!check_issuer(current, issuer, cdp))
|
||||||
@@ -815,7 +721,8 @@ static cert_validation_t check_crl(private_revocation_validator_t *this,
|
|||||||
while (enumerator->enumerate(enumerator, &cdp))
|
while (enumerator->enumerate(enumerator, &cdp))
|
||||||
{
|
{
|
||||||
uri_found = TRUE;
|
uri_found = TRUE;
|
||||||
current = fetch_crl(cdp->uri, timeout);
|
current = this->fetcher->fetch_crl(this->fetcher, cdp->uri,
|
||||||
|
timeout);
|
||||||
if (current)
|
if (current)
|
||||||
{
|
{
|
||||||
if (!check_issuer(current, issuer, cdp))
|
if (!check_issuer(current, issuer, cdp))
|
||||||
@@ -1023,6 +930,7 @@ METHOD(revocation_validator_t, reload, void,
|
|||||||
METHOD(revocation_validator_t, destroy, void,
|
METHOD(revocation_validator_t, destroy, void,
|
||||||
private_revocation_validator_t *this)
|
private_revocation_validator_t *this)
|
||||||
{
|
{
|
||||||
|
this->fetcher->destroy(this->fetcher);
|
||||||
this->lock->destroy(this->lock);
|
this->lock->destroy(this->lock);
|
||||||
free(this);
|
free(this);
|
||||||
}
|
}
|
||||||
@@ -1041,6 +949,7 @@ revocation_validator_t *revocation_validator_create()
|
|||||||
.reload = _reload,
|
.reload = _reload,
|
||||||
.destroy = _destroy,
|
.destroy = _destroy,
|
||||||
},
|
},
|
||||||
|
.fetcher = revocation_fetcher_create(),
|
||||||
.lock = spinlock_create(),
|
.lock = spinlock_create(),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user