From 258e1853683318248b5ddbb5139516e7abc7a9d0 Mon Sep 17 00:00:00 2001 From: qmuntal Date: Fri, 12 May 2023 11:57:00 +0200 Subject: [PATCH 1/7] Make built-in types implement the new DigestSigner and DigestVerify interface Signed-off-by: qmuntal --- ecdsa.go | 22 ++++++++++++++++++++++ rsa.go | 26 ++++++++++++++++++++------ signer.go | 12 ++++++++++++ verifier.go | 12 ++++++++++++ 4 files changed, 66 insertions(+), 6 deletions(-) diff --git a/ecdsa.go b/ecdsa.go index 7e426be..36839ba 100644 --- a/ecdsa.go +++ b/ecdsa.go @@ -57,6 +57,13 @@ func (es *ecdsaKeySigner) Sign(rand io.Reader, content []byte) ([]byte, error) { if err != nil { return nil, err } + return es.SignDigest(rand, digest) +} + +// SignDigest signs message digest with the private key, possibly using +// entropy from rand. +// The resulting signature should follow RFC 8152 section 8. +func (es *ecdsaKeySigner) SignDigest(rand io.Reader, digest []byte) ([]byte, error) { r, s, err := ecdsa.Sign(rand, es.key, digest) if err != nil { return nil, err @@ -86,6 +93,13 @@ func (es *ecdsaCryptoSigner) Sign(rand io.Reader, content []byte) ([]byte, error if err != nil { return nil, err } + return es.SignDigest(rand, digest) +} + +// SignDigest signs message digest with the private key, possibly using +// entropy from rand. +// The resulting signature should follow RFC 8152 section 8. +func (es *ecdsaCryptoSigner) SignDigest(rand io.Reader, digest []byte) ([]byte, error) { sigASN1, err := es.signer.Sign(rand, digest, nil) if err != nil { return nil, err @@ -153,7 +167,15 @@ func (ev *ecdsaVerifier) Verify(content []byte, signature []byte) error { if err != nil { return err } + return ev.VerifyDigest(digest, signature) +} +// VerifyDigest verifies message digest with the public key, returning nil +// for success. +// Otherwise, it returns ErrVerification. +// +// Reference: https://datatracker.ietf.org/doc/html/rfc8152#section-8.1 +func (ev *ecdsaVerifier) VerifyDigest(digest, signature []byte) error { // verify signature r, s, err := decodeECDSASignature(ev.key.Curve, signature) if err != nil { diff --git a/rsa.go b/rsa.go index b63098c..9dc045d 100644 --- a/rsa.go +++ b/rsa.go @@ -24,14 +24,20 @@ func (rs *rsaSigner) Algorithm() Algorithm { // // Reference: https://datatracker.ietf.org/doc/html/rfc8152#section-8 func (rs *rsaSigner) Sign(rand io.Reader, content []byte) ([]byte, error) { - hash := rs.alg.hashFunc() - digest, err := computeHash(hash, content) + digest, err := rs.alg.computeHash(content) if err != nil { return nil, err } + return rs.SignDigest(rand, digest) +} + +// SignDigest signs message digest with the private key, possibly using +// entropy from rand. +// The resulting signature should follow RFC 8152 section 8. +func (rs *rsaSigner) SignDigest(rand io.Reader, digest []byte) ([]byte, error) { return rs.key.Sign(rand, digest, &rsa.PSSOptions{ SaltLength: rsa.PSSSaltLengthEqualsHash, // defined in RFC 8230 sec 2 - Hash: hash, + Hash: rs.alg.hashFunc(), }) } @@ -54,12 +60,20 @@ func (rv *rsaVerifier) Algorithm() Algorithm { // // Reference: https://datatracker.ietf.org/doc/html/rfc8152#section-8 func (rv *rsaVerifier) Verify(content []byte, signature []byte) error { - hash := rv.alg.hashFunc() - digest, err := computeHash(hash, content) + digest, err := rv.alg.computeHash(content) if err != nil { return err } - if err := rsa.VerifyPSS(rv.key, hash, digest, signature, &rsa.PSSOptions{ + return rv.VerifyDigest(digest, signature) +} + +// VerifyDigest verifies message digest with the public key, returning nil +// for success. +// Otherwise, it returns ErrVerification. +// +// Reference: https://datatracker.ietf.org/doc/html/rfc8152#section-8.1 +func (rv *rsaVerifier) VerifyDigest(digest, signature []byte) error { + if err := rsa.VerifyPSS(rv.key, rv.alg.hashFunc(), digest, signature, &rsa.PSSOptions{ SaltLength: rsa.PSSSaltLengthEqualsHash, // defined in RFC 8230 sec 2 }); err != nil { return ErrVerification diff --git a/signer.go b/signer.go index 6747546..5de1f4c 100644 --- a/signer.go +++ b/signer.go @@ -23,6 +23,16 @@ type Signer interface { Sign(rand io.Reader, content []byte) ([]byte, error) } +// DigestSigner is an interface for private keys to sign digested COSE signatures. +type DigestSigner interface { + Signer + + // SignDigest signs message digest with the private key, possibly using + // entropy from rand. + // The resulting signature should follow RFC 8152 section 8. + SignDigest(rand io.Reader, digest []byte) ([]byte, error) +} + // NewSigner returns a signer with a given signing key. // The signing key can be a golang built-in crypto private key, a key in HSM, or // a remote KMS. @@ -34,6 +44,8 @@ type Signer interface { // public key of type `*rsa.PublicKey`, `*ecdsa.PublicKey`, or // `ed25519.PublicKey` are accepted. // +// The returned signer for rsa and ecdsa keys also implements `cose.DigestSigner`. +// // Note: `*rsa.PrivateKey`, `*ecdsa.PrivateKey`, and `ed25519.PrivateKey` // implement `crypto.Signer`. func NewSigner(alg Algorithm, key crypto.Signer) (Signer, error) { diff --git a/verifier.go b/verifier.go index 1c6e83b..5dfe4df 100644 --- a/verifier.go +++ b/verifier.go @@ -22,9 +22,21 @@ type Verifier interface { Verify(content, signature []byte) error } +// DigestVerifier is an interface for public keys to verify digested COSE signatures. +type DigestVerifier interface { + Verifier + + // VerifyDigest verifies message digest with the public key, returning nil + // for success. + // Otherwise, it returns ErrVerification. + VerifyDigest(digest, signature []byte) error +} + // NewVerifier returns a verifier with a given public key. // Only golang built-in crypto public keys of type `*rsa.PublicKey`, // `*ecdsa.PublicKey`, and `ed25519.PublicKey` are accepted. +// +// The returned signer for rsa and ecdsa keys also implements `cose.DigestSigner`. func NewVerifier(alg Algorithm, key crypto.PublicKey) (Verifier, error) { switch alg { case AlgorithmPS256, AlgorithmPS384, AlgorithmPS512: From d314e95be90c1f07ccf32915aa9b6629a6dab23b Mon Sep 17 00:00:00 2001 From: qmuntal Date: Mon, 10 Jul 2023 09:34:28 +0200 Subject: [PATCH 2/7] update README.md Signed-off-by: qmuntal --- README.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/README.md b/README.md index 74a8f77..aa01ca4 100644 --- a/README.md +++ b/README.md @@ -137,6 +137,16 @@ See [example_test.go](./example_test.go) for more examples. Untagged COSE_Sign1 messages can be signed and verified as above, using `cose.UntaggedSign1Message` instead of `cose.Sign1Message`. +#### Signing and Verification of digested payloads + +When `cose.NewSigner` is used with PS{256,384,512} or ES{256,384,512}, the returned signer +can be casted to the `cose.DigestSigner` interface, whose `SignDigest` method signs an +already digested message. + +When `cose.NewVerifier` is used with PS{256,384,512} or ES{256,384,512}, the returned verifier +can be casted to the `cose.DigestVerifier` interface, whose `VerifyDigest` method verifies an +already digested message. + ### About hashing `go-cose` does not import any hash package by its own to avoid linking unnecessary algorithms to the final binary. From 2893601c59d556933e29b09c51e25da460cc8c5f Mon Sep 17 00:00:00 2001 From: qmuntal Date: Tue, 18 Jul 2023 08:13:18 +0200 Subject: [PATCH 3/7] make VerifyDigest param types explicit Signed-off-by: qmuntal --- ecdsa.go | 2 +- rsa.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ecdsa.go b/ecdsa.go index 36839ba..d1ae0e9 100644 --- a/ecdsa.go +++ b/ecdsa.go @@ -175,7 +175,7 @@ func (ev *ecdsaVerifier) Verify(content []byte, signature []byte) error { // Otherwise, it returns ErrVerification. // // Reference: https://datatracker.ietf.org/doc/html/rfc8152#section-8.1 -func (ev *ecdsaVerifier) VerifyDigest(digest, signature []byte) error { +func (ev *ecdsaVerifier) VerifyDigest(digest []byte, signature []byte) error { // verify signature r, s, err := decodeECDSASignature(ev.key.Curve, signature) if err != nil { diff --git a/rsa.go b/rsa.go index 9dc045d..bb920d6 100644 --- a/rsa.go +++ b/rsa.go @@ -72,7 +72,7 @@ func (rv *rsaVerifier) Verify(content []byte, signature []byte) error { // Otherwise, it returns ErrVerification. // // Reference: https://datatracker.ietf.org/doc/html/rfc8152#section-8.1 -func (rv *rsaVerifier) VerifyDigest(digest, signature []byte) error { +func (rv *rsaVerifier) VerifyDigest(digest []byte, signature []byte) error { if err := rsa.VerifyPSS(rv.key, rv.alg.hashFunc(), digest, signature, &rsa.PSSOptions{ SaltLength: rsa.PSSSaltLengthEqualsHash, // defined in RFC 8230 sec 2 }); err != nil { From bbfd793c0da239335e83ec8f3caa84a77bf6ba0f Mon Sep 17 00:00:00 2001 From: qmuntal Date: Tue, 18 Jul 2023 08:16:52 +0200 Subject: [PATCH 4/7] update digest interfaces to not use embedding Signed-off-by: qmuntal --- signer.go | 3 ++- verifier.go | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/signer.go b/signer.go index 5de1f4c..78e1573 100644 --- a/signer.go +++ b/signer.go @@ -25,7 +25,8 @@ type Signer interface { // DigestSigner is an interface for private keys to sign digested COSE signatures. type DigestSigner interface { - Signer + // Algorithm returns the signing algorithm associated with the private key. + Algorithm() Algorithm // SignDigest signs message digest with the private key, possibly using // entropy from rand. diff --git a/verifier.go b/verifier.go index 5dfe4df..31d1f51 100644 --- a/verifier.go +++ b/verifier.go @@ -24,7 +24,8 @@ type Verifier interface { // DigestVerifier is an interface for public keys to verify digested COSE signatures. type DigestVerifier interface { - Verifier + // Algorithm returns the signing algorithm associated with the public key. + Algorithm() Algorithm // VerifyDigest verifies message digest with the public key, returning nil // for success. From 82a1648f4a359716c9ab21981c884d780893e9ba Mon Sep 17 00:00:00 2001 From: qmuntal Date: Tue, 18 Jul 2023 08:36:24 +0200 Subject: [PATCH 5/7] add digest test cases Signed-off-by: qmuntal --- ecdsa_test.go | 20 +++++++++++++++++++- ed25519_test.go | 9 +++++++++ rsa_test.go | 20 +++++++++++++++++++- 3 files changed, 47 insertions(+), 2 deletions(-) diff --git a/ecdsa_test.go b/ecdsa_test.go index 308ca6a..1cb2c12 100644 --- a/ecdsa_test.go +++ b/ecdsa_test.go @@ -219,7 +219,7 @@ func testSignVerify(t *testing.T, alg Algorithm, key crypto.Signer, isCryptoSign // sign / verify round trip // see also conformance_test.go for strict tests. - content := []byte("hello world") + content := []byte("hello world, مرحبا بالعالم") sig, err := signer.Sign(rand.Reader, content) if err != nil { t.Fatalf("Sign() error = %v", err) @@ -232,6 +232,24 @@ func testSignVerify(t *testing.T, alg Algorithm, key crypto.Signer, isCryptoSign if err := verifier.Verify(content, sig); err != nil { t.Fatalf("Verifier.Verify() error = %v", err) } + + // digested sign/verify round trip + dsigner, ok := signer.(DigestSigner) + if !ok { + t.Fatalf("signer is not a DigestSigner") + } + digest := sha256.Sum256(content) + dsig, err := dsigner.SignDigest(rand.Reader, digest[:]) + if err != nil { + t.Fatalf("SignDigest() error = %v", err) + } + dverifier, ok := verifier.(DigestVerifier) + if !ok { + t.Fatalf("verifier is not a DigestVerifier") + } + if err := dverifier.VerifyDigest(digest[:], dsig); err != nil { + t.Fatalf("VerifyDigest() error = %v", err) + } } type ecdsaBadCryptoSigner struct { diff --git a/ed25519_test.go b/ed25519_test.go index d23e65b..20bb555 100644 --- a/ed25519_test.go +++ b/ed25519_test.go @@ -47,6 +47,15 @@ func Test_ed25519Signer(t *testing.T) { if err := verifier.Verify(content, sig); err != nil { t.Fatalf("Verifier.Verify() error = %v", err) } + + _, ok := signer.(DigestSigner) + if ok { + t.Fatalf("signer shouldn't be a DigestSigner") + } + _, ok = verifier.(DigestVerifier) + if ok { + t.Fatalf("verifier shouldn't be a DigestVerifier") + } } func Test_ed25519Verifier_Verify_Success(t *testing.T) { diff --git a/rsa_test.go b/rsa_test.go index e859b78..803406b 100644 --- a/rsa_test.go +++ b/rsa_test.go @@ -36,7 +36,7 @@ func Test_rsaSigner(t *testing.T) { // sign / verify round trip // see also conformance_test.go for strict tests. - content := []byte("hello world") + content := []byte("hello world, مرحبا بالعالم") sig, err := signer.Sign(rand.Reader, content) if err != nil { t.Fatalf("Sign() error = %v", err) @@ -49,6 +49,24 @@ func Test_rsaSigner(t *testing.T) { if err := verifier.Verify(content, sig); err != nil { t.Fatalf("Verifier.Verify() error = %v", err) } + + // digested sign/verify round trip + dsigner, ok := signer.(DigestSigner) + if !ok { + t.Fatalf("signer is not a DigestSigner") + } + digest := sha256.Sum256(content) + dsig, err := dsigner.SignDigest(rand.Reader, digest[:]) + if err != nil { + t.Fatalf("SignDigest() error = %v", err) + } + dverifier, ok := verifier.(DigestVerifier) + if !ok { + t.Fatalf("verifier is not a DigestVerifier") + } + if err := dverifier.VerifyDigest(digest[:], dsig); err != nil { + t.Fatalf("VerifyDigest() error = %v", err) + } } func Test_rsaSigner_SignHashFailure(t *testing.T) { From c36e47e7a19d837928d63b30a8bc38bf9be56ea5 Mon Sep 17 00:00:00 2001 From: qmuntal Date: Wed, 19 Jul 2023 16:14:03 +0200 Subject: [PATCH 6/7] add DigestSigner example Signed-off-by: qmuntal --- example_test.go | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/example_test.go b/example_test.go index 07ea624..afdf128 100644 --- a/example_test.go +++ b/example_test.go @@ -4,6 +4,7 @@ import ( "crypto/ecdsa" "crypto/elliptic" "crypto/rand" + "crypto/sha512" _ "crypto/sha512" "fmt" @@ -202,3 +203,29 @@ func ExampleSign1Untagged() { // Output: // message signed } + +func ExampleDigestSigner() { + // create a signer + privateKey, err := ecdsa.GenerateKey(elliptic.P521(), rand.Reader) + if err != nil { + panic(err) + } + signer, err := cose.NewSigner(cose.AlgorithmES256, privateKey) + if err != nil { + panic(err) + } + digestSigner, ok := signer.(cose.DigestSigner) + if !ok { + panic("signer does not support digest signing") + } + + // hash payload outside go-cose. + payload := []byte("hello world") + digested := sha512.Sum512(payload) + sig, err := digestSigner.SignDigest(rand.Reader, digested[:]) + + fmt.Println("digest signed") + _ = sig // further process on sig + // Output: + // digest signed +} From 5548b0ad4d52cb8370dfcba2089cb38818e1ba4b Mon Sep 17 00:00:00 2001 From: qmuntal Date: Mon, 24 Jul 2023 12:46:34 +0200 Subject: [PATCH 7/7] point to examples Signed-off-by: qmuntal --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index aa01ca4..837fde9 100644 --- a/README.md +++ b/README.md @@ -137,7 +137,7 @@ See [example_test.go](./example_test.go) for more examples. Untagged COSE_Sign1 messages can be signed and verified as above, using `cose.UntaggedSign1Message` instead of `cose.Sign1Message`. -#### Signing and Verification of digested payloads +#### Signing and Verification of payload digest When `cose.NewSigner` is used with PS{256,384,512} or ES{256,384,512}, the returned signer can be casted to the `cose.DigestSigner` interface, whose `SignDigest` method signs an @@ -147,6 +147,8 @@ When `cose.NewVerifier` is used with PS{256,384,512} or ES{256,384,512}, the ret can be casted to the `cose.DigestVerifier` interface, whose `VerifyDigest` method verifies an already digested message. +Please refer to [example_test.go](./example_test.go) for the API usage. + ### About hashing `go-cose` does not import any hash package by its own to avoid linking unnecessary algorithms to the final binary.