Move integration test to rust

Signed-off-by: Patrick Uiterwijk <patrick@puiterwijk.org>
This commit is contained in:
Patrick Uiterwijk 2021-12-08 13:14:31 +01:00
parent 9068cd1c07
commit be375d1a4c
No known key found for this signature in database
GPG key ID: A0A847B80FBBFF4A
3 changed files with 201 additions and 49 deletions

View file

@ -35,6 +35,10 @@ jobs:
- name: Remove clevis-pin-tpm2
run: |
dnf erase -y clevis-pin-tpm2
- name: Grab newer copies of the clevis in-tree TPM2 pin
run: |
curl https://raw.githubusercontent.com/latchset/clevis/master/src/pins/tpm2/clevis-encrypt-tpm2 -o /usr/bin/clevis-encrypt-tpm2
curl https://raw.githubusercontent.com/latchset/clevis/master/src/pins/tpm2/clevis-decrypt-tpm2 -o /usr/bin/clevis-decrypt-tpm2
- name: Build
run: cargo build
- name: Start swtpm
@ -44,6 +48,7 @@ jobs:
--tpmstate /tmp/tpmdir \
--createek --decryption --create-ek-cert \
--create-platform-cert \
--pcr-banks sha1,sha256 \
--display
swtpm socket --tpm2 \
--tpmstate dir=/tmp/tpmdir \
@ -51,11 +56,11 @@ jobs:
--ctrl type=tcp,port=2322 \
--server type=tcp,port=2321 \
--daemon
- name: Run PCR tests
- name: Run integration tests
run: |
TCTI=swtpm: ./tests/test_pcr
TCTI=swtpm: cargo test -- --nocapture
- name: Run policy tests
run: |
TCTI=swtpm: ./tests/test_policy
# TCTI=swtpm: ./tests/test_policy
- name: Run clippy
run: cargo clippy -- -D warnings

193
tests/integration_test.rs Normal file
View file

