Skip to content

Commit

Permalink
Refactor x509 parsing code into module separate from yubikey (#7)
Browse files Browse the repository at this point in the history
  • Loading branch information
obelisk authored Jan 10, 2022
1 parent 56e4b05 commit 7b597d8
Show file tree
Hide file tree
Showing 7 changed files with 127 additions and 113 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ jobs:
steps:
- uses: actions/checkout@v2
- name: Build
run: cargo build --features="yubikey_support" --examples --verbose
run: cargo build --features="yubikey-support" --examples --verbose
- name: Run tests
run: cargo test --features="encrypted-keys" --verbose

Expand Down
15 changes: 8 additions & 7 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "sshcerts"
version = "0.7.0"
version = "0.8.0"
authors = ["Mitchell Grenier <[email protected]>"]
edition = "2018"
license-file = "LICENSE"
Expand All @@ -13,11 +13,12 @@ categories = ["authentication"]
[features]
default = ["rsa-signing"]

all = ["encrypted-keys", "rsa-signing", "yubikey_support"]
all = ["encrypted-keys", "rsa-signing", "x509-support", "yubikey-support"]

yubikey_support = ["der-parser", "log", "rcgen", "x509", "x509-parser", "yubikey"]
rsa-signing = ["simple_asn1", "num-bigint"]
encrypted-keys = ["aes", "bcrypt-pbkdf", "ctr"]
rsa-signing = ["simple_asn1", "num-bigint"]
x509-support = ["der-parser", "x509", "x509-parser"]
yubikey-support = ["der-parser", "log", "rcgen", "x509", "x509-parser", "yubikey"]

[dependencies]
base64 = "0.13"
Expand Down Expand Up @@ -51,15 +52,15 @@ criterion = "0.3"
[[bench]]
name = "certs_per_second"
harness = false
required-features = ["yubikey_support"]
required-features = ["yubikey-support"]

[[example]]
name = "yk-fingerprint"
required-features = ["yubikey_support"]
required-features = ["yubikey-support"]

[[example]]
name = "yk-provision"
required-features = ["yubikey_support"]
required-features = ["yubikey-support"]

[[example]]
name = "ssh-pkey-info"
Expand Down
15 changes: 12 additions & 3 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ pub enum Error {
/// The provided data was not a certificate
NotCertificate,
/// The requested signature or key was incompatible with what was previously specified
/// or an x509 certificate contains a public key that is not compatible with SSH.
KeyTypeMismatch,
/// The certificate is not signed correctly and invalid
CertificateInvalidSignature,
Expand All @@ -32,8 +33,14 @@ pub enum Error {
/// The curve in an ECC public/private key/signature is unknown
UnknownCurve(String),
/// An error occured in the yubikey module
#[cfg(feature = "yubikey")]
#[cfg(feature = "yubikey_support")]
YubikeyError(crate::yubikey::Error),
/// A generic parsing error which occurs whenever data sent does not match the
/// expected format
ParsingError,
/// This occurs when you try to use a feature that could technically work
/// but is currently unimplemented.
Unsupported,
}

impl fmt::Display for Error {
Expand All @@ -53,8 +60,10 @@ impl fmt::Display for Error {
Error::EncryptedPrivateKeyNotSupported => write!(f, "This method of private key encryption is not supported or sshcerts was not compiled with encrypted private key support"),
Error::UnknownKeyType(ref v) => write!(f, "Unknown key type {}", v),
Error::UnknownCurve(ref v) => write!(f, "Unknown curve {}", v),
#[cfg(feature = "yubikey")]
Error::YubikeyError(ref e) => write!(f, "{}", e),
#[cfg(feature = "yubikey_support")]
Error::YubikeyError(ref e) => write!(f, "{}", e),
Error::ParsingError => write!(f, "Could not parse the data provided"),
Error::Unsupported => write!(f, "Functionality either not implemented or cannot be technically supported"),
}
}
}
Expand Down
10 changes: 8 additions & 2 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
warnings ,
)]

#[cfg(feature = "yubikey_support")]
#[cfg(feature = "yubikey-support")]
#[macro_use]
extern crate log;

Expand All @@ -62,7 +62,13 @@ pub mod utils;
/// Functions for dealing with Yubikey signing.
/// Also contains an SSH submodule containing helper functions to generate
/// SSH encoded versions of it's normal functions.
#[cfg(feature = "yubikey_support")]
#[cfg(feature = "yubikey-support")]
pub mod yubikey;

/// Contains some helper functions for pulling SSH public keys from x509
/// certificates and CSRs. Is enabled whenever yubikey_support is enabled
/// because some functionality is currently shared.
#[cfg(any(feature = "yubikey-support", feature = "x509-support"))]
pub mod x509;

