Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support per-shard signing keys #2330

Merged
merged 8 commits into from
Jan 17, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 19 additions & 5 deletions cmd/rekor-cli/app/get.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,11 +92,6 @@ var getCmd = &cobra.Command{
if logIndex == "" && uuid == "" {
return nil, errors.New("either --uuid or --log-index must be specified")
}
// retrieve rekor pubkey for verification
verifier, err := loadVerifier(rekorClient)
if err != nil {
return nil, fmt.Errorf("retrieving rekor public key")
}

if logIndex != "" {
params := entries.NewGetLogEntryByIndexParams()
Expand All @@ -113,6 +108,15 @@ var getCmd = &cobra.Command{
}
var e models.LogEntryAnon
for ix, entry := range resp.Payload {
// retrieve rekor pubkey for verification
treeID, err := sharding.TreeID(ix)
if err != nil {
return nil, err
}
verifier, err := loadVerifier(rekorClient, strconv.FormatInt(treeID, 10))
if err != nil {
return nil, fmt.Errorf("retrieving rekor public key: %w", err)
}
// verify log entry
e = entry
if err := verify.VerifyLogEntry(ctx, &e, verifier); err != nil {
Expand Down Expand Up @@ -143,6 +147,16 @@ var getCmd = &cobra.Command{

var e models.LogEntryAnon
for k, entry := range resp.Payload {
// retrieve rekor pubkey for verification
treeID, err := sharding.TreeID(k)
if err != nil {
return nil, err
}
verifier, err := loadVerifier(rekorClient, strconv.FormatInt(treeID, 10))
if err != nil {
return nil, fmt.Errorf("retrieving rekor public key: %w", err)
}

if err := compareEntryUUIDs(params.EntryUUID, k); err != nil {
return nil, err
}
Expand Down
7 changes: 4 additions & 3 deletions cmd/rekor-cli/app/log_info.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import (
"github.com/sigstore/rekor/cmd/rekor-cli/app/format"
"github.com/sigstore/rekor/cmd/rekor-cli/app/state"
"github.com/sigstore/rekor/pkg/client"
"github.com/sigstore/rekor/pkg/generated/client/pubkey"
"github.com/sigstore/rekor/pkg/generated/client/tlog"
"github.com/sigstore/rekor/pkg/log"
"github.com/sigstore/rekor/pkg/util"
Expand Down Expand Up @@ -131,7 +132,7 @@ func verifyTree(ctx context.Context, rekorClient *rclient.Rekor, signedTreeHead,
if err := sth.UnmarshalText([]byte(signedTreeHead)); err != nil {
return err
}
verifier, err := loadVerifier(rekorClient)
verifier, err := loadVerifier(rekorClient, treeID)
if err != nil {
return err
}
Expand Down Expand Up @@ -160,11 +161,11 @@ func verifyTree(ctx context.Context, rekorClient *rclient.Rekor, signedTreeHead,
return nil
}

func loadVerifier(rekorClient *rclient.Rekor) (signature.Verifier, error) {
func loadVerifier(rekorClient *rclient.Rekor, treeID string) (signature.Verifier, error) {
publicKey := viper.GetString("rekor_server_public_key")
if publicKey == "" {
// fetch key from server
keyResp, err := rekorClient.Pubkey.GetPublicKey(nil)
keyResp, err := rekorClient.Pubkey.GetPublicKey(pubkey.NewGetPublicKeyParams().WithTreeID(swag.String(treeID)))
if err != nil {
return nil, err
}
Expand Down
13 changes: 11 additions & 2 deletions cmd/rekor-cli/app/upload.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (
"net/url"
"os"
"path/filepath"
"strconv"

"github.com/go-openapi/runtime"
"github.com/go-openapi/swag"
Expand All @@ -35,6 +36,7 @@ import (
"github.com/sigstore/rekor/pkg/generated/client/entries"
"github.com/sigstore/rekor/pkg/generated/models"
"github.com/sigstore/rekor/pkg/log"
"github.com/sigstore/rekor/pkg/sharding"
"github.com/sigstore/rekor/pkg/types"
"github.com/sigstore/rekor/pkg/verify"
)
Expand Down Expand Up @@ -122,13 +124,20 @@ var uploadCmd = &cobra.Command{

var newIndex int64
var logEntry models.LogEntryAnon
for _, entry := range resp.Payload {
var uuid string
for k, entry := range resp.Payload {
uuid = k
newIndex = swag.Int64Value(entry.LogIndex)
logEntry = entry
}

treeID, err := sharding.TreeID(uuid)
if err != nil {
return nil, err
}

// verify log entry
verifier, err := loadVerifier(rekorClient)
verifier, err := loadVerifier(rekorClient, strconv.FormatInt(treeID, 10))
if err != nil {
return nil, fmt.Errorf("retrieving rekor public key")
}
Expand Down
7 changes: 6 additions & 1 deletion cmd/rekor-cli/app/verify.go
Original file line number Diff line number Diff line change
Expand Up @@ -165,9 +165,14 @@ var verifyCmd = &cobra.Command{
}
}

treeID, err := sharding.TreeID(o.EntryUUID)
if err != nil {
return nil, err
}

// Get Rekor Pub
// TODO(asraa): Replace with sigstore's GetRekorPubs to use TUF.
verifier, err := loadVerifier(rekorClient)
verifier, err := loadVerifier(rekorClient, strconv.FormatInt(treeID, 10))
if err != nil {
return nil, err
}
Expand Down
55 changes: 14 additions & 41 deletions pkg/api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,8 @@ package api

import (
"context"
"crypto/sha256"
"crypto/tls"
"crypto/x509"
"encoding/hex"
"fmt"
"os"
"path/filepath"
Expand All @@ -42,9 +40,6 @@ import (
"github.com/sigstore/rekor/pkg/storage"
"github.com/sigstore/rekor/pkg/trillianclient"
"github.com/sigstore/rekor/pkg/witness"
"github.com/sigstore/sigstore/pkg/cryptoutils"
"github.com/sigstore/sigstore/pkg/signature"
"github.com/sigstore/sigstore/pkg/signature/options"

_ "github.com/sigstore/rekor/pkg/pubsub/gcp" // Load GCP pubsub implementation
)
Expand Down Expand Up @@ -92,12 +87,9 @@ func dial(rpcServer string) (*grpc.ClientConn, error) {
}

type API struct {
logClient trillian.TrillianLogClient
logID int64
logRanges sharding.LogRanges
pubkey string // PEM encoded public key
pubkeyHash string // SHA256 hash of DER-encoded public key
signer signature.Signer
logClient trillian.TrillianLogClient
treeID int64
logRanges sharding.LogRanges
// stops checkpoint publishing
checkpointPublishCancel context.CancelFunc
// Publishes notifications when new entries are added to the log. May be
Expand All @@ -117,12 +109,6 @@ func NewAPI(treeID uint) (*API, error) {
logAdminClient := trillian.NewTrillianAdminClient(tConn)
logClient := trillian.NewTrillianLogClient(tConn)

shardingConfig := viper.GetString("trillian_log_server.sharding_config")
ranges, err := sharding.NewLogRanges(ctx, logClient, shardingConfig, treeID)
if err != nil {
return nil, fmt.Errorf("unable get sharding details from sharding config: %w", err)
}

tid := int64(treeID)
if tid == 0 {
log.Logger.Info("No tree ID specified, attempting to create a new tree")
Expand All @@ -133,27 +119,18 @@ func NewAPI(treeID uint) (*API, error) {
tid = t.TreeId
}
log.Logger.Infof("Starting Rekor server with active tree %v", tid)
ranges.SetActive(tid)

rekorSigner, err := signer.New(ctx, viper.GetString("rekor_server.signer"),
viper.GetString("rekor_server.signer-passwd"),
viper.GetString("rekor_server.tink_kek_uri"),
viper.GetString("rekor_server.tink_keyset_path"),
)
if err != nil {
return nil, fmt.Errorf("getting new signer: %w", err)
}
pk, err := rekorSigner.PublicKey(options.WithContext(ctx))
if err != nil {
return nil, fmt.Errorf("getting public key: %w", err)
shardingConfig := viper.GetString("trillian_log_server.sharding_config")
signingConfig := signer.SigningConfig{
SigningSchemeOrKeyPath: viper.GetString("rekor_server.signer"),
FileSignerPassword: viper.GetString("rekor_server.signer-passwd"),
TinkKEKURI: viper.GetString("rekor_server.tink_kek_uri"),
TinkKeysetPath: viper.GetString("rekor_server.tink_keyset_path"),
}
b, err := x509.MarshalPKIXPublicKey(pk)
ranges, err := sharding.NewLogRanges(ctx, logClient, shardingConfig, tid, signingConfig)
if err != nil {
return nil, fmt.Errorf("marshalling public key: %w", err)
return nil, fmt.Errorf("unable get sharding details from sharding config: %w", err)
}
pubkeyHashBytes := sha256.Sum256(b)

pubkey := cryptoutils.PEMEncode(cryptoutils.PublicKeyPEMType, b)

var newEntryPublisher pubsub.Publisher
if p := viper.GetString("rekor_server.new_entry_publisher"); p != "" {
Expand All @@ -170,12 +147,8 @@ func NewAPI(treeID uint) (*API, error) {
return &API{
// Transparency Log Stuff
logClient: logClient,
logID: tid,
treeID: tid,
logRanges: ranges,
// Signing/verifying fields
pubkey: string(pubkey),
pubkeyHash: hex.EncodeToString(pubkeyHashBytes[:]),
signer: rekorSigner,
// Utility functionality not required for operation of the core service
newEntryPublisher: newEntryPublisher,
}, nil
Expand Down Expand Up @@ -212,8 +185,8 @@ func ConfigureAPI(treeID uint) {

if viper.GetBool("enable_stable_checkpoint") {
redisClient = NewRedisClient()
checkpointPublisher := witness.NewCheckpointPublisher(context.Background(), api.logClient, api.logRanges.ActiveTreeID(),
viper.GetString("rekor_server.hostname"), api.signer, redisClient, viper.GetUint("publish_frequency"), CheckpointPublishCount)
checkpointPublisher := witness.NewCheckpointPublisher(context.Background(), api.logClient, api.logRanges.GetActive().TreeID,
viper.GetString("rekor_server.hostname"), api.logRanges.GetActive().Signer, redisClient, viper.GetUint("publish_frequency"), CheckpointPublishCount)

// create context to cancel goroutine on server shutdown
ctx, cancel := context.WithCancel(context.Background())
Expand Down
35 changes: 20 additions & 15 deletions pkg/api/entries.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ func signEntry(ctx context.Context, signer signature.Signer, entry models.LogEnt
}

// logEntryFromLeaf creates a signed LogEntry struct from trillian structs
func logEntryFromLeaf(ctx context.Context, signer signature.Signer, _ trillianclient.TrillianClient, leaf *trillian.LogLeaf,
func logEntryFromLeaf(ctx context.Context, _ trillianclient.TrillianClient, leaf *trillian.LogLeaf,
signedLogRoot *trillian.SignedLogRoot, proof *trillian.Proof, tid int64, ranges sharding.LogRanges) (models.LogEntry, error) {

log.ContextLogger(ctx).Debugf("log entry from leaf %d", leaf.GetLeafIndex())
Expand All @@ -88,19 +88,24 @@ func logEntryFromLeaf(ctx context.Context, signer signature.Signer, _ trilliancl
}

virtualIndex := sharding.VirtualLogIndex(leaf.GetLeafIndex(), tid, ranges)
logRange, err := ranges.GetLogRangeByTreeID(tid)
if err != nil {
return nil, err
}

logEntryAnon := models.LogEntryAnon{
LogID: swag.String(api.pubkeyHash),
LogID: swag.String(logRange.LogID),
LogIndex: &virtualIndex,
Body: leaf.LeafValue,
IntegratedTime: swag.Int64(leaf.IntegrateTimestamp.AsTime().Unix()),
}

signature, err := signEntry(ctx, signer, logEntryAnon)
signature, err := signEntry(ctx, logRange.Signer, logEntryAnon)
if err != nil {
return nil, fmt.Errorf("signing entry error: %w", err)
}

scBytes, err := util.CreateAndSignCheckpoint(ctx, viper.GetString("rekor_server.hostname"), tid, root.TreeSize, root.RootHash, api.signer)
scBytes, err := util.CreateAndSignCheckpoint(ctx, viper.GetString("rekor_server.hostname"), tid, root.TreeSize, root.RootHash, logRange.Signer)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -194,7 +199,7 @@ func createLogEntry(params entries.CreateLogEntryParams) (models.LogEntry, middl
return nil, handleRekorAPIError(params, http.StatusInternalServerError, err, failedToGenerateCanonicalEntry)
}

tc := trillianclient.NewTrillianClient(ctx, api.logClient, api.logID)
tc := trillianclient.NewTrillianClient(ctx, api.logClient, api.treeID)

resp := tc.AddLeaf(leaf)
// this represents overall GRPC response state (not the results of insertion into the log)
Expand All @@ -209,7 +214,7 @@ func createLogEntry(params entries.CreateLogEntryParams) (models.LogEntry, middl
case int32(code.Code_OK):
case int32(code.Code_ALREADY_EXISTS), int32(code.Code_FAILED_PRECONDITION):
existingUUID := hex.EncodeToString(rfc6962.DefaultHasher.HashLeaf(leaf))
activeTree := fmt.Sprintf("%x", api.logID)
activeTree := fmt.Sprintf("%x", api.treeID)
entryIDstruct, err := sharding.CreateEntryIDFromParts(activeTree, existingUUID)
if err != nil {
err := fmt.Errorf("error creating EntryID from active treeID %v and uuid %v: %w", activeTree, existingUUID, err)
Expand All @@ -230,7 +235,7 @@ func createLogEntry(params entries.CreateLogEntryParams) (models.LogEntry, middl
queuedLeaf := resp.GetAddResult.QueuedLeaf.Leaf

uuid := hex.EncodeToString(queuedLeaf.GetMerkleLeafHash())
activeTree := fmt.Sprintf("%x", api.logID)
activeTree := fmt.Sprintf("%x", api.treeID)
entryIDstruct, err := sharding.CreateEntryIDFromParts(activeTree, uuid)
if err != nil {
err := fmt.Errorf("error creating EntryID from active treeID %v and uuid %v: %w", activeTree, uuid, err)
Expand All @@ -239,9 +244,9 @@ func createLogEntry(params entries.CreateLogEntryParams) (models.LogEntry, middl
entryID := entryIDstruct.ReturnEntryIDString()

// The log index should be the virtual log index across all shards
virtualIndex := sharding.VirtualLogIndex(queuedLeaf.LeafIndex, api.logRanges.ActiveTreeID(), api.logRanges)
virtualIndex := sharding.VirtualLogIndex(queuedLeaf.LeafIndex, api.logRanges.GetActive().TreeID, api.logRanges)
logEntryAnon := models.LogEntryAnon{
LogID: swag.String(api.pubkeyHash),
LogID: swag.String(api.logRanges.GetActive().LogID),
LogIndex: swag.Int64(virtualIndex),
Body: queuedLeaf.GetLeafValue(),
IntegratedTime: swag.Int64(queuedLeaf.IntegrateTimestamp.AsTime().Unix()),
Expand Down Expand Up @@ -286,7 +291,7 @@ func createLogEntry(params entries.CreateLogEntryParams) (models.LogEntry, middl
}
}

signature, err := signEntry(ctx, api.signer, logEntryAnon)
signature, err := signEntry(ctx, api.logRanges.GetActive().Signer, logEntryAnon)
if err != nil {
return nil, handleRekorAPIError(params, http.StatusInternalServerError, fmt.Errorf("signing entry error: %w", err), signingError)
}
Expand All @@ -300,7 +305,7 @@ func createLogEntry(params entries.CreateLogEntryParams) (models.LogEntry, middl
hashes = append(hashes, hex.EncodeToString(hash))
}

scBytes, err := util.CreateAndSignCheckpoint(ctx, viper.GetString("rekor_server.hostname"), api.logID, root.TreeSize, root.RootHash, api.signer)
scBytes, err := util.CreateAndSignCheckpoint(ctx, viper.GetString("rekor_server.hostname"), api.treeID, root.TreeSize, root.RootHash, api.logRanges.GetActive().Signer)
if err != nil {
return nil, handleRekorAPIError(params, http.StatusInternalServerError, err, sthGenerateError)
}
Expand Down Expand Up @@ -511,7 +516,7 @@ func SearchLogQueryHandler(params entries.SearchLogQueryParams) middleware.Respo
continue
}
tcs := trillianclient.NewTrillianClient(httpReqCtx, api.logClient, shard)
logEntry, err := logEntryFromLeaf(httpReqCtx, api.signer, tcs, leafResp.Leaf, leafResp.SignedLogRoot, leafResp.Proof, shard, api.logRanges)
logEntry, err := logEntryFromLeaf(httpReqCtx, tcs, leafResp.Leaf, leafResp.SignedLogRoot, leafResp.Proof, shard, api.logRanges)
if err != nil {
return handleRekorAPIError(params, http.StatusInternalServerError, err, err.Error())
}
Expand Down Expand Up @@ -558,7 +563,7 @@ func retrieveLogEntryByIndex(ctx context.Context, logIndex int) (models.LogEntry
return models.LogEntry{}, ErrNotFound
}

return logEntryFromLeaf(ctx, api.signer, tc, leaf, result.SignedLogRoot, result.Proof, tid, api.logRanges)
return logEntryFromLeaf(ctx, tc, leaf, result.SignedLogRoot, result.Proof, tid, api.logRanges)
}

// Retrieve a Log Entry
Expand All @@ -580,7 +585,7 @@ func retrieveLogEntry(ctx context.Context, entryUUID string) (models.LogEntry, e

// If we got a UUID instead of an EntryID, search all shards
if errors.Is(err, sharding.ErrPlainUUID) {
trees := []sharding.LogRange{{TreeID: api.logRanges.ActiveTreeID()}}
trees := []sharding.LogRange{api.logRanges.GetActive()}
trees = append(trees, api.logRanges.GetInactive()...)

for _, t := range trees {
Expand Down Expand Up @@ -623,7 +628,7 @@ func retrieveUUIDFromTree(ctx context.Context, uuid string, tid int64) (models.L
return models.LogEntry{}, err
}

logEntry, err := logEntryFromLeaf(ctx, api.signer, tc, result.Leaf, result.SignedLogRoot, result.Proof, tid, api.logRanges)
logEntry, err := logEntryFromLeaf(ctx, tc, result.Leaf, result.SignedLogRoot, result.Proof, tid, api.logRanges)
if err != nil {
return models.LogEntry{}, fmt.Errorf("could not create log entry from leaf: %w", err)
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/api/public_key.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import (

func GetPublicKeyHandler(params pubkey.GetPublicKeyParams) middleware.Responder {
treeID := swag.StringValue(params.TreeID)
pk, err := api.logRanges.PublicKey(api.pubkey, treeID)
pk, err := api.logRanges.PublicKey(treeID)
if err != nil {
return handleRekorAPIError(params, http.StatusBadRequest, err, "")
}
Expand Down
Loading
Loading