@@ -18,6 +18,7 @@ use std::collections::HashSet;
1818use std:: fmt;
1919use std:: fmt:: Display ;
2020use std:: sync:: LazyLock ;
21+ use base64:: Engine ;
2122use openssl:: pkey;
2223use openssl:: bn:: { BigNum , BigNumContext } ;
2324use openssl:: ec:: { EcGroup , EcKey , EcPoint } ;
@@ -27,6 +28,17 @@ use openssl::pkey::PKey;
2728use yubihsmrs:: object:: { ObjectAlgorithm , ObjectCapability , ObjectDescriptor , ObjectHandle , ObjectOrigin , ObjectType } ;
2829use 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+
3042use crate :: error:: MgmError ;
3143use crate :: MAIN_STRING ;
3244use 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+
626662fn 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 (
0 commit comments