Skip to content

Commit 2e7dea7

Browse files
authored
Merge pull request #49 from hnez/tpm
machines: machine: attach TPM emulation if a state file is present
2 parents 531ba42 + fa898b1 commit 2e7dea7

6 files changed

Lines changed: 218 additions & 1 deletion

File tree

contrib/tpm-setup/setup.sh

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
#!/usr/bin/bash
2+
3+
set -Eeuo pipefail
4+
5+
# Print an error message if any command fails.
6+
trap 'echo "Error: command failed at ${BASH_SOURCE[0]}:${LINENO}" >&2' ERR
7+
8+
usage() {
9+
echo "Usage: $(basename "$0") <base dir> <user/repo/machine> [scripts]" >&2
10+
echo >&2
11+
echo "Initialize a software TPM and set ip up for ssh / x509 certificate"
12+
echo >&2
13+
echo " Example: $(basename "$0") env forrest-runner/test/build ssh.sh"
14+
15+
exit 1
16+
}
17+
18+
main() {
19+
if [[ $# -lt 2 ]]; then
20+
usage
21+
fi
22+
23+
local self="$(realpath "${0}")"
24+
local selfdir="$(dirname "${self}")"
25+
26+
local base_dir="$1"
27+
local triplet="$2"
28+
29+
if [[ ! "$triplet" =~ ^[A-Za-z0-9_-]+/[A-Za-z0-9_-]+/[A-Za-z0-9_-]+$ ]]; then
30+
echo "Error: argument must be of the form 'user/repo/machine'" >&2
31+
exit 1
32+
fi
33+
34+
local state_file="${base_dir}/machines/${triplet}.swtpm"
35+
local store_dir="${base_dir}/machines/${triplet}/tpm/tpm2_pkcs11"
36+
37+
mkdir -p "${store_dir}"
38+
39+
swtpm_setup --tpm2 --ecc --tpmstate "file://${state_file}"
40+
41+
swtpm socket \
42+
--tpm2 \
43+
--tpmstate "backend-uri=file://${state_file}" \
44+
--server type=unixio,path=server.sock \
45+
--ctrl type=unixio,path=server.sock.ctrl \
46+
--daemon \
47+
--pid file=swtpm.pid \
48+
--flags startup-clear
49+
50+
if [[ -e "/usr/lib/x86_64-linux-gnu/pkcs11/libtpm2_pkcs11.so" ]]; then
51+
export PKCS11_PROVIDER_MODULE="/usr/lib/x86_64-linux-gnu/pkcs11/libtpm2_pkcs11.so"
52+
else
53+
export PKCS11_PROVIDER_MODULE="/usr/lib/pkcs11/libtpm2_pkcs11.so"
54+
fi
55+
56+
export PKCS11_SO_PIN="${PKCS11_SO_PIN:-0000}"
57+
export PKCS11_USER_PIN="${PKCS11_USER_PIN:-0000}"
58+
59+
export TPM2TOOLS_TCTI="swtpm:path=$(readlink -f server.sock)"
60+
export TPM2_PKCS11_TCTI="${TPM2TOOLS_TCTI}"
61+
export TPM2_PKCS11_STORE="${store_dir}"
62+
63+
pkcs11-tool \
64+
--module "${PKCS11_PROVIDER_MODULE}" \
65+
--slot-index=0 \
66+
--init-token \
67+
--label=tpm \
68+
--so-pin="${PKCS11_SO_PIN}"
69+
70+
pkcs11-tool \
71+
--module "${PKCS11_PROVIDER_MODULE}" \
72+
--init-pin --login --so-pin="${PKCS11_SO_PIN}" \
73+
--slot-index=0 \
74+
--new-pin="${PKCS11_USER_PIN}"
75+
76+
echo "-----------------"
77+
echo "Run setup scripts"
78+
echo "-----------------"
79+
80+
for script in "${@:3}"; do
81+
PATH="${selfdir}:${PATH}" ${script}
82+
done
83+
84+
kill "$(cat swtpm.pid)"
85+
}
86+
87+
main $*

contrib/tpm-setup/ssh.sh

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
#!/usr/bin/bash
2+
3+
set -Eeuo pipefail
4+
5+
pkcs11-tool \
6+
--module "${PKCS11_PROVIDER_MODULE}" \
7+
--keypairgen \
8+
--login --pin="${PKCS11_USER_PIN}" \
9+
--label="ssh" \
10+
--usage-sign \
11+
--key-type EC:prime256v1
12+
13+
echo "--- SSH Public Key ---"
14+
ssh-keygen -D "${PKCS11_PROVIDER_MODULE}"
15+
echo "--- SSH Public Key ---"

contrib/tpm-setup/x509-keygen.sh

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
#!/usr/bin/bash
2+
3+
set -Eeuo pipefail
4+
5+
label="${X509_LABEL:-key}"
6+
subject="${X509_LABEL:-/CN=${label}}"
7+
cert_path="${TPM2_PKCS11_STORE}/../${label}.cert.pem"
8+
key_path="${TPM2_PKCS11_STORE}/../${label}.key.pem"
9+
key_uri="pkcs11:object=${label};pin-value=${PKCS11_USER_PIN}"
10+
11+
pkcs11-tool \
12+
--module "${PKCS11_PROVIDER_MODULE}" \
13+
--keypairgen \
14+
--login --pin "${PKCS11_USER_PIN}" \
15+
--label="${label}" \
16+
--usage-sign \
17+
--key-type EC:prime256v1
18+
19+
# Generate self-signed certificate using the keypair generated above
20+
21+
openssl req \
22+
-new -x509 \
23+
-provider default -provider base -provider pkcs11 \
24+
-days 36500 \
25+
-subj "${subject}" \
26+
-key "${key_uri}" \
27+
-out "${cert_path}"
28+
29+
pkcs11-tool \
30+
--module "${PKCS11_PROVIDER_MODULE}" \
31+
--login --pin "${PKCS11_USER_PIN}" \
32+
--label="${label}" \
33+
--write-object ${cert_path} \
34+
--type cert
35+
36+
tmp_dir="$(mktemp --directory)"
37+
38+
cat > "${tmp_dir}/asn1.conf" <<EOF
39+
asn1=SEQUENCE:pkcs11_uri_seq
40+
41+
[pkcs11_uri_seq]
42+
version=VISIBLESTRING:PKCS\#11 Provider URI v1.0
43+
uri=UTF8:${key_uri}
44+
EOF
45+
46+
openssl asn1parse \
47+
-genconf "${tmp_dir}/asn1.conf" \
48+
-noout \
49+
-out "${tmp_dir}/key.der"
50+
51+
{
52+
echo "-----BEGIN PKCS#11 PROVIDER URI-----"
53+
openssl base64 -in "${tmp_dir}/key.der"
54+
echo "-----END PKCS#11 PROVIDER URI-----"
55+
} > "${key_path}"
56+
57+
rm -rf "${tmp_dir}"

src/machines/machine.rs

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,19 @@ const QEMU_ARGS: &[&[&str]] = &[
5555
"if=virtio,format=raw,discard=unmap,cache.writeback=on,cache.direct=on,cache.no-flush=on,file=job-config.img",
5656
],
5757
];
58+
const QEMU_ARGS_SWTPM: &[&[&str]] = &[
59+
&["-chardev", "socket,id=chrtpm,path=swtpm.ctrl"],
60+
&["-tpmdev", "emulator,id=tpm0,chardev=chrtpm"],
61+
&["-device", "tpm-tis,tpmdev=tpm0"],
62+
];
63+
64+
const SWTPM_CMD: &str = "/usr/bin/swtpm";
65+
const SWTPM_ARGS: &[&[&str]] = &[
66+
&["socket"],
67+
&["--tpm2"],
68+
&["--tpmstate", "backend-uri=file://tpm.swtpm"],
69+
&["--ctrl", "type=unixio,path=swtpm.ctrl"],
70+
];
5871

5972
#[derive(PartialEq, Clone, Copy, Debug)]
6073
pub(super) enum Status {
@@ -415,6 +428,30 @@ impl Machine {
415428
["-virtfs".into(), arg].into_iter()
416429
});
417430

431+
// Spawn a software TPM emulation if a state file is present.
432+
// We do not monitor the status of this process or wait for the control
433+
// socket to be ready.
434+
// Instead we just hope that it starts successfully.
435+
let (_swtpm, swtpm_args) = {
436+
let inner = self.inner();
437+
let pwd = inner.run_dir.as_ref().unwrap();
438+
439+
if pwd.path().join("tpm.swtpm").exists() {
440+
let mut swtpm = Command::new(SWTPM_CMD);
441+
442+
swtpm
443+
.kill_on_drop(true)
444+
.current_dir(pwd.path())
445+
.args(SWTPM_ARGS.iter().flat_map(|arg_list| *arg_list));
446+
447+
let child = swtpm.spawn()?;
448+
449+
(Some(child), QEMU_ARGS_SWTPM)
450+
} else {
451+
(None, [].as_slice())
452+
}
453+
};
454+
418455
// Assemble the complete set of arguments to pass to the qemu command.
419456
let mut qemu = {
420457
let inner = self.inner();
@@ -431,6 +468,7 @@ impl Machine {
431468
.arg("-smp")
432469
.arg(&smp)
433470
.args(QEMU_ARGS.iter().flat_map(|arg_list| *arg_list))
471+
.args(swtpm_args.iter().flat_map(|arg_list| *arg_list))
434472
.args(virtfs_args);
435473

436474
qemu

src/machines/run_dir.rs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use std::fs::{create_dir_all, File};
1+
use std::fs::{copy, create_dir_all, File};
22
use std::io::ErrorKind;
33
use std::path::{Path, PathBuf};
44

@@ -176,6 +176,18 @@ impl RunDir {
176176
)?
177177
};
178178

179+
// Copy a TPM state to the run dir _if_ one was prepared for the
180+
// machine type. This is optional. Continue if none is present.
181+
let machine_tmp_state_path = triplet.machine_tmp_state_path(base_dir);
182+
let tmp_state_path = run_dir.join("tpm.swtpm");
183+
copy(machine_tmp_state_path, tmp_state_path).or_else(|err| match err.kind() {
184+
ErrorKind::NotFound => {
185+
info!("Did not find TPM state for {machine}, continuing without TPM");
186+
Ok(0)
187+
}
188+
err => Err(err),
189+
})?;
190+
179191
let dir = Self {
180192
run_dir,
181193
machine_image,

src/machines/triplet.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,14 @@ impl Triplet {
118118
.join(&self.repository)
119119
.join(format!("{}.img", self.machine_name))
120120
}
121+
122+
pub(super) fn machine_tmp_state_path(&self, base_dir_path: &Path) -> PathBuf {
123+
base_dir_path
124+
.join("machines")
125+
.join(&self.owner)
126+
.join(&self.repository)
127+
.join(format!("{}.swtpm", self.machine_name))
128+
}
121129
}
122130

123131
impl std::fmt::Display for Triplet {

0 commit comments

Comments
 (0)