use base64::Engine;
use openssl::{
bn::BigNumRef,
ec::{EcGroup, EcKey},
hash::MessageDigest,
nid::Nid,
pkey::{PKey, Private},
rsa::Rsa,
sign::Signer,
};
use rand::Rng;
use crate::errors::RvError;
pub const SSH_CERT_TYPE_USER: u32 = 1;
pub const SSH_CERT_TYPE_HOST: u32 = 2;
pub fn encode_string(data: &[u8]) -> Result<Vec<u8>, RvError> {
let len: u32 = data.len().try_into().map_err(|_| RvError::ErrPkiInternal)?;
let mut buf = Vec::with_capacity(4 + data.len());
buf.extend_from_slice(&len.to_be_bytes());
buf.extend_from_slice(data);
Ok(buf)
}
pub fn encode_u32(v: u32) -> Vec<u8> {
v.to_be_bytes().to_vec()
}
pub fn encode_u64(v: u64) -> Vec<u8> {
v.to_be_bytes().to_vec()
}
pub fn encode_mpint(bn: &BigNumRef) -> Result<Vec<u8>, RvError> {
let bytes = bn.to_vec();
if bytes.is_empty() {
return encode_string(&[]);
}
if bytes[0] & 0x80 != 0 {
let mut padded = Vec::with_capacity(1 + bytes.len());
padded.push(0);
padded.extend_from_slice(&bytes);
encode_string(&padded)
} else {
encode_string(&bytes)
}
}
pub fn encode_rsa_pubkey(pkey: &PKey<Private>) -> Result<Vec<u8>, RvError> {
let rsa = pkey.rsa()?;
let mut buf = Vec::new();
buf.extend_from_slice(&encode_string(b"ssh-rsa")?);
buf.extend_from_slice(&encode_mpint(rsa.e())?);
buf.extend_from_slice(&encode_mpint(rsa.n())?);
Ok(buf)
}
pub fn encode_ec_pubkey(pkey: &PKey<Private>, nid: Nid) -> Result<Vec<u8>, RvError> {
let ec = pkey.ec_key()?;
let group = ec.group();
let mut ctx = openssl::bn::BigNumContext::new()?;
let point_bytes = ec.public_key().to_bytes(
group,
openssl::ec::PointConversionForm::UNCOMPRESSED,
&mut ctx,
)?;
let (key_type, curve_id) = match nid {
Nid::X9_62_PRIME256V1 => ("ecdsa-sha2-nistp256", "nistp256"),
Nid::SECP384R1 => ("ecdsa-sha2-nistp384", "nistp384"),
Nid::SECP521R1 => ("ecdsa-sha2-nistp521", "nistp521"),
_ => return Err(RvError::ErrPkiKeyTypeInvalid),
};
let mut buf = Vec::new();
buf.extend_from_slice(&encode_string(key_type.as_bytes())?);
buf.extend_from_slice(&encode_string(curve_id.as_bytes())?);
buf.extend_from_slice(&encode_string(&point_bytes)?);
Ok(buf)
}
pub fn encode_ed25519_pubkey(pkey: &PKey<Private>) -> Result<Vec<u8>, RvError> {
let raw = pkey.raw_public_key()?;
let mut buf = Vec::new();
buf.extend_from_slice(&encode_string(b"ssh-ed25519")?);
buf.extend_from_slice(&encode_string(&raw)?);
Ok(buf)
}
pub fn ssh_key_type_str(pkey: &PKey<Private>, nid: Option<Nid>) -> Result<&'static str, RvError> {
match pkey.id() {
openssl::pkey::Id::RSA => Ok("ssh-rsa"),
openssl::pkey::Id::EC => match nid {
Some(Nid::X9_62_PRIME256V1) => Ok("ecdsa-sha2-nistp256"),
Some(Nid::SECP384R1) => Ok("ecdsa-sha2-nistp384"),
Some(Nid::SECP521R1) => Ok("ecdsa-sha2-nistp521"),
_ => Err(RvError::ErrPkiKeyTypeInvalid),
},
openssl::pkey::Id::ED25519 => Ok("ssh-ed25519"),
_ => Err(RvError::ErrPkiKeyTypeInvalid),
}
}
pub fn ssh_cert_type_str(pkey: &PKey<Private>, nid: Option<Nid>) -> Result<&'static str, RvError> {
match pkey.id() {
openssl::pkey::Id::RSA => Ok("ssh-rsa-cert-v01@openssh.com"),
openssl::pkey::Id::EC => match nid {
Some(Nid::X9_62_PRIME256V1) => Ok("ecdsa-sha2-nistp256-cert-v01@openssh.com"),
Some(Nid::SECP384R1) => Ok("ecdsa-sha2-nistp384-cert-v01@openssh.com"),
Some(Nid::SECP521R1) => Ok("ecdsa-sha2-nistp521-cert-v01@openssh.com"),
_ => Err(RvError::ErrPkiKeyTypeInvalid),
},
openssl::pkey::Id::ED25519 => Ok("ssh-ed25519-cert-v01@openssh.com"),
_ => Err(RvError::ErrPkiKeyTypeInvalid),
}
}
pub fn encode_pubkey_for_cert(pkey: &PKey<Private>, nid: Option<Nid>) -> Result<Vec<u8>, RvError> {
match pkey.id() {
openssl::pkey::Id::RSA => {
let rsa = pkey.rsa()?;
let mut buf = Vec::new();
buf.extend_from_slice(&encode_mpint(rsa.e())?);
buf.extend_from_slice(&encode_mpint(rsa.n())?);
Ok(buf)
}
openssl::pkey::Id::EC => {
let ec = pkey.ec_key()?;
let group = ec.group();
let mut ctx = openssl::bn::BigNumContext::new()?;
let point_bytes = ec.public_key().to_bytes(
group,
openssl::ec::PointConversionForm::UNCOMPRESSED,
&mut ctx,
)?;
let curve_id = match nid {
Some(Nid::X9_62_PRIME256V1) => "nistp256",
Some(Nid::SECP384R1) => "nistp384",
Some(Nid::SECP521R1) => "nistp521",
_ => return Err(RvError::ErrPkiKeyTypeInvalid),
};
let mut buf = Vec::new();
buf.extend_from_slice(&encode_string(curve_id.as_bytes())?);
buf.extend_from_slice(&encode_string(&point_bytes)?);
Ok(buf)
}
openssl::pkey::Id::ED25519 => {
let raw = pkey.raw_public_key()?;
Ok(encode_string(&raw)?)
}
_ => Err(RvError::ErrPkiKeyTypeInvalid),
}
}
pub fn build_ssh_certificate(
cert_type_str: &str,
user_pubkey_data: &[u8],
serial: u64,
key_id: &str,
valid_principals: &[String],
valid_after: u64,
valid_before: u64,
cert_type: u32,
extensions: &std::collections::HashMap<String, String>,
ca_key: &PKey<Private>,
ca_nid: Option<Nid>,
) -> Result<Vec<u8>, RvError> {
let nonce: [u8; 32] = rand::rng().random();
let mut cert = Vec::new();
cert.extend_from_slice(&encode_string(cert_type_str.as_bytes())?);
cert.extend_from_slice(&encode_string(&nonce)?);
cert.extend_from_slice(user_pubkey_data);
cert.extend_from_slice(&encode_u64(serial));
cert.extend_from_slice(&encode_u32(cert_type));
cert.extend_from_slice(&encode_string(key_id.as_bytes())?);
let mut principals_buf = Vec::new();
for p in valid_principals {
principals_buf.extend_from_slice(&encode_string(p.as_bytes())?);
}
cert.extend_from_slice(&encode_string(&principals_buf)?);
cert.extend_from_slice(&encode_u64(valid_after));
cert.extend_from_slice(&encode_u64(valid_before));
cert.extend_from_slice(&encode_string(&[])?);
let mut ext_buf = Vec::new();
let mut sorted_keys: Vec<&String> = extensions.keys().collect();
sorted_keys.sort();
for k in sorted_keys {
let v = &extensions[k];
ext_buf.extend_from_slice(&encode_string(k.as_bytes())?);
ext_buf.extend_from_slice(&encode_string(v.as_bytes())?);
}
cert.extend_from_slice(&encode_string(&ext_buf)?);
cert.extend_from_slice(&encode_string(&[])?);
let ca_pubkey_wire = encode_ca_pubkey(ca_key, ca_nid)?;
cert.extend_from_slice(&encode_string(&ca_pubkey_wire)?);
let signature = sign_ssh_data(ca_key, &cert)?;
cert.extend_from_slice(&encode_string(&signature)?);
Ok(cert)
}
fn encode_ca_pubkey(pkey: &PKey<Private>, nid: Option<Nid>) -> Result<Vec<u8>, RvError> {
match pkey.id() {
openssl::pkey::Id::RSA => encode_rsa_pubkey(pkey),
openssl::pkey::Id::EC => encode_ec_pubkey(pkey, nid.unwrap_or(Nid::X9_62_PRIME256V1)),
openssl::pkey::Id::ED25519 => encode_ed25519_pubkey(pkey),
_ => Err(RvError::ErrPkiKeyTypeInvalid),
}
}
fn sign_ssh_data(pkey: &PKey<Private>, data: &[u8]) -> Result<Vec<u8>, RvError> {
match pkey.id() {
openssl::pkey::Id::RSA => {
let mut signer = Signer::new(MessageDigest::sha512(), pkey)?;
signer.set_rsa_padding(openssl::rsa::Padding::PKCS1)?;
signer.update(data)?;
let sig = signer.sign_to_vec()?;
let mut buf = Vec::new();
buf.extend_from_slice(&encode_string(b"rsa-sha2-512")?);
buf.extend_from_slice(&encode_string(&sig)?);
Ok(buf)
}
openssl::pkey::Id::EC => {
let ec = pkey.ec_key()?;
let nid = ec.group().curve_name().unwrap_or(Nid::X9_62_PRIME256V1);
let md = match nid {
Nid::X9_62_PRIME256V1 => MessageDigest::sha256(),
Nid::SECP384R1 => MessageDigest::sha384(),
Nid::SECP521R1 => MessageDigest::sha512(),
_ => return Err(RvError::ErrPkiKeyTypeInvalid),
};
let sig_type = match nid {
Nid::X9_62_PRIME256V1 => "ecdsa-sha2-nistp256",
Nid::SECP384R1 => "ecdsa-sha2-nistp384",
Nid::SECP521R1 => "ecdsa-sha2-nistp521",
_ => return Err(RvError::ErrPkiKeyTypeInvalid),
};
let mut signer = Signer::new(md, pkey)?;
signer.update(data)?;
let sig = signer.sign_to_vec()?;
let mut buf = Vec::new();
buf.extend_from_slice(&encode_string(sig_type.as_bytes())?);
buf.extend_from_slice(&encode_string(&sig)?);
Ok(buf)
}
openssl::pkey::Id::ED25519 => {
let mut signer = Signer::new_without_digest(pkey)?;
let sig = signer.sign_oneshot_to_vec(data)?;
let mut buf = Vec::new();
buf.extend_from_slice(&encode_string(b"ssh-ed25519")?);
buf.extend_from_slice(&encode_string(&sig)?);
Ok(buf)
}
_ => Err(RvError::ErrPkiKeyTypeInvalid),
}
}
pub fn generate_ssh_keypair(
key_type: &str,
key_bits: u32,
) -> Result<(PKey<Private>, Option<Nid>), RvError> {
match key_type {
"rsa" => {
let bits = if key_bits == 0 { 2048 } else { key_bits };
let rsa = Rsa::generate(bits)?;
let pkey = PKey::from_rsa(rsa)?;
Ok((pkey, None))
}
"ec" => {
let (nid, _bits) = match key_bits {
0 | 256 => (Nid::X9_62_PRIME256V1, 256),
384 => (Nid::SECP384R1, 384),
521 => (Nid::SECP521R1, 521),
_ => return Err(RvError::ErrPkiKeyBitsInvalid),
};
let group = EcGroup::from_curve_name(nid)?;
let ec = EcKey::generate(&group)?;
let pkey = PKey::from_ec_key(ec)?;
Ok((pkey, Some(nid)))
}
"ed25519" => {
let pkey = PKey::generate_ed25519()?;
Ok((pkey, None))
}
_ => Err(RvError::ErrPkiKeyTypeInvalid),
}
}
pub fn format_openssh_pubkey(pkey: &PKey<Private>, nid: Option<Nid>) -> Result<String, RvError> {
let wire = encode_ca_pubkey(pkey, nid)?;
let key_type = ssh_key_type_str(pkey, nid)?;
Ok(format!(
"{} {}",
key_type,
base64::engine::general_purpose::STANDARD.encode(&wire)
))
}
pub fn format_openssh_cert(cert_type_str: &str, cert_bytes: &[u8]) -> String {
format!(
"{} {}",
cert_type_str,
base64::engine::general_purpose::STANDARD.encode(cert_bytes)
)
}