2024-11-22 19:14:25 +03:00
use std ::collections ::HashSet ;
2024-06-03 17:57:19 +03:00
use sequoia_openpgp as openpgp ;
2024-11-22 19:14:25 +03:00
use openpgp ::Cert ;
use openpgp ::Fingerprint ;
use openpgp ::Result ;
use openpgp ::cert ::amalgamation ::ValidAmalgamation ;
use openpgp ::parse ::Parse ;
use openpgp ::types ::RevocationStatus ;
2024-06-03 17:57:19 +03:00
2024-08-15 14:38:43 +03:00
use super ::common ::FileOrKeyHandle ;
2024-11-22 19:14:25 +03:00
use super ::common ::STANDARD_POLICY ;
2024-08-15 14:38:43 +03:00
use super ::common ::Sq ;
2024-06-03 17:57:19 +03:00
#[ test ]
fn sq_key_password ( ) -> Result < ( ) > {
2024-07-05 23:02:35 +03:00
let mut sq = Sq ::new ( ) ;
2024-06-03 17:57:19 +03:00
2024-07-05 23:02:35 +03:00
let ( cert , cert_path , _rev_path ) = sq . key_generate ( & [ ] , & [ " alice " ] ) ;
let orig_password = sq . scratch_file ( " orig-password.txt " ) ;
std ::fs ::write ( & orig_password , " t00 ez " ) . unwrap ( ) ;
2024-06-03 18:12:47 +03:00
2024-07-05 23:02:35 +03:00
let new_password = sq . scratch_file ( " new-password.txt " ) ;
std ::fs ::write ( & new_password , " crazy passw0rd " ) . unwrap ( ) ;
2024-06-03 18:12:47 +03:00
2024-07-05 23:02:35 +03:00
let msg_txt = sq . scratch_file ( " msg.txt " ) ;
std ::fs ::write ( & msg_txt , " hello world " ) . unwrap ( ) ;
2024-06-03 18:12:47 +03:00
2024-07-05 23:02:35 +03:00
for keystore in [ false , true ] {
eprintln! ( " Keystore: {} " , keystore ) ;
2024-06-03 18:12:47 +03:00
// Two days go by.
sq . tick ( 2 * 24 * 60 * 60 ) ;
if keystore {
sq . key_import ( & cert_path ) ;
}
2024-07-05 23:02:35 +03:00
let cert_handle = if keystore {
FileOrKeyHandle ::from ( cert . fingerprint ( ) )
2024-06-03 18:12:47 +03:00
} else {
2024-07-05 23:02:35 +03:00
cert_path . as_path ( ) . into ( )
} ;
// Sign a message. No password should be required.
sq . sign ( & cert_handle , None , msg_txt . as_path ( ) , None ) ;
2024-06-03 18:12:47 +03:00
// Change the key's password.
eprintln! ( " Change the key's password. " ) ;
2024-07-05 23:02:35 +03:00
let cert_updated = sq . scratch_file ( " cert-updated " ) ;
let cert = sq . key_password (
& cert_handle ,
None , Some ( & new_password ) ,
2024-11-22 19:14:25 +03:00
if keystore { None } else { Some ( cert_updated . as_path ( ) ) } ) ;
2024-07-05 23:02:35 +03:00
assert! ( cert . keys ( ) . all ( | ka | {
ka . has_secret ( )
& & ! ka . has_unencrypted_secret ( )
} ) ) ;
let cert_handle = if keystore {
FileOrKeyHandle ::from ( cert . fingerprint ( ) )
2024-06-03 18:12:47 +03:00
} else {
2024-07-05 23:02:35 +03:00
cert_updated . as_path ( ) . into ( )
} ;
2024-06-03 18:12:47 +03:00
// Sign a message.
2024-07-05 23:02:35 +03:00
sq . sign ( & cert_handle ,
Some ( new_password . as_path ( ) ) ,
msg_txt . as_path ( ) , None ) ;
2024-06-03 18:12:47 +03:00
// Clear the key's password.
eprintln! ( " Clear the key's password. " ) ;
2024-07-05 23:02:35 +03:00
let cert_updated2 = sq . scratch_file ( " cert-updated2 " ) ;
let cert = sq . key_password (
& cert_handle ,
Some ( & new_password ) , None ,
2024-11-22 19:14:25 +03:00
if keystore { None } else { Some ( cert_updated2 . as_path ( ) ) } ) ;
2024-07-05 23:02:35 +03:00
assert! ( cert . keys ( ) . all ( | ka | ka . has_unencrypted_secret ( ) ) ) ;
let cert_handle = if keystore {
FileOrKeyHandle ::from ( cert . fingerprint ( ) )
2024-06-03 18:12:47 +03:00
} else {
2024-07-05 23:02:35 +03:00
cert_updated2 . as_path ( ) . into ( )
} ;
2024-06-03 18:12:47 +03:00
// Sign a message.
2024-07-05 23:02:35 +03:00
sq . sign ( & cert_handle , None , msg_txt . as_path ( ) , None ) ;
2024-06-03 18:12:47 +03:00
}
2024-06-03 17:57:19 +03:00
Ok ( ( ) )
}
2024-11-22 19:14:25 +03:00
#[ test ]
fn unbound_subkey ( ) {
// Make sure we don't change the password for an unbound subkey.
let sq = Sq ::new ( ) ;
let new_password = sq . scratch_file ( " new-password.txt " ) ;
std ::fs ::write ( & new_password , " crazy passw0rd " ) . unwrap ( ) ;
let cert_path = sq . test_data ( )
. join ( " keys " )
. join ( " unbound-subkey.pgp " ) ;
let cert = Cert ::from_file ( & cert_path ) . expect ( " can read " ) ;
let vc = cert . with_policy ( STANDARD_POLICY , sq . now ( ) )
. expect ( " valid cert " ) ;
// One subkey should be considered invalid.
let bound : HashSet < Fingerprint >
= HashSet ::from_iter ( vc . keys ( ) . map ( | ka | ka . fingerprint ( ) ) ) ;
let all : HashSet < Fingerprint >
= HashSet ::from_iter ( cert . keys ( ) . map ( | ka | ka . fingerprint ( ) ) ) ;
assert! ( bound . len ( ) < all . len ( ) ) ;
let result = sq . key_password (
& cert_path , None , Some ( & new_password ) , None ) ;
// Make sure the password for the unbound key was not changed.
for ka in result . keys ( ) {
if bound . contains ( & ka . fingerprint ( ) ) {
assert! ( ! ka . has_unencrypted_secret ( ) ) ;
} else {
assert! ( ka . has_unencrypted_secret ( ) ) ;
}
}
}
#[ test ]
fn soft_revoked_subkey ( ) {
// Make sure we change the password for a soft revoked subkey.
let sq = Sq ::new ( ) ;
let new_password = sq . scratch_file ( " new-password.txt " ) ;
std ::fs ::write ( & new_password , " crazy passw0rd " ) . unwrap ( ) ;
let cert_path = sq . test_data ( )
. join ( " keys " )
. join ( " soft-revoked-subkey.pgp " ) ;
let cert = Cert ::from_file ( & cert_path ) . expect ( " can read " ) ;
let vc = cert . with_policy ( STANDARD_POLICY , sq . now ( ) )
. expect ( " valid cert " ) ;
// Make sure the revoked key is there and is really revoked.
let mut revoked = None ;
for k in vc . keys ( ) . subkeys ( ) {
if let RevocationStatus ::Revoked ( _ ) = k . revocation_status ( ) {
assert! ( revoked . is_none ( ) ,
" Only expected a single revoked subkey " ) ;
revoked = Some ( k . key_handle ( ) ) ;
}
}
if revoked . is_none ( ) {
panic! ( " Expected a revoked subkey, but didn't fine one " ) ;
}
let updated = sq . key_password (
cert_path , None , Some ( new_password . as_path ( ) ) , None ) ;
for ka in updated . keys ( ) {
assert! ( ! ka . has_unencrypted_secret ( ) ) ;
}
}
#[ test ]
fn hard_revoked_subkey ( ) {
// Make sure we can delete a hard revoked subkey.
let sq = Sq ::new ( ) ;
let new_password = sq . scratch_file ( " new-password.txt " ) ;
std ::fs ::write ( & new_password , " crazy passw0rd " ) . unwrap ( ) ;
let cert_path = sq . test_data ( )
. join ( " keys " )
. join ( " hard-revoked-subkey.pgp " ) ;
let cert = Cert ::from_file ( & cert_path ) . expect ( " can read " ) ;
let vc = cert . with_policy ( STANDARD_POLICY , sq . now ( ) )
. expect ( " valid cert " ) ;
// Make sure the revoked key is there and is really revoked.
let mut revoked = None ;
for k in vc . keys ( ) . subkeys ( ) {
if let RevocationStatus ::Revoked ( _ ) = k . revocation_status ( ) {
assert! ( revoked . is_none ( ) ,
" Only expected a single revoked subkey " ) ;
revoked = Some ( k . key_handle ( ) ) ;
}
}
if revoked . is_none ( ) {
panic! ( " Expected a revoked subkey, but didn't fine one " ) ;
}
let updated = sq . key_password (
cert_path , None , Some ( new_password . as_path ( ) ) , None ) ;
for ka in updated . keys ( ) {
assert! ( ! ka . has_unencrypted_secret ( ) ) ;
}
}
2024-11-22 18:55:23 +03:00
#[ test ]
fn sha1_subkey ( ) {
// Make sure we can change the password of keys that are bound
// using SHA-1.
let sq = Sq ::new ( ) ;
let new_password = sq . scratch_file ( " new-password.txt " ) ;
std ::fs ::write ( & new_password , " crazy passw0rd " ) . unwrap ( ) ;
let cert_path = sq . test_data ( )
. join ( " keys " )
. join ( " sha1-subkey-priv.pgp " ) ;
let cert = Cert ::from_file ( & cert_path ) . expect ( " can read " ) ;
let vc = cert . with_policy ( STANDARD_POLICY , sq . now ( ) )
. expect ( " valid cert " ) ;
// Make sure the subkey key is there and really uses SHA-1.
let valid_subkeys : Vec < _ > = vc . keys ( ) . subkeys ( )
. map ( | ka | ka . fingerprint ( ) )
. collect ( ) ;
let all_subkeys : Vec < _ > = cert . keys ( ) . subkeys ( )
. map ( | ka | ka . fingerprint ( ) )
. collect ( ) ;
assert_eq! ( valid_subkeys . len ( ) , 0 ) ;
assert_eq! ( all_subkeys . len ( ) , 1 ) ;
let updated = sq . key_password (
cert_path , None , Some ( new_password . as_path ( ) ) , None ) ;
for ka in updated . keys ( ) {
assert! ( ! ka . has_unencrypted_secret ( ) ) ;
}
}
2024-11-22 19:14:25 +03:00
#[ test ]
fn subkey_without_secret_key_material ( ) {
// Make sure we can change the password of keys where some of the
// subkeys are missing secret key material.
let sq = Sq ::new ( ) ;
let new_password = sq . scratch_file ( " new-password.txt " ) ;
std ::fs ::write ( & new_password , " crazy passw0rd " ) . unwrap ( ) ;
let ( cert , cert_path , _rev_path ) = sq . key_generate ( & [ ] , & [ " alice " ] ) ;
// Delete some secret key material.
let stripped = cert . keys ( ) . subkeys ( ) . next ( ) . unwrap ( ) ;
let update = sq . scratch_file (
Some ( & format! ( " delete- {} " , stripped . fingerprint ( ) ) [ .. ] ) ) ;
sq . key_subkey_delete (
cert_path , & [ stripped . key_handle ( ) ] , update . as_path ( ) ) ;
// Make sure it is stripped.
let cert = Cert ::from_file ( & update ) . expect ( " can read " ) ;
for ka in cert . keys ( ) {
if ka . fingerprint ( ) = = stripped . fingerprint ( ) {
assert! ( ! ka . has_secret ( ) ,
" {} still has secret key material " , ka . fingerprint ( ) ) ;
} else {
assert! ( ka . has_secret ( ) ) ;
}
}
let updated = sq . key_password (
& update , None , Some ( new_password . as_path ( ) ) , None ) ;
for ka in updated . keys ( ) {
assert! ( ! ka . has_unencrypted_secret ( ) ) ;
}
}