@ -0,0 +1,193 @@
use std::{io::Write, os::unix::process::CommandExt, process::Command};
use anyhow::{bail, Context, Result};
type CheckFunction = dyn Fn(&str) -> Result<()>;
struct EncryptFunc {
func: Box<dyn Fn(&str, &str) -> Result<String>>,
name: &'static str,
}
struct DecryptFunc {
func: Box<dyn Fn(&str) -> Result<String>>,
name: &'static str,
}
const EXENAME: &str = env!("CARGO_BIN_EXE_clevis-pin-tpm2");
const CONFIG_STRINGS: &[(&str, &CheckFunction)] = &[
// No sealing
(r#"{}"#, &always_success),
// No sealing, RSA
(r#"{"key": "rsa"}"#, &always_success),
// No sealing with sha1 name alg
(r#"{"hash": "sha1"}"#, &always_success),
// Sealed against PCR23
(r#"{"pcr_ids": [23]}"#, &always_success),
// sealed against SHA1 PCR23
(r#"{"pcr_bank": "sha1", "pcr_ids": [23]}"#, &always_success),
];
// Check functions
fn always_success(_token: &str) -> Result<()> {
Ok(())
}
fn call_cmd_and_get_output(cmd: &mut Command, input: &str) -> Result<String> {
if let Ok(val) = std::env::var("TCTI") {
cmd.env("TCTI", &val);
cmd.env("TPM2TOOLS_TCTI", &val);
}
let mut child = cmd
.stdin(std::process::Stdio::piped())
.stdout(std::process::Stdio::piped())
.spawn()
.context("Failed to spawn process")?;
child
.stdin
.as_mut()
.unwrap()
.write_all(input.as_bytes())
.context("Failed to write input")?;
let output = child
.wait_with_output()
.context("Failed to wait for process")?;
if !output.status.success() {
bail!("Command failed: {:?}", cmd);
}
Ok(String::from_utf8(output.stdout)?)
}
// Encrypt/Decrypt functions
fn generate_encrypt_us(renamed: bool) -> EncryptFunc {
EncryptFunc {
name: if renamed { "us_renamed" } else { "us" },
func: Box::new(move |plaintext: &str, config: &str| -> Result<String> {
let mut cmd = Command::new(EXENAME);
call_cmd_and_get_output(
if renamed {
cmd.arg0("clevis-encrypt-tpm2plus").arg(config)
} else {
cmd.arg("encrypt").arg(config)
},
plaintext,
)
}),
}
}
fn generate_decrypt_us(renamed: bool) -> DecryptFunc {
DecryptFunc {
name: if renamed { "us_renamed" } else { "us" },
func: Box::new(move |input: &str| -> Result<String> {
let mut cmd = Command::new(EXENAME);
call_cmd_and_get_output(
if renamed {
cmd.arg0("clevis-decrypt-tpm2plus")
} else {
cmd.arg("decrypt")
},
input,
)
}),
}
}
fn generate_encrypt_clevis() -> EncryptFunc {
EncryptFunc {
name: "clevis",
func: Box::new(move |plaintext: &str, config: &str| -> Result<String> {
call_cmd_and_get_output(
Command::new("clevis")
.arg("encrypt")
.arg("tpm2")
.arg(config),
plaintext,
)
}),
}
}
fn generate_decrypt_clevis() -> DecryptFunc {
DecryptFunc {
name: "clevis",
func: Box::new(move |input: &str| -> Result<String> {
call_cmd_and_get_output(Command::new("clevis").arg("decrypt"), input)
}),
}
}
const INPUT: &str = "some-static-content";
const FAIL_FAST: Option<&'static str> = option_env!("FAIL_FAST");
// Testing against clevis requires https://github.com/latchset/clevis/commit/c6fc63fc055c18927decc7bcaa07821d5ae37614
#[test]
fn pcr_tests() {
let encrypters = vec![
generate_encrypt_us(false),
generate_encrypt_us(true),
generate_encrypt_clevis(),
];
let decrypters = vec![
generate_decrypt_us(false),
generate_decrypt_us(true),
generate_decrypt_clevis(),
];
let mut failed: u64 = 0;
for (config, checker) in CONFIG_STRINGS {
for encrypt_fn in &encrypters {
for decrypt_fn in &decrypters {
if encrypt_fn.name == decrypt_fn.name && encrypt_fn.name == "clevis" {
// This is a boring case we're not interested in
continue;
}
if failed != 0 && FAIL_FAST.is_some() {
panic!("At least one test failed, and fail-fast enabled");
}
eprintln!(
"Executing with encrypt: {}, decrypt: {}, config: '{}'",
encrypt_fn.name, decrypt_fn.name, config,
);
eprintln!("\tStarting encrypter");
let encrypted = (encrypt_fn.func)(INPUT, config);
if let Err(e) = encrypted {
eprintln!("FAILED: error: {:?}", e);
failed += 1;
continue;
}
let encrypted = encrypted.unwrap();
eprintln!("\tStarting checker");
if let Err(e) = checker(&encrypted) {
eprintln!("FAILED: error: {:?}", e);
failed += 1;
continue;
}
eprintln!("\tStarting decrypter");
let decrypted = (decrypt_fn.func)(&encrypted);
if let Err(e) = decrypted {
eprintln!("FAILED: error: {:?}", e);
failed += 1;
continue;
}
let decrypted = decrypted.unwrap();
eprintln!("\tStarting contents checker");
if decrypted != INPUT {
eprintln!("FAILED: '{}' (input) != '{}' (decrypted)", INPUT, decrypted);
failed += 1;
continue;
}
eprintln!("\tPASSED");
}
}
}
if failed != 0 {
panic!("{} tests failed", failed);
}
}

View file

@ -1,46 +0,0 @@
#!/bin/bash
rm -f target/debug/clevis-{encrypt,decrypt}-tpm2plus
cargo build || (echo "Failed to build"; exit 1)
ln -s clevis-pin-tpm2 target/debug/clevis-encrypt-tpm2plus
ln -s clevis-pin-tpm2 target/debug/clevis-decrypt-tpm2plus
echo "Working: no sealing" | ./target/debug/clevis-pin-tpm2 encrypt '{}' | ./target/debug/clevis-pin-tpm2 decrypt || (echo "Failed: no sealing"; exit 1)
# This tests we can handle the extra argument (either empty string or -y) from Clevis v15
# https://github.com/latchset/clevis/commit/36fae7c2dbf030d6c74abaed945db7bf3c25d054
echo "Working: no sealing (clevis v15, empty)" | ./target/debug/clevis-pin-tpm2 encrypt '{}' '' | ./target/debug/clevis-pin-tpm2 decrypt || (echo "Failed: no sealing"; exit 1)
echo "Working: no sealing (clevis v15, -y)" | ./target/debug/clevis-pin-tpm2 encrypt '{}' '-y' | ./target/debug/clevis-pin-tpm2 decrypt || (echo "Failed: no sealing"; exit 1)
echo "Working: no sealing (clevis decrypt)" | ./target/debug/clevis-pin-tpm2 encrypt '{}' | clevis decrypt || (echo "Failed: no sealing (clevis decrypt)"; exit 1)
echo "Working: no sealing (clevis encrypt)" | clevis encrypt tpm2 '{}' | ./target/debug/clevis-pin-tpm2 decrypt || (echo "Failed: no sealing (clevis encrypt)"; exit 1)
echo "Working: no sealing (renamed encrypt)" | ./target/debug/clevis-encrypt-tpm2plus '{}' | ./target/debug/clevis-pin-tpm2 decrypt || (echo "Failed: no sealing"; exit 1)
echo "Working: no sealing (renamed decrypt)" | ./target/debug/clevis-pin-tpm2 encrypt '{}' | ./target/debug/clevis-decrypt-tpm2plus || (echo "Failed: no sealing (clevis decrypt)"; exit 1)
name_alg_out=$(echo "Working: with name alg" | ./target/debug/clevis-pin-tpm2 encrypt '{"hash": "sha1"}')
echo $name_alg_out | cut -d'.' -f1 | jose b64 dec -i- | grep "\"hash\":\"sha1\"" || (echo "Failed: with name alg: not using sha1"; exit 1)
echo $name_alg_out | ./target/debug/clevis-pin-tpm2 decrypt || (echo "Failed: with name alg"; exit 1)
echo "Working: with name alg (clevis decrypt)" | ./target/debug/clevis-pin-tpm2 encrypt '{"hash": "sha1"}' | clevis decrypt || (echo "Failed: with name alg (clevis decrypt)"; exit 1)
echo "Working: with name alg (clevis encrypt)" | clevis encrypt tpm2 '{"hash": "sha1"}' | ./target/debug/clevis-pin-tpm2 decrypt || (echo "Failed: with name alg (clevis encrypt)"; exit 1)
echo "Working: with PCRs" | ./target/debug/clevis-pin-tpm2 encrypt '{"pcr_ids":[23]}' | ./target/debug/clevis-pin-tpm2 decrypt || (echo "Failed: with PCRs"; exit 1)
echo "Working: with PCRs (clevis decrypt)" | ./target/debug/clevis-pin-tpm2 encrypt '{"pcr_ids":[23]}' | clevis decrypt || (echo "Failed: with PCRs (clevis decrypt)"; exit 1)
echo "Working: with PCRs (clevis encrypt)" | clevis encrypt tpm2 '{"pcr_ids":[23]}' | ./target/debug/clevis-pin-tpm2 decrypt || (echo "Failed: with PCRs (clevis encrypt)"; exit 1)
echo "Working: with PCRs sha1" | ./target/debug/clevis-pin-tpm2 encrypt '{"pcr_bank": "sha1", "pcr_ids":[23]}' | ./target/debug/clevis-pin-tpm2 decrypt || (echo "Failed: with PCRs sha1"; exit 1)
echo "Working: with PCRs sha1 (clevis decrypt)" | ./target/debug/clevis-pin-tpm2 encrypt '{"pcr_bank": "sha1", "pcr_ids":[23]}' | clevis decrypt || (echo "Failed: with PCRs sha1 (clevis decrypt)"; exit 1)
echo "Working: with PCRs sha1 (clevis encrypt)" | clevis encrypt tpm2 '{"pcr_bank": "sha1", "pcr_ids":[23]}' | ./target/debug/clevis-pin-tpm2 decrypt || (echo "Failed: with PCRs sha1 (clevis encrypt)"; exit 1)
echo "Working: no sealing rsa" | ./target/debug/clevis-pin-tpm2 encrypt '{"key": "rsa"}' | ./target/debug/clevis-pin-tpm2 decrypt || (echo "Failed: no sealing rsa"; exit 1)
echo "Working: no sealing rsa (clevis decrypt)" | ./target/debug/clevis-pin-tpm2 encrypt '{"key": "rsa"}' | clevis decrypt || (echo "Failed: no sealing rsa (clevis decrypt)"; exit 1)
echo "Working: no sealing rsa (clevis encrypt)" | clevis encrypt tpm2 '{"key": "rsa"}' | ./target/debug/clevis-pin-tpm2 decrypt || (echo "Failed: no sealing rsa (clevis encrypt)"; exit 1)
# Negative test (PCR change)
token=$(echo Failed | ./target/debug/clevis-pin-tpm2 encrypt '{"pcr_ids":[23]}')
tpm2_pcrevent -Q README.md 23
res=$(echo "$token" | ./target/debug/clevis-pin-tpm2 decrypt 2>/dev/null)
ret=$?
if [ $ret == 0 -a "$res" == "Failed" ]
then
echo "Managed to decrypt after changing PCR"
exit 1
elif [ $ret == 0 -o "$res" != "" ]
then
echo "Something went wrong"
exit 1
else
echo "Working: with PCRs and change"
fi