wfe/wfe2: make JWS signature alg error msgs match reality (#4519)

Errors that were being returned in the checkAlgorithm methods of both wfe and
wfe2 didn't really match up to what was actually being checked. This change
attempts to bring the errors in line with what is actually being tested.

Fixes #4452.
This commit is contained in:
Roland Bracewell Shoemaker 2019-10-31 06:55:11 -07:00 committed by Daniel McCarney
parent e448e81dc4
commit e5eb8f8736
5 changed files with 112 additions and 87 deletions

View File

@ -83,6 +83,7 @@ func AssertDeepEquals(t *testing.T, one interface{}, two interface{}) {
// AssertMarshaledEquals marshals one and two to JSON, and then uses // AssertMarshaledEquals marshals one and two to JSON, and then uses
// the equality operator to measure them // the equality operator to measure them
func AssertMarshaledEquals(t *testing.T, one interface{}, two interface{}) { func AssertMarshaledEquals(t *testing.T, one interface{}, two interface{}) {
t.Helper()
oneJSON, err := json.Marshal(one) oneJSON, err := json.Marshal(one)
AssertNotError(t, err, "Could not marshal 1st argument") AssertNotError(t, err, "Could not marshal 1st argument")
twoJSON, err := json.Marshal(two) twoJSON, err := json.Marshal(two)

View File

@ -22,7 +22,7 @@ func algorithmForKey(key *jose.JSONWebKey) (string, error) {
return string(jose.ES512), nil return string(jose.ES512), nil
} }
} }
return "", fmt.Errorf("no signature algorithms suitable for given key type") return "", fmt.Errorf("JWK contains unsupported key type (expected RSA, or ECDSA P-256, P-384, or P-521")
} }
const ( const (
@ -31,26 +31,35 @@ const (
invalidAlgorithmOnKey = "WFE.Errors.InvalidAlgorithmOnKey" invalidAlgorithmOnKey = "WFE.Errors.InvalidAlgorithmOnKey"
) )
var supportedAlgs = map[string]bool{
string(jose.RS256): true,
string(jose.ES256): true,
string(jose.ES384): true,
string(jose.ES512): true,
}
// Check that (1) there is a suitable algorithm for the provided key based on its // Check that (1) there is a suitable algorithm for the provided key based on its
// Golang type, (2) the Algorithm field on the JWK is either absent, or matches // Golang type, (2) the Algorithm field on the JWK is either absent, or matches
// that algorithm, and (3) the Algorithm field on the JWK is present and matches // that algorithm, and (3) the Algorithm field on the JWK is present and matches
// that algorithm. Precondition: parsedJws must have exactly one signature on // that algorithm. Precondition: parsedJWS must have exactly one signature on
// it. Returns stat name to increment if err is non-nil. // it. Returns stat name to increment if err is non-nil.
func checkAlgorithm(key *jose.JSONWebKey, parsedJws *jose.JSONWebSignature) (string, error) { func checkAlgorithm(key *jose.JSONWebKey, parsedJWS *jose.JSONWebSignature) (string, error) {
algorithm, err := algorithmForKey(key) sigHeaderAlg := parsedJWS.Signatures[0].Header.Algorithm
if !supportedAlgs[sigHeaderAlg] {
return invalidJWSAlgorithm, fmt.Errorf(
"JWS signature header contains unsupported algorithm %q, expected one of RS256, ES256, ES384 or ES512",
parsedJWS.Signatures[0].Header.Algorithm,
)
}
expectedAlg, err := algorithmForKey(key)
if err != nil { if err != nil {
return noAlgorithmForKey, err return noAlgorithmForKey, err
} }
jwsAlgorithm := parsedJws.Signatures[0].Header.Algorithm if sigHeaderAlg != string(expectedAlg) {
if jwsAlgorithm != algorithm { return invalidJWSAlgorithm, fmt.Errorf("JWS signature header algorithm %q does not match expected algorithm %q for JWK", sigHeaderAlg, string(expectedAlg))
return invalidJWSAlgorithm, fmt.Errorf(
"signature type '%s' in JWS header is not supported, expected one of RS256, ES256, ES384 or ES512",
jwsAlgorithm,
)
} }
if key.Algorithm != "" && key.Algorithm != algorithm { if key.Algorithm != "" && key.Algorithm != string(expectedAlg) {
return invalidAlgorithmOnKey, fmt.Errorf("algorithm '%s' on JWK is unacceptable", return invalidAlgorithmOnKey, fmt.Errorf("JWK key header algorithm %q does not match expected algorithm %q for JWK", key.Algorithm, string(expectedAlg))
key.Algorithm)
} }
return "", nil return "", nil
} }

View File

@ -1,6 +1,7 @@
package wfe package wfe
import ( import (
"crypto/dsa"
"crypto/ecdsa" "crypto/ecdsa"
"crypto/elliptic" "crypto/elliptic"
"crypto/rsa" "crypto/rsa"
@ -28,7 +29,7 @@ func TestRejectsNone(t *testing.T) {
if prob == nil { if prob == nil {
t.Fatalf("verifyPOST did not reject JWS with alg: 'none'") t.Fatalf("verifyPOST did not reject JWS with alg: 'none'")
} }
if prob.Detail != "signature type 'none' in JWS header is not supported, expected one of RS256, ES256, ES384 or ES512" { if prob.Detail != "JWS signature header contains unsupported algorithm \"none\", expected one of RS256, ES256, ES384 or ES512" {
t.Fatalf("verifyPOST rejected JWS with alg: 'none', but for wrong reason: %#v", prob) t.Fatalf("verifyPOST rejected JWS with alg: 'none', but for wrong reason: %#v", prob)
} }
} }
@ -52,7 +53,7 @@ func TestRejectsHS256(t *testing.T) {
if prob == nil { if prob == nil {
t.Fatalf("verifyPOST did not reject JWS with alg: 'HS256'") t.Fatalf("verifyPOST did not reject JWS with alg: 'HS256'")
} }
expected := "signature type 'HS256' in JWS header is not supported, expected one of RS256, ES256, ES384 or ES512" expected := "JWS signature header contains unsupported algorithm \"HS256\", expected one of RS256, ES256, ES384 or ES512"
if prob.Detail != expected { if prob.Detail != expected {
t.Fatalf("verifyPOST rejected JWS with alg: 'none', but for wrong reason: got %q, wanted %q", prob, expected) t.Fatalf("verifyPOST rejected JWS with alg: 'none', but for wrong reason: got %q, wanted %q", prob, expected)
} }
@ -66,17 +67,7 @@ func TestCheckAlgorithm(t *testing.T) {
expectedStat string expectedStat string
}{ }{
{ {
jose.JSONWebKey{ jose.JSONWebKey{},
Algorithm: "HS256",
},
jose.JSONWebSignature{},
"no signature algorithms suitable for given key type",
"WFE.Errors.NoAlgorithmForKey",
},
{
jose.JSONWebKey{
Key: &rsa.PublicKey{},
},
jose.JSONWebSignature{ jose.JSONWebSignature{
Signatures: []jose.Signature{ Signatures: []jose.Signature{
{ {
@ -86,25 +77,41 @@ func TestCheckAlgorithm(t *testing.T) {
}, },
}, },
}, },
"signature type 'HS256' in JWS header is not supported, expected one of RS256, ES256, ES384 or ES512", "JWS signature header contains unsupported algorithm \"HS256\", expected one of RS256, ES256, ES384 or ES512",
"WFE.Errors.InvalidJWSAlgorithm", invalidJWSAlgorithm,
}, },
{ {
jose.JSONWebKey{ jose.JSONWebKey{
Algorithm: "HS256", Key: &dsa.PublicKey{},
},
jose.JSONWebSignature{
Signatures: []jose.Signature{
{
Header: jose.Header{
Algorithm: "ES512",
},
},
},
},
"JWK contains unsupported key type (expected RSA, or ECDSA P-256, P-384, or P-521",
noAlgorithmForKey,
},
{
jose.JSONWebKey{
Algorithm: "RS256",
Key: &rsa.PublicKey{}, Key: &rsa.PublicKey{},
}, },
jose.JSONWebSignature{ jose.JSONWebSignature{
Signatures: []jose.Signature{ Signatures: []jose.Signature{
{ {
Header: jose.Header{ Header: jose.Header{
Algorithm: "HS256", Algorithm: "ES512",
}, },
}, },
}, },
}, },
"signature type 'HS256' in JWS header is not supported, expected one of RS256, ES256, ES384 or ES512", "JWS signature header algorithm \"ES512\" does not match expected algorithm \"RS256\" for JWK",
"WFE.Errors.InvalidJWSAlgorithm", invalidJWSAlgorithm,
}, },
{ {
jose.JSONWebKey{ jose.JSONWebKey{
@ -120,8 +127,8 @@ func TestCheckAlgorithm(t *testing.T) {
}, },
}, },
}, },
"algorithm 'HS256' on JWK is unacceptable", "JWK key header algorithm \"HS256\" does not match expected algorithm \"RS256\" for JWK",
"WFE.Errors.InvalidAlgorithmOnKey", invalidAlgorithmOnKey,
}, },
} }
for i, tc := range testCases { for i, tc := range testCases {

View File

@ -2,7 +2,6 @@ package wfe2
import ( import (
"context" "context"
"crypto"
"crypto/ecdsa" "crypto/ecdsa"
"crypto/rsa" "crypto/rsa"
"encoding/json" "encoding/json"
@ -27,29 +26,28 @@ import (
// POST requests with a JWS body must have the following Content-Type header // POST requests with a JWS body must have the following Content-Type header
const expectedJWSContentType = "application/jose+json" const expectedJWSContentType = "application/jose+json"
var sigAlgErr = errors.New("no signature algorithms suitable for given key type") func sigAlgorithmForKey(key *jose.JSONWebKey) (jose.SignatureAlgorithm, error) {
switch k := key.Key.(type) {
func sigAlgorithmForECDSAKey(key *ecdsa.PublicKey) (jose.SignatureAlgorithm, error) {
params := key.Params()
switch params.Name {
case "P-256":
return jose.ES256, nil
case "P-384":
return jose.ES384, nil
case "P-521":
return jose.ES512, nil
}
return "", sigAlgErr
}
func sigAlgorithmForKey(key crypto.PublicKey) (jose.SignatureAlgorithm, error) {
switch k := key.(type) {
case *rsa.PublicKey: case *rsa.PublicKey:
return jose.RS256, nil return jose.RS256, nil
case *ecdsa.PublicKey: case *ecdsa.PublicKey:
return sigAlgorithmForECDSAKey(k) switch k.Params().Name {
case "P-256":
return jose.ES256, nil
case "P-384":
return jose.ES384, nil
case "P-521":
return jose.ES512, nil
}
} }
return "", sigAlgErr return "", errors.New("JWK contains unsupported key type (expected RSA, or ECDSA P-256, P-384, or P-521")
}
var supportedAlgs = map[string]bool{
string(jose.RS256): true,
string(jose.ES256): true,
string(jose.ES384): true,
string(jose.ES512): true,
} }
// Check that (1) there is a suitable algorithm for the provided key based on its // Check that (1) there is a suitable algorithm for the provided key based on its
@ -58,19 +56,22 @@ func sigAlgorithmForKey(key crypto.PublicKey) (jose.SignatureAlgorithm, error) {
// that algorithm. Precondition: parsedJws must have exactly one signature on // that algorithm. Precondition: parsedJws must have exactly one signature on
// it. // it.
func checkAlgorithm(key *jose.JSONWebKey, parsedJWS *jose.JSONWebSignature) error { func checkAlgorithm(key *jose.JSONWebKey, parsedJWS *jose.JSONWebSignature) error {
algorithm, err := sigAlgorithmForKey(key.Key) sigHeaderAlg := parsedJWS.Signatures[0].Header.Algorithm
if !supportedAlgs[sigHeaderAlg] {
return fmt.Errorf(
"JWS signature header contains unsupported algorithm %q, expected one of RS256, ES256, ES384 or ES512",
parsedJWS.Signatures[0].Header.Algorithm,
)
}
expectedAlg, err := sigAlgorithmForKey(key)
if err != nil { if err != nil {
return err return err
} }
jwsAlgorithm := parsedJWS.Signatures[0].Header.Algorithm if sigHeaderAlg != string(expectedAlg) {
if jwsAlgorithm != string(algorithm) { return fmt.Errorf("JWS signature header algorithm %q does not match expected algorithm %q for JWK", sigHeaderAlg, string(expectedAlg))
return fmt.Errorf(
"signature type '%s' in JWS header is not supported, expected one of RS256, ES256, ES384 or ES512",
jwsAlgorithm,
)
} }
if key.Algorithm != "" && key.Algorithm != string(algorithm) { if key.Algorithm != "" && key.Algorithm != string(expectedAlg) {
return fmt.Errorf("algorithm '%s' on JWK is unacceptable", key.Algorithm) return fmt.Errorf("JWK key header algorithm %q does not match expected algorithm %q for JWK", key.Algorithm, string(expectedAlg))
} }
return nil return nil
} }

View File

@ -3,6 +3,7 @@ package wfe2
import ( import (
"context" "context"
"crypto" "crypto"
"crypto/dsa"
"crypto/ecdsa" "crypto/ecdsa"
"crypto/elliptic" "crypto/elliptic"
"crypto/rsa" "crypto/rsa"
@ -26,15 +27,15 @@ func sigAlgForKey(t *testing.T, key interface{}) jose.SignatureAlgorithm {
var err error var err error
// Gracefully handle the case where a non-pointer public key is given where // Gracefully handle the case where a non-pointer public key is given where
// sigAlgorithmForKey always wants a pointer. It may be tempting to try and do // sigAlgorithmForKey always wants a pointer. It may be tempting to try and do
// `sigAlgorithmForKey(&key)` without a type switch but this produces // `sigAlgorithmForKey(&jose.JSONWebKey{Key: &key})` without a type switch but this produces
// `*interface {}` and not the desired `*rsa.PublicKey` or `*ecdsa.PublicKey`. // `*interface {}` and not the desired `*rsa.PublicKey` or `*ecdsa.PublicKey`.
switch k := key.(type) { switch k := key.(type) {
case rsa.PublicKey: case rsa.PublicKey:
sigAlg, err = sigAlgorithmForKey(&k) sigAlg, err = sigAlgorithmForKey(&jose.JSONWebKey{Key: &k})
case ecdsa.PublicKey: case ecdsa.PublicKey:
sigAlg, err = sigAlgorithmForKey(&k) sigAlg, err = sigAlgorithmForKey(&jose.JSONWebKey{Key: &k})
default: default:
sigAlg, err = sigAlgorithmForKey(k) sigAlg, err = sigAlgorithmForKey(&jose.JSONWebKey{Key: k})
} }
test.Assert(t, err == nil, fmt.Sprintf("Error getting signature algorithm for key %#v", key)) test.Assert(t, err == nil, fmt.Sprintf("Error getting signature algorithm for key %#v", key))
return sigAlg return sigAlg
@ -185,7 +186,7 @@ func TestRejectsNone(t *testing.T) {
if err == nil { if err == nil {
t.Fatalf("checkAlgorithm did not reject JWS with alg: 'none'") t.Fatalf("checkAlgorithm did not reject JWS with alg: 'none'")
} }
if err.Error() != "signature type 'none' in JWS header is not supported, expected one of RS256, ES256, ES384 or ES512" { if err.Error() != "JWS signature header contains unsupported algorithm \"none\", expected one of RS256, ES256, ES384 or ES512" {
t.Fatalf("checkAlgorithm rejected JWS with alg: 'none', but for wrong reason: %#v", err) t.Fatalf("checkAlgorithm rejected JWS with alg: 'none', but for wrong reason: %#v", err)
} }
} }
@ -216,9 +217,9 @@ func TestRejectsHS256(t *testing.T) {
if err == nil { if err == nil {
t.Fatalf("checkAlgorithm did not reject JWS with alg: 'HS256'") t.Fatalf("checkAlgorithm did not reject JWS with alg: 'HS256'")
} }
expected := "signature type 'HS256' in JWS header is not supported, expected one of RS256, ES256, ES384 or ES512" expected := "JWS signature header contains unsupported algorithm \"HS256\", expected one of RS256, ES256, ES384 or ES512"
if err.Error() != expected { if err.Error() != expected {
t.Fatalf("checkAlgorithm rejected JWS with alg: 'none', but for wrong reason: got '%s', wanted %s", err.Error(), expected) t.Fatalf("checkAlgorithm rejected JWS with alg: 'none', but for wrong reason: got %q, wanted %q", err.Error(), expected)
} }
} }
@ -229,16 +230,7 @@ func TestCheckAlgorithm(t *testing.T) {
expectedErr string expectedErr string
}{ }{
{ {
jose.JSONWebKey{ jose.JSONWebKey{},
Algorithm: "HS256",
},
jose.JSONWebSignature{},
"no signature algorithms suitable for given key type",
},
{
jose.JSONWebKey{
Key: &rsa.PublicKey{},
},
jose.JSONWebSignature{ jose.JSONWebSignature{
Signatures: []jose.Signature{ Signatures: []jose.Signature{
{ {
@ -248,23 +240,38 @@ func TestCheckAlgorithm(t *testing.T) {
}, },
}, },
}, },
"signature type 'HS256' in JWS header is not supported, expected one of RS256, ES256, ES384 or ES512", "JWS signature header contains unsupported algorithm \"HS256\", expected one of RS256, ES256, ES384 or ES512",
}, },
{ {
jose.JSONWebKey{ jose.JSONWebKey{
Algorithm: "HS256", Key: &dsa.PublicKey{},
},
jose.JSONWebSignature{
Signatures: []jose.Signature{
{
Header: jose.Header{
Algorithm: "ES512",
},
},
},
},
"JWK contains unsupported key type (expected RSA, or ECDSA P-256, P-384, or P-521",
},
{
jose.JSONWebKey{
Algorithm: "RS256",
Key: &rsa.PublicKey{}, Key: &rsa.PublicKey{},
}, },
jose.JSONWebSignature{ jose.JSONWebSignature{
Signatures: []jose.Signature{ Signatures: []jose.Signature{
{ {
Header: jose.Header{ Header: jose.Header{
Algorithm: "HS256", Algorithm: "ES512",
}, },
}, },
}, },
}, },
"signature type 'HS256' in JWS header is not supported, expected one of RS256, ES256, ES384 or ES512", "JWS signature header algorithm \"ES512\" does not match expected algorithm \"RS256\" for JWK",
}, },
{ {
jose.JSONWebKey{ jose.JSONWebKey{
@ -280,13 +287,13 @@ func TestCheckAlgorithm(t *testing.T) {
}, },
}, },
}, },
"algorithm 'HS256' on JWK is unacceptable", "JWK key header algorithm \"HS256\" does not match expected algorithm \"RS256\" for JWK",
}, },
} }
for i, tc := range testCases { for i, tc := range testCases {
err := checkAlgorithm(&tc.key, &tc.jws) err := checkAlgorithm(&tc.key, &tc.jws)
if tc.expectedErr != "" && err.Error() != tc.expectedErr { if tc.expectedErr != "" && err.Error() != tc.expectedErr {
t.Errorf("TestCheckAlgorithm %d: Expected '%s', got '%s'", i, tc.expectedErr, err) t.Errorf("TestCheckAlgorithm %d: Expected %q, got %q", i, tc.expectedErr, err)
} }
} }
} }
@ -1162,7 +1169,7 @@ func TestValidJWSForKey(t *testing.T) {
JWK: goodJWK, JWK: goodJWK,
ExpectedProblem: &probs.ProblemDetails{ ExpectedProblem: &probs.ProblemDetails{
Type: probs.BadSignatureAlgorithmProblem, Type: probs.BadSignatureAlgorithmProblem,
Detail: "signature type 'HS256' in JWS header is not supported, expected one of RS256, ES256, ES384 or ES512", Detail: "JWS signature header contains unsupported algorithm \"HS256\", expected one of RS256, ES256, ES384 or ES512",
HTTPStatus: http.StatusBadRequest, HTTPStatus: http.StatusBadRequest,
}, },
ErrorStatType: "JWSAlgorithmCheckFailed", ErrorStatType: "JWSAlgorithmCheckFailed",