Skip to content

Commit

Permalink
Support other hash algs for pre-signed timestamp besides SHA256
Browse files Browse the repository at this point in the history
There are three relevant hash algs used in this codebase:

* The certificate hash alg, which specifies how the CA certificate
  hashed the to-be-signed certificate
* The message hash alg, specified in the request, which says how the
  timestamp message was hashed
* The timestamp hash alg, which specifies how the timestamp signer
  should hash the pre-signed timestamp structure

The latter of these three was not configurable. We had a previous
approach that used the certificate hash alg, but this does not have to
match the timestamp hash alg.

Signed-off-by: Hayden Blauzvern <[email protected]>
  • Loading branch information
haydentherapper committed Oct 3, 2023
1 parent 43342cb commit 503d0b0
Show file tree
Hide file tree
Showing 8 changed files with 43 additions and 16 deletions.
1 change: 1 addition & 0 deletions cmd/timestamp-server/app/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ func init() {
rootCmd.PersistentFlags().BoolVar(&enablePprof, "enable-pprof", false, "enable pprof for profiling on port 6060")
rootCmd.PersistentFlags().BoolVar(&httpPingOnly, "http-ping-only", false, "serve only /ping in the http server")
rootCmd.PersistentFlags().String("timestamp-signer", "memory", "Timestamping authority signer. Valid options include: [kms, tink, memory, file]. Memory and file-based signers should only be used for testing")
rootCmd.PersistentFlags().String("timestamp-signer-hash", "sha256", "Hash algorithm used by the signer. Must match the hash algorithm specified for a KMS or Tink key. Valid options include: [sha256, sha384, sha512]. Ignored for Memory signer.")
// KMS flags
rootCmd.PersistentFlags().String("kms-key-resource", "", "KMS key for signing timestamp responses. Valid options include: [gcpkms://resource, azurekms://resource, hashivault://resource, awskms://resource]")
// Tink flags
Expand Down
21 changes: 14 additions & 7 deletions pkg/api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,15 +34,21 @@ import (
)

type API struct {
tsaSigner crypto.Signer // the signer to use for timestamping
certChain []*x509.Certificate // timestamping cert chain
certChainPem string // PEM encoded timestamping cert chain
tsaSigner crypto.Signer // the signer to use for timestamping
tsaSignerHash crypto.Hash // hash algorithm used to hash pre-signed timestamps
certChain []*x509.Certificate // timestamping cert chain
certChainPem string // PEM encoded timestamping cert chain
}

func NewAPI() (*API, error) {
ctx := context.Background()

tsaSigner, err := signer.NewCryptoSigner(ctx, viper.GetString("timestamp-signer"),
tsaSignerHash, err := signer.HashToAlg(viper.GetString("timestamp-signer-hash"))
if err != nil {
return nil, errors.Wrap(err, "error getting hash")
}
tsaSigner, err := signer.NewCryptoSigner(ctx, tsaSignerHash,
viper.GetString("timestamp-signer"),
viper.GetString("kms-key-resource"),
viper.GetString("tink-key-resource"), viper.GetString("tink-keyset-path"),
viper.GetString("tink-hcvault-token"),
Expand Down Expand Up @@ -81,9 +87,10 @@ func NewAPI() (*API, error) {
}

return &API{
tsaSigner: tsaSigner,
certChain: certChain,
certChainPem: string(certChainPEM),
tsaSigner: tsaSigner,
tsaSignerHash: tsaSignerHash,
certChain: certChain,
certChainPem: string(certChainPEM),
}, nil
}

Expand Down
2 changes: 1 addition & 1 deletion pkg/api/timestamp.go
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ func TimestampResponseHandler(params ts.GetTimestampResponseParams) middleware.R
ExtraExtensions: req.Extensions,
}

resp, err := tsStruct.CreateResponseWithOpts(api.certChain[0], api.tsaSigner, crypto.SHA256)
resp, err := tsStruct.CreateResponseWithOpts(api.certChain[0], api.tsaSigner, api.tsaSignerHash)
if err != nil {
return handleTimestampAPIError(params, http.StatusInternalServerError, err, failedToGenerateTimestampResponse)
}
Expand Down
6 changes: 3 additions & 3 deletions pkg/signer/file.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,21 +30,21 @@ type File struct {
crypto.Signer
}

func NewFileSigner(keyPath, keyPass string) (*File, error) {
func NewFileSigner(keyPath, keyPass string, hash crypto.Hash) (*File, error) {
opaqueKey, err := pemutil.Read(keyPath, pemutil.WithPassword([]byte(keyPass)))
if err != nil {
return nil, fmt.Errorf("file: provide a valid signer, %s is not valid: %w", keyPath, err)
}
// Cannot use signature.LoadSignerVerifier because the SignerVerifier interface does not extend crypto.Signer
switch pk := opaqueKey.(type) {
case *rsa.PrivateKey:
signer, err := signature.LoadRSAPKCS1v15SignerVerifier(pk, crypto.SHA256)
signer, err := signature.LoadRSAPKCS1v15SignerVerifier(pk, hash)
if err != nil {
return nil, err
}
return &File{signer}, nil
case *ecdsa.PrivateKey:
signer, err := signature.LoadECDSASignerVerifier(pk, crypto.SHA256)
signer, err := signature.LoadECDSASignerVerifier(pk, hash)
if err != nil {
return nil, err
}
Expand Down
3 changes: 2 additions & 1 deletion pkg/signer/file_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
package signer

import (
"crypto"
"crypto/ecdsa"
"crypto/ed25519"
"crypto/elliptic"
Expand Down Expand Up @@ -88,7 +89,7 @@ func TestNewFileSigner(t *testing.T) {
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
tc := tc
_, err := NewFileSigner(tc.keyPath, tc.keyPass)
_, err := NewFileSigner(tc.keyPath, tc.keyPass, crypto.SHA256)
if tc.wantErr != (err != nil) {
t.Errorf("NewFileSigner() expected %t, got err %s", tc.wantErr, err)
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/signer/memory_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ import (
func TestNewTimestampingCertWithChain(t *testing.T) {
ctx := context.Background()

signer, err := NewCryptoSigner(ctx, "memory", "", "", "", "", "", "")
signer, err := NewCryptoSigner(ctx, crypto.Hash(0), "memory", "", "", "", "", "", "")
if err != nil {
t.Fatalf("new signer: %v", err)
}
Expand Down
23 changes: 20 additions & 3 deletions pkg/signer/signer.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"crypto/elliptic"
"crypto/rand"
"fmt"
"strings"

"github.com/sigstore/sigstore/pkg/signature"
"github.com/sigstore/sigstore/pkg/signature/kms"
Expand All @@ -36,15 +37,15 @@ const TinkScheme = "tink"
const MemoryScheme = "memory"
const FileScheme = "file"

func NewCryptoSigner(ctx context.Context, signer, kmsKey, tinkKmsKey, tinkKeysetPath, hcVaultToken, fileSignerPath, fileSignerPasswd string) (crypto.Signer, error) {
func NewCryptoSigner(ctx context.Context, hash crypto.Hash, signer, kmsKey, tinkKmsKey, tinkKeysetPath, hcVaultToken, fileSignerPath, fileSignerPasswd string) (crypto.Signer, error) {
switch signer {
case MemoryScheme:
sv, _, err := signature.NewECDSASignerVerifier(elliptic.P256(), rand.Reader, crypto.SHA256)
return sv, err
case FileScheme:
return NewFileSigner(fileSignerPath, fileSignerPasswd)
return NewFileSigner(fileSignerPath, fileSignerPasswd, hash)
case KMSScheme:
signer, err := kms.Get(ctx, kmsKey, crypto.SHA256)
signer, err := kms.Get(ctx, kmsKey, hash) // hash is ignored for all KMS providers except Hashivault
if err != nil {
return nil, err
}
Expand All @@ -60,3 +61,19 @@ func NewCryptoSigner(ctx context.Context, signer, kmsKey, tinkKmsKey, tinkKeyset
return nil, fmt.Errorf("unsupported signer type: %s", signer)
}
}

func HashToAlg(signerHashAlg string) (crypto.Hash, error) {
lowercaseAlg := strings.ToLower(signerHashAlg)
var hash crypto.Hash
switch lowercaseAlg {
case "sha256":
hash = crypto.SHA256
case "sha384":
hash = crypto.SHA384
case "sha512":
hash = crypto.SHA512
default:
return crypto.Hash(0), fmt.Errorf("unsupported hash algorithm: %s", lowercaseAlg)
}
return hash, nil
}
1 change: 1 addition & 0 deletions pkg/tests/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import (

func createServer(t *testing.T) string {
viper.Set("timestamp-signer", "memory")
viper.Set("timestamp-signer-hash", "sha256")
// unused port
apiServer := server.NewRestAPIServer("localhost", 0, []string{"http"}, false, 10*time.Second, 10*time.Second)
server := httptest.NewServer(apiServer.GetHandler())
Expand Down

0 comments on commit 503d0b0

Please sign in to comment.