init
diff --git a/base64.go b/base64.go
new file mode 100644
index 0000000..1956791
--- /dev/null
+++ b/base64.go
@@ -0,0 +1,44 @@
+package jose
+
+import "encoding/base64"
+
+// Encoder is satisfied if the type can marshal itself into a valid
+// structure for a JWS.
+type Encoder interface {
+ // Base64 implies -> JSON -> Base64
+ Base64() ([]byte, error)
+}
+
+// Base64Decode decodes a base64-encoded byte slice.
+func Base64Decode(b []byte) ([]byte, error) {
+ buf := make([]byte, base64.RawURLEncoding.DecodedLen(len(b)))
+ n, err := base64.RawURLEncoding.Decode(buf, b)
+ return buf[:n], err
+}
+
+// Base64Encode encodes a byte slice.
+func Base64Encode(b []byte) []byte {
+ buf := make([]byte, base64.RawURLEncoding.EncodedLen(len(b)))
+ base64.RawURLEncoding.Encode(buf, b)
+ return buf
+}
+
+// EncodeEscape base64-encodes a byte slice but escapes it for JSON.
+// It'll return the format: `"base64"`
+func EncodeEscape(b []byte) []byte {
+ buf := make([]byte, base64.RawURLEncoding.EncodedLen(len(b))+2)
+ buf[0] = '"'
+ base64.RawURLEncoding.Encode(buf[1:], b)
+ buf[len(buf)-1] = '"'
+ return buf
+}
+
+// DecodeEscaped decodes a base64-encoded byte slice straight from a JSON
+// structure. It assumes it's in the format: `"base64"`, but can handle
+// cases where it's not.
+func DecodeEscaped(b []byte) ([]byte, error) {
+ if len(b) > 1 && b[0] == '"' && b[len(b)-1] == '"' {
+ b = b[1 : len(b)-1]
+ }
+ return Base64Decode(b)
+}
diff --git a/base64_test.go b/base64_test.go
new file mode 100644
index 0000000..d24078c
--- /dev/null
+++ b/base64_test.go
@@ -0,0 +1,25 @@
+package jose
+
+import (
+ "bytes"
+ "testing"
+)
+
+func TestBase64(t *testing.T) {
+ encoded := []byte("SGVsbG8sIHBsYXlncm91bmQ")
+ raw := []byte("Hello, playground")
+
+ testEnc := Base64Encode(raw)
+ if !bytes.Equal(testEnc, encoded) {
+ Error(t, encoded, testEnc)
+ }
+
+ testDec, err := Base64Decode(testEnc)
+ if err != nil {
+ t.Error(err)
+ }
+
+ if !bytes.Equal(testDec, raw) {
+ Error(t, raw, testDec)
+ }
+}
diff --git a/header.go b/header.go
new file mode 100644
index 0000000..ef58feb
--- /dev/null
+++ b/header.go
@@ -0,0 +1,119 @@
+package jose
+
+import "encoding/json"
+
+// Header implements a JOSE Header with the addition of some helper
+// methods, similar to net/url.Values.
+type Header map[string]interface{}
+
+// Get retrieves the value corresponding with key from the Header.
+func (h Header) Get(key string) interface{} {
+ if h == nil {
+ return nil
+ }
+ return h[key]
+}
+
+// Set sets Claims[key] = val. It'll overwrite without warning.
+func (h Header) Set(key string, val interface{}) {
+ h[key] = val
+}
+
+// Del removes the value that corresponds with key from the Header.
+func (h Header) Del(key string) {
+ delete(h, key)
+}
+
+// Has returns true if a value for the given key exists inside the Header.
+func (h Header) Has(key string) bool {
+ _, ok := h[key]
+ return ok
+}
+
+// MarshalJSON implements json.Marshaler for Header.
+func (h Header) MarshalJSON() ([]byte, error) {
+ if h == nil || len(h) == 0 {
+ return nil, nil
+ }
+ b, err := json.Marshal(map[string]interface{}(h))
+ if err != nil {
+ return nil, err
+ }
+ return EncodeEscape(b), nil
+}
+
+// Base64 implements the Encoder interface.
+func (h Header) Base64() ([]byte, error) {
+ return h.MarshalJSON()
+}
+
+// Protected Headers are base64-encoded after they're marshaled into
+// JSON.
+type Protected Header
+
+// Get retrieves the value corresponding with key from the Protected Header.
+func (p Protected) Get(key string) interface{} {
+ if p == nil {
+ return nil
+ }
+ return p[key]
+}
+
+// Set sets Protected[key] = val. It'll overwrite without warning.
+func (p Protected) Set(key string, val interface{}) {
+ p[key] = val
+}
+
+// Del removes the value that corresponds with key from the Protected Header.
+func (p Protected) Del(key string) {
+ delete(p, key)
+}
+
+// Has returns true if a value for the given key exists inside the Protected
+// Header.
+func (p Protected) Has(key string) bool {
+ _, ok := p[key]
+ return ok
+}
+
+// MarshalJSON implements json.Marshaler for Protected.
+func (p Protected) MarshalJSON() ([]byte, error) {
+ b, err := json.Marshal(map[string]interface{}(p))
+ if err != nil {
+ return nil, err
+ }
+ return EncodeEscape(b), nil
+}
+
+// Base64 implements the Encoder interface.
+func (p Protected) Base64() ([]byte, error) {
+ b, err := json.Marshal(map[string]interface{}(p))
+ if err != nil {
+ return nil, err
+ }
+ return Base64Encode(b), nil
+}
+
+// UnmarshalJSON implements json.Unmarshaler for Protected.
+func (p *Protected) UnmarshalJSON(b []byte) error {
+ b, err := DecodeEscaped(b)
+ if err != nil {
+ return err
+ }
+
+ // Since json.Unmarshal calls UnmarshalJSON,
+ // calling json.Unmarshal on *p would be infinitely recursive
+ // A temp variable is needed because &Header(*p) is invalid Go.
+
+ tmp := map[string]interface{}(*p)
+ if err = json.Unmarshal(b, &tmp); err != nil {
+ return err
+ }
+ *p = Protected(tmp)
+ return nil
+}
+
+var (
+ _ json.Marshaler = (Protected)(nil)
+ _ json.Unmarshaler = (*Protected)(nil)
+)
diff --git a/header_test.go b/header_test.go
new file mode 100644
index 0000000..ead6e57
--- /dev/null
+++ b/header_test.go
@@ -0,0 +1,27 @@
+package jose
+
+import (
+ "encoding/json"
+ "testing"
+)
+
+func TestMarshalProtectedHeader(t *testing.T) {
+ p := Protected{
+ "alg": "HM256",
+ }
+
+ b, err := json.Marshal(p)
+ if err != nil {
+ t.Error(err)
+ }
+
+ var p2 Protected
+
+ if json.Unmarshal(b, &p2); err != nil {
+ t.Error(err)
+ }
+
+ if p2["alg"] != p["alg"] {
+ Error(t, p["alg"], p2["alg"])
+ }
+}
diff --git a/jws/ecdsa.go b/jws/ecdsa.go
new file mode 100644
index 0000000..21a6328
--- /dev/null
+++ b/jws/ecdsa.go
@@ -0,0 +1,113 @@
+package jws
+
+import (
+ "crypto"
+ "crypto/ecdsa"
+ "crypto/rand"
+ "encoding/asn1"
+ "encoding/json"
+ "errors"
+ "math/big"
+)
+
+// ErrECDSAVerification is missing from crypto/ecdsa compared to crypto/rsa
+var ErrECDSAVerification = errors.New("crypto/ecdsa: verification error")
+
+// SigningMethodECDSA implements the ECDSA family of signing methods signing
+// methods
+type SigningMethodECDSA struct {
+ Name string
+ Hash crypto.Hash
+ _ struct{}
+}
+
+// ECPoint is a marshalling structure for the EC points R and S.
+type ECPoint struct {
+ R *big.Int
+ S *big.Int
+}
+
+// Specific instances of EC SigningMethods.
+var (
+ // SigningMethodES256 implements ES256.
+ SigningMethodES256 = &SigningMethodECDSA{
+ Name: "ES256",
+ Hash: crypto.SHA256,
+ }
+
+ // SigningMethodES384 implements ES384.
+ SigningMethodES384 = &SigningMethodECDSA{
+ Name: "ES384",
+ Hash: crypto.SHA384,
+ }
+
+ // SigningMethodES512 implements ES512.
+ SigningMethodES512 = &SigningMethodECDSA{
+ Name: "ES512",
+ Hash: crypto.SHA512,
+ }
+)
+
+// Alg returns the name of the SigningMethodECDSA instance.
+func (m *SigningMethodECDSA) Alg() string { return m.Name }
+
+// Verify implements the Verify method from SigningMethod.
+// For this verify method, key must be an *ecdsa.PublicKey.
+func (m *SigningMethodECDSA) Verify(raw []byte, signature Signature, key interface{}) error {
+
+ ecdsaKey, ok := key.(*ecdsa.PublicKey)
+ if !ok {
+ return ErrInvalidKey
+ }
+
+ // Unmarshal asn1 ECPoint
+ var ecpoint ECPoint
+ if _, err := asn1.Unmarshal(signature, &ecpoint); err != nil {
+ return err
+ }
+
+ // Verify the signature
+ if !ecdsa.Verify(ecdsaKey, m.sum(raw), ecpoint.R, ecpoint.S) {
+ return ErrECDSAVerification
+ }
+ return nil
+}
+
+// Sign implements the Sign method from SigningMethod.
+// For this signing method, key must be an *ecdsa.PrivateKey.
+func (m *SigningMethodECDSA) Sign(data []byte, key interface{}) (Signature, error) {
+
+ ecdsaKey, ok := key.(*ecdsa.PrivateKey)
+ if !ok {
+ return nil, ErrInvalidKey
+ }
+
+ r, s, err := ecdsa.Sign(rand.Reader, ecdsaKey, m.sum(data))
+ if err != nil {
+ return nil, err
+ }
+
+ signature, err := asn1.Marshal(ECPoint{R: r, S: s})
+ if err != nil {
+ return nil, err
+ }
+ return Signature(signature), nil
+}
+
+func (m *SigningMethodECDSA) sum(b []byte) []byte {
+ h := m.Hash.New()
+ h.Write(b)
+ return h.Sum(nil)
+}
+
+// Hasher implements the Hasher method from SigningMethod.
+func (m *SigningMethodECDSA) Hasher() crypto.Hash { return m.Hash }
+
+// MarshalJSON is in case somebody decides to place SigningMethodECDSA
+// inside the Claims in the "alg" portion. In order to keep things sane,
+// marshalling this will do the same thing as jws.SetProtected("alg", m.Alg())
+func (m *SigningMethodECDSA) MarshalJSON() ([]byte, error) {
+ return []byte(`"` + m.Alg() + `"`), nil
+}
+
+var _ json.Marshaler = (*SigningMethodECDSA)(nil)
diff --git a/jws/ecdsa_test.go b/jws/ecdsa_test.go
new file mode 100644
index 0000000..30a0f60
--- /dev/null
+++ b/jws/ecdsa_test.go
@@ -0,0 +1,100 @@
+package jws
+
+// import (
+// "crypto/ecdsa"
+// "io/ioutil"
+// "strings"
+// "testing"
+
+// "github.com/dgrijalva/jwt-go"
+// )
+
+// var ecdsaTestData = []struct {
+// name string
+// keys map[string]string
+// tokenString string
+// alg string
+// claims map[string]interface{}
+// valid bool
+// }{
+// {
+// "Basic ES256",
+// map[string]string{"private": "test/ec256-private.pem", "public": "test/ec256-public.pem"},
+// "eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIifQ.MEQCIHoSJnmGlPaVQDqacx_2XlXEhhqtWceVopjomc2PJLtdAiAUTeGPoNYxZw0z8mgOnnIcjoxRuNDVZvybRZF3wR1l8w",
+// "ES256",
+// map[string]interface{}{"foo": "bar"},
+// true,
+// },
+// {
+// "Basic ES384",
+// map[string]string{"private": "test/ec384-private.pem", "public": "test/ec384-public.pem"},
+// "eyJhbGciOiJFUzM4NCIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIifQ.MGUCMQCHBr61FXDuFY9xUhyp8iWQAuBIaSgaf1z2j_8XrKcCfzTPzoSa3SZKq-m3L492xe8CMG3kafRMeuaN5Aw8ZJxmOLhkTo4D3-LaGzcaUWINvWvkwFMl7dMC863s0gov6xvXuA",
+// "ES384",
+// map[string]interface{}{"foo": "bar"},
+// true,
+// },
+// {
+// "Basic ES512",
+// map[string]string{"private": "test/ec512-private.pem", "public": "test/ec512-public.pem"},
+// "eyJhbGciOiJFUzUxMiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIifQ.MIGIAkIAmVKjdJE5lG1byOFgZZVTeNDRp6E7SNvUj0UrvpzoBH6nrleWVTcwfHzbwWuooNpPADDSFR_Ql3ze-Vwwi8hBqQsCQgHn-ZooL8zegkOVeEEsqd7WHWdhb8UekFCYw3X8JnNP-D3wvZQ1-tkkHakt5gZ2-xO29TxfSPun4ViGkMYa7Q4N-Q",
+// "ES512",
+// map[string]interface{}{"foo": "bar"},
+// true,
+// },
+// {
+// "basic ES256 invalid: foo => bar",
+// map[string]string{"private": "test/ec256-private.pem", "public": "test/ec256-public.pem"},
+// "eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIifQ.MEQCIHoSJnmGlPaVQDqacx_2XlXEhhqtWceVopjomc2PJLtdAiAUTeGPoNYxZw0z8mgOnnIcjoxRuNDVZvybRZF3wR1l8W",
+// "ES256",
+// map[string]interface{}{"foo": "bar"},
+// false,
+// },
+// }
+
+// func TestECDSAVerify(t *testing.T) {
+// for _, data := range ecdsaTestData {
+// var err error
+
+// key, _ := ioutil.ReadFile(data.keys["public"])
+
+// var ecdsaKey *ecdsa.PublicKey
+// if ecdsaKey, err = jwt.ParseECPublicKeyFromPEM(key); err != nil {
+// t.Errorf("Unable to parse ECDSA public key: %v", err)
+// }
+
+// parts := strings.Split(data.tokenString, ".")
+
+// method := jwt.GetSigningMethod(data.alg)
+// err = method.Verify(strings.Join(parts[0:2], "."), parts[2], ecdsaKey)
+// if data.valid && err != nil {
+// t.Errorf("[%v] Error while verifying key: %v", data.name, err)
+// }
+// if !data.valid && err == nil {
+// t.Errorf("[%v] Invalid key passed validation", data.name)
+// }
+// }
+// }
+
+// func TestECDSASign(t *testing.T) {
+// for _, data := range ecdsaTestData {
+// var err error
+// key, _ := ioutil.ReadFile(data.keys["private"])
+
+// var ecdsaKey *ecdsa.PrivateKey
+// if ecdsaKey, err = jwt.ParseECPrivateKeyFromPEM(key); err != nil {
+// t.Errorf("Unable to parse ECDSA private key: %v", err)
+// }
+
+// if data.valid {
+// parts := strings.Split(data.tokenString, ".")
+// method := jwt.GetSigningMethod(data.alg)
+// sig, err := method.Sign(strings.Join(parts[0:2], "."), ecdsaKey)
+// if err != nil {
+// t.Errorf("[%v] Error signing token: %v", data.name, err)
+// }
+// if sig == parts[2] {
+// t.Errorf("[%v] Identical signatures\nbefore:\n%v\nafter:\n%v", data.name, parts[2], sig)
+// }
+// }
+// }
+// }
diff --git a/jws/ecdsa_utils.go b/jws/ecdsa_utils.go
new file mode 100644
index 0000000..e08a346
--- /dev/null
+++ b/jws/ecdsa_utils.go
@@ -0,0 +1,48 @@
+package jws
+
+import (
+ "crypto/ecdsa"
+ "crypto/x509"
+ "encoding/pem"
+ "errors"
+)
+
+// ECDSA parsing errors.
+var (
+ ErrNotECPublicKey = errors.New("Key is not a valid ECDSA public key")
+ ErrNotECPrivateKey = errors.New("Key is not a valid ECDSA private key")
+)
+
+// ParseECPrivateKeyFromPEM will parse a PEM encoded EC Private
+// Key Structure.
+func ParseECPrivateKeyFromPEM(key []byte) (*ecdsa.PrivateKey, error) {
+ block, _ := pem.Decode(key)
+ if block == nil {
+ return nil, ErrKeyMustBePEMEncoded
+ }
+ return x509.ParseECPrivateKey(block.Bytes)
+}
+
+// ParseECPublicKeyFromPEM will parse a PEM encoded PKCS1 or PKCS8 public key
+func ParseECPublicKeyFromPEM(key []byte) (*ecdsa.PublicKey, error) {
+
+ block, _ := pem.Decode(key)
+ if block == nil {
+ return nil, ErrKeyMustBePEMEncoded
+ }
+
+ parsedKey, err := x509.ParsePKIXPublicKey(block.Bytes)
+ if err != nil {
+ cert, err := x509.ParseCertificate(block.Bytes)
+ if err != nil {
+ return nil, err
+ }
+ parsedKey = cert.PublicKey
+ }
+
+ pkey, ok := parsedKey.(*ecdsa.PublicKey)
+ if !ok {
+ return nil, ErrNotECPublicKey
+ }
+ return pkey, nil
+}
diff --git a/jws/errors.go b/jws/errors.go
new file mode 100644
index 0000000..6c792a2
--- /dev/null
+++ b/jws/errors.go
@@ -0,0 +1,17 @@
+package jws
+
+import "errors"
+
+var (
+ // ErrInvalidKey means the key argument passed to SigningMethod.Verify
+ // was not the correct type.
+ ErrInvalidKey = errors.New("key is invalid")
+
+ // ErrNotEnoughMethods is returned if New was called _or_ the Flat/Compact
+ // methods were called with 0 SigningMethods.
+ ErrNotEnoughMethods = errors.New("not enough methods provided")
+
+ // ErrCouldNotUnmarshal is returned when Parse's json.Unmarshaler
+ // parameter returns an error.
+ ErrCouldNotUnmarshal = errors.New("custom unmarshal failed")
+)
diff --git a/jws/hmac.go b/jws/hmac.go
new file mode 100644
index 0000000..1d25d72
--- /dev/null
+++ b/jws/hmac.go
@@ -0,0 +1,79 @@
+package jws
+
+import (
+ "crypto"
+ "crypto/hmac"
+ "encoding/json"
+ "errors"
+)
+
+// SigningMethodHMAC implements the HMAC-SHA family of SigningMethods.
+type SigningMethodHMAC struct {
+ Name string
+ Hash crypto.Hash
+ _ struct{}
+}
+
+// Specific instances of HMAC-SHA SigningMethods.
+var (
+ // SigningMethodHS256 implements HS256.
+ SigningMethodHS256 = &SigningMethodHMAC{
+ Name: "HS256",
+ Hash: crypto.SHA256,
+ }
+
+ // SigningMethodHS384 implements HS384.
+ SigningMethodHS384 = &SigningMethodHMAC{
+ Name: "HS384",
+ Hash: crypto.SHA384,
+ }
+
+ // SigningMethodHS512 implements HS512.
+ SigningMethodHS512 = &SigningMethodHMAC{
+ Name: "HS512",
+ Hash: crypto.SHA512,
+ }
+
+ // ErrSignatureInvalid is returned when the provided signature is found
+ // to be invalid.
+ ErrSignatureInvalid = errors.New("signature is invalid")
+)
+
+// Alg implements the SigningMethod interface.
+func (m *SigningMethodHMAC) Alg() string { return m.Name }
+
+// Verify implements the Verify method from SigningMethod.
+// For this signing method, must be a []byte.
+func (m *SigningMethodHMAC) Verify(raw []byte, signature Signature, key interface{}) error {
+ keyBytes, ok := key.([]byte)
+ if !ok {
+ return ErrInvalidKey
+ }
+ hasher := hmac.New(m.Hash.New, keyBytes)
+ if hmac.Equal(signature, hasher.Sum(raw)) {
+ return nil
+ }
+ return ErrSignatureInvalid
+}
+
+// Sign implements the Sign method from SigningMethod for this signing method.
+// Key must be a []byte.
+func (m *SigningMethodHMAC) Sign(data []byte, key interface{}) (Signature, error) {
+ keyBytes, ok := key.([]byte)
+ if !ok {
+ return nil, ErrInvalidKey
+ }
+ hasher := hmac.New(m.Hash.New, keyBytes)
+ return Signature(hasher.Sum(data)), nil
+}
+
+// Hasher implements the SigningMethod interface.
+func (m *SigningMethodHMAC) Hasher() crypto.Hash { return m.Hash }
+
+// MarshalJSON implements json.Marshaler.
+// See SigningMethodECDSA.MarshalJSON() for information.
+func (m *SigningMethodHMAC) MarshalJSON() ([]byte, error) {
+ return []byte(`"` + m.Alg() + `"`), nil
+}
+
+var _ json.Marshaler = (*SigningMethodHMAC)(nil)
diff --git a/jws/hmac_test.go b/jws/hmac_test.go
new file mode 100644
index 0000000..86b357e
--- /dev/null
+++ b/jws/hmac_test.go
@@ -0,0 +1,91 @@
+package jws
+
+// import (
+// "github.com/dgrijalva/jwt-go"
+// "io/ioutil"
+// "strings"
+// "testing"
+// )
+
+// var hmacTestData = []struct {
+// name string
+// tokenString string
+// alg string
+// claims map[string]interface{}
+// valid bool
+// }{
+// {
+// "web sample",
+// "eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ.dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk",
+// "HS256",
+// map[string]interface{}{"iss": "joe", "exp": 1300819380, "http://example.com/is_root": true},
+// true,
+// },
+// {
+// "HS384",
+// "eyJhbGciOiJIUzM4NCIsInR5cCI6IkpXVCJ9.eyJleHAiOjEuMzAwODE5MzhlKzA5LCJodHRwOi8vZXhhbXBsZS5jb20vaXNfcm9vdCI6dHJ1ZSwiaXNzIjoiam9lIn0.KWZEuOD5lbBxZ34g7F-SlVLAQ_r5KApWNWlZIIMyQVz5Zs58a7XdNzj5_0EcNoOy",
+// "HS384",
+// map[string]interface{}{"iss": "joe", "exp": 1300819380, "http://example.com/is_root": true},
+// true,
+// },
+// {
+// "HS512",
+// "eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJleHAiOjEuMzAwODE5MzhlKzA5LCJodHRwOi8vZXhhbXBsZS5jb20vaXNfcm9vdCI6dHJ1ZSwiaXNzIjoiam9lIn0.CN7YijRX6Aw1n2jyI2Id1w90ja-DEMYiWixhYCyHnrZ1VfJRaFQz1bEbjjA5Fn4CLYaUG432dEYmSbS4Saokmw",
+// "HS512",
+// map[string]interface{}{"iss": "joe", "exp": 1300819380, "http://example.com/is_root": true},
+// true,
+// },
+// {
+// "web sample: invalid",
+// "eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ.dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXo",
+// "HS256",
+// map[string]interface{}{"iss": "joe", "exp": 1300819380, "http://example.com/is_root": true},
+// false,
+// },
+// }
+
+// // Sample data from http://tools.ietf.org/html/draft-jones-json-web-signature-04#appendix-A.1
+// var hmacTestKey, _ = ioutil.ReadFile("test/hmacTestKey")
+
+// func TestHMACVerify(t *testing.T) {
+// for _, data := range hmacTestData {
+// parts := strings.Split(data.tokenString, ".")
+
+// method := jwt.GetSigningMethod(data.alg)
+// err := method.Verify(strings.Join(parts[0:2], "."), parts[2], hmacTestKey)
+// if data.valid && err != nil {
+// t.Errorf("[%v] Error while verifying key: %v", data.name, err)
+// }
+// if !data.valid && err == nil {
+// t.Errorf("[%v] Invalid key passed validation", data.name)
+// }
+// }
+// }
+
+// func TestHMACSign(t *testing.T) {
+// for _, data := range hmacTestData {
+// if data.valid {
+// parts := strings.Split(data.tokenString, ".")
+// method := jwt.GetSigningMethod(data.alg)
+// sig, err := method.Sign(strings.Join(parts[0:2], "."), hmacTestKey)
+// if err != nil {
+// t.Errorf("[%v] Error signing token: %v", data.name, err)
+// }
+// if sig != parts[2] {
+// t.Errorf("[%v] Incorrect signature.\nwas:\n%v\nexpecting:\n%v", data.name, sig, parts[2])
+// }
+// }
+// }
+// }
+
+// func BenchmarkHS256Signing(b *testing.B) {
+// benchmarkSigning(b, jwt.SigningMethodHS256, hmacTestKey)
+// }
+
+// func BenchmarkHS384Signing(b *testing.B) {
+// benchmarkSigning(b, jwt.SigningMethodHS384, hmacTestKey)
+// }
+
+// func BenchmarkHS512Signing(b *testing.B) {
+// benchmarkSigning(b, jwt.SigningMethodHS512, hmacTestKey)
+// }
diff --git a/jws/jws.go b/jws/jws.go
new file mode 100644
index 0000000..de0f355
--- /dev/null
+++ b/jws/jws.go
@@ -0,0 +1,175 @@
+package jws
+
+import (
+ "encoding/json"
+ "fmt"
+
+ "github.com/SermoDigital/jose"
+)
+
+// JWS represents a specific JWS.
+type JWS struct {
+ payload *payload
+ plcache rawBase64
+ clean bool
+
+ sb []sigHead
+ methods []SigningMethod
+}
+
+// sigHead represents the 'signatures' member of the JWS' "general"
+// serialization form per
+// https://tools.ietf.org/html/rfc7515#section-7.2.1
+//
+// It's embedded inside the "flat" structure in order to properly
+// create the "flat" JWS.
+type sigHead struct {
+ Protected rawBase64 `json:"protected,omitempty"`
+ Unprotected rawBase64 `json:"header,omitempty"`
+ Signature Signature `json:"signature"`
+
+ protected jose.Protected `json:"-"`
+ unprotected jose.Header `json:"-"`
+ clean bool `json:"-"`
+}
+
+// New creates a new JWS with the provided SigningMethods.
+func New(content interface{}, methods ...SigningMethod) *JWS {
+ sb := make([]sigHead, len(methods))
+ for i := range methods {
+ sb[i] = sigHead{
+ protected: jose.Protected{
+ "alg": methods[i].Alg(),
+ },
+ unprotected: make(jose.Header),
+ }
+ }
+ return &JWS{
+ payload: &payload{v: content},
+ sb: sb,
+ methods: methods,
+ }
+}
+
+type generic struct {
+ Payload rawBase64 `json:"payload"`
+ sigHead
+ Signatures []sigHead `json:"signatures,omitempty"`
+}
+
+// Parse parses any of the three serialized JWS forms into a physical
+// JWS per https://tools.ietf.org/html/rfc7515#section-5.2
+//
+// It accepts a json.Unmarshaler in order to properly parse
+// the payload. The reason for this is sometimes the payload
+// might implement the json.Marshaler interface, and since
+// the JWS' payload member is an interface{}, a simple
+// json.Unmarshal call cannot magically identify the original
+// type. So, in order to keep the caller from having to do extra
+// parsing of the payload, the a json.Unmarshaler can be passed
+// which will be called to unmarshal the payload however the caller
+// wishes. Do note that if json.Unmarshal returns an error the
+// original payload will be used as if no json.Unmarshaler was
+// passed.
+//
+// Internally, Parse applies some heuristics and then calls either
+// ParseGeneral, ParseFlat, or ParseCompact.
+// It should only be called if, for whatever reason, you do not
+// know which form the serialized JWT is in.
+func Parse(encoded []byte, u ...json.Unmarshaler) (*JWS, error) {
+ // Try and unmarshal into a generic struct that'll
+ // hopefully hold either of the two JSON serialization
+ // formats.s
+ var g generic
+
+ // Not valid JSON. Let's try compact.
+ if err := json.Unmarshal(encoded, &g); err != nil {
+ return ParseCompact(encoded, u...)
+ }
+
+ if g.Signatures == nil {
+ return g.parseFlat(u...)
+ }
+ return g.parseGeneral(u...)
+}
+
+// ParseGeneral parses a JWS serialized into its "general" form per
+// https://tools.ietf.org/html/rfc7515#section-7.2.1
+// into a physical JWS per
+// https://tools.ietf.org/html/rfc7515#section-5.2
+//
+// For information on the json.Unmarshaler parameter, see Parse.
+func ParseGeneral(encoded []byte, u ...json.Unmarshaler) (*JWS, error) {
+ var g generic
+ if err := json.Unmarshal(encoded, &g); err != nil {
+ return nil, err
+ }
+ return g.parseGeneral(u...)
+}
+
+func (g *generic) parseGeneral(u ...json.Unmarshaler) (*JWS, error) {
+
+ var (
+ p payload
+ err error
+ )
+
+ if len(u) > 0 {
+ if k := u[0]; k.UnmarshalJSON(g.Payload) != nil {
+ p.v = u
+ err = ErrCouldNotUnmarshal
+ }
+ }
+
+ if err != nil {
+ fmt.Println(string(g.Payload))
+ if err := json.Unmarshal(g.Payload, &p); err != nil {
+ return nil, err
+ }
+ }
+
+ return &JWS{
+ payload: &p,
+ sb: g.Signatures,
+ }, err
+}
+
+// ParseFlat parses a JWS serialized into its "flat" form per
+// https://tools.ietf.org/html/rfc7515#section-7.2.2
+// into a physical JWS per
+// https://tools.ietf.org/html/rfc7515#section-5.2
+//
+// For information on the json.Unmarshaler parameter, see Parse.
+func ParseFlat(encoded []byte, u ...json.Unmarshaler) (*JWS, error) {
+ var g generic
+ if err := json.Unmarshal(encoded, &g); err != nil {
+ return nil, err
+ }
+ return g.parseFlat(u...)
+}
+
+func (g *generic) parseFlat(u ...json.Unmarshaler) (*JWS, error) {
+
+ var p payload
+ if len(u) > 0 {
+ p.u = u[0]
+ }
+
+ if err := p.UnmarshalJSON(g.Payload); err != nil {
+ return nil, err
+ }
+
+ return &JWS{
+ payload: &p,
+ sb: []sigHead{g.sigHead},
+ }, nil
+}
+
+// ParseCompact parses a JWS serialized into its "compact" form per
+// https://tools.ietf.org/html/rfc7515#section-7.1
+// into a physical JWS per
+// https://tools.ietf.org/html/rfc7515#section-5.2//
+// For information on the json.Unmarshaler parameter, see Parse.
+func ParseCompact(encoded []byte, u ...json.Unmarshaler) (*JWS, error) {
+ return nil, nil
+}
diff --git a/jws/jws_serialize.go b/jws/jws_serialize.go
new file mode 100644
index 0000000..a79c59c
--- /dev/null
+++ b/jws/jws_serialize.go
@@ -0,0 +1,124 @@
+package jws
+
+import (
+ "bytes"
+ "encoding/json"
+)
+
+// Flat serializes the JWS to its "flattened" form per
+// https://tools.ietf.org/html/rfc7515#section-7.2.2
+func (j *JWS) Flat(key interface{}) ([]byte, error) {
+ if len(j.sb) < 1 {
+ return nil, ErrNotEnoughMethods
+ }
+
+ if err := j.sign(key); err != nil {
+ return nil, err
+ }
+ return json.Marshal(struct {
+ Payload rawBase64 `json:"payload"`
+ sigHead
+ }{
+ Payload: j.plcache,
+ sigHead: j.sb[0],
+ })
+}
+
+// General serializes the JWS into its "general" form per
+// https://tools.ietf.org/html/rfc7515#section-7.2.1
+func (j *JWS) General(keys ...interface{}) ([]byte, error) {
+ if err := j.sign(keys...); err != nil {
+ return nil, err
+ }
+ return json.Marshal(struct {
+ Payload rawBase64 `json:"payload"`
+ Signatures []sigHead `json:"signatures"`
+ }{
+ Payload: j.plcache,
+ Signatures: j.sb,
+ })
+}
+
+// Compact serializes the JWS into its "compact" form per
+// https://tools.ietf.org/html/rfc7515#section-7.1
+func (j *JWS) Compact(key interface{}) ([]byte, error) {
+ if len(j.sb) < 1 {
+ return nil, ErrNotEnoughMethods
+ }
+
+ if err := j.sign(key); err != nil {
+ return nil, err
+ }
+
+ sig, err := j.sb[0].Signature.Base64()
+ if err != nil {
+ return nil, err
+ }
+ return format(
+ j.sb[0].Protected,
+ j.plcache,
+ sig,
+ ), nil
+}
+
+// sign signs each index of j's sb member.
+func (j *JWS) sign(keys ...interface{}) error {
+ if err := j.cache(); err != nil {
+ return err
+ }
+
+ for i := range j.sb {
+ if err := j.sb[i].cache(); err != nil {
+ return err
+ }
+
+ raw := format(j.sb[i].Protected, j.plcache)
+ sig, err := j.methods[i].Sign(raw, keys[i])
+ if err != nil {
+ return err
+ }
+ j.sb[i].Signature = sig
+ }
+
+ return nil
+}
+
+// cache marshals the payload, but only if it's changed since the last cache.
+func (j *JWS) cache() error {
+ if !j.clean {
+ var err error
+ j.plcache, err = j.payload.Base64()
+ j.clean = err == nil
+ return err
+ }
+ return nil
+}
+
+// cache marshals the protected and unprotected headers, but only if
+// they've changed since their last cache.
+func (s *sigHead) cache() error {
+ if !s.clean {
+ var err error
+
+ s.Protected, err = s.protected.Base64()
+ if err != nil {
+ goto err_return
+ }
+
+ s.Unprotected, err = s.unprotected.Base64()
+ if err != nil {
+ goto err_return
+ }
+
+ err_return:
+ s.clean = err == nil
+ return err
+ }
+ return nil
+}
+
+// format formats a slice of bytes in the order given, joining
+// them with a period.
+func format(a ...[]byte) []byte {
+ return bytes.Join(a, []byte{'.'})
+}
diff --git a/jws/jws_serialize_test.go b/jws/jws_serialize_test.go
new file mode 100644
index 0000000..61a828e
--- /dev/null
+++ b/jws/jws_serialize_test.go
@@ -0,0 +1,111 @@
+package jws
+
+import (
+ "bytes"
+ "encoding/json"
+ "testing"
+
+ "github.com/SermoDigital/jose"
+)
+
+var dataRaw = struct {
+ Name string
+ Scopes []string
+ Admin bool
+ Data struct{ Foo, Bar int }
+}{
+ Name: "Eric",
+ Scopes: []string{
+ "user.account.info",
+ "user.account.update",
+ "user.account.delete",
+ },
+ Admin: true,
+ Data: struct {
+ Foo, Bar int
+ }{
+ Foo: 12,
+ Bar: int(^uint(0) >> 1),
+ },
+}
+
+var dataSerialized []byte
+
+func init() {
+ var err error
+ dataSerialized, err = json.Marshal(dataRaw)
+ if err != nil {
+ panic(err)
+ }
+}
+
+func TestGeneralIntegrity(t *testing.T) {
+ j := New(dataRaw, SigningMethodRS512)
+ b, err := j.General(rsaPriv)
+ if err != nil {
+ t.Error(err)
+ }
+
+ var jj struct {
+ Payload json.RawMessage `json:"payload"`
+ Signatures []sigHead `json:"signatures"`
+ }
+
+ if err := json.Unmarshal(b, &jj); err != nil {
+ t.Error(err)
+ }
+
+ got, err := jose.DecodeEscaped(jj.Payload)
+ if err != nil {
+ t.Error(err)
+ }
+
+ if !bytes.Equal(got, dataSerialized) {
+ Error(t, dataSerialized, got)
+ }
+}
+
+func TestFlatIntegrity(t *testing.T) {
+ j := New(dataRaw, SigningMethodRS512)
+ b, err := j.Flat(rsaPriv)
+ if err != nil {
+ t.Error(err)
+ }
+
+ var jj struct {
+ Payload json.RawMessage `json:"payload"`
+ sigHead
+ }
+
+ if err := json.Unmarshal(b, &jj); err != nil {
+ t.Error(err)
+ }
+
+ got, err := jose.DecodeEscaped(jj.Payload)
+ if err != nil {
+ t.Error(err)
+ }
+
+ if !bytes.Equal(got, dataSerialized) {
+ Error(t, dataSerialized, got)
+ }
+}
+
+func TestCompactIntegrity(t *testing.T) {
+ j := New(dataRaw, SigningMethodRS512)
+ b, err := j.Compact(rsaPriv)
+ if err != nil {
+ t.Error(err)
+ }
+
+ parts := bytes.Split(b, []byte{'.'})
+
+ dec, err := jose.Base64Decode(parts[1])
+ if err != nil {
+ t.Error(err)
+ }
+
+ if !bytes.Equal(dec, dataSerialized) {
+ Error(t, dec, dataSerialized)
+ }
+}
diff --git a/jws/jws_test.go b/jws/jws_test.go
new file mode 100644
index 0000000..8afb931
--- /dev/null
+++ b/jws/jws_test.go
@@ -0,0 +1,44 @@
+package jws
+
+import (
+ "bytes"
+ "encoding/base64"
+ "encoding/json"
+ "testing"
+)
+
+type easy []byte
+
+func (e *easy) UnmarshalJSON(b []byte) error {
+ // json.Marshal encodes easy as it would a []byte, so in
+ // `"base64"` format.
+ dst := make([]byte, base64.StdEncoding.DecodedLen(len(b)-2))
+ n, err := base64.StdEncoding.Decode(dst, b[1:len(b)-1])
+ if err != nil {
+ return err
+ }
+ *e = easy(dst[:n])
+ return nil
+}
+
+var _ json.Unmarshaler = (*easy)(nil)
+
+var easyData = easy(`"easy data!"`)
+
+func TestParseWithUnmarshaler(t *testing.T) {
+ j := New(easyData, SigningMethodRS512)
+ b, err := j.Flat(rsaPriv)
+ if err != nil {
+ t.Error(err)
+ }
+
+ var e easy
+ j2, err := Parse(b, &e)
+ if err != nil {
+ t.Error(err)
+ }
+
+ if !bytes.Equal(easyData, *j2.payload.v.(*easy)) {
+ Error(t, easyData, *j2.payload.v.(*easy))
+ }
+}
diff --git a/jws/none.go b/jws/none.go
new file mode 100644
index 0000000..a1b66b2
--- /dev/null
+++ b/jws/none.go
@@ -0,0 +1,67 @@
+package jws
+
+import (
+ "crypto"
+ "encoding/json"
+ "hash"
+ "io"
+)
+
+func init() {
+ crypto.RegisterHash(crypto.Hash(0), H)
+ RegisterSigningMethod(Unsecured)
+}
+
+// H is passed to crypto.RegisterHash.
+func H() hash.Hash { return &f{Writer: nil} }
+
+type f struct{ io.Writer }
+
+// Sum helps implement the hash.Hash interface.
+func (f *f) Sum(b []byte) []byte { return nil }
+
+// Reset helps implement the hash.Hash interface.
+func (f *f) Reset() {}
+
+// Size helps implement the hash.Hash interface.
+func (f *f) Size() int { return -1 }
+
+// BlockSize helps implement the hash.Hash interface.
+func (f *f) BlockSize() int { return -1 }
+
+// Unsecured is the default "none" algorithm.
+var Unsecured = &SigningMethodNone{
+ Name: "none",
+ Hash: crypto.Hash(0),
+}
+
+// SigningMethodNone is the default "none" algorithm.
+type SigningMethodNone struct {
+ Name string
+ Hash crypto.Hash
+ _ struct{}
+}
+
+// Verify helps implement the SigningMethod interface.
+func (m *SigningMethodNone) Verify(_ []byte, _ Signature, _ interface{}) error {
+ return nil
+}
+
+// Sign helps implement the SigningMethod interface.
+func (m *SigningMethodNone) Sign(_ []byte, _ interface{}) (Signature, error) {
+ return nil, nil
+}
+
+// Alg helps implement the SigningMethod interface.
+func (m *SigningMethodNone) Alg() string { return m.Name }
+
+// Hasher helps implement the SigningMethod interface.
+func (m *SigningMethodNone) Hasher() crypto.Hash { return m.Hash }
+
+// MarshalJSON implements json.Marshaler.
+// See SigningMethodECDSA.MarshalJSON() for information.
+func (m *SigningMethodNone) MarshalJSON() ([]byte, error) {
+ return []byte(`"` + m.Alg() + `"`), nil
+}
+
+var _ json.Marshaler = (*SigningMethodNone)(nil)
diff --git a/jws/payload.go b/jws/payload.go
new file mode 100644
index 0000000..3cc203c
--- /dev/null
+++ b/jws/payload.go
@@ -0,0 +1,53 @@
+package jws
+
+import (
+ "encoding/json"
+
+ "github.com/SermoDigital/jose"
+)
+
+// payload represents the payload of a JWS.
+type payload struct {
+ v interface{}
+ u json.Unmarshaler
+ _ struct{}
+}
+
+// MarshalJSON implements json.Marshaler for payload.
+func (p *payload) MarshalJSON() ([]byte, error) {
+ b, err := json.Marshal(p.v)
+ if err != nil {
+ return nil, err
+ }
+ return jose.EncodeEscape(b), nil
+}
+
+// Base64 implements jose.Encoder.
+func (p *payload) Base64() ([]byte, error) {
+ b, err := json.Marshal(p.v)
+ if err != nil {
+ return nil, err
+ }
+ return jose.Base64Encode(b), nil
+}
+
+// MarshalJSON implements json.Unmarshaler for payload.
+func (p *payload) UnmarshalJSON(b []byte) error {
+ b2, err := jose.DecodeEscaped(b)
+ if err != nil {
+ return err
+ }
+
+ if p.u != nil {
+ err := p.u.UnmarshalJSON(b2)
+ p.v = p.u
+ return err
+ }
+ return json.Unmarshal(b2, &p.v)
+}
+
+var (
+ _ json.Marshaler = (*payload)(nil)
+ _ json.Unmarshaler = (*payload)(nil)
+ _ jose.Encoder = (*payload)(nil)
+)
diff --git a/jws/payload_test.go b/jws/payload_test.go
new file mode 100644
index 0000000..9b075f6
--- /dev/null
+++ b/jws/payload_test.go
@@ -0,0 +1,60 @@
+package jws
+
+import (
+ "encoding/json"
+ "testing"
+
+ "github.com/SermoDigital/jose"
+)
+
+func TestPayloadMarshal(t *testing.T) {
+ p := &payload{v: "Test string!"}
+
+ enc, err := json.Marshal(p)
+ if err != nil {
+ t.Error(err)
+ }
+
+ var pp payload
+ if err = json.Unmarshal(enc, &pp); err != nil {
+ t.Error(err)
+ }
+
+ if pp.v != "Test string!" {
+ Error(t, "Test string!", pp.v)
+ }
+}
+
+func TestComplexPayloadMarshal(t *testing.T) {
+ p := payload{
+ v: map[string]interface{}{
+ "alg": "HM256",
+ "typ": "JWT",
+ },
+ }
+
+ enc, err := json.Marshal(&p)
+ if err != nil {
+ t.Error(err)
+ }
+
+ var pp payload
+ if err = json.Unmarshal(enc, &pp); err != nil {
+ t.Error(err)
+ }
+
+ h, ok := pp.v.(map[string]interface{})
+ if !ok {
+ ErrorTypes(t, map[string]interface{}{}, pp.v)
+ }
+
+ ph := jose.Protected(h)
+
+ if alg := ph.Get("alg"); alg != "HM256" {
+ Error(t, "HM256", alg)
+ }
+
+ if typ := ph.Get("typ"); typ != "JWT" {
+ Error(t, "JWT", typ)
+ }
+}
diff --git a/jws/rawbase64.go b/jws/rawbase64.go
new file mode 100644
index 0000000..f2c4060
--- /dev/null
+++ b/jws/rawbase64.go
@@ -0,0 +1,28 @@
+package jws
+
+import "encoding/json"
+
+type rawBase64 []byte
+
+// MarshalJSON implements json.Marshaler for rawBase64.
+func (r rawBase64) MarshalJSON() ([]byte, error) {
+ buf := make([]byte, len(r)+2)
+ buf[0] = '"'
+ copy(buf[1:], r)
+ buf[len(buf)-1] = '"'
+ return buf, nil
+}
+
+// MarshalJSON implements json.Unmarshaler for rawBase64.
+func (r *rawBase64) UnmarshalJSON(b []byte) error {
+ if len(b) > 1 && b[0] == '"' && b[len(b)-1] == '"' {
+ b = b[1 : len(b)-1]
+ }
+ *r = rawBase64(b)
+ return nil
+}
+
+var (
+ _ json.Marshaler = (rawBase64)(nil)
+ _ json.Unmarshaler = (*rawBase64)(nil)
+)
diff --git a/jws/rawbase64_test.go b/jws/rawbase64_test.go
new file mode 100644
index 0000000..9a7635c
--- /dev/null
+++ b/jws/rawbase64_test.go
@@ -0,0 +1,25 @@
+package jws
+
+import (
+ "bytes"
+ "encoding/json"
+ "testing"
+)
+
+func TestMarshalRawBase64(t *testing.T) {
+ s := rawBase64("Test string!")
+
+ enc, err := json.Marshal(s)
+ if err != nil {
+ t.Error(err)
+ }
+
+ var ss rawBase64
+ if err = json.Unmarshal(enc, &ss); err != nil {
+ t.Error(err)
+ }
+
+ if !bytes.Equal(ss, s) {
+ Error(t, s, ss)
+ }
+}
diff --git a/jws/rsa.go b/jws/rsa.go
new file mode 100644
index 0000000..9174478
--- /dev/null
+++ b/jws/rsa.go
@@ -0,0 +1,80 @@
+package jws
+
+import (
+ "crypto"
+ "crypto/rand"
+ "crypto/rsa"
+ "encoding/json"
+)
+
+// SigningMethodRSA implements the RSA family of SigningMethods.
+type SigningMethodRSA struct {
+ Name string
+ Hash crypto.Hash
+ _ struct{}
+}
+
+// Specific instances of RSA SigningMethods.
+var (
+ // SigningMethodRS256 implements RS256.
+ SigningMethodRS256 = &SigningMethodRSA{
+ Name: "RS256",
+ Hash: crypto.SHA256,
+ }
+
+ // SigningMethodRS384 implements RS384.
+ SigningMethodRS384 = &SigningMethodRSA{
+ Name: "RS384",
+ Hash: crypto.SHA384,
+ }
+
+ // SigningMethodRS512 implements RS512.
+ SigningMethodRS512 = &SigningMethodRSA{
+ Name: "RS512",
+ Hash: crypto.SHA512,
+ }
+)
+
+// Alg implements the SigningMethod interface.
+func (m *SigningMethodRSA) Alg() string { return m.Name }
+
+// Verify implements the Verify method from SigningMethod.
+// For this signing method, must be an *rsa.PublicKey.
+func (m *SigningMethodRSA) Verify(raw []byte, sig Signature, key interface{}) error {
+ rsaKey, ok := key.(*rsa.PublicKey)
+ if !ok {
+ return ErrInvalidKey
+ }
+ return rsa.VerifyPKCS1v15(rsaKey, m.Hash, m.sum(raw), sig)
+}
+
+// Sign implements the Sign method from SigningMethod.
+// For this signing method, must be an *rsa.PrivateKey structure.
+func (m *SigningMethodRSA) Sign(data []byte, key interface{}) (Signature, error) {
+ rsaKey, ok := key.(*rsa.PrivateKey)
+ if !ok {
+ return nil, ErrInvalidKey
+ }
+ sigBytes, err := rsa.SignPKCS1v15(rand.Reader, rsaKey, m.Hash, m.sum(data))
+ if err != nil {
+ return nil, err
+ }
+ return Signature(sigBytes), nil
+}
+
+func (m *SigningMethodRSA) sum(b []byte) []byte {
+ h := m.Hash.New()
+ h.Write(b)
+ return h.Sum(nil)
+}
+
+// Hasher implements the SigningMethod interface.
+func (m *SigningMethodRSA) Hasher() crypto.Hash { return m.Hash }
+
+// MarshalJSON implements json.Marshaler.
+// See SigningMethodECDSA.MarshalJSON() for information.
+func (m *SigningMethodRSA) MarshalJSON() ([]byte, error) {
+ return []byte(`"` + m.Alg() + `"`), nil
+}
+
+var _ json.Marshaler = (*SigningMethodRSA)(nil)
diff --git a/jws/rsa_pss.go b/jws/rsa_pss.go
new file mode 100644
index 0000000..81330b8
--- /dev/null
+++ b/jws/rsa_pss.go
@@ -0,0 +1,96 @@
+// +build go1.4
+
+package jws
+
+import (
+ "crypto"
+ "crypto/rand"
+ "crypto/rsa"
+ "encoding/json"
+)
+
+// SigningMethodRSAPSS implements the RSAPSS family of SigningMethods.
+type SigningMethodRSAPSS struct {
+ *SigningMethodRSA
+ Options *rsa.PSSOptions
+}
+
+// Specific instances for RS/PS SigningMethods.
+var (
+ // SigningMethodPS256 implements PS256.
+ SigningMethodPS256 = &SigningMethodRSAPSS{
+ &SigningMethodRSA{
+ Name: "PS256",
+ Hash: crypto.SHA256,
+ },
+ &rsa.PSSOptions{
+ SaltLength: rsa.PSSSaltLengthAuto,
+ Hash: crypto.SHA256,
+ },
+ }
+
+ // SigningMethodPS384 implements PS384.
+ SigningMethodPS384 = &SigningMethodRSAPSS{
+ &SigningMethodRSA{
+ Name: "PS384",
+ Hash: crypto.SHA384,
+ },
+ &rsa.PSSOptions{
+ SaltLength: rsa.PSSSaltLengthAuto,
+ Hash: crypto.SHA384,
+ },
+ }
+
+ // SigningMethodPS512 implements PS512.
+ SigningMethodPS512 = &SigningMethodRSAPSS{
+ &SigningMethodRSA{
+ Name: "PS512",
+ Hash: crypto.SHA512,
+ },
+ &rsa.PSSOptions{
+ SaltLength: rsa.PSSSaltLengthAuto,
+ Hash: crypto.SHA512,
+ },
+ }
+)
+
+// Verify implements the Verify method from SigningMethod.
+// For this verify method, key must be an *rsa.PublicKey.
+func (m *SigningMethodRSAPSS) Verify(raw []byte, signature Signature, key interface{}) error {
+ rsaKey, ok := key.(*rsa.PublicKey)
+ if !ok {
+ return ErrInvalidKey
+ }
+ return rsa.VerifyPSS(rsaKey, m.Hash, m.sum(raw), signature, m.Options)
+}
+
+// Sign implements the Sign method from SigningMethod.
+// For this signing method, key must be an *rsa.PrivateKey.
+func (m *SigningMethodRSAPSS) Sign(raw []byte, key interface{}) (Signature, error) {
+ rsaKey, ok := key.(*rsa.PrivateKey)
+ if !ok {
+ return nil, ErrInvalidKey
+ }
+ sigBytes, err := rsa.SignPSS(rand.Reader, rsaKey, m.Hash, m.sum(raw), m.Options)
+ if err != nil {
+ return nil, err
+ }
+ return Signature(sigBytes), nil
+}
+
+func (m *SigningMethodRSAPSS) sum(b []byte) []byte {
+ h := m.Hash.New()
+ h.Write(b)
+ return h.Sum(nil)
+}
+
+// Hasher implements the Hasher method from SigningMethod.
+func (m *SigningMethodRSAPSS) Hasher() crypto.Hash { return m.Hash }
+
+// MarshalJSON implements json.Marshaler.
+// See SigningMethodECDSA.MarshalJSON() for information.
+func (m *SigningMethodRSAPSS) MarshalJSON() ([]byte, error) {
+ return []byte(`"` + m.Alg() + `"`), nil
+}
+
+var _ json.Marshaler = (*SigningMethodRSAPSS)(nil)
diff --git a/jws/rsa_pss_test.go b/jws/rsa_pss_test.go
new file mode 100644
index 0000000..c45ecf7
--- /dev/null
+++ b/jws/rsa_pss_test.go
@@ -0,0 +1,96 @@
+// +build go1.4
+
+package jws_test
+
+// import (
+// "crypto/rsa"
+// "io/ioutil"
+// "strings"
+// "testing"
+
+// "github.com/dgrijalva/jwt-go"
+// )
+
+// var rsaPSSTestData = []struct {
+// name string
+// tokenString string
+// alg string
+// claims map[string]interface{}
+// valid bool
+// }{
+// {
+// "Basic PS256",
+// "eyJhbGciOiJQUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIifQ.PPG4xyDVY8ffp4CcxofNmsTDXsrVG2npdQuibLhJbv4ClyPTUtR5giNSvuxo03kB6I8VXVr0Y9X7UxhJVEoJOmULAwRWaUsDnIewQa101cVhMa6iR8X37kfFoiZ6NkS-c7henVkkQWu2HtotkEtQvN5hFlk8IevXXPmvZlhQhwzB1sGzGYnoi1zOfuL98d3BIjUjtlwii5w6gYG2AEEzp7HnHCsb3jIwUPdq86Oe6hIFjtBwduIK90ca4UqzARpcfwxHwVLMpatKask00AgGVI0ysdk0BLMjmLutquD03XbThHScC2C2_Pp4cHWgMzvbgLU2RYYZcZRKr46QeNgz9w",
+// "PS256",
+// map[string]interface{}{"foo": "bar"},
+// true,
+// },
+// {
+// "Basic PS384",
+// "eyJhbGciOiJQUzM4NCIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIifQ.w7-qqgj97gK4fJsq_DCqdYQiylJjzWONvD0qWWWhqEOFk2P1eDULPnqHRnjgTXoO4HAw4YIWCsZPet7nR3Xxq4ZhMqvKW8b7KlfRTb9cH8zqFvzMmybQ4jv2hKc3bXYqVow3AoR7hN_CWXI3Dv6Kd2X5xhtxRHI6IL39oTVDUQ74LACe-9t4c3QRPuj6Pq1H4FAT2E2kW_0KOc6EQhCLWEhm2Z2__OZskDC8AiPpP8Kv4k2vB7l0IKQu8Pr4RcNBlqJdq8dA5D3hk5TLxP8V5nG1Ib80MOMMqoS3FQvSLyolFX-R_jZ3-zfq6Ebsqr0yEb0AH2CfsECF7935Pa0FKQ",
+// "PS384",
+// map[string]interface{}{"foo": "bar"},
+// true,
+// },
+// {
+// "Basic PS512",
+// "eyJhbGciOiJQUzUxMiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIifQ.GX1HWGzFaJevuSLavqqFYaW8_TpvcjQ8KfC5fXiSDzSiT9UD9nB_ikSmDNyDILNdtjZLSvVKfXxZJqCfefxAtiozEDDdJthZ-F0uO4SPFHlGiXszvKeodh7BuTWRI2wL9-ZO4mFa8nq3GMeQAfo9cx11i7nfN8n2YNQ9SHGovG7_T_AvaMZB_jT6jkDHpwGR9mz7x1sycckEo6teLdHRnH_ZdlHlxqknmyTu8Odr5Xh0sJFOL8BepWbbvIIn-P161rRHHiDWFv6nhlHwZnVzjx7HQrWSGb6-s2cdLie9QL_8XaMcUpjLkfOMKkDOfHo6AvpL7Jbwi83Z2ZTHjJWB-A",
+// "PS512",
+// map[string]interface{}{"foo": "bar"},
+// true,
+// },
+// {
+// "basic PS256 invalid: foo => bar",
+// "eyJhbGciOiJQUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIifQ.PPG4xyDVY8ffp4CcxofNmsTDXsrVG2npdQuibLhJbv4ClyPTUtR5giNSvuxo03kB6I8VXVr0Y9X7UxhJVEoJOmULAwRWaUsDnIewQa101cVhMa6iR8X37kfFoiZ6NkS-c7henVkkQWu2HtotkEtQvN5hFlk8IevXXPmvZlhQhwzB1sGzGYnoi1zOfuL98d3BIjUjtlwii5w6gYG2AEEzp7HnHCsb3jIwUPdq86Oe6hIFjtBwduIK90ca4UqzARpcfwxHwVLMpatKask00AgGVI0ysdk0BLMjmLutquD03XbThHScC2C2_Pp4cHWgMzvbgLU2RYYZcZRKr46QeNgz9W",
+// "PS256",
+// map[string]interface{}{"foo": "bar"},
+// false,
+// },
+// }
+
+// func TestRSAPSSVerify(t *testing.T) {
+// var err error
+
+// key, _ := ioutil.ReadFile("test/sample_key.pub")
+// var rsaPSSKey *rsa.PublicKey
+// if rsaPSSKey, err = jwt.ParseRSAPublicKeyFromPEM(key); err != nil {
+// t.Errorf("Unable to parse RSA public key: %v", err)
+// }
+
+// for _, data := range rsaPSSTestData {
+// parts := strings.Split(data.tokenString, ".")
+
+// method := jwt.GetSigningMethod(data.alg)
+// err := method.Verify(strings.Join(parts[0:2], "."), parts[2], rsaPSSKey)
+// if data.valid && err != nil {
+// t.Errorf("[%v] Error while verifying key: %v", data.name, err)
+// }
+// if !data.valid && err == nil {
+// t.Errorf("[%v] Invalid key passed validation", data.name)
+// }
+// }
+// }
+
+// func TestRSAPSSSign(t *testing.T) {
+// var err error
+
+// key, _ := ioutil.ReadFile("test/sample_key")
+// var rsaPSSKey *rsa.PrivateKey
+// if rsaPSSKey, err = jwt.ParseRSAPrivateKeyFromPEM(key); err != nil {
+// t.Errorf("Unable to parse RSA private key: %v", err)
+// }
+
+// for _, data := range rsaPSSTestData {
+// if data.valid {
+// parts := strings.Split(data.tokenString, ".")
+// method := jwt.GetSigningMethod(data.alg)
+// sig, err := method.Sign(strings.Join(parts[0:2], "."), rsaPSSKey)
+// if err != nil {
+// t.Errorf("[%v] Error signing token: %v", data.name, err)
+// }
+// if sig == parts[2] {
+// t.Errorf("[%v] Signatures shouldn't match\nnew:\n%v\noriginal:\n%v", data.name, sig, parts[2])
+// }
+// }
+// }
+// }
diff --git a/jws/rsa_test.go b/jws/rsa_test.go
new file mode 100644
index 0000000..7eb9753
--- /dev/null
+++ b/jws/rsa_test.go
@@ -0,0 +1,176 @@
+package jws_test
+
+// import (
+// "github.com/dgrijalva/jwt-go"
+// "io/ioutil"
+// "strings"
+// "testing"
+// )
+
+// var rsaTestData = []struct {
+// name string
+// tokenString string
+// alg string
+// claims map[string]interface{}
+// valid bool
+// }{
+// {
+// "Basic RS256",
+// "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJmb28iOiJiYXIifQ.FhkiHkoESI_cG3NPigFrxEk9Z60_oXrOT2vGm9Pn6RDgYNovYORQmmA0zs1AoAOf09ly2Nx2YAg6ABqAYga1AcMFkJljwxTT5fYphTuqpWdy4BELeSYJx5Ty2gmr8e7RonuUztrdD5WfPqLKMm1Ozp_T6zALpRmwTIW0QPnaBXaQD90FplAg46Iy1UlDKr-Eupy0i5SLch5Q-p2ZpaL_5fnTIUDlxC3pWhJTyx_71qDI-mAA_5lE_VdroOeflG56sSmDxopPEG3bFlSu1eowyBfxtu0_CuVd-M42RU75Zc4Gsj6uV77MBtbMrf4_7M_NUTSgoIF3fRqxrj0NzihIBg",
+// "RS256",
+// map[string]interface{}{"foo": "bar"},
+// true,
+// },
+// {
+// "Basic RS384",
+// "eyJhbGciOiJSUzM4NCIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIifQ.W-jEzRfBigtCWsinvVVuldiuilzVdU5ty0MvpLaSaqK9PlAWWlDQ1VIQ_qSKzwL5IXaZkvZFJXT3yL3n7OUVu7zCNJzdwznbC8Z-b0z2lYvcklJYi2VOFRcGbJtXUqgjk2oGsiqUMUMOLP70TTefkpsgqDxbRh9CDUfpOJgW-dU7cmgaoswe3wjUAUi6B6G2YEaiuXC0XScQYSYVKIzgKXJV8Zw-7AN_DBUI4GkTpsvQ9fVVjZM9csQiEXhYekyrKu1nu_POpQonGd8yqkIyXPECNmmqH5jH4sFiF67XhD7_JpkvLziBpI-uh86evBUadmHhb9Otqw3uV3NTaXLzJw",
+// "RS384",
+// map[string]interface{}{"foo": "bar"},
+// true,
+// },
+// {
+// "Basic RS512",
+// "eyJhbGciOiJSUzUxMiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIifQ.zBlLlmRrUxx4SJPUbV37Q1joRcI9EW13grnKduK3wtYKmDXbgDpF1cZ6B-2Jsm5RB8REmMiLpGms-EjXhgnyh2TSHE-9W2gA_jvshegLWtwRVDX40ODSkTb7OVuaWgiy9y7llvcknFBTIg-FnVPVpXMmeV_pvwQyhaz1SSwSPrDyxEmksz1hq7YONXhXPpGaNbMMeDTNP_1oj8DZaqTIL9TwV8_1wb2Odt_Fy58Ke2RVFijsOLdnyEAjt2n9Mxihu9i3PhNBkkxa2GbnXBfq3kzvZ_xxGGopLdHhJjcGWXO-NiwI9_tiu14NRv4L2xC0ItD9Yz68v2ZIZEp_DuzwRQ",
+// "RS512",
+// map[string]interface{}{"foo": "bar"},
+// true,
+// },
+// {
+// "basic invalid: foo => bar",
+// "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJmb28iOiJiYXIifQ.EhkiHkoESI_cG3NPigFrxEk9Z60_oXrOT2vGm9Pn6RDgYNovYORQmmA0zs1AoAOf09ly2Nx2YAg6ABqAYga1AcMFkJljwxTT5fYphTuqpWdy4BELeSYJx5Ty2gmr8e7RonuUztrdD5WfPqLKMm1Ozp_T6zALpRmwTIW0QPnaBXaQD90FplAg46Iy1UlDKr-Eupy0i5SLch5Q-p2ZpaL_5fnTIUDlxC3pWhJTyx_71qDI-mAA_5lE_VdroOeflG56sSmDxopPEG3bFlSu1eowyBfxtu0_CuVd-M42RU75Zc4Gsj6uV77MBtbMrf4_7M_NUTSgoIF3fRqxrj0NzihIBg",
+// "RS256",
+// map[string]interface{}{"foo": "bar"},
+// false,
+// },
+// }
+
+// func TestRSAVerify(t *testing.T) {
+// keyData, _ := ioutil.ReadFile("test/sample_key.pub")
+// key, _ := jwt.ParseRSAPublicKeyFromPEM(keyData)
+
+// for _, data := range rsaTestData {
+// parts := strings.Split(data.tokenString, ".")
+
+// method := jwt.GetSigningMethod(data.alg)
+// err := method.Verify(strings.Join(parts[0:2], "."), parts[2], key)
+// if data.valid && err != nil {
+// t.Errorf("[%v] Error while verifying key: %v", data.name, err)
+// }
+// if !data.valid && err == nil {
+// t.Errorf("[%v] Invalid key passed validation", data.name)
+// }
+// }
+// }
+
+// func TestRSASign(t *testing.T) {
+// keyData, _ := ioutil.ReadFile("test/sample_key")
+// key, _ := jwt.ParseRSAPrivateKeyFromPEM(keyData)
+
+// for _, data := range rsaTestData {
+// if data.valid {
+// parts := strings.Split(data.tokenString, ".")
+// method := jwt.GetSigningMethod(data.alg)
+// sig, err := method.Sign(strings.Join(parts[0:2], "."), key)
+// if err != nil {
+// t.Errorf("[%v] Error signing token: %v", data.name, err)
+// }
+// if sig != parts[2] {
+// t.Errorf("[%v] Incorrect signature.\nwas:\n%v\nexpecting:\n%v", data.name, sig, parts[2])
+// }
+// }
+// }
+// }
+
+// func TestRSAVerifyWithPreParsedPrivateKey(t *testing.T) {
+// key, _ := ioutil.ReadFile("test/sample_key.pub")
+// parsedKey, err := jwt.ParseRSAPublicKeyFromPEM(key)
+// if err != nil {
+// t.Fatal(err)
+// }
+// testData := rsaTestData[0]
+// parts := strings.Split(testData.tokenString, ".")
+// err = jwt.SigningMethodRS256.Verify(strings.Join(parts[0:2], "."), parts[2], parsedKey)
+// if err != nil {
+// t.Errorf("[%v] Error while verifying key: %v", testData.name, err)
+// }
+// }
+
+// func TestRSAWithPreParsedPrivateKey(t *testing.T) {
+// key, _ := ioutil.ReadFile("test/sample_key")
+// parsedKey, err := jwt.ParseRSAPrivateKeyFromPEM(key)
+// if err != nil {
+// t.Fatal(err)
+// }
+// testData := rsaTestData[0]
+// parts := strings.Split(testData.tokenString, ".")
+// sig, err := jwt.SigningMethodRS256.Sign(strings.Join(parts[0:2], "."), parsedKey)
+// if err != nil {
+// t.Errorf("[%v] Error signing token: %v", testData.name, err)
+// }
+// if sig != parts[2] {
+// t.Errorf("[%v] Incorrect signature.\nwas:\n%v\nexpecting:\n%v", testData.name, sig, parts[2])
+// }
+// }
+
+// func TestRSAKeyParsing(t *testing.T) {
+// key, _ := ioutil.ReadFile("test/sample_key")
+// pubKey, _ := ioutil.ReadFile("test/sample_key.pub")
+// badKey := []byte("All your base are belong to key")
+
+// // Test parsePrivateKey
+// if _, e := jwt.ParseRSAPrivateKeyFromPEM(key); e != nil {
+// t.Errorf("Failed to parse valid private key: %v", e)
+// }
+
+// if k, e := jwt.ParseRSAPrivateKeyFromPEM(pubKey); e == nil {
+// t.Errorf("Parsed public key as valid private key: %v", k)
+// }
+
+// if k, e := jwt.ParseRSAPrivateKeyFromPEM(badKey); e == nil {
+// t.Errorf("Parsed invalid key as valid private key: %v", k)
+// }
+
+// // Test parsePublicKey
+// if _, e := jwt.ParseRSAPublicKeyFromPEM(pubKey); e != nil {
+// t.Errorf("Failed to parse valid public key: %v", e)
+// }
+
+// if k, e := jwt.ParseRSAPublicKeyFromPEM(key); e == nil {
+// t.Errorf("Parsed private key as valid public key: %v", k)
+// }
+
+// if k, e := jwt.ParseRSAPublicKeyFromPEM(badKey); e == nil {
+// t.Errorf("Parsed invalid key as valid private key: %v", k)
+// }
+
+// }
+
+// func BenchmarkRS256Signing(b *testing.B) {
+// key, _ := ioutil.ReadFile("test/sample_key")
+// parsedKey, err := jwt.ParseRSAPrivateKeyFromPEM(key)
+// if err != nil {
+// b.Fatal(err)
+// }
+
+// benchmarkSigning(b, jwt.SigningMethodRS256, parsedKey)
+// }
+
+// func BenchmarkRS384Signing(b *testing.B) {
+// key, _ := ioutil.ReadFile("test/sample_key")
+// parsedKey, err := jwt.ParseRSAPrivateKeyFromPEM(key)
+// if err != nil {
+// b.Fatal(err)
+// }
+
+// benchmarkSigning(b, jwt.SigningMethodRS384, parsedKey)
+// }
+
+// func BenchmarkRS512Signing(b *testing.B) {
+// key, _ := ioutil.ReadFile("test/sample_key")
+// parsedKey, err := jwt.ParseRSAPrivateKeyFromPEM(key)
+// if err != nil {
+// b.Fatal(err)
+// }
+
+// benchmarkSigning(b, jwt.SigningMethodRS512, parsedKey)
+// }
diff --git a/jws/rsa_utils.go b/jws/rsa_utils.go
new file mode 100644
index 0000000..d8a1a75
--- /dev/null
+++ b/jws/rsa_utils.go
@@ -0,0 +1,69 @@
+package jws
+
+import (
+ "crypto/rsa"
+ "crypto/x509"
+ "encoding/pem"
+ "errors"
+)
+
+// Errors specific to rsa_utils.
+var (
+ ErrKeyMustBePEMEncoded = errors.New("Invalid Key: Key must be PEM encoded PKCS1 or PKCS8 private key")
+ ErrNotRSAPrivateKey = errors.New("Key is not a valid RSA private key")
+)
+
+// ParseRSAPrivateKeyFromPEM parses a PEM encoded PKCS1 or PKCS8 private key.
+func ParseRSAPrivateKeyFromPEM(key []byte) (*rsa.PrivateKey, error) {
+ var err error
+
+ // Parse PEM block
+ var block *pem.Block
+ if block, _ = pem.Decode(key); block == nil {
+ return nil, ErrKeyMustBePEMEncoded
+ }
+
+ var parsedKey interface{}
+ if parsedKey, err = x509.ParsePKCS1PrivateKey(block.Bytes); err != nil {
+ if parsedKey, err = x509.ParsePKCS8PrivateKey(block.Bytes); err != nil {
+ return nil, err
+ }
+ }
+
+ var pkey *rsa.PrivateKey
+ var ok bool
+ if pkey, ok = parsedKey.(*rsa.PrivateKey); !ok {
+ return nil, ErrNotRSAPrivateKey
+ }
+
+ return pkey, nil
+}
+
+// ParseRSAPublicKeyFromPEM parses PEM encoded PKCS1 or PKCS8 public key.
+func ParseRSAPublicKeyFromPEM(key []byte) (*rsa.PublicKey, error) {
+ var err error
+
+ // Parse PEM block
+ var block *pem.Block
+ if block, _ = pem.Decode(key); block == nil {
+ return nil, ErrKeyMustBePEMEncoded
+ }
+
+ // Parse the key
+ var parsedKey interface{}
+ if parsedKey, err = x509.ParsePKIXPublicKey(block.Bytes); err != nil {
+ if cert, err := x509.ParseCertificate(block.Bytes); err == nil {
+ parsedKey = cert.PublicKey
+ } else {
+ return nil, err
+ }
+ }
+
+ var pkey *rsa.PublicKey
+ var ok bool
+ if pkey, ok = parsedKey.(*rsa.PublicKey); !ok {
+ return nil, ErrNotRSAPrivateKey
+ }
+
+ return pkey, nil
+}
diff --git a/jws/signature.go b/jws/signature.go
new file mode 100644
index 0000000..6fbdc78
--- /dev/null
+++ b/jws/signature.go
@@ -0,0 +1,36 @@
+package jws
+
+import (
+ "encoding/json"
+
+ "github.com/SermoDigital/jose"
+)
+
+// Signature is a JWS signature.
+type Signature []byte
+
+// MarshalJSON implements json.Marshaler for a signature.
+func (s Signature) MarshalJSON() ([]byte, error) {
+ return jose.EncodeEscape(s), nil
+}
+
+// Base64 helps implements jose.Encoder for Signature.
+func (s Signature) Base64() ([]byte, error) {
+ return jose.Base64Encode(s), nil
+}
+
+// UnmarshalJSON implements json.Unmarshaler for signature.
+func (s *Signature) UnmarshalJSON(b []byte) error {
+ dec, err := jose.DecodeEscaped(b)
+ if err != nil {
+ return err
+ }
+ *s = Signature(dec)
+ return nil
+}
+
+var (
+ _ json.Marshaler = (Signature)(nil)
+ _ json.Unmarshaler = (*Signature)(nil)
+ _ jose.Encoder = (Signature)(nil)
+)
diff --git a/jws/signature_test.go b/jws/signature_test.go
new file mode 100644
index 0000000..74714be
--- /dev/null
+++ b/jws/signature_test.go
@@ -0,0 +1,25 @@
+package jws
+
+import (
+ "bytes"
+ "encoding/json"
+ "testing"
+)
+
+func TestMarshalSignature(t *testing.T) {
+ s := Signature("Test string!")
+
+ enc, err := json.Marshal(s)
+ if err != nil {
+ t.Error(err)
+ }
+
+ var ss Signature
+ if err = json.Unmarshal(enc, &ss); err != nil {
+ t.Error(err)
+ }
+
+ if !bytes.Equal(ss, s) {
+ Error(t, s, ss)
+ }
+}
diff --git a/jws/signing_methods.go b/jws/signing_methods.go
new file mode 100644
index 0000000..bc21d69
--- /dev/null
+++ b/jws/signing_methods.go
@@ -0,0 +1,79 @@
+package jws
+
+import (
+ "crypto"
+ "sync"
+)
+
+var (
+ mu = &sync.RWMutex{}
+
+ signingMethods = map[string]SigningMethod{
+ SigningMethodES256.Alg(): SigningMethodES256,
+ SigningMethodES384.Alg(): SigningMethodES384,
+ SigningMethodES512.Alg(): SigningMethodES512,
+
+ SigningMethodPS256.Alg(): SigningMethodPS256,
+ SigningMethodPS384.Alg(): SigningMethodPS384,
+ SigningMethodPS512.Alg(): SigningMethodPS512,
+
+ SigningMethodRS256.Alg(): SigningMethodRS256,
+ SigningMethodRS384.Alg(): SigningMethodRS384,
+ SigningMethodRS512.Alg(): SigningMethodRS512,
+
+ SigningMethodHS256.Alg(): SigningMethodHS256,
+ SigningMethodHS384.Alg(): SigningMethodHS384,
+ SigningMethodHS512.Alg(): SigningMethodHS512,
+ }
+)
+
+// SigningMethod is an interface that provides a way to sign JWS tokens.
+type SigningMethod interface {
+ // Alg describes the signing algorithm, and is used to uniquely
+ // describe the specific SigningMethod.
+ Alg() string
+
+ // Verify accepts the raw content, the signature, and the key used
+ // to sign the raw content, and returns any errors found while validating
+ // the signature and content.
+ Verify(raw []byte, sig Signature, key interface{}) error
+
+ // Sign returns a Signature for the raw bytes, as well as any errors
+ // that occurred during the signing.
+ Sign(raw []byte, key interface{}) (Signature, error)
+
+ // Used to cause quick panics when a SigningMethod whose form of hashing
+ // isn't linked in the binary when you register a SigningMethod.
+ // To spoof this, see "SigningMethodNone".
+ Hasher() crypto.Hash
+}
+
+// RegisterSigningMethod registers the SigningMethod in the global map.
+// This is typically done inside the caller's init function.
+func RegisterSigningMethod(sm SigningMethod) {
+ if GetSigningMethod(sm.Alg()) != nil {
+ panic("jose/jws: cannot duplicate signing methods")
+ }
+
+ if !sm.Hasher().Available() {
+ panic("jose/jws: specific hash is unavailable")
+ }
+
+ mu.Lock()
+ signingMethods[sm.Alg()] = sm
+ mu.Unlock()
+}
+
+// RemoveSigningMethod removes the SigningMethod from the global map.
+func RemoveSigningMethod(sm SigningMethod) {
+ mu.Lock()
+ delete(signingMethods, sm.Alg())
+ mu.Unlock()
+}
+
+// GetSigningMethod retrieves a SigningMethod from the global map.
+func GetSigningMethod(alg string) SigningMethod {
+ mu.RLock()
+ defer mu.RUnlock()
+ return signingMethods[alg]
+}
diff --git a/jws/signing_methods_test.go b/jws/signing_methods_test.go
new file mode 100644
index 0000000..59d52c2
--- /dev/null
+++ b/jws/signing_methods_test.go
@@ -0,0 +1,69 @@
+package jws
+
+import (
+ "crypto"
+ "hash"
+ "io"
+ "testing"
+)
+
+func init() { crypto.RegisterHash(crypto.Hash(0), HH) }
+
+func HH() hash.Hash { return &ff{Writer: nil} }
+
+type ff struct{ io.Writer }
+
+func (f *ff) Sum(b []byte) []byte { return nil }
+func (f *ff) Reset() {}
+func (f *ff) Size() int { return -1 }
+func (f *ff) BlockSize() int { return -1 }
+
+// MySigningMethod is the default "none" algorithm.
+var MySigningMethod = &TestSigningMethod{
+ Name: "SuperSignerAlgorithm1000",
+ Hash: crypto.Hash(0),
+}
+
+type TestSigningMethod struct {
+ Name string
+ Hash crypto.Hash
+}
+
+func (m *TestSigningMethod) Verify(_ []byte, _ Signature, _ interface{}) error {
+ return nil
+}
+
+func (m *TestSigningMethod) Sign(_ []byte, _ interface{}) (Signature, error) {
+ return nil, nil
+}
+
+func (m *TestSigningMethod) Alg() string { return m.Name }
+func (m *TestSigningMethod) Sum(b []byte) []byte { return nil }
+func (m *TestSigningMethod) Hasher() crypto.Hash { return m.Hash }
+
+// GetSigningMethod is implicitly tested inside the following two functions.
+
+func TestRegisterSigningMethod(t *testing.T) {
+
+ RegisterSigningMethod(MySigningMethod)
+
+ if GetSigningMethod("SuperSignerAlgorithm1000") == nil {
+ t.Error("Expected SuperSignerAlgorithm1000, got nil")
+ }
+
+ RemoveSigningMethod(MySigningMethod)
+}
+
+func TestRemoveSigningMethod(t *testing.T) {
+ RegisterSigningMethod(MySigningMethod)
+
+ if GetSigningMethod("SuperSignerAlgorithm1000") == nil {
+ t.Error("Expected SuperSignerAlgorithm1000, got nil")
+ }
+
+ RemoveSigningMethod(MySigningMethod)
+
+ if a := GetSigningMethod("SuperSignerAlgorithm1000"); a != nil {
+ t.Errorf("Expected nil, got %v", a)
+ }
+}
diff --git a/jws/stubs_test.go b/jws/stubs_test.go
new file mode 100644
index 0000000..0b9e9b2
--- /dev/null
+++ b/jws/stubs_test.go
@@ -0,0 +1,118 @@
+package jws
+
+import (
+ "crypto/ecdsa"
+ "crypto/rsa"
+ "crypto/x509"
+ "encoding/pem"
+ "fmt"
+ "io/ioutil"
+ "path/filepath"
+ "testing"
+)
+
+func Error(t *testing.T, want, got interface{}) {
+ format := "\nWanted: %s\nGot: %s"
+
+ switch want.(type) {
+ case []byte, string, nil, rawBase64, easy:
+ default:
+ format = fmt.Sprintf(format, "%v", "%v")
+ }
+
+ t.Errorf(format, want, got)
+}
+
+func ErrorTypes(t *testing.T, want, got interface{}) {
+ t.Errorf("\nWanted: %T\nGot: %T", want, got)
+}
+
+var (
+ rsaPriv *rsa.PrivateKey
+ rsaPub interface{}
+ ec256Priv *ecdsa.PrivateKey
+ ec256Pub *ecdsa.PublicKey
+ ec384Priv *ecdsa.PrivateKey
+ ec384Pub *ecdsa.PublicKey
+ ec512Priv *ecdsa.PrivateKey
+ ec512Pub *ecdsa.PublicKey
+ hm256 interface{}
+)
+
+func init() {
+ derBytes, err := ioutil.ReadFile(filepath.Join("test", "sample_key.pub"))
+ if err != nil {
+ panic(err)
+ }
+ block, _ := pem.Decode(derBytes)
+
+ rsaPub, err = x509.ParsePKIXPublicKey(block.Bytes)
+ if err != nil {
+ panic(err)
+ }
+
+ der, err := ioutil.ReadFile(filepath.Join("test", "sample_key.priv"))
+ if err != nil {
+ panic(err)
+ }
+ block2, _ := pem.Decode(der)
+
+ rsaPriv, err = x509.ParsePKCS1PrivateKey(block2.Bytes)
+ if err != nil {
+ panic(err)
+ }
+
+ ecData, err := ioutil.ReadFile(filepath.Join("test", "ec256-private.pem"))
+ if err != nil {
+ panic(err)
+ }
+ ec256Priv, err = ParseECPrivateKeyFromPEM(ecData)
+ if err != nil {
+ panic(err)
+ }
+ ecData, err = ioutil.ReadFile(filepath.Join("test", "ec256-public.pem"))
+ if err != nil {
+ panic(err)
+ }
+ ec256Pub, err = ParseECPublicKeyFromPEM(ecData)
+ if err != nil {
+ panic(err)
+ }
+ ecData, err = ioutil.ReadFile(filepath.Join("test", "ec384-private.pem"))
+ if err != nil {
+ panic(err)
+ }
+ ec384Priv, err = ParseECPrivateKeyFromPEM(ecData)
+ if err != nil {
+ panic(err)
+ }
+ ecData, err = ioutil.ReadFile(filepath.Join("test", "ec384-public.pem"))
+ if err != nil {
+ panic(err)
+ }
+ ec384Pub, err = ParseECPublicKeyFromPEM(ecData)
+ if err != nil {
+ panic(err)
+ }
+ ecData, err = ioutil.ReadFile(filepath.Join("test", "ec512-private.pem"))
+ if err != nil {
+ panic(err)
+ }
+ ec512Priv, err = ParseECPrivateKeyFromPEM(ecData)
+ if err != nil {
+ panic(err)
+ }
+ ecData, err = ioutil.ReadFile(filepath.Join("test", "ec512-public.pem"))
+ if err != nil {
+ panic(err)
+ }
+ ec512Pub, err = ParseECPublicKeyFromPEM(ecData)
+ if err != nil {
+ panic(err)
+ }
+
+ hm256, err = ioutil.ReadFile(filepath.Join("test", "hmacTestKey"))
+ if err != nil {
+ panic(err)
+ }
+}
diff --git a/jws/test/ec256-private.pem b/jws/test/ec256-private.pem
new file mode 100644
index 0000000..a6882b3
--- /dev/null
+++ b/jws/test/ec256-private.pem
@@ -0,0 +1,5 @@
+-----BEGIN EC PRIVATE KEY-----
+MHcCAQEEIAh5qA3rmqQQuu0vbKV/+zouz/y/Iy2pLpIcWUSyImSwoAoGCCqGSM49
+AwEHoUQDQgAEYD54V/vp+54P9DXarYqx4MPcm+HKRIQzNasYSoRQHQ/6S6Ps8tpM
+cT+KvIIC8W/e9k0W7Cm72M1P9jU7SLf/vg==
+-----END EC PRIVATE KEY-----
diff --git a/jws/test/ec256-public.pem b/jws/test/ec256-public.pem
new file mode 100644
index 0000000..7191361
--- /dev/null
+++ b/jws/test/ec256-public.pem
@@ -0,0 +1,4 @@
+-----BEGIN PUBLIC KEY-----
+MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEYD54V/vp+54P9DXarYqx4MPcm+HK
+RIQzNasYSoRQHQ/6S6Ps8tpMcT+KvIIC8W/e9k0W7Cm72M1P9jU7SLf/vg==
+-----END PUBLIC KEY-----
diff --git a/jws/test/ec384-private.pem b/jws/test/ec384-private.pem
new file mode 100644
index 0000000..a86c823
--- /dev/null
+++ b/jws/test/ec384-private.pem
@@ -0,0 +1,6 @@
+-----BEGIN EC PRIVATE KEY-----
+MIGkAgEBBDCaCvMHKhcG/qT7xsNLYnDT7sE/D+TtWIol1ROdaK1a564vx5pHbsRy
+SEKcIxISi1igBwYFK4EEACKhZANiAATYa7rJaU7feLMqrAx6adZFNQOpaUH/Uylb
+ZLriOLON5YFVwtVUpO1FfEXZUIQpptRPtc5ixIPY658yhBSb6irfIJUSP9aYTflJ
+GKk/mDkK4t8mWBzhiD5B6jg9cEGhGgA=
+-----END EC PRIVATE KEY-----
diff --git a/jws/test/ec384-public.pem b/jws/test/ec384-public.pem
new file mode 100644
index 0000000..e80d005
--- /dev/null
+++ b/jws/test/ec384-public.pem
@@ -0,0 +1,5 @@
+-----BEGIN PUBLIC KEY-----
+MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE2Gu6yWlO33izKqwMemnWRTUDqWlB/1Mp
+W2S64jizjeWBVcLVVKTtRXxF2VCEKabUT7XOYsSD2OufMoQUm+oq3yCVEj/WmE35
+SRipP5g5CuLfJlgc4Yg+Qeo4PXBBoRoA
+-----END PUBLIC KEY-----
diff --git a/jws/test/ec512-private.pem b/jws/test/ec512-private.pem
new file mode 100644
index 0000000..213afaf
--- /dev/null
+++ b/jws/test/ec512-private.pem
@@ -0,0 +1,7 @@
+-----BEGIN EC PRIVATE KEY-----
+MIHcAgEBBEIB0pE4uFaWRx7t03BsYlYvF1YvKaBGyvoakxnodm9ou0R9wC+sJAjH
+QZZJikOg4SwNqgQ/hyrOuDK2oAVHhgVGcYmgBwYFK4EEACOhgYkDgYYABAAJXIuw
+12MUzpHggia9POBFYXSxaOGKGbMjIyDI+6q7wi7LMw3HgbaOmgIqFG72o8JBQwYN
+4IbXHf+f86CRY1AA2wHzbHvt6IhkCXTNxBEffa1yMUgu8n9cKKF2iLgyQKcKqW33
+8fGOw/n3Rm2Yd/EB56u2rnD29qS+nOM9eGS+gy39OQ==
+-----END EC PRIVATE KEY-----
diff --git a/jws/test/ec512-public.pem b/jws/test/ec512-public.pem
new file mode 100644
index 0000000..02ea022
--- /dev/null
+++ b/jws/test/ec512-public.pem
@@ -0,0 +1,6 @@
+-----BEGIN PUBLIC KEY-----
+MIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQACVyLsNdjFM6R4IImvTzgRWF0sWjh
+ihmzIyMgyPuqu8IuyzMNx4G2jpoCKhRu9qPCQUMGDeCG1x3/n/OgkWNQANsB82x7
+7eiIZAl0zcQRH32tcjFILvJ/XCihdoi4MkCnCqlt9/HxjsP590ZtmHfxAeertq5w
+9vakvpzjPXhkvoMt/Tk=
+-----END PUBLIC KEY-----
diff --git a/jws/test/hmacTestKey b/jws/test/hmacTestKey
new file mode 100644
index 0000000..435b8dd
--- /dev/null
+++ b/jws/test/hmacTestKey
@@ -0,0 +1 @@
+#5K+¥¼~ew{¦Z³(æðTÉ(©²ÒP.¿ÓûZÒGï´Ãwb="=.!r.OÀÍõgУ
\ No newline at end of file
diff --git a/jws/test/sample_key.priv b/jws/test/sample_key.priv
new file mode 100644
index 0000000..227abc9
--- /dev/null
+++ b/jws/test/sample_key.priv
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEowIBAAKCAQEA1/yKYrWdyTMGWKZ2ZUrY7T9h1VyZCOJPo7jobC3/4gLQqb0v
+GLJ3KQxN3IyW+XHBElGLGS3ol4swnq+EutFZFqq2Rkl2NTn5KTuvNqgxLj0aDYae
+fD576ip623rZVG8KsfdtW5kYgb/Vna83Isc6rbNbuwZGSqRF7BN9VJTWQyrjT+ul
+e0FCW060LbWfp3WPDfJION5AznukFHsl/zPFSMfreobaaY+L4CQiRmbI0eNgnibP
+Ej1IyCwHhzAIfR8Dhw5JYXOGYnB4adePD1/ImahR93opO/BylUxHcIPg4cW0G7KA
+G7lZXG7bbQoP8MJMubsDqnJ6O2XZ5+2U+BSZeQIDAQABAoIBAF43P3aHmuilZWNO
+Wd8dozr6pwaXefoyHmNgyaJG/pPsfnFMoTq4vtodD+nhOgpd4MbI41B8gjV49iCC
+l/eWUrJcjV5jtTjKrTGbY3cIL0voQ3EbBA9Wgl9HYIhVMnBub8/qCr4mLlaLA/fg
+8sAtAB+9WYpf1lHBDu9IISfANSbexVuw7rpSbIkSsvsDtqgfQlAZZCmt7b/yTBKJ
+nbr/xAqS8PWFUqO3OJR/S+a95joRhs3G8uLcb50k9bWIayBLEO/lFfKQhyBPF5j3
+2GpzW/TKw8YPQaKt0uKZwiYerYIS7HYbl3xBMLkxqcDiseorqRTjmorNgxtaKbRh
+ig6V9zECgYEA6tIQLlWT5XX7FOg4yGKe6YFwl37lMWFTBuVPCq562cbDYzYxRYS8
+xTexwki3ONcuKKlnWPIaLq/ds8j3k71uw6jXE/kLKq/cwu3+hWUkWaBOKejQ4tZ5
+MvzLMdlB0klZJejUm1plic4Sqq5EdjELi2MOKaJXXEvwktxidTYCD+sCgYEA63ea
+rr7LSDuM5YiNjbKbc5Y3XPQf5LaNRA7902uuSkzoKur7EKPUos4zi2K3OBRI9w7P
+AEOarh8kbcwQQs93EiwIYFu0miinMgbU1aKIdYcQgdAX/rRqje22YGsV5S6AsvCi
+9Ok5VOPEgWMkbMCPN64s6RcKxConilOeVk9FhysCgYEAm9GIqzZeTYVcdyMhitbm
+XExoSh8KNxo93fWL3aqqGpiqTstHJrpOCRBMBH/8FNu05YaD7aG5fgF1PDe/EWpC
+ddMz5Sz9G/ZWp4MFwb67wD1/oI+9DMkE47CbpRhldQT3AxDdA7mYZzk3acr54vPo
+lTXF0BrJnZVGri1PqDeOZsECgYA1xlfeJpaYRXlVBk2Fmc+kox67rMkH26UBcQcI
+3KOuNkgjXQy/bRrnn33oDEBZn1BWk9w0bDwMHfKc3KiuCWFhFgtHrkndlwEwDJ1Y
+uMi/Rnw/mT2RnrRi+3HWLT6P24Hq28frdMPyHh5HilPMPmG2zqFVDj0YNaRYE+aq
+QWpYhwKBgFfiE7ld9WMymVdT65PNJVTf2XR94ec99Y/BK8RC2HsqFLiRIMIKf5cz
+qouqEutKwXdF5tRS9681Bzu4txwzHuRH+r/Y1cIhPq4/slE727aaEedqvgY51C0m
+U92DBkXyBEtk3G07Xqk3Z6NKLDbMCnxhOYz9XN2iOMJRwkBi00xK
+-----END RSA PRIVATE KEY-----
diff --git a/jws/test/sample_key.pub b/jws/test/sample_key.pub
new file mode 100644
index 0000000..f2f7ffb
--- /dev/null
+++ b/jws/test/sample_key.pub
@@ -0,0 +1,9 @@
+-----BEGIN PUBLIC KEY-----
+MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1/yKYrWdyTMGWKZ2ZUrY
+7T9h1VyZCOJPo7jobC3/4gLQqb0vGLJ3KQxN3IyW+XHBElGLGS3ol4swnq+EutFZ
+Fqq2Rkl2NTn5KTuvNqgxLj0aDYaefD576ip623rZVG8KsfdtW5kYgb/Vna83Isc6
+rbNbuwZGSqRF7BN9VJTWQyrjT+ule0FCW060LbWfp3WPDfJION5AznukFHsl/zPF
+SMfreobaaY+L4CQiRmbI0eNgnibPEj1IyCwHhzAIfR8Dhw5JYXOGYnB4adePD1/I
+mahR93opO/BylUxHcIPg4cW0G7KAG7lZXG7bbQoP8MJMubsDqnJ6O2XZ5+2U+BSZ
+eQIDAQAB
+-----END PUBLIC KEY-----
diff --git a/jws/test/test.go b/jws/test/test.go
new file mode 100644
index 0000000..48d6a21
--- /dev/null
+++ b/jws/test/test.go
@@ -0,0 +1,52 @@
+// +build ignore
+
+package main
+
+import (
+ "crypto/rand"
+ "crypto/rsa"
+ "crypto/x509"
+ "encoding/pem"
+ "os"
+)
+
+func main() {
+
+ f1, err := os.Create("sample_key.priv")
+ if err != nil {
+ panic(err)
+ }
+ f2, err := os.Create("sample_key.pub")
+ if err != nil {
+ panic(err)
+ }
+
+ privateKey, err := rsa.GenerateKey(rand.Reader, 2048)
+ if err != nil {
+ panic(err)
+ }
+
+ privateKeyDer := x509.MarshalPKCS1PrivateKey(privateKey)
+ privateKeyBlock := pem.Block{
+ Type: "RSA PRIVATE KEY",
+ Headers: nil,
+ Bytes: privateKeyDer,
+ }
+ pem.Encode(f1, &privateKeyBlock)
+
+ publicKey := privateKey.PublicKey
+ publicKeyDer, err := x509.MarshalPKIXPublicKey(&publicKey)
+ if err != nil {
+ panic(err)
+ }
+
+ publicKeyBlock := pem.Block{
+ Type: "PUBLIC KEY",
+ Headers: nil,
+ Bytes: publicKeyDer,
+ }
+ pem.Encode(f2, &publicKeyBlock)
+
+ f1.Close()
+ f2.Close()
+}
diff --git a/stubs_test.go b/stubs_test.go
new file mode 100644
index 0000000..be30c82
--- /dev/null
+++ b/stubs_test.go
@@ -0,0 +1,7 @@
+package jose
+
+import "testing"
+
+func Error(t *testing.T, want, got interface{}) {
+ t.Errorf("Wanted: %q\n\t Got: %q", want, got)
+}