@@ -17,12 +17,14 @@ use ngx::sync::RwLock;
17
17
use openssl:: pkey:: { PKey , Private } ;
18
18
use thiserror:: Error ;
19
19
20
+ use super :: ext:: NgxConfExt ;
20
21
use super :: order:: CertificateOrder ;
21
22
use super :: pkey:: PrivateKey ;
22
23
use super :: ssl:: NgxSsl ;
23
24
use super :: AcmeMainConfig ;
24
- use crate :: state:: certificate:: CertificateContext ;
25
+ use crate :: state:: certificate:: { CertificateContext , CertificateContextInner } ;
25
26
use crate :: state:: issuer:: IssuerContext ;
27
+ use crate :: time:: { Time , TimeRange } ;
26
28
27
29
const ACCOUNT_KEY_FILE : & str = "account.key" ;
28
30
const NGX_ACME_DEFAULT_RESOLVER_TIMEOUT : ngx_msec_t = 30000 ;
@@ -163,7 +165,32 @@ impl Issuer {
163
165
self . name
164
166
) ;
165
167
166
- let cert = CertificateContext :: Empty ;
168
+ let mut cert = CertificateContext :: Empty ;
169
+
170
+ if let Some ( state_dir) = unsafe { StateDir :: from_ptr ( self . state_path ) } {
171
+ match state_dir. load_certificate ( cf, order) {
172
+ Ok ( x) => {
173
+ ngx_log_debug ! (
174
+ cf. log,
175
+ "acme: found cached certificate {}/{}, next update in {:?}" ,
176
+ self . name,
177
+ order. cache_key( ) ,
178
+ ( x. next - Time :: now( ) ) ,
179
+ ) ;
180
+ cert = CertificateContext :: Local ( x) ;
181
+ }
182
+ Err ( CachedCertificateError :: NotFound ) => ( ) ,
183
+ Err ( err) => {
184
+ ngx_log_debug ! (
185
+ cf. log,
186
+ "acme: cannot load certificate {}/{} from state path: {}" ,
187
+ self . name,
188
+ order. cache_key( ) ,
189
+ err
190
+ ) ;
191
+ }
192
+ }
193
+ }
167
194
168
195
if self . orders . try_insert ( order. clone ( ) , cert) . is_err ( ) {
169
196
return Err ( Status :: NGX_ERROR ) ;
@@ -239,6 +266,20 @@ impl Issuer {
239
266
}
240
267
}
241
268
269
+ #[ derive( Debug , thiserror:: Error ) ]
270
+ enum CachedCertificateError {
271
+ #[ error( transparent) ]
272
+ Alloc ( #[ from] AllocError ) ,
273
+ #[ error( "X509_check_private_key() failed: {0}" ) ]
274
+ Mismatch ( openssl:: error:: ErrorStack ) ,
275
+ #[ error( "file not found" ) ]
276
+ NotFound ,
277
+ #[ error( transparent) ]
278
+ Ssl ( #[ from] openssl:: error:: ErrorStack ) ,
279
+ #[ error( "failed to load file: {0}" ) ]
280
+ CertificateFetch ( #[ from] super :: ssl:: CertificateFetchError ) ,
281
+ }
282
+
242
283
/// The StateDir helper encapsulates operations with a persistent state in the state directory.
243
284
#[ repr( transparent) ]
244
285
struct StateDir ( ngx_path_t ) ;
@@ -264,4 +305,52 @@ impl StateDir {
264
305
pub fn write ( & self , path : & std:: path:: Path , data : & [ u8 ] ) -> Result < ( ) , std:: io:: Error > {
265
306
std:: fs:: write ( path, data)
266
307
}
308
+
309
+ pub fn load_certificate (
310
+ & self ,
311
+ cf : & mut ngx_conf_t ,
312
+ order : & CertificateOrder < ngx_str_t , Pool > ,
313
+ ) -> Result < CertificateContextInner < Pool > , CachedCertificateError > {
314
+ use openssl_foreign_types:: ForeignType ;
315
+ #[ cfg( ngx_ssl_cache) ]
316
+ use openssl_foreign_types:: ForeignTypeRef ;
317
+
318
+ let name = order. cache_key ( ) ;
319
+
320
+ let cert = std:: format!( "{}/{}.crt" , self . 0 . name, name) ;
321
+ if matches ! ( std:: fs:: exists( & cert) , Ok ( false ) ) {
322
+ return Err ( CachedCertificateError :: NotFound ) ;
323
+ }
324
+
325
+ let key = std:: format!( "{}/{}.key" , self . 0 . name, name) ;
326
+ if matches ! ( std:: fs:: exists( & key) , Ok ( false ) ) {
327
+ return Err ( CachedCertificateError :: NotFound ) ;
328
+ }
329
+
330
+ let stack = super :: ssl:: conf_read_certificate ( cf, & cert) ?;
331
+ #[ allow( clippy:: get_first) ] // ^ can return Stack or Vec, depending on the NGINX version
332
+ let cert = stack
333
+ . get ( 0 )
334
+ . ok_or ( super :: ssl:: CertificateFetchError :: Fetch ( c"no certificates" ) ) ?;
335
+ let pkey = super :: ssl:: conf_read_private_key ( cf, & key) ?;
336
+
337
+ if unsafe { openssl_sys:: X509_check_private_key ( cert. as_ptr ( ) , pkey. as_ptr ( ) ) } != 1 {
338
+ return Err ( CachedCertificateError :: Mismatch (
339
+ openssl:: error:: ErrorStack :: get ( ) ,
340
+ ) ) ;
341
+ }
342
+
343
+ let valid = TimeRange :: from_x509 ( cert) . unwrap_or_default ( ) ;
344
+ let temp_alloc = unsafe { Pool :: from_ngx_pool ( cf. temp_pool ) } ;
345
+
346
+ let mut chain: Vec < u8 , Pool > = Vec :: new_in ( temp_alloc. clone ( ) ) ;
347
+ for x509 in stack. into_iter ( ) {
348
+ chain. extend ( x509. to_pem ( ) ?. into_iter ( ) ) ;
349
+ }
350
+
351
+ let mut cert = CertificateContextInner :: new_in ( cf. pool ( ) ) ;
352
+ cert. set ( & chain, & pkey. private_key_to_pem_pkcs8 ( ) ?, valid) ?;
353
+
354
+ Ok ( cert)
355
+ }
267
356
}
0 commit comments