Skip to content

Commit 05ab0eb

Browse files
committed
Seal info w EK pub or cert on systems without TPM
This patch introduces support for sealing information to an endorsement key without needing to be on a system with a TPM. This feature enables support for clients that want to encrypt small amounts of data in order to bootstrap a virtual machine that may only have an endorsement key prior to first boot.
1 parent c49efc4 commit 05ab0eb

21 files changed

Lines changed: 1798 additions & 168 deletions

examples/tpm2-ekseal/main.go

Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
// Binary tpm2-ekseal seals plain-text using a provided EK public area or
2+
// certificate and emits a duplicated object that can only be unsealed on the
3+
// system that has the provided EK.
4+
package main
5+
6+
import (
7+
"crypto/x509"
8+
"encoding/base64"
9+
"encoding/json"
10+
"encoding/pem"
11+
"flag"
12+
"fmt"
13+
"io"
14+
"os"
15+
16+
"github.com/google/go-tpm/tpm2"
17+
)
18+
19+
var (
20+
flagEKPub = flag.String(
21+
"ek-pub", "",
22+
"Path to the endorsement key's public area saved as a binary file.\n"+
23+
"May not be used with -ek-pem")
24+
25+
flagEKPem = flag.String(
26+
"ek-pem", "",
27+
"Path to the endorsement key's public certificate.\n"+
28+
"May not be used with -ek-pub")
29+
30+
flagPlainText = flag.String(
31+
"plain-text", "-",
32+
`Plain-text data. Defaults to STDIN via "-".`)
33+
34+
flagOutFormat = flag.String(
35+
"f", "json",
36+
"Output format. Options are 'json', 'cmds', and '0'.\n"+
37+
"The format 'json' emits the encrypted data as a JSON object.\n"+
38+
"The format 'cmds' emits three shell commands that can be copied \n"+
39+
"and pasted into another system to easily create the encrypted\n"+
40+
"data's three parts as files.\n"+
41+
"The format '0' emits the encrypted data as base64-encoded\n"+
42+
"data structures delimited by @@NULL@@.\n"+
43+
"Both the 'cmds' and '0' formats may be used with unseal.sh on a\n"+
44+
"Linux system with tpm2-tools to unseal the data.",
45+
)
46+
)
47+
48+
func main() {
49+
flag.Parse()
50+
51+
var ek tpm2.TPMTPublic
52+
53+
switch {
54+
case *flagEKPem != "" && *flagEKPub != "":
55+
fmt.Fprintln(os.Stderr, "-ek-pem and -ek-pub are mutually exclusive")
56+
os.Exit(1)
57+
case *flagEKPem != "":
58+
pemData, err := os.ReadFile(*flagEKPem)
59+
if err != nil {
60+
fmt.Fprintf(os.Stderr, "failed to read ek pem data: %s", err)
61+
os.Exit(1)
62+
}
63+
pemBlock, _ := pem.Decode([]byte(pemData))
64+
if pemBlock == nil {
65+
fmt.Fprintln(os.Stderr, "failed to decode ek pem data")
66+
os.Exit(1)
67+
}
68+
cert, err := x509.ParseCertificate(pemBlock.Bytes)
69+
if err != nil {
70+
fmt.Fprintf(os.Stderr, "failed to load ek cert %s", err)
71+
os.Exit(1)
72+
}
73+
if ek, err = tpm2.EKCertToTPMTPublic(*cert); err != nil {
74+
fmt.Fprintf(os.Stderr, "failed to load ek from cert %s", err)
75+
os.Exit(1)
76+
}
77+
case *flagEKPub != "":
78+
ekData, err := os.ReadFile(*flagEKPub)
79+
if err != nil {
80+
fmt.Fprintf(os.Stderr, "failed to read ek binary data: %s", err)
81+
os.Exit(1)
82+
}
83+
ekTPM2BPublic, err := tpm2.Unmarshal[tpm2.TPM2BPublic](ekData)
84+
if err != nil {
85+
fmt.Fprintf(os.Stderr, "failed to load ek: %s", err)
86+
os.Exit(1)
87+
}
88+
ekPtr, err := ekTPM2BPublic.Contents()
89+
if err != nil {
90+
fmt.Fprintf(os.Stderr, "failed to unbox ek: %s", err)
91+
os.Exit(1)
92+
}
93+
ek = *ekPtr
94+
}
95+
96+
// Read the plain-text.
97+
var plainTextFile *os.File
98+
if *flagPlainText == "-" {
99+
plainTextFile = os.Stdin
100+
} else {
101+
f, err := os.Open(*flagPlainText)
102+
if err != nil {
103+
fmt.Fprintf(os.Stderr, "failed to open input file: %s", err)
104+
os.Exit(1)
105+
}
106+
plainTextFile = f
107+
defer f.Close()
108+
}
109+
plainText, err := io.ReadAll(plainTextFile)
110+
if err != nil {
111+
fmt.Fprintf(os.Stderr, "failed to read input data: %s", err)
112+
os.Exit(1)
113+
}
114+
115+
pub, priv, seed, err := tpm2.EKSeal(ek, plainText)
116+
if err != nil {
117+
fmt.Fprintf(os.Stderr, "failed to seal plain-text: %s", err)
118+
os.Exit(1)
119+
}
120+
121+
pubData, privData, seedData :=
122+
tpm2.Marshal(pub),
123+
tpm2.Marshal(priv),
124+
tpm2.Marshal(seed)
125+
126+
switch *flagOutFormat {
127+
case "json":
128+
enc := json.NewEncoder(os.Stdout)
129+
enc.SetIndent("", " ")
130+
if err := enc.Encode(struct {
131+
Public []byte `json:"public"`
132+
Private []byte `json:"private"`
133+
Seed []byte `json:"seed"`
134+
}{
135+
Public: pubData,
136+
Private: privData,
137+
Seed: seedData,
138+
}); err != nil {
139+
fmt.Fprintf(os.Stderr, "failed to encode duped object: %s", err)
140+
os.Exit(1)
141+
}
142+
case "cmds":
143+
fmt.Printf("echo '%s' | base64 -d >enc.bin.pub;",
144+
base64.StdEncoding.EncodeToString(pubData))
145+
fmt.Printf("echo '%s' | base64 -d >enc.bin.priv;",
146+
base64.StdEncoding.EncodeToString(privData))
147+
fmt.Printf("echo '%s' | base64 -d >enc.bin.seed\n",
148+
base64.StdEncoding.EncodeToString(seedData))
149+
case "0":
150+
fmt.Printf("%s@@NULL@@%s@@NULL@@%s",
151+
base64.StdEncoding.EncodeToString(pubData),
152+
base64.StdEncoding.EncodeToString(privData),
153+
base64.StdEncoding.EncodeToString(seedData))
154+
}
155+
156+
}
Lines changed: 236 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,236 @@
1+
#!/bin/sh
2+
#
3+
# A POSIX-compliant shell script
4+
#
5+
6+
set -o errexit # Exits immediately on unexpected errors (does not bypass traps)
7+
set -o nounset # Errors if variables are used without first being defined
8+
9+
################################################################################
10+
## USAGE
11+
################################################################################
12+
13+
USAGE="USAGE: ${0} [FLAGS]
14+
unseals information sealed tog this system's endorsement key
15+
16+
FLAGS
17+
-h prints this help
18+
-0 read the encrypted data via stdin as the TPM2B_PUBLIC, TPM2B_PRIVATE,
19+
and TPM2B_ENCRYPTED_SECRET data structures serialized as binary data,
20+
encoded with base64, and delimited with '@@NULL@@'
21+
-u PATH a binary file with the encrypted data's TPM2B_PUBLIC struct
22+
-i PATH a binary file with the encrypted data's TPM2B_PRIVATE struct
23+
-s PATH a binary file with the encrypted data's TPM2B_ENCRYPTED_SECRET struct
24+
-G ALG ek algorithm. valid choices are rsa and ecc. defaults to rsa
25+
"
26+
27+
################################################################################
28+
## TPM_HANDLES
29+
################################################################################
30+
31+
TPK_EK_CRT='0x01C00002' # reserved handle for endorsement key cert
32+
# - https://via.vmw.com/tpm2-provisioning-guide
33+
34+
TPM_RH_ENDORSEMENT='0x4000000B' # reserved handle for endorsement primary seed
35+
# - https://via.vmw.com/TPM_RH_ENDORSEMENT
36+
# - https://via.vmw.com/tpm2-structs
37+
38+
39+
################################################################################
40+
## temp files
41+
################################################################################
42+
43+
# Create some temporary files used in decrypting the data.
44+
EK_CTX="$(mktemp)"; rm -f "${EK_CTX}"
45+
SESSION_DAT="$(mktemp)"; rm -f "${SESSION_DAT}"
46+
ENC_OBJ_CTX="$(mktemp)"; rm -f "${ENC_OBJ_CTX}"
47+
ENC_OBJ_KEY="$(mktemp)"; rm -f "${ENC_OBJ_KEY}"
48+
ENC_OBJ_PUB="$(mktemp)"; rm -f "${ENC_OBJ_PUB}"
49+
ENC_OBJ_PRIV="$(mktemp)"; rm -f "${ENC_OBJ_PRIV}"
50+
ENC_OBJ_SEED="$(mktemp)"; rm -f "${ENC_OBJ_SEED}"
51+
52+
53+
################################################################################
54+
## flag values
55+
################################################################################
56+
57+
READ_STDIN=""
58+
EK_ALG="rsa"
59+
60+
61+
################################################################################
62+
## funcs
63+
################################################################################
64+
65+
# error stores exit code, writes arguments to STDERR, and returns stored exit
66+
# code fatal is like error except it will exit program if exit code >0
67+
error() {
68+
exit_code="${?}"
69+
echo "${@}" 1>&2
70+
return "${exit_code}"
71+
}
72+
fatal() {
73+
error "${@}"
74+
exit_code="${?}"
75+
[ "${exit_code}" -gt "0" ] || exit_code=1
76+
exit "${exit_code}"
77+
}
78+
check_command() {
79+
command -v "${1}" >/dev/null 2>&1 || fatal "${1} is required"
80+
}
81+
check_dependencies() {
82+
check_command tpm2_createek # https://via.vmw.com/tpm2_createek.md
83+
check_command tpm2_flushcontext # https://via.vmw.com/tpm2_flushcontext.md
84+
check_command tpm2_import # https://via.vmw.com/tpm2_import.md
85+
check_command tpm2_load # https://via.vmw.com/tpm2_load.md
86+
check_command tpm2_nvread # https://via.vmw.com/tpm2_nvread.md
87+
check_command tpm2_policysecret # https://via.vmw.com/tpm2_policysecret.md
88+
check_command tpm2_readpublic # https://via.vmw.com/tpm2_readpublic.md
89+
check_command tpm2_startauthsession # https://via.vmw.com/tpm2_startauthsession.md
90+
check_command tpm2_unseal # https://via.vmw.com/tpm2_unseal.md
91+
}
92+
cleanup() {
93+
# remove any temp files that might exist
94+
rm -f "${EK_CTX}" \
95+
"${SESSION_DAT}" \
96+
"${ENC_OBJ_CTX}" \
97+
"${ENC_OBJ_KEY}" \
98+
99+
# only delete the encrypted object's public, private, and seed files if they
100+
# were created as temporary files due to the input coming from stdin
101+
if [ "${READ_STDIN}" = "1" ]; then
102+
rm -f "${ENC_OBJ_PUB}" \
103+
"${ENC_OBJ_PRIV}" \
104+
"${ENC_OBJ_SEED}"
105+
fi
106+
}
107+
108+
109+
################################################################################
110+
## main
111+
################################################################################
112+
113+
# Clean up any lingering files on exit.
114+
trap cleanup EXIT
115+
116+
# Verify the required dependencies are met.
117+
check_dependencies
118+
119+
# Parse the command line arguments
120+
while getopts ":h0u:i:s:G:" opt; do
121+
case ${opt} in
122+
h)
123+
fatal "${USAGE}"
124+
;;
125+
0)
126+
READ_STDIN="1"
127+
;;
128+
u)
129+
ENC_OBJ_PUB="${OPTARG}"
130+
;;
131+
i)
132+
ENC_OBJ_PRIV="${OPTARG}"
133+
;;
134+
s)
135+
ENC_OBJ_SEED="${OPTARG}"
136+
;;
137+
G)
138+
EK_ALG="${OPTARG}"
139+
;;
140+
*)
141+
# Ignore other flags
142+
;;
143+
esac
144+
done
145+
shift $((OPTIND - 1))
146+
147+
if [ "${READ_STDIN}" = "1" ]; then
148+
read -r stdin
149+
echo "${stdin}" | awk -F'@@NULL@@' '{print $1}' | base64 -d >"${ENC_OBJ_PUB}"
150+
echo "${stdin}" | awk -F'@@NULL@@' '{print $2}' | base64 -d >"${ENC_OBJ_PRIV}"
151+
echo "${stdin}" | awk -F'@@NULL@@' '{print $3}' | base64 -d >"${ENC_OBJ_SEED}"
152+
fi
153+
154+
if [ ! "${EK_ALG:-}" = "rsa" ] && [ ! "${EK_ALG:-}" = "ecc" ]; then
155+
fatal "-G ${EK_ALG:-} is invalid. valid choices are rsa or ecc"
156+
fi
157+
158+
159+
#
160+
# VALIDATE EK CERT
161+
#
162+
163+
# vSphere VMs with a vTPM have an EK certificate in their NVRAM at the address
164+
# TPK_EK_CRT. Exit with an error if this certificate does not exist.
165+
tpm2_nvread "${TPK_EK_CRT}" >/dev/null 2>&1 || fatal "missing ek cert"
166+
167+
168+
#
169+
# CREATE EK
170+
#
171+
172+
# While vSphere VMs with a vTPM have an EK certificate in their NVRAM, the
173+
# actual EK is not created by default.
174+
tpm2_createek -c "${EK_CTX}" -G "${EK_ALG}" 1>&2
175+
176+
177+
#
178+
# IMPORT
179+
#
180+
181+
# Create an authentication session with the TPM in order to use it to unseal
182+
# the provided data.
183+
tpm2_startauthsession --policy-session -S "${SESSION_DAT}" 1>&2
184+
185+
# Couple the authentication session with the EK's primary seed.
186+
tpm2_policysecret -c "${TPM_RH_ENDORSEMENT}" -S "${SESSION_DAT}" 1>&2
187+
188+
# Import the encrypted object's cryptographic information as a child of the EK
189+
# and persist to disk the encrypted, private portion of the object to be
190+
# decrypted.
191+
tpm2_import \
192+
-C "${EK_CTX}" \
193+
-P "session:${SESSION_DAT}" \
194+
-u "${ENC_OBJ_PUB}" \
195+
-i "${ENC_OBJ_PRIV}" \
196+
-s "${ENC_OBJ_SEED}" \
197+
-r "${ENC_OBJ_KEY}" 1>&2
198+
199+
# Flush the session context.
200+
tpm2_flushcontext "${SESSION_DAT}" 1>&2
201+
202+
#
203+
# LOAD
204+
#
205+
206+
# Create an authentication session with the TPM in order to use it to unseal
207+
# the provided data.
208+
tpm2_startauthsession --policy-session -S "${SESSION_DAT}" 1>&2
209+
210+
# Couple the authentication session with the EK's primary seed.
211+
tpm2_policysecret -c "${TPM_RH_ENDORSEMENT}" -S "${SESSION_DAT}" 1>&2
212+
213+
# Load the encrypted object into the specified context file so it can be
214+
# unsealed. This step requires the key from the previous step.
215+
tpm2_load \
216+
-C "${EK_CTX}" \
217+
-P "session:${SESSION_DAT}" \
218+
-u "${ENC_OBJ_PUB}" \
219+
-r "${ENC_OBJ_KEY}" \
220+
-c "${ENC_OBJ_CTX}" 1>&2
221+
222+
# Flush the session context.
223+
tpm2_flushcontext "${SESSION_DAT}" 1>&2
224+
225+
#
226+
# UNSEAL
227+
#
228+
229+
# Create an authentication session with the TPM in order to use it to unseal
230+
# the provided data.
231+
tpm2_startauthsession --policy-session -S "${SESSION_DAT}" 1>&2
232+
233+
tpm2_unseal -p "session:${SESSION_DAT}" -c "${ENC_OBJ_CTX}"
234+
235+
# Flush the session context.
236+
tpm2_flushcontext "${SESSION_DAT}" 1>&2

0 commit comments

Comments
 (0)