Skip to content
Draft
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
1 change: 1 addition & 0 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ type KeyConfig struct {
Timestamp bool // If true, attach a timestamped countersignature when possible
Timestamper string // If set, use the named timestamper to countersign
Hide bool // If true, then omit this key from 'remote list-keys'
Memcache []string // host:port of memcached to use for caching signatures

name string
token *TokenConfig
Expand Down
4 changes: 4 additions & 0 deletions doc/relic.yml
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,10 @@ keys:
# see `namedurls` below. Implies "timestamp: true".
#timestamper: apple

# Optional memcache servers for memoizing signing requests
#memcache:
# - 127.0.0.1:11211

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should i mention that the cache should be cleared after changing the key configuration?

# Clients with any of these roles can utilize this key
roles: ["somegroup"]

Expand Down
10 changes: 10 additions & 0 deletions internal/signinit/signinit.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import (
"github.com/sassoftware/relic/v8/signers"
"github.com/sassoftware/relic/v8/signers/sigerrors"
"github.com/sassoftware/relic/v8/token"
"github.com/sassoftware/relic/v8/token/signaturecache"
)

// InitKey loads the cert chain for a key
Expand Down Expand Up @@ -85,6 +86,15 @@ func Init(ctx context.Context, mod *signers.Signer, tok token.Token, keyName str
Audit: auditInfo,
Flags: flags,
}

// Use signature cache if configured
if len(kconf.Memcache) > 0 {
cert.PrivateKey, err = signaturecache.New(kconf, &opts, cert.PrivateKey.(crypto.Signer))
if err != nil {
return nil, nil, err
}
}

opts = opts.WithContext(ctx)
return cert, &opts, nil
}
Expand Down
80 changes: 80 additions & 0 deletions token/signaturecache/signaturecache.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
// Copyright © Leonhard Oelke
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package signaturecache

import (
"crypto"
"fmt"
"io"
"time"

"github.com/bradfitz/gomemcache/memcache"
"github.com/sassoftware/relic/v8/config"
"github.com/sassoftware/relic/v8/signers"
)

const (
// Settings for now are the same as the timestamp cache settings
memcacheTimeout = 1 * time.Second
memcacheExpiry = 7 * 24 * time.Hour
)

type SignatureCache interface {
crypto.Signer
}

type signatureCache struct {
keyConf *config.KeyConfig
opts *signers.SignOpts
signer crypto.Signer
Memcache *memcache.Client
}

func New(keyConf *config.KeyConfig, opts *signers.SignOpts, signer crypto.Signer) (SignatureCache, error) {
selector := new(memcache.ServerList)
if err := selector.SetServers(keyConf.Memcache...); err != nil {
return nil, fmt.Errorf("parsing memcache servers: %w", err)
}
mc := memcache.NewFromSelector(selector)
mc.Timeout = memcacheTimeout
return &signatureCache{keyConf, opts, signer, mc}, nil
}

func (c *signatureCache) Public() crypto.PublicKey {
return c.signer.Public()
}

func (c *signatureCache) Sign(rand io.Reader, digest []byte, opts crypto.SignerOpts) ([]byte, error) {
cacheKey := fmt.Sprintf("sig-%s-%x", c.keyConf.Name(), digest)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm making the following assumptions here:

  • If the key configuration changes for the named key, the user should clear the cache
  • Digests from different Hashing-Algorithms will not collide


item, err := c.Memcache.Get(cacheKey)
if err == nil && item != nil {
c.opts.Audit.Attributes["sig.cache"] = "hit"
return item.Value, nil
}

signature, err := c.signer.Sign(rand, digest, opts)
if err != nil {
return nil, err
}

_ = c.Memcache.Set(&memcache.Item{
Key: cacheKey,
Value: signature,
Expiration: int32(memcacheExpiry / time.Second),
})

c.opts.Audit.Attributes["sig.cache"] = "miss"
return signature, nil
}