1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package api
17
18 import (
19 "context"
20 "encoding/hex"
21 "fmt"
22 "net/http"
23 "strconv"
24
25 "github.com/go-openapi/runtime/middleware"
26 "github.com/go-openapi/swag"
27 "github.com/google/trillian/types"
28 "github.com/spf13/viper"
29 "google.golang.org/grpc/codes"
30
31 "github.com/sigstore/rekor/pkg/generated/models"
32 "github.com/sigstore/rekor/pkg/generated/restapi/operations/tlog"
33 "github.com/sigstore/rekor/pkg/log"
34 "github.com/sigstore/rekor/pkg/trillianclient"
35 "github.com/sigstore/rekor/pkg/util"
36 )
37
38
39 func GetLogInfoHandler(params tlog.GetLogInfoParams) middleware.Responder {
40 tc := trillianclient.NewTrillianClient(params.HTTPRequest.Context(), api.logClient, api.logID)
41
42
43 var inactiveShards []*models.InactiveShardLogInfo
44 for _, shard := range api.logRanges.GetInactive() {
45 if shard.TreeID == api.logRanges.ActiveTreeID() {
46 break
47 }
48
49 is, err := inactiveShardLogInfo(params.HTTPRequest.Context(), shard.TreeID)
50 if err != nil {
51 return handleRekorAPIError(params, http.StatusInternalServerError, fmt.Errorf("inactive shard error: %w", err), unexpectedInactiveShardError)
52 }
53 inactiveShards = append(inactiveShards, is)
54 }
55
56 if swag.BoolValue(params.Stable) && redisClient != nil {
57
58 key := fmt.Sprintf("%d/latest", api.logRanges.ActiveTreeID())
59 redisResult, err := redisClient.Get(params.HTTPRequest.Context(), key).Result()
60 if err != nil {
61 return handleRekorAPIError(params, http.StatusInternalServerError,
62 fmt.Errorf("error getting checkpoint from redis: %w", err), "error getting checkpoint from redis")
63 }
64
65 if redisResult == "" {
66 return handleRekorAPIError(params, http.StatusInternalServerError,
67 fmt.Errorf("no checkpoint found in redis: %w", err), "no checkpoint found in redis")
68 }
69 decoded, err := hex.DecodeString(redisResult)
70 if err != nil {
71 return handleRekorAPIError(params, http.StatusInternalServerError,
72 fmt.Errorf("error decoding checkpoint from redis: %w", err), "error decoding checkpoint from redis")
73 }
74 checkpoint := util.SignedCheckpoint{}
75 if err := checkpoint.UnmarshalText(decoded); err != nil {
76 return handleRekorAPIError(params, http.StatusInternalServerError, fmt.Errorf("invalid checkpoint: %w", err), "invalid checkpoint")
77 }
78 logInfo := models.LogInfo{
79 RootHash: stringPointer(hex.EncodeToString(checkpoint.Hash)),
80 TreeSize: swag.Int64(int64(checkpoint.Size)),
81 SignedTreeHead: stringPointer(string(decoded)),
82 TreeID: stringPointer(fmt.Sprintf("%d", api.logID)),
83 InactiveShards: inactiveShards,
84 }
85 return tlog.NewGetLogInfoOK().WithPayload(&logInfo)
86 }
87
88 resp := tc.GetLatest(0)
89 if resp.Status != codes.OK {
90 return handleRekorAPIError(params, http.StatusInternalServerError, fmt.Errorf("grpc error: %w", resp.Err), trillianCommunicationError)
91 }
92 result := resp.GetLatestResult
93
94 root := &types.LogRootV1{}
95 if err := root.UnmarshalBinary(result.SignedLogRoot.LogRoot); err != nil {
96 return handleRekorAPIError(params, http.StatusInternalServerError, err, trillianUnexpectedResult)
97 }
98
99 hashString := hex.EncodeToString(root.RootHash)
100 treeSize := int64(root.TreeSize)
101
102 scBytes, err := util.CreateAndSignCheckpoint(params.HTTPRequest.Context(),
103 viper.GetString("rekor_server.hostname"), api.logRanges.ActiveTreeID(), root.TreeSize, root.RootHash, api.signer)
104 if err != nil {
105 return handleRekorAPIError(params, http.StatusInternalServerError, err, sthGenerateError)
106 }
107
108 logInfo := models.LogInfo{
109 RootHash: &hashString,
110 TreeSize: &treeSize,
111 SignedTreeHead: stringPointer(string(scBytes)),
112 TreeID: stringPointer(fmt.Sprintf("%d", api.logID)),
113 InactiveShards: inactiveShards,
114 }
115
116 return tlog.NewGetLogInfoOK().WithPayload(&logInfo)
117 }
118
119 func stringPointer(s string) *string {
120 return &s
121 }
122
123
124 func GetLogProofHandler(params tlog.GetLogProofParams) middleware.Responder {
125 if *params.FirstSize > params.LastSize {
126 return handleRekorAPIError(params, http.StatusBadRequest, nil, fmt.Sprintf(firstSizeLessThanLastSize, *params.FirstSize, params.LastSize))
127 }
128 tc := trillianclient.NewTrillianClient(params.HTTPRequest.Context(), api.logClient, api.logID)
129 if treeID := swag.StringValue(params.TreeID); treeID != "" {
130 id, err := strconv.Atoi(treeID)
131 if err != nil {
132 log.Logger.Infof("Unable to convert %s to string, skipping initializing client with Tree ID: %v", treeID, err)
133 } else {
134 tc = trillianclient.NewTrillianClient(params.HTTPRequest.Context(), api.logClient, int64(id))
135 }
136 }
137
138 resp := tc.GetConsistencyProof(*params.FirstSize, params.LastSize)
139 if resp.Status != codes.OK {
140 return handleRekorAPIError(params, http.StatusInternalServerError, fmt.Errorf("grpc error: %w", resp.Err), trillianCommunicationError)
141 }
142 result := resp.GetConsistencyProofResult
143
144 var root types.LogRootV1
145 if err := root.UnmarshalBinary(result.SignedLogRoot.LogRoot); err != nil {
146 return handleRekorAPIError(params, http.StatusInternalServerError, err, trillianUnexpectedResult)
147 }
148
149 hashString := hex.EncodeToString(root.RootHash)
150 proofHashes := []string{}
151
152 if proof := result.GetProof(); proof != nil {
153 for _, hash := range proof.Hashes {
154 proofHashes = append(proofHashes, hex.EncodeToString(hash))
155 }
156 } else {
157
158
159
160 err := fmt.Errorf(lastSizeGreaterThanKnown, params.LastSize, root.TreeSize)
161 return handleRekorAPIError(params, http.StatusBadRequest, err, err.Error())
162 }
163
164 consistencyProof := models.ConsistencyProof{
165 RootHash: &hashString,
166 Hashes: proofHashes,
167 }
168
169 return tlog.NewGetLogProofOK().WithPayload(&consistencyProof)
170 }
171
172 func inactiveShardLogInfo(ctx context.Context, tid int64) (*models.InactiveShardLogInfo, error) {
173 tc := trillianclient.NewTrillianClient(ctx, api.logClient, tid)
174 resp := tc.GetLatest(0)
175 if resp.Status != codes.OK {
176 return nil, fmt.Errorf("resp code is %d", resp.Status)
177 }
178 result := resp.GetLatestResult
179
180 root := &types.LogRootV1{}
181 if err := root.UnmarshalBinary(result.SignedLogRoot.LogRoot); err != nil {
182 return nil, err
183 }
184
185 hashString := hex.EncodeToString(root.RootHash)
186 treeSize := int64(root.TreeSize)
187
188 scBytes, err := util.CreateAndSignCheckpoint(ctx, viper.GetString("rekor_server.hostname"), tid, root.TreeSize, root.RootHash, api.signer)
189 if err != nil {
190 return nil, err
191 }
192
193 m := models.InactiveShardLogInfo{
194 RootHash: &hashString,
195 TreeSize: &treeSize,
196 TreeID: stringPointer(fmt.Sprintf("%d", tid)),
197 SignedTreeHead: stringPointer(string(scBytes)),
198 }
199 return &m, nil
200 }
201
202
203
204 func GetLogInfoNotImplementedHandler(_ tlog.GetLogInfoParams) middleware.Responder {
205 err := &models.Error{
206 Code: http.StatusNotImplemented,
207 Message: "Get Log Info API not enabled in this Rekor instance",
208 }
209
210 return tlog.NewGetLogInfoDefault(http.StatusNotImplemented).WithPayload(err)
211 }
212
213 func GetLogProofNotImplementedHandler(_ tlog.GetLogProofParams) middleware.Responder {
214 err := &models.Error{
215 Code: http.StatusNotImplemented,
216 Message: "Get Log Proof API not enabled in this Rekor instance",
217 }
218
219 return tlog.NewGetLogProofDefault(http.StatusNotImplemented).WithPayload(err)
220 }
221
View as plain text