@@ -17,12 +17,14 @@ use ngx::sync::RwLock;
1717use openssl:: pkey:: { PKey , Private } ;
1818use thiserror:: Error ;
1919
20+ use super :: ext:: NgxConfExt ;
2021use super :: order:: CertificateOrder ;
2122use super :: pkey:: PrivateKey ;
2223use super :: ssl:: NgxSsl ;
2324use super :: AcmeMainConfig ;
24- use crate :: state:: certificate:: CertificateContext ;
25+ use crate :: state:: certificate:: { CertificateContext , CertificateContextInner } ;
2526use crate :: state:: issuer:: IssuerContext ;
27+ use crate :: time:: { Time , TimeRange } ;
2628
2729const ACCOUNT_KEY_FILE : & str = "account.key" ;
2830const NGX_ACME_DEFAULT_RESOLVER_TIMEOUT : ngx_msec_t = 30000 ;
@@ -163,7 +165,32 @@ impl Issuer {
163165 self . name
164166 ) ;
165167
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+ }
167194
168195 if self . orders . try_insert ( order. clone ( ) , cert) . is_err ( ) {
169196 return Err ( Status :: NGX_ERROR ) ;
@@ -239,6 +266,20 @@ impl Issuer {
239266 }
240267}
241268
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+
242283/// The StateDir helper encapsulates operations with a persistent state in the state directory.
243284#[ repr( transparent) ]
244285struct StateDir ( ngx_path_t ) ;
@@ -264,4 +305,52 @@ impl StateDir {
264305 pub fn write ( & self , path : & std:: path:: Path , data : & [ u8 ] ) -> Result < ( ) , std:: io:: Error > {
265306 std:: fs:: write ( path, data)
266307 }
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+ }
267356}
0 commit comments