diff --git a/Cargo.toml b/Cargo.toml index 27d9a92638..dc5255f4a8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "git2" -version = "0.15.0" +version = "0.16.0" authors = ["Josh Triplett ", "Alex Crichton "] license = "MIT OR Apache-2.0" readme = "README.md" @@ -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 } diff --git a/README.md b/README.md index 7e11dcbfd9..bf5daf5e47 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ libgit2 bindings for Rust. ```toml [dependencies] -git2 = "0.15" +git2 = "0.16" ``` ## Rust version requirements diff --git a/git2-curl/Cargo.toml b/git2-curl/Cargo.toml index 3dc80e4246..ac906367fc 100644 --- a/git2-curl/Cargo.toml +++ b/git2-curl/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "git2-curl" -version = "0.16.0" +version = "0.17.0" authors = ["Josh Triplett ", "Alex Crichton "] license = "MIT OR Apache-2.0" repository = "https://github.com/rust-lang/git2-rs" @@ -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" diff --git a/git2-curl/src/lib.rs b/git2-curl/src/lib.rs index 480c51c982..c0f0e30d19 100644 --- a/git2-curl/src/lib.rs +++ b/git2-curl/src/lib.rs @@ -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))] diff --git a/libgit2-sys/Cargo.toml b/libgit2-sys/Cargo.toml index 1f5fe34e4d..fd3e337f95 100644 --- a/libgit2-sys/Cargo.toml +++ b/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 ", "Alex Crichton "] links = "git2" build = "build.rs" diff --git a/libgit2-sys/lib.rs b/libgit2-sys/lib.rs index 5e943c0d09..ea66159f0c 100644 --- a/libgit2-sys/lib.rs +++ b/libgit2-sys/lib.rs @@ -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, } } diff --git a/src/cert.rs b/src/cert.rs index d62b8304ce..b232cc3ce8 100644 --- a/src/cert.rs +++ b/src/cert.rs @@ -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. /// @@ -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 { + 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> { diff --git a/src/lib.rs b/src/lib.rs index b65581612d..40be0c4b5b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -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)] @@ -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; diff --git a/src/remote_callbacks.rs b/src/remote_callbacks.rs index bcc73e85e9..fe1802273b 100644 --- a/src/remote_callbacks.rs +++ b/src/remote_callbacks.rs @@ -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 + '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. /// @@ -162,7 +173,7 @@ impl<'a> RemoteCallbacks<'a> { /// connection to proceed. pub fn certificate_check(&mut self, cb: F) -> &mut RemoteCallbacks<'a> where - F: FnMut(&Cert<'_>, &str) -> bool + 'a, + F: FnMut(&Cert<'_>, &str) -> Result + 'a, { self.certificate_check = Some(Box::new(cb) as Box>); self @@ -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 + } } }