WIP: Handling of public key material from cards
This commit is contained in:
parent
3edadb8607
commit
44d5abd7ed
4 changed files with 146 additions and 69 deletions
|
@ -4,31 +4,32 @@
|
||||||
//! This library supports using openpgp-card functionality with
|
//! This library supports using openpgp-card functionality with
|
||||||
//! sequoia_openpgp data structures.
|
//! sequoia_openpgp data structures.
|
||||||
|
|
||||||
|
use anyhow::{anyhow, Context, Result};
|
||||||
use std::convert::TryFrom;
|
use std::convert::TryFrom;
|
||||||
use std::convert::TryInto;
|
use std::convert::TryInto;
|
||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
use std::io;
|
use std::io;
|
||||||
use std::time::SystemTime;
|
use std::time::SystemTime;
|
||||||
|
|
||||||
use anyhow::{anyhow, Context, Result};
|
|
||||||
use openpgp::armor;
|
use openpgp::armor;
|
||||||
use openpgp::cert::amalgamation::key::ValidErasedKeyAmalgamation;
|
use openpgp::cert::amalgamation::key::ValidErasedKeyAmalgamation;
|
||||||
use openpgp::crypto::mpi;
|
use openpgp::crypto::mpi;
|
||||||
use openpgp::crypto::mpi::{ProtectedMPI, MPI};
|
use openpgp::crypto::mpi::{ProtectedMPI, MPI};
|
||||||
|
use openpgp::packet::key::{Key4, PublicParts};
|
||||||
use openpgp::packet::key::{SecretParts, UnspecifiedRole};
|
use openpgp::packet::key::{SecretParts, UnspecifiedRole};
|
||||||
use openpgp::packet::{key, Key};
|
use openpgp::packet::{key, Key};
|
||||||
use openpgp::parse::{stream::DecryptorBuilder, Parse};
|
use openpgp::parse::{stream::DecryptorBuilder, Parse};
|
||||||
use openpgp::policy::StandardPolicy;
|
use openpgp::policy::StandardPolicy;
|
||||||
use openpgp::serialize::stream::{Message, Signer};
|
use openpgp::serialize::stream::{Message, Signer};
|
||||||
|
use openpgp::types::Timestamp;
|
||||||
use sequoia_openpgp as openpgp;
|
use sequoia_openpgp as openpgp;
|
||||||
|
|
||||||
use openpgp_card::card_app::CardApp;
|
use openpgp_card::card_app::CardApp;
|
||||||
use openpgp_card::{
|
use openpgp_card::{
|
||||||
errors::OpenpgpCardError, CardAdmin, CardUploadableKey, EccKey, EccType,
|
errors::OpenpgpCardError, Algo, CardAdmin, CardUploadableKey, Curve,
|
||||||
KeyType, PrivateKeyMaterial, PublicKeyMaterial, RSAKey,
|
EccKey, EccType, KeyType, PrivateKeyMaterial, PublicKeyMaterial, RSAKey,
|
||||||
};
|
};
|
||||||
use sequoia_openpgp::packet::key::{Key4, PublicParts};
|
use sequoia_openpgp::types::PublicKeyAlgorithm;
|
||||||
use sequoia_openpgp::types::Timestamp;
|
|
||||||
|
|
||||||
mod decryptor;
|
mod decryptor;
|
||||||
mod signer;
|
mod signer;
|
||||||
|
@ -74,6 +75,7 @@ pub fn vka_as_uploadable_key(
|
||||||
/// Helper fn: get a Key<PublicParts, UnspecifiedRole> for a PublicKeyMaterial
|
/// Helper fn: get a Key<PublicParts, UnspecifiedRole> for a PublicKeyMaterial
|
||||||
pub fn public_key_material_to_key(
|
pub fn public_key_material_to_key(
|
||||||
pkm: &PublicKeyMaterial,
|
pkm: &PublicKeyMaterial,
|
||||||
|
key_type: KeyType,
|
||||||
time: SystemTime,
|
time: SystemTime,
|
||||||
) -> Result<Key<PublicParts, UnspecifiedRole>> {
|
) -> Result<Key<PublicParts, UnspecifiedRole>> {
|
||||||
match pkm {
|
match pkm {
|
||||||
|
@ -83,7 +85,73 @@ pub fn public_key_material_to_key(
|
||||||
|
|
||||||
Ok(Key::from(k4))
|
Ok(Key::from(k4))
|
||||||
}
|
}
|
||||||
_ => unimplemented!("ECC not implemented yet"),
|
PublicKeyMaterial::E(ecc) => {
|
||||||
|
let algo = ecc.algo.clone(); // FIXME?
|
||||||
|
if let Algo::Ecc(algo_ecc) = algo {
|
||||||
|
match key_type {
|
||||||
|
KeyType::Authentication | KeyType::Signing => {
|
||||||
|
if algo_ecc.curve == Curve::Ed25519 {
|
||||||
|
// EdDSA
|
||||||
|
let k4: Key4<
|
||||||
|
key::PublicParts,
|
||||||
|
key::UnspecifiedRole,
|
||||||
|
> = Key4::import_public_ed25519(&ecc.data, time)?;
|
||||||
|
|
||||||
|
println!("k4 {:?}", k4);
|
||||||
|
|
||||||
|
Ok(Key::from(k4))
|
||||||
|
} else {
|
||||||
|
// ECDSA
|
||||||
|
|
||||||
|
// The public key for ECDSA/DH consists of of two raw
|
||||||
|
// big-endian integers with the same length as a field element
|
||||||
|
// each. In compliance with EN 419212 the format is 04 || x || y
|
||||||
|
// where the first byte (04) indicates an uncompressed raw format.
|
||||||
|
|
||||||
|
let ec = &ecc.data;
|
||||||
|
|
||||||
|
assert_eq!(ec[0], 0x4);
|
||||||
|
|
||||||
|
let len = ec.len();
|
||||||
|
assert_eq!(len % 2, 1); // odd number of bytes
|
||||||
|
|
||||||
|
// ---
|
||||||
|
|
||||||
|
let curve = match algo_ecc.curve {
|
||||||
|
Curve::NistP256r1 => {
|
||||||
|
openpgp::types::Curve::NistP256
|
||||||
|
}
|
||||||
|
Curve::NistP384r1 => {
|
||||||
|
openpgp::types::Curve::NistP384
|
||||||
|
}
|
||||||
|
Curve::NistP521r1 => {
|
||||||
|
openpgp::types::Curve::NistP521
|
||||||
|
}
|
||||||
|
_ => unimplemented!(),
|
||||||
|
};
|
||||||
|
|
||||||
|
// NIST
|
||||||
|
let k4 = Key4::new(
|
||||||
|
time,
|
||||||
|
PublicKeyAlgorithm::ECDSA,
|
||||||
|
mpi::PublicKey::ECDSA {
|
||||||
|
curve,
|
||||||
|
q: mpi::MPI::new(&ec),
|
||||||
|
},
|
||||||
|
)?;
|
||||||
|
|
||||||
|
Ok(Key::from(k4))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
KeyType::Decryption => {
|
||||||
|
unimplemented!("Decryption keys not implemented yet")
|
||||||
|
}
|
||||||
|
_ => unimplemented!("Unsupported KeyType"),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
panic!("unexpected algo {:?}", algo);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -18,16 +18,15 @@ use anyhow::{anyhow, Result};
|
||||||
use crate::apdu::{commands, response::Response};
|
use crate::apdu::{commands, response::Response};
|
||||||
use crate::errors::OpenpgpCardError;
|
use crate::errors::OpenpgpCardError;
|
||||||
use crate::parse::{
|
use crate::parse::{
|
||||||
algo_attrs::Algo, algo_attrs::RsaAttrs, algo_info::AlgoInfo,
|
algo_info::AlgoInfo, application_id::ApplicationId,
|
||||||
application_id::ApplicationId, cardholder::CardHolder,
|
cardholder::CardHolder, extended_cap::ExtendedCap,
|
||||||
extended_cap::ExtendedCap, extended_length_info::ExtendedLengthInfo,
|
extended_length_info::ExtendedLengthInfo, fingerprint,
|
||||||
fingerprint, historical::Historical, key_generation_times,
|
historical::Historical, key_generation_times, pw_status::PWStatus, KeySet,
|
||||||
pw_status::PWStatus, KeySet,
|
|
||||||
};
|
};
|
||||||
use crate::tlv::{tag::Tag, Tlv, TlvEntry};
|
use crate::tlv::{tag::Tag, Tlv, TlvEntry};
|
||||||
use crate::{
|
use crate::{
|
||||||
apdu, keys, CardCaps, CardClientBox, CardUploadableKey, DecryptMe,
|
apdu, keys, Algo, CardCaps, CardClientBox, CardUploadableKey, DecryptMe,
|
||||||
EccType, Hash, KeyGeneration, KeyType, PublicKeyMaterial, Sex,
|
EccType, Hash, KeyGeneration, KeyType, PublicKeyMaterial, RsaAttrs, Sex,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct CardApp {
|
pub struct CardApp {
|
||||||
|
@ -573,7 +572,7 @@ impl CardApp {
|
||||||
|
|
||||||
let data = match algo {
|
let data = match algo {
|
||||||
Algo::Rsa(rsa) => Self::rsa_algo_attrs(rsa)?,
|
Algo::Rsa(rsa) => Self::rsa_algo_attrs(rsa)?,
|
||||||
Algo::Ecc(ecc) => Self::ecc_algo_attrs(&ecc.oid, ecc.ecc_type),
|
Algo::Ecc(ecc) => Self::ecc_algo_attrs(ecc.oid(), ecc.ecc_type),
|
||||||
_ => unimplemented!(),
|
_ => unimplemented!(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -635,14 +634,22 @@ impl CardApp {
|
||||||
keys::upload_key(self, key, key_type, algo_list)
|
keys::upload_key(self, key, key_type, algo_list)
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME: use subset of CardUploadableKey to specify algo?
|
/// Generate a key on the card.
|
||||||
|
/// If the `algo` parameter is Some, then this algorithm will be set on
|
||||||
|
/// the card for "key_type".
|
||||||
pub fn generate_key(
|
pub fn generate_key(
|
||||||
&mut self,
|
&mut self,
|
||||||
fp_from_pub: fn(&PublicKeyMaterial, SystemTime) -> Result<[u8; 20]>,
|
fp_from_pub: fn(
|
||||||
|
&PublicKeyMaterial,
|
||||||
|
SystemTime,
|
||||||
|
KeyType,
|
||||||
|
&Algo,
|
||||||
|
) -> Result<[u8; 20]>,
|
||||||
key_type: KeyType,
|
key_type: KeyType,
|
||||||
) -> Result<(), OpenpgpCardError> {
|
algo: Option<&Algo>,
|
||||||
|
) -> Result<(PublicKeyMaterial, u32), OpenpgpCardError> {
|
||||||
// FIXME: specify algo; pass in algo list?
|
// FIXME: specify algo; pass in algo list?
|
||||||
keys::gen_key_with_metadata(self, fp_from_pub, key_type)
|
keys::gen_key_with_metadata(self, fp_from_pub, key_type, algo)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_pub_key(
|
pub fn get_pub_key(
|
||||||
|
|
|
@ -10,22 +10,47 @@ use crate::apdu::command::Command;
|
||||||
use crate::apdu::commands;
|
use crate::apdu::commands;
|
||||||
use crate::card_app::CardApp;
|
use crate::card_app::CardApp;
|
||||||
use crate::errors::OpenpgpCardError;
|
use crate::errors::OpenpgpCardError;
|
||||||
use crate::parse::algo_attrs::{Algo, EccAttrs, RsaAttrs};
|
|
||||||
use crate::parse::algo_info::AlgoInfo;
|
use crate::parse::algo_info::AlgoInfo;
|
||||||
use crate::tlv::{tag::Tag, Tlv, TlvEntry};
|
use crate::tlv::{tag::Tag, Tlv, TlvEntry};
|
||||||
use crate::{apdu, EccPub, PublicKeyMaterial, RSAPub};
|
use crate::{apdu, Curve, EccPub, PublicKeyMaterial, RSAPub};
|
||||||
use crate::{
|
use crate::{
|
||||||
tlv, CardUploadableKey, EccKey, KeyType, PrivateKeyMaterial, RSAKey,
|
tlv, Algo, CardUploadableKey, EccAttrs, EccKey, KeyType,
|
||||||
|
PrivateKeyMaterial, RSAKey, RsaAttrs,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// `gen_key_with_metadata` calculates the fingerprint for a public key
|
/// `gen_key_with_metadata` calculates the fingerprint for a public key
|
||||||
/// data object
|
/// data object
|
||||||
pub(crate) fn gen_key_with_metadata(
|
pub(crate) fn gen_key_with_metadata(
|
||||||
card_app: &mut CardApp,
|
card_app: &mut CardApp,
|
||||||
fp_from_pub: fn(&PublicKeyMaterial, SystemTime) -> Result<[u8; 20]>,
|
fp_from_pub: fn(
|
||||||
|
&PublicKeyMaterial,
|
||||||
|
SystemTime,
|
||||||
|
KeyType,
|
||||||
|
&Algo,
|
||||||
|
) -> Result<[u8; 20]>,
|
||||||
key_type: KeyType,
|
key_type: KeyType,
|
||||||
) -> Result<(), OpenpgpCardError> {
|
algo: Option<&Algo>,
|
||||||
let pubkey = gen_key(card_app, key_type)?;
|
) -> Result<(PublicKeyMaterial, u32), OpenpgpCardError> {
|
||||||
|
// set algo on card if it's Some
|
||||||
|
if let Some(algo) = algo {
|
||||||
|
println!("set algo {:?}", algo);
|
||||||
|
card_app
|
||||||
|
.set_algorithm_attributes(key_type, algo)?
|
||||||
|
.check_ok()?;
|
||||||
|
println!("set algo done");
|
||||||
|
}
|
||||||
|
|
||||||
|
// algo
|
||||||
|
let ard = card_app.get_app_data()?; // no caching, here!
|
||||||
|
let algo = CardApp::get_algorithm_attributes(&ard, key_type)?;
|
||||||
|
|
||||||
|
// generate key
|
||||||
|
let tlv = gen_key(card_app, key_type)?;
|
||||||
|
|
||||||
|
// derive pubkey
|
||||||
|
let pubkey = tlv_to_pubkey(&tlv, &algo)?;
|
||||||
|
|
||||||
|
log::trace!("public {:x?}", pubkey);
|
||||||
|
|
||||||
// set creation time
|
// set creation time
|
||||||
let time = SystemTime::now();
|
let time = SystemTime::now();
|
||||||
|
@ -39,13 +64,13 @@ pub(crate) fn gen_key_with_metadata(
|
||||||
card_app.set_creation_time(ts, key_type)?.check_ok()?;
|
card_app.set_creation_time(ts, key_type)?.check_ok()?;
|
||||||
|
|
||||||
// calculate/store fingerprint
|
// calculate/store fingerprint
|
||||||
let fp = fp_from_pub(&pubkey, time)?;
|
let fp = fp_from_pub(&pubkey, time, key_type, &algo)?;
|
||||||
card_app.set_fingerprint(fp, key_type)?.check_ok()?;
|
card_app.set_fingerprint(fp, key_type)?.check_ok()?;
|
||||||
|
|
||||||
Ok(())
|
Ok((pubkey, ts))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn tlv_to_pubkey(tlv: &Tlv) -> Result<PublicKeyMaterial> {
|
fn tlv_to_pubkey(tlv: &Tlv, algo: &Algo) -> Result<PublicKeyMaterial> {
|
||||||
let n = tlv.find(&Tag::new(vec![0x81]));
|
let n = tlv.find(&Tag::new(vec![0x81]));
|
||||||
let v = tlv.find(&Tag::new(vec![0x82]));
|
let v = tlv.find(&Tag::new(vec![0x82]));
|
||||||
|
|
||||||
|
@ -61,26 +86,13 @@ fn tlv_to_pubkey(tlv: &Tlv) -> Result<PublicKeyMaterial> {
|
||||||
Ok(PublicKeyMaterial::R(rsa))
|
Ok(PublicKeyMaterial::R(rsa))
|
||||||
}
|
}
|
||||||
(None, None, Some(ec)) => {
|
(None, None, Some(ec)) => {
|
||||||
let ec = ec.serialize();
|
let data = ec.serialize();
|
||||||
|
println!("EC --- len {}, data {:x?}", data.len(), data);
|
||||||
|
|
||||||
// The public key for ECDSA/DH consists of of two raw
|
Ok(PublicKeyMaterial::E(EccPub {
|
||||||
// big-endian integers with the same length as a field element
|
data,
|
||||||
// each. In compliance with EN 419212 the format is 04 || x || y
|
algo: algo.clone(),
|
||||||
// where the first byte (04) indicates an uncompressed raw format.
|
}))
|
||||||
|
|
||||||
assert_eq!(ec[0], 0x4);
|
|
||||||
|
|
||||||
let len = ec.len();
|
|
||||||
assert_eq!(len % 2, 1); // odd number of bytes
|
|
||||||
|
|
||||||
// len 3 -> 4/2 = 2
|
|
||||||
let middle = (len + 1) / 2;
|
|
||||||
let x = ec[1..middle].to_vec();
|
|
||||||
let y = ec[middle..].to_vec();
|
|
||||||
|
|
||||||
let ecc = EccPub { x, y };
|
|
||||||
|
|
||||||
Ok(PublicKeyMaterial::E(ecc))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
(_, _, _) => {
|
(_, _, _) => {
|
||||||
|
@ -92,7 +104,7 @@ fn tlv_to_pubkey(tlv: &Tlv) -> Result<PublicKeyMaterial> {
|
||||||
pub(crate) fn gen_key(
|
pub(crate) fn gen_key(
|
||||||
card_app: &mut CardApp,
|
card_app: &mut CardApp,
|
||||||
key_type: KeyType,
|
key_type: KeyType,
|
||||||
) -> Result<PublicKeyMaterial, OpenpgpCardError> {
|
) -> Result<Tlv, OpenpgpCardError> {
|
||||||
println!("gen key for {:?}", key_type);
|
println!("gen key for {:?}", key_type);
|
||||||
|
|
||||||
// generate key
|
// generate key
|
||||||
|
@ -106,11 +118,7 @@ pub(crate) fn gen_key(
|
||||||
|
|
||||||
let tlv = Tlv::try_from(resp.data()?)?;
|
let tlv = Tlv::try_from(resp.data()?)?;
|
||||||
|
|
||||||
let pubkey = tlv_to_pubkey(&tlv)?;
|
Ok(tlv)
|
||||||
|
|
||||||
log::trace!("public {:x?}", pubkey);
|
|
||||||
|
|
||||||
Ok(pubkey)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn get_pub_key(
|
pub(crate) fn get_pub_key(
|
||||||
|
@ -119,17 +127,19 @@ pub(crate) fn get_pub_key(
|
||||||
) -> Result<PublicKeyMaterial, OpenpgpCardError> {
|
) -> Result<PublicKeyMaterial, OpenpgpCardError> {
|
||||||
println!("get pub key for {:?}", key_type);
|
println!("get pub key for {:?}", key_type);
|
||||||
|
|
||||||
let card_client = card_app.card();
|
// algo
|
||||||
|
let ard = card_app.get_app_data()?; // FIXME: caching
|
||||||
|
let algo = CardApp::get_algorithm_attributes(&ard, key_type)?;
|
||||||
|
|
||||||
// get public key
|
// get public key
|
||||||
let crt = get_crt(key_type)?;
|
let crt = get_crt(key_type)?;
|
||||||
let get_pub_key_cmd = commands::get_pub_key(crt.serialize().to_vec());
|
let get_pub_key_cmd = commands::get_pub_key(crt.serialize().to_vec());
|
||||||
|
|
||||||
let resp = apdu::send_command(card_client, get_pub_key_cmd, true)?;
|
let resp = apdu::send_command(card_app.card(), get_pub_key_cmd, true)?;
|
||||||
resp.check_ok()?;
|
resp.check_ok()?;
|
||||||
|
|
||||||
let tlv = Tlv::try_from(resp.data()?)?;
|
let tlv = Tlv::try_from(resp.data()?)?;
|
||||||
let pubkey = tlv_to_pubkey(&tlv)?;
|
let pubkey = tlv_to_pubkey(&tlv, &algo)?;
|
||||||
|
|
||||||
Ok(pubkey)
|
Ok(pubkey)
|
||||||
}
|
}
|
||||||
|
@ -192,7 +202,7 @@ pub(crate) fn upload_key(
|
||||||
|
|
||||||
let algo = Algo::Ecc(EccAttrs {
|
let algo = Algo::Ecc(EccAttrs {
|
||||||
ecc_type: ecc_key.get_type(),
|
ecc_type: ecc_key.get_type(),
|
||||||
oid: ecc_key.get_oid().to_vec(),
|
curve: Curve::from(ecc_key.get_oid()).expect("unepected oid"),
|
||||||
import_format: None,
|
import_format: None,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -264,7 +274,7 @@ fn check_card_algo_ecdh(
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
// Check if this OID exists in the supported algorithms
|
// Check if this OID exists in the supported algorithms
|
||||||
ecdh_algos.iter().any(|e| e.oid == oid)
|
ecdh_algos.iter().any(|e| e.oid() == oid)
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME: refactor, these checks currently pointlessly duplicate code
|
// FIXME: refactor, these checks currently pointlessly duplicate code
|
||||||
|
@ -288,7 +298,7 @@ fn check_card_algo_ecdsa(
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
// Check if this OID exists in the supported algorithms
|
// Check if this OID exists in the supported algorithms
|
||||||
ecdsa_algos.iter().any(|e| e.oid == oid)
|
ecdsa_algos.iter().any(|e| e.oid() == oid)
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME: refactor, these checks currently pointlessly duplicate code
|
// FIXME: refactor, these checks currently pointlessly duplicate code
|
||||||
|
@ -312,7 +322,7 @@ fn check_card_algo_eddsa(
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
// Check if this OID exists in the supported algorithms
|
// Check if this OID exists in the supported algorithms
|
||||||
eddsa_algos.iter().any(|e| e.oid == oid)
|
eddsa_algos.iter().any(|e| e.oid() == oid)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn ecc_key_cmd(
|
fn ecc_key_cmd(
|
||||||
|
|
|
@ -265,14 +265,6 @@ pub trait CardUploadableKey {
|
||||||
pub enum PublicKeyMaterial {
|
pub enum PublicKeyMaterial {
|
||||||
R(RSAPub),
|
R(RSAPub),
|
||||||
E(EccPub),
|
E(EccPub),
|
||||||
T(Tffon), // 25519
|
|
||||||
}
|
|
||||||
|
|
||||||
/// ed25519/cv25519
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct Tffon {
|
|
||||||
/// Public key
|
|
||||||
pub pk: Vec<u8>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// RSA-specific container for public key material from an OpenPGP card.
|
/// RSA-specific container for public key material from an OpenPGP card.
|
||||||
|
@ -288,8 +280,8 @@ pub struct RSAPub {
|
||||||
/// ECC-specific container for public key material from an OpenPGP card.
|
/// ECC-specific container for public key material from an OpenPGP card.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct EccPub {
|
pub struct EccPub {
|
||||||
pub x: Vec<u8>,
|
pub data: Vec<u8>,
|
||||||
pub y: Vec<u8>,
|
pub algo: Algo,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Algorithm-independent container for private key material to upload to
|
/// Algorithm-independent container for private key material to upload to
|
||||||
|
|
Loading…
Reference in a new issue