Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ability to get the SSH host key and its type. #909

Merged
merged 3 commits into from Jan 10, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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