feat: remove generate key, since PIV key genration should use ykman
This commit is contained in:
@@ -29,12 +29,9 @@ use crate::{
|
|||||||
find_all_keys,
|
find_all_keys,
|
||||||
find_key,
|
find_key,
|
||||||
find_key2,
|
find_key2,
|
||||||
generate_key,
|
|
||||||
Algorithm,
|
|
||||||
YubikeyPivPrivateKey,
|
YubikeyPivPrivateKey,
|
||||||
YubikeyPivPublicKey,
|
YubikeyPivPublicKey,
|
||||||
},
|
},
|
||||||
keychain,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default)]
|
||||||
@@ -130,16 +127,10 @@ impl Backend for YubikeyPivBackend {
|
|||||||
#[instrument]
|
#[instrument]
|
||||||
fn generate_key(
|
fn generate_key(
|
||||||
&self,
|
&self,
|
||||||
algorithm: native_pkcs11_traits::KeyAlgorithm,
|
_algorithm: native_pkcs11_traits::KeyAlgorithm,
|
||||||
label: Option<&str>,
|
_label: Option<&str>,
|
||||||
) -> native_pkcs11_traits::Result<Arc<dyn native_pkcs11_traits::PrivateKey>> {
|
) -> native_pkcs11_traits::Result<Arc<dyn native_pkcs11_traits::PrivateKey>> {
|
||||||
let alg = match algorithm {
|
Err("Generate key not supported, please use ykman, URL: https://hatter.in/ykman")?
|
||||||
native_pkcs11_traits::KeyAlgorithm::Rsa => Algorithm::RSA,
|
|
||||||
native_pkcs11_traits::KeyAlgorithm::Ecc => Algorithm::ECC,
|
|
||||||
};
|
|
||||||
let label = label.unwrap_or("");
|
|
||||||
Ok(generate_key(alg, label, Some(keychain::location()?))
|
|
||||||
.map(|key| YubikeyPivPrivateKey::new(key, label, None).map(Arc::new))??)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn find_all_private_keys(
|
fn find_all_private_keys(
|
||||||
|
|||||||
@@ -354,59 +354,3 @@ pub fn self_signed_certificate(key_algorithm: Algorithm, private_key: &SecKey) -
|
|||||||
|
|
||||||
Ok(cert.to_der()?)
|
Ok(cert.to_der()?)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod test {
|
|
||||||
use native_pkcs11_traits::random_label;
|
|
||||||
use serial_test::serial;
|
|
||||||
|
|
||||||
use super::*;
|
|
||||||
#[test]
|
|
||||||
#[serial]
|
|
||||||
#[ignore = "https://github.com/google/native-pkcs11/issues/302"]
|
|
||||||
fn test_self_signed_certificate() -> Result<()> {
|
|
||||||
use security_framework::item::{ItemClass, Limit};
|
|
||||||
|
|
||||||
use crate::key::generate_key;
|
|
||||||
|
|
||||||
let label = random_label();
|
|
||||||
let key = generate_key(Algorithm::RSA, &label, Some(keychain::location()?))?;
|
|
||||||
|
|
||||||
let cert = self_signed_certificate(Algorithm::RSA, &key)?;
|
|
||||||
|
|
||||||
let cert = import_certificate(&cert)?;
|
|
||||||
|
|
||||||
// NOTE(kcking): Importing a certificate that has a private key already
|
|
||||||
// stored in the keychain will treat that certificate as an identity, even
|
|
||||||
// without calling import_identity.
|
|
||||||
// let identity = import_identity(&cert)?;
|
|
||||||
|
|
||||||
// HACK(kcking): The macOS keychain takes some time to flush all of the updates
|
|
||||||
// such that they are visible to the next search query.
|
|
||||||
std::thread::sleep(std::time::Duration::from_secs(1));
|
|
||||||
|
|
||||||
assert!(
|
|
||||||
crate::piv::keychain::item_search_options()?
|
|
||||||
.class(ItemClass::identity())
|
|
||||||
.limit(Limit::All)
|
|
||||||
.load_refs(true)
|
|
||||||
.search()?
|
|
||||||
.iter()
|
|
||||||
.any(|result| match result {
|
|
||||||
security_framework::item::SearchResult::Ref(
|
|
||||||
security_framework::item::Reference::Identity(id),
|
|
||||||
) => id.certificate().unwrap().subject() == cert.subject(),
|
|
||||||
_ => false,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
// Clean up
|
|
||||||
cert.delete()?;
|
|
||||||
// NOTE(kcking): Deleting the certificate also deletes the identity since
|
|
||||||
// they are the same underlying object, so identity.delete() is not needed.
|
|
||||||
// identity.delete()?;
|
|
||||||
key.public_key().ok_or("no public key")?.delete()?;
|
|
||||||
key.delete()?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -12,13 +12,11 @@
|
|||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
use std::fmt::Debug;
|
|
||||||
|
|
||||||
use core_foundation::base::ToVoid;
|
use core_foundation::base::ToVoid;
|
||||||
use native_pkcs11_traits::{KeyAlgorithm, PrivateKey, PublicKey, SignatureAlgorithm};
|
use native_pkcs11_traits::{KeyAlgorithm, PrivateKey, PublicKey, SignatureAlgorithm};
|
||||||
use security_framework::{
|
use security_framework::{
|
||||||
item::{ItemClass, KeyClass, Limit, Location, Reference},
|
item::{ItemClass, KeyClass, Limit, Reference},
|
||||||
key::{GenerateKeyOptions, KeyType, SecKey},
|
key::SecKey,
|
||||||
};
|
};
|
||||||
// TODO(bweeks,kcking): remove dependency on security-framework-sys crate.
|
// TODO(bweeks,kcking): remove dependency on security-framework-sys crate.
|
||||||
use security_framework_sys::item::{
|
use security_framework_sys::item::{
|
||||||
@@ -27,6 +25,7 @@ use security_framework_sys::item::{
|
|||||||
kSecAttrKeyTypeRSA,
|
kSecAttrKeyTypeRSA,
|
||||||
kSecAttrTokenID,
|
kSecAttrTokenID,
|
||||||
};
|
};
|
||||||
|
use std::fmt::Debug;
|
||||||
use tracing::instrument;
|
use tracing::instrument;
|
||||||
|
|
||||||
use crate::Result;
|
use crate::Result;
|
||||||
@@ -67,7 +66,7 @@ fn sigalg_to_seckeyalg(
|
|||||||
return Err(crate::ErrorKind::UnsupportedSignatureAlgorithm(
|
return Err(crate::ErrorKind::UnsupportedSignatureAlgorithm(
|
||||||
signature_algorithm.clone(),
|
signature_algorithm.clone(),
|
||||||
)
|
)
|
||||||
.into());
|
.into());
|
||||||
}
|
}
|
||||||
match mask_generation_function {
|
match mask_generation_function {
|
||||||
native_pkcs11_traits::DigestType::Sha1 => RSASignatureDigestPSSSHA1,
|
native_pkcs11_traits::DigestType::Sha1 => RSASignatureDigestPSSSHA1,
|
||||||
@@ -248,30 +247,6 @@ impl PublicKey for YubikeyPivPublicKey {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[instrument(skip(location))]
|
|
||||||
pub fn generate_key(
|
|
||||||
algorithm: Algorithm,
|
|
||||||
label: &str,
|
|
||||||
location: Option<Location>,
|
|
||||||
) -> Result<SecKey> {
|
|
||||||
let (ty, size) = match algorithm {
|
|
||||||
Algorithm::RSA => (KeyType::rsa(), 2048),
|
|
||||||
Algorithm::ECC => (KeyType::ec(), 256),
|
|
||||||
};
|
|
||||||
|
|
||||||
let opts = GenerateKeyOptions {
|
|
||||||
key_type: Some(ty),
|
|
||||||
size_in_bits: Some(size),
|
|
||||||
label: Some(label.into()),
|
|
||||||
token: Some(security_framework::key::Token::Software),
|
|
||||||
location,
|
|
||||||
access_control: None,
|
|
||||||
}
|
|
||||||
.to_dictionary();
|
|
||||||
|
|
||||||
Ok(SecKey::generate(opts).map_err(|e| e.to_string())?)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn find_key(class: KeyClass, label: &str) -> Result<SecKey> {
|
pub fn find_key(class: KeyClass, label: &str) -> Result<SecKey> {
|
||||||
let results = crate::keychain::item_search_options()?
|
let results = crate::keychain::item_search_options()?
|
||||||
.load_refs(true)
|
.load_refs(true)
|
||||||
@@ -342,224 +317,3 @@ pub fn find_all_keys(key_class: KeyClass) -> Result<Vec<SecKey>> {
|
|||||||
|
|
||||||
Ok(keys)
|
Ok(keys)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod test {
|
|
||||||
use core_foundation::base::{TCFType, ToVoid};
|
|
||||||
use native_pkcs11_traits::{random_label, Backend};
|
|
||||||
use security_framework::item::{add_item, AddRef, ItemAddOptions, Limit};
|
|
||||||
use security_framework_sys::item::{kSecAttrLabel, kSecValueRef};
|
|
||||||
use serial_test::serial;
|
|
||||||
|
|
||||||
use super::*;
|
|
||||||
use crate::{keychain, KeychainBackend};
|
|
||||||
#[test]
|
|
||||||
#[serial]
|
|
||||||
fn key_label() -> crate::Result<()> {
|
|
||||||
let label = random_label();
|
|
||||||
let key = generate_key(Algorithm::RSA, &label, Some(keychain::location()?))?;
|
|
||||||
|
|
||||||
let mut found = false;
|
|
||||||
for res in crate::keychain::item_search_options()?
|
|
||||||
.key_class(KeyClass::private())
|
|
||||||
.limit(Limit::Max(1))
|
|
||||||
.load_attributes(true)
|
|
||||||
.load_refs(true)
|
|
||||||
.label(&label)
|
|
||||||
.search()?
|
|
||||||
{
|
|
||||||
found = true;
|
|
||||||
let (found_key, found_label) = match res {
|
|
||||||
security_framework::item::SearchResult::Ref(_) => panic!(),
|
|
||||||
security_framework::item::SearchResult::Dict(d) => {
|
|
||||||
let key = unsafe {
|
|
||||||
SecKey::wrap_under_get_rule(d.get(kSecValueRef.to_void()).cast_mut().cast())
|
|
||||||
};
|
|
||||||
let label = unsafe {
|
|
||||||
core_foundation::string::CFString::wrap_under_get_rule(
|
|
||||||
d.get(kSecAttrLabel.to_void()).cast_mut().cast(),
|
|
||||||
)
|
|
||||||
};
|
|
||||||
(key, label.to_string())
|
|
||||||
}
|
|
||||||
security_framework::item::SearchResult::Data(_) => panic!(),
|
|
||||||
security_framework::item::SearchResult::Other => panic!(),
|
|
||||||
};
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
found_key.external_representation().unwrap().to_vec(),
|
|
||||||
key.external_representation().unwrap().to_vec()
|
|
||||||
);
|
|
||||||
|
|
||||||
assert_eq!(found_label, label);
|
|
||||||
}
|
|
||||||
key.public_key().unwrap().delete()?;
|
|
||||||
key.delete()?;
|
|
||||||
assert!(found);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
#[serial]
|
|
||||||
fn key_lifecycle() -> Result<()> {
|
|
||||||
for (key_alg, sig_alg) in [
|
|
||||||
(
|
|
||||||
Algorithm::ECC,
|
|
||||||
security_framework_sys::key::Algorithm::ECDSASignatureDigestX962,
|
|
||||||
),
|
|
||||||
(
|
|
||||||
Algorithm::RSA,
|
|
||||||
security_framework_sys::key::Algorithm::RSASignatureDigestPKCS1v15Raw,
|
|
||||||
),
|
|
||||||
] {
|
|
||||||
let label = &random_label();
|
|
||||||
|
|
||||||
let key = generate_key(key_alg, label, Some(keychain::location()?))?;
|
|
||||||
|
|
||||||
let first_pubkey = key
|
|
||||||
.public_key()
|
|
||||||
.ok_or("no pubkey")?
|
|
||||||
.external_representation()
|
|
||||||
.ok_or("no external_representation")?
|
|
||||||
.to_vec();
|
|
||||||
|
|
||||||
std::mem::drop(key);
|
|
||||||
|
|
||||||
let loaded_key = find_key(KeyClass::private(), label)?;
|
|
||||||
|
|
||||||
let payload = vec![0u8; 32];
|
|
||||||
let signature = loaded_key.create_signature(sig_alg, &payload)?;
|
|
||||||
|
|
||||||
let loaded_pubkey = loaded_key.public_key().ok_or("no pubkey")?;
|
|
||||||
let sig_valid = loaded_pubkey.verify_signature(sig_alg, &payload, &signature)?;
|
|
||||||
assert!(sig_valid);
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
loaded_pubkey.external_representation().unwrap().to_vec(),
|
|
||||||
first_pubkey
|
|
||||||
);
|
|
||||||
|
|
||||||
loaded_key.public_key().ok_or("no pubkey")?.delete()?;
|
|
||||||
loaded_key.delete()?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
#[ignore]
|
|
||||||
fn stress_test_keygen() {
|
|
||||||
let try_gen_key = || -> bool {
|
|
||||||
let label = random_label();
|
|
||||||
match generate_key(Algorithm::RSA, &label, Some(keychain::location().unwrap())) {
|
|
||||||
Ok(key) => {
|
|
||||||
let _ = key.delete();
|
|
||||||
true
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
eprintln!("{:?}", e);
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut handles = vec![];
|
|
||||||
for _ in 0..20 {
|
|
||||||
handles.push(std::thread::spawn(try_gen_key));
|
|
||||||
}
|
|
||||||
assert!(
|
|
||||||
handles
|
|
||||||
.into_iter()
|
|
||||||
.map(|h| h.join().unwrap())
|
|
||||||
// fold so we don't early exit other threads
|
|
||||||
.all(|b| b)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
#[ignore = "https://github.com/google/native-pkcs11/issues/302"]
|
|
||||||
fn keychain_pubkey_hash_find() -> Result<()> {
|
|
||||||
let key1 = generate_key(Algorithm::ECC, &random_label(), Some(keychain::location()?))?;
|
|
||||||
let key2 = generate_key(Algorithm::ECC, &random_label(), Some(keychain::location()?))?;
|
|
||||||
assert_ne!(key1.application_label(), key2.application_label());
|
|
||||||
|
|
||||||
for keyclass in [KeyClass::public(), KeyClass::private()] {
|
|
||||||
for key in [&key1, &key2] {
|
|
||||||
assert_eq!(
|
|
||||||
find_key2(keyclass, &key.application_label().unwrap())?
|
|
||||||
.unwrap()
|
|
||||||
.application_label()
|
|
||||||
.unwrap(),
|
|
||||||
key.application_label().unwrap()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for key in [&key1, &key2] {
|
|
||||||
key.public_key().as_ref().map(SecKey::delete);
|
|
||||||
let _ = key.delete();
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
#[ignore = "demonstrate bug"]
|
|
||||||
fn unpersisted_public_key() -> Result<()> {
|
|
||||||
// NOTE(kcking):
|
|
||||||
// 1) Manually-imported keys are super scuffed.
|
|
||||||
// Manually imported key can be searched by label (if imported with
|
|
||||||
// that label), but cannot be searched by application_label (aka public
|
|
||||||
// key hash). Perhaps we are supposed to set this value at import time.
|
|
||||||
//
|
|
||||||
// 2) Manually-imported private keys do not return a corresponding
|
|
||||||
// public key from SecKeyCopyPublicKey (`.public_key()` in rust).
|
|
||||||
|
|
||||||
let label = random_label();
|
|
||||||
let key1 = SecKey::generate(
|
|
||||||
GenerateKeyOptions::default()
|
|
||||||
.set_key_type(KeyType::ec())
|
|
||||||
.set_label(&label)
|
|
||||||
.to_dictionary(),
|
|
||||||
)?;
|
|
||||||
|
|
||||||
let pubkey_hash = key1.public_key().unwrap().application_label().unwrap();
|
|
||||||
|
|
||||||
add_item(
|
|
||||||
ItemAddOptions::new(security_framework::item::ItemAddValue::Ref(AddRef::Key(
|
|
||||||
key1,
|
|
||||||
)))
|
|
||||||
.set_label(&label)
|
|
||||||
.to_dictionary(),
|
|
||||||
)?;
|
|
||||||
|
|
||||||
// NOTE(kcking): this fails to find the generated key, most likely
|
|
||||||
// because application_label is not automatically populated by
|
|
||||||
// SecurityFramework when importing a SecKey
|
|
||||||
//
|
|
||||||
// let found_key =
|
|
||||||
// KeychainBackend::find_private_key(native_pkcs11_traits::KeySearchOptions::PublicKeyHash(
|
|
||||||
// pubkey_hash
|
|
||||||
// .as_slice()
|
|
||||||
// .try_into()
|
|
||||||
// .map_err(|_| "into array")?,
|
|
||||||
// ))
|
|
||||||
// .map_err(|e| {
|
|
||||||
// dbg!(e);
|
|
||||||
// "find"
|
|
||||||
// })?
|
|
||||||
// .unwrap();
|
|
||||||
|
|
||||||
let found_key = KeychainBackend
|
|
||||||
.find_private_key(native_pkcs11_traits::KeySearchOptions::Label(label))
|
|
||||||
.map_err(|e| {
|
|
||||||
dbg!(e);
|
|
||||||
"find"
|
|
||||||
})?
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
assert_eq!(pubkey_hash, found_key.public_key_hash());
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
Reference in New Issue
Block a user