-
Notifications
You must be signed in to change notification settings - Fork 161
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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.
- Loading branch information
Showing
21 changed files
with
1,798 additions
and
168 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,156 @@ | ||
// Binary tpm2-ekseal seals plain-text using a provided EK public area or | ||
// certificate and emits a duplicated object that can only be unsealed on the | ||
// system that has the provided EK. | ||
package main | ||
|
||
import ( | ||
"crypto/x509" | ||
"encoding/base64" | ||
"encoding/json" | ||
"encoding/pem" | ||
"flag" | ||
"fmt" | ||
"io" | ||
"os" | ||
|
||
"github.com/google/go-tpm/tpm2" | ||
) | ||
|
||
var ( | ||
flagEKPub = flag.String( | ||
"ek-pub", "", | ||
"Path to the endorsement key's public area saved as a binary file.\n"+ | ||
"May not be used with -ek-pem") | ||
|
||
flagEKPem = flag.String( | ||
"ek-pem", "", | ||
"Path to the endorsement key's public certificate.\n"+ | ||
"May not be used with -ek-pub") | ||
|
||
flagPlainText = flag.String( | ||
"plain-text", "-", | ||
`Plain-text data. Defaults to STDIN via "-".`) | ||
|
||
flagOutFormat = flag.String( | ||
"f", "json", | ||
"Output format. Options are 'json', 'cmds', and '0'.\n"+ | ||
"The format 'json' emits the encrypted data as a JSON object.\n"+ | ||
"The format 'cmds' emits three shell commands that can be copied \n"+ | ||
"and pasted into another system to easily create the encrypted\n"+ | ||
"data's three parts as files.\n"+ | ||
"The format '0' emits the encrypted data as base64-encoded\n"+ | ||
"data structures delimited by @@NULL@@.\n"+ | ||
"Both the 'cmds' and '0' formats may be used with unseal.sh on a\n"+ | ||
"Linux system with tpm2-tools to unseal the data.", | ||
) | ||
) | ||
|
||
func main() { | ||
flag.Parse() | ||
|
||
var ek tpm2.TPMTPublic | ||
|
||
switch { | ||
case *flagEKPem != "" && *flagEKPub != "": | ||
fmt.Fprintln(os.Stderr, "-ek-pem and -ek-pub are mutually exclusive") | ||
os.Exit(1) | ||
case *flagEKPem != "": | ||
pemData, err := os.ReadFile(*flagEKPem) | ||
if err != nil { | ||
fmt.Fprintf(os.Stderr, "failed to read ek pem data: %s", err) | ||
os.Exit(1) | ||
} | ||
pemBlock, _ := pem.Decode([]byte(pemData)) | ||
if pemBlock == nil { | ||
fmt.Fprintln(os.Stderr, "failed to decode ek pem data") | ||
os.Exit(1) | ||
} | ||
cert, err := x509.ParseCertificate(pemBlock.Bytes) | ||
if err != nil { | ||
fmt.Fprintf(os.Stderr, "failed to load ek cert %s", err) | ||
os.Exit(1) | ||
} | ||
if ek, err = tpm2.EKCertToTPMTPublic(*cert); err != nil { | ||
fmt.Fprintf(os.Stderr, "failed to load ek from cert %s", err) | ||
os.Exit(1) | ||
} | ||
case *flagEKPub != "": | ||
ekData, err := os.ReadFile(*flagEKPub) | ||
if err != nil { | ||
fmt.Fprintf(os.Stderr, "failed to read ek binary data: %s", err) | ||
os.Exit(1) | ||
} | ||
ekTPM2BPublic, err := tpm2.Unmarshal[tpm2.TPM2BPublic](ekData) | ||
if err != nil { | ||
fmt.Fprintf(os.Stderr, "failed to load ek: %s", err) | ||
os.Exit(1) | ||
} | ||
ekPtr, err := ekTPM2BPublic.Contents() | ||
if err != nil { | ||
fmt.Fprintf(os.Stderr, "failed to unbox ek: %s", err) | ||
os.Exit(1) | ||
} | ||
ek = *ekPtr | ||
} | ||
|
||
// Read the plain-text. | ||
var plainTextFile *os.File | ||
if *flagPlainText == "-" { | ||
plainTextFile = os.Stdin | ||
} else { | ||
f, err := os.Open(*flagPlainText) | ||
if err != nil { | ||
fmt.Fprintf(os.Stderr, "failed to open input file: %s", err) | ||
os.Exit(1) | ||
} | ||
plainTextFile = f | ||
defer f.Close() | ||
} | ||
plainText, err := io.ReadAll(plainTextFile) | ||
if err != nil { | ||
fmt.Fprintf(os.Stderr, "failed to read input data: %s", err) | ||
os.Exit(1) | ||
} | ||
|
||
pub, priv, seed, err := tpm2.EKSeal(ek, plainText) | ||
if err != nil { | ||
fmt.Fprintf(os.Stderr, "failed to seal plain-text: %s", err) | ||
os.Exit(1) | ||
} | ||
|
||
pubData, privData, seedData := | ||
tpm2.Marshal(pub), | ||
tpm2.Marshal(priv), | ||
tpm2.Marshal(seed) | ||
|
||
switch *flagOutFormat { | ||
case "json": | ||
enc := json.NewEncoder(os.Stdout) | ||
enc.SetIndent("", " ") | ||
if err := enc.Encode(struct { | ||
Public []byte `json:"public"` | ||
Private []byte `json:"private"` | ||
Seed []byte `json:"seed"` | ||
}{ | ||
Public: pubData, | ||
Private: privData, | ||
Seed: seedData, | ||
}); err != nil { | ||
fmt.Fprintf(os.Stderr, "failed to encode duped object: %s", err) | ||
os.Exit(1) | ||
} | ||
case "cmds": | ||
fmt.Printf("echo '%s' | base64 -d >enc.bin.pub;", | ||
base64.StdEncoding.EncodeToString(pubData)) | ||
fmt.Printf("echo '%s' | base64 -d >enc.bin.priv;", | ||
base64.StdEncoding.EncodeToString(privData)) | ||
fmt.Printf("echo '%s' | base64 -d >enc.bin.seed\n", | ||
base64.StdEncoding.EncodeToString(seedData)) | ||
case "0": | ||
fmt.Printf("%s@@NULL@@%s@@NULL@@%s", | ||
base64.StdEncoding.EncodeToString(pubData), | ||
base64.StdEncoding.EncodeToString(privData), | ||
base64.StdEncoding.EncodeToString(seedData)) | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,236 @@ | ||
#!/bin/sh | ||
# | ||
# A POSIX-compliant shell script | ||
# | ||
|
||
set -o errexit # Exits immediately on unexpected errors (does not bypass traps) | ||
set -o nounset # Errors if variables are used without first being defined | ||
|
||
################################################################################ | ||
## USAGE | ||
################################################################################ | ||
|
||
USAGE="USAGE: ${0} [FLAGS] | ||
unseals information sealed tog this system's endorsement key | ||
FLAGS | ||
-h prints this help | ||
-0 read the encrypted data via stdin as the TPM2B_PUBLIC, TPM2B_PRIVATE, | ||
and TPM2B_ENCRYPTED_SECRET data structures serialized as binary data, | ||
encoded with base64, and delimited with '@@NULL@@' | ||
-u PATH a binary file with the encrypted data's TPM2B_PUBLIC struct | ||
-i PATH a binary file with the encrypted data's TPM2B_PRIVATE struct | ||
-s PATH a binary file with the encrypted data's TPM2B_ENCRYPTED_SECRET struct | ||
-G ALG ek algorithm. valid choices are rsa and ecc. defaults to rsa | ||
" | ||
|
||
################################################################################ | ||
## TPM_HANDLES | ||
################################################################################ | ||
|
||
TPK_EK_CRT='0x01C00002' # reserved handle for endorsement key cert | ||
# - https://via.vmw.com/tpm2-provisioning-guide | ||
|
||
TPM_RH_ENDORSEMENT='0x4000000B' # reserved handle for endorsement primary seed | ||
# - https://via.vmw.com/TPM_RH_ENDORSEMENT | ||
# - https://via.vmw.com/tpm2-structs | ||
|
||
|
||
################################################################################ | ||
## temp files | ||
################################################################################ | ||
|
||
# Create some temporary files used in decrypting the data. | ||
EK_CTX="$(mktemp)"; rm -f "${EK_CTX}" | ||
SESSION_DAT="$(mktemp)"; rm -f "${SESSION_DAT}" | ||
ENC_OBJ_CTX="$(mktemp)"; rm -f "${ENC_OBJ_CTX}" | ||
ENC_OBJ_KEY="$(mktemp)"; rm -f "${ENC_OBJ_KEY}" | ||
ENC_OBJ_PUB="$(mktemp)"; rm -f "${ENC_OBJ_PUB}" | ||
ENC_OBJ_PRIV="$(mktemp)"; rm -f "${ENC_OBJ_PRIV}" | ||
ENC_OBJ_SEED="$(mktemp)"; rm -f "${ENC_OBJ_SEED}" | ||
|
||
|
||
################################################################################ | ||
## flag values | ||
################################################################################ | ||
|
||
READ_STDIN="" | ||
EK_ALG="rsa" | ||
|
||
|
||
################################################################################ | ||
## funcs | ||
################################################################################ | ||
|
||
# error stores exit code, writes arguments to STDERR, and returns stored exit | ||
# code fatal is like error except it will exit program if exit code >0 | ||
error() { | ||
exit_code="${?}" | ||
echo "${@}" 1>&2 | ||
return "${exit_code}" | ||
} | ||
fatal() { | ||
error "${@}" | ||
exit_code="${?}" | ||
[ "${exit_code}" -gt "0" ] || exit_code=1 | ||
exit "${exit_code}" | ||
} | ||
check_command() { | ||
command -v "${1}" >/dev/null 2>&1 || fatal "${1} is required" | ||
} | ||
check_dependencies() { | ||
check_command tpm2_createek # https://via.vmw.com/tpm2_createek.md | ||
check_command tpm2_flushcontext # https://via.vmw.com/tpm2_flushcontext.md | ||
check_command tpm2_import # https://via.vmw.com/tpm2_import.md | ||
check_command tpm2_load # https://via.vmw.com/tpm2_load.md | ||
check_command tpm2_nvread # https://via.vmw.com/tpm2_nvread.md | ||
check_command tpm2_policysecret # https://via.vmw.com/tpm2_policysecret.md | ||
check_command tpm2_readpublic # https://via.vmw.com/tpm2_readpublic.md | ||
check_command tpm2_startauthsession # https://via.vmw.com/tpm2_startauthsession.md | ||
check_command tpm2_unseal # https://via.vmw.com/tpm2_unseal.md | ||
} | ||
cleanup() { | ||
# remove any temp files that might exist | ||
rm -f "${EK_CTX}" \ | ||
"${SESSION_DAT}" \ | ||
"${ENC_OBJ_CTX}" \ | ||
"${ENC_OBJ_KEY}" \ | ||
|
||
# only delete the encrypted object's public, private, and seed files if they | ||
# were created as temporary files due to the input coming from stdin | ||
if [ "${READ_STDIN}" = "1" ]; then | ||
rm -f "${ENC_OBJ_PUB}" \ | ||
"${ENC_OBJ_PRIV}" \ | ||
"${ENC_OBJ_SEED}" | ||
fi | ||
} | ||
|
||
|
||
################################################################################ | ||
## main | ||
################################################################################ | ||
|
||
# Clean up any lingering files on exit. | ||
trap cleanup EXIT | ||
|
||
# Verify the required dependencies are met. | ||
check_dependencies | ||
|
||
# Parse the command line arguments | ||
while getopts ":h0u:i:s:G:" opt; do | ||
case ${opt} in | ||
h) | ||
fatal "${USAGE}" | ||
;; | ||
0) | ||
READ_STDIN="1" | ||
;; | ||
u) | ||
ENC_OBJ_PUB="${OPTARG}" | ||
;; | ||
i) | ||
ENC_OBJ_PRIV="${OPTARG}" | ||
;; | ||
s) | ||
ENC_OBJ_SEED="${OPTARG}" | ||
;; | ||
G) | ||
EK_ALG="${OPTARG}" | ||
;; | ||
*) | ||
# Ignore other flags | ||
;; | ||
esac | ||
done | ||
shift $((OPTIND - 1)) | ||
|
||
if [ "${READ_STDIN}" = "1" ]; then | ||
read -r stdin | ||
echo "${stdin}" | awk -F'@@NULL@@' '{print $1}' | base64 -d >"${ENC_OBJ_PUB}" | ||
echo "${stdin}" | awk -F'@@NULL@@' '{print $2}' | base64 -d >"${ENC_OBJ_PRIV}" | ||
echo "${stdin}" | awk -F'@@NULL@@' '{print $3}' | base64 -d >"${ENC_OBJ_SEED}" | ||
fi | ||
|
||
if [ ! "${EK_ALG:-}" = "rsa" ] && [ ! "${EK_ALG:-}" = "ecc" ]; then | ||
fatal "-G ${EK_ALG:-} is invalid. valid choices are rsa or ecc" | ||
fi | ||
|
||
|
||
# | ||
# VALIDATE EK CERT | ||
# | ||
|
||
# vSphere VMs with a vTPM have an EK certificate in their NVRAM at the address | ||
# TPK_EK_CRT. Exit with an error if this certificate does not exist. | ||
tpm2_nvread "${TPK_EK_CRT}" >/dev/null 2>&1 || fatal "missing ek cert" | ||
|
||
|
||
# | ||
# CREATE EK | ||
# | ||
|
||
# While vSphere VMs with a vTPM have an EK certificate in their NVRAM, the | ||
# actual EK is not created by default. | ||
tpm2_createek -c "${EK_CTX}" -G "${EK_ALG}" 1>&2 | ||
|
||
|
||
# | ||
# IMPORT | ||
# | ||
|
||
# Create an authentication session with the TPM in order to use it to unseal | ||
# the provided data. | ||
tpm2_startauthsession --policy-session -S "${SESSION_DAT}" 1>&2 | ||
|
||
# Couple the authentication session with the EK's primary seed. | ||
tpm2_policysecret -c "${TPM_RH_ENDORSEMENT}" -S "${SESSION_DAT}" 1>&2 | ||
|
||
# Import the encrypted object's cryptographic information as a child of the EK | ||
# and persist to disk the encrypted, private portion of the object to be | ||
# decrypted. | ||
tpm2_import \ | ||
-C "${EK_CTX}" \ | ||
-P "session:${SESSION_DAT}" \ | ||
-u "${ENC_OBJ_PUB}" \ | ||
-i "${ENC_OBJ_PRIV}" \ | ||
-s "${ENC_OBJ_SEED}" \ | ||
-r "${ENC_OBJ_KEY}" 1>&2 | ||
|
||
# Flush the session context. | ||
tpm2_flushcontext "${SESSION_DAT}" 1>&2 | ||
|
||
# | ||
# LOAD | ||
# | ||
|
||
# Create an authentication session with the TPM in order to use it to unseal | ||
# the provided data. | ||
tpm2_startauthsession --policy-session -S "${SESSION_DAT}" 1>&2 | ||
|
||
# Couple the authentication session with the EK's primary seed. | ||
tpm2_policysecret -c "${TPM_RH_ENDORSEMENT}" -S "${SESSION_DAT}" 1>&2 | ||
|
||
# Load the encrypted object into the specified context file so it can be | ||
# unsealed. This step requires the key from the previous step. | ||
tpm2_load \ | ||
-C "${EK_CTX}" \ | ||
-P "session:${SESSION_DAT}" \ | ||
-u "${ENC_OBJ_PUB}" \ | ||
-r "${ENC_OBJ_KEY}" \ | ||
-c "${ENC_OBJ_CTX}" 1>&2 | ||
|
||
# Flush the session context. | ||
tpm2_flushcontext "${SESSION_DAT}" 1>&2 | ||
|
||
# | ||
# UNSEAL | ||
# | ||
|
||
# Create an authentication session with the TPM in order to use it to unseal | ||
# the provided data. | ||
tpm2_startauthsession --policy-session -S "${SESSION_DAT}" 1>&2 | ||
|
||
tpm2_unseal -p "session:${SESSION_DAT}" -c "${ENC_OBJ_CTX}" | ||
|
||
# Flush the session context. | ||
tpm2_flushcontext "${SESSION_DAT}" 1>&2 |
Oops, something went wrong.