auths_verifier/
commit_error.rs1use thiserror::Error;
4
5use crate::error::AuthsErrorInfo;
6
7#[derive(Error, Debug)]
18pub enum CommitVerificationError {
19 #[error("commit is unsigned")]
21 UnsignedCommit,
22
23 #[error("GPG signatures are not verified by Auths — use did:keri trailers via `auths init`")]
25 GpgNotSupported,
26
27 #[error("SSHSIG parse failed: {0}")]
29 SshSigParseFailed(String),
30
31 #[error("unsupported SSH key type: {found}")]
33 UnsupportedKeyType {
34 found: String,
36 },
37
38 #[error("namespace mismatch: expected \"{expected}\", found \"{found}\"")]
40 NamespaceMismatch {
41 expected: String,
43 found: String,
45 },
46
47 #[error("unsupported hash algorithm: {0}")]
49 HashAlgorithmUnsupported(String),
50
51 #[error("signature verification failed")]
53 SignatureInvalid,
54
55 #[error("signer identity is not trusted (no matching pinned root)")]
57 UnknownSigner,
58
59 #[error("commit parse failed: {0}")]
61 CommitParseFailed(String),
62}
63
64impl AuthsErrorInfo for CommitVerificationError {
65 fn error_code(&self) -> &'static str {
66 match self {
67 Self::UnsignedCommit => "AUTHS-E2101",
68 Self::GpgNotSupported => "AUTHS-E2102",
69 Self::SshSigParseFailed(_) => "AUTHS-E2103",
70 Self::UnsupportedKeyType { .. } => "AUTHS-E2104",
71 Self::NamespaceMismatch { .. } => "AUTHS-E2105",
72 Self::HashAlgorithmUnsupported(_) => "AUTHS-E2106",
73 Self::SignatureInvalid => "AUTHS-E2107",
74 Self::UnknownSigner => "AUTHS-E2108",
75 Self::CommitParseFailed(_) => "AUTHS-E2109",
76 }
77 }
78
79 fn suggestion(&self) -> Option<&'static str> {
80 match self {
81 Self::UnsignedCommit => Some(
82 "This commit has no Auths-Id/Auths-Device trailer. Run `auths init` so the \
83 prepare-commit-msg hook signs future commits, or backfill with `auths sign <ref>`.",
84 ),
85 Self::GpgNotSupported => Some(
86 "Auths verifies its own did:keri commit trailers, not GPG or SSH signatures. \
87 Run `auths init` to enable Auths signing.",
88 ),
89 Self::UnsupportedKeyType { .. } => {
90 Some("Use an Ed25519 or ECDSA P-256 SSH key for signing")
91 }
92 Self::UnknownSigner => Some(
93 "The signer's identity is not trusted here. Pin it with `auths trust pin --did <did>`, \
94 or add it to .auths/roots.",
95 ),
96 Self::SshSigParseFailed(_) => Some(
97 "The SSH signature could not be parsed; verify the commit was signed correctly",
98 ),
99 Self::NamespaceMismatch { .. } => Some(
100 "The signature namespace doesn't match; ensure git config gpg.ssh.defaultKeyCommand is set correctly",
101 ),
102 Self::HashAlgorithmUnsupported(_) => {
103 Some("Use SHA-256 or SHA-512 hash algorithm for signing")
104 }
105 Self::SignatureInvalid => Some(
106 "The commit signature does not match the signed data; the commit may have been modified after signing",
107 ),
108 Self::CommitParseFailed(_) => Some(
109 "The Git commit object is malformed; check repository integrity with `git fsck`",
110 ),
111 }
112 }
113}
114
115#[cfg(test)]
116mod tests {
117 use super::*;
118
119 #[test]
120 fn retired_advice_is_gone() {
121 let retired = ["git commit -S", "gpg.format", "allowed signers"];
125 for err in [
126 CommitVerificationError::UnsignedCommit,
127 CommitVerificationError::GpgNotSupported,
128 CommitVerificationError::UnknownSigner,
129 ] {
130 let suggestion = err.suggestion().expect("headline verdicts carry advice");
131 for phrase in retired {
132 assert!(
133 !suggestion.contains(phrase),
134 "{} still advises `{phrase}`: {suggestion}",
135 err.error_code()
136 );
137 }
138 assert!(
139 suggestion.contains("auths "),
140 "{} must speak the KEL-native flow: {suggestion}",
141 err.error_code()
142 );
143 }
144 }
145}