| // Copyright 2023 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| package bcid |
| |
| import ( |
| "context" |
| "encoding/json" |
| "fmt" |
| "io" |
| "os" |
| "os/exec" |
| |
| spb "github.com/in-toto/attestation/go/v1" |
| "go.chromium.org/luci/common/logging" |
| "google.golang.org/protobuf/encoding/protojson" |
| ) |
| |
| const ProdKeyID = "projects/fuchsia-bcid/locations/global/keyRings/bcid/cryptoKeys/attestation_signer" |
| |
| type attestCommander struct{} |
| |
| func (a *attestCommander) CombinedOutput(ctx context.Context, args ...string) ([]byte, error) { |
| cmd := exec.CommandContext(ctx, "attestation_tool", args...) |
| |
| logging.Infof(ctx, "Executing %+v\n", cmd) |
| return cmd.CombinedOutput() |
| } |
| |
| func Attest(ctx context.Context, stmt *spb.Statement, keyID string) ([]byte, error) { |
| return attestWithCommander(ctx, &attestCommander{}, stmt, keyID) |
| } |
| |
| type CombinedOutputer interface { |
| CombinedOutput(context.Context, ...string) ([]byte, error) |
| } |
| |
| func attestWithCommander(ctx context.Context, cmd CombinedOutputer, stmt *spb.Statement, keyID string) ([]byte, error) { |
| // TODO(cflewis): The attestation tool support stdin using - and stdout using -. Do that instead of writing files. |
| tempFiles := make(map[string]*os.File) |
| for _, name := range []string{"input", "output"} { |
| f, err := os.CreateTemp("", "bcid") |
| if err != nil { |
| return nil, err |
| } |
| tempFiles[name] = f |
| defer os.Remove(f.Name()) |
| } |
| stmtJSON, err := protojson.Marshal(stmt) |
| if err != nil { |
| return nil, err |
| } |
| tempFiles["input"].Write(stmtJSON) |
| |
| out, err := cmd.CombinedOutput(ctx, "sign_intoto_statement", |
| "--input", tempFiles["input"].Name(), |
| "--key_id", keyID, |
| "--output", tempFiles["output"].Name()) |
| if err != nil { |
| return nil, fmt.Errorf("exit status %v, got out %q", err, out) |
| } |
| |
| return io.ReadAll(tempFiles["output"]) |
| } |
| |
| type Signature struct { |
| KeyID string `json:"keyid"` |
| Sig string `json:"sig"` |
| } |
| |
| // InToToBundle represents the attestation bundle. |
| // TODO(https://github.com/in-toto/attestation/issues/280): This struct should ideally be provided upstream. |
| type InTotoBundle struct { |
| ContentType string `json:"payloadType"` |
| Payload string `json:"payload"` |
| Signatures []Signature `json:"signatures"` |
| } |
| |
| func ReadInToto(data io.Reader) (*InTotoBundle, error) { |
| all, err := io.ReadAll(data) |
| if err != nil { |
| return nil, fmt.Errorf("can't read input data: %v", err) |
| } |
| |
| var itt InTotoBundle |
| if err := json.Unmarshal(all, &itt); err != nil { |
| return nil, fmt.Errorf("unable to pass in-toto payload: %v", err) |
| } |
| |
| return &itt, nil |
| } |