Skip to content

Commit a370c58

Browse files
committed
Asym: Replace OpenSSL dependency when returning public key
1 parent a7dadf7 commit a370c58

3 files changed

Lines changed: 141 additions & 91 deletions

File tree

Cargo.toml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,14 @@ cliclack = "0.1.9"
4040
console = "0.15.7"
4141
comfy-table = "7.0.1"
4242

43+
rsa = "0.9.2"
44+
k256 = { version = "0.13.1", features = ["ecdsa-core"] }
45+
p224 = { version = "0.13.1", features = ["ecdsa"] }
46+
p256 = { version = "0.13.2", features = ["ecdsa-core"] }
47+
p384 = { version = "0.13.0", features = ["ecdsa"] }
48+
p521 = { version = "0.13.0", features = ["ecdsa"] }
49+
spki = { version = "0.7.2", features = ["pem"] }
50+
4351

4452
[dependencies.yubihsmrs]
4553
path = "../yubihsmrs/"

src/asym_commands.rs

Lines changed: 127 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ use std::collections::HashSet;
1818
use std::fmt;
1919
use std::fmt::Display;
2020
use std::sync::LazyLock;
21+
use base64::Engine;
2122
use openssl::pkey;
2223
use openssl::bn::{BigNum, BigNumContext};
2324
use openssl::ec::{EcGroup, EcKey, EcPoint};
@@ -27,6 +28,17 @@ use openssl::pkey::PKey;
2728
use yubihsmrs::object::{ObjectAlgorithm, ObjectCapability, ObjectDescriptor, ObjectHandle, ObjectOrigin, ObjectType};
2829
use yubihsmrs::Session;
2930

31+
use rsa::{RsaPublicKey, traits::PublicKeyParts};
32+
use p224::{ecdsa::VerifyingKey as VerifyingKeyP224, EncodedPoint as EncodedPointP224};
33+
use k256::{ecdsa::VerifyingKey as VerifyingKeyK256, EncodedPoint as EncodedPointK256};
34+
use p256::{ecdsa::VerifyingKey as VerifyingKeyP256, EncodedPoint as EncodedPointP256};
35+
use p384::{ecdsa::VerifyingKey as VerifyingKeyP384, EncodedPoint as EncodedPointP384};
36+
use p521::{PublicKey as PublicKeyP521, EncodedPoint as EncodedPointP521, elliptic_curve::sec1::FromEncodedPoint};
37+
38+
use spki::{
39+
der::pem::LineEnding, EncodePublicKey,
40+
};
41+
3042
use crate::error::MgmError;
3143
use crate::MAIN_STRING;
3244
use crate::util::{convert_handlers, get_ec_pubkey_from_pem_string, get_file_path, get_new_object_basics, get_op_key, list_objects, print_object_properties, read_input_bytes, read_input_string, read_pem_file, read_string_from_file, select_one_object, write_bytes_to_file};
@@ -502,127 +514,151 @@ fn get_public_key(session: &Session) -> Result<(), MgmError> {
502514
}
503515
};
504516

