From 3f542bc54714e52a0b99dc6a1cea5ce220d3fecb Mon Sep 17 00:00:00 2001 From: fulder Date: Wed, 19 Aug 2020 14:42:16 +0200 Subject: [PATCH 01/19] Add HS2019 algorithm to rsa --- rsa.go | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/rsa.go b/rsa.go index b92760f..fee8445 100644 --- a/rsa.go +++ b/rsa.go @@ -69,6 +69,31 @@ func (a rsa_sha256) Verify(key interface{}, data, sig []byte) error { return RSAVerify(k, crypto.SHA256, data, sig) } +// HS2019 implements RSA PSS signatures over a SHA512 digest +var HS2019 Algorithm = hs_2019{} + +type hs_2019 struct{} + +func (hs_2019) Name() string { + return "hs2019" +} + +func (a hs_2019) Sign(key interface{}, data []byte) ([]byte, error) { + k := toRSAPrivateKey(key) + if k == nil { + return nil, unsupportedAlgorithm(a) + } + return RSASign(k, crypto.SHA512, data) +} + +func (a hs_2019) Verify(key interface{}, data, sig []byte) error { + k := toRSAPublicKey(key) + if k == nil { + return unsupportedAlgorithm(a) + } + return RSAVerify(k, crypto.SH, data, sig) +} + // RSASign signs a digest of the data hashed using the provided hash func RSASign(key *rsa.PrivateKey, hash crypto.Hash, data []byte) ( signature []byte, err error) { From 1e370d72cfad3214de79b505de8b3775fbf3df00 Mon Sep 17 00:00:00 2001 From: fulder Date: Thu, 20 Aug 2020 12:28:27 +0200 Subject: [PATCH 02/19] Use new active algorithm in httpsig_test --- httpsig_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/httpsig_test.go b/httpsig_test.go index 7f7809c..aad1729 100644 --- a/httpsig_test.go +++ b/httpsig_test.go @@ -51,7 +51,7 @@ G6aFKaqQfOXKCyWoUiVknQJAXrlgySFci/2ueKlIE1QqIiLSZ8V8OlpFLRnb1pzI func TestDate(t *testing.T) { test := NewTest(t) - signer := NewRSASHA256Signer("Test", test.PrivateKey, []string{"date"}) + signer := NewHS2019Signer("Test", test.PrivateKey, []string{"date"}) verifier := NewVerifier(test) req := test.NewRequest() @@ -80,7 +80,7 @@ func TestRequestTargetAndHost(t *testing.T) { test := NewTest(t) headers := []string{"(request-target)", "host", "date"} - signer := NewRSASHA256Signer("Test", test.PrivateKey, headers) + signer := NewHS2019Signer("Test", test.PrivateKey, headers) verifier := NewVerifier(test) req := test.NewRequest() From 0148c7c5c797d7722b3fcb929a29698ebc8083e1 Mon Sep 17 00:00:00 2001 From: fulder Date: Thu, 20 Aug 2020 12:29:54 +0200 Subject: [PATCH 03/19] Add hs2019 to accepted algorithms --- rsa.go | 28 ++++++++++++++++++++++++++-- sign.go | 7 +++++++ verify.go | 9 ++++++++- 3 files changed, 41 insertions(+), 3 deletions(-) diff --git a/rsa.go b/rsa.go index fee8445..c17ab25 100644 --- a/rsa.go +++ b/rsa.go @@ -83,7 +83,7 @@ func (a hs_2019) Sign(key interface{}, data []byte) ([]byte, error) { if k == nil { return nil, unsupportedAlgorithm(a) } - return RSASign(k, crypto.SHA512, data) + return RSASignPSS(k, crypto.SHA512, data) } func (a hs_2019) Verify(key interface{}, data, sig []byte) error { @@ -91,7 +91,7 @@ func (a hs_2019) Verify(key interface{}, data, sig []byte) error { if k == nil { return unsupportedAlgorithm(a) } - return RSAVerify(k, crypto.SH, data, sig) + return RSAVerifyPSS(k, crypto.SHA512, data, sig) } // RSASign signs a digest of the data hashed using the provided hash @@ -115,3 +115,27 @@ func RSAVerify(key *rsa.PublicKey, hash crypto.Hash, data, sig []byte) ( } return rsa.VerifyPKCS1v15(key, hash, h.Sum(nil), sig) } + +// RSASignPSS signs a digest of the data hashed using the provided hash +func RSASignPSS(key *rsa.PrivateKey, hash crypto.Hash, data []byte) ( + signature []byte, err error) { + + h := hash.New() + if _, err := h.Write(data); err != nil { + return nil, err + } + //TODO: Derive signing algorithm from keyID? + return rsa.SignPSS(Rand, key, hash, h.Sum(nil), nil) +} + +// RSAVerifyPSS verifies a signed digest of the data hashed using the provided hash +func RSAVerifyPSS(key *rsa.PublicKey, hash crypto.Hash, data, sig []byte) ( + err error) { + + h := hash.New() + if _, err := h.Write(data); err != nil { + return err + } + //TODO: Derive signing algorithm from keyID? + return rsa.VerifyPSS(key, hash, h.Sum(nil), sig, nil) +} diff --git a/sign.go b/sign.go index 745ee69..e299a1b 100644 --- a/sign.go +++ b/sign.go @@ -73,6 +73,13 @@ func NewHMACSHA256Signer(id string, key []byte, headers []string) ( return NewSigner(id, key, HMACSHA256, headers) } +// NewHS2019Signer contructs a signer with the specified key id, hmac key, +// and headers to sign. +func NewHS2019Signer(id string, key *rsa.PrivateKey, headers []string) ( + signer *Signer) { + return NewSigner(id, key, HS2019, headers) +} + // Sign signs an http request and adds the signature to the authorization header func (r *Signer) Sign(req *http.Request) error { params, err := signRequest(r.id, r.key, r.algo, r.headers, req) diff --git a/verify.go b/verify.go index d9df7ab..b0bb280 100644 --- a/verify.go +++ b/verify.go @@ -112,6 +112,13 @@ header_check: params.Algorithm, params.KeyId) } return HMACVerify(hmac_key, crypto.SHA256, sig_data, params.Signature) + case "hs2019": + rsa_pubkey := toRSAPublicKey(key) + if rsa_pubkey == nil { + return fmt.Errorf("algorithm %q is not supported by key %q", + params.Algorithm, params.KeyId) + } + return RSAVerifyPSS(rsa_pubkey, crypto.SHA512, sig_data, params.Signature) default: return fmt.Errorf("unsupported algorithm %q", params.Algorithm) } @@ -182,7 +189,7 @@ func getParams(req *http.Request, header, prefix string) *Params { func parseAlgorithm(s string) (algorithm string, ok bool) { s = strings.TrimSpace(s) switch s { - case "rsa-sha1", "rsa-sha256", "hmac-sha256": + case "rsa-sha1", "rsa-sha256", "hmac-sha256", "hs2019": return s, true } return "", false From f3fce1b35d37a7f08adefe750f0820b838077ab1 Mon Sep 17 00:00:00 2001 From: fulder Date: Thu, 20 Aug 2020 12:30:10 +0200 Subject: [PATCH 04/19] Print deprecation message for other algorithms than hs2019 --- sign.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/sign.go b/sign.go index e299a1b..76655c0 100644 --- a/sign.go +++ b/sign.go @@ -41,6 +41,10 @@ func NewSigner(id string, key interface{}, algo Algorithm, headers []string) ( algo: algo, } + if algo.Name() != "hs2019" { + fmt.Printf("Algorithm %s is deprecated, please update to 'hs2019'", algo.Name()) + } + // copy the headers slice, lowercasing as necessary if len(headers) == 0 { headers = []string{"(request-target)", "date"} From fa5b9a8a0715ff632c09f21dfcb54d84e3bad6ba Mon Sep 17 00:00:00 2001 From: fulder Date: Thu, 20 Aug 2020 13:10:25 +0200 Subject: [PATCH 05/19] Move hs2019 to own file and rename to hs2019_pss --- httpsig_test.go | 4 ++-- rsa.go | 47 ----------------------------------------------- sign.go | 6 +++--- verify.go | 4 ++-- 4 files changed, 7 insertions(+), 54 deletions(-) diff --git a/httpsig_test.go b/httpsig_test.go index aad1729..a0ca1d1 100644 --- a/httpsig_test.go +++ b/httpsig_test.go @@ -51,7 +51,7 @@ G6aFKaqQfOXKCyWoUiVknQJAXrlgySFci/2ueKlIE1QqIiLSZ8V8OlpFLRnb1pzI func TestDate(t *testing.T) { test := NewTest(t) - signer := NewHS2019Signer("Test", test.PrivateKey, []string{"date"}) + signer := NewHS2019PSSSigner("Test", test.PrivateKey, []string{"date"}) verifier := NewVerifier(test) req := test.NewRequest() @@ -80,7 +80,7 @@ func TestRequestTargetAndHost(t *testing.T) { test := NewTest(t) headers := []string{"(request-target)", "host", "date"} - signer := NewHS2019Signer("Test", test.PrivateKey, headers) + signer := NewHS2019PSSSigner("Test", test.PrivateKey, headers) verifier := NewVerifier(test) req := test.NewRequest() diff --git a/rsa.go b/rsa.go index c17ab25..3e29562 100644 --- a/rsa.go +++ b/rsa.go @@ -69,30 +69,7 @@ func (a rsa_sha256) Verify(key interface{}, data, sig []byte) error { return RSAVerify(k, crypto.SHA256, data, sig) } -// HS2019 implements RSA PSS signatures over a SHA512 digest -var HS2019 Algorithm = hs_2019{} -type hs_2019 struct{} - -func (hs_2019) Name() string { - return "hs2019" -} - -func (a hs_2019) Sign(key interface{}, data []byte) ([]byte, error) { - k := toRSAPrivateKey(key) - if k == nil { - return nil, unsupportedAlgorithm(a) - } - return RSASignPSS(k, crypto.SHA512, data) -} - -func (a hs_2019) Verify(key interface{}, data, sig []byte) error { - k := toRSAPublicKey(key) - if k == nil { - return unsupportedAlgorithm(a) - } - return RSAVerifyPSS(k, crypto.SHA512, data, sig) -} // RSASign signs a digest of the data hashed using the provided hash func RSASign(key *rsa.PrivateKey, hash crypto.Hash, data []byte) ( @@ -115,27 +92,3 @@ func RSAVerify(key *rsa.PublicKey, hash crypto.Hash, data, sig []byte) ( } return rsa.VerifyPKCS1v15(key, hash, h.Sum(nil), sig) } - -// RSASignPSS signs a digest of the data hashed using the provided hash -func RSASignPSS(key *rsa.PrivateKey, hash crypto.Hash, data []byte) ( - signature []byte, err error) { - - h := hash.New() - if _, err := h.Write(data); err != nil { - return nil, err - } - //TODO: Derive signing algorithm from keyID? - return rsa.SignPSS(Rand, key, hash, h.Sum(nil), nil) -} - -// RSAVerifyPSS verifies a signed digest of the data hashed using the provided hash -func RSAVerifyPSS(key *rsa.PublicKey, hash crypto.Hash, data, sig []byte) ( - err error) { - - h := hash.New() - if _, err := h.Write(data); err != nil { - return err - } - //TODO: Derive signing algorithm from keyID? - return rsa.VerifyPSS(key, hash, h.Sum(nil), sig, nil) -} diff --git a/sign.go b/sign.go index 76655c0..9c3844c 100644 --- a/sign.go +++ b/sign.go @@ -41,7 +41,7 @@ func NewSigner(id string, key interface{}, algo Algorithm, headers []string) ( algo: algo, } - if algo.Name() != "hs2019" { + if !strings.Contains(algo.Name(), "hs2019") { fmt.Printf("Algorithm %s is deprecated, please update to 'hs2019'", algo.Name()) } @@ -79,9 +79,9 @@ func NewHMACSHA256Signer(id string, key []byte, headers []string) ( // NewHS2019Signer contructs a signer with the specified key id, hmac key, // and headers to sign. -func NewHS2019Signer(id string, key *rsa.PrivateKey, headers []string) ( +func NewHS2019PSSSigner(id string, key *rsa.PrivateKey, headers []string) ( signer *Signer) { - return NewSigner(id, key, HS2019, headers) + return NewSigner(id, key, HS2019_PSS, headers) } // Sign signs an http request and adds the signature to the authorization header diff --git a/verify.go b/verify.go index b0bb280..f46b3a3 100644 --- a/verify.go +++ b/verify.go @@ -112,7 +112,7 @@ header_check: params.Algorithm, params.KeyId) } return HMACVerify(hmac_key, crypto.SHA256, sig_data, params.Signature) - case "hs2019": + case "hs2019_pss": rsa_pubkey := toRSAPublicKey(key) if rsa_pubkey == nil { return fmt.Errorf("algorithm %q is not supported by key %q", @@ -189,7 +189,7 @@ func getParams(req *http.Request, header, prefix string) *Params { func parseAlgorithm(s string) (algorithm string, ok bool) { s = strings.TrimSpace(s) switch s { - case "rsa-sha1", "rsa-sha256", "hmac-sha256", "hs2019": + case "rsa-sha1", "rsa-sha256", "hmac-sha256", "hs2019_pss": return s, true } return "", false From 11c01d5a94d9f3d12519edaf91df60debc25bff4 Mon Sep 17 00:00:00 2001 From: fulder Date: Thu, 20 Aug 2020 13:18:53 +0200 Subject: [PATCH 06/19] Add new hs2019 file --- hs2019.go | 54 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 hs2019.go diff --git a/hs2019.go b/hs2019.go new file mode 100644 index 0000000..a68dec0 --- /dev/null +++ b/hs2019.go @@ -0,0 +1,54 @@ +package httpsig + +import ( + "crypto" + "crypto/rsa" +) + + +// HS2019 implements PSS signatures over a SHA512 digest +var HS2019_PSS Algorithm = hs_2019_pss{} + +type hs_2019_pss struct{} + +func (hs_2019_pss) Name() string { + return "hs2019_pss" +} + +func (a hs_2019_pss) Sign(key interface{}, data []byte) ([]byte, error) { + k := toRSAPrivateKey(key) + if k == nil { + return nil, unsupportedAlgorithm(a) + } + return RSASignPSS(k, crypto.SHA512, data) +} + +func (a hs_2019_pss) Verify(key interface{}, data, sig []byte) error { + k := toRSAPublicKey(key) + if k == nil { + return unsupportedAlgorithm(a) + } + return RSAVerifyPSS(k, crypto.SHA512, data, sig) +} + +// RSASignPSS signs a digest of the data hashed using the provided hash +func RSASignPSS(key *rsa.PrivateKey, hash crypto.Hash, data []byte) ( + signature []byte, err error) { + + h := hash.New() + if _, err := h.Write(data); err != nil { + return nil, err + } + return rsa.SignPSS(Rand, key, hash, h.Sum(nil), nil) +} + +// RSAVerifyPSS verifies a signed digest of the data hashed using the provided hash +func RSAVerifyPSS(key *rsa.PublicKey, hash crypto.Hash, data, sig []byte) ( + err error) { + + h := hash.New() + if _, err := h.Write(data); err != nil { + return err + } + return rsa.VerifyPSS(key, hash, h.Sum(nil), sig, nil) +} \ No newline at end of file From a99a4476f57fbc251f2c66f41a03f027ceb76b76 Mon Sep 17 00:00:00 2001 From: fulder Date: Thu, 20 Aug 2020 13:30:23 +0200 Subject: [PATCH 07/19] Rename hs_2019_pss struct to hs2019_pss --- hs2019.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/hs2019.go b/hs2019.go index a68dec0..07d88fb 100644 --- a/hs2019.go +++ b/hs2019.go @@ -7,15 +7,15 @@ import ( // HS2019 implements PSS signatures over a SHA512 digest -var HS2019_PSS Algorithm = hs_2019_pss{} +var HS2019_PSS Algorithm = hs2019_pss{} -type hs_2019_pss struct{} +type hs2019_pss struct{} -func (hs_2019_pss) Name() string { +func (hs2019_pss) Name() string { return "hs2019_pss" } -func (a hs_2019_pss) Sign(key interface{}, data []byte) ([]byte, error) { +func (a hs2019_pss) Sign(key interface{}, data []byte) ([]byte, error) { k := toRSAPrivateKey(key) if k == nil { return nil, unsupportedAlgorithm(a) @@ -23,7 +23,7 @@ func (a hs_2019_pss) Sign(key interface{}, data []byte) ([]byte, error) { return RSASignPSS(k, crypto.SHA512, data) } -func (a hs_2019_pss) Verify(key interface{}, data, sig []byte) error { +func (a hs2019_pss) Verify(key interface{}, data, sig []byte) error { k := toRSAPublicKey(key) if k == nil { return unsupportedAlgorithm(a) From 3c22d66d5cc1ecad98541f3a5478a5e614a12958 Mon Sep 17 00:00:00 2001 From: fulder Date: Thu, 20 Aug 2020 13:31:48 +0200 Subject: [PATCH 08/19] Remove empty spaces --- rsa.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/rsa.go b/rsa.go index 3e29562..b92760f 100644 --- a/rsa.go +++ b/rsa.go @@ -69,8 +69,6 @@ func (a rsa_sha256) Verify(key interface{}, data, sig []byte) error { return RSAVerify(k, crypto.SHA256, data, sig) } - - // RSASign signs a digest of the data hashed using the provided hash func RSASign(key *rsa.PrivateKey, hash crypto.Hash, data []byte) ( signature []byte, err error) { From b13e78b10aedbd415e30638f45794d80a5023ac3 Mon Sep 17 00:00:00 2001 From: fulder Date: Thu, 20 Aug 2020 13:32:39 +0200 Subject: [PATCH 09/19] Fix comment for NewHS2019PSSSigner func --- sign.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sign.go b/sign.go index 9c3844c..38f2711 100644 --- a/sign.go +++ b/sign.go @@ -77,7 +77,7 @@ func NewHMACSHA256Signer(id string, key []byte, headers []string) ( return NewSigner(id, key, HMACSHA256, headers) } -// NewHS2019Signer contructs a signer with the specified key id, hmac key, +// NewHS2019PSSSigner constructs a signer with the specified key id, hmac key, // and headers to sign. func NewHS2019PSSSigner(id string, key *rsa.PrivateKey, headers []string) ( signer *Signer) { From 574864966cd667074e0af282f37e6e8e13e5e1ae Mon Sep 17 00:00:00 2001 From: fulder Date: Thu, 20 Aug 2020 13:33:45 +0200 Subject: [PATCH 10/19] Update README with hs2019 --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index f58524b..7e0073d 100644 --- a/README.md +++ b/README.md @@ -118,6 +118,9 @@ If signature validation fails, a `401` is returned along with a ## Supported algorithms +- hs2019 (using PSS) + +### Deprecated algorithms - rsa-sha1 (using PKCS1v15) - rsa-sha256 (using PKCS1v15) - hmac-sha256 From df970042a4ac6eb5a9ed81928ac689b1907f31c8 Mon Sep 17 00:00:00 2001 From: fulder Date: Thu, 20 Aug 2020 15:11:30 +0200 Subject: [PATCH 11/19] Add optional algorithm to verifier struct --- hs2019.go | 2 +- httpsig_test.go | 28 ++++++++++++++++++++++++++++ verify.go | 28 +++++++++++++++++----------- 3 files changed, 46 insertions(+), 12 deletions(-) diff --git a/hs2019.go b/hs2019.go index 07d88fb..6ae76c0 100644 --- a/hs2019.go +++ b/hs2019.go @@ -12,7 +12,7 @@ var HS2019_PSS Algorithm = hs2019_pss{} type hs2019_pss struct{} func (hs2019_pss) Name() string { - return "hs2019_pss" + return "hs2019" } func (a hs2019_pss) Sign(key interface{}, data []byte) ([]byte, error) { diff --git a/httpsig_test.go b/httpsig_test.go index a0ca1d1..d9ac93e 100644 --- a/httpsig_test.go +++ b/httpsig_test.go @@ -53,6 +53,7 @@ func TestDate(t *testing.T) { signer := NewHS2019PSSSigner("Test", test.PrivateKey, []string{"date"}) verifier := NewVerifier(test) + verifier.algorithm = HS2019_PSS req := test.NewRequest() test.AssertNoError(signer.Sign(req)) @@ -82,6 +83,7 @@ func TestRequestTargetAndHost(t *testing.T) { headers := []string{"(request-target)", "host", "date"} signer := NewHS2019PSSSigner("Test", test.PrivateKey, headers) verifier := NewVerifier(test) + verifier.algorithm = HS2019_PSS req := test.NewRequest() test.AssertNoError(signer.Sign(req)) @@ -116,6 +118,32 @@ func TestRequestTargetAndHost(t *testing.T) { req.Host = orig_host } +func TestAlgorithmMismatch(t *testing.T) { + test := NewTest(t) + + signer := NewHS2019PSSSigner("Test", test.PrivateKey, nil) + verifier := NewVerifier(test) + verifier.algorithm = RSASHA256 + + req := test.NewRequest() + test.AssertNoError(signer.Sign(req)) + + err := verifier.Verify(req) + test.AssertAnyError(err) + test.AssertStringEqual(err.Error(),"algorithm header mismatch. Signature header value: hs2019, derived value: rsa-sha256") +} + +func TestDeprecatedAlgorithm(t *testing.T) { + test := NewTest(t) + + signer := NewRSASHA256Signer("Test", test.PrivateKey, nil) + verifier := NewVerifier(test) + + req := test.NewRequest() + test.AssertNoError(signer.Sign(req)) + test.AssertNoError(verifier.Verify(req)) +} + ///////////////////////////////////////////////////////////////////////////// // Helpers ///////////////////////////////////////////////////////////////////////////// diff --git a/verify.go b/verify.go index f46b3a3..a18c0f7 100644 --- a/verify.go +++ b/verify.go @@ -26,6 +26,7 @@ import ( type Verifier struct { key_getter KeyGetter required_headers []string + algorithm Algorithm } func NewVerifier(key_getter KeyGetter) *Verifier { @@ -60,9 +61,6 @@ func (v *Verifier) Verify(req *http.Request) error { if params.KeyId == "" { return fmt.Errorf("keyId is required") } - if params.Algorithm == "" { - return fmt.Errorf("algorithm is required") - } if len(params.Signature) == 0 { return fmt.Errorf("signature is required") } @@ -90,6 +88,18 @@ header_check: return fmt.Errorf("no key with id %q", params.KeyId) } + if params.Algorithm != "hs2019" { + fmt.Printf("algorithm %s is deprecated, please update to 'hs2019'", params.Algorithm) + } + + if params.Algorithm == "hs2019" && v.algorithm == nil { + return fmt.Errorf("missing required verifier algorithm") + } + + if v.algorithm != nil && params.Algorithm != v.algorithm.Name() { + return fmt.Errorf("algorithm header mismatch. Signature header value: %s, derived value: %s", params.Algorithm, v.algorithm.Name()) + } + switch params.Algorithm { case "rsa-sha1": rsa_pubkey := toRSAPublicKey(key) @@ -112,13 +122,8 @@ header_check: params.Algorithm, params.KeyId) } return HMACVerify(hmac_key, crypto.SHA256, sig_data, params.Signature) - case "hs2019_pss": - rsa_pubkey := toRSAPublicKey(key) - if rsa_pubkey == nil { - return fmt.Errorf("algorithm %q is not supported by key %q", - params.Algorithm, params.KeyId) - } - return RSAVerifyPSS(rsa_pubkey, crypto.SHA512, sig_data, params.Signature) + case "hs2019": + return v.algorithm.Verify(key, sig_data, params.Signature) default: return fmt.Errorf("unsupported algorithm %q", params.Algorithm) } @@ -188,8 +193,9 @@ func getParams(req *http.Request, header, prefix string) *Params { // parseAlgorithm parses recognized algorithm values func parseAlgorithm(s string) (algorithm string, ok bool) { s = strings.TrimSpace(s) + switch s { - case "rsa-sha1", "rsa-sha256", "hmac-sha256", "hs2019_pss": + case "rsa-sha1", "rsa-sha256", "hmac-sha256", "hs2019": return s, true } return "", false From 6e7458ec520acdcd87e5b9c3fc4e360c65537d7e Mon Sep 17 00:00:00 2001 From: fulder Date: Thu, 20 Aug 2020 15:30:24 +0200 Subject: [PATCH 12/19] Start updating README with newest active algorithm --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 7e0073d..bd79165 100644 --- a/README.md +++ b/README.md @@ -18,11 +18,11 @@ Signing requests is done by constructing a new `Signer`. The key id, key, algorithm, and what headers to sign are required. For example to construct a `Signer` with key id `"foo"`, using an RSA private -key, for the rsa-sha256 algorithm, with the default header set, you can do: +key, for the hs2019 algorithm and RSA key (PSS), with the default header set, you can do: ``` var key *rsa.PrivateKey = ... -signer := httpsig.NewSigner("foo", key, httpsig.RSASHA256, nil) +signer := httpsig.NewSigner("foo", key, httpsig.HS1029_PSS, nil) ``` There are helper functions for specific algorithms that are less verbose and @@ -31,7 +31,7 @@ because the type required for the algorithm is known). ``` var key *rsa.PrivateKey = ... -signer := httpsig.NewRSASHA256Signer("foo", key, nil) +signer := httpsig.NewHS2019Signer("foo", key, nil) ``` By default, if no headers are passed to `NewSigner` (or the helpers), the @@ -49,7 +49,7 @@ fmt.Println("AUTHORIZATION:", req.Header.Get('Authorization')) ... -AUTHORIZATION: Signature: keyId="foo",algorithm="sha-256",signature="..." +AUTHORIZATION: Signature: keyId="foo",algorithm="hs2019",signature="..." ``` From 68987618c83cfe2ee0dd07a9b727a4001885785b Mon Sep 17 00:00:00 2001 From: fulder Date: Thu, 20 Aug 2020 15:30:41 +0200 Subject: [PATCH 13/19] Move key algorithm to KeyStore interface --- httpsig.go | 1 + keystore.go | 9 +++++++++ verify.go | 15 +++++++-------- 3 files changed, 17 insertions(+), 8 deletions(-) diff --git a/httpsig.go b/httpsig.go index 36d982f..8d69b50 100644 --- a/httpsig.go +++ b/httpsig.go @@ -32,6 +32,7 @@ type Algorithm interface { // Other types will treated as if no key was returned. type KeyGetter interface { GetKey(id string) interface{} + GetAlgorithm(id string) Algorithm } // KeyGetterFunc is a convenience type for implementing a KeyGetter with a diff --git a/keystore.go b/keystore.go index efead79..95e8c7f 100644 --- a/keystore.go +++ b/keystore.go @@ -16,6 +16,7 @@ package httpsig type MemoryKeyStore struct { keys map[string]interface{} + keyAlgorithms map[string]Algorithm } func NewMemoryKeyStore() *MemoryKeyStore { @@ -31,3 +32,11 @@ func (m *MemoryKeyStore) GetKey(id string) interface{} { func (m *MemoryKeyStore) SetKey(id string, key interface{}) { m.keys[id] = key } + +func (m *MemoryKeyStore) SetKeyAlgorithm(id string, algorithm Algorithm) { + m.keyAlgorithms[id] = algorithm +} + +func (m *MemoryKeyStore) GetKeyAlgorithm(id string) Algorithm { + return m.keyAlgorithms[id] +} diff --git a/verify.go b/verify.go index a18c0f7..dfd2d51 100644 --- a/verify.go +++ b/verify.go @@ -26,7 +26,6 @@ import ( type Verifier struct { key_getter KeyGetter required_headers []string - algorithm Algorithm } func NewVerifier(key_getter KeyGetter) *Verifier { @@ -87,17 +86,17 @@ header_check: if key == nil { return fmt.Errorf("no key with id %q", params.KeyId) } + keyAlgorithm := v.key_getter.GetAlgorithm(params.KeyId) + if params.Algorithm == "hs2019" && keyAlgorithm == nil { + return fmt.Errorf("no key algorithm with id %q", params.KeyId) + } if params.Algorithm != "hs2019" { fmt.Printf("algorithm %s is deprecated, please update to 'hs2019'", params.Algorithm) } - if params.Algorithm == "hs2019" && v.algorithm == nil { - return fmt.Errorf("missing required verifier algorithm") - } - - if v.algorithm != nil && params.Algorithm != v.algorithm.Name() { - return fmt.Errorf("algorithm header mismatch. Signature header value: %s, derived value: %s", params.Algorithm, v.algorithm.Name()) + if keyAlgorithm != nil && params.Algorithm != keyAlgorithm.Name() { + return fmt.Errorf("algorithm header mismatch. Signature header value: %s, derived value: %s", params.Algorithm, keyAlgorithm.Name()) } switch params.Algorithm { @@ -123,7 +122,7 @@ header_check: } return HMACVerify(hmac_key, crypto.SHA256, sig_data, params.Signature) case "hs2019": - return v.algorithm.Verify(key, sig_data, params.Signature) + return keyAlgorithm.Verify(key, sig_data, params.Signature) default: return fmt.Errorf("unsupported algorithm %q", params.Algorithm) } From ad5d26a36c343dee4291ef21bea47c84d0d71f25 Mon Sep 17 00:00:00 2001 From: fulder Date: Thu, 20 Aug 2020 15:33:37 +0200 Subject: [PATCH 14/19] Add README info about the new SetKeyAlgorithm function --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index bd79165..8f024fd 100644 --- a/README.md +++ b/README.md @@ -98,6 +98,12 @@ var hmac_key []byte = ... keystore.SetKey("foo", hmac_key) ``` +In order to support hs2019 the keystore also includes a store for the key +algorithm. Key algorithms can be added using the SetKeyAlgorithm method: +``` +keystore.SetKeyAlgorithm("foo", algorithm.HS2019_PSS) +``` + ## Handler A convenience function is provided that wraps an `http.Handler` and verifies From 5bf966e16035a501d78cca06c3b39073f3c2c49c Mon Sep 17 00:00:00 2001 From: fulder Date: Thu, 20 Aug 2020 16:03:11 +0200 Subject: [PATCH 15/19] Move key algorithm to keystore --- handler_test.go | 2 +- httpsig.go | 2 +- httpsig_test.go | 14 +++++++------- keystore.go | 1 + verify.go | 2 +- 5 files changed, 11 insertions(+), 10 deletions(-) diff --git a/handler_test.go b/handler_test.go index 991b8b8..7c80d25 100644 --- a/handler_test.go +++ b/handler_test.go @@ -136,7 +136,7 @@ func TestHandlerAcceptsSignedRequest(t *testing.T) { req, err := http.NewRequest("GET", server.URL, nil) test.AssertNoError(err) - s := NewRSASHA256Signer("Test", test.PrivateKey, v.RequiredHeaders()) + s := NewHS2019PSSSigner("Test", test.PrivateKey, v.RequiredHeaders()) test.AssertNoError(s.Sign(req)) resp, err := http.DefaultClient.Do(req) diff --git a/httpsig.go b/httpsig.go index 8d69b50..60fe8af 100644 --- a/httpsig.go +++ b/httpsig.go @@ -32,7 +32,7 @@ type Algorithm interface { // Other types will treated as if no key was returned. type KeyGetter interface { GetKey(id string) interface{} - GetAlgorithm(id string) Algorithm + GetKeyAlgorithm(id string) Algorithm } // KeyGetterFunc is a convenience type for implementing a KeyGetter with a diff --git a/httpsig_test.go b/httpsig_test.go index d9ac93e..8344965 100644 --- a/httpsig_test.go +++ b/httpsig_test.go @@ -53,7 +53,6 @@ func TestDate(t *testing.T) { signer := NewHS2019PSSSigner("Test", test.PrivateKey, []string{"date"}) verifier := NewVerifier(test) - verifier.algorithm = HS2019_PSS req := test.NewRequest() test.AssertNoError(signer.Sign(req)) @@ -83,7 +82,6 @@ func TestRequestTargetAndHost(t *testing.T) { headers := []string{"(request-target)", "host", "date"} signer := NewHS2019PSSSigner("Test", test.PrivateKey, headers) verifier := NewVerifier(test) - verifier.algorithm = HS2019_PSS req := test.NewRequest() test.AssertNoError(signer.Sign(req)) @@ -121,22 +119,21 @@ func TestRequestTargetAndHost(t *testing.T) { func TestAlgorithmMismatch(t *testing.T) { test := NewTest(t) - signer := NewHS2019PSSSigner("Test", test.PrivateKey, nil) + signer := NewRSASHA256Signer("Test", test.PrivateKey, nil) verifier := NewVerifier(test) - verifier.algorithm = RSASHA256 req := test.NewRequest() test.AssertNoError(signer.Sign(req)) err := verifier.Verify(req) test.AssertAnyError(err) - test.AssertStringEqual(err.Error(),"algorithm header mismatch. Signature header value: hs2019, derived value: rsa-sha256") + test.AssertStringEqual(err.Error(),"algorithm header mismatch. Signature header value: rsa-sha256, derived value: hs2019") } func TestDeprecatedAlgorithm(t *testing.T) { test := NewTest(t) - signer := NewRSASHA256Signer("Test", test.PrivateKey, nil) + signer := NewRSASHA256Signer("Test_SHA256", test.PrivateKey, nil) verifier := NewVerifier(test) req := test.NewRequest() @@ -180,6 +177,9 @@ func NewTest(tb testing.TB) *Test { keystore := NewMemoryKeyStore() keystore.SetKey("Test", key) + keystore.SetKeyAlgorithm("Test", HS2019_PSS) + + keystore.SetKey("Test_SHA256", key) return &Test{ tb: tb, @@ -195,7 +195,7 @@ func (t *Test) NewRequest() *http.Request { req.Header.Set("Date", "Thu, 05 Jan 2014 21:31:40 GMT") req.Header.Set("Content-Type", "application/json") req.Header.Set("Digest", - "SHA-256=X48E9qOokqqrvdts8nOJRJN3OWDUoyWxBf7kbu9DBPE=") + "SHA-512=X48E9qOokqqrvdts8nOJRJN3OWDUoyWxBf7kbu9DBPE=") return req } diff --git a/keystore.go b/keystore.go index 95e8c7f..406326c 100644 --- a/keystore.go +++ b/keystore.go @@ -22,6 +22,7 @@ type MemoryKeyStore struct { func NewMemoryKeyStore() *MemoryKeyStore { return &MemoryKeyStore{ keys: make(map[string]interface{}), + keyAlgorithms: make(map[string]Algorithm), } } diff --git a/verify.go b/verify.go index dfd2d51..392d842 100644 --- a/verify.go +++ b/verify.go @@ -86,7 +86,7 @@ header_check: if key == nil { return fmt.Errorf("no key with id %q", params.KeyId) } - keyAlgorithm := v.key_getter.GetAlgorithm(params.KeyId) + keyAlgorithm := v.key_getter.GetKeyAlgorithm(params.KeyId) if params.Algorithm == "hs2019" && keyAlgorithm == nil { return fmt.Errorf("no key algorithm with id %q", params.KeyId) } From 75381078c9ecd442bffb17fea4d425c39770eb12 Mon Sep 17 00:00:00 2001 From: fulder Date: Thu, 20 Aug 2020 16:33:11 +0200 Subject: [PATCH 16/19] Correct new signer func name in README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 8f024fd..652711c 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ because the type required for the algorithm is known). ``` var key *rsa.PrivateKey = ... -signer := httpsig.NewHS2019Signer("foo", key, nil) +signer := httpsig.NewHS2019PSSSigner("foo", key, nil) ``` By default, if no headers are passed to `NewSigner` (or the helpers), the From 52b766fd5efccc063608ef482d5c9d34f2055bf3 Mon Sep 17 00:00:00 2001 From: fulder Date: Fri, 28 Aug 2020 12:43:55 +0200 Subject: [PATCH 17/19] Change salt length to eqaul hash --- hs2019.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/hs2019.go b/hs2019.go index 6ae76c0..30eac3c 100644 --- a/hs2019.go +++ b/hs2019.go @@ -39,7 +39,8 @@ func RSASignPSS(key *rsa.PrivateKey, hash crypto.Hash, data []byte) ( if _, err := h.Write(data); err != nil { return nil, err } - return rsa.SignPSS(Rand, key, hash, h.Sum(nil), nil) + opt := &rsa.PSSOptions{SaltLength: rsa.PSSSaltLengthEqualsHash} + return rsa.SignPSS(Rand, key, hash, h.Sum(nil), opt) } // RSAVerifyPSS verifies a signed digest of the data hashed using the provided hash @@ -50,5 +51,6 @@ func RSAVerifyPSS(key *rsa.PublicKey, hash crypto.Hash, data, sig []byte) ( if _, err := h.Write(data); err != nil { return err } - return rsa.VerifyPSS(key, hash, h.Sum(nil), sig, nil) + opt := &rsa.PSSOptions{SaltLength: rsa.PSSSaltLengthEqualsHash} + return rsa.VerifyPSS(key, hash, h.Sum(nil), sig, opt) } \ No newline at end of file From 3ecf717956b9bf7c810b7db962edce5e2de8017e Mon Sep 17 00:00:00 2001 From: fulder Date: Fri, 28 Aug 2020 13:38:52 +0200 Subject: [PATCH 18/19] Add configurable salt length to hs2019_pss struct --- hs2019.go | 45 +++++++++++++++++++++------------------------ 1 file changed, 21 insertions(+), 24 deletions(-) diff --git a/hs2019.go b/hs2019.go index 30eac3c..e673e40 100644 --- a/hs2019.go +++ b/hs2019.go @@ -5,11 +5,20 @@ import ( "crypto/rsa" ) - // HS2019 implements PSS signatures over a SHA512 digest var HS2019_PSS Algorithm = hs2019_pss{} -type hs2019_pss struct{} +type hs2019_pss struct { + saltLength int + hash crypto.Hash +} + +func (hs2019_pss) HS2019_PSS(saltLenght int) *hs2019_pss { + return &hs2019_pss{ + saltLength: saltLenght, + hash: crypto.SHA512, + } +} func (hs2019_pss) Name() string { return "hs2019" @@ -20,7 +29,13 @@ func (a hs2019_pss) Sign(key interface{}, data []byte) ([]byte, error) { if k == nil { return nil, unsupportedAlgorithm(a) } - return RSASignPSS(k, crypto.SHA512, data) + + h := a.hash.New() + if _, err := h.Write(data); err != nil { + return nil, err + } + opt := &rsa.PSSOptions{SaltLength: a.saltLength} + return rsa.SignPSS(Rand, k, a.hash, h.Sum(nil), opt) } func (a hs2019_pss) Verify(key interface{}, data, sig []byte) error { @@ -28,29 +43,11 @@ func (a hs2019_pss) Verify(key interface{}, data, sig []byte) error { if k == nil { return unsupportedAlgorithm(a) } - return RSAVerifyPSS(k, crypto.SHA512, data, sig) -} -// RSASignPSS signs a digest of the data hashed using the provided hash -func RSASignPSS(key *rsa.PrivateKey, hash crypto.Hash, data []byte) ( - signature []byte, err error) { - - h := hash.New() - if _, err := h.Write(data); err != nil { - return nil, err - } - opt := &rsa.PSSOptions{SaltLength: rsa.PSSSaltLengthEqualsHash} - return rsa.SignPSS(Rand, key, hash, h.Sum(nil), opt) -} - -// RSAVerifyPSS verifies a signed digest of the data hashed using the provided hash -func RSAVerifyPSS(key *rsa.PublicKey, hash crypto.Hash, data, sig []byte) ( - err error) { - - h := hash.New() + h := a.hash.New() if _, err := h.Write(data); err != nil { return err } opt := &rsa.PSSOptions{SaltLength: rsa.PSSSaltLengthEqualsHash} - return rsa.VerifyPSS(key, hash, h.Sum(nil), sig, opt) -} \ No newline at end of file + return rsa.VerifyPSS(k, a.hash, h.Sum(nil), sig, opt) +} From a945e1554c97005ba17e319a7f291eef5656c09a Mon Sep 17 00:00:00 2001 From: fulder Date: Fri, 28 Aug 2020 13:51:30 +0200 Subject: [PATCH 19/19] Set salt length in helper functions, readme and tests --- README.md | 4 ++-- handler_test.go | 3 ++- hs2019.go | 7 ++----- httpsig_test.go | 6 +++--- sign.go | 4 ++-- 5 files changed, 11 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 652711c..7bf748d 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ because the type required for the algorithm is known). ``` var key *rsa.PrivateKey = ... -signer := httpsig.NewHS2019PSSSigner("foo", key, nil) +signer := httpsig.NewHS2019PSSSigner("foo", key, nil, rsa.PSSSaltLengthEqualsHash) ``` By default, if no headers are passed to `NewSigner` (or the helpers), the @@ -101,7 +101,7 @@ keystore.SetKey("foo", hmac_key) In order to support hs2019 the keystore also includes a store for the key algorithm. Key algorithms can be added using the SetKeyAlgorithm method: ``` -keystore.SetKeyAlgorithm("foo", algorithm.HS2019_PSS) +keystore.SetKeyAlgorithm("foo", NewHS2019_PSS(rsa.PSSSaltLengthEqualHash)) ``` ## Handler diff --git a/handler_test.go b/handler_test.go index 7c80d25..b27c1ea 100644 --- a/handler_test.go +++ b/handler_test.go @@ -15,6 +15,7 @@ package httpsig import ( + "crypto/rsa" "net/http" "net/http/httptest" "testing" @@ -136,7 +137,7 @@ func TestHandlerAcceptsSignedRequest(t *testing.T) { req, err := http.NewRequest("GET", server.URL, nil) test.AssertNoError(err) - s := NewHS2019PSSSigner("Test", test.PrivateKey, v.RequiredHeaders()) + s := NewHS2019PSSSigner("Test", test.PrivateKey, v.RequiredHeaders(), rsa.PSSSaltLengthAuto) test.AssertNoError(s.Sign(req)) resp, err := http.DefaultClient.Do(req) diff --git a/hs2019.go b/hs2019.go index e673e40..2f6ff9a 100644 --- a/hs2019.go +++ b/hs2019.go @@ -5,15 +5,12 @@ import ( "crypto/rsa" ) -// HS2019 implements PSS signatures over a SHA512 digest -var HS2019_PSS Algorithm = hs2019_pss{} - type hs2019_pss struct { saltLength int hash crypto.Hash } -func (hs2019_pss) HS2019_PSS(saltLenght int) *hs2019_pss { +func NewHS2019_PSS(saltLenght int) *hs2019_pss { return &hs2019_pss{ saltLength: saltLenght, hash: crypto.SHA512, @@ -48,6 +45,6 @@ func (a hs2019_pss) Verify(key interface{}, data, sig []byte) error { if _, err := h.Write(data); err != nil { return err } - opt := &rsa.PSSOptions{SaltLength: rsa.PSSSaltLengthEqualsHash} + opt := &rsa.PSSOptions{SaltLength: a.saltLength} return rsa.VerifyPSS(k, a.hash, h.Sum(nil), sig, opt) } diff --git a/httpsig_test.go b/httpsig_test.go index 8344965..8c2a558 100644 --- a/httpsig_test.go +++ b/httpsig_test.go @@ -51,7 +51,7 @@ G6aFKaqQfOXKCyWoUiVknQJAXrlgySFci/2ueKlIE1QqIiLSZ8V8OlpFLRnb1pzI func TestDate(t *testing.T) { test := NewTest(t) - signer := NewHS2019PSSSigner("Test", test.PrivateKey, []string{"date"}) + signer := NewHS2019PSSSigner("Test", test.PrivateKey, []string{"date"}, rsa.PSSSaltLengthAuto) verifier := NewVerifier(test) req := test.NewRequest() @@ -80,7 +80,7 @@ func TestRequestTargetAndHost(t *testing.T) { test := NewTest(t) headers := []string{"(request-target)", "host", "date"} - signer := NewHS2019PSSSigner("Test", test.PrivateKey, headers) + signer := NewHS2019PSSSigner("Test", test.PrivateKey, headers, rsa.PSSSaltLengthAuto) verifier := NewVerifier(test) req := test.NewRequest() @@ -177,7 +177,7 @@ func NewTest(tb testing.TB) *Test { keystore := NewMemoryKeyStore() keystore.SetKey("Test", key) - keystore.SetKeyAlgorithm("Test", HS2019_PSS) + keystore.SetKeyAlgorithm("Test", NewHS2019_PSS(rsa.PSSSaltLengthAuto)) keystore.SetKey("Test_SHA256", key) diff --git a/sign.go b/sign.go index 38f2711..9d050f1 100644 --- a/sign.go +++ b/sign.go @@ -79,9 +79,9 @@ func NewHMACSHA256Signer(id string, key []byte, headers []string) ( // NewHS2019PSSSigner constructs a signer with the specified key id, hmac key, // and headers to sign. -func NewHS2019PSSSigner(id string, key *rsa.PrivateKey, headers []string) ( +func NewHS2019PSSSigner(id string, key *rsa.PrivateKey, headers []string, saltLength int) ( signer *Signer) { - return NewSigner(id, key, HS2019_PSS, headers) + return NewSigner(id, key, NewHS2019_PSS(saltLength), headers) } // Sign signs an http request and adds the signature to the authorization header