Skip to content

Commit

Permalink
Add support for crypto.Signer when signing and verifying signatures
Browse files Browse the repository at this point in the history
  • Loading branch information
wolfeidau committed Aug 28, 2024
1 parent 176ba77 commit 8efb3c6
Show file tree
Hide file tree
Showing 2 changed files with 50 additions and 15 deletions.
62 changes: 49 additions & 13 deletions signature/sign.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"crypto"
"crypto/sha256"
"crypto/x509"
"encoding/hex"
"encoding/json"
"errors"
Expand All @@ -12,6 +13,7 @@ import (

"github.com/buildkite/go-pipeline"
"github.com/gowebpki/jcs"
"github.com/lestrrat-go/jwx/v2/jwa"
"github.com/lestrrat-go/jwx/v2/jwk"
"github.com/lestrrat-go/jwx/v2/jws"
)
Expand Down Expand Up @@ -73,9 +75,13 @@ func configureOptions(opts ...Option) options {
return options
}

type Key interface {
Algorithm() jwa.KeyAlgorithm
}

// Sign computes a new signature for an environment (env) combined with an
// object containing values (sf) using a given key.
func Sign(_ context.Context, key jwk.Key, sf SignedFielder, opts ...Option) (*pipeline.Signature, error) {
func Sign(_ context.Context, key Key, sf SignedFielder, opts ...Option) (*pipeline.Signature, error) {
options := configureOptions(opts...)

values, err := sf.SignedFields()
Expand Down Expand Up @@ -113,16 +119,28 @@ func Sign(_ context.Context, key jwk.Key, sf SignedFielder, opts ...Option) (*pi
return nil, err
}

if options.logger != nil {
switch key := key.(type) {
case jwk.Key:
pk, err := key.PublicKey()
if err != nil {
return nil, fmt.Errorf("unable to generate public key: %w", err)
}

fingerprint, err := pk.Thumbprint(crypto.SHA256)
if err != nil {
return nil, fmt.Errorf("calculating key thumbprint: %w", err)
}

debug(options.logger, "Public Key Thumbprint (sha256): %s", hex.EncodeToString(fingerprint))
case crypto.Signer:
data, err := x509.MarshalPKIXPublicKey(key.Public())
if err != nil {
return nil, fmt.Errorf("failed to marshal public key: %w", err)
}

debug(options.logger, "Public Key Thumbprint (sha256): %x", sha256.Sum256(data))
default:
panic(fmt.Sprintf("unsupported key type: %T", key)) // should never happen
}

if options.debugSigning {
Expand All @@ -147,7 +165,7 @@ func Sign(_ context.Context, key jwk.Key, sf SignedFielder, opts ...Option) (*pi

// Verify verifies an existing signature against environment (env) combined with
// an object containing values (sf) using keys from a keySet.
func Verify(ctx context.Context, s *pipeline.Signature, keySet jwk.Set, sf SignedFielder, opts ...Option) error {
func Verify(ctx context.Context, s *pipeline.Signature, keySet any, sf SignedFielder, opts ...Option) error {
options := configureOptions(opts...)

if len(s.SignedFields) == 0 {
Expand Down Expand Up @@ -187,22 +205,40 @@ func Verify(ctx context.Context, s *pipeline.Signature, keySet jwk.Set, sf Signe
return err
}

for it := keySet.Keys(ctx); it.Next(ctx); {
pair := it.Pair()
publicKey := pair.Value.(jwk.Key)
fingerprint, err := publicKey.Thumbprint(crypto.SHA256)
if options.debugSigning {
debug(options.logger, "Signed Step: %s checksum: %x", payload, sha256.Sum256(payload))
}

var keyOpt jws.VerifyOption
switch keySet := keySet.(type) {
case jwk.Set:
for it := keySet.Keys(ctx); it.Next(ctx); {
pair := it.Pair()
publicKey := pair.Value.(jwk.Key)
fingerprint, err := publicKey.Thumbprint(crypto.SHA256)
if err != nil {
return fmt.Errorf("calculating key thumbprint: %w", err)
}

debug(options.logger, "Public Key Thumbprint (sha256): %s", hex.EncodeToString(fingerprint))
}

keyOpt = jws.WithKeySet(keySet)
case crypto.Signer:
data, err := x509.MarshalPKIXPublicKey(keySet.Public())
if err != nil {
return fmt.Errorf("calculating key thumbprint: %w", err)
return fmt.Errorf("failed to marshal public key: %w", err)
}
debug(options.logger, "Public Key Thumbprint (sha256): %s", hex.EncodeToString(fingerprint))
}

if options.debugSigning {
debug(options.logger, "Signed Step: %s checksum: %x", payload, sha256.Sum256(payload))
debug(options.logger, "Public Key Thumbprint (sha256): %x", sha256.Sum256(data))

keyOpt = jws.WithKey(jwa.ES256, keySet)
default:
panic(fmt.Sprintf("unsupported key type: %T", keySet)) // should never happen
}

_, err = jws.Verify([]byte(s.Value),
jws.WithKeySet(keySet),
keyOpt,
jws.WithDetachedPayload(payload),
)
return err
Expand Down
3 changes: 1 addition & 2 deletions signature/steps.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,13 @@ import (
"fmt"

"github.com/buildkite/go-pipeline"
"github.com/lestrrat-go/jwx/v2/jwk"
)

var errSigningRefusedUnknownStepType = errors.New("refusing to sign pipeline containing a step of unknown type, because the pipeline could be incorrectly parsed - please contact support")

// SignSteps adds signatures to each command step (and recursively to any command steps that are within group steps).
// The steps are mutated directly, so an error part-way through may leave some steps un-signed.
func SignSteps(ctx context.Context, s pipeline.Steps, key jwk.Key, repoURL string, opts ...Option) error {
func SignSteps(ctx context.Context, s pipeline.Steps, key Key, repoURL string, opts ...Option) error {
for _, step := range s {
switch step := step.(type) {
case *pipeline.CommandStep:
Expand Down

0 comments on commit 8efb3c6

Please sign in to comment.