Skip to content

Commit

Permalink
Add SSHSIG Support (#22)
Browse files Browse the repository at this point in the history
  • Loading branch information
obelisk authored Apr 24, 2024
1 parent 40df6a5 commit bfddbf2
Show file tree
Hide file tree
Showing 25 changed files with 1,064 additions and 147 deletions.
13 changes: 9 additions & 4 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "sshcerts"
version = "0.13.0"
version = "0.13.1"
authors = ["Mitchell Grenier <mitchell@confurious.io>"]
edition = "2021"
license-file = "LICENSE"
Expand Down Expand Up @@ -74,9 +74,9 @@ ctr = { version = "0.8", optional = true }
minicbor = { version = "0.13", optional = true }

# Dependencies for fido-support-mozilla
authenticator = { git = "https://github.com/mozilla/authenticator-rs", default-features = false, features = [
authenticator = { version = "0.4.0-alpha.24", default-features = false, features = [
"crypto_openssl",
], branch = "ctap2-2021", optional = true }
], optional = true }
# authenticator = { path = "../authenticator-rs", default-features = false, features = [
# "crypto_openssl",
# ], optional = true }
Expand Down Expand Up @@ -108,7 +108,7 @@ name = "yk-provision"
required-features = ["yubikey-support"]

[[example]]
name = "sign-with-yubikey"
name = "sign-cert-with-yubikey"
required-features = ["yubikey-support"]

[[example]]
Expand Down Expand Up @@ -142,3 +142,8 @@ required-features = ["fido-support-mozilla"]
name = "yubikey-lite"
path = "tests/yubikey-lite.rs"
required-features = ["yubikey-lite"]

[[test]]
name = "signature-creation-rsa"
path = "tests/signature-creation-rsa.rs"
required-features = ["rsa-signing"]
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use sshcerts::*;

fn main() {
env_logger::init();
let matches = Command::new("sign-with-file")
let matches = Command::new("sign-cert-with-file")
.version(env!("CARGO_PKG_VERSION"))
.author("Mitchell Grenier <mitchell@confurious.io>")
.about("Sign an OpenSSH private key with another OpenSSH private key")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ impl SSHCertificateSigner for YubikeySigner {

fn main() {
env_logger::init();
let matches = Command::new("sign-with-yubikey")
let matches = Command::new("sign-cert-with-yubikey")
.version(env!("CARGO_PKG_VERSION"))
.author("Mitchell Grenier <mitchell@confurious.io>")
.about("Sign an OpenSSH private key with a Yubikey")
Expand Down
62 changes: 62 additions & 0 deletions examples/sign-file-with-file.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
use std::env;

use clap::{Arg, Command};

use sshcerts::{ssh::VerifiedSshSignature, *};

fn main() {
env_logger::init();
let matches = Command::new("sign-file-with-file")
.version(env!("CARGO_PKG_VERSION"))
.author("Mitchell Grenier <mitchell@confurious.io>")
.about("Sign a file with an OpenSSH private key")
.arg(
Arg::new("sign")
.help("The private key file you want to use to sign the file")
.long("signing_key")
.short('s')
.required(true)
.takes_value(true),
)
.arg(
Arg::new("pin")
.help("If using an SK key handle, what PIN to use with the key (not always needed)")
.long("pin")
.short('p')
.required(false)
.takes_value(true),
)
.arg(
Arg::new("file")
.help("The file to sign with the provided key")
.long("file")
.short('f')
.required(true)
.takes_value(true),
)
.arg(
Arg::new("namespace")
.help("The signing namespace you'd like the signature to be in")
.long("namespace")
.short('n')
.default_value("file")
.takes_value(true),
)
.get_matches();

let mut private_key = PrivateKey::from_path(matches.value_of("sign").unwrap()).unwrap();

if let Some(pin) = matches.value_of("pin") {
private_key.set_pin(pin);
}

let namespace = matches.value_of("namespace").unwrap();

let contents = std::fs::read(matches.value_of("file").unwrap()).unwrap();

let signature =
VerifiedSshSignature::new_with_private_key(&contents, namespace, private_key, None)
.unwrap();

println!("{}", signature);
}
147 changes: 6 additions & 141 deletions src/ssh/cert.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,10 @@ use std::fs::File;
use std::io::Read;
use std::path::Path;

use ring::{
digest,
rand::{SecureRandom, SystemRandom},
signature::{
RsaPublicKeyComponents, UnparsedPublicKey, ECDSA_P256_SHA256_FIXED,
ECDSA_P384_SHA384_FIXED, ED25519, RSA_PKCS1_2048_8192_SHA1_FOR_LEGACY_USE_ONLY,
RSA_PKCS1_2048_8192_SHA256, RSA_PKCS1_2048_8192_SHA512,
},
};

use super::SSHCertificateSigner;
use super::{
keytype::KeyType,
pubkey::{PublicKey, PublicKeyKind},
reader::Reader,
writer::Writer,
};
use ring::rand::{SecureRandom, SystemRandom};

use super::{keytype::KeyType, pubkey::PublicKey, reader::Reader, writer::Writer};
use super::{signature, SSHCertificateSigner};
use crate::{error::Error, Result};

use std::convert::TryFrom;
Expand Down Expand Up @@ -219,7 +206,7 @@ impl Certificate {
let signed_bytes = reader.read_raw_bytes(signed_len)?;

// Verify the certificate is properly signed
verify_signature(&signature, &signed_bytes, &signature_key)?;
signature::verify_signature(&signature, &signed_bytes, &signature_key)?;

Ok(Certificate {
key_type,
Expand Down Expand Up @@ -449,7 +436,7 @@ impl Certificate {
/// certificate under the set CA key.
pub fn add_signature(mut self, signature: &[u8]) -> Result<Self> {
let mut tbs = self.tbs_certificate();
verify_signature(signature, &tbs, &self.signature_key)?;
signature::verify_signature(signature, &tbs, &self.signature_key)?;

let mut wrapped_writer = Writer::new();
wrapped_writer.write_bytes(signature);
Expand Down Expand Up @@ -536,128 +523,6 @@ fn read_principals(buf: &[u8]) -> Result<Vec<String>> {
Ok(items)
}

/// Verifies the certificate's signature is valid.
fn verify_signature(
signature_buf: &[u8],
signed_bytes: &[u8],
public_key: &PublicKey,
) -> Result<Vec<u8>> {
let mut reader = Reader::new(&signature_buf);
let sig_type = reader.read_string().and_then(|v| KeyType::from_name(&v))?;

if public_key.key_type.kind != sig_type.kind {
return Err(Error::KeyTypeMismatch);
}

match &public_key.kind {
PublicKeyKind::Ecdsa(key) => {
let sig_reader = reader.read_bytes()?;
let mut sig_reader = Reader::new(&sig_reader);

let (alg, len) = match sig_type.name {
"ecdsa-sha2-nistp256" | "sk-ecdsa-sha2-nistp256@openssh.com" => {
(&ECDSA_P256_SHA256_FIXED, 32)
}
"ecdsa-sha2-nistp384" => (&ECDSA_P384_SHA384_FIXED, 48),
_ => return Err(Error::KeyTypeMismatch),
};

// Read the R value
let r_bytes = sig_reader.read_positive_mpint()?;
// Read the S value
let s_bytes = sig_reader.read_positive_mpint()?;

// (r/s)_bytes are user controlled so ensure maliciously signatures
// can't cause integer underflow.
if r_bytes.len() > len || s_bytes.len() > len {
return Err(Error::InvalidFormat);
}

// Determine and create the padding required
let mut r = vec![0; len - r_bytes.len()];
let mut s = vec![0; len - s_bytes.len()];

// Pad *_bytes
r.extend(r_bytes);
s.extend(s_bytes);

// Build a properly padded signature
let mut sig = r;
sig.extend(s);

if let Some(sk_application) = &key.sk_application {
let flags = reader.read_raw_bytes(1)?[0];
let signature_counter = reader.read_u32()?;

let mut app_hash = digest::digest(&digest::SHA256, sk_application.as_bytes())
.as_ref()
.to_vec();
let mut data_hash = digest::digest(&digest::SHA256, signed_bytes)
.as_ref()
.to_vec();

app_hash.push(flags);
app_hash.extend_from_slice(&signature_counter.to_be_bytes());
app_hash.append(&mut data_hash);

UnparsedPublicKey::new(alg, &key.key).verify(&app_hash, &sig)?;
} else {
UnparsedPublicKey::new(alg, &key.key).verify(signed_bytes, &sig)?;
}

Ok(signature_buf.to_vec())
}
PublicKeyKind::Rsa(key) => {
let alg = match sig_type.name {
"rsa-sha2-256" => &RSA_PKCS1_2048_8192_SHA256,
"rsa-sha2-512" => &RSA_PKCS1_2048_8192_SHA512,
"ssh-rsa" => &RSA_PKCS1_2048_8192_SHA1_FOR_LEGACY_USE_ONLY,
_ => return Err(Error::KeyTypeMismatch),
};
let signature = reader.read_bytes()?;
let public_key = RsaPublicKeyComponents {
n: &key.n,
e: &key.e,
};
public_key.verify(alg, signed_bytes, &signature)?;
Ok(signature_buf.to_vec())
}
PublicKeyKind::Ed25519(key) => {
match sig_type.name {
"ssh-ed25519" => (),
"sk-ssh-ed25519@openssh.com" => (),
_ => return Err(Error::KeyTypeMismatch),
};

let alg = &ED25519;
let signature = reader.read_bytes()?;
let peer_public_key = UnparsedPublicKey::new(alg, &key.key);

if let Some(sk_application) = &key.sk_application {
let flags = reader.read_raw_bytes(1)?[0];
let signature_counter = reader.read_u32()?;

let mut app_hash = digest::digest(&digest::SHA256, sk_application.as_bytes())
.as_ref()
.to_vec();
let mut data_hash = digest::digest(&digest::SHA256, signed_bytes)
.as_ref()
.to_vec();

app_hash.push(flags);
app_hash.extend_from_slice(&signature_counter.to_be_bytes());
app_hash.append(&mut data_hash);

peer_public_key.verify(&app_hash, &signature)?;
} else {
peer_public_key.verify(signed_bytes, &signature)?;
}

Ok(signature_buf.to_vec())
}
}
}

impl fmt::Display for Certificate {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if !f.alternate() {
Expand Down
2 changes: 2 additions & 0 deletions src/ssh/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ mod keytype;
mod privkey;
mod pubkey;
mod reader;
mod signature;
mod writer;

/// Types that implement this trait can be used to sign SSH certificates using
Expand All @@ -33,4 +34,5 @@ pub use self::pubkey::{
RsaPublicKey,
};
pub use self::reader::Reader;
pub use self::signature::{HashAlgorithm, SshSignature, VerifiedSshSignature};
pub use self::writer::Writer;
Loading

0 comments on commit bfddbf2

Please sign in to comment.