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) +}