2024-10-30 16:12:00 +03:00
use std ::fs ::File ;
2023-03-14 19:09:03 +03:00
use sequoia_openpgp as openpgp ;
2024-10-30 16:12:00 +03:00
use openpgp ::Packet ;
use openpgp ::PacketPile ;
2023-03-14 19:09:03 +03:00
use openpgp ::Result ;
use openpgp ::cert ::prelude ::* ;
2024-10-30 16:12:00 +03:00
use openpgp ::packet ::Tag ;
2023-03-14 19:09:03 +03:00
use openpgp ::parse ::Parse ;
2024-10-30 16:12:00 +03:00
use openpgp ::serialize ::Serialize ;
use openpgp ::types ::RevocationStatus ;
2023-03-14 19:09:03 +03:00
2024-08-29 11:59:20 +03:00
use super ::common ::Sq ;
2024-10-30 16:12:00 +03:00
use super ::common ::STANDARD_POLICY ;
2024-08-29 11:59:20 +03:00
2023-03-14 19:09:03 +03:00
#[ test ]
2024-08-22 12:30:26 +03:00
fn sq_cert_import ( ) -> Result < ( ) >
2023-03-14 19:09:03 +03:00
{
2024-08-29 11:59:20 +03:00
let sq = Sq ::new ( ) ;
2023-03-14 19:09:03 +03:00
// Generate keys.
2024-10-11 17:53:54 +03:00
let ( _cert , alice_pgp , _rev ) =
sq . key_generate ( & [ " --expiration " , " never " ] , & [ " <alice@example.org> " ] ) ;
2023-03-14 19:09:03 +03:00
let alice_bytes = std ::fs ::read ( & alice_pgp ) ? ;
2024-10-11 17:53:54 +03:00
let ( _cert , bob_pgp , _rev ) =
sq . key_generate ( & [ " --expiration " , " never " ] , & [ " <bob@example.org> " ] ) ;
let ( _cert , carol_pgp , _rev ) =
sq . key_generate ( & [ " --expiration " , " never " ] , & [ " <carol@example.org> " ] ) ;
let alice_pgp = alice_pgp . display ( ) . to_string ( ) ;
let alice_pgp = & alice_pgp [ .. ] ;
let bob_pgp = bob_pgp . display ( ) . to_string ( ) ;
let bob_pgp = & bob_pgp [ .. ] ;
let carol_pgp = carol_pgp . display ( ) . to_string ( ) ;
let carol_pgp = & carol_pgp [ .. ] ;
2023-03-14 19:09:03 +03:00
let files = & [ alice_pgp , bob_pgp , carol_pgp ] ;
let check = | files : & [ & str ] , stdin : Option < & [ u8 ] > , expected : usize |
{
// Use a fresh certd.
2024-08-29 11:59:20 +03:00
let sq = Sq ::new ( ) ;
2023-03-14 19:09:03 +03:00
// Import.
2024-08-29 11:59:20 +03:00
let mut cmd = sq . command ( ) ;
cmd . args ( [ " cert " , " import " ] ) ;
2023-03-14 19:09:03 +03:00
cmd . args ( files ) ;
if let Some ( stdin ) = stdin {
cmd . write_stdin ( stdin ) ;
}
2024-01-19 13:24:21 +03:00
eprintln! ( " sq cert import {} {} " ,
2023-03-14 19:09:03 +03:00
files . join ( " " ) ,
if stdin . is_some ( ) { " <BYTES " } else { " " } ) ;
cmd . assert ( ) . success ( ) ;
// Export.
2024-08-29 11:59:20 +03:00
let mut cmd = sq . command ( ) ;
cmd . args ( [ " cert " , " export " , " --all " ] ) ;
2023-03-14 19:09:03 +03:00
2024-01-19 13:24:21 +03:00
eprintln! ( " sq cert export... " ) ;
2023-03-14 19:09:03 +03:00
let output = cmd . output ( ) . expect ( " success " ) ;
let stdout = String ::from_utf8_lossy ( & output . stdout ) ;
let stderr = String ::from_utf8_lossy ( & output . stderr ) ;
assert! ( output . status . success ( ) ,
2024-01-19 13:24:21 +03:00
" sq cert export should succeed \n \
2023-03-14 19:09:03 +03:00
stdout :\ n { } \ nstderr :\ n { } " ,
stdout , stderr ) ;
let parser = CertParser ::from_bytes ( stdout . as_bytes ( ) )
. expect ( " valid " ) ;
let found = parser . collect ::< Result < Vec < Cert > > > ( )
. expect ( " valid " ) ;
assert_eq! ( expected , found . len ( ) ,
" expected: {} \n found: {} ({}) \n \
stdout :\ n { } \ nstderr :\ n { } " ,
expected , found . len ( ) ,
found . iter ( ) . map ( | c | c . fingerprint ( ) . to_string ( ) )
. collect ::< Vec < _ > > ( )
. join ( " , " ) ,
stdout , stderr ) ;
} ;
// Import from N files.
for i in 1 ..= files . len ( ) {
check ( & files [ 0 .. i ] , None , i ) ;
}
// Import from stdin.
check ( & [ ] , Some ( & alice_bytes [ .. ] ) , 1 ) ;
// Specify "-".
check ( & [ " - " ] , Some ( & alice_bytes [ .. ] ) , 1 ) ;
// Provide stdin and a file. Only the file should be read.
check ( & [ bob_pgp ] , Some ( & alice_bytes [ .. ] ) , 1 ) ;
// Provide stdin explicitly and a file. Both should be read.
check ( & [ bob_pgp , " - " ] , Some ( & alice_bytes [ .. ] ) , 2 ) ;
Ok ( ( ) )
}
2024-10-30 16:12:00 +03:00
#[ test ]
fn sq_cert_import_rev ( ) -> Result < ( ) >
{
let sq = Sq ::new ( ) ;
// Generate a key. (We don't use sq on purpose: we want to make
// sure we have a bare revocation certificate.)
let ( cert , rev ) = CertBuilder ::general_purpose (
None , Some ( " alice@example.org " ) )
. set_creation_time ( sq . now ( ) )
. generate ( ) ? ;
let cert_file = sq . scratch_file ( " cert " ) ;
cert . as_tsk ( ) . serialize ( & mut File ::create ( & cert_file ) ? ) ? ;
sq . key_import ( & cert_file ) ;
// We shouldn't be able to import a signature over a data file.
// Create a detached signature.
let sig_file = sq . scratch_file ( " sig " ) ;
sq . sign_detached ( & [ ] , cert . fingerprint ( ) ,
cert_file . as_path ( ) , sig_file . as_path ( ) ) ;
// Be extra sure that it is a single packet.
let pp = PacketPile ::from_file ( & sig_file ) ? ;
let packets = pp . into_children ( ) . collect ::< Vec < _ > > ( ) ;
assert_eq! ( packets . len ( ) , 1 ) ;
assert_eq! ( packets [ 0 ] . tag ( ) , Tag ::Signature ) ;
// Assert that it can't be imported.
assert! ( sq . cert_import_maybe ( & sig_file ) . is_err ( ) ) ;
// We should be able to import a bare revocation certificate.
// Assert that the certificate is not revoked.
let cert = sq . cert_export ( cert . key_handle ( ) ) ;
assert! ( ! matches! (
cert . revocation_status ( STANDARD_POLICY , sq . now ( ) ) ,
RevocationStatus ::Revoked ( _ ) ) ) ;
// Import the revocation certificate.
let rev_file = sq . scratch_file ( " rev " ) ;
Packet ::from ( rev ) . serialize ( & mut File ::create ( & rev_file ) ? ) ? ;
sq . cert_import ( rev_file ) ;
// Assert that the certificate is now revoked.
let cert = sq . cert_export ( cert . key_handle ( ) ) ;
assert! ( matches! (
cert . revocation_status ( STANDARD_POLICY , sq . now ( ) ) ,
RevocationStatus ::Revoked ( _ ) ) ) ;
Ok ( ( ) )
}