1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package api
16
17 import (
18 "bytes"
19 "crypto"
20 "encoding/asn1"
21 "encoding/base64"
22 "encoding/json"
23 "fmt"
24 "io"
25 "math/big"
26 "net/http"
27 "strconv"
28 "strings"
29 "time"
30
31 "github.com/digitorus/timestamp"
32 "github.com/go-openapi/runtime/middleware"
33 "github.com/pkg/errors"
34 ts "github.com/sigstore/timestamp-authority/pkg/generated/restapi/operations/timestamp"
35 "github.com/sigstore/timestamp-authority/pkg/verification"
36 )
37
38 type JSONRequest struct {
39 ArtifactHash string `json:"artifactHash"`
40 Certificates bool `json:"certificates"`
41 HashAlgorithm string `json:"hashAlgorithm"`
42 Nonce *big.Int `json:"nonce"`
43 TSAPolicyOID string `json:"tsaPolicyOID"`
44 }
45
46 func getHashAlg(alg string) (crypto.Hash, string, error) {
47 lowercaseAlg := strings.ToLower(alg)
48 switch lowercaseAlg {
49 case "sha256":
50 return crypto.SHA256, "", nil
51 case "sha384":
52 return crypto.SHA384, "", nil
53 case "sha512":
54 return crypto.SHA512, "", nil
55 case "sha1":
56 return 0, WeakHashAlgorithmTimestampRequest, verification.ErrWeakHashAlg
57 default:
58 return 0, failedToGenerateTimestampResponse, fmt.Errorf("unsupported hash algorithm: %s", alg)
59 }
60 }
61
62
63 func ParseJSONRequest(reqBytes []byte) (*timestamp.Request, string, error) {
64
65 var req JSONRequest
66 if err := json.Unmarshal(reqBytes, &req); err != nil {
67 return nil, failedToGenerateTimestampResponse, fmt.Errorf("failed to parse JSON into request: %v", err)
68 }
69
70
71
72 hashAlgo, errMsg, err := getHashAlg(req.HashAlgorithm)
73 if err != nil {
74 return nil, errMsg, fmt.Errorf("failed to parse hash algorithm: %v", err)
75 }
76
77 var oidInts []int
78 if req.TSAPolicyOID == "" {
79 oidInts = nil
80 } else {
81 for _, v := range strings.Split(req.TSAPolicyOID, ".") {
82 i, _ := strconv.Atoi(v)
83 oidInts = append(oidInts, i)
84 }
85 }
86
87
88 decoded, err := base64.StdEncoding.DecodeString(req.ArtifactHash)
89 if err != nil {
90 return nil, failedToGenerateTimestampResponse, fmt.Errorf("failed to decode base64 encoded artifact hash: %v", err)
91 }
92
93
94 tsReq := timestamp.Request{
95 HashAlgorithm: hashAlgo,
96 HashedMessage: decoded,
97 Certificates: req.Certificates,
98 Nonce: req.Nonce,
99 TSAPolicyOID: oidInts,
100 }
101
102 return &tsReq, "", nil
103 }
104
105 func parseDERRequest(reqBytes []byte) (*timestamp.Request, string, error) {
106 parsed, err := timestamp.ParseRequest(reqBytes)
107 if err != nil {
108 return nil, failedToGenerateTimestampResponse, err
109 }
110
111
112 if err := verification.VerifyRequest(parsed); err != nil {
113 return nil, WeakHashAlgorithmTimestampRequest, err
114 }
115
116 return parsed, "", nil
117 }
118
119 func getContentType(r *http.Request) (string, error) {
120 contentTypeHeader := r.Header.Get("Content-Type")
121 splitHeader := strings.Split(contentTypeHeader, "application/")
122 if len(splitHeader) != 2 {
123 return "", errors.New("expected header value to be split into two pieces")
124 }
125 return splitHeader[1], nil
126 }
127
128 func requestBodyToTimestampReq(reqBytes []byte, contentType string) (*timestamp.Request, string, error) {
129 switch contentType {
130 case "json":
131 return ParseJSONRequest(reqBytes)
132 case "timestamp-query":
133 return parseDERRequest(reqBytes)
134 default:
135 return nil, failedToGenerateTimestampResponse, fmt.Errorf("unsupported content type")
136 }
137 }
138
139 func TimestampResponseHandler(params ts.GetTimestampResponseParams) middleware.Responder {
140 requestBytes, err := io.ReadAll(params.Request)
141 if err != nil {
142 return handleTimestampAPIError(params, http.StatusBadRequest, err, failedToGenerateTimestampResponse)
143 }
144
145 contentType, err := getContentType(params.HTTPRequest)
146 if err != nil {
147 return handleTimestampAPIError(params, http.StatusUnsupportedMediaType, err, failedToGenerateTimestampResponse)
148 }
149
150 req, errMsg, err := requestBodyToTimestampReq(requestBytes, contentType)
151 if err != nil {
152 return handleTimestampAPIError(params, http.StatusBadRequest, err, errMsg)
153 }
154
155 policyID := req.TSAPolicyOID
156 if policyID.String() == "" {
157 policyID = asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 2}
158 }
159
160 duration, _ := time.ParseDuration("1s")
161
162 tsStruct := timestamp.Timestamp{
163 HashAlgorithm: req.HashAlgorithm,
164 HashedMessage: req.HashedMessage,
165 Time: time.Now(),
166 Nonce: req.Nonce,
167 Policy: policyID,
168 Ordering: false,
169 Accuracy: duration,
170
171 Qualified: false,
172 AddTSACertificate: req.Certificates,
173 ExtraExtensions: req.Extensions,
174 }
175
176 resp, err := tsStruct.CreateResponseWithOpts(api.certChain[0], api.tsaSigner, api.tsaSignerHash)
177 if err != nil {
178 return handleTimestampAPIError(params, http.StatusInternalServerError, err, failedToGenerateTimestampResponse)
179 }
180
181 return ts.NewGetTimestampResponseCreated().WithPayload(io.NopCloser(bytes.NewReader(resp)))
182 }
183
184 func GetTimestampCertChainHandler(_ ts.GetTimestampCertChainParams) middleware.Responder {
185 return ts.NewGetTimestampCertChainOK().WithPayload(api.certChainPem)
186 }
187
View as plain text