diff --git a/jwt/jws.go b/jwt/jws.go index c562749..8709efb 100644 --- a/jwt/jws.go +++ b/jwt/jws.go @@ -80,9 +80,9 @@ func isBase64URLEncoding(b byte) bool { } // DecodeJWS decode an object encoded with the JWS serialized fromat -// as specified by RFX 7515. to avoid an attack where the unprotected -// JOSE header would contain a tempered signing algorithm, the Signer -// should also be specified. +// as specified by RFC 7515. to avoid an attack where the unprotected +// JOSE header would contain a modified alg field, the Signer should +// also be specified. func DecodeJWS(data []byte, v interface{}, s Signer) error { var headerLength, payloadLength int for i, c := range data { diff --git a/jwt/jws_test.go b/jwt/jws_test.go index 3fdca5c..74e7d09 100644 --- a/jwt/jws_test.go +++ b/jwt/jws_test.go @@ -2,6 +2,11 @@ package jwt import ( "crypto/rsa" + "crypto/x509" + "encoding/json" + "fmt" + "regexp" + "time" . "gopkg.in/check.v1" ) @@ -75,3 +80,132 @@ func (s *JWSSuite) TestCanEncodeUnprotected(c *C) { c.Check(redecoded, DeepEquals, decoded) } + +func (s *JWSSuite) TestAttackerTriesToForgeAToken(c *C) { + type ident struct { + Iat int64 + Adm bool + } + + //-------------------------------------------------------------------------- + // Server side + //-------------------------------------------------------------------------- + + //we create a token and sign it + + validToken, err := EncodeJWS(&JOSE{}, ident{Iat: time.Now().Unix(), Adm: false}, s.signers[0]) + c.Assert(err, IsNil) + + //-------------------------------------------------------------------------- + // Untrusted side + //-------------------------------------------------------------------------- + + // now an attacker takes our valid token, unmarshall it, and create a new one with no signature + t := ident{} + + tokenRx := regexp.MustCompile(`\A([a-zA-Z0-9_\-]+)\.([a-zA-Z0-9_\-]+).([a-zA-Z0-9_\-]+)\z`) + + matches := tokenRx.FindSubmatch(validToken) + c.Assert(matches, NotNil) + + // extract JOSE + j, err := DecodeJOSE(matches[1]) + c.Assert(err, IsNil) + + // extract payload + payload := make([]byte, Base64DecodedLenFromStripped(len(matches[2]))) + Base64Decode(payload, matches[2]) + err = json.Unmarshal(payload, &t) + c.Assert(err, IsNil) + + //we forge an admin token + t.Adm = true + + //we un-sign the token, by using the none algorithm + forgedToken, err := EncodeJWS(j, t, nil) + + //-------------------------------------------------------------------------- + // Server side + //-------------------------------------------------------------------------- + + //we verify the forgedToken, it should fail + received := ident{Adm: false} + // please note that we speficy ecplitely the signer, and therefore + // the algorithm to use. + err = DecodeJWS(forgedToken, &received, s.signers[0]) + + //we got a wrong signature for our chosen algorithm + c.Assert(err, ErrorMatches, fmt.Sprintf(`jws: .* %s .*`, s.signers[0].Algorithm())) + // of course, our token remain Adm:false + c.Assert(received.Adm, Equals, false) + +} + +func (s *JWSSuite) TestAttackerTriesToForgeAHMACToken(c *C) { + type ident struct { + Iat int64 + Adm bool + } + + //-------------------------------------------------------------------------- + // Server side + //-------------------------------------------------------------------------- + + //we create a token and sign it using RSA256 + serverSigner := NewRSA256Signer(s.rsaKey) + validToken, err := EncodeJWS(&JOSE{}, ident{Iat: time.Now().Unix(), Adm: false}, serverSigner) + c.Assert(err, IsNil) + + //-------------------------------------------------------------------------- + // Untrusted side + //-------------------------------------------------------------------------- + + // now an attacker takes our valid token, unmarshall it, and create a new one with no signature + t := ident{} + + tokenRx := regexp.MustCompile(`\A([a-zA-Z0-9_\-]+)\.([a-zA-Z0-9_\-]+).([a-zA-Z0-9_\-]+)\z`) + + matches := tokenRx.FindSubmatch(validToken) + c.Assert(matches, NotNil) + + // extract JOSE + j, err := DecodeJOSE(matches[1]) + c.Assert(err, IsNil) + + // extract payload + payload := make([]byte, Base64DecodedLenFromStripped(len(matches[2]))) + Base64Decode(payload, matches[2]) + err = json.Unmarshal(payload, &t) + c.Assert(err, IsNil) + + //we forge an admin token + t.Adm = true + + //now here is the trick, we sign an HMAC token using the serverPublicKey + + publicKeyAsByte, err := x509.MarshalPKIXPublicKey(s.rsaKey.Public()) + c.Assert(err, IsNil, Commentf("While marshalling key")) + //we create a new signing algorithm expecting the server will + //still use the assymetric key on the HMAC. + attackerSigner := NewHMAC256Signer(publicKeyAsByte) + //we un-sign the token, by using another symmetric algorithm, and + //the publickKey + forgedToken, err := EncodeJWS(j, t, attackerSigner) + + //-------------------------------------------------------------------------- + // Server side + //-------------------------------------------------------------------------- + + //we verify the forgedToken, it should fail + received := ident{Adm: false} + // please note that we speficy ecplitely the signer, and therefore + // the algorithm to use. + err = DecodeJWS(forgedToken, &received, serverSigner) + + //we got a wrong signature for our chosen algorithm, we still are + //using the rsa algorithm + c.Assert(err, ErrorMatches, `crypto/rsa: verification error`) + // of course, our token remain Adm:false + c.Assert(received.Adm, Equals, false) + +}