blob: 2a911073142b28f2483391cb338c68c8bb9a7417 [file] [log] [blame]
// 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
}