Skip to content

Commit

Permalink
Merge pull request #909 from ehuss/ssh-keys
Browse files Browse the repository at this point in the history
Add ability to get the SSH host key and its type.
  • Loading branch information
ehuss committed Jan 10, 2023
2 parents e6aa666 + 222fbf3 commit bce1555
Show file tree
Hide file tree
Showing 9 changed files with 122 additions and 16 deletions.
4 changes: 2 additions & 2 deletions Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "git2"
version = "0.15.0"
version = "0.16.0"
authors = ["Josh Triplett <josh@joshtriplett.org>", "Alex Crichton <alex@alexcrichton.com>"]
license = "MIT OR Apache-2.0"
readme = "README.md"
Expand All @@ -20,7 +20,7 @@ url = "2.0"
bitflags = "1.1.0"
libc = "0.2"
log = "0.4.8"
libgit2-sys = { path = "libgit2-sys", version = "0.14.0" }
libgit2-sys = { path = "libgit2-sys", version = "0.14.1" }

[target."cfg(all(unix, not(target_os = \"macos\")))".dependencies]
openssl-sys = { version = "0.9.0", optional = true }
Expand Down
2 changes: 1 addition & 1 deletion README.md
Expand Up @@ -6,7 +6,7 @@ libgit2 bindings for Rust.

```toml
[dependencies]
git2 = "0.14"
git2 = "0.16"
```

## Rust version requirements
Expand Down
4 changes: 2 additions & 2 deletions git2-curl/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "git2-curl"
version = "0.16.0"
version = "0.17.0"
authors = ["Josh Triplett <josh@joshtriplett.org>", "Alex Crichton <alex@alexcrichton.com>"]
license = "MIT OR Apache-2.0"
repository = "https://github.com/rust-lang/git2-rs"
Expand All @@ -16,7 +16,7 @@ edition = "2018"
curl = "0.4.33"
url = "2.0"
log = "0.4"
git2 = { path = "..", version = "0.15", default-features = false }
git2 = { path = "..", version = "0.16", default-features = false }

[dev-dependencies]
civet = "0.11"
Expand Down
2 changes: 1 addition & 1 deletion git2-curl/src/lib.rs
Expand Up @@ -15,7 +15,7 @@
//! > **NOTE**: At this time this crate likely does not support a `git push`
//! > operation, only clones.

#![doc(html_root_url = "https://docs.rs/git2-curl/0.16")]
#![doc(html_root_url = "https://docs.rs/git2-curl/0.17")]
#![deny(missing_docs)]
#![warn(rust_2018_idioms)]
#![cfg_attr(test, deny(warnings))]
Expand Down
2 changes: 1 addition & 1 deletion libgit2-sys/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "libgit2-sys"
version = "0.14.0+1.5.0"
version = "0.14.1+1.5.0"
authors = ["Josh Triplett <josh@joshtriplett.org>", "Alex Crichton <alex@alexcrichton.com>"]
links = "git2"
build = "build.rs"
Expand Down
4 changes: 4 additions & 0 deletions libgit2-sys/lib.rs
Expand Up @@ -489,6 +489,10 @@ git_enum! {
GIT_CERT_SSH_RAW_TYPE_UNKNOWN = 0,
GIT_CERT_SSH_RAW_TYPE_RSA = 1,
GIT_CERT_SSH_RAW_TYPE_DSS = 2,
GIT_CERT_SSH_RAW_TYPE_KEY_ECDSA_256 = 3,
GIT_CERT_SSH_RAW_TYPE_KEY_ECDSA_384 = 4,
GIT_CERT_SSH_RAW_TYPE_KEY_ECDSA_521 = 5,
GIT_CERT_SSH_RAW_TYPE_KEY_ED25519 = 6,
}
}

Expand Down
81 changes: 81 additions & 0 deletions src/cert.rs
Expand Up @@ -27,6 +27,54 @@ pub struct CertX509<'a> {
_marker: marker::PhantomData<&'a raw::git_cert>,
}

