1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package app
17
18 import (
19 "crypto/x509"
20 "fmt"
21 "math/big"
22 "os"
23 "path/filepath"
24 "strconv"
25 "strings"
26
27 "github.com/digitorus/timestamp"
28 "github.com/sigstore/sigstore/pkg/cryptoutils"
29 "github.com/sigstore/timestamp-authority/cmd/timestamp-cli/app/format"
30 "github.com/sigstore/timestamp-authority/pkg/log"
31 "github.com/sigstore/timestamp-authority/pkg/verification"
32 "github.com/spf13/cobra"
33 "github.com/spf13/viper"
34 )
35
36 type verifyCmdOutput struct {
37 TimestampPath string
38 ParsedTimestamp timestamp.Timestamp
39 }
40
41 func (v *verifyCmdOutput) String() string {
42 return fmt.Sprintf("Successfully verified timestamp %s", v.TimestampPath)
43 }
44
45 func addVerifyFlags(cmd *cobra.Command) {
46 cmd.Flags().Var(NewFlagValue(fileFlag, ""), "artifact", "path to an blob with signed data")
47 cmd.MarkFlagRequired("artifact")
48 cmd.Flags().Var(NewFlagValue(fileFlag, ""), "timestamp", "path to timestamp response to verify")
49 cmd.MarkFlagRequired("timestamp")
50 cmd.Flags().Var(NewFlagValue(fileFlag, ""), "certificate-chain", "path to file with PEM-encoded certificate chain. Ordered from intermediate CA certificate that issued the TSA certificate, ending with the root CA certificate.")
51 cmd.Flags().String("nonce", "", "optional nonce passed with the request")
52 cmd.Flags().Var(NewFlagValue(oidFlag, ""), "oid", "optional TSA policy OID passed with the request")
53 cmd.Flags().String("common-name", "", "expected leaf certificate subject common name")
54 cmd.Flags().Var(NewFlagValue(fileFlag, ""), "certificate", "path to file with PEM-encoded leaf certificate")
55 cmd.Flags().Var(NewFlagValue(fileFlag, ""), "intermediate-certificates", "path to file with PEM-encoded intermediate certificates. Must be called with the root-certificate flag.")
56 cmd.Flags().Var(NewFlagValue(fileFlag, ""), "root-certificates", "path to file with a PEM-encoded root certificates. Optionally can be called with the intermediate-certificates flag.")
57 }
58
59 var verifyCmd = &cobra.Command{
60 Use: "verify",
61 Short: "Verify timestamp",
62 Long: "Verify the timestamp response using a timestamp certificate chain.",
63 PreRunE: func(cmd *cobra.Command, args []string) error {
64 if err := viper.BindPFlags(cmd.Flags()); err != nil {
65 log.CliLogger.Fatal("Error initializing cmd line args: ", err)
66 }
67 return nil
68 },
69 Run: format.WrapCmd(func(args []string) (interface{}, error) {
70 return runVerify()
71 }),
72 }
73
74 func runVerify() (interface{}, error) {
75 tsrPath := viper.GetString("timestamp")
76 tsrBytes, err := os.ReadFile(filepath.Clean(tsrPath))
77 if err != nil {
78 return nil, fmt.Errorf("error reading request from file: %w", err)
79 }
80
81 artifactPath := viper.GetString("artifact")
82 artifact, err := os.Open(filepath.Clean(artifactPath))
83 if err != nil {
84 return nil, err
85 }
86
87 opts, err := newVerifyOpts()
88 if err != nil {
89 return verifyCmdOutput{TimestampPath: tsrPath}, fmt.Errorf("failed to created VerifyOpts: %w", err)
90 }
91
92 ts, err := verification.VerifyTimestampResponse(tsrBytes, artifact, opts)
93 if err != nil {
94 return verifyCmdOutput{TimestampPath: tsrPath}, fmt.Errorf("failed to verify timestamp: %w", err)
95 }
96
97 return &verifyCmdOutput{TimestampPath: tsrPath, ParsedTimestamp: *ts}, nil
98 }
99
100 func newVerifyOpts() (verification.VerifyOpts, error) {
101 opts := verification.VerifyOpts{}
102
103 oid, err := getOID()
104 if err != nil {
105 return verification.VerifyOpts{}, fmt.Errorf("failed to parse value from oid flag: %w", err)
106 }
107 opts.OID = oid
108
109 certPathFlagVal := viper.GetString("certificate")
110 if certPathFlagVal != "" {
111 cert, err := parseTSACertificate(certPathFlagVal)
112 if err != nil {
113 return verification.VerifyOpts{}, fmt.Errorf("failed to parse cert flag value from PEM file: %w", err)
114 }
115 opts.TSACertificate = cert
116 }
117
118 roots, intermediates, err := getRootAndIntermediateCerts()
119 if err != nil {
120 return verification.VerifyOpts{}, fmt.Errorf("failed to parse root and intermediate certs from certificate-chain flag: %w", err)
121 }
122 opts.Roots = roots
123 opts.Intermediates = intermediates
124
125 nonce, err := getNonce()
126 if err != nil {
127 return verification.VerifyOpts{}, fmt.Errorf("failed to parse value from nonce flag: %w", err)
128 }
129 opts.Nonce = nonce
130
131 commonNameFlagVal := viper.GetString("common-name")
132 opts.CommonName = commonNameFlagVal
133
134 return opts, nil
135 }
136
137 func getNonce() (*big.Int, error) {
138 nonceFlagVal := viper.GetString("nonce")
139 if nonceFlagVal == "" {
140 return nil, nil
141 }
142
143 nonce := new(big.Int)
144 nonce, ok := nonce.SetString(nonceFlagVal, 10)
145 if !ok {
146 return nil, fmt.Errorf("failed to convert string to big.Int")
147 }
148 return nonce, nil
149 }
150
151 func getRootAndIntermediateCerts() ([]*x509.Certificate, []*x509.Certificate, error) {
152 certChainPEM := viper.GetString("certificate-chain")
153 rootPEM := viper.GetString("root-certificates")
154 intermediatePEM := viper.GetString("intermediate-certificates")
155
156
157
158
159
160
161 if !((rootPEM != "" && certChainPEM == "") || (intermediatePEM == "" && rootPEM == "" && certChainPEM != "")) {
162 return nil, nil, fmt.Errorf("the verify command must be called with either only the --certificate-chain flag or with the --root-certificates and --intermediate-certificates flags")
163 }
164
165
166
167 if certChainPEM != "" {
168 pemBytes, err := os.ReadFile(filepath.Clean(certChainPEM))
169 if err != nil {
170 return nil, nil, fmt.Errorf("error reading request from file: %w", err)
171 }
172
173 certs, err := cryptoutils.UnmarshalCertificatesFromPEM(pemBytes)
174 if err != nil {
175 return nil, nil, fmt.Errorf("failed to parse intermediate and root certs from PEM file: %w", err)
176 }
177
178 if len(certs) == 0 {
179 return nil, nil, fmt.Errorf("expected at least one certificate to represent the root")
180 }
181
182
183 intermediateCerts := certs[0 : len(certs)-1]
184
185 rootCerts := []*x509.Certificate{certs[len(certs)-1]}
186
187 return rootCerts, intermediateCerts, nil
188 }
189
190
191
192 rootPEMBytes, err := os.ReadFile(filepath.Clean(rootPEM))
193 if err != nil {
194 return nil, nil, fmt.Errorf("error reading request from file: %w", err)
195 }
196
197 rootCerts, err := cryptoutils.UnmarshalCertificatesFromPEM(rootPEMBytes)
198 if err != nil {
199 return nil, nil, fmt.Errorf("failed to parse intermediate and root certs from PEM file: %w", err)
200 }
201
202 if len(rootCerts) == 0 {
203 return nil, nil, fmt.Errorf("expected at least one certificate to represent the root")
204 }
205
206 if intermediatePEM == "" {
207 return rootCerts, []*x509.Certificate{}, nil
208 }
209
210
211 intermediatePEMBytes, err := os.ReadFile(filepath.Clean(intermediatePEM))
212 if err != nil {
213 return nil, nil, fmt.Errorf("error reading request from file: %w", err)
214 }
215
216 intermediateCerts, err := cryptoutils.UnmarshalCertificatesFromPEM(intermediatePEMBytes)
217 if err != nil {
218 return nil, nil, fmt.Errorf("failed to parse intermediate and root certs from PEM file: %w", err)
219 }
220
221 if len(intermediateCerts) == 0 {
222 return nil, nil, fmt.Errorf("expected at least one intermediate certificate")
223 }
224
225 return rootCerts, intermediateCerts, nil
226 }
227
228 func getOID() ([]int, error) {
229 oidFlagVal := viper.GetString("oid")
230 if oidFlagVal == "" {
231 return nil, nil
232 }
233
234 oidStrSlice := strings.Split(oidFlagVal, ".")
235 oid := make([]int, len(oidStrSlice))
236 for i, el := range oidStrSlice {
237 intVar, err := strconv.Atoi(el)
238 if err != nil {
239 return nil, err
240 }
241 oid[i] = intVar
242 }
243
244 return oid, nil
245 }
246
247 func parseTSACertificate(certPath string) (*x509.Certificate, error) {
248 pemBytes, err := os.ReadFile(filepath.Clean(certPath))
249 if err != nil {
250 return nil, fmt.Errorf("error reading TSA's certificate file: %w", err)
251 }
252
253 certs, err := cryptoutils.UnmarshalCertificatesFromPEM(pemBytes)
254 if err != nil {
255 return nil, fmt.Errorf("failed to parse TSA certificate during verification: %w", err)
256 }
257 if len(certs) != 1 {
258 return nil, fmt.Errorf("expected one certificate, received %d instead", len(certs))
259 }
260
261 return certs[0], nil
262 }
263
264 func init() {
265 initializePFlagMap()
266 addVerifyFlags(verifyCmd)
267 rootCmd.AddCommand(verifyCmd)
268 }
269
View as plain text