Implement SSH identity authentication

This commit is contained in:
hugo committed 2026-08-25 19:52:47 +02:00
1 parent f636f3b551
commit 5dbda4bbd2
14 files changed
+1964 -60

No files matched your search

+171 -1
View File
@@ -24,7 +24,7 @@ use sha1::{Digest as _, Sha1};
use zeroize::Zeroize as _;
use crate::{
config::{ApplicationId, GitRemote, RemoteEndpoint, RemoteTransport, ServerId},
config::{ApplicationId, GitRemote, RemoteEndpoint, RemoteTransport, ServerId, SshFingerprint},
crypto::{KeyHandle, KeyStore, SecretProvider},
mutation::{TreeCommit, TreeCommitError, TreeCommitter},
recipient::{PolicyCommit, PolicyCommitError, PolicyCommitter},
@@ -364,6 +364,67 @@ impl GitDiffEntry {
}
}
#[derive(Clone, Eq, PartialEq)]
pub struct SshHostKey {
host: String,
port: u16,
algorithm: String,
fingerprint: SshFingerprint,
encoded: String,
}
impl SshHostKey {
#[cfg(feature = "ssh")]
pub(crate) fn new(
host: String,
port: u16,
algorithm: String,
fingerprint: SshFingerprint,
encoded: String,
) -> Self {
Self {
host,
port,
algorithm,
fingerprint,
encoded,
}
}
pub fn host(&self) -> &str {
&self.host
}
pub const fn port(&self) -> u16 {
self.port
}
pub fn algorithm(&self) -> &str {
&self.algorithm
}
pub const fn fingerprint(&self) -> &SshFingerprint {
&self.fingerprint
}
#[cfg(feature = "ssh")]
pub(crate) fn encoded(&self) -> &str {
&self.encoded
}
}
impl fmt::Debug for SshHostKey {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter
.debug_struct("SshHostKey")
.field("host", &self.host)
.field("port", &self.port)
.field("algorithm", &self.algorithm)
.field("fingerprint", &self.fingerprint)
.finish_non_exhaustive()
}
}
#[derive(Debug, Clone, Eq, PartialEq)]
pub enum GitError {
NotRepository,
@@ -390,6 +451,44 @@ pub enum GitError {
RemoteNotFound {
name: String,
},
SshAuthenticationNotConfigured,
SshIdentityMissing {
path: PathBuf,
},
SshIdentityInvalid {
path: PathBuf,
},
SshUnsupportedAlgorithm {
algorithm: String,
},
SshKeyPassphraseUnavailable {
fingerprint: SshFingerprint,
},
SshKeyPassphraseDenied {
fingerprint: SshFingerprint,
},
SshKeyPassphraseCancelled {
fingerprint: SshFingerprint,
},
SshKeyPassphraseRejected {
fingerprint: SshFingerprint,
},
SshAgentUnavailable,
SshAgentIdentityMissing {
fingerprint: SshFingerprint,
},
SshAuthenticationRejected,
UnknownSshHostKey {
host_key: Box<SshHostKey>,
},
ChangedSshHostKey {
host_key: Box<SshHostKey>,
line: usize,
},
SshKnownHostsUnavailable {
path: PathBuf,
},
SshProtocolFailed,
CredentialsUnavailable,
CredentialAccessDenied,
CredentialCancelled,
@@ -441,6 +540,73 @@ impl fmt::Display for GitError {
)
}
Self::RemoteNotFound { name } => write!(formatter, "Git remote not found: {name}"),
Self::SshAuthenticationNotConfigured => {
formatter.write_str("SSH authentication is not configured")
}
Self::SshIdentityMissing { path } => {
write!(
formatter,
"SSH identity file was not found: {}",
path.display()
)
}
Self::SshIdentityInvalid { path } => {
write!(
formatter,
"SSH identity file is invalid: {}",
path.display()
)
}
Self::SshUnsupportedAlgorithm { algorithm } => {
write!(formatter, "unsupported SSH key algorithm: {algorithm}")
}
Self::SshKeyPassphraseUnavailable { fingerprint } => {
write!(
formatter,
"SSH key passphrase is unavailable for {fingerprint}"
)
}
Self::SshKeyPassphraseDenied { fingerprint } => {
write!(
formatter,
"access to the SSH key passphrase was denied for {fingerprint}"
)
}
Self::SshKeyPassphraseCancelled { fingerprint } => {
write!(
formatter,
"SSH key passphrase access was cancelled for {fingerprint}"
)
}
Self::SshKeyPassphraseRejected { fingerprint } => {
write!(
formatter,
"SSH key passphrase was rejected for {fingerprint}"
)
}
Self::SshAgentUnavailable => formatter.write_str("the SSH agent is unavailable"),
Self::SshAgentIdentityMissing { fingerprint } => {
write!(formatter, "the SSH agent does not contain {fingerprint}")
}
Self::SshAuthenticationRejected => {
formatter.write_str("SSH public-key authentication was rejected")
}
Self::UnknownSshHostKey { host_key } => write!(
formatter,
"unknown SSH host key for {}:{} ({})",
host_key.host, host_key.port, host_key.fingerprint
),
Self::ChangedSshHostKey { host_key, line } => write!(
formatter,
"changed SSH host key for {}:{} ({}, known_hosts line {line})",
host_key.host, host_key.port, host_key.fingerprint
),
Self::SshKnownHostsUnavailable { path } => write!(
formatter,
"SSH known-hosts file is unavailable: {}",
path.display()
),
Self::SshProtocolFailed => formatter.write_str("the SSH protocol failed"),
Self::CredentialsUnavailable => {
formatter.write_str("HTTPS Git credentials are unavailable")
}
@@ -617,6 +783,10 @@ pub trait GitCredentialProvider {
) -> Result<GitCredential, GitError>;
}
pub trait SshPassphraseProvider {
fn ssh_key_passphrase(&self, fingerprint: &SshFingerprint) -> Result<SecretBytes, GitError>;
}
pub trait GitSmartHttpTransport {
fn advertise_receive_pack(
&self,