sequoia-sq/tests/integration/sq_key_subkey_bind.rs
2024-10-09 16:57:48 +02:00

755 lines
19 KiB
Rust

use super::common::FileOrKeyHandle;
use super::common::{Sq, artifact};
use std::path;
use sequoia_openpgp as openpgp;
use openpgp::Fingerprint;
use openpgp::KeyHandle;
use openpgp::Result;
use openpgp::cert::prelude::*;
use openpgp::parse::Parse;
use openpgp::policy::StandardPolicy;
use openpgp::types::KeyFlags;
fn alice() -> path::PathBuf {
// Fingerprint: 5CCB BA06 74EA 5162 615E 36E9 80E5 ADE9 43CA 0DC3
// Public-key algo: EdDSA Edwards-curve Digital Signature Algorithm
// Public-key size: 256 bits
// Secret key: Unencrypted
// Creation time: 2020-12-21 23:00:49 UTC
// Key flags: certification
//
// Subkey: 6A3B 1EC7 6233 62BC 066E 75AB DC42 7976 95D6 24E5
// Public-key algo: EdDSA Edwards-curve Digital Signature Algorithm
// Public-key size: 256 bits
// Secret key: Unencrypted
// Creation time: 2020-12-21 23:00:49 UTC
// Key flags: signing
//
// Subkey: 827E 4397 F330 7EDA 6ABD 2A6E AD9C 461D 6D2F 0982
// Public-key algo: ECDH public key algorithm
// Public-key size: 256 bits
// Secret key: Unencrypted
// Creation time: 2020-12-21 23:00:49 UTC
// Key flags: transport encryption, data-at-rest encryption
//
// UserID: Alice Lovelace <alice@example.org>
artifact("keys").join("alice-lovelace-encryption-subkey-signing-subkey-priv.pgp")
}
fn alice_primary() -> (Fingerprint, KeyFlags) {
("5CCB BA06 74EA 5162 615E 36E9 80E5 ADE9 43CA 0DC3".parse().unwrap(),
KeyFlags::empty().set_certification())
}
fn alice_signing() -> (Fingerprint, KeyFlags) {
("6A3B 1EC7 6233 62BC 066E 75AB DC42 7976 95D6 24E5".parse().unwrap(),
KeyFlags::empty().set_signing())
}
fn alice_encryption() -> (Fingerprint, KeyFlags) {
("827E 4397 F330 7EDA 6ABD 2A6E AD9C 461D 6D2F 0982".parse().unwrap(),
KeyFlags::empty().set_transport_encryption().set_storage_encryption())
}
fn bob() -> path::PathBuf {
// Fingerprint: C1CF 22F6 C838 07CE 3901 6CDE 8463 B196 87EE 13BB
// Public-key algo: EdDSA Edwards-curve Digital Signature Algorithm
// Public-key size: 256 bits
// Secret key: Unencrypted
// Creation time: 2020-12-21 23:02:23 UTC
// Key flags: certification
//
// UserID: Bob Babbage <bob@example.org>
artifact("keys").join("bob-babbage-cert-only-priv.pgp")
}
fn bob_primary() -> (Fingerprint, KeyFlags) {
("C1CF 22F6 C838 07CE 3901 6CDE 8463 B196 87EE 13BB".parse().unwrap(),
KeyFlags::empty().set_certification())
}
fn carol() -> path::PathBuf {
// Fingerprint: 0B17 34A8 2726 A5D1 D5AC 1568 1EC1 4781 FD88 09B4
// Public-key algo: EdDSA Edwards-curve Digital Signature Algorithm
// Public-key size: 256 bits
// Secret key: Unencrypted
// Creation time: 2020-12-22 00:02:24 UTC
// Key flags: certification
//
// Subkey: 3D56 A424 3D5C C345 638D FB19 05D8 B9EA DB92 A8C1
// Public-key algo: EdDSA Edwards-curve Digital Signature Algorithm
// Public-key size: 256 bits
// Secret key: Unencrypted
// Creation time: 2020-12-22 00:02:24 UTC
// Key flags: signing
//
// Subkey: 1F47 6866 1260 CFFA D3DE B630 5652 476A 8B74 5CE5
// Public-key algo: ECDH public key algorithm
// Public-key size: 256 bits
// Secret key: Unencrypted
// Creation time: 2020-12-22 00:02:24 UTC
// Key flags: transport encryption, data-at-rest encryption
//
// UserID: Carol <carol@example.org>
artifact("keys").join("carol-encryption-subkey-signing-subkey-priv.pgp")
}
fn carol_primary() -> (Fingerprint, KeyFlags) {
("0B17 34A8 2726 A5D1 D5AC 1568 1EC1 4781 FD88 09B4".parse().unwrap(),
KeyFlags::empty().set_certification())
}
fn carol_signing() -> (Fingerprint, KeyFlags) {
("3D56 A424 3D5C C345 638D FB19 05D8 B9EA DB92 A8C1".parse().unwrap(),
KeyFlags::empty().set_signing())
}
fn carol_encryption() -> (Fingerprint, KeyFlags) {
("1F47 6866 1260 CFFA D3DE B630 5652 476A 8B74 5CE5".parse().unwrap(),
KeyFlags::empty().set_transport_encryption().set_storage_encryption())
}
fn bare() -> path::PathBuf {
// $ sq inspect bare.pgp
// bare.pgp: Revocation Certificate.
//
// Fingerprint: B321BA8F650CB16443E06826DBFA98A78CF6562F
// Invalid: No binding signature at time 2024-09-26T08:21:08Z
// Public-key algo: RSA
// Public-key size: 2048 bits
// Creation time: 1970-01-01 00:00:00 UTC
artifact("keys").join("bare.pgp")
}
fn bare_signing() -> (Fingerprint, KeyFlags) {
("B321BA8F650CB16443E06826DBFA98A78CF6562F".parse().unwrap(),
KeyFlags::empty().set_signing())
}
fn check(cert: &Cert,
key_count: usize,
keys: ((Fingerprint, KeyFlags), &[(Fingerprint, KeyFlags)]))
-> Result<()>
{
let p = &StandardPolicy::new();
let vc = cert.with_policy(p, None).unwrap();
assert_eq!(key_count, vc.keys().count());
assert_eq!(vc.primary_key().fingerprint(), keys.0.0);
assert_eq!(vc.primary_key().key_flags(), Some(keys.0.1));
for (subkey, keyflags) in keys.1 {
let mut found = false;
for k in vc.keys().subkeys() {
if k.fingerprint() == *subkey {
assert_eq!(k.key_flags().as_ref(), Some(keyflags));
found = true;
break;
}
}
assert!(found);
}
Ok(())
}
#[test]
fn bind_encryption() -> Result<()> {
for (keyrings, key_imports, handle) in [
(
// Keyrings
&[ alice() ][..],
// Key store imports.
&[][..],
// Handle
FileOrKeyHandle::from(bob()),
),
(
// Keyrings
&[][..],
// Key store imports.
&[ alice() ],
// Handle
bob().into()
),
(
// Keyrings
&[][..],
// Key store imports.
&[ alice(), bob() ],
// Handle
bob_primary().0.into()
),
] {
let sq = Sq::new();
for file in key_imports {
sq.key_import(file);
}
// Have Bob bind alice's encryption subkey.
let cert = sq.key_subkey_bind(
&[],
keyrings.to_vec(),
handle,
[ alice_encryption().0.clone() ].to_vec(),
"-");
assert!(
check(&cert, 2, (bob_primary(), &[alice_encryption()])).is_ok());
}
Ok(())
}
#[test]
fn bind_signing() -> Result<()> {
for (keyrings, key_imports, handle) in [
(
// Keyrings
&[ alice() ][..],
// Key store imports.
&[][..],
// Handle
FileOrKeyHandle::from(bob()),
),
(
// Keyrings
&[][..],
// Key store imports.
&[ alice() ],
// Handle
bob().into()
),
(
// Keyrings
&[][..],
// Key store imports.
&[ alice(), bob() ],
// Handle
bob_primary().0.into()
),
] {
let sq = Sq::new();
for file in key_imports {
sq.key_import(file);
}
// Bind a signing subkey (subkey has secret key material).
let cert = sq.key_subkey_bind(
&[],
keyrings.to_vec(),
handle,
[ alice_signing().0.clone() ].to_vec(),
"-");
assert!(
check(&cert, 2, (bob_primary(), &[alice_signing()])).is_ok());
}
Ok(())
}
#[test]
fn bind_certification() -> Result<()> {
for (keyrings, key_imports, handle) in [
(
// Keyrings
&[ alice() ][..],
// Key store imports.
&[][..],
// Handle
FileOrKeyHandle::from(carol()),
),
(
// Keyrings
&[][..],
// Key store imports.
&[ alice() ],
// Handle
carol().into()
),
(
// Keyrings
&[][..],
// Key store imports.
&[ alice(), carol() ],
// Handle
carol_primary().0.into()
),
] {
let sq = Sq::new();
for file in key_imports {
sq.key_import(file);
}
// Bind a certification subkey (subkey has secret key
// material).
let cert = sq.key_subkey_bind(
&[],
keyrings.to_vec(),
handle,
[ alice_primary().0.clone() ].to_vec(),
"-");
assert!(check(&cert, 4, (carol_primary(), &[alice_primary()])).is_ok());
}
Ok(())
}
#[test]
fn bind_encryption_and_signing() -> Result<()> {
for (keyrings, key_imports, handle) in [
(
// Keyrings
&[ alice() ][..],
// Key store imports.
&[][..],
// Handle
FileOrKeyHandle::from(bob()),
),
(
// Keyrings
&[][..],
// Key store imports.
&[ alice() ],
// Handle
bob().into()
),
(
// Keyrings
&[][..],
// Key store imports.
&[ alice(), bob() ],
// Handle
bob_primary().0.into()
),
] {
let sq = Sq::new();
for file in key_imports {
sq.key_import(file);
}
let cert = sq.key_subkey_bind(
&[],
keyrings.to_vec(),
handle,
[
alice_signing().0.clone(),
alice_encryption().0.clone(),
].to_vec(),
"-");
assert!(
check(&cert, 3,
(bob_primary(),
&[alice_signing(), alice_encryption()]))
.is_ok());
}
Ok(())
}
#[test]
fn bind_twice() -> Result<()> {
for (keyrings, key_imports, handle) in [
(
// Keyrings
&[ alice() ][..],
// Key store imports.
&[][..],
// Handle
FileOrKeyHandle::from(bob()),
),
(
// Keyrings
&[][..],
// Key store imports.
&[ alice() ],
// Handle
bob().into()
),
(
// Keyrings
&[][..],
// Key store imports.
&[ alice(), bob() ],
// Handle
bob_primary().0.into()
),
] {
let sq = Sq::new();
for file in key_imports {
sq.key_import(file);
}
// Bind the same an encryption subkey twice.
let cert = sq.key_subkey_bind(
&[],
keyrings.to_vec(),
handle,
[
alice_encryption().0.clone(),
alice_encryption().0.clone(),
].to_vec(),
"-");
assert!(
check(&cert, 2, (bob_primary(), &[alice_encryption()])).is_ok());
}
Ok(())
}
#[test]
fn bind_key_appears_twice() -> Result<()> {
let sq = Sq::new();
// Bind an encryption subkey that appears twice.
let cert = sq.key_subkey_bind(
&[],
[ alice(), alice(), ].to_vec(),
bob(),
[
alice_encryption().0.clone(),
].to_vec(),
"-");
assert!(
check(&cert, 2, (bob_primary(), &[alice_encryption()])).is_ok());
Ok(())
}
#[test]
fn bind_own_encryption() -> Result<()> {
for (keyrings, key_imports, handle) in [
(
// Keyrings
&[ alice() ][..],
// Key store imports.
&[][..],
// Handle
FileOrKeyHandle::from(alice()),
),
(
// Keyrings
&[][..],
// Key store imports.
&[ alice() ],
// Handle
alice().into()
),
(
// Keyrings
&[][..],
// Key store imports.
&[ alice(), bob() ],
// Handle
alice_primary().0.into()
),
] {
let sq = Sq::new();
for file in key_imports {
sq.key_import(file);
}
// Bind its own encryption subkey. This should be a noop.
let cert = sq.key_subkey_bind(
&[],
keyrings.to_vec(),
handle,
[
alice_encryption().0.clone(),
].to_vec(),
"-");
assert!(
check(&cert, 3, (alice_primary(), &[alice_encryption()])).is_ok());
}
Ok(())
}
#[test]
fn bind_own_primary() -> Result<()> {
for (keyrings, key_imports, handle) in [
(
// Keyrings
&[ bob() ][..],
// Key store imports.
&[][..],
// Handle
FileOrKeyHandle::from(bob()),
),
(
// Keyrings
&[][..],
// Key store imports.
&[ bob() ],
// Handle
bob().into()
),
(
// Keyrings
&[][..],
// Key store imports.
&[ bob() ],
// Handle
bob_primary().0.into()
),
] {
let sq = Sq::new();
for file in key_imports {
sq.key_import(file);
}
// Bind own primary key.
let cert = sq.key_subkey_bind(
&[],
keyrings.to_vec(),
handle,
[
bob_primary().0.clone(),
].to_vec(),
"-");
assert!(
check(&cert, 2, (bob_primary(), &[bob_primary()])).is_ok());
}
Ok(())
}
#[test]
fn bind_missing() -> Result<()> {
for (keyrings, key_imports, handle) in [
(
// Keyrings
&[ alice(), bob() ][..],
// Key store imports.
&[][..],
// Handle
FileOrKeyHandle::from(bob()),
),
(
// Keyrings
&[][..],
// Key store imports.
&[][..],
// Handle
FileOrKeyHandle::from(bob()),
),
(
// Keyrings
&[][..],
// Key store imports.
&[ bob() ],
// Handle
bob().into()
),
(
// Keyrings
&[][..],
// Key store imports.
&[ bob() ],
// Handle
bob_primary().0.into()
),
] {
let sq = Sq::new();
for file in key_imports {
sq.key_import(file);
}
// Bind a key that is not present.
let r = sq.key_subkey_bind_maybe(
&[],
keyrings.to_vec(),
handle,
[
"1234 5678 90AB CDEF 1234 5678 90AB CDEF"
.parse::<KeyHandle>()
.expect("valid fingerprint")
].to_vec(),
"-");
assert!(r.is_err());
}
Ok(())
}
#[test]
fn bind_from_multiple() -> Result<()> {
for (keyrings, key_imports, handle) in [
(
// Keyrings
&[ alice(), carol() ][..],
// Key store imports.
&[][..],
// Handle
FileOrKeyHandle::from(bob()),
),
(
// Keyrings
&[][..],
// Key store imports.
&[ alice(), carol() ],
// Handle
bob().into()
),
(
// Keyrings
&[][..],
// Key store imports.
&[ alice(), bob(), carol(), ],
// Handle
bob_primary().0.into()
),
] {
let sq = Sq::new();
for file in key_imports {
sq.key_import(file);
}
// Bind own primary key.
let cert = sq.key_subkey_bind(
&[],
keyrings.to_vec(),
handle,
[
alice_signing().0.clone(),
alice_encryption().0.clone(),
carol_signing().0.clone(),
carol_encryption().0.clone(),
].to_vec(),
"-");
assert!(
check(&cert, 5,
(bob_primary(),
&[
alice_signing(), alice_encryption(),
carol_signing(), carol_encryption()
]))
.is_ok());
}
Ok(())
}
// A bare certificate is a certificate that consists of just a primary
// key (no subkeys, no user IDs, and no signatures). Make sure we are
// able to bind one.
#[test]
fn bind_bare() -> Result<()> {
let sq = Sq::new();
sq.key_import(alice());
let alice2_pgp = sq.scratch_file("alice2.pgp");
let to_bind = bare_signing().0;
let bare_file = bare();
let bare = Cert::from_file(&bare_file).expect("can read file");
// First, a bare certificate doesn't have any key flags set. Make
// sure `sq key bind` complains, if we don't specify any (e.g.,
// `--can-encrypt`).
let r = sq.key_subkey_bind_maybe(
&[],
vec![ bare_file.clone() ],
alice_primary().0,
vec![ to_bind.clone() ],
&alice2_pgp);
if r.is_ok() {
panic!("sq key bind succeeded, but should have complained about \
missing key flags");
}
let cert = sq.key_subkey_bind(
&["--can-encrypt", "universal"],
vec![ bare_file.clone() ],
alice_primary().0,
vec![ to_bind.clone() ],
&alice2_pgp);
let mut found = false;
for k in cert.keys() {
let was_bound = k.mpis() == bare.primary_key().mpis();
eprintln!("{}{}", k.fingerprint(),
if was_bound {
" (bound)"
} else {
""
});
if was_bound {
found = true;
}
}
if ! found {
panic!("{} was not bound", to_bind);
}
Ok(())
}
// Check that we can set the key creation time.
#[test]
fn key_creation_time() -> Result<()> {
let sq = Sq::new();
sq.key_import(alice());
let alice2_pgp = sq.scratch_file("alice2.pgp");
let to_bind = bare_signing().0;
// $ date --iso-8601=seconds --utc --date='@1577483647'
// 2019-12-27T21:54:07+00:00
let time = 1577483647;
let time = std::time::UNIX_EPOCH + std::time::Duration::new(time, 0);
let time_str = "2019-12-27T21:54:07+00:00";
let bare_file = bare();
let bare = Cert::from_file(&bare_file).expect("can read file");
let cert = sq.key_subkey_bind(
&["--can-encrypt", "universal", "--creation-time", time_str ],
vec![ bare_file ],
alice_primary().0,
vec![ to_bind.clone() ],
&alice2_pgp);
let mut found = false;
for k in cert.keys() {
let was_bound = k.mpis() == bare.primary_key().mpis();
eprintln!("{}: {:?}{}",
k.fingerprint(),
k.creation_time(),
if was_bound {
" (bound)"
} else {
""
});
if was_bound {
assert_eq!(k.creation_time(), time);
found = true;
}
}
if ! found {
panic!("{} was not bound", to_bind);
}
Ok(())
}