use crate::context::AuthsContext;
use crate::ports::artifact::{ArtifactDigest, ArtifactMetadata, ArtifactSource};
use auths_core::crypto::signer as core_signer;
use auths_core::crypto::ssh::{self, SecureSeed};
use auths_core::signing::{PassphraseProvider, SecureSigner};
use auths_core::storage::keychain::{IdentityDID, KeyAlias, KeyStorage};
use auths_id::attestation::core::resign_attestation;
use auths_id::attestation::create::create_signed_attestation;
use auths_id::storage::git_refs::AttestationMetadata;
use auths_verifier::core::{ResourceId, SignerType};
use auths_verifier::types::CanonicalDid;
use chrono::{DateTime, Utc};
use sha2::{Digest, Sha256};
use std::collections::HashMap;
use std::path::Path;
use std::sync::Arc;
use zeroize::Zeroizing;
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum SigningError {
#[error("identity is frozen: {0}")]
IdentityFrozen(String),
#[error("key resolution failed: {0}")]
KeyResolution(String),
#[error("signing operation failed: {0}")]
SigningFailed(String),
#[error("invalid passphrase")]
InvalidPassphrase,
#[error("PEM encoding failed: {0}")]
PemEncoding(String),
#[error("agent unavailable: {0}")]
AgentUnavailable(String),
#[error("agent signing failed")]
AgentSigningFailed(#[source] crate::ports::agent::AgentSigningError),
#[error("passphrase exhausted after {attempts} attempt(s)")]
PassphraseExhausted {
attempts: usize,
},
#[error("keychain unavailable: {0}")]
KeychainUnavailable(String),
#[error("key decryption failed: {0}")]
KeyDecryptionFailed(String),
}
impl auths_core::error::AuthsErrorInfo for SigningError {
fn error_code(&self) -> &'static str {
match self {
Self::IdentityFrozen(_) => "AUTHS-E5901",
Self::KeyResolution(_) => "AUTHS-E5902",
Self::SigningFailed(_) => "AUTHS-E5903",
Self::InvalidPassphrase => "AUTHS-E5904",
Self::PemEncoding(_) => "AUTHS-E5905",
Self::AgentUnavailable(_) => "AUTHS-E5906",
Self::AgentSigningFailed(_) => "AUTHS-E5907",
Self::PassphraseExhausted { .. } => "AUTHS-E5908",
Self::KeychainUnavailable(_) => "AUTHS-E5909",
Self::KeyDecryptionFailed(_) => "AUTHS-E5910",
}
}
fn suggestion(&self) -> Option<&'static str> {
match self {
Self::IdentityFrozen(_) => Some("To unfreeze: auths emergency unfreeze"),
Self::KeyResolution(_) => Some("Run `auths key list` to check available keys"),
Self::SigningFailed(_) => Some(
"The signing operation failed; verify your key is accessible with `auths key list`",
),
Self::InvalidPassphrase => Some("Check your passphrase and try again"),
Self::PemEncoding(_) => {
Some("Failed to encode the key in PEM format; the key material may be corrupted")
}
Self::AgentUnavailable(_) => Some("Start the agent with `auths agent start`"),
Self::AgentSigningFailed(_) => Some("Check agent logs with `auths agent status`"),
Self::PassphraseExhausted { .. } => Some(
"The passphrase you entered is incorrect (tried 3 times). Verify it matches what you set during init, or try: auths key export --key-alias <alias> --format pub",
),
Self::KeychainUnavailable(_) => Some("Run `auths doctor` to diagnose keychain issues"),
Self::KeyDecryptionFailed(_) => Some("Check your passphrase and try again"),
}
}
}
pub struct SigningConfig {
pub namespace: String,
}
pub fn validate_freeze_state(
repo_path: &Path,
now: chrono::DateTime<chrono::Utc>,
) -> Result<(), SigningError> {
use auths_id::freeze::load_active_freeze;
if let Some(state) = load_active_freeze(repo_path, now)
.map_err(|e| SigningError::IdentityFrozen(e.to_string()))?
{
return Err(SigningError::IdentityFrozen(format!(
"frozen until {}. Remaining: {}. To unfreeze: auths emergency unfreeze",
state.frozen_until.format("%Y-%m-%d %H:%M UTC"),
state.expires_description(now),
)));
}
Ok(())
}
pub fn construct_signature_payload(data: &[u8], namespace: &str) -> Result<Vec<u8>, SigningError> {
ssh::construct_sshsig_signed_data(data, namespace)
.map_err(|e| SigningError::SigningFailed(e.to_string()))
}
pub fn sign_with_seed(
seed: &SecureSeed,
data: &[u8],
namespace: &str,
curve: auths_crypto::CurveType,
) -> Result<String, SigningError> {
ssh::create_sshsig(seed, data, namespace, curve)
.map_err(|e| SigningError::PemEncoding(e.to_string()))
}
pub enum SigningKeyMaterial {
Alias(KeyAlias),
Direct(SecureSeed),
}
pub struct ArtifactSigningParams {
pub artifact: Arc<dyn ArtifactSource>,
pub identity_key: Option<SigningKeyMaterial>,
pub device_key: SigningKeyMaterial,
pub expires_in: Option<u64>,
pub note: Option<String>,
pub commit_sha: Option<String>,
}
#[derive(Debug)]
pub struct ArtifactSigningResult {
pub attestation_json: String,
pub rid: ResourceId,
pub digest: String,
pub dsse_signature: Option<Vec<u8>>,
}
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum ArtifactSigningError {
#[error("identity not found in configured identity storage")]
IdentityNotFound,
#[error("key resolution failed: {0}")]
KeyResolutionFailed(String),
#[error("key decryption failed: {0}")]
KeyDecryptionFailed(String),
#[error("digest computation failed: {0}")]
DigestFailed(String),
#[error("attestation creation failed: {0}")]
AttestationFailed(String),
#[error("attestation re-signing failed: {0}")]
ResignFailed(String),
#[error("invalid commit SHA: {0} (expected 40 or 64 hex characters)")]
InvalidCommitSha(String),
}
impl auths_core::error::AuthsErrorInfo for ArtifactSigningError {
fn error_code(&self) -> &'static str {
match self {
Self::IdentityNotFound => "AUTHS-E5850",
Self::KeyResolutionFailed(_) => "AUTHS-E5851",
Self::KeyDecryptionFailed(_) => "AUTHS-E5852",
Self::DigestFailed(_) => "AUTHS-E5853",
Self::AttestationFailed(_) => "AUTHS-E5854",
Self::ResignFailed(_) => "AUTHS-E5855",
Self::InvalidCommitSha(_) => "AUTHS-E5856",
}
}
fn suggestion(&self) -> Option<&'static str> {
match self {
Self::IdentityNotFound => {
Some("Run `auths init` to create an identity, or `auths key import` to restore one")
}
Self::KeyResolutionFailed(_) => {
Some("Run `auths status` to see available device aliases")
}
Self::KeyDecryptionFailed(_) => Some("Check your passphrase and try again"),
Self::DigestFailed(_) => Some("Verify the file exists and is readable"),
Self::AttestationFailed(_) => Some("Check identity storage with `auths status`"),
Self::ResignFailed(_) => {
Some("Verify your device key is accessible with `auths status`")
}
Self::InvalidCommitSha(_) => {
Some("Provide a full SHA-1 (40 hex chars) or SHA-256 (64 hex chars) commit hash")
}
}
}
}
struct SeedMapSigner {
seeds: HashMap<String, (SecureSeed, auths_crypto::CurveType)>,
}
impl SecureSigner for SeedMapSigner {
fn sign_with_alias(
&self,
alias: &auths_core::storage::keychain::KeyAlias,
_passphrase_provider: &dyn PassphraseProvider,
message: &[u8],
) -> Result<Vec<u8>, auths_core::AgentError> {
let (seed, curve) = self
.seeds
.get(alias.as_str())
.ok_or(auths_core::AgentError::KeyNotFound)?;
let typed = match curve {
auths_crypto::CurveType::Ed25519 => auths_crypto::TypedSeed::Ed25519(*seed.as_bytes()),
auths_crypto::CurveType::P256 => auths_crypto::TypedSeed::P256(*seed.as_bytes()),
};
auths_crypto::typed_sign(&typed, message)
.map_err(|e| auths_core::AgentError::CryptoError(e.to_string()))
}
fn sign_for_identity(
&self,
_identity_did: &IdentityDID,
_passphrase_provider: &dyn PassphraseProvider,
_message: &[u8],
) -> Result<Vec<u8>, auths_core::AgentError> {
Err(auths_core::AgentError::KeyNotFound)
}
}
struct ResolvedKey {
alias: KeyAlias,
seed: Option<SecureSeed>,
public_key_bytes: Vec<u8>,
curve: auths_crypto::CurveType,
is_hardware: bool,
}
fn resolve_optional_key(
material: Option<&SigningKeyMaterial>,
synthetic_alias: &'static str,
keychain: &(dyn KeyStorage + Send + Sync),
passphrase_provider: &dyn PassphraseProvider,
passphrase_prompt: &str,
) -> Result<Option<ResolvedKey>, ArtifactSigningError> {
match material {
None => Ok(None),
Some(SigningKeyMaterial::Alias(alias)) => {
if keychain.is_hardware_backend() {
let (pubkey, curve) = auths_core::storage::keychain::extract_public_key_bytes(
keychain,
alias,
passphrase_provider,
)
.map_err(|e| ArtifactSigningError::KeyResolutionFailed(e.to_string()))?;
return Ok(Some(ResolvedKey {
alias: alias.clone(),
seed: None,
public_key_bytes: pubkey,
curve,
is_hardware: true,
}));
}
let (_, _role, encrypted) = keychain
.load_key(alias)
.map_err(|e| ArtifactSigningError::KeyResolutionFailed(e.to_string()))?;
let passphrase = passphrase_provider
.get_passphrase(passphrase_prompt)
.map_err(|e| ArtifactSigningError::KeyDecryptionFailed(e.to_string()))?;
debug_assert!(
!keychain.is_hardware_backend(),
"SE keys must never reach decrypt_keypair"
);
let pkcs8 = core_signer::decrypt_keypair(&encrypted, &passphrase)
.map_err(|e| ArtifactSigningError::KeyDecryptionFailed(e.to_string()))?;
let (seed, pubkey, curve) = core_signer::load_seed_and_pubkey(&pkcs8)
.map_err(|e| ArtifactSigningError::KeyDecryptionFailed(e.to_string()))?;
Ok(Some(ResolvedKey {
alias: alias.clone(),
seed: Some(seed),
public_key_bytes: pubkey.to_vec(),
curve,
is_hardware: false,
}))
}
Some(SigningKeyMaterial::Direct(seed)) => {
let typed = auths_crypto::TypedSeed::Ed25519(*seed.as_bytes());
let pubkey = auths_crypto::typed_public_key(&typed)
.map_err(|e| ArtifactSigningError::KeyDecryptionFailed(e.to_string()))?;
Ok(Some(ResolvedKey {
alias: KeyAlias::new_unchecked(synthetic_alias),
seed: Some(SecureSeed::new(*seed.as_bytes())),
public_key_bytes: pubkey,
curve: auths_crypto::CurveType::Ed25519,
is_hardware: false,
}))
}
}
}
fn resolve_required_key(
material: &SigningKeyMaterial,
synthetic_alias: &'static str,
keychain: &(dyn KeyStorage + Send + Sync),
passphrase_provider: &dyn PassphraseProvider,
passphrase_prompt: &str,
) -> Result<ResolvedKey, ArtifactSigningError> {
resolve_optional_key(
Some(material),
synthetic_alias,
keychain,
passphrase_provider,
passphrase_prompt,
)
.map(|opt| {
opt.ok_or(ArtifactSigningError::KeyDecryptionFailed(
"expected key material but got None".into(),
))
})?
}
pub fn validate_commit_sha(sha: &str) -> Result<String, ArtifactSigningError> {
let normalized = sha.to_ascii_lowercase();
let len = normalized.len();
if (len != 40 && len != 64) || !normalized.bytes().all(|b| b.is_ascii_hexdigit()) {
return Err(ArtifactSigningError::InvalidCommitSha(sha.to_string()));
}
Ok(normalized)
}
pub fn sign_artifact(
params: ArtifactSigningParams,
ctx: &AuthsContext,
) -> Result<ArtifactSigningResult, ArtifactSigningError> {
let managed = ctx
.identity_storage
.load_identity()
.map_err(|_| ArtifactSigningError::IdentityNotFound)?;
let keychain = ctx.key_storage.as_ref();
let passphrase_provider = ctx.passphrase_provider.as_ref();
let identity_resolved = resolve_optional_key(
params.identity_key.as_ref(),
"__artifact_identity__",
keychain,
passphrase_provider,
"Enter passphrase for identity key:",
)?;
let device_resolved = resolve_required_key(
¶ms.device_key,
"__artifact_device__",
keychain,
passphrase_provider,
"Enter passphrase for device key:",
)?;
let mut seeds: HashMap<String, (SecureSeed, auths_crypto::CurveType)> = HashMap::new();
let identity_alias: Option<KeyAlias> = identity_resolved.map(|r| {
let alias = r.alias.clone();
let curve = r.curve;
if let Some(seed) = r.seed {
seeds.insert(r.alias.into_inner(), (seed, curve));
}
alias
});
let device_alias = device_resolved.alias.clone();
let device_is_hardware = device_resolved.is_hardware;
let device_curve = device_resolved.curve;
if let Some(seed) = device_resolved.seed {
seeds.insert(device_resolved.alias.into_inner(), (seed, device_curve));
}
let device_pk_bytes = device_resolved.public_key_bytes;
let device_did = CanonicalDid::from_public_key_did_key(&device_pk_bytes, device_resolved.curve);
let artifact_meta = params
.artifact
.metadata()
.map_err(|e| ArtifactSigningError::DigestFailed(e.to_string()))?;
let rid = ResourceId::new(format!("sha256:{}", artifact_meta.digest.hex));
let now = ctx.clock.now();
let meta = AttestationMetadata {
timestamp: Some(now),
expires_at: params
.expires_in
.map(|s| now + chrono::Duration::seconds(s as i64)),
note: params.note,
};
let payload = serde_json::to_value(&artifact_meta)
.map_err(|e| ArtifactSigningError::AttestationFailed(e.to_string()))?;
let validated_commit_sha = params
.commit_sha
.map(|sha| validate_commit_sha(&sha))
.transpose()?;
let attestation_json = create_and_sign_attestation(
ctx,
seeds,
device_is_hardware,
now,
&rid,
&managed.controller_did,
&device_did,
&device_pk_bytes,
device_curve,
payload,
&meta,
identity_alias.as_ref(),
&device_alias,
validated_commit_sha,
)?;
if let Some(ref alias) = identity_alias {
anchor_artifact_attestation(ctx, &managed.controller_did, alias, &attestation_json, now)?;
}
Ok(ArtifactSigningResult {
attestation_json,
rid,
digest: artifact_meta.digest.hex,
dsse_signature: None,
})
}
fn anchor_artifact_attestation(
ctx: &AuthsContext,
controller_did: &auths_core::storage::keychain::IdentityDID,
alias: &KeyAlias,
attestation_json: &str,
now: DateTime<Utc>,
) -> Result<(), ArtifactSigningError> {
let prefix = auths_id::keri::parse_did_keri(controller_did.as_str())
.map_err(|e| ArtifactSigningError::AttestationFailed(e.to_string()))?;
let att: auths_verifier::core::Attestation = serde_json::from_str(attestation_json)
.map_err(|e| ArtifactSigningError::AttestationFailed(e.to_string()))?;
let storage_signer = auths_core::signing::StorageSigner::new(Arc::clone(&ctx.key_storage));
let mut batch = auths_id::storage::registry::backend::AtomicWriteBatch::new();
batch.stage_attestation(att.clone());
auths_id::keri::anchor_and_persist_via_backend(
ctx.registry.as_ref(),
&storage_signer,
alias,
ctx.passphrase_provider.as_ref(),
&prefix,
&att,
&mut batch,
&ctx.witness_params(),
now,
)
.map_err(|e| ArtifactSigningError::AttestationFailed(e.to_string()))?;
Ok(())
}
#[allow(clippy::too_many_arguments)]
fn create_and_sign_attestation(
ctx: &AuthsContext,
seeds: HashMap<String, (SecureSeed, auths_crypto::CurveType)>,
device_is_hardware: bool,
now: DateTime<Utc>,
rid: &ResourceId,
controller_did: &IdentityDID,
subject: &CanonicalDid,
device_pk_bytes: &[u8],
device_curve: auths_crypto::CurveType,
payload: serde_json::Value,
meta: &AttestationMetadata,
identity_alias: Option<&KeyAlias>,
device_alias: &KeyAlias,
commit_sha: Option<String>,
) -> Result<String, ArtifactSigningError> {
let seed_signer = SeedMapSigner { seeds };
let storage_signer = auths_core::signing::StorageSigner::new(Arc::clone(&ctx.key_storage));
let signer: &dyn SecureSigner = if device_is_hardware {
&storage_signer
} else {
&seed_signer
};
let noop_provider = auths_core::PrefilledPassphraseProvider::new("");
let mut attestation = create_signed_attestation(
now,
auths_id::attestation::create::AttestationInput {
rid: rid.as_str(),
identity_did: controller_did,
subject,
device_public_key: device_pk_bytes,
device_curve,
payload: Some(payload),
meta,
identity_alias,
device_alias: Some(device_alias),
delegated_by: None,
commit_sha,
signer_type: None,
},
signer,
&noop_provider,
)
.map_err(|e| ArtifactSigningError::AttestationFailed(e.to_string()))?;
resign_attestation(
&mut attestation,
signer,
&noop_provider,
identity_alias,
device_alias,
)
.map_err(|e| ArtifactSigningError::ResignFailed(e.to_string()))?;
serde_json::to_string_pretty(&attestation)
.map_err(|e| ArtifactSigningError::AttestationFailed(e.to_string()))
}
pub fn sign_artifact_ephemeral(
now: DateTime<Utc>,
data: &[u8],
artifact_name: Option<String>,
commit_sha: String,
expires_in: Option<u64>,
note: Option<String>,
ci_env: Option<serde_json::Value>,
) -> Result<ArtifactSigningResult, ArtifactSigningError> {
let mut seed_bytes = Zeroizing::new([0u8; 32]);
ring::rand::SecureRandom::fill(&ring::rand::SystemRandom::new(), seed_bytes.as_mut())
.map_err(|_| ArtifactSigningError::AttestationFailed("RNG failure".into()))?;
let typed_seed = auths_crypto::TypedSeed::P256(*seed_bytes);
let pubkey_vec = auths_crypto::typed_public_key(&typed_seed)
.map_err(|e| ArtifactSigningError::AttestationFailed(e.to_string()))?;
let device_did =
CanonicalDid::from_public_key_did_key(&pubkey_vec, auths_crypto::CurveType::P256);
#[allow(clippy::disallowed_methods)]
let identity_did = IdentityDID::new_unchecked(device_did.as_str());
let digest_hex = hex::encode(Sha256::digest(data));
let artifact_meta = ArtifactMetadata {
artifact_type: "file".to_string(),
digest: ArtifactDigest {
algorithm: "sha256".to_string(),
hex: digest_hex,
},
name: artifact_name,
size: Some(data.len() as u64),
};
let mut payload_value = serde_json::to_value(&artifact_meta)
.map_err(|e| ArtifactSigningError::AttestationFailed(e.to_string()))?;
if let Some(env) = ci_env
&& let serde_json::Value::Object(ref mut map) = payload_value
{
map.insert("ci_environment".to_string(), env);
}
let rid = ResourceId::new(format!("sha256:{}", artifact_meta.digest.hex));
let meta = AttestationMetadata {
timestamp: Some(now),
expires_at: expires_in.map(|s| now + chrono::Duration::seconds(s as i64)),
note,
};
let validated_sha = validate_commit_sha(&commit_sha)?;
let identity_alias = KeyAlias::new_unchecked("__ephemeral_identity__");
let device_alias = KeyAlias::new_unchecked("__ephemeral_device__");
let mut seeds: HashMap<String, (SecureSeed, auths_crypto::CurveType)> = HashMap::new();
seeds.insert(
identity_alias.as_str().to_string(),
(SecureSeed::new(*seed_bytes), auths_crypto::CurveType::P256),
);
seeds.insert(
device_alias.as_str().to_string(),
(SecureSeed::new(*seed_bytes), auths_crypto::CurveType::P256),
);
let signer = SeedMapSigner { seeds };
let noop_provider = auths_core::PrefilledPassphraseProvider::new("");
let attestation = create_signed_attestation(
now,
auths_id::attestation::create::AttestationInput {
rid: rid.as_str(),
identity_did: &identity_did,
subject: &device_did,
device_public_key: &pubkey_vec,
device_curve: auths_crypto::CurveType::P256,
payload: Some(payload_value),
meta: &meta,
identity_alias: Some(&identity_alias),
device_alias: Some(&device_alias),
delegated_by: None,
commit_sha: Some(validated_sha),
signer_type: Some(SignerType::Workload),
},
&signer,
&noop_provider,
)
.map_err(|e| ArtifactSigningError::AttestationFailed(e.to_string()))?;
let attestation_json = serde_json::to_string_pretty(&attestation)
.map_err(|e| ArtifactSigningError::AttestationFailed(e.to_string()))?;
let pae = dsse_pae("application/vnd.auths+json", attestation_json.as_bytes());
let dsse_sig = auths_crypto::typed_sign(&typed_seed, &pae)
.map_err(|e| ArtifactSigningError::AttestationFailed(format!("DSSE sign: {e}")))?;
Ok(ArtifactSigningResult {
attestation_json,
rid,
digest: artifact_meta.digest.hex,
dsse_signature: Some(dsse_sig),
})
}
pub fn sign_artifact_raw(
now: DateTime<Utc>,
seed: &SecureSeed,
identity_did: &IdentityDID,
data: &[u8],
expires_in: Option<u64>,
note: Option<String>,
commit_sha: Option<String>,
) -> Result<ArtifactSigningResult, ArtifactSigningError> {
let curve = auths_crypto::CurveType::default();
let typed = match curve {
auths_crypto::CurveType::Ed25519 => auths_crypto::TypedSeed::Ed25519(*seed.as_bytes()),
auths_crypto::CurveType::P256 => auths_crypto::TypedSeed::P256(*seed.as_bytes()),
};
let pubkey = auths_crypto::typed_public_key(&typed)
.map_err(|e| ArtifactSigningError::AttestationFailed(e.to_string()))?;
let device_did = CanonicalDid::from_public_key_did_key(&pubkey, curve);
let digest_hex = hex::encode(Sha256::digest(data));
let artifact_meta = ArtifactMetadata {
artifact_type: "bytes".to_string(),
digest: ArtifactDigest {
algorithm: "sha256".to_string(),
hex: digest_hex,
},
name: None,
size: Some(data.len() as u64),
};
let rid = ResourceId::new(format!("sha256:{}", artifact_meta.digest.hex));
let meta = AttestationMetadata {
timestamp: Some(now),
expires_at: expires_in.map(|s| now + chrono::Duration::seconds(s as i64)),
note,
};
let payload = serde_json::to_value(&artifact_meta)
.map_err(|e| ArtifactSigningError::AttestationFailed(e.to_string()))?;
let identity_alias = KeyAlias::new_unchecked("__raw_identity__");
let device_alias = KeyAlias::new_unchecked("__raw_device__");
let mut seeds: HashMap<String, (SecureSeed, auths_crypto::CurveType)> = HashMap::new();
seeds.insert(
identity_alias.as_str().to_string(),
(SecureSeed::new(*seed.as_bytes()), curve),
);
seeds.insert(
device_alias.as_str().to_string(),
(SecureSeed::new(*seed.as_bytes()), curve),
);
let signer = SeedMapSigner { seeds };
let noop_provider = auths_core::PrefilledPassphraseProvider::new("");
let validated_commit_sha = commit_sha
.map(|sha| validate_commit_sha(&sha))
.transpose()?;
let attestation = create_signed_attestation(
now,
auths_id::attestation::create::AttestationInput {
rid: rid.as_str(),
identity_did,
subject: &device_did,
device_public_key: &pubkey,
device_curve: curve,
payload: Some(payload),
meta: &meta,
identity_alias: Some(&identity_alias),
device_alias: Some(&device_alias),
delegated_by: None,
commit_sha: validated_commit_sha,
signer_type: None,
},
&signer,
&noop_provider,
)
.map_err(|e| ArtifactSigningError::AttestationFailed(e.to_string()))?;
let attestation_json = serde_json::to_string_pretty(&attestation)
.map_err(|e| ArtifactSigningError::AttestationFailed(e.to_string()))?;
Ok(ArtifactSigningResult {
attestation_json,
rid,
digest: artifact_meta.digest.hex,
dsse_signature: None,
})
}
pub fn dsse_pae(payload_type: &str, payload: &[u8]) -> Vec<u8> {
let header = format!(
"DSSEv1 {} {} {} ",
payload_type.len(),
payload_type,
payload.len()
);
let mut result = Vec::with_capacity(header.len() + payload.len());
result.extend_from_slice(header.as_bytes());
result.extend_from_slice(payload);
result
}