forked from golang-jwt/jwt
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request golang-jwt#72 from patbaker82/rsapss
Add RSASSA-PSS signatures
- Loading branch information
Showing
2 changed files
with
222 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,126 @@ | ||
// +build go1.4 | ||
|
||
package jwt | ||
|
||
import ( | ||
"crypto" | ||
"crypto/rand" | ||
"crypto/rsa" | ||
) | ||
|
||
// Implements the RSAPSS family of signing methods signing methods | ||
type SigningMethodRSAPSS struct { | ||
*SigningMethodRSA | ||
Options *rsa.PSSOptions | ||
} | ||
|
||
// Specific instances for RS/PS and company | ||
var ( | ||
SigningMethodPS256 *SigningMethodRSAPSS | ||
SigningMethodPS384 *SigningMethodRSAPSS | ||
SigningMethodPS512 *SigningMethodRSAPSS | ||
) | ||
|
||
func init() { | ||
// PS256 | ||
SigningMethodPS256 = &SigningMethodRSAPSS{ | ||
&SigningMethodRSA{ | ||
Name: "PS256", | ||
Hash: crypto.SHA256, | ||
}, | ||
&rsa.PSSOptions{ | ||
SaltLength: rsa.PSSSaltLengthAuto, | ||
Hash: crypto.SHA256, | ||
}, | ||
} | ||
RegisterSigningMethod(SigningMethodPS256.Alg(), func() SigningMethod { | ||
return SigningMethodPS256 | ||
}) | ||
|
||
// PS384 | ||
SigningMethodPS384 = &SigningMethodRSAPSS{ | ||
&SigningMethodRSA{ | ||
Name: "PS384", | ||
Hash: crypto.SHA384, | ||
}, | ||
&rsa.PSSOptions{ | ||
SaltLength: rsa.PSSSaltLengthAuto, | ||
Hash: crypto.SHA384, | ||
}, | ||
} | ||
RegisterSigningMethod(SigningMethodPS384.Alg(), func() SigningMethod { | ||
return SigningMethodPS384 | ||
}) | ||
|
||
// PS512 | ||
SigningMethodPS512 = &SigningMethodRSAPSS{ | ||
&SigningMethodRSA{ | ||
Name: "PS512", | ||
Hash: crypto.SHA512, | ||
}, | ||
&rsa.PSSOptions{ | ||
SaltLength: rsa.PSSSaltLengthAuto, | ||
Hash: crypto.SHA512, | ||
}, | ||
} | ||
RegisterSigningMethod(SigningMethodPS512.Alg(), func() SigningMethod { | ||
return SigningMethodPS512 | ||
}) | ||
} | ||
|
||
// Implements the Verify method from SigningMethod | ||
// For this verify method, key must be an rsa.PrivateKey struct | ||
func (m *SigningMethodRSAPSS) Verify(signingString, signature string, key interface{}) error { | ||
var err error | ||
|
||
// Decode the signature | ||
var sig []byte | ||
if sig, err = DecodeSegment(signature); err != nil { | ||
return err | ||
} | ||
|
||
var rsaKey *rsa.PublicKey | ||
switch k := key.(type) { | ||
case *rsa.PublicKey: | ||
rsaKey = k | ||
default: | ||
return ErrInvalidKey | ||
} | ||
|
||
// Create hasher | ||
if !m.Hash.Available() { | ||
return ErrHashUnavailable | ||
} | ||
hasher := m.Hash.New() | ||
hasher.Write([]byte(signingString)) | ||
|
||
return rsa.VerifyPSS(rsaKey, m.Hash, hasher.Sum(nil), sig, m.Options) | ||
} | ||
|
||
// Implements the Sign method from SigningMethod | ||
// For this signing method, key must be an rsa.PublicKey struct | ||
func (m *SigningMethodRSAPSS) Sign(signingString string, key interface{}) (string, error) { | ||
var rsaKey *rsa.PrivateKey | ||
|
||
switch k := key.(type) { | ||
case *rsa.PrivateKey: | ||
rsaKey = k | ||
default: | ||
return "", ErrInvalidKey | ||
} | ||
|
||
// Create the hasher | ||
if !m.Hash.Available() { | ||
return "", ErrHashUnavailable | ||
} | ||
|
||
hasher := m.Hash.New() | ||
hasher.Write([]byte(signingString)) | ||
|
||
// Sign the string and return the encoded bytes | ||
if sigBytes, err := rsa.SignPSS(rand.Reader, rsaKey, m.Hash, hasher.Sum(nil), m.Options); err == nil { | ||
return EncodeSegment(sigBytes), nil | ||
} else { | ||
return "", err | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,96 @@ | ||
// +build go1.4 | ||
|
||
package jwt_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]) | ||
} | ||
} | ||
} | ||
} |