Skip to content

Commit 641af25

Browse files
committed
Merge branch 'revocation-fetcher'
Combines concurrent requests for the same CRL URI by multiple threads. So only the first thread actually fetches it, the others wait for that result. This is particularly helpful if the CRL can currently not be fetched due to DNS or HTTP/LDAP timeouts as it prevents each thread from having to wait for the complete timeouts, which reduces the number of SAs that can concurrently be established. A negative result is cached for a while (currently 3 times the fetch timeout, i.e. 30 seconds by default) so requests can fail quickly and threads can continue establishing SAs if they use a relaxed revocation policy. Closes strongswan#2918
2 parents 041d064 + df6977d commit 641af25

File tree

4 files changed

+424
-125
lines changed

4 files changed

+424
-125
lines changed

src/libstrongswan/plugins/revocation/Makefile.am

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ endif
1212

1313
libstrongswan_revocation_la_SOURCES = \
1414
revocation_plugin.h revocation_plugin.c \
15+
revocation_fetcher.h revocation_fetcher.c \
1516
revocation_validator.h revocation_validator.c
1617

1718
libstrongswan_revocation_la_LDFLAGS = -module -avoid-version
Lines changed: 312 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,312 @@
1+
/*
2+
* Copyright (C) 2025 Martin Willi
3+
* Copyright (C) 2015-2018 Tobias Brunner
4+
* Copyright (C) 2009-2022 Andreas Steffen
5+
*
6+
* Copyright (C) secunet Security Networks AG
7+
*
8+
* This program is free software; you can redistribute it and/or modify it
9+
* under the terms of the GNU General Public License as published by the
10+
* Free Software Foundation; either version 2 of the License, or (at your
11+
* option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
12+
*
13+
* This program is distributed in the hope that it will be useful, but
14+
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
15+
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
16+
* for more details.
17+
*/
18+
19+
#include "revocation_fetcher.h"
20+
21+
#include <utils/debug.h>
22+
#include <threading/mutex.h>
23+
#include <threading/condvar.h>
24+
#include <collections/hashtable.h>
25+
#include <credentials/certificates/crl.h>
26+
#include <credentials/certificates/ocsp_request.h>
27+
#include <credentials/certificates/ocsp_response.h>
28+
29+
/* number of fetch timeouts to degrade a CRL fetch after a failure */
30+
#define CRL_DEGRADATION_TIMES 3
31+
32+
typedef struct private_revocation_fetcher_t private_revocation_fetcher_t;
33+
34+
/**
35+
* Private data of an revocation_fetcher_t object.
36+
*/
37+
struct private_revocation_fetcher_t {
38+
39+
/**
40+
* Public revocation_fetcher_t interface.
41+
*/
42+
revocation_fetcher_t public;
43+
44+
/**
45+
* Mutex to synchronize CRL fetches
46+
*/
47+
mutex_t *mutex;
48+
49+
/**
50+
* Active/completed/failed CRL fetches, crl_fetch_t.
51+
*/
52+
hashtable_t *crls;
53+
};
54+
55+
typedef struct crl_fetch_t crl_fetch_t;
56+
57+
/**
58+
* Represents an active/completed/failed CRL fetch.
59+
*/
60+
struct crl_fetch_t {
61+
62+
/**
63+
* URL of the CRL.
64+
*/
65+
char *url;
66+
67+
/**
68+
* Condition variable to signal completion of the fetch.
69+
*/
70+
condvar_t *condvar;
71+
72+
/**
73+
* Number of threads fetching this CRL.
74+
*/
75+
u_int fetchers;
76+
77+
/**
78+
* Has the previous fetch failed, until when is this URL degraded?
79+
*/
80+
time_t failing;
81+
82+
/**
83+
* CRL received in the currently active fetch.
84+
*/
85+
certificate_t *crl;
86+
};
87+
88+
/**
89+
* Perform the actual CRL fetch from the given URL.
90+
*/
91+
static certificate_t *do_crl_fetch(private_revocation_fetcher_t *this,
92+
char *url, u_int timeout)
93+
{
94+
certificate_t *crl;
95+
chunk_t chunk = chunk_empty;
96+
97+
DBG1(DBG_CFG, " fetching crl from '%s' ...", url);
98+
if (lib->fetcher->fetch(lib->fetcher, url, &chunk,
99+
FETCH_TIMEOUT, timeout,
100+
FETCH_END) != SUCCESS)
101+
{
102+
DBG1(DBG_CFG, "crl fetching failed");
103+
chunk_free(&chunk);
104+
return NULL;
105+
}
106+
crl = lib->creds->create(lib->creds, CRED_CERTIFICATE, CERT_X509_CRL,
107+
BUILD_BLOB_PEM, chunk, BUILD_END);
108+
chunk_free(&chunk);
109+
if (!crl)
110+
{
111+
DBG1(DBG_CFG, "crl fetched successfully but parsing failed");
112+
return NULL;
113+
}
114+
return crl;
115+
}
116+
117+
/**
118+
* Start a new CRL fetch and signal completion to waiting threads.
119+
*/
120+
static certificate_t *start_crl_fetch(private_revocation_fetcher_t *this,
121+
crl_fetch_t *fetch, u_int timeout)
122+
{
123+
certificate_t *crl;
124+
125+
fetch->fetchers++;
126+
this->mutex->unlock(this->mutex);
127+
crl = do_crl_fetch(this, fetch->url, timeout);
128+
this->mutex->lock(this->mutex);
129+
fetch->crl = crl;
130+
if (crl)
131+
{
132+
fetch->failing = 0;
133+
}
134+
else
135+
{
136+
fetch->failing = time_monotonic(NULL) + timeout * CRL_DEGRADATION_TIMES;
137+
}
138+
while (fetch->fetchers > 1)
139+
{
140+
fetch->condvar->signal(fetch->condvar);
141+
fetch->condvar->wait(fetch->condvar, this->mutex);
142+
}
143+
fetch->fetchers--;
144+
fetch->crl = NULL;
145+
return crl;
146+
}
147+
148+
/**
149+
* Wait for a CRL fetch performed by another thread to complete.
150+
*/
151+
static certificate_t *wait_for_crl(private_revocation_fetcher_t *this,
152+
crl_fetch_t *fetch)
153+
{
154+
certificate_t *crl = NULL;
155+
156+
if (fetch->failing && fetch->failing > time_monotonic(NULL))
157+
{
158+
DBG1(DBG_CFG, " crl fetch from '%s' recently failed, skipping",
159+
fetch->url);
160+
return NULL;
161+
}
162+
DBG1(DBG_CFG, " waiting for crl fetch from '%s' ...", fetch->url);
163+
if (fetch->crl)
164+
{
165+
/* fetch is already complete, no need to wait */
166+
return fetch->crl->get_ref(fetch->crl);
167+
}
168+
fetch->fetchers++;
169+
fetch->condvar->wait(fetch->condvar, this->mutex);
170+
fetch->fetchers--;
171+
if (fetch->crl)
172+
{
173+
crl = fetch->crl->get_ref(fetch->crl);
174+
}
175+
fetch->condvar->signal(fetch->condvar);
176+
return crl;
177+
}
178+
179+
METHOD(revocation_fetcher_t, fetch_crl, certificate_t*,
180+
private_revocation_fetcher_t *this, char *url, u_int timeout)
181+
{
182+
certificate_t *crl;
183+
crl_fetch_t *fetch;
184+
185+
this->mutex->lock(this->mutex);
186+
fetch = this->crls->get(this->crls, url);
187+
if (!fetch)
188+
{
189+
INIT(fetch,
190+
.url = strdup(url),
191+
.condvar = condvar_create(CONDVAR_TYPE_DEFAULT),
192+
);
193+
this->crls->put(this->crls, fetch->url, fetch);
194+
}
195+
if (fetch->fetchers)
196+
{
197+
crl = wait_for_crl(this, fetch);
198+
}
199+
else
200+
{
201+
crl = start_crl_fetch(this, fetch, timeout);
202+
}
203+
this->mutex->unlock(this->mutex);
204+
return crl;
205+
}
206+
207+
METHOD(revocation_fetcher_t, fetch_ocsp, certificate_t*,
208+
private_revocation_fetcher_t *this, char *url,
209+
certificate_t *subject, certificate_t *issuer, u_int timeout)
210+
{
211+
certificate_t *request, *response;
212+
ocsp_request_t *ocsp_request;
213+
ocsp_response_t *ocsp_response;
214+
chunk_t send, receive = chunk_empty;
215+
216+
/* TODO: requestor name, signature */
217+
request = lib->creds->create(lib->creds,
218+
CRED_CERTIFICATE, CERT_X509_OCSP_REQUEST,
219+
BUILD_CA_CERT, issuer,
220+
BUILD_CERT, subject, BUILD_END);
221+
if (!request)
222+
{
223+
DBG1(DBG_CFG, "generating ocsp request failed");
224+
return NULL;
225+
}
226+
227+
if (!request->get_encoding(request, CERT_ASN1_DER, &send))
228+
{
229+
DBG1(DBG_CFG, "encoding ocsp request failed");
230+
request->destroy(request);
231+
return NULL;
232+
}
233+
234+
DBG1(DBG_CFG, " requesting ocsp status from '%s' ...", url);
235+
if (lib->fetcher->fetch(lib->fetcher, url, &receive,
236+
FETCH_REQUEST_DATA, send,
237+
FETCH_REQUEST_TYPE, "application/ocsp-request",
238+
FETCH_TIMEOUT, timeout,
239+
FETCH_END) != SUCCESS)
240+
{
241+
DBG1(DBG_CFG, "ocsp request to %s failed", url);
242+
request->destroy(request);
243+
chunk_free(&receive);
244+
chunk_free(&send);
245+
return NULL;
246+
}
247+
chunk_free(&send);
248+
249+
response = lib->creds->create(lib->creds,
250+
CRED_CERTIFICATE, CERT_X509_OCSP_RESPONSE,
251+
BUILD_BLOB_ASN1_DER, receive, BUILD_END);
252+
chunk_free(&receive);
253+
if (!response)
254+
{
255+
DBG1(DBG_CFG, "parsing ocsp response failed");
256+
request->destroy(request);
257+
return NULL;
258+
}
259+
ocsp_response = (ocsp_response_t*)response;
260+
if (ocsp_response->get_ocsp_status(ocsp_response) != OCSP_SUCCESSFUL)
261+
{
262+
response->destroy(response);
263+
request->destroy(request);
264+
return NULL;
265+
}
266+
ocsp_request = (ocsp_request_t*)request;
267+
if (ocsp_response->get_nonce(ocsp_response).len &&
268+
!chunk_equals_const(ocsp_request->get_nonce(ocsp_request),
269+
ocsp_response->get_nonce(ocsp_response)))
270+
{
271+
DBG1(DBG_CFG, "nonce in ocsp response doesn't match");
272+
request->destroy(request);
273+
return NULL;
274+
}
275+
request->destroy(request);
276+
return response;
277+
}
278+
279+
CALLBACK(crl_fetch_destroy, void, crl_fetch_t *fetch, const void *key)
280+
{
281+
fetch->condvar->destroy(fetch->condvar);
282+
free(fetch->url);
283+
free(fetch);
284+
}
285+
286+
METHOD(revocation_fetcher_t, destroy, void,
287+
private_revocation_fetcher_t *this)
288+
{
289+
this->crls->destroy_function(this->crls, crl_fetch_destroy);
290+
this->mutex->destroy(this->mutex);
291+
free(this);
292+
}
293+
294+
/**
295+
* See header
296+
*/
297+
revocation_fetcher_t *revocation_fetcher_create()
298+
{
299+
private_revocation_fetcher_t *this;
300+
301+
INIT(this,
302+
.public = {
303+
.fetch_crl = _fetch_crl,
304+
.fetch_ocsp = _fetch_ocsp,
305+
.destroy = _destroy,
306+
},
307+
.mutex = mutex_create(MUTEX_TYPE_DEFAULT),
308+
.crls = hashtable_create(hashtable_hash_str, hashtable_equals_str, 8),
309+
);
310+
311+
return &this->public;
312+
}
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
/*
2+
* Copyright (C) 2025 Martin Willi
3+
*
4+
* Copyright (C) secunet Security Networks AG
5+
*
6+
* This program is free software; you can redistribute it and/or modify it
7+
* under the terms of the GNU General Public License as published by the
8+
* Free Software Foundation; either version 2 of the License, or (at your
9+
* option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
10+
*
11+
* This program is distributed in the hope that it will be useful, but
12+
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
13+
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14+
* for more details.
15+
*/
16+
17+
/**
18+
* @defgroup revocation_fetcher revocation_fetcher
19+
* @{ @ingroup revocation
20+
*/
21+
22+
#ifndef REVOCATION_FETCHER_H_
23+
#define REVOCATION_FETCHER_H_
24+
25+
#include <credentials/certificates/certificate.h>
26+
27+
typedef struct revocation_fetcher_t revocation_fetcher_t;
28+
29+
/**
30+
* Certificate fetcher performing the CRL/OCSP transfer.
31+
*/
32+
struct revocation_fetcher_t {
33+
34+
/**
35+
* Fetch a CRL from given URL.
36+
*
37+
* @param this revocation fetcher
38+
* @param url URL to retrieve the CRL from
39+
* @param timeout timeout in seconds for the fetch operation
40+
* @return fetched CRL or NULL on error
41+
*/
42+
certificate_t *(*fetch_crl)(revocation_fetcher_t *this, char *url,
43+
u_int timeout);
44+
45+
/**
46+
* Fetch an OCSP response from given URL.
47+
*
48+
* @param this revocation fetcher
49+
* @param url URL to retrieve the OCSP response from
50+
* @param subject subject to request OSCP status for
51+
* @param issuer issuer of the subject
52+
* @param timeout timeout in seconds for the fetch operation
53+
* @return fetched OCSP response or NULL on error
54+
*/
55+
certificate_t *(*fetch_ocsp)(revocation_fetcher_t *this, char *url,
56+
certificate_t *subject, certificate_t *issuer,
57+
u_int timeout);
58+
59+
/**
60+
* Destroy a revocation_fetcher_t.
61+
*/
62+
void (*destroy)(revocation_fetcher_t *this);
63+
};
64+
65+
/**
66+
* Create a revocation_fetcher instance.
67+
*/
68+
revocation_fetcher_t *revocation_fetcher_create();
69+
70+
#endif /** REVOCATION_FETCHER_H_ @}*/

0 commit comments

Comments
 (0)