@@ -277,35 +277,71 @@ async fn main(req: Request, env: Env, ctx: Context) -> Result<Response> {
277277 response
278278}
279279
280+ /// Builds the issuer RDN with the trust anchor ID.
281+ fn build_issuer_rdn ( log_id : & str ) -> std:: result:: Result < RdnSequence , String > {
282+ let utf8_value = Utf8StringRef :: new ( log_id) . map_err ( |e| e. to_string ( ) ) ?;
283+ let any_value = Any :: new ( Tag :: Utf8String , utf8_value. as_bytes ( ) ) . map_err ( |e| e. to_string ( ) ) ?;
284+
285+ let attr = AttributeTypeAndValue {
286+ oid : ID_RDNA_TRUSTANCHOR_ID ,
287+ value : any_value,
288+ } ;
289+
290+ let rdn = RelativeDistinguishedName (
291+ SetOfVec :: from_iter ( [ attr] ) . expect ( "single attribute should always succeed" ) ,
292+ ) ;
293+
294+ Ok ( RdnSequence :: from ( vec ! [ rdn] ) )
295+ }
296+
297+ fn build_validity ( now_millis : u64 , max_lifetime_secs : u64 ) -> std:: result:: Result < Validity , String > {
298+ let now = Duration :: from_millis ( now_millis) ;
299+ let not_before = UtcTime :: from_unix_duration ( now) . map_err ( |e| e. to_string ( ) ) ?;
300+ let not_after =
301+ UtcTime :: from_unix_duration ( now + Duration :: from_secs ( max_lifetime_secs) ) . map_err ( |e| e. to_string ( ) ) ?;
302+
303+ Ok ( Validity {
304+ not_before : Time :: UtcTime ( not_before) ,
305+ not_after : Time :: UtcTime ( not_after) ,
306+ } )
307+ }
308+
309+ /// Returns the issuer cert for SCT validation. For multi-cert chains, that's
310+ /// chain[1]. For single-cert chains, we look it up from the roots pool.
311+ fn resolve_issuer_for_sct (
312+ chain : & [ Vec < u8 > ] ,
313+ roots : & x509_util:: CertPool ,
314+ ) -> std:: result:: Result < Vec < u8 > , String > {
315+ use der:: { Decode , Encode } ;
316+ use x509_cert:: Certificate ;
317+
318+ if chain. is_empty ( ) {
319+ return Err ( "chain is empty" . into ( ) ) ;
320+ }
321+
322+ if chain. len ( ) > 1 {
323+ return Ok ( chain[ 1 ] . clone ( ) ) ;
324+ }
325+
326+ // Single-cert chain: look up issuer from roots pool
327+ let leaf = Certificate :: from_der ( & chain[ 0 ] ) . map_err ( |e| format ! ( "failed to parse leaf: {e}" ) ) ?;
328+ let issuer_dn = & leaf. tbs_certificate . issuer ;
329+
330+ roots
331+ . find_by_subject ( issuer_dn)
332+ . ok_or_else ( || format ! ( "issuer not found in roots pool: {issuer_dn}" ) ) ?
333+ . to_der ( )
334+ . map_err ( |e| format ! ( "failed to encode issuer: {e}" ) )
335+ }
336+
280337#[ allow( clippy:: too_many_lines) ]
281338async fn add_entry ( mut req : Request , env : & Env , name : & str ) -> Result < Response > {
282339 let params = & CONFIG . logs [ name] ;
283340 let req: AddEntryRequest = req. json ( ) . await ?;
284341
285- let issuer = RdnSequence :: from ( vec ! [ RelativeDistinguishedName (
286- SetOfVec :: from_iter( [ AttributeTypeAndValue {
287- oid: ID_RDNA_TRUSTANCHOR_ID ,
288- value: Any :: new(
289- Tag :: Utf8String ,
290- Utf8StringRef :: new( & params. log_id)
291- . map_err( |e| e. to_string( ) ) ?
292- . as_bytes( ) ,
293- )
294- . map_err( |e| e. to_string( ) ) ?,
295- } ] )
296- . unwrap( ) ,
297- ) ] ) ;
298-
299- let now = Duration :: from_millis ( now_millis ( ) ) ;
300- let mut validity = Validity {
301- not_before : Time :: UtcTime ( UtcTime :: from_unix_duration ( now) . map_err ( |e| e. to_string ( ) ) ?) ,
302- not_after : Time :: UtcTime (
303- UtcTime :: from_unix_duration (
304- now + Duration :: from_secs ( params. max_certificate_lifetime_secs as u64 ) ,
305- )
306- . map_err ( |e| e. to_string ( ) ) ?,
307- ) ,
308- } ;
342+ let issuer = build_issuer_rdn ( & params. log_id ) . map_err ( |e| e. to_string ( ) ) ?;
343+ let mut validity =
344+ build_validity ( now_millis ( ) , params. max_certificate_lifetime_secs as u64 ) . map_err ( |e| e. to_string ( ) ) ?;
309345
310346 let roots = load_roots ( env, name) . await ?;
311347 let ( pending_entry, found_root_idx) =
@@ -328,25 +364,7 @@ async fn add_entry(mut req: Request, env: &Env, name: &str) -> Result<Response>
328364
329365 // Get leaf and issuer DER for SCT validation
330366 let leaf_der = req. chain . first ( ) . ok_or ( "Chain is empty" ) ?;
331- let issuer_der: Vec < u8 > = if req. chain . len ( ) > 1 {
332- // Issuer is in the chain
333- req. chain [ 1 ] . clone ( )
334- } else {
335- // Single-cert chain: look up issuer (root) from roots pool
336- use der:: { Decode , Encode } ;
337- use x509_cert:: Certificate ;
338-
339- let leaf = Certificate :: from_der ( leaf_der)
340- . map_err ( |e| format ! ( "failed to parse leaf: {e}" ) ) ?;
341-
342- let issuer_dn = & leaf. tbs_certificate . issuer ;
343-
344- roots
345- . find_by_subject ( issuer_dn)
346- . ok_or_else ( || format ! ( "issuer not found in roots pool: {issuer_dn}" ) ) ?
347- . to_der ( )
348- . map_err ( |e| format ! ( "failed to encode issuer {e}" ) ) ?
349- } ;
367+ let issuer_der = resolve_issuer_for_sct ( & req. chain , roots) . map_err ( |e| e. to_string ( ) ) ?;
350368
351369 let validation_time_secs = now_millis ( ) / 1000 ;
352370
@@ -500,3 +518,56 @@ fn headers_from_http_metadata(meta: HttpMetadata) -> Headers {
500518 }
501519 h
502520}
521+
522+ #[ cfg( test) ]
523+ mod tests {
524+ use super :: * ;
525+ use der:: Encode ;
526+
527+ #[ test]
528+ fn test_build_issuer_rdn ( ) {
529+ let rdn = build_issuer_rdn ( "test-log-id" ) . unwrap ( ) ;
530+ assert_eq ! ( rdn. 0 . len( ) , 1 ) ;
531+
532+ let attr = rdn. 0 [ 0 ] . 0 . iter ( ) . next ( ) . unwrap ( ) ;
533+ assert_eq ! ( attr. oid, ID_RDNA_TRUSTANCHOR_ID ) ;
534+
535+ let encoded = attr. value . to_der ( ) . unwrap ( ) ;
536+ assert_eq ! ( encoded[ 0 ] , 0x0C ) ; // UTF8String tag
537+ }
538+
539+ #[ test]
540+ fn test_build_validity ( ) {
541+ let now_ms = 1_700_000_000_000_u64 ; // Nov 2023
542+ let lifetime_secs = 86400_u64 ; // 1 day
543+
544+ let validity = build_validity ( now_ms, lifetime_secs) . unwrap ( ) ;
545+
546+ assert_eq ! ( validity. not_before. to_unix_duration( ) . as_secs( ) , now_ms / 1000 ) ;
547+ assert_eq ! ( validity. not_after. to_unix_duration( ) . as_secs( ) , now_ms / 1000 + lifetime_secs) ;
548+ }
549+
550+ #[ test]
551+ fn test_resolve_issuer_empty_chain ( ) {
552+ let chain: Vec < Vec < u8 > > = vec ! [ ] ;
553+ let roots = x509_util:: CertPool :: new ( vec ! [ ] ) . unwrap ( ) ;
554+ assert ! ( resolve_issuer_for_sct( & chain, & roots) . is_err( ) ) ;
555+ }
556+
557+ #[ test]
558+ fn test_resolve_issuer_multi_cert_chain ( ) {
559+ let leaf = vec ! [ 1 , 2 , 3 ] ;
560+ let issuer = vec ! [ 4 , 5 , 6 ] ;
561+ let chain = vec ! [ leaf, issuer. clone( ) ] ;
562+ let roots = x509_util:: CertPool :: new ( vec ! [ ] ) . unwrap ( ) ;
563+
564+ assert_eq ! ( resolve_issuer_for_sct( & chain, & roots) . unwrap( ) , issuer) ;
565+ }
566+
567+ #[ test]
568+ fn test_resolve_issuer_invalid_der ( ) {
569+ let chain = vec ! [ vec![ 0xFF , 0xFF , 0xFF ] ] ;
570+ let roots = x509_util:: CertPool :: new ( vec ! [ ] ) . unwrap ( ) ;
571+ assert ! ( resolve_issuer_for_sct( & chain, & roots) . is_err( ) ) ;
572+ }
573+ }
0 commit comments