pub use ssh::{Certificate, PublicKey, PrivateKey};
88 changes: 88 additions & 0 deletions src/x509/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
use x509_parser::prelude::FromDer;

use crate::error::Error;
use crate::ssh::{
Curve,
EcdsaPublicKey,
KeyType,
PublicKey,
PublicKeyKind,
};

const OID_RSA_ENCRYPTION: &str = "1.2.840.113549.1.1.1";
const OID_EC_PUBLIC_KEY: &str = "1.2.840.10045.2.1";
const OID_NIST_P256: &str = "1.2.840.10045.3.1.7";
const OID_NIST_P384: &str = "1.3.132.0.34";

/// This function is used to extract an SSH public key from an x509
/// certificate signing request
pub fn extract_ssh_pubkey_from_x509_certificate(cert: &[u8]) -> Result<PublicKey, Error> {
let parsed_cert = match x509_parser::parse_x509_certificate(&cert) {
Ok((_, c)) => c,
Err(_) => return Err(Error::ParsingError)
};
let pki = &parsed_cert.tbs_certificate.subject_pki;
convert_x509_pki_to_pubkey(pki)
}

/// This function is used to extract an SSH public key from an x509
/// certificate signing request
pub fn extract_ssh_pubkey_from_x509_csr(csr: &[u8]) -> Result<PublicKey, Error> {
let parsed_csr = match x509_parser::certification_request::X509CertificationRequest::from_der(&csr) {
Ok((_, csr)) => csr,
Err(_) => return Err(Error::ParsingError)
};
let pki = &parsed_csr.certification_request_info.subject_pki;
convert_x509_pki_to_pubkey(pki)
}

fn convert_x509_pki_to_pubkey(pki: &x509_parser::x509::SubjectPublicKeyInfo<'_>) -> Result<PublicKey, Error> {
return match pki.algorithm.algorithm.to_string().as_str() {
OID_RSA_ENCRYPTION => {
Err(Error::Unsupported)
},
OID_EC_PUBLIC_KEY => {
let key_bytes = &pki.subject_public_key.data;
let algorithm_parameters = pki
.algorithm
.parameters
.as_ref()
.ok_or(Error::ParsingError)?;

let curve_oid = algorithm_parameters.as_oid_val().map_err(|_| Error::ParsingError)?;

match curve_oid.to_string().as_str() {
OID_NIST_P256 => {
let key_type = KeyType::from_name("ecdsa-sha2-nistp256").unwrap();
let curve = Curve::from_identifier("nistp256").unwrap();
let kind = EcdsaPublicKey {
curve,
key: key_bytes.to_vec(),
};

Ok(PublicKey {
key_type,
kind: PublicKeyKind::Ecdsa(kind),
comment: None,
})
},
OID_NIST_P384 => {
let key_type = KeyType::from_name("ecdsa-sha2-nistp384").unwrap();
let curve = Curve::from_identifier("nistp384").unwrap();
let kind = EcdsaPublicKey {
curve,
key: key_bytes.to_vec(),
};

Ok(PublicKey {
key_type,
kind: PublicKeyKind::Ecdsa(kind),
comment: None,
})
}
_ => Err(Error::KeyTypeMismatch),
}
}
_ => Err(Error::ParsingError),
}
}
12 changes: 3 additions & 9 deletions src/yubikey/piv/management.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use crate::PublicKey;

use ring::digest;

use yubikey::{MgmKey, YubiKey};
Expand Down Expand Up @@ -113,8 +114,7 @@ impl super::Yubikey {
Ok(cert.subject().to_string())
}