505-
let pem_pubkey;
517+
let pem_pubkey: String;
506518
let key_algo = pubkey.1;
507519
if RSA_KEY_ALGORITHM.contains(&key_algo) {
508-
let e = match BigNum::from_slice(&[0x01, 0x00, 0x01]) {
509-
Ok(bn) => bn,
510-
Err(err) => {
511-
cliclack::log::error(format!("Failed to construct exponent for key 0x{:04x}. {}", key.id, err))?;
512-
return Ok(())
513-
}
514-
};
520+
let modulus = rsa::BigUint::from_bytes_be(pubkey.0.clone().as_slice());
521+
let exponent = rsa::BigUint::from_bytes_be(&[0x01, 0x00, 0x01]);
522+
let rsa_pubkey:RsaPublicKey = RsaPublicKey::new(modulus, exponent).unwrap();
523+
pem_pubkey = rsa_pubkey.to_public_key_pem(rsa::pkcs8::LineEnding::LF).unwrap();
515524

516-
let n = match BigNum::from_slice(pubkey.0.as_slice()) {
517-
Ok(bn) => bn,
518-
Err(err) => {
519-
cliclack::log::error(format!("Failed to construct n for key 0x{:04x}. {}", key.id, err))?;
520-
return Ok(())
525+
} else if EC_KEY_ALGORITHM.contains(&key_algo) {
526+
let mut ec_pubkey_bytes_1: Vec<u8> = Vec::new();
527+
ec_pubkey_bytes_1.push(0x04);
528+
ec_pubkey_bytes_1.extend(pubkey.0.clone());
529+
530+
pem_pubkey = match key_algo {
531+
ObjectAlgorithm::EcK256 => {
532+
// Repeat the process for k256
533+
let point = EncodedPointK256::from_bytes(&ec_pubkey_bytes_1)
534+
.map_err(|e| MgmError::Error(format!("Failed to create encoded point for K256: {}", e)))?;
535+
let key = VerifyingKeyK256::from_encoded_point(&point)
536+
.map_err(|e| MgmError::Error(format!("Failed to parse K256 public key: {}", e)))?;
537+
key.to_public_key_pem(Default::default())
538+
.map_err(|e| MgmError::Error(format!("Failed to encode K256 key to PEM: {}", e)))?
521539
}
522-
};
523-
524-
let rsa_pubkey = match openssl::rsa::Rsa::from_public_components(n, e) {
525-
Ok(rsa) => rsa,
526-
Err(err) => {
527-
cliclack::log::error(format!("Failed to parse RSA public key for key 0x{:04x}. {}", key.id, err))?;
528-
return Ok(())
540+
ObjectAlgorithm::EcP224 => {
541+
let point = EncodedPointP224::from_bytes(&ec_pubkey_bytes_1).map_err(|e| MgmError::Error(e.to_string()))?;
542+
let key = VerifyingKeyP224::from_encoded_point(&point).map_err(|e| MgmError::Error(e.to_string()))?;
543+
key.to_public_key_pem(Default::default()).map_err(|e| MgmError::Error(e.to_string()))?
529544
}
530-
};
531-
532-
pem_pubkey = match rsa_pubkey.public_key_to_pem() {
533-
Ok(pem) => pem,
534-
Err(err) => {
535-
cliclack::log::error(format!("Failed to convert RSA public key to PEM format for key 0x{:04x}. {}", key.id, err))?;
536-
return Ok(())
545+
ObjectAlgorithm::EcP256 => {
546+
let point = EncodedPointP256::from_bytes(&ec_pubkey_bytes_1)
547+
.map_err(|e| MgmError::Error(e.to_string()))?;
548+
let key = VerifyingKeyP256::from_encoded_point(&point)
549+
.map_err(|e| MgmError::Error(e.to_string()))?;
550+
key.to_public_key_pem(Default::default())
551+
.map_err(|e| MgmError::Error(e.to_string()))?
537552
}
538-
};
539-
} else if EC_KEY_ALGORITHM.contains(&key_algo) {
540-
let nid = match key_algo {
541-
ObjectAlgorithm::EcP256 => Nid::X9_62_PRIME256V1,
542-
ObjectAlgorithm::EcK256 => Nid::SECP256K1,
543-
ObjectAlgorithm::EcP384 => Nid::SECP384R1,
544-
ObjectAlgorithm::EcP521 => Nid::SECP521R1,
545-
ObjectAlgorithm::EcP224 => Nid::SECP224R1,
546-
ObjectAlgorithm::EcBp256 => Nid::BRAINPOOL_P256R1,
547-
ObjectAlgorithm::EcBp384 => Nid::BRAINPOOL_P384R1,
548-
ObjectAlgorithm::EcBp512 => Nid::BRAINPOOL_P512R1,
549-
_ => unreachable!()
550-
};
551-
let ec_group = match EcGroup::from_curve_name(nid) {
552-
Ok(group) => group,
553-
Err(err) => {
554-
cliclack::log::error(format!("Failed to get EC group from key algorithm for key 0x{:04x}. {}", key.id, err))?;
555-
return Ok(())
553+
ObjectAlgorithm::EcP384 => {
554+
let point = EncodedPointP384::from_bytes(&ec_pubkey_bytes_1).map_err(|e| MgmError::Error(e.to_string()))?;
555+
let key = VerifyingKeyP384::from_encoded_point(&point).map_err(|e| MgmError::Error(e.to_string()))?;
556+
key.to_public_key_pem(Default::default()).map_err(|e| MgmError::Error(e.to_string()))?
556557
}
557-
};
558-
559-
let mut ctx = match BigNumContext::new() {
560-
Ok(bnc) => bnc,
561-
Err(err) => {
562-
cliclack::log::error(format!("Failed to create BigNumContext for key 0x{:04x}. {}", key.id, err))?;
563-
return Ok(())
558+
ObjectAlgorithm::EcP521 => {
559+
let point = EncodedPointP521::from_bytes(&ec_pubkey_bytes_1).unwrap();
560+
let key = PublicKeyP521::from_encoded_point(&point).unwrap();
561+
562+
// The EncodePublicKey trait handles OIDs and SPKI structure automatically
563+
let spki = key.to_public_key_der().unwrap();
564+
let pem = spki.to_pem("PUBLIC KEY", LineEnding::LF).unwrap();
565+
String::from_utf8(pem.as_bytes().to_vec()).unwrap()
564566
}
565-
};
567+
ObjectAlgorithm::EcBp256 | ObjectAlgorithm::EcBp384 | ObjectAlgorithm::EcBp512 => {
568+
// Pre-encoded OIDs (DER TLVs)
569+
const OID_EC_PUB_KEY: &[u8] = &[0x06, 0x07, 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x02, 0x01];
570+
let oid_brainpool: &[u8] = match key_algo {
571+
ObjectAlgorithm::EcBp256 => &[0x06, 0x0A, 0x2B, 0x24, 0x03, 0x03, 0x02, 0x08, 0x01, 0x01, 0x07],
572+
ObjectAlgorithm::EcBp384 => &[0x06, 0x09, 0x2B, 0x24, 0x03, 0x03, 0x02, 0x08, 0x01, 0x01, 0x0B],
573+
ObjectAlgorithm::EcBp512 => &[0x06, 0x09, 0x2B, 0x24, 0x03, 0x03, 0x02, 0x08, 0x01, 0x01, 0x0D],
574+
_ => unreachable!(),
575+
};
566576

567-
let mut ec_pubkey_bytes: Vec<u8> = Vec::new();
568-
ec_pubkey_bytes.push(0x04);
569-
ec_pubkey_bytes.extend(pubkey.0);
570-
let ec_point = match EcPoint::from_bytes(&ec_group, ec_pubkey_bytes.as_slice(), &mut ctx) {
571-
Ok(p) => p,
572-
Err(err) => {
573-
cliclack::log::error(format!("Failed to parse EC point for key 0x{:04x}. {}", key.id, err))?;
574-
return Ok(())
575-
}
576-
};
577+
// AlgorithmIdentifier = SEQUENCE { id-ecPublicKey, brainpoolP256r1 }
578+
// We'll build: 30 <len> <ID_EC_PUB_KEY> <OID_BRAINPOOL_P256R1>
579+
let mut alg_id = Vec::new();
580+
alg_id.push(0x30);
581+
push_der_length(&mut alg_id, OID_EC_PUB_KEY.len() + oid_brainpool.len());
582+
alg_id.extend_from_slice(OID_EC_PUB_KEY);
583+
alg_id.extend_from_slice(oid_brainpool);
577584

578-
let ec_pubkey = match EcKey::from_public_key(&ec_group, &ec_point) {
579-
Ok(pk) => pk,
580-
Err(err) => {
581-
cliclack::log::error(format!("Failed to parse EC public key for key 0x{:04x}. {}", key.id, err))?;
582-
return Ok(())
585+
get_spki_pem_string(ec_pubkey_bytes_1.as_slice(), alg_id)
583586
}
587+
_ => {"Unsupported curve".to_string()}
584588
};
585589

586-
pem_pubkey = match ec_pubkey.public_key_to_pem() {
587-
Ok(pem) => pem,
588-
Err(err) => {
589-
cliclack::log::error(format!("Failed to convert EC public key to PEM format for key 0x{:04x}. {}", key.id, err))?;
590-
return Ok(())
591-
}
592-
};
593590
} else if key_algo == ObjectAlgorithm::Ed25519 {
594-
let ed_pubkey = match PKey::public_key_from_raw_bytes(pubkey.0.as_slice(), pkey::Id::ED25519) {
595-
Ok(pk) => pk,
596-
Err(err) => {
597-
cliclack::log::error(format!("Failed to parse ED public key for key 0x{:04x}. {}", key.id, err))?;
598-
return Ok(());
599-
}
600-
};
591+
let ED25519_OID: &[u8] = &[0x06, 0x03, 0x2B, 0x65, 0x70];
592+
let mut algo_seq = Vec::new();
593+
algo_seq.push(0x30);
594+
push_der_length(&mut algo_seq, ED25519_OID.len());
595+
algo_seq.extend_from_slice(ED25519_OID);
596+
pem_pubkey = get_spki_pem_string(pubkey.0.as_slice(), algo_seq);
601597

602-
pem_pubkey = match ed_pubkey.public_key_to_pem() {
603-
Ok(pem) => pem,
604-
Err(err) => {
605-
cliclack::log::error(format!("Failed to convert ED public key to PEM format for key 0x{:04x}. {}", key.id, err))?;
606-
return Ok(())
607-
}
608-
};
609598
} else {
610599
cliclack::log::error(format!("Object 0x{:04x} is not an asymmetric key", key.id))?;
611600
return Ok(())
612601
}
613602

614-
if let Ok(str) = String::from_utf8(pem_pubkey.clone()) { println!("{}\n", str) }
603+
println!("{}\n", pem_pubkey);
615604

616605
if cliclack::confirm("Write to file?").interact()? {
617606
let filename = format!("0x{:04x}.pubkey.pem", key.id);
618-
if let Err(err) = write_bytes_to_file(pem_pubkey, "", filename.as_str()) {
607+
if let Err(err) = write_bytes_to_file(pem_pubkey.into_bytes(), "", filename.as_str()) {
619608
cliclack::log::error(
620609
format!("Failed to write public key 0x{:04x} to file. {}", key.id, err))?;
621610
}
622611
}
623612
Ok(())
624613
}
625614

615+
fn get_spki_pem_string(pubkey_raw: &[u8], algorithm_seq: Vec<u8>) -> String {
616+
let mut bit_string = Vec::new();
617+
{
618+
bit_string.push(0x03);
619+
push_der_length(&mut bit_string, 1 + pubkey_raw.len());
620+
bit_string.push(0x00); // unused bits
621+
bit_string.extend_from_slice(pubkey_raw);
622+
}
623+
624+
// Outer SEQUENCE
625+
let mut spki = Vec::new();
626+
{
627+
spki.push(0x30);
628+
push_der_length(&mut spki, algorithm_seq.len() + bit_string.len());
629+
spki.extend_from_slice(&algorithm_seq);
630+
spki.extend_from_slice(&bit_string);
631+
}
632+
633+
// Base64 encode and format lines (typically 64-char lines)
634+
let b64 = base64::engine::general_purpose::STANDARD.encode(&spki);
635+
let mut pem_body = String::new();
636+
for chunk in b64.as_bytes().chunks(64) {
637+
pem_body.push_str(std::str::from_utf8(chunk).unwrap());
638+
pem_body.push('\n');
639+
}
640+
641+
let mut pem = String::new();
642+
pem.push_str("-----BEGIN PUBLIC KEY-----\n");
643+
pem.push_str(&pem_body);
644+
pem.push_str("-----END PUBLIC KEY-----\n");
645+
pem
646+
}
647+
648+
/// Encode DER length (definite, short or long form)
649+
fn push_der_length(buf: &mut Vec<u8>, len: usize) {
650+
if len < 0x80 {
651+
buf.push(len as u8);
652+
} else if len < 0x100 {
653+
buf.push(0x81);
654+
buf.push(len as u8);
655+
} else {
656+
buf.push(0x82);
657+
buf.push(((len >> 8) & 0xFF) as u8);
658+
buf.push((len & 0xFF) as u8);
659+
}
660+
}
661+
626662
fn get_cert(session: &Session) -> Result<(), MgmError> {
627663
let certs = session.list_objects_with_filter(0, ObjectType::Opaque, "", ObjectAlgorithm::OpaqueX509Certificate, &Vec::new())?;
628664
let cert = select_one_object(

src/main.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,12 @@ extern crate serde;
2828
extern crate yubihsmrs;
2929
extern crate comfy_table;
3030

31+
extern crate rsa;
32+
extern crate k256;
33+
extern crate p224;
34+
extern crate p256;
35+
extern crate p384;
36+
extern crate p521;
3137

3238
use std::fs;
3339
use std::str::FromStr;

0 commit comments

Comments
 (0)