Skip to content

Commit 009c59e

Browse files
committed
Merge pull request rust-lang#909 from ehuss/ssh-keys
Add ability to get the SSH host key and its type.
1 parent 1b93090 commit 009c59e

File tree

9 files changed

+122
-16
lines changed

9 files changed

+122
-16
lines changed

Cargo.toml

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "git2"
3-
version = "0.15.0"
3+
version = "0.16.0"
44
authors = ["Josh Triplett <[email protected]>", "Alex Crichton <[email protected]>"]
55
license = "MIT OR Apache-2.0"
66
readme = "README.md"
@@ -20,7 +20,7 @@ url = "2.0"
2020
bitflags = "1.1.0"
2121
libc = "0.2"
2222
log = "0.4.8"
23-
libgit2-sys = { path = "libgit2-sys", version = "0.14.0" }
23+
libgit2-sys = { path = "libgit2-sys", version = "0.14.1" }
2424

2525
[target."cfg(all(unix, not(target_os = \"macos\")))".dependencies]
2626
openssl-sys = { version = "0.9.0", optional = true }

README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ libgit2 bindings for Rust.
66

77
```toml
88
[dependencies]
9-
git2 = "0.15"
9+
git2 = "0.16"
1010
```
1111

1212
## Rust version requirements

git2-curl/Cargo.toml

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "git2-curl"
3-
version = "0.16.0"
3+
version = "0.17.0"
44
authors = ["Josh Triplett <[email protected]>", "Alex Crichton <[email protected]>"]
55
license = "MIT OR Apache-2.0"
66
repository = "https://github.com/rust-lang/git2-rs"
@@ -16,7 +16,7 @@ edition = "2018"
1616
curl = "0.4.33"
1717
url = "2.0"
1818
log = "0.4"
19-
git2 = { path = "..", version = "0.15", default-features = false }
19+
git2 = { path = "..", version = "0.16", default-features = false }
2020

2121
[dev-dependencies]
2222
civet = "0.11"

git2-curl/src/lib.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
//! > **NOTE**: At this time this crate likely does not support a `git push`
1616
//! > operation, only clones.
1717
18-
#![doc(html_root_url = "https://docs.rs/git2-curl/0.16")]
18+
#![doc(html_root_url = "https://docs.rs/git2-curl/0.17")]
1919
#![deny(missing_docs)]
2020
#![warn(rust_2018_idioms)]
2121
#![cfg_attr(test, deny(warnings))]

libgit2-sys/Cargo.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "libgit2-sys"
3-
version = "0.14.0+1.5.0"
3+
version = "0.14.1+1.5.0"
44
authors = ["Josh Triplett <[email protected]>", "Alex Crichton <[email protected]>"]
55
links = "git2"
66
build = "build.rs"

libgit2-sys/lib.rs

+4
Original file line numberDiff line numberDiff line change
@@ -489,6 +489,10 @@ git_enum! {
489489
GIT_CERT_SSH_RAW_TYPE_UNKNOWN = 0,
490490
GIT_CERT_SSH_RAW_TYPE_RSA = 1,
491491
GIT_CERT_SSH_RAW_TYPE_DSS = 2,
492+
GIT_CERT_SSH_RAW_TYPE_KEY_ECDSA_256 = 3,
493+
GIT_CERT_SSH_RAW_TYPE_KEY_ECDSA_384 = 4,
494+
GIT_CERT_SSH_RAW_TYPE_KEY_ECDSA_521 = 5,
495+
GIT_CERT_SSH_RAW_TYPE_KEY_ED25519 = 6,
492496
}
493497
}
494498

src/cert.rs

+81
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,54 @@ pub struct CertX509<'a> {
2727
_marker: marker::PhantomData<&'a raw::git_cert>,
2828
}
2929