/// The SSH host key type.
#[derive(Copy, Clone, Debug)]
#[non_exhaustive]
pub enum SshHostKeyType {
/// Unknown key type
Unknown = raw::GIT_CERT_SSH_RAW_TYPE_UNKNOWN as isize,
/// RSA key type
Rsa = raw::GIT_CERT_SSH_RAW_TYPE_RSA as isize,
/// DSS key type
Dss = raw::GIT_CERT_SSH_RAW_TYPE_DSS as isize,
/// ECDSA 256 key type
Ecdsa256 = raw::GIT_CERT_SSH_RAW_TYPE_KEY_ECDSA_256 as isize,
/// ECDSA 384 key type
Ecdsa384 = raw::GIT_CERT_SSH_RAW_TYPE_KEY_ECDSA_384 as isize,
/// ECDSA 521 key type
Ecdsa521 = raw::GIT_CERT_SSH_RAW_TYPE_KEY_ECDSA_521 as isize,
/// ED25519 key type
Ed255219 = raw::GIT_CERT_SSH_RAW_TYPE_KEY_ED25519 as isize,
}

impl SshHostKeyType {
/// The name of the key type as encoded in the known_hosts file.
pub fn name(&self) -> &'static str {
match self {
SshHostKeyType::Unknown => "unknown",
SshHostKeyType::Rsa => "ssh-rsa",
SshHostKeyType::Dss => "ssh-dss",
SshHostKeyType::Ecdsa256 => "ecdsa-sha2-nistp256",
SshHostKeyType::Ecdsa384 => "ecdsa-sha2-nistp384",
SshHostKeyType::Ecdsa521 => "ecdsa-sha2-nistp521",
SshHostKeyType::Ed255219 => "ssh-ed25519",
}
}

/// A short name of the key type, the colloquial form used as a human-readable description.
pub fn short_name(&self) -> &'static str {
match self {
SshHostKeyType::Unknown => "Unknown",
SshHostKeyType::Rsa => "RSA",
SshHostKeyType::Dss => "DSA",
SshHostKeyType::Ecdsa256 => "ECDSA",
SshHostKeyType::Ecdsa384 => "ECDSA",
SshHostKeyType::Ecdsa521 => "ECDSA",
SshHostKeyType::Ed255219 => "ED25519",
}
}
}

impl<'a> Cert<'a> {
/// Attempt to view this certificate as an SSH hostkey.
///
Expand Down Expand Up @@ -87,6 +135,39 @@ impl<'a> CertHostkey<'a> {
}
}
}

/// Returns the raw host key.
pub fn hostkey(&self) -> Option<&[u8]> {
unsafe {
if (*self.raw).kind & raw::GIT_CERT_SSH_RAW == 0 {
return None;
}
Some(slice::from_raw_parts(
(*self.raw).hostkey as *const u8,
(*self.raw).hostkey_len as usize,
))
}
}

/// Returns the type of the host key.
pub fn hostkey_type(&self) -> Option<SshHostKeyType> {
unsafe {
if (*self.raw).kind & raw::GIT_CERT_SSH_RAW == 0 {
return None;
}
let t = match (*self.raw).raw_type {
raw::GIT_CERT_SSH_RAW_TYPE_UNKNOWN => SshHostKeyType::Unknown,
raw::GIT_CERT_SSH_RAW_TYPE_RSA => SshHostKeyType::Rsa,
raw::GIT_CERT_SSH_RAW_TYPE_DSS => SshHostKeyType::Dss,
raw::GIT_CERT_SSH_RAW_TYPE_KEY_ECDSA_256 => SshHostKeyType::Ecdsa256,
raw::GIT_CERT_SSH_RAW_TYPE_KEY_ECDSA_384 => SshHostKeyType::Ecdsa384,
raw::GIT_CERT_SSH_RAW_TYPE_KEY_ECDSA_521 => SshHostKeyType::Ecdsa521,
raw::GIT_CERT_SSH_RAW_TYPE_KEY_ED25519 => SshHostKeyType::Ed255219,
t => panic!("unexpected host key type {:?}", t),
};
Some(t)
}
}
}

