1
2
3
4
5
6
7
8
9
10
11
12
13
14
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")
43 cmd.Flags().String("hash", "sha256", "hash algorithm to use - Valid values are sha256, sha384, and sha512")
44 cmd.MarkFlagRequired("hash")
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 := ×tamp.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
123
124
125
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
147 ts, err := timestamp.ParseResponse(respBytes.Bytes())
148 if err != nil {
149 return nil, err
150 }
151
152
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 ×tampCmdOutput{
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