1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321
|
package cmd
import (
"errors"
"fmt"
"io"
"os"
"strconv"
"strings"
"github.com/google/go-tpm-tools/client"
"github.com/google/go-tpm/legacy/tpm2"
"github.com/spf13/cobra"
"google.golang.org/protobuf/proto"
)
var (
output string
input string
nvIndex uint32
nonce []byte
teeNonce []byte
keyAlgo = tpm2.AlgRSA
pcrs []int
format string
asAddress string
audience string
eventLog string
cloudLog bool
customNonce []string
)
type pcrsFlag struct {
value *[]int
}
func (f *pcrsFlag) Set(val string) error {
for _, d := range strings.Split(val, ",") {
pcr, err := strconv.Atoi(d)
if err != nil {
return err
}
if pcr < 0 || pcr >= client.NumPCRs {
return errors.New("pcr out of range")
}
*f.value = append(*f.value, pcr)
}
return nil
}
func (f *pcrsFlag) Type() string {
return "pcrs"
}
func (f *pcrsFlag) String() string {
if len(*f.value) == 0 {
return ""
}
var b strings.Builder
fmt.Fprintf(&b, "%d", (*f.value)[0])
for _, pcr := range (*f.value)[1:] {
fmt.Fprintf(&b, ",%d", pcr)
}
return b.String()
}
var algos = map[tpm2.Algorithm]string{
tpm2.AlgUnknown: "",
tpm2.AlgRSA: "rsa",
tpm2.AlgECC: "ecc",
tpm2.AlgSHA1: "sha1",
tpm2.AlgSHA256: "sha256",
tpm2.AlgSHA384: "sha384",
tpm2.AlgSHA512: "sha512",
}
type algoFlag struct {
value *tpm2.Algorithm
allowed []tpm2.Algorithm
}
func (f *algoFlag) Set(val string) error {
present := false
for _, algo := range f.allowed {
if algos[algo] == val {
*f.value = algo
present = true
}
}
if !present {
return errors.New("unknown algorithm")
}
return nil
}
func (f *algoFlag) Type() string {
return "algo"
}
func (f *algoFlag) String() string {
return algos[*f.value]
}
// Allowed gives a string list of the permitted algorithm values for this flag.
func (f *algoFlag) Allowed() string {
out := make([]string, len(f.allowed))
for i, a := range f.allowed {
out[i] = algos[a]
}
return strings.Join(out, ", ")
}
// Disable the "help" subcommand (and just use the -h/--help flags).
// This should be called on all commands with subcommands.
// See https://github.com/spf13/cobra/issues/587 for why this is needed.
func hideHelp(cmd *cobra.Command) {
cmd.SetHelpCommand(&cobra.Command{Hidden: true})
}
// Lets this command specify an output file, for use with dataOutput().
func addOutputFlag(cmd *cobra.Command) {
cmd.PersistentFlags().StringVar(&output, "output", "",
"output file (defaults to stdout)")
}
// Lets this command specify an input file, for use with dataInput().
func addInputFlag(cmd *cobra.Command) {
cmd.PersistentFlags().StringVar(&input, "input", "",
"input file (defaults to stdin)")
}
// Lets this command specify an Attestation Server Address.
func addAsAddressFlag(cmd *cobra.Command) {
cmd.PersistentFlags().StringVar(&asAddress, "verifier-endpoint", "https://confidentialcomputing.googleapis.com",
"the attestation verifier endpoint used to retrieve an attestation claims token")
}
// Lets this command enable Cloud logging.
func addCloudLoggingFlag(cmd *cobra.Command) {
cmd.Flags().BoolVar(&cloudLog, "cloud-log", false, "logs the attestation and token to Cloud Logging for auditing purposes. Requires the audience flag.")
}
// Lets this command specify custom audience field of the attestation token.
func addAudienceFlag(cmd *cobra.Command) {
cmd.PersistentFlags().StringVar(&audience, "audience", "",
"the audience field in the claims token. Cannot be sts.googleapis.com.")
}
// Lets this command specify custom nonce field of the attestation token.
func addCustomNonceFlag(cmd *cobra.Command) {
cmd.PersistentFlags().StringArrayVar(&customNonce, "custom-nonce", nil,
"the custom nonce field in the claims token. use this flag multiple times to add multiple custom nonces.")
}
// Lets this command specify event log path.
func addEventLogFlag(cmd *cobra.Command) {
cmd.PersistentFlags().StringVar(&eventLog, "event-log", "/sys/kernel/security/tpm0/binary_bios_measurements", "specifies the event log file path.")
}
// Lets this command specify an NVDATA index, for use with nvIndex.
func addIndexFlag(cmd *cobra.Command) {
cmd.PersistentFlags().Uint32Var(&nvIndex, "index", 0,
"NVDATA index, cannot be 0")
}
// Lets this command specify some number of PCR arguments, check if in range.
func addPCRsFlag(cmd *cobra.Command) {
cmd.PersistentFlags().Var(&pcrsFlag{&pcrs}, "pcrs", "comma separated list of PCR numbers")
}
// Lets this command specify the public key algorithm.
func addPublicKeyAlgoFlag(cmd *cobra.Command) {
f := algoFlag{&keyAlgo, []tpm2.Algorithm{tpm2.AlgRSA, tpm2.AlgECC}}
cmd.PersistentFlags().Var(&f, "algo", "public key algorithm: "+f.Allowed())
}
func addHashAlgoFlag(cmd *cobra.Command, hashAlgo *tpm2.Algorithm) {
f := algoFlag{hashAlgo, []tpm2.Algorithm{tpm2.AlgSHA1, tpm2.AlgSHA256, tpm2.AlgSHA384, tpm2.AlgSHA512}}
cmd.PersistentFlags().Var(&f, "hash-algo", "hash algorithm: "+f.Allowed())
}
func addNonceFlag(cmd *cobra.Command) {
cmd.PersistentFlags().BytesHexVar(&nonce, "nonce", []byte{}, "hex encoded nonce for vTPM attestation, cannot be empty")
}
// Lets this command specify the type of output file (binary or txt)
func addFormatFlag(cmd *cobra.Command) {
cmd.PersistentFlags().StringVar(&format, "format", "binarypb", "type of output file where attestation report stored <binarypb|textproto>")
}
func addTeeNonceflag(cmd *cobra.Command) {
cmd.PersistentFlags().BytesHexVar(&teeNonce, "tee-nonce", []byte{}, "hex encoded teenonce for hardware attestation, can be empty")
}
// alwaysError implements io.ReadWriter by always returning an error
type alwaysError struct {
error
}
func (ae alwaysError) Write([]byte) (int, error) {
return 0, ae.error
}
func (ae alwaysError) Read(_ []byte) (n int, err error) {
return 0, ae.error
}
// Handle to output data file. If there is an issue opening the file, the Writer
// returned will return the error upon any call to Write()
func dataOutput() io.Writer {
if output == "" {
return os.Stdout
}
file, err := os.Create(output)
if err != nil {
return alwaysError{err}
}
return file
}
func openForWrite(path string) io.Writer {
if path == "" {
return os.Stdout
}
file, err := os.Create(path)
if err != nil {
return alwaysError{err}
}
return file
}
func writeProtoToOutput(message proto.Message) error {
var out []byte
var err error
switch format {
case "binarypb":
out, err = proto.Marshal(message)
if err != nil {
return fmt.Errorf("failed to marshal proto: %v", message)
}
case "textproto":
out = []byte(marshalOptions.Format(message))
default:
return fmt.Errorf("format should be either binarypb or textproto")
}
if _, err := dataOutput().Write(out); err != nil {
return fmt.Errorf("failed to write attestation report: %v", err)
}
return nil
}
// Handle to input data file. If there is an issue opening the file, the Reader
// returned will return the error upon any call to Read()
func dataInput() io.Reader {
if input == "" {
return os.Stdin
}
file, err := os.Open(input)
if err != nil {
return alwaysError{err}
}
return file
}
var errMustSpecifyPath = errors.New("must specify path to read file")
func readBytes(path string) ([]byte, error) {
if path == "" {
return nil, errMustSpecifyPath
}
file, err := os.Open(path)
if err != nil {
return nil, err
}
bytes, err := io.ReadAll(file)
if err != nil {
return nil, fmt.Errorf("failed to read proto: %v", err)
}
return bytes, nil
}
// Reads binarypb file from path
func readProtoFromPath(path string, message proto.Message) error {
requestBytes, err := readBytes(path)
if err != nil {
return fmt.Errorf("failed to read proto: %v", err)
}
err = proto.Unmarshal(requestBytes, message)
if err != nil {
return fmt.Errorf("failed to unmarshal proto: %v", err)
}
return nil
}
// Load SRK based on tpm2.Algorithm set in the global flag vars.
func getSRK(rwc io.ReadWriter) (*client.Key, error) {
switch keyAlgo {
case tpm2.AlgRSA:
return client.StorageRootKeyRSA(rwc)
case tpm2.AlgECC:
return client.StorageRootKeyECC(rwc)
default:
panic("unexpected keyAlgo")
}
}
// Load EK based on tpm2.Algorithm set in the global flag vars.
func getEK(rwc io.ReadWriter) (*client.Key, error) {
switch keyAlgo {
case tpm2.AlgRSA:
return client.EndorsementKeyRSA(rwc)
case tpm2.AlgECC:
return client.EndorsementKeyECC(rwc)
default:
panic("unexpected keyAlgo")
}
}
|