Adds unit testing for jws
This commit is contained in:
22
jwt/jose.go
22
jwt/jose.go
@@ -12,17 +12,17 @@ import (
|
|||||||
// JOSE represent a JSON Object Signing and Encryption header as
|
// JOSE represent a JSON Object Signing and Encryption header as
|
||||||
// defined in RFC 7515.
|
// defined in RFC 7515.
|
||||||
type JOSE struct {
|
type JOSE struct {
|
||||||
Algorithm string `json:"alg"`
|
Algorithm string `json:"alg"`
|
||||||
Type string `json:"typ,omitempty"`
|
Type string `json:"typ,omitempty"`
|
||||||
Content string `json:"cty,omitempty"`
|
Content string `json:"cty,omitempty"`
|
||||||
Critical string `json:"crit,omitempty"`
|
Critical []string `json:"crit,omitempty"`
|
||||||
JWKSetURL string `json:"jku,omitempty"`
|
JWKSetURL string `json:"jku,omitempty"`
|
||||||
JSONWebKey string `json:"jwk,omitempty"`
|
JSONWebKey string `json:"jwk,omitempty"`
|
||||||
KeyID string `json:"kid,omitempty"`
|
KeyID string `json:"kid,omitempty"`
|
||||||
X509URL string `json:"x5u,omitempty"`
|
X509URL string `json:"x5u,omitempty"`
|
||||||
X509CertificateChain string `json:"x5c,omitempty"`
|
X509CertificateChain string `json:"x5c,omitempty"`
|
||||||
X509ThumbprintSha1 string `json:"x5t,omitempty"`
|
X509ThumbprintSha1 string `json:"x5t,omitempty"`
|
||||||
X509ThumbprintSha256 string `json:"x5t#S256,omitempty"`
|
X509ThumbprintSha256 string `json:"x5t#S256,omitempty"`
|
||||||
|
|
||||||
//Sets of adfditional headers, private or public
|
//Sets of adfditional headers, private or public
|
||||||
AdditionalHeaders map[string]interface{} `json:"-"`
|
AdditionalHeaders map[string]interface{} `json:"-"`
|
||||||
|
|||||||
@@ -86,3 +86,18 @@ func (s *JOSESuite) TestHandlesPrivateHeaders(c *C) {
|
|||||||
c.Assert(res, DeepEquals, j)
|
c.Assert(res, DeepEquals, j)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *JOSESuite) TestEncodingIsCompact(c *C) {
|
||||||
|
j := &JOSE{
|
||||||
|
Algorithm: "none",
|
||||||
|
Critical: []string{"exp", "foo"},
|
||||||
|
}
|
||||||
|
// it does not print empty fields.
|
||||||
|
data, err := j.EncodeJSON()
|
||||||
|
c.Check(err, IsNil)
|
||||||
|
c.Check(string(data), Equals, `{"alg":"none","crit":["exp","foo"]}`)
|
||||||
|
|
||||||
|
b64, err := j.EncodeBase64()
|
||||||
|
c.Check(err, IsNil)
|
||||||
|
c.Check(len(b64), Equals, 47)
|
||||||
|
}
|
||||||
|
|||||||
35
jwt/jws.go
35
jwt/jws.go
@@ -14,7 +14,11 @@ func EncodeJWS(j *JOSE, v interface{}, s Signer) ([]byte, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
j.Algorithm = s.Algorithm()
|
if s != nil {
|
||||||
|
j.Algorithm = s.Algorithm()
|
||||||
|
} else {
|
||||||
|
j.Algorithm = "none"
|
||||||
|
}
|
||||||
|
|
||||||
header, err := j.EncodeJSON()
|
header, err := j.EncodeJSON()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -24,17 +28,17 @@ func EncodeJWS(j *JOSE, v interface{}, s Signer) ([]byte, error) {
|
|||||||
//Allocate a buffer long enough for all payload
|
//Allocate a buffer long enough for all payload
|
||||||
res := make([]byte, Base64EncodedBufferLen(len(header))+Base64EncodedBufferLen(len(payload))+2)
|
res := make([]byte, Base64EncodedBufferLen(len(header))+Base64EncodedBufferLen(len(payload))+2)
|
||||||
base64.URLEncoding.Encode(res, header)
|
base64.URLEncoding.Encode(res, header)
|
||||||
lengthHeader := Base64EncodedStrippedLen(len(res))
|
lengthHeader := Base64EncodedStrippedLen(len(header))
|
||||||
res[lengthHeader] = '.'
|
res[lengthHeader] = '.'
|
||||||
base64.URLEncoding.Encode(res[lengthHeader+1:], payload)
|
base64.URLEncoding.Encode(res[lengthHeader+1:], payload)
|
||||||
fullPayloadLength := Base64EncodedBufferLen(len(payload)) + 1 + lengthHeader
|
fullPayloadLength := Base64EncodedStrippedLen(len(payload)) + 1 + lengthHeader
|
||||||
res[fullPayloadLength] = '.'
|
res[fullPayloadLength] = '.'
|
||||||
|
|
||||||
if s == nil {
|
if s == nil {
|
||||||
// unprotected jws, not signing it
|
// unprotected jws, not signing it
|
||||||
return res[:fullPayloadLength+1], nil
|
return res[:fullPayloadLength+1], nil
|
||||||
}
|
}
|
||||||
signature, err := s.Sign(res)
|
signature, err := s.Sign(res[:fullPayloadLength])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -111,9 +115,16 @@ func DecodeJWS(data []byte, v interface{}, s Signer) error {
|
|||||||
signature := make([]byte, Base64DecodedLenFromStripped(signatureLength))
|
signature := make([]byte, Base64DecodedLenFromStripped(signatureLength))
|
||||||
signedLength := headerLength + payloadLength + 1
|
signedLength := headerLength + payloadLength + 1
|
||||||
Base64Decode(signature, data[signedLength+1:])
|
Base64Decode(signature, data[signedLength+1:])
|
||||||
|
signature = signature[:Base64DecodedStrippedLen(signatureLength)]
|
||||||
|
|
||||||
if err := s.Verify(data[:signedLength], signature); err != nil {
|
if s != nil {
|
||||||
return err
|
if err := s.Verify(data[:signedLength], signature); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if signatureLength != 0 {
|
||||||
|
return fmt.Errorf("jws: Invalid JWS, got a signature, but none expected")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//decode jose
|
//decode jose
|
||||||
@@ -122,12 +133,18 @@ func DecodeJWS(data []byte, v interface{}, s Signer) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if jose.Algorithm != s.Algorithm() {
|
algo := "none"
|
||||||
return fmt.Errorf("jws: Mismatched signing algorithm got %s, expected %s", jose.Algorithm, s.Algorithm())
|
if s != nil {
|
||||||
|
algo = s.Algorithm()
|
||||||
|
}
|
||||||
|
|
||||||
|
if jose.Algorithm != algo {
|
||||||
|
return fmt.Errorf("jws: Mismatched signing algorithm got %s, expected %s", jose.Algorithm, algo)
|
||||||
}
|
}
|
||||||
|
|
||||||
payload := make([]byte, Base64DecodedLenFromStripped(payloadLength))
|
payload := make([]byte, Base64DecodedLenFromStripped(payloadLength))
|
||||||
Base64Decode(payload, data[headerLength+1:signatureLength])
|
Base64Decode(payload, data[headerLength+1:headerLength+1+payloadLength])
|
||||||
|
payload = payload[:Base64DecodedStrippedLen(payloadLength)]
|
||||||
//data is safe, just need to decode it.
|
//data is safe, just need to decode it.
|
||||||
return json.Unmarshal(payload, v)
|
return json.Unmarshal(payload, v)
|
||||||
}
|
}
|
||||||
|
|||||||
77
jwt/jws_test.go
Normal file
77
jwt/jws_test.go
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
package jwt
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/rsa"
|
||||||
|
|
||||||
|
. "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)
|
||||||
|
|
||||||
|
}
|
||||||
@@ -49,7 +49,7 @@ func NewHMAC256Signer(key []byte) Signer {
|
|||||||
// NewHMAC384Signer returs a HMACSigner using a SHA384 hash.
|
// NewHMAC384Signer returs a HMACSigner using a SHA384 hash.
|
||||||
func NewHMAC384Signer(key []byte) Signer {
|
func NewHMAC384Signer(key []byte) Signer {
|
||||||
return &HMACSigner{
|
return &HMACSigner{
|
||||||
name: "HS256",
|
name: "HS384",
|
||||||
hash: crypto.SHA384,
|
hash: crypto.SHA384,
|
||||||
key: key,
|
key: key,
|
||||||
}
|
}
|
||||||
@@ -58,8 +58,8 @@ func NewHMAC384Signer(key []byte) Signer {
|
|||||||
// NewHMAC512Signer returs a HMACSigner using a SHA512 hash.
|
// NewHMAC512Signer returs a HMACSigner using a SHA512 hash.
|
||||||
func NewHMAC512Signer(key []byte) Signer {
|
func NewHMAC512Signer(key []byte) Signer {
|
||||||
return &HMACSigner{
|
return &HMACSigner{
|
||||||
name: "HS256",
|
name: "HS512",
|
||||||
hash: crypto.SHA384,
|
hash: crypto.SHA512,
|
||||||
key: key,
|
key: key,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
55
jwt/signer_test.go
Normal file
55
jwt/signer_test.go
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
package jwt
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/rsa"
|
||||||
|
"crypto/x509"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewHMACKey(n int) []byte {
|
||||||
|
res := make([]byte, n)
|
||||||
|
rand.Reader.Read(res)
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
func CachedRSAkey() (*rsa.PrivateKey, error) {
|
||||||
|
keyPath := filepath.Join(os.TempDir(), "narco-jwt-test.key")
|
||||||
|
f, err := os.Open(keyPath)
|
||||||
|
if err != nil {
|
||||||
|
if os.IsNotExist(err) == false {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// generate a key
|
||||||
|
log.Printf("Generating a new key")
|
||||||
|
key, err := rsa.GenerateKey(rand.Reader, 1024)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
f, err := os.Create(keyPath)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Could not cache the generated key: %s", err)
|
||||||
|
return key, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
data := x509.MarshalPKCS1PrivateKey(key)
|
||||||
|
_, err = f.Write(data)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Could not cache the generated key: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return key, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
keydata, err := ioutil.ReadAll(f)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Could not read %s: %s", keyPath, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return x509.ParsePKCS1PrivateKey(keydata)
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user