feat: works with errors
This commit is contained in:
7
Cargo.lock
generated
7
Cargo.lock
generated
@@ -406,6 +406,12 @@ version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
||||
|
||||
[[package]]
|
||||
name = "hex"
|
||||
version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
|
||||
|
||||
[[package]]
|
||||
name = "hkdf"
|
||||
version = "0.12.4"
|
||||
@@ -585,6 +591,7 @@ dependencies = [
|
||||
name = "native-pkcs11-piv"
|
||||
version = "0.2.18"
|
||||
dependencies = [
|
||||
"hex",
|
||||
"native-pkcs11-traits",
|
||||
"p256",
|
||||
"pinentry",
|
||||
|
||||
@@ -30,6 +30,7 @@ x509-cert = { version = "0.2.5", default-features = false }
|
||||
yubikey = { version = "0.8.0", features = ["untested"] }
|
||||
sha1 = "0.10"
|
||||
x509-parser = "0.16.0"
|
||||
hex = "0.4.3"
|
||||
|
||||
[dev-dependencies]
|
||||
serial_test = { version = "3.1.1", default-features = false }
|
||||
|
||||
@@ -16,25 +16,24 @@ use std::sync::{Arc, Mutex};
|
||||
|
||||
use tracing::instrument;
|
||||
use x509_cert::der::Encode;
|
||||
use x509_parser::nom::Parser;
|
||||
use yubikey::piv::AlgorithmId;
|
||||
use yubikey::YubiKey;
|
||||
|
||||
use native_pkcs11_traits::{Backend, KeySearchOptions};
|
||||
use crate::piv::slot::SlotObject;
|
||||
use crate::piv::util::get_algorithm_id_by_certificate;
|
||||
use native_pkcs11_traits::Certificate as P11Certificate;
|
||||
use native_pkcs11_traits::KeyAlgorithm as P11KeyAlgorithm;
|
||||
use native_pkcs11_traits::KeySearchOptions as P11KeySearchOptions;
|
||||
use native_pkcs11_traits::PrivateKey as P11PrivateKey;
|
||||
use native_pkcs11_traits::PublicKey as P11PublicKey;
|
||||
use native_pkcs11_traits::Result as P11Result;
|
||||
|
||||
use crate::certificate::YubikeyPivCertificate;
|
||||
use crate::piv::util::get_algorithm_id_by_certificate;
|
||||
use native_pkcs11_traits::{Backend, KeyAlgorithm, KeySearchOptions};
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct YubikeyPivBackend {
|
||||
cached_pin: Mutex<Option<String>>,
|
||||
yubikey: Mutex<Option<YubiKey>>,
|
||||
slot_objects: Mutex<Option<Vec<SlotObject>>>,
|
||||
}
|
||||
|
||||
impl YubikeyPivBackend {
|
||||
@@ -77,6 +76,53 @@ impl YubikeyPivBackend {
|
||||
}
|
||||
Ok(cached_pin.as_deref().unwrap().to_string())
|
||||
}
|
||||
|
||||
fn init_slot_objects(&self) -> P11Result<()> {
|
||||
let mut slot_objects_opt = self.slot_objects.lock().unwrap();
|
||||
if slot_objects_opt.is_some() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let mut slot_objects = vec![];
|
||||
self.run_with_yubikey(false, |yk| {
|
||||
let keys = yk.piv_keys()?;
|
||||
for key in keys {
|
||||
let slot_id = key.slot().to_string();
|
||||
let certificate_der = key.certificate().cert.to_der()?;
|
||||
let public_key_der = key.certificate().cert.tbs_certificate.subject_public_key_info.to_der()?;
|
||||
|
||||
let algorithm_id = get_algorithm_id_by_certificate(key.certificate())?;
|
||||
let algorithm = match algorithm_id {
|
||||
AlgorithmId::Rsa1024 | AlgorithmId::Rsa2048 => KeyAlgorithm::Rsa,
|
||||
AlgorithmId::EccP256 | AlgorithmId::EccP384 => KeyAlgorithm::Ecc,
|
||||
};
|
||||
// println!(">>> {} {:?} {}", &slot_id, algorithm_id, &key.certificate().cert.tbs_certificate.subject); // TODO remove
|
||||
let slot_object = SlotObject::new(
|
||||
algorithm,
|
||||
slot_id,
|
||||
certificate_der,
|
||||
public_key_der,
|
||||
);
|
||||
slot_objects.push(slot_object);
|
||||
}
|
||||
Ok(())
|
||||
})?;
|
||||
*slot_objects_opt = Some(slot_objects);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn for_each_slot_objects<F>(&self, mut callback: F) -> P11Result<()>
|
||||
where
|
||||
F: FnMut(&SlotObject) -> P11Result<()>,
|
||||
{
|
||||
let slot_objects = &self.slot_objects.lock().unwrap();
|
||||
for slot_objects in slot_objects.iter() {
|
||||
for slot_object in slot_objects {
|
||||
callback(slot_object)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Backend for YubikeyPivBackend {
|
||||
@@ -88,25 +134,11 @@ impl Backend for YubikeyPivBackend {
|
||||
fn find_all_certificates(
|
||||
&self,
|
||||
) -> P11Result<Vec<Box<dyn P11Certificate>>> {
|
||||
println!("[find_all_certificates]");
|
||||
let mut certs = vec![];
|
||||
self.run_with_yubikey(false, |yk| {
|
||||
let keys = yk.piv_keys()?;
|
||||
for key in keys {
|
||||
let certificate_der = key.certificate().cert.to_der()?;
|
||||
let public_key_der = key.certificate().cert.tbs_certificate.subject_public_key_info.to_der()?;
|
||||
let algorithm_id = get_algorithm_id_by_certificate(key.certificate())?;
|
||||
println!(">>> {:?} {}", algorithm_id, &key.certificate().cert.tbs_certificate.subject);
|
||||
if algorithm_id == AlgorithmId::EccP256 || algorithm_id == AlgorithmId::EccP384 {
|
||||
let cert: Box<dyn P11Certificate> = Box::new(YubikeyPivCertificate::new(
|
||||
key.slot().to_string(),
|
||||
key.slot().to_string(),
|
||||
certificate_der,
|
||||
public_key_der,
|
||||
)?);
|
||||
certs.push(cert);
|
||||
}
|
||||
}
|
||||
// println!("[find_all_certificates]");
|
||||
self.init_slot_objects()?;
|
||||
let mut certs: Vec<Box<dyn P11Certificate>> = vec![];
|
||||
self.for_each_slot_objects(|slot_object| {
|
||||
certs.push(slot_object.to_certificate()?);
|
||||
Ok(())
|
||||
})?;
|
||||
Ok(certs)
|
||||
@@ -118,16 +150,26 @@ impl Backend for YubikeyPivBackend {
|
||||
query: P11KeySearchOptions,
|
||||
) -> P11Result<Option<Arc<dyn P11PrivateKey>>> {
|
||||
println!("[find_private_key]");
|
||||
match query {
|
||||
self.init_slot_objects()?;
|
||||
let mut private_key: Option<Arc<dyn P11PrivateKey>> = None;
|
||||
self.for_each_slot_objects(|slot_object| {
|
||||
if private_key.is_none() {
|
||||
match &query {
|
||||
KeySearchOptions::Label(label) => {
|
||||
println!(">>> find private key >>>: {}", label);
|
||||
if &slot_object.label == label {
|
||||
private_key = Some(Arc::from(slot_object.to_private_key()?));
|
||||
}
|
||||
}
|
||||
KeySearchOptions::PublicKeyHash(public_key_hash) => {
|
||||
println!(">>> find private key >>>: {:?}", public_key_hash);
|
||||
if hex::encode(&slot_object.public_key_hash) == hex::encode(public_key_hash) {
|
||||
private_key = Some(Arc::from(slot_object.to_private_key()?));
|
||||
}
|
||||
}
|
||||
// TODO ...
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
})?;
|
||||
Ok(private_key)
|
||||
}
|
||||
|
||||
#[instrument]
|
||||
@@ -136,35 +178,52 @@ impl Backend for YubikeyPivBackend {
|
||||
query: P11KeySearchOptions,
|
||||
) -> P11Result<Option<Box<dyn P11PublicKey>>> {
|
||||
println!("[find_public_key]");
|
||||
match query {
|
||||
self.init_slot_objects()?;
|
||||
let mut public_key: Option<Box<dyn P11PublicKey>> = None;
|
||||
self.for_each_slot_objects(|slot_object| {
|
||||
if public_key.is_none() {
|
||||
match &query {
|
||||
KeySearchOptions::Label(label) => {
|
||||
println!(">>> find public key >>>: {}", label);
|
||||
if &slot_object.label == label {
|
||||
public_key = Some(slot_object.to_public_key()?);
|
||||
}
|
||||
}
|
||||
KeySearchOptions::PublicKeyHash(public_key_hash) => {
|
||||
println!(">>> find public key >>>: {:?}", public_key_hash);
|
||||
if hex::encode(&slot_object.public_key_hash) == hex::encode(public_key_hash) {
|
||||
public_key = Some(slot_object.to_public_key()?);
|
||||
}
|
||||
}
|
||||
// TODO ...
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
})?;
|
||||
Ok(public_key)
|
||||
}
|
||||
|
||||
fn find_all_private_keys(
|
||||
&self,
|
||||
) -> P11Result<Vec<Arc<dyn P11PrivateKey>>> {
|
||||
println!("[find_all_private_keys]");
|
||||
// TODO ...
|
||||
Ok(vec![])
|
||||
self.init_slot_objects()?;
|
||||
let mut private_keys: Vec<Arc<dyn P11PrivateKey>> = vec![];
|
||||
self.for_each_slot_objects(|slot_object| {
|
||||
private_keys.push(Arc::from(slot_object.to_private_key()?));
|
||||
Ok(())
|
||||
})?;
|
||||
Ok(private_keys)
|
||||
}
|
||||
|
||||
fn find_all_public_keys(
|
||||
&self,
|
||||
) -> P11Result<Vec<Arc<dyn P11PublicKey>>> {
|
||||
println!("[find_all_public_keys]");
|
||||
// self.find_all_certificates().map(|c|{
|
||||
// c.as_mut().map(|c| )
|
||||
// })
|
||||
// TODO ...
|
||||
Ok(vec![])
|
||||
self.init_slot_objects()?;
|
||||
let mut public_keys: Vec<Arc<dyn P11PublicKey>> = vec![];
|
||||
self.for_each_slot_objects(|slot_object| {
|
||||
public_keys.push(Arc::from(slot_object.to_public_key()?));
|
||||
Ok(())
|
||||
})?;
|
||||
Ok(public_keys)
|
||||
}
|
||||
|
||||
#[instrument]
|
||||
|
||||
@@ -14,60 +14,43 @@
|
||||
|
||||
use std::fmt::Debug;
|
||||
|
||||
use native_pkcs11_traits::{Certificate as P11Certificate, KeyAlgorithm};
|
||||
use crate::key::YubikeyPivPublicKey;
|
||||
use crate::piv::slot::SlotObject;
|
||||
use native_pkcs11_traits::Certificate as P11Certificate;
|
||||
use native_pkcs11_traits::PublicKey as P11PublicKey;
|
||||
use native_pkcs11_traits::Result as P11Result;
|
||||
|
||||
use crate::key::YubikeyPivPublicKey;
|
||||
|
||||
pub struct YubikeyPivCertificate {
|
||||
pub label: String,
|
||||
pub identity: String,
|
||||
pub public_key: YubikeyPivPublicKey,
|
||||
certificate_der: Vec<u8>,
|
||||
slot_object: SlotObject,
|
||||
public_key: YubikeyPivPublicKey,
|
||||
}
|
||||
|
||||
impl YubikeyPivCertificate {
|
||||
pub fn new(label: String, identity: String, certificate_der: Vec<u8>, public_key_der: Vec<u8>) -> P11Result<Self> {
|
||||
let public_key = YubikeyPivPublicKey::new(
|
||||
label.clone(), KeyAlgorithm::Ecc, public_key_der,
|
||||
)?;
|
||||
Ok(Self {
|
||||
label,
|
||||
identity,
|
||||
pub fn new(slot_object: SlotObject) -> P11Result<Self> {
|
||||
let public_key = YubikeyPivPublicKey::new(slot_object.clone())?;
|
||||
Ok(YubikeyPivCertificate {
|
||||
slot_object,
|
||||
public_key,
|
||||
certificate_der,
|
||||
})
|
||||
}
|
||||
// pub fn new(identity: impl Into<SecIdentity>) -> Result<Self> {
|
||||
// let identity: SecIdentity = identity.into();
|
||||
// let label = identity.certificate().unwrap().subject_summary();
|
||||
// let pk = identity.certificate()?.public_key()?;
|
||||
// Ok(Self {
|
||||
// certificate_der: identity.certificate()?.to_der(),
|
||||
// label: label.clone(),
|
||||
// identity,
|
||||
// public_key: YubikeyPivPublicKey::new(pk, label)?,
|
||||
// })
|
||||
// }
|
||||
}
|
||||
|
||||
impl Debug for YubikeyPivCertificate {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("KeychainCertificate")
|
||||
.field("label", &self.label)
|
||||
.field("identity", &self.identity)
|
||||
.field("label", &self.slot_object.label)
|
||||
.field("identity", &self.slot_object.public_key_hash)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl P11Certificate for YubikeyPivCertificate {
|
||||
fn label(&self) -> String {
|
||||
self.label.to_string()
|
||||
self.slot_object.label.clone()
|
||||
}
|
||||
|
||||
fn to_der(&self) -> Vec<u8> {
|
||||
self.certificate_der.clone()
|
||||
self.slot_object.certificate_der.clone()
|
||||
}
|
||||
|
||||
fn public_key(&self) -> &dyn P11PublicKey {
|
||||
|
||||
@@ -16,10 +16,9 @@ use std::fmt::Debug;
|
||||
|
||||
use tracing::instrument;
|
||||
|
||||
use native_pkcs11_traits::{Backend, KeyAlgorithm, PrivateKey, PublicKey, SignatureAlgorithm};
|
||||
use crate::piv::slot::SlotObject;
|
||||
use native_pkcs11_traits::Result as P11Result;
|
||||
|
||||
use crate::piv::util::sha1_bytes;
|
||||
use native_pkcs11_traits::{Backend, KeyAlgorithm, PrivateKey, PublicKey, SignatureAlgorithm};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Algorithm {
|
||||
@@ -29,41 +28,27 @@ pub enum Algorithm {
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct YubikeyPivPrivateKey {
|
||||
// sec_key: SecKey,
|
||||
label: String,
|
||||
public_key_hash: Vec<u8>,
|
||||
algorithm: KeyAlgorithm,
|
||||
pub_key: Option<YubikeyPivPublicKey>,
|
||||
slot_object: SlotObject,
|
||||
}
|
||||
|
||||
impl YubikeyPivPrivateKey {
|
||||
// #[instrument]
|
||||
// pub fn new(
|
||||
// sec_key: SecKey,
|
||||
// label: impl Into<String> + Debug,
|
||||
// pub_key: Option<YubikeyPivPublicKey>,
|
||||
// ) -> Result<Self> {
|
||||
// let label = label.into();
|
||||
// let public_key_hash = sec_key.application_label().ok_or("no application_label")?;
|
||||
// Ok(Self {
|
||||
// algorithm: sec_key_algorithm(&sec_key)?,
|
||||
// sec_key,
|
||||
// label,
|
||||
// public_key_hash,
|
||||
// pub_key,
|
||||
// })
|
||||
// }
|
||||
#[instrument]
|
||||
pub fn new(slot_object: SlotObject) -> P11Result<Self> {
|
||||
Ok(YubikeyPivPrivateKey {
|
||||
slot_object,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl PrivateKey for YubikeyPivPrivateKey {
|
||||
#[instrument]
|
||||
fn public_key_hash(&self) -> Vec<u8> {
|
||||
self.public_key_hash.clone()
|
||||
self.slot_object.public_key_hash.clone()
|
||||
}
|
||||
|
||||
#[instrument]
|
||||
fn label(&self) -> String {
|
||||
self.label.clone()
|
||||
self.slot_object.label.clone()
|
||||
}
|
||||
|
||||
#[instrument]
|
||||
@@ -72,6 +57,7 @@ impl PrivateKey for YubikeyPivPrivateKey {
|
||||
algorithm: &SignatureAlgorithm,
|
||||
data: &[u8],
|
||||
) -> P11Result<Vec<u8>> {
|
||||
println!(">> CALL: sign");
|
||||
match algorithm {
|
||||
SignatureAlgorithm::Ecdsa => {}
|
||||
_ => return Err("RSA algorithm not supported.")?,
|
||||
@@ -82,12 +68,12 @@ impl PrivateKey for YubikeyPivPrivateKey {
|
||||
|
||||
#[instrument]
|
||||
fn delete(&self) {
|
||||
// yubikey-piv-pkcs11 just cannot delete private key
|
||||
// TODO ... yubikey-piv-pkcs11 just cannot delete private key
|
||||
}
|
||||
|
||||
#[instrument]
|
||||
fn algorithm(&self) -> KeyAlgorithm {
|
||||
self.algorithm
|
||||
self.slot_object.algorithm
|
||||
}
|
||||
|
||||
fn find_public_key(
|
||||
@@ -101,54 +87,32 @@ impl PrivateKey for YubikeyPivPrivateKey {
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct YubikeyPivPublicKey {
|
||||
// pub sec_key: SecKey,
|
||||
pub label: String,
|
||||
der: Vec<u8>,
|
||||
public_key_hash: Vec<u8>,
|
||||
algorithm: KeyAlgorithm,
|
||||
slot_object: SlotObject,
|
||||
}
|
||||
|
||||
impl YubikeyPivPublicKey {
|
||||
#[instrument]
|
||||
pub fn new(label: String, algorithm: KeyAlgorithm, public_key_der: Vec<u8>) -> P11Result<Self> {
|
||||
let public_key_hash = sha1_bytes(&public_key_der);
|
||||
Ok(Self {
|
||||
label,
|
||||
der: public_key_der,
|
||||
public_key_hash,
|
||||
algorithm,
|
||||
pub fn new(slot_object: SlotObject) -> P11Result<Self> {
|
||||
Ok(YubikeyPivPublicKey {
|
||||
slot_object,
|
||||
})
|
||||
}
|
||||
// #[instrument]
|
||||
// pub fn new(sec_key: SecKey, label: impl Into<String> + Debug) -> Result<Self> {
|
||||
// let der = sec_key
|
||||
// .external_representation()
|
||||
// .ok_or("no external representation")?;
|
||||
// let key_ty = sec_key_algorithm(&sec_key)?;
|
||||
// Ok(Self {
|
||||
// public_key_hash: sec_key.application_label().ok_or("no application_label")?,
|
||||
// sec_key,
|
||||
// label: label.into(),
|
||||
// der: der.to_vec(),
|
||||
// algorithm: key_ty,
|
||||
// })
|
||||
// }
|
||||
}
|
||||
|
||||
impl PublicKey for YubikeyPivPublicKey {
|
||||
#[instrument]
|
||||
fn public_key_hash(&self) -> Vec<u8> {
|
||||
self.public_key_hash.clone()
|
||||
self.slot_object.public_key_hash.clone()
|
||||
}
|
||||
|
||||
#[instrument]
|
||||
fn label(&self) -> String {
|
||||
self.label.clone()
|
||||
self.slot_object.label.clone()
|
||||
}
|
||||
|
||||
#[instrument]
|
||||
fn to_der(&self) -> Vec<u8> {
|
||||
self.der.clone()
|
||||
self.slot_object.public_key_der.clone()
|
||||
}
|
||||
|
||||
#[instrument]
|
||||
@@ -158,6 +122,7 @@ impl PublicKey for YubikeyPivPublicKey {
|
||||
data: &[u8],
|
||||
signature: &[u8],
|
||||
) -> P11Result<()> {
|
||||
println!(">> CALL: verify");
|
||||
match algorithm {
|
||||
SignatureAlgorithm::Ecdsa => {}
|
||||
_ => return Err("RSA algorithm not supported.")?,
|
||||
@@ -175,6 +140,6 @@ impl PublicKey for YubikeyPivPublicKey {
|
||||
}
|
||||
|
||||
fn algorithm(&self) -> KeyAlgorithm {
|
||||
self.algorithm
|
||||
self.slot_object.algorithm
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ pub mod certificate;
|
||||
pub mod key;
|
||||
mod pinentry;
|
||||
mod util;
|
||||
mod slot;
|
||||
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
|
||||
54
native-pkcs11-piv/src/piv/slot.rs
Normal file
54
native-pkcs11-piv/src/piv/slot.rs
Normal file
@@ -0,0 +1,54 @@
|
||||
// Copyright 2024 Hatter Jiang
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use crate::certificate::YubikeyPivCertificate;
|
||||
use crate::key::{YubikeyPivPrivateKey, YubikeyPivPublicKey};
|
||||
use crate::piv::util::sha1_bytes;
|
||||
use native_pkcs11_traits::{Certificate, PublicKey, Result as P11Result};
|
||||
use native_pkcs11_traits::{KeyAlgorithm, PrivateKey};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct SlotObject {
|
||||
pub label: String,
|
||||
// EQUALS: sha1_bytes(&public_key_der)
|
||||
pub public_key_hash: Vec<u8>,
|
||||
pub algorithm: KeyAlgorithm,
|
||||
pub public_key_der: Vec<u8>,
|
||||
pub certificate_der: Vec<u8>,
|
||||
}
|
||||
|
||||
impl SlotObject {
|
||||
pub fn new(algorithm: KeyAlgorithm, label: String, certificate_der: Vec<u8>, public_key_der: Vec<u8>) -> Self {
|
||||
let public_key_hash = sha1_bytes(&public_key_der);
|
||||
SlotObject {
|
||||
label,
|
||||
public_key_hash,
|
||||
algorithm,
|
||||
public_key_der,
|
||||
certificate_der,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_private_key(&self) -> P11Result<Box<dyn PrivateKey>> {
|
||||
Ok(Box::new(YubikeyPivPrivateKey::new(self.clone())?))
|
||||
}
|
||||
|
||||
pub fn to_public_key(&self) -> P11Result<Box<dyn PublicKey>> {
|
||||
Ok(Box::new(YubikeyPivPublicKey::new(self.clone())?))
|
||||
}
|
||||
|
||||
pub fn to_certificate(&self) -> P11Result<Box<dyn Certificate>> {
|
||||
Ok(Box::new(YubikeyPivCertificate::new(self.clone())?))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user