diff --git a/tools/show/README.md b/tools/show/README.md new file mode 100644 index 0000000..d6740d5 --- /dev/null +++ b/tools/show/README.md @@ -0,0 +1,46 @@ +# `show` CLI tool + +This binary is a CLI tool for parsing and displaying Intel TDX quotes. + +The tool's input is a quote. + +The tool's output is the quote in any specified format to either standard out +or directly to a file. + +## Usage + +``` +./show [options...] +``` + +### `-in` + +This flag provides the path to the quote to show. Stdin is "-". + +### `-inform` + +The format that input takes. One of + +* `bin`: for a raw binary quote. +* `proto`: A binary serialized `tdx.QuoteV4` message. +* `textproto`: The `tdx.QuoteV4` message in textproto format. + +Default value is `bin`. + +### `-out` + +Path to output file to write quote to. + +Default is empty, interpreted as stdout. + +### `-outform` + +The format that output takes. Currently only `textproto` is supported. + +Default value is `textproto`. + +### `-verbosity` + +Used to set the verbosity of logger, where higher number means more verbose output. + +Default value is `0`. diff --git a/tools/show/show.go b/tools/show/show.go new file mode 100644 index 0000000..6fe164b --- /dev/null +++ b/tools/show/show.go @@ -0,0 +1,162 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package main implements a CLI tool for showing Intel TDX quotes. +package main + +import ( + "flag" + "fmt" + "io" + "os" + + "github.com/google/go-tdx-guest/abi" + pb "github.com/google/go-tdx-guest/proto/tdx" + "github.com/google/logger" + "google.golang.org/protobuf/encoding/prototext" + "google.golang.org/protobuf/proto" +) + +var ( + infile = flag.String("in", "-", "Path to the TDX quote to show. Stdin is \"-\".") + inform = flag.String("inform", "bin", "The input format for the TDX quote. One of \"bin\", \"proto\", \"textproto\".") + out = flag.String("out", "", "Path to output file to write attestation report to. "+ + "If unset, outputs to stdout.") + outform = flag.String("outform", "textproto", "The format of the output attestation report. Currently only \"textproto\" is supported.") + verbosity = flag.Int("verbosity", 0, "The output verbosity. Higher number means more verbose output.") +) + +func parseQuoteBytes(b []byte) (any, error) { + quote, err := abi.QuoteToProto(b) + if err != nil { + return nil, fmt.Errorf("could not parse the TDX Quote from %q: %v", *infile, err) + } + + return quote, nil +} + +func parseQuote(b []byte) (any, error) { + switch *inform { + case "bin": + return parseQuoteBytes(b) + case "proto": + result := &pb.QuoteV4{} + if err := proto.Unmarshal(b, result); err != nil { + return nil, fmt.Errorf("could not parse %q as proto: %v", *infile, err) + } + return result, nil + case "textproto": + result := &pb.QuoteV4{} + if err := prototext.Unmarshal(b, result); err != nil { + return nil, fmt.Errorf("could not parse %q as textproto: %v", *infile, err) + } + return result, nil + default: + return nil, fmt.Errorf("unknown value -inform=%s", *inform) + } +} + +func readQuote() (any, error) { + var in io.Reader + var f *os.File + if *infile == "-" { + in = os.Stdin + } else { + file, err := os.Open(*infile) + if err != nil { + return nil, fmt.Errorf("could not open %q: %v", *infile, err) + } + f = file + in = file + } + defer func() { + if f != nil { + f.Close() + } + }() + + contents, err := io.ReadAll(in) + if err != nil { + return nil, fmt.Errorf("could not read %q: %v", *infile, err) + } + return parseQuote(contents) +} + +func marshalAndWriteBytes(quote any, out io.Writer) error { + switch q := quote.(type) { + case *pb.QuoteV4: + mo := prototext.MarshalOptions{ + Multiline: true, + Indent: " ", + EmitASCII: true, + } + bytes, err := mo.Marshal(q) + if err != nil { + return err + } + if _, err := out.Write(bytes); err != nil { + return err + } + return nil + default: + return fmt.Errorf("unsupported quote type: %T", quote) + } +} + +func outWriter() (io.Writer, *os.File, error) { + if *out == "" { + return os.Stdout, nil, nil + } + file, err := os.Create(*out) + if err != nil { + return nil, nil, err + } + return file, file, nil +} + +func writeQuote(quote any, out io.Writer) error { + switch *outform { + case "textproto": + return marshalAndWriteBytes(quote, out) + default: + return fmt.Errorf("unknown value -outform=%s", *outform) + } +} + +func main() { + logger.Init("", false, false, os.Stdout) + flag.Parse() + logger.SetLevel(logger.Level(*verbosity)) + + quote, err := readQuote() + if err != nil { + logger.Fatal(err) + } + logger.V(1).Info("TDX Quote parsed successfully") + + outwriter, filetoclose, err := outWriter() + if err != nil { + logger.Fatalf("failed to open output file: %v", err) + } + defer func() { + if filetoclose != nil { + filetoclose.Close() + } + }() + + err2 := writeQuote(quote, outwriter) + if err2 != nil { + logger.Fatal(err2) + } +}