Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Seal info w EK pub or cert on systems without TPM #343

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
156 changes: 156 additions & 0 deletions examples/tpm2-ekseal/main.go
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))
}

}
236 changes: 236 additions & 0 deletions examples/tpm2-ekseal/tpm2-ekunseal.sh
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
Loading