@@ -195,6 +195,108 @@ fn rate_limit_secs(err: &auths_sdk::workflows::log_submit::LogSubmitError) -> u6
195195 }
196196}
197197
198+ /// Re-export DSSE PAE from the SDK for use in CLI signing paths.
199+ pub use auths_sdk:: domains:: signing:: service:: dsse_pae;
200+
201+ /// Submit an attestation to a transparency log and return the JSON to embed.
202+ ///
203+ /// The `dsse_signature` is the signature over the DSSE PAE of the attestation,
204+ /// computed by the caller while the signing key is still available.
205+ ///
206+ /// Returns `None` if `allow_unlogged` is set or `--log` wasn't passed.
207+ fn submit_to_log (
208+ attestation_json : & str ,
209+ log : & Option < String > ,
210+ allow_unlogged : bool ,
211+ dsse_signature : Option < & [ u8 ] > ,
212+ ) -> Result < Option < serde_json:: Value > > {
213+ if allow_unlogged {
214+ eprintln ! (
215+ "WARNING: Signing without transparency log. \
216+ This artifact will not be verifiable against any log."
217+ ) ;
218+ return Ok ( None ) ;
219+ }
220+
221+ // If --log wasn't passed, skip silently (non-CI default behavior)
222+ if log. is_none ( ) {
223+ return Ok ( None ) ;
224+ }
225+
226+ let sig_bytes = dsse_signature
227+ . ok_or_else ( || anyhow:: anyhow!( "DSSE signature required for log submission" ) ) ?;
228+
229+ let attestation_value: serde_json:: Value = serde_json:: from_str ( attestation_json)
230+ . map_err ( |e| anyhow:: anyhow!( "Failed to parse attestation: {e}" ) ) ?;
231+
232+ // device_public_key may be a hex string or {"curve": "...", "key": "..."}
233+ let pk_hex = if let Some ( s) = attestation_value[ "device_public_key" ] . as_str ( ) {
234+ s. to_string ( )
235+ } else if let Some ( key_field) = attestation_value[ "device_public_key" ] [ "key" ] . as_str ( ) {
236+ key_field. to_string ( )
237+ } else {
238+ return Err ( anyhow:: anyhow!( "missing device_public_key" ) ) ;
239+ } ;
240+ let pk_bytes =
241+ hex:: decode ( & pk_hex) . map_err ( |e| anyhow:: anyhow!( "invalid public key hex: {e}" ) ) ?;
242+
243+ let rt = tokio:: runtime:: Runtime :: new ( )
244+ . map_err ( |e| anyhow:: anyhow!( "Failed to create async runtime: {e}" ) ) ?;
245+
246+ let log_client: std:: sync:: Arc < dyn auths_sdk:: ports:: TransparencyLog > = match log. as_deref ( ) {
247+ Some ( "sigstore-rekor" ) => std:: sync:: Arc :: new (
248+ auths_infra_rekor:: RekorClient :: public ( )
249+ . map_err ( |e| anyhow:: anyhow!( "Failed to create Rekor client: {e}" ) ) ?,
250+ ) ,
251+ Some ( other) => bail ! ( "Unknown log '{}'. Available: sigstore-rekor" , other) ,
252+ None => unreachable ! ( ) ,
253+ } ;
254+
255+ let submit = || {
256+ rt. block_on ( auths_sdk:: workflows:: log_submit:: submit_attestation_to_log (
257+ attestation_json. as_bytes ( ) ,
258+ & pk_bytes,
259+ sig_bytes,
260+ log_client. as_ref ( ) ,
261+ ) )
262+ } ;
263+
264+ let submission_result = match submit ( ) {
265+ Ok ( bundle) => Ok ( bundle) ,
266+ Err ( ref e) if is_rate_limited ( e) => {
267+ let secs = rate_limit_secs ( e) ;
268+ eprintln ! ( "Rate limited by transparency log. Retrying in {secs}s..." ) ;
269+ std:: thread:: sleep ( std:: time:: Duration :: from_secs ( secs) ) ;
270+ submit ( )
271+ }
272+ Err ( e) => Err ( e) ,
273+ } ;
274+
275+ match submission_result {
276+ Ok ( bundle) => {
277+ eprintln ! (
278+ " Logged to {} at index {}" ,
279+ bundle. log_id, bundle. leaf_index
280+ ) ;
281+ Ok ( Some ( serde_json:: to_value ( & bundle) . map_err ( |e| {
282+ anyhow:: anyhow!( "Failed to serialize: {e}" )
283+ } ) ?) )
284+ }
285+ Err ( e) => Err ( anyhow:: anyhow!( "Transparency log submission failed: {e}" ) ) ,
286+ }
287+ }
288+
289+ /// Merge transparency JSON into an attestation and return the final JSON string.
290+ fn merge_transparency ( attestation_json : & str , transparency : serde_json:: Value ) -> Result < String > {
291+ let mut attestation: serde_json:: Value = serde_json:: from_str ( attestation_json)
292+ . map_err ( |e| anyhow:: anyhow!( "Failed to re-parse attestation: {e}" ) ) ?;
293+ if let serde_json:: Value :: Object ( ref mut map) = attestation {
294+ map. insert ( "transparency" . to_string ( ) , transparency) ;
295+ }
296+ serde_json:: to_string_pretty ( & attestation)
297+ . map_err ( |e| anyhow:: anyhow!( "Failed to serialize attestation: {e}" ) )
298+ }
299+
198300/// Resolve the commit SHA from CLI flags.
199301fn resolve_commit_sha_from_flags (
200302 commit : Option < String > ,
@@ -290,93 +392,15 @@ pub fn handle_artifact(
290392 . map_err ( |e| anyhow:: anyhow!( "Ephemeral signing failed: {}" , e) ) ?;
291393
292394 // Submit to transparency log (unless --allow-unlogged)
293- let transparency_json = if allow_unlogged {
294- eprintln ! (
295- "WARNING: Signing without transparency log. \
296- This artifact will not be verifiable against any log."
297- ) ;
298- None
299- } else {
300- // Parse the attestation to extract public key and signature
301- let attestation_value: serde_json:: Value =
302- serde_json:: from_str ( & result. attestation_json )
303- . map_err ( |e| anyhow:: anyhow!( "Failed to parse attestation: {e}" ) ) ?;
304-
305- let identity_sig_hex = attestation_value[ "identity_signature" ]
306- . as_str ( )
307- . ok_or_else ( || anyhow:: anyhow!( "missing identity_signature" ) ) ?;
308- let sig_bytes = hex:: decode ( identity_sig_hex)
309- . map_err ( |e| anyhow:: anyhow!( "invalid signature hex: {e}" ) ) ?;
310-
311- let device_pk_hex = attestation_value[ "device_public_key" ]
312- . as_str ( )
313- . ok_or_else ( || anyhow:: anyhow!( "missing device_public_key" ) ) ?;
314- let pk_bytes = hex:: decode ( device_pk_hex)
315- . map_err ( |e| anyhow:: anyhow!( "invalid public key hex: {e}" ) ) ?;
316-
317- let rt = tokio:: runtime:: Runtime :: new ( )
318- . map_err ( |e| anyhow:: anyhow!( "Failed to create async runtime: {e}" ) ) ?;
319-
320- // Build the transparency log client
321- let log_client: std:: sync:: Arc < dyn auths_sdk:: ports:: TransparencyLog > =
322- match log. as_deref ( ) {
323- Some ( "sigstore-rekor" ) | None => std:: sync:: Arc :: new (
324- auths_infra_rekor:: RekorClient :: public ( ) . map_err ( |e| {
325- anyhow:: anyhow!( "Failed to create Rekor client: {e}" )
326- } ) ?,
327- ) ,
328- Some ( other) => {
329- bail ! ( "Unknown log '{}'. Available: sigstore-rekor" , other)
330- }
331- } ;
395+ let transparency_json = submit_to_log (
396+ & result. attestation_json ,
397+ & log,
398+ allow_unlogged,
399+ result. dsse_signature . as_deref ( ) ,
400+ ) ?;
332401
333- let submit = || {
334- rt. block_on ( auths_sdk:: workflows:: log_submit:: submit_attestation_to_log (
335- result. attestation_json . as_bytes ( ) ,
336- & pk_bytes,
337- & sig_bytes,
338- log_client. as_ref ( ) ,
339- ) )
340- } ;
341-
342- let submission_result = match submit ( ) {
343- Ok ( bundle) => Ok ( bundle) ,
344- Err ( ref e) if is_rate_limited ( e) => {
345- let secs = rate_limit_secs ( e) ;
346- eprintln ! ( "Rate limited by transparency log. Retrying in {secs}s..." ) ;
347- std:: thread:: sleep ( std:: time:: Duration :: from_secs ( secs) ) ;
348- submit ( )
349- }
350- Err ( e) => Err ( e) ,
351- } ;
352-
353- match submission_result {
354- Ok ( bundle) => {
355- eprintln ! (
356- " Logged to {} at index {}" ,
357- bundle. log_id, bundle. leaf_index
358- ) ;
359- Some (
360- serde_json:: to_value ( & bundle)
361- . map_err ( |e| anyhow:: anyhow!( "Failed to serialize: {e}" ) ) ?,
362- )
363- }
364- Err ( e) => {
365- return Err ( anyhow:: anyhow!( "Transparency log submission failed: {e}" ) ) ;
366- }
367- }
368- } ;
369-
370- // Build final .auths.json with optional transparency section
371402 let final_json = if let Some ( transparency) = transparency_json {
372- let mut attestation: serde_json:: Value =
373- serde_json:: from_str ( & result. attestation_json )
374- . map_err ( |e| anyhow:: anyhow!( "Failed to re-parse attestation: {e}" ) ) ?;
375- if let serde_json:: Value :: Object ( ref mut map) = attestation {
376- map. insert ( "transparency" . to_string ( ) , transparency) ;
377- }
378- serde_json:: to_string_pretty ( & attestation)
379- . map_err ( |e| anyhow:: anyhow!( "Failed to serialize final JSON: {e}" ) ) ?
403+ merge_transparency ( & result. attestation_json , transparency) ?
380404 } else {
381405 result. attestation_json . clone ( )
382406 } ;
@@ -424,6 +448,8 @@ pub fn handle_artifact(
424448 repo_opt,
425449 passphrase_provider,
426450 env_config,
451+ & log,
452+ allow_unlogged,
427453 )
428454 }
429455 }
@@ -465,6 +491,8 @@ pub fn handle_artifact(
465491 repo_opt. clone ( ) ,
466492 passphrase_provider,
467493 env_config,
494+ & None ,
495+ false ,
468496 ) ?;
469497 default_sig
470498 }
0 commit comments