impl<'a> CertX509<'a> {
Expand Down
4 changes: 2 additions & 2 deletions src/lib.rs
Expand Up @@ -65,7 +65,7 @@
//! source `Repository`, to ensure that they do not outlive the repository
//! itself.

#![doc(html_root_url = "https://docs.rs/git2/0.15")]
#![doc(html_root_url = "https://docs.rs/git2/0.16")]
#![allow(trivial_numeric_casts, trivial_casts)]
#![deny(missing_docs)]
#![warn(rust_2018_idioms)]
Expand Down Expand Up @@ -123,7 +123,7 @@ pub use crate::refspec::Refspec;
pub use crate::remote::{
FetchOptions, PushOptions, Refspecs, Remote, RemoteConnection, RemoteHead, RemoteRedirect,
};
pub use crate::remote_callbacks::{Credentials, RemoteCallbacks};
pub use crate::remote_callbacks::{CertificateCheckStatus, Credentials, RemoteCallbacks};
pub use crate::remote_callbacks::{TransportMessage, UpdateTips};
pub use crate::repo::{Repository, RepositoryInitOptions};
pub use crate::revert::RevertOptions;
Expand Down
35 changes: 28 additions & 7 deletions src/remote_callbacks.rs
Expand Up @@ -51,7 +51,18 @@ pub type UpdateTips<'a> = dyn FnMut(&str, Oid, Oid) -> bool + 'a;
///
/// The second argument is the hostname for the connection is passed as the last
/// argument.
pub type CertificateCheck<'a> = dyn FnMut(&Cert<'_>, &str) -> bool + 'a;
pub type CertificateCheck<'a> =
dyn FnMut(&Cert<'_>, &str) -> Result<CertificateCheckStatus, Error> + 'a;

/// The return value for the [`CertificateCheck`] callback.
pub enum CertificateCheckStatus {
/// Indicates that the certificate should be accepted.
CertificateOk,
/// Indicates that the certificate callback is neither accepting nor
/// rejecting the certificate. The result of the certificate checks
/// built-in to libgit2 will be used instead.
CertificatePassthrough,
}

/// Callback for each updated reference on push.
///
Expand Down Expand Up @@ -162,7 +173,7 @@ impl<'a> RemoteCallbacks<'a> {
/// connection to proceed.
pub fn certificate_check<F>(&mut self, cb: F) -> &mut RemoteCallbacks<'a>
where
F: FnMut(&Cert<'_>, &str) -> bool + 'a,
F: FnMut(&Cert<'_>, &str) -> Result<CertificateCheckStatus, Error> + 'a,
{
self.certificate_check = Some(Box::new(cb) as Box<CertificateCheck<'a>>);
self
Expand Down Expand Up @@ -371,16 +382,26 @@ extern "C" fn certificate_check_cb(
let payload = &mut *(data as *mut RemoteCallbacks<'_>);
let callback = match payload.certificate_check {
Some(ref mut c) => c,
None => return true,
None => return Ok(CertificateCheckStatus::CertificatePassthrough),
};
let cert = Binding::from_raw(cert);
let hostname = str::from_utf8(CStr::from_ptr(hostname).to_bytes()).unwrap();
callback(&cert, hostname)
});
if ok == Some(true) {
0
} else {
-1
match ok {
Some(Ok(CertificateCheckStatus::CertificateOk)) => 0,
Some(Ok(CertificateCheckStatus::CertificatePassthrough)) => raw::GIT_PASSTHROUGH as c_int,
Some(Err(e)) => {
let s = CString::new(e.message()).unwrap();
unsafe {
raw::git_error_set_str(e.class() as c_int, s.as_ptr());
}
e.raw_code() as c_int
}
None => {
// Panic. The *should* get resumed by some future call to check().
-1
}
}
}

Expand Down

0 comments on commit bce1555

Please sign in to comment.