...

Source file src/github.com/sigstore/timestamp-authority/cmd/timestamp-cli/app/timestamp.go

Documentation: github.com/sigstore/timestamp-authority/cmd/timestamp-cli/app

     1  //
     2  // Copyright 2022 The Sigstore Authors.
     3  //
     4  // Licensed under the Apache License, Version 2.0 (the "License");
     5  // you may not use this file except in compliance with the License.
     6  // You may obtain a copy of the License at
     7  //
     8  //     http://www.apache.org/licenses/LICENSE-2.0
     9  //
    10  // Unless required by applicable law or agreed to in writing, software
    11  // distributed under the License is distributed on an "AS IS" BASIS,
    12  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  // See the License for the specific language governing permissions and
    14  // limitations under the License.
    15  
    16  package app
    17  
    18  import (
    19  	"bytes"
    20  	"crypto"
    21  	"errors"
    22  	"fmt"
    23  	"io"
    24  	"os"
    25  	"path/filepath"
    26  	"strconv"
    27  	"strings"
    28  	"time"
    29  
    30  	"github.com/digitorus/timestamp"
    31  	"github.com/sigstore/sigstore/pkg/cryptoutils"
    32  	"github.com/sigstore/timestamp-authority/cmd/timestamp-cli/app/format"
    33  	"github.com/sigstore/timestamp-authority/pkg/client"
    34  	ts "github.com/sigstore/timestamp-authority/pkg/generated/client/timestamp"
    35  	"github.com/sigstore/timestamp-authority/pkg/log"
    36  	"github.com/spf13/cobra"
    37  	"github.com/spf13/viper"
    38  )
    39  
    40  func addTimestampFlags(cmd *cobra.Command) {
    41  	cmd.Flags().Var(NewFlagValue(fileFlag, ""), "artifact", "path to an artifact to timestamp")
    42  	cmd.MarkFlagRequired("artifact") //nolint:errcheck
    43  	cmd.Flags().String("hash", "sha256", "hash algorithm to use - Valid values are sha256, sha384, and sha512")
    44  	cmd.MarkFlagRequired("hash") //nolint:errcheck
    45  	cmd.Flags().Bool("nonce", true, "specify a pseudo-random nonce in the request")
    46  	cmd.Flags().Bool("certificate", true, "if the timestamp response should contain a certificate chain")
    47  	cmd.Flags().Var(NewFlagValue(oidFlag, ""), "tsa-policy", "optional dotted OID notation for the policy that the TSA should use to create the response")
    48  	cmd.Flags().String("out", "response.tsr", "path to a file to write response.")
    49  }
    50  
    51  type timestampCmdOutput struct {
    52  	Timestamp time.Time
    53  	Location  string
    54  }
    55  
    56  func (t *timestampCmdOutput) String() string {
    57  	return fmt.Sprintf("Artifact timestamped at %s\nWrote timestamp response to %v\n", t.Timestamp, t.Location)
    58  }
    59  
    60  var timestampCmd = &cobra.Command{
    61  	Use:   "timestamp",
    62  	Short: "Signed timestamp command",
    63  	Long:  "Fetches a signed RFC 3161 timestamp. The timestamp response can be verified locally using a timestamp certificate chain.",
    64  	PreRunE: func(cmd *cobra.Command, args []string) error {
    65  		if err := viper.BindPFlags(cmd.Flags()); err != nil {
    66  			log.CliLogger.Fatal("Error initializing cmd line args: ", err)
    67  		}
    68  		return nil
    69  	},
    70  	Run: format.WrapCmd(func(args []string) (interface{}, error) {
    71  		return runTimestamp()
    72  	}),
    73  }
    74  
    75  func createRequestFromFlags() ([]byte, error) {
    76  	artifactStr := viper.GetString("artifact")
    77  	artifactBytes, err := os.ReadFile(filepath.Clean(artifactStr))
    78  	if err != nil {
    79  		return nil, fmt.Errorf("error reading request from file: %w", err)
    80  	}
    81  
    82  	var hash crypto.Hash
    83  	switch viper.GetString("hash") {
    84  	case "sha256":
    85  		hash = crypto.SHA256
    86  	case "sha384":
    87  		hash = crypto.SHA384
    88  	case "sha512":
    89  		hash = crypto.SHA512
    90  	default:
    91  		return nil, errors.New("invalid hash algorithm - must be either sha256, sha384, or sha512")
    92  	}
    93  
    94  	reqOpts := &timestamp.RequestOptions{
    95  		Hash:         hash,
    96  		Certificates: viper.GetBool("certificate"),
    97  	}
    98  
    99  	if viper.GetBool("nonce") {
   100  		nonce, err := cryptoutils.GenerateSerialNumber()
   101  		if err != nil {
   102  			return nil, err
   103  		}
   104  		reqOpts.Nonce = nonce
   105  	}
   106  
   107  	if policyStr := viper.GetString("tsa-policy"); policyStr != "" {
   108  		var oidInts []int
   109  		for _, v := range strings.Split(policyStr, ".") {
   110  			i, _ := strconv.Atoi(v)
   111  			oidInts = append(oidInts, i)
   112  		}
   113  		reqOpts.TSAPolicyOID = oidInts
   114  	}
   115  
   116  	return timestamp.CreateRequest(bytes.NewReader(artifactBytes), reqOpts)
   117  }
   118  
   119  func runTimestamp() (interface{}, error) {
   120  	fmt.Println("Generating a new signed timestamp")
   121  
   122  	// Set the Content-Type header to application/timestamp-query for the
   123  	// request that will be made to the server. Since the server accepts
   124  	// both application/timestamp-query and application/json as consumers for
   125  	// the /api/v1/timestamp endpoint, we need to specify which one we want to use
   126  	tsClient, err := client.GetTimestampClient(viper.GetString("timestamp_server"), client.WithUserAgent(UserAgent()), client.WithContentType(client.TimestampQueryMediaType))
   127  	if err != nil {
   128  		return nil, err
   129  	}
   130  
   131  	requestBytes, err := createRequestFromFlags()
   132  	if err != nil {
   133  		return nil, err
   134  	}
   135  
   136  	params := ts.NewGetTimestampResponseParams()
   137  	params.SetTimeout(viper.GetDuration("timeout"))
   138  	params.Request = io.NopCloser(bytes.NewReader(requestBytes))
   139  
   140  	var respBytes bytes.Buffer
   141  	_, err = tsClient.Timestamp.GetTimestampResponse(params, &respBytes)
   142  	if err != nil {
   143  		return nil, err
   144  	}
   145  
   146  	// validate that timestamp is parseable
   147  	ts, err := timestamp.ParseResponse(respBytes.Bytes())
   148  	if err != nil {
   149  		return nil, err
   150  	}
   151  
   152  	// Write response to file
   153  	outStr := viper.GetString("out")
   154  	if outStr == "" {
   155  		outStr = "response.tsr"
   156  	}
   157  	if err := os.WriteFile(outStr, respBytes.Bytes(), 0600); err != nil {
   158  		return nil, err
   159  	}
   160  
   161  	return &timestampCmdOutput{
   162  		Timestamp: ts.Time,
   163  		Location:  outStr,
   164  	}, nil
   165  }
   166  
   167  func init() {
   168  	initializePFlagMap()
   169  	addTimestampFlags(timestampCmd)
   170  	rootCmd.AddCommand(timestampCmd)
   171  }
   172  

View as plain text