/// Fetch the certificate from a given Yubikey slot. If there is not one, this
/// will fail
/// Fetch the certificate from a given Yubikey slot.
pub fn fetch_certificate(&mut self, slot: &SlotId) -> Result<Vec<u8>> {
let cert = Certificate::read(&mut self.yk, *slot)?;
Ok(cert.as_ref().to_vec())
Expand All @@ -125,12 +125,6 @@ impl super::Yubikey {
Ok(Certificate::from_bytes(data.to_vec())?.write(&mut self.yk, *slot, yubikey::certificate::CertInfo::Uncompressed)?)
}

/// Fetch a public key from the provided slot. If there is not exactly one
/// Yubikey this will fail.
pub fn fetch_pubkey(&mut self, slot: &SlotId) -> Result<PublicKey> {
super::ssh::extract_ssh_pubkey_from_x509_certificate(&self.fetch_certificate(slot)?)
}

/// Generate attestation for a slot
pub fn fetch_attestation(&mut self, slot: &SlotId) -> Result<Vec<u8>> {
Ok(attest(&mut self.yk, *slot)?.to_vec())
Expand Down Expand Up @@ -171,7 +165,7 @@ impl super::Yubikey {
extensions,
)?;

self.fetch_pubkey(slot)
self.ssh_cert_fetch_pubkey(slot)
}

/// Take data, an algorithm, and a slot and attempt to sign the data field
Expand Down
98 changes: 7 additions & 91 deletions src/yubikey/piv/ssh.rs
Original file line number Diff line number Diff line change
@@ -1,29 +1,25 @@
use x509_parser::prelude::FromDer;

use yubikey::piv::{AlgorithmId, SlotId};
use crate::ssh::{
Curve,
CurveKind,
EcdsaPublicKey,
KeyType,
PublicKey,
PublicKeyKind,
};

use crate::utils::signature_convert_asn1_ecdsa_to_ssh;
use crate::x509::extract_ssh_pubkey_from_x509_certificate;

use super::{Error, Result};

const OID_RSA_ENCRYPTION: &str = "1.2.840.113549.1.1.1";
const OID_EC_PUBLIC_KEY: &str = "1.2.840.10045.2.1";
const OID_NIST_P256: &str = "1.2.840.10045.3.1.7";
const OID_NIST_P384: &str = "1.3.132.0.34";

impl super::Yubikey {
/// Pull the public key from the YubiKey and wrap it in a sshcerts
/// PublicKey object.
pub fn ssh_cert_fetch_pubkey(&mut self, slot: &SlotId) -> Result<PublicKey> {
extract_ssh_pubkey_from_x509_certificate(&self.fetch_certificate(slot)?)
match extract_ssh_pubkey_from_x509_certificate(&self.fetch_certificate(slot)?) {
Ok(public_key) => Ok(public_key),
Err(crate::error::Error::ParsingError) => Err(super::Error::ParsingError),
Err(crate::error::Error::KeyTypeMismatch) => Err(super::Error::WrongKeyType),
Err(_) => Err(super::Error::Unsupported),
}
}

/// Returns the AlgorithmId of the kind of key stored in the given
Expand Down Expand Up @@ -73,84 +69,4 @@ impl super::Yubikey {
}
}

}

/// This function is used to extract an SSH public key from an x509
/// certificate signing request
pub fn extract_ssh_pubkey_from_x509_certificate(cert: &[u8]) -> Result<PublicKey> {
let parsed_cert = match x509_parser::parse_x509_certificate(&cert) {
Ok((_, c)) => c,
Err(e) => {
error!("Parsing Error: {:?}", e);
return Err(Error::ParsingError)
}
};
let pki = &parsed_cert.tbs_certificate.subject_pki;
convert_x509_pki_to_pubkey(pki)
}

/// This function is used to extract an SSH public key from an x509
/// certificate signing request
pub fn extract_ssh_pubkey_from_x509_csr(csr: &[u8]) -> Result<PublicKey> {
let parsed_csr = match x509_parser::certification_request::X509CertificationRequest::from_der(&csr) {
Ok((_, csr)) => csr,
Err(e) => {
error!("Parsing Error: {:?}", e);
return Err(Error::ParsingError)
}
};
let pki = &parsed_csr.certification_request_info.subject_pki;
convert_x509_pki_to_pubkey(pki)
}

fn convert_x509_pki_to_pubkey(pki: &x509_parser::x509::SubjectPublicKeyInfo<'_>) -> Result<PublicKey> {
return match pki.algorithm.algorithm.to_string().as_str() {
OID_RSA_ENCRYPTION => {
error!("RSA keys are not yet supported");
Err(Error::Unsupported)
},
OID_EC_PUBLIC_KEY => {
let key_bytes = &pki.subject_public_key.data;
let algorithm_parameters = pki
.algorithm
.parameters
.as_ref()
.ok_or(Error::ParsingError)?;

let curve_oid = algorithm_parameters.as_oid_val().map_err(|_| Error::ParsingError)?;

match curve_oid.to_string().as_str() {
OID_NIST_P256 => {
let key_type = KeyType::from_name("ecdsa-sha2-nistp256").unwrap();
let curve = Curve::from_identifier("nistp256").unwrap();
let kind = EcdsaPublicKey {
curve,
key: key_bytes.to_vec(),
};

Ok(PublicKey {
key_type,
kind: PublicKeyKind::Ecdsa(kind),
comment: None,
})
},
OID_NIST_P384 => {
let key_type = KeyType::from_name("ecdsa-sha2-nistp384").unwrap();
let curve = Curve::from_identifier("nistp384").unwrap();
let kind = EcdsaPublicKey {
curve,
key: key_bytes.to_vec(),
};

Ok(PublicKey {
key_type,
kind: PublicKeyKind::Ecdsa(kind),
comment: None,
})
}
_ => Err(Error::WrongKeyType),
}
}
_ => Err(Error::ParsingError),
}
}

0 comments on commit 7b597d8

Please sign in to comment.