30+
/// The SSH host key type.
31+
#[derive(Copy, Clone, Debug)]
32+
#[non_exhaustive]
33+
pub enum SshHostKeyType {
34+
/// Unknown key type
35+
Unknown = raw::GIT_CERT_SSH_RAW_TYPE_UNKNOWN as isize,
36+
/// RSA key type
37+
Rsa = raw::GIT_CERT_SSH_RAW_TYPE_RSA as isize,
38+
/// DSS key type
39+
Dss = raw::GIT_CERT_SSH_RAW_TYPE_DSS as isize,
40+
/// ECDSA 256 key type
41+
Ecdsa256 = raw::GIT_CERT_SSH_RAW_TYPE_KEY_ECDSA_256 as isize,
42+
/// ECDSA 384 key type
43+
Ecdsa384 = raw::GIT_CERT_SSH_RAW_TYPE_KEY_ECDSA_384 as isize,
44+
/// ECDSA 521 key type
45+
Ecdsa521 = raw::GIT_CERT_SSH_RAW_TYPE_KEY_ECDSA_521 as isize,
46+
/// ED25519 key type
47+
Ed255219 = raw::GIT_CERT_SSH_RAW_TYPE_KEY_ED25519 as isize,
48+
}
49+
50+
impl SshHostKeyType {
51+
/// The name of the key type as encoded in the known_hosts file.
52+
pub fn name(&self) -> &'static str {
53+
match self {
54+
SshHostKeyType::Unknown => "unknown",
55+
SshHostKeyType::Rsa => "ssh-rsa",
56+
SshHostKeyType::Dss => "ssh-dss",
57+
SshHostKeyType::Ecdsa256 => "ecdsa-sha2-nistp256",
58+
SshHostKeyType::Ecdsa384 => "ecdsa-sha2-nistp384",
59+
SshHostKeyType::Ecdsa521 => "ecdsa-sha2-nistp521",
60+
SshHostKeyType::Ed255219 => "ssh-ed25519",
61+
}
62+
}
63+
64+
/// A short name of the key type, the colloquial form used as a human-readable description.
65+
pub fn short_name(&self) -> &'static str {
66+
match self {
67+
SshHostKeyType::Unknown => "Unknown",
68+
SshHostKeyType::Rsa => "RSA",
69+
SshHostKeyType::Dss => "DSA",
70+
SshHostKeyType::Ecdsa256 => "ECDSA",
71+
SshHostKeyType::Ecdsa384 => "ECDSA",
72+
SshHostKeyType::Ecdsa521 => "ECDSA",
73+
SshHostKeyType::Ed255219 => "ED25519",
74+
}
75+
}
76+
}
77+
3078
impl<'a> Cert<'a> {
3179
/// Attempt to view this certificate as an SSH hostkey.
3280
///
@@ -87,6 +135,39 @@ impl<'a> CertHostkey<'a> {
87135
}
88136
}
89137
}
138+
139+
/// Returns the raw host key.
140+
pub fn hostkey(&self) -> Option<&[u8]> {
141+
unsafe {
142+
if (*self.raw).kind & raw::GIT_CERT_SSH_RAW == 0 {
143+
return None;
144+
}
145+
Some(slice::from_raw_parts(
146+
(*self.raw).hostkey as *const u8,
147+
(*self.raw).hostkey_len as usize,
148+
))
149+
}
150+
}
151+
152+
/// Returns the type of the host key.
153+
pub fn hostkey_type(&self) -> Option<SshHostKeyType> {
154+
unsafe {
155+
if (*self.raw).kind & raw::GIT_CERT_SSH_RAW == 0 {
156+
return None;
157+
}
158+
let t = match (*self.raw).raw_type {
159+
raw::GIT_CERT_SSH_RAW_TYPE_UNKNOWN => SshHostKeyType::Unknown,
160+
raw::GIT_CERT_SSH_RAW_TYPE_RSA => SshHostKeyType::Rsa,
161+
raw::GIT_CERT_SSH_RAW_TYPE_DSS => SshHostKeyType::Dss,
162+
raw::GIT_CERT_SSH_RAW_TYPE_KEY_ECDSA_256 => SshHostKeyType::Ecdsa256,
163+
raw::GIT_CERT_SSH_RAW_TYPE_KEY_ECDSA_384 => SshHostKeyType::Ecdsa384,
164+
raw::GIT_CERT_SSH_RAW_TYPE_KEY_ECDSA_521 => SshHostKeyType::Ecdsa521,
165+
raw::GIT_CERT_SSH_RAW_TYPE_KEY_ED25519 => SshHostKeyType::Ed255219,
166+
t => panic!("unexpected host key type {:?}", t),
167+
};
168+
Some(t)
169+
}
170+
}
90171
}
91172

92173
impl<'a> CertX509<'a> {

src/lib.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@
6565
//! source `Repository`, to ensure that they do not outlive the repository
6666
//! itself.
6767
68-
#![doc(html_root_url = "https://docs.rs/git2/0.15")]
68+
#![doc(html_root_url = "https://docs.rs/git2/0.16")]
6969
#![allow(trivial_numeric_casts, trivial_casts)]
7070
#![deny(missing_docs)]
7171
#![warn(rust_2018_idioms)]
@@ -123,7 +123,7 @@ pub use crate::refspec::Refspec;
123123
pub use crate::remote::{
124124
FetchOptions, PushOptions, Refspecs, Remote, RemoteConnection, RemoteHead, RemoteRedirect,
125125
};
126-
pub use crate::remote_callbacks::{Credentials, RemoteCallbacks};
126+
pub use crate::remote_callbacks::{CertificateCheckStatus, Credentials, RemoteCallbacks};
127127
pub use crate::remote_callbacks::{TransportMessage, UpdateTips};
128128
pub use crate::repo::{Repository, RepositoryInitOptions};
129129
pub use crate::revert::RevertOptions;

src/remote_callbacks.rs

+28-7
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,18 @@ pub type UpdateTips<'a> = dyn FnMut(&str, Oid, Oid) -> bool + 'a;
5151
///
5252
/// The second argument is the hostname for the connection is passed as the last
5353
/// argument.
54-
pub type CertificateCheck<'a> = dyn FnMut(&Cert<'_>, &str) -> bool + 'a;
54+
pub type CertificateCheck<'a> =
55+
dyn FnMut(&Cert<'_>, &str) -> Result<CertificateCheckStatus, Error> + 'a;
56+
57+
/// The return value for the [`CertificateCheck`] callback.
58+
pub enum CertificateCheckStatus {
59+
/// Indicates that the certificate should be accepted.
60+
CertificateOk,
61+
/// Indicates that the certificate callback is neither accepting nor
62+
/// rejecting the certificate. The result of the certificate checks
63+
/// built-in to libgit2 will be used instead.
64+
CertificatePassthrough,
65+
}
5566

5667
/// Callback for each updated reference on push.
5768
///
@@ -162,7 +173,7 @@ impl<'a> RemoteCallbacks<'a> {
162173
/// connection to proceed.
163174
pub fn certificate_check<F>(&mut self, cb: F) -> &mut RemoteCallbacks<'a>
164175
where
165-
F: FnMut(&Cert<'_>, &str) -> bool + 'a,
176+
F: FnMut(&Cert<'_>, &str) -> Result<CertificateCheckStatus, Error> + 'a,
166177
{
167178
self.certificate_check = Some(Box::new(cb) as Box<CertificateCheck<'a>>);
168179
self
@@ -371,16 +382,26 @@ extern "C" fn certificate_check_cb(
371382
let payload = &mut *(data as *mut RemoteCallbacks<'_>);
372383
let callback = match payload.certificate_check {
373384
Some(ref mut c) => c,
374-
None => return true,
385+
None => return Ok(CertificateCheckStatus::CertificatePassthrough),
375386
};
376387
let cert = Binding::from_raw(cert);
377388
let hostname = str::from_utf8(CStr::from_ptr(hostname).to_bytes()).unwrap();
378389
callback(&cert, hostname)
379390
});
380-
if ok == Some(true) {
381-
0
382-
} else {
383-
-1
391+
match ok {
392+
Some(Ok(CertificateCheckStatus::CertificateOk)) => 0,
393+
Some(Ok(CertificateCheckStatus::CertificatePassthrough)) => raw::GIT_PASSTHROUGH as c_int,
394+
Some(Err(e)) => {
395+
let s = CString::new(e.message()).unwrap();
396+
unsafe {
397+
raw::git_error_set_str(e.class() as c_int, s.as_ptr());
398+
}
399+
e.raw_code() as c_int
400+
}
401+
None => {
402+
// Panic. The *should* get resumed by some future call to check().
403+
-1
404+
}
384405
}
385406
}
386407

0 commit comments

Comments
 (0)