44//! Agent communication and passphrase prompting remain in the CLI.
55
66use crate :: context:: AuthsContext ;
7- use crate :: ports:: artifact:: ArtifactSource ;
7+ use crate :: ports:: artifact:: { ArtifactDigest , ArtifactMetadata , ArtifactSource } ;
88use auths_core:: crypto:: ssh:: { self , SecureSeed } ;
99use auths_core:: crypto:: { provider_bridge, signer as core_signer} ;
1010use auths_core:: signing:: { PassphraseProvider , SecureSigner } ;
@@ -14,6 +14,8 @@ use auths_id::attestation::create::create_signed_attestation;
1414use auths_id:: storage:: git_refs:: AttestationMetadata ;
1515use auths_verifier:: core:: { Capability , ResourceId } ;
1616use auths_verifier:: types:: DeviceDID ;
17+ use chrono:: { DateTime , Utc } ;
18+ use sha2:: { Digest , Sha256 } ;
1719use std:: collections:: HashMap ;
1820use std:: path:: Path ;
1921use std:: sync:: Arc ;
@@ -512,3 +514,100 @@ pub fn sign_artifact(
512514 digest : artifact_meta. digest . hex ,
513515 } )
514516}
517+
518+ /// Signs artifact bytes with a raw Ed25519 seed, bypassing keychain and identity storage.
519+ ///
520+ /// This is the raw-key equivalent of [`sign_artifact`]. It does not require an
521+ /// [`AuthsContext`] or any filesystem/keychain access. The same seed is used for
522+ /// both identity and device signing roles.
523+ ///
524+ /// Args:
525+ /// * `now` - Current UTC time (injected per clock pattern).
526+ /// * `seed` - Ed25519 32-byte seed.
527+ /// * `identity_did` - Parsed identity DID (must be `did:keri:` — caller validates via `IdentityDID::parse()`).
528+ /// * `data` - Raw artifact bytes to sign.
529+ /// * `expires_in` - Optional TTL in seconds.
530+ /// * `note` - Optional attestation note.
531+ ///
532+ /// Usage:
533+ /// ```ignore
534+ /// let did = IdentityDID::parse("did:keri:E...")?;
535+ /// let result = sign_artifact_raw(Utc::now(), &seed, &did, b"payload", None, None)?;
536+ /// ```
537+ pub fn sign_artifact_raw (
538+ now : DateTime < Utc > ,
539+ seed : & SecureSeed ,
540+ identity_did : & IdentityDID ,
541+ data : & [ u8 ] ,
542+ expires_in : Option < u64 > ,
543+ note : Option < String > ,
544+ ) -> Result < ArtifactSigningResult , ArtifactSigningError > {
545+ let pubkey = provider_bridge:: ed25519_public_key_from_seed_sync ( seed)
546+ . map_err ( |e| ArtifactSigningError :: AttestationFailed ( e. to_string ( ) ) ) ?;
547+
548+ let device_did = DeviceDID :: from_ed25519 ( & pubkey) ;
549+
550+ let digest_hex = hex:: encode ( Sha256 :: digest ( data) ) ;
551+ let artifact_meta = ArtifactMetadata {
552+ artifact_type : "bytes" . to_string ( ) ,
553+ digest : ArtifactDigest {
554+ algorithm : "sha256" . to_string ( ) ,
555+ hex : digest_hex,
556+ } ,
557+ name : None ,
558+ size : Some ( data. len ( ) as u64 ) ,
559+ } ;
560+
561+ let rid = ResourceId :: new ( format ! ( "sha256:{}" , artifact_meta. digest. hex) ) ;
562+ let meta = AttestationMetadata {
563+ timestamp : Some ( now) ,
564+ expires_at : expires_in. map ( |s| now + chrono:: Duration :: seconds ( s as i64 ) ) ,
565+ note,
566+ } ;
567+
568+ let payload = serde_json:: to_value ( & artifact_meta)
569+ . map_err ( |e| ArtifactSigningError :: AttestationFailed ( e. to_string ( ) ) ) ?;
570+
571+ let identity_alias = KeyAlias :: new_unchecked ( "__raw_identity__" ) ;
572+ let device_alias = KeyAlias :: new_unchecked ( "__raw_device__" ) ;
573+
574+ let mut seeds: HashMap < String , SecureSeed > = HashMap :: new ( ) ;
575+ seeds. insert (
576+ identity_alias. as_str ( ) . to_string ( ) ,
577+ SecureSeed :: new ( * seed. as_bytes ( ) ) ,
578+ ) ;
579+ seeds. insert (
580+ device_alias. as_str ( ) . to_string ( ) ,
581+ SecureSeed :: new ( * seed. as_bytes ( ) ) ,
582+ ) ;
583+ let signer = SeedMapSigner { seeds } ;
584+ // Seeds are already resolved — passphrase provider will not be called.
585+ let noop_provider = auths_core:: PrefilledPassphraseProvider :: new ( "" ) ;
586+
587+ let attestation = create_signed_attestation (
588+ now,
589+ & rid,
590+ identity_did,
591+ & device_did,
592+ & pubkey,
593+ Some ( payload) ,
594+ & meta,
595+ & signer,
596+ & noop_provider,
597+ Some ( & identity_alias) ,
598+ Some ( & device_alias) ,
599+ vec ! [ Capability :: sign_release( ) ] ,
600+ None ,
601+ None ,
602+ )
603+ . map_err ( |e| ArtifactSigningError :: AttestationFailed ( e. to_string ( ) ) ) ?;
604+
605+ let attestation_json = serde_json:: to_string_pretty ( & attestation)
606+ . map_err ( |e| ArtifactSigningError :: AttestationFailed ( e. to_string ( ) ) ) ?;
607+
608+ Ok ( ArtifactSigningResult {
609+ attestation_json,
610+ rid,
611+ digest : artifact_meta. digest . hex ,
612+ } )
613+ }
0 commit comments