212 lines
5.7 KiB
Go
212 lines
5.7 KiB
Go
package jwt
|
|
|
|
import (
|
|
"crypto/rsa"
|
|
"crypto/x509"
|
|
"encoding/json"
|
|
"fmt"
|
|
"regexp"
|
|
"time"
|
|
|
|
. "gopkg.in/check.v1"
|
|
)
|
|
|
|
type JWSSuite struct {
|
|
signers []Signer
|
|
hmacKey []byte
|
|
rsaKey *rsa.PrivateKey
|
|
}
|
|
|
|
var _ = Suite(&JWSSuite{})
|
|
|
|
func (s *JWSSuite) SetUpSuite(c *C) {
|
|
var err error
|
|
s.hmacKey = NewHMACKey(24)
|
|
|
|
s.rsaKey, err = CachedRSAkey()
|
|
|
|
c.Assert(err, IsNil)
|
|
|
|
s.signers = []Signer{
|
|
NewHMAC256Signer(s.hmacKey),
|
|
NewHMAC384Signer(s.hmacKey),
|
|
NewHMAC512Signer(s.hmacKey),
|
|
NewRSA256Signer(s.rsaKey),
|
|
NewRSA384Signer(s.rsaKey),
|
|
NewRSA512Signer(s.rsaKey),
|
|
}
|
|
}
|
|
|
|
func (s *JWSSuite) TestCanEncodeSigned(c *C) {
|
|
type foo struct {
|
|
A string
|
|
B int
|
|
}
|
|
|
|
j := &JOSE{}
|
|
|
|
for _, signer := range s.signers {
|
|
decoded := foo{A: "blah", B: 42}
|
|
res, err := EncodeJWS(j, decoded, signer)
|
|
c.Check(string(res), Matches, `\A[0-9A-Za-z_\-]+\.[0-9A-Za-z_\-]+.[0-9A-Za-z_\-]+\z`)
|
|
if c.Check(err, IsNil) == false {
|
|
continue
|
|
}
|
|
redecoded := foo{}
|
|
err = DecodeJWS(res, &redecoded, signer)
|
|
if c.Check(err, IsNil, Commentf("Algo is: %s", signer.Algorithm())) == true {
|
|
c.Check(redecoded, DeepEquals, decoded)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (s *JWSSuite) TestCanEncodeUnprotected(c *C) {
|
|
|
|
type foo struct {
|
|
A string
|
|
B int
|
|
}
|
|
|
|
j := &JOSE{}
|
|
|
|
decoded := foo{A: "blah", B: 42}
|
|
res, err := EncodeJWS(j, decoded, nil)
|
|
c.Check(string(res), Matches, `\A[0-9A-Za-z_\-]+\.[0-9A-Za-z_\-]+.\z`)
|
|
c.Assert(err, IsNil)
|
|
|
|
redecoded := foo{}
|
|
err = DecodeJWS(res, &redecoded, nil)
|
|
c.Assert(err, IsNil)
|
|
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)
|
|
|
|
}
|