parent
9cb590d800
commit
0a9496462a
@ -0,0 +1,57 @@ |
||||
### Generate RSA keys for JWT |
||||
|
||||
``` |
||||
mkdir -p ~/.minio/web |
||||
``` |
||||
|
||||
``` |
||||
openssl genrsa -out ~/.minio/web/private.key 2048 |
||||
``` |
||||
|
||||
``` |
||||
openssl rsa -in ~/.minio/web/private.key -outform PEM -pubout -out ~/.minio/web/public.key |
||||
``` |
||||
### Start minio server |
||||
|
||||
``` |
||||
minio server <testdir> |
||||
``` |
||||
|
||||
### Now you can make curl requests to the server at port 9001. |
||||
|
||||
Currently username and password are defaulted for testing purposes. |
||||
|
||||
``` |
||||
curl -X POST -H "Content-Type: application/json" -d '{"username":"WLGDGYAQYIGI833EV05A", "password": "BYvgJM101sHngl2uzjXS/OBF/aMxAN06JrJ3qJlF"}' http://127.0.0.1:9001/login |
||||
{"token":"eyJhbGciOiJSUzUxMiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE0NTM1NDM0MjMsImlhdCI6MTQ1MzUwNzQyMywic3ViIjoiV0xHREdZQVFZSUdJODMzRVYwNUEifQ.zhL0vG5dwwak3SvpysW0CzdPRjpadrCLIpte2QHSxj2XjIQb2oK0dDD9Yvl-45E14CMVQhV3CCsf9LFaK2C94I5aop6nP7sSCyG2_l4w2xrfEPWKgyOY9P0QxUIPV3o43o2XjnMlU_6xE2mk8S9N7psk15sf0Ma1EoXkQlfqEZzbxyQjwKx4UxzkVpwN4k6wavtwU-rgVU0QwJwXXss0hVhY7HWtOzUGrhVWL42pOwNwZ73lrHpJkSyQi6fbc5lIALgFoeei_iSUXxRaJjvm36rn4vui3qLCoH79E-WhkoP_mqDvf_YfiTqcFHgdgnu2wtlQl90RNh2-wgR-XJiedQ"} |
||||
``` |
||||
|
||||
Replies back with a token which can be used to logout |
||||
|
||||
``` |
||||
curl -i -X GET -H "Authorization: Bearer eyJhbGciOiJSUzUxMiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE0NTM1NDM0MjMsImlhdCI6MTQ1MzUwNzQyMywic3ViIjoiV0xHREdZQVFZSUdJODMzRVYwNUEifQ.zhL0vG5dwwak3SvpysW0CzdPRjpadrCLIpte2QHSxj2XjIQb2oK0dDD9Yvl-45E14CMVQhV3CCsf9LFaK2C94I5aop6nP7sSCyG2_l4w2xrfEPWKgyOY9P0QxUIPV3o43o2XjnMlU_6xE2mk8S9N7psk15sf0Ma1EoXkQlfqEZzbxyQjwKx4UxzkVpwN4k6wavtwU-rgVU0QwJwXXss0hVhY7HWtOzUGrhVWL42pOwNwZ73lrHpJkSyQi6fbc5lIALgFoeei_iSUXxRaJjvm36rn4vui3qLCoH79E-WhkoP_mqDvf_YfiTqcFHgdgnu2wtlQl90RNh2-wgR-XJiedQ" http://127.0.0.1:9001/logout |
||||
HTTP/1.1 200 OK |
||||
Content-Type: application/json |
||||
Date: Sat, 23 Jan 2016 00:05:02 GMT |
||||
Content-Length: 0 |
||||
``` |
||||
|
||||
|
||||
Now attempt with wrong authorization, you should get 401. |
||||
|
||||
``` |
||||
$ curl -i -X GET -H "Authorization: Bearer testing123" http://127.0.0.1:9001/logout |
||||
HTTP/1.1 401 Unauthorized |
||||
Date: Sat, 23 Jan 2016 00:05:58 GMT |
||||
Content-Length: 0 |
||||
Content-Type: text/plain; charset=utf-8 |
||||
``` |
||||
|
||||
Without authorization logout is not possible. |
||||
``` |
||||
$ curl -i -X GET http://127.0.0.1:9001/logout |
||||
HTTP/1.1 401 Unauthorized |
||||
Date: Sat, 23 Jan 2016 00:07:00 GMT |
||||
Content-Length: 0 |
||||
Content-Type: text/plain; charset=utf-8 |
||||
``` |
@ -0,0 +1,39 @@ |
||||
package main |
||||
|
||||
import ( |
||||
"fmt" |
||||
"net/http" |
||||
|
||||
"github.com/dgrijalva/jwt-go" |
||||
) |
||||
|
||||
type authHandler struct { |
||||
handler http.Handler |
||||
} |
||||
|
||||
// AuthHandler -
|
||||
// Verify if authorization header has signature version '2', reject it cleanly.
|
||||
func AuthHandler(h http.Handler) http.Handler { |
||||
return authHandler{h} |
||||
} |
||||
|
||||
// Ignore request if authorization header is not valid.
|
||||
func (h authHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { |
||||
// For login attempts please issue a new token.
|
||||
if r.Method == "POST" && r.URL.Path == "/login-token" { |
||||
h.handler.ServeHTTP(w, r) |
||||
return |
||||
} |
||||
authBackend := InitJWT() |
||||
token, err := jwt.ParseFromRequest(r, func(token *jwt.Token) (interface{}, error) { |
||||
if _, ok := token.Method.(*jwt.SigningMethodRSA); !ok { |
||||
return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"]) |
||||
} |
||||
return authBackend.PublicKey, nil |
||||
}) |
||||
if err != nil || !token.Valid { |
||||
w.WriteHeader(http.StatusUnauthorized) |
||||
return |
||||
} |
||||
h.handler.ServeHTTP(w, r) |
||||
} |
@ -0,0 +1,108 @@ |
||||
package main |
||||
|
||||
import ( |
||||
"crypto/rsa" |
||||
"crypto/x509" |
||||
"encoding/pem" |
||||
"io/ioutil" |
||||
"time" |
||||
|
||||
jwt "github.com/dgrijalva/jwt-go" |
||||
"golang.org/x/crypto/bcrypt" |
||||
) |
||||
|
||||
// JWTAuthBackend - jwt auth backend
|
||||
type JWTAuthBackend struct { |
||||
privateKey *rsa.PrivateKey |
||||
PublicKey *rsa.PublicKey |
||||
} |
||||
|
||||
const ( |
||||
jwtExpirationDelta = 10 |
||||
expireOffset = 3600 |
||||
) |
||||
|
||||
// InitJWT - init.
|
||||
func InitJWT() *JWTAuthBackend { |
||||
authBackendInstance := &JWTAuthBackend{ |
||||
privateKey: getPrivateKey(), |
||||
PublicKey: getPublicKey(), |
||||
} |
||||
return authBackendInstance |
||||
} |
||||
|
||||
// GenerateToken -
|
||||
func (b *JWTAuthBackend) GenerateToken(userName string) (string, error) { |
||||
token := jwt.New(jwt.SigningMethodRS512) |
||||
token.Claims["exp"] = time.Now().Add(time.Hour * time.Duration(jwtExpirationDelta)).Unix() |
||||
token.Claims["iat"] = time.Now().Unix() |
||||
token.Claims["sub"] = userName |
||||
tokenString, err := token.SignedString(b.privateKey) |
||||
if err != nil { |
||||
return "", err |
||||
} |
||||
return tokenString, nil |
||||
} |
||||
|
||||
// Authenticate -
|
||||
func (b *JWTAuthBackend) Authenticate(user *User) bool { |
||||
hashedPassword, _ := bcrypt.GenerateFromPassword([]byte("BYvgJM101sHngl2uzjXS/OBF/aMxAN06JrJ3qJlF"), 10) |
||||
testUser := User{ |
||||
Username: "WLGDGYAQYIGI833EV05A", |
||||
Password: string(hashedPassword), |
||||
} |
||||
if user.Username == testUser.Username { |
||||
return bcrypt.CompareHashAndPassword([]byte(testUser.Password), []byte(user.Password)) == nil |
||||
} |
||||
return false |
||||
} |
||||
|
||||
//
|
||||
func (b *JWTAuthBackend) getTokenRemainingValidity(timestamp interface{}) int { |
||||
if validity, ok := timestamp.(float64); ok { |
||||
tm := time.Unix(int64(validity), 0) |
||||
remainer := tm.Sub(time.Now()) |
||||
if remainer > 0 { |
||||
return int(remainer.Seconds() + expireOffset) |
||||
} |
||||
} |
||||
return expireOffset |
||||
} |
||||
|
||||
// Logout - logout
|
||||
func (b *JWTAuthBackend) Logout(tokenString string, token *jwt.Token) error { |
||||
b.getTokenRemainingValidity(token.Claims["exp"]) |
||||
return nil |
||||
} |
||||
|
||||
func getPrivateKey() *rsa.PrivateKey { |
||||
pemBytes, err := ioutil.ReadFile(mustGetPrivateKeyPath()) |
||||
if err != nil { |
||||
panic(err) |
||||
} |
||||
data, _ := pem.Decode([]byte(pemBytes)) |
||||
privateKeyImported, err := x509.ParsePKCS1PrivateKey(data.Bytes) |
||||
if err != nil { |
||||
panic(err) |
||||
} |
||||
return privateKeyImported |
||||
} |
||||
|
||||
func getPublicKey() *rsa.PublicKey { |
||||
pemBytes, err := ioutil.ReadFile(mustGetPublicKeyPath()) |
||||
if err != nil { |
||||
panic(err) |
||||
} |
||||
data, _ := pem.Decode([]byte(pemBytes)) |
||||
publicKeyImported, err := x509.ParsePKIXPublicKey(data.Bytes) |
||||
if err != nil { |
||||
panic(err) |
||||
} |
||||
|
||||
rsaPub, ok := publicKeyImported.(*rsa.PublicKey) |
||||
if !ok { |
||||
panic(err) |
||||
} |
||||
|
||||
return rsaPub |
||||
} |
@ -0,0 +1,8 @@ |
||||
Copyright (c) 2012 Dave Grijalva |
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: |
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. |
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
||||
|
@ -0,0 +1,98 @@ |
||||
A [go](http://www.golang.org) (or 'golang' for search engine friendliness) implementation of [JSON Web Tokens](http://self-issued.info/docs/draft-jones-json-web-token.html) |
||||
|
||||
[![Build Status](https://travis-ci.org/dgrijalva/jwt-go.svg?branch=master)](https://travis-ci.org/dgrijalva/jwt-go) |
||||
|
||||
**NOTICE:** A vulnerability in JWT was [recently published](https://auth0.com/blog/2015/03/31/critical-vulnerabilities-in-json-web-token-libraries/). As this library doesn't force users to validate the `alg` is what they expected, it's possible your usage is effected. There will be an update soon to remedy this, and it will likey require backwards-incompatible changes to the API. In the short term, please make sure your implementation verifies the `alg` is what you expect. |
||||
|
||||
## What the heck is a JWT? |
||||
|
||||
In short, it's a signed JSON object that does something useful (for example, authentication). It's commonly used for `Bearer` tokens in Oauth 2. A token is made of three parts, separated by `.`'s. The first two parts are JSON objects, that have been [base64url](http://tools.ietf.org/html/rfc4648) encoded. The last part is the signature, encoded the same way. |
||||
|
||||
The first part is called the header. It contains the necessary information for verifying the last part, the signature. For example, which encryption method was used for signing and what key was used. |
||||
|
||||
The part in the middle is the interesting bit. It's called the Claims and contains the actual stuff you care about. Refer to [the RFC](http://self-issued.info/docs/draft-jones-json-web-token.html) for information about reserved keys and the proper way to add your own. |
||||
|
||||
## What's in the box? |
||||
|
||||
This library supports the parsing and verification as well as the generation and signing of JWTs. Current supported signing algorithms are HMAC SHA, RSA, RSA-PSS, and ECDSA, though hooks are present for adding your own. |
||||
|
||||
## Parse and Verify |
||||
|
||||
Parsing and verifying tokens is pretty straight forward. You pass in the token and a function for looking up the key. This is done as a callback since you may need to parse the token to find out what signing method and key was used. |
||||
|
||||
```go |
||||
token, err := jwt.Parse(myToken, func(token *jwt.Token) (interface{}, error) { |
||||
// Don't forget to validate the alg is what you expect: |
||||
if _, ok := token.Method.(*jwt.SigningMethodRSA); !ok { |
||||
return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"]) |
||||
} |
||||
return myLookupKey(token.Header["kid"]), nil |
||||
}) |
||||
|
||||
if err == nil && token.Valid { |
||||
deliverGoodness("!") |
||||
} else { |
||||
deliverUtterRejection(":(") |
||||
} |
||||
``` |
||||
|
||||
## Create a token |
||||
|
||||
```go |
||||
// Create the token |
||||
token := jwt.New(jwt.SigningMethodHS256) |
||||
// Set some claims |
||||
token.Claims["foo"] = "bar" |
||||
token.Claims["exp"] = time.Now().Add(time.Hour * 72).Unix() |
||||
// Sign and get the complete encoded token as a string |
||||
tokenString, err := token.SignedString(mySigningKey) |
||||
``` |
||||
|
||||
## Extensions |
||||
|
||||
This library publishes all the necessary components for adding your own signing methods. Simply implement the `SigningMethod` interface and register a factory method using `RegisterSigningMethod`. |
||||
|
||||
Here's an example of an extension that integrates with the Google App Engine signing tools: https://github.com/someone1/gcp-jwt-go |
||||
|
||||
## Project Status & Versioning |
||||
|
||||
This library is considered production ready. Feedback and feature requests are appreciated. The API should be considered stable. There should be very few backwards-incompatible changes outside of major version updates (and only with good reason). |
||||
|
||||
This project uses [Semantic Versioning 2.0.0](http://semver.org). Accepted pull requests will land on `master`. Periodically, versions will be tagged from `master`. You can find all the releases on [the project releases page](https://github.com/dgrijalva/jwt-go/releases). |
||||
|
||||
While we try to make it obvious when we make breaking changes, there isn't a great mechanism for pushing announcements out to users. You may want to use this alternative package include: `gopkg.in/dgrijalva/jwt-go.v2`. It will do the right thing WRT semantic versioning. |
||||
|
||||
## Usage Tips |
||||
|
||||
### Signing vs Encryption |
||||
|
||||
A token is simply a JSON object that is signed by its author. this tells you exactly two things about the data: |
||||
|
||||
* The author of the token was in the possession of the signing secret |
||||
* The data has not been modified since it was signed |
||||
|
||||
It's important to know that JWT does not provide encryption, which means anyone who has access to the token can read its contents. If you need to protect (encrypt) the data, there is a companion spec, `JWE`, that provides this functionality. JWE is currently outside the scope of this library. |
||||
|
||||
### Choosing a Signing Method |
||||
|
||||
There are several signing methods available, and you should probably take the time to learn about the various options before choosing one. The principal design decision is most likely going to be symmetric vs asymmetric. |
||||
|
||||
Symmetric signing methods, such as HSA, use only a single secret. This is probably the simplest signing method to use since any `[]byte` can be used as a valid secret. They are also slightly computationally faster to use, though this rarely is enough to matter. Symmetric signing methods work the best when both producers and consumers of tokens are trusted, or even the same system. Since the same secret is used to both sign and validate tokens, you can't easily distribute the key for validation. |
||||
|
||||
Asymmetric signing methods, such as RSA, use different keys for signing and verifying tokens. This makes it possible to produce tokens with a private key, and allow any consumer to access the public key for verification. |
||||
|
||||
### JWT and OAuth |
||||
|
||||
It's worth mentioning that OAuth and JWT are not the same thing. A JWT token is simply a signed JSON object. It can be used anywhere such a thing is useful. There is some confusion, though, as JWT is the most common type of bearer token used in OAuth2 authentication. |
||||
|
||||
Without going too far down the rabbit hole, here's a description of the interaction of these technologies: |
||||
|
||||
* OAuth is a protocol for allowing an identity provider to be separate from the service a user is logging in to. For example, whenever you use Facebook to log into a different service (Yelp, Spotify, etc), you are using OAuth. |
||||
* OAuth defines several options for passing around authentication data. One popular method is called a "bearer token". A bearer token is simply a string that _should_ only be held by an authenticated user. Thus, simply presenting this token proves your identity. You can probably derive from here why a JWT might make a good bearer token. |
||||
* Because bearer tokens are used for authentication, it's important they're kept secret. This is why transactions that use bearer tokens typically happen over SSL. |
||||
|
||||
## More |
||||
|
||||
Documentation can be found [on godoc.org](http://godoc.org/github.com/dgrijalva/jwt-go). |
||||
|
||||
The command line utility included in this project (cmd/jwt) provides a straightforward example of token creation and parsing as well as a useful tool for debugging your own integration. For a more http centric example, see [this gist](https://gist.github.com/cryptix/45c33ecf0ae54828e63b). |
@ -0,0 +1,67 @@ |
||||
## `jwt-go` Version History |
||||
|
||||
#### 2.4.0 |
||||
|
||||
* Added new type, Parser, to allow for configuration of various parsing parameters |
||||
* You can now specify a list of valid signing methods. Anything outside this set will be rejected. |
||||
* You can now opt to use the `json.Number` type instead of `float64` when parsing token JSON |
||||
* Added support for [Travis CI](https://travis-ci.org/dgrijalva/jwt-go) |
||||
* Fixed some bugs with ECDSA parsing |
||||
|
||||
#### 2.3.0 |
||||
|
||||
* Added support for ECDSA signing methods |
||||
* Added support for RSA PSS signing methods (requires go v1.4) |
||||
|
||||
#### 2.2.0 |
||||
|
||||
* Gracefully handle a `nil` `Keyfunc` being passed to `Parse`. Result will now be the parsed token and an error, instead of a panic. |
||||
|
||||
#### 2.1.0 |
||||
|
||||
Backwards compatible API change that was missed in 2.0.0. |
||||
|
||||
* The `SignedString` method on `Token` now takes `interface{}` instead of `[]byte` |
||||
|
||||
#### 2.0.0 |
||||
|
||||
There were two major reasons for breaking backwards compatibility with this update. The first was a refactor required to expand the width of the RSA and HMAC-SHA signing implementations. There will likely be no required code changes to support this change. |
||||
|
||||
The second update, while unfortunately requiring a small change in integration, is required to open up this library to other signing methods. Not all keys used for all signing methods have a single standard on-disk representation. Requiring `[]byte` as the type for all keys proved too limiting. Additionally, this implementation allows for pre-parsed tokens to be reused, which might matter in an application that parses a high volume of tokens with a small set of keys. Backwards compatibilty has been maintained for passing `[]byte` to the RSA signing methods, but they will also accept `*rsa.PublicKey` and `*rsa.PrivateKey`. |
||||
|
||||
It is likely the only integration change required here will be to change `func(t *jwt.Token) ([]byte, error)` to `func(t *jwt.Token) (interface{}, error)` when calling `Parse`. |
||||
|
||||
* **Compatibility Breaking Changes** |
||||
* `SigningMethodHS256` is now `*SigningMethodHMAC` instead of `type struct` |
||||
* `SigningMethodRS256` is now `*SigningMethodRSA` instead of `type struct` |
||||
* `KeyFunc` now returns `interface{}` instead of `[]byte` |
||||
* `SigningMethod.Sign` now takes `interface{}` instead of `[]byte` for the key |
||||
* `SigningMethod.Verify` now takes `interface{}` instead of `[]byte` for the key |
||||
* Renamed type `SigningMethodHS256` to `SigningMethodHMAC`. Specific sizes are now just instances of this type. |
||||
* Added public package global `SigningMethodHS256` |
||||
* Added public package global `SigningMethodHS384` |
||||
* Added public package global `SigningMethodHS512` |
||||
* Renamed type `SigningMethodRS256` to `SigningMethodRSA`. Specific sizes are now just instances of this type. |
||||
* Added public package global `SigningMethodRS256` |
||||
* Added public package global `SigningMethodRS384` |
||||
* Added public package global `SigningMethodRS512` |
||||
* Moved sample private key for HMAC tests from an inline value to a file on disk. Value is unchanged. |
||||
* Refactored the RSA implementation to be easier to read |
||||
* Exposed helper methods `ParseRSAPrivateKeyFromPEM` and `ParseRSAPublicKeyFromPEM` |
||||
|
||||
#### 1.0.2 |
||||
|
||||
* Fixed bug in parsing public keys from certificates |
||||
* Added more tests around the parsing of keys for RS256 |
||||
* Code refactoring in RS256 implementation. No functional changes |
||||
|
||||
#### 1.0.1 |
||||
|
||||
* Fixed panic if RS256 signing method was passed an invalid key |
||||
|
||||
#### 1.0.0 |
||||
|
||||
* First versioned release |
||||
* API stabilized |
||||
* Supports creating, signing, parsing, and validating JWT tokens |
||||
* Supports RS256 and HS256 signing methods |
@ -0,0 +1,4 @@ |
||||
// Package jwt is a Go implementation of JSON Web Tokens: http://self-issued.info/docs/draft-jones-json-web-token.html
|
||||
//
|
||||
// See README.md for more info.
|
||||
package jwt |
@ -0,0 +1,147 @@ |
||||
package jwt |
||||
|
||||
import ( |
||||
"crypto" |
||||
"crypto/ecdsa" |
||||
"crypto/rand" |
||||
"errors" |
||||
"math/big" |
||||
) |
||||
|
||||
var ( |
||||
// Sadly this is missing from crypto/ecdsa compared to crypto/rsa
|
||||
ErrECDSAVerification = errors.New("crypto/ecdsa: verification error") |
||||
) |
||||
|
||||
// Implements the ECDSA family of signing methods signing methods
|
||||
type SigningMethodECDSA struct { |
||||
Name string |
||||
Hash crypto.Hash |
||||
KeySize int |
||||
CurveBits int |
||||
} |
||||
|
||||
// Specific instances for EC256 and company
|
||||
var ( |
||||
SigningMethodES256 *SigningMethodECDSA |
||||
SigningMethodES384 *SigningMethodECDSA |
||||
SigningMethodES512 *SigningMethodECDSA |
||||
) |
||||
|
||||
func init() { |
||||
// ES256
|
||||
SigningMethodES256 = &SigningMethodECDSA{"ES256", crypto.SHA256, 32, 256} |
||||
RegisterSigningMethod(SigningMethodES256.Alg(), func() SigningMethod { |
||||
return SigningMethodES256 |
||||
}) |
||||
|
||||
// ES384
|
||||
SigningMethodES384 = &SigningMethodECDSA{"ES384", crypto.SHA384, 48, 384} |
||||
RegisterSigningMethod(SigningMethodES384.Alg(), func() SigningMethod { |
||||
return SigningMethodES384 |
||||
}) |
||||
|
||||
// ES512
|
||||
SigningMethodES512 = &SigningMethodECDSA{"ES512", crypto.SHA512, 66, 521} |
||||
RegisterSigningMethod(SigningMethodES512.Alg(), func() SigningMethod { |
||||
return SigningMethodES512 |
||||
}) |
||||
} |
||||
|
||||
func (m *SigningMethodECDSA) Alg() string { |
||||
return m.Name |
||||
} |
||||
|
||||
// Implements the Verify method from SigningMethod
|
||||
// For this verify method, key must be an ecdsa.PublicKey struct
|
||||
func (m *SigningMethodECDSA) 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 |
||||
} |
||||
|
||||
// Get the key
|
||||
var ecdsaKey *ecdsa.PublicKey |
||||
switch k := key.(type) { |
||||
case *ecdsa.PublicKey: |
||||
ecdsaKey = k |
||||
default: |
||||
return ErrInvalidKey |
||||
} |
||||
|
||||
if len(sig) != 2*m.KeySize { |
||||
return ErrECDSAVerification |
||||
} |
||||
|
||||
r := big.NewInt(0).SetBytes(sig[:m.KeySize]) |
||||
s := big.NewInt(0).SetBytes(sig[m.KeySize:]) |
||||
|
||||
// Create hasher
|
||||
if !m.Hash.Available() { |
||||
return ErrHashUnavailable |
||||
} |
||||
hasher := m.Hash.New() |
||||
hasher.Write([]byte(signingString)) |
||||
|
||||
// Verify the signature
|
||||
if verifystatus := ecdsa.Verify(ecdsaKey, hasher.Sum(nil), r, s); verifystatus == true { |
||||
return nil |
||||
} else { |
||||
return ErrECDSAVerification |
||||
} |
||||
} |
||||
|
||||
// Implements the Sign method from SigningMethod
|
||||
// For this signing method, key must be an ecdsa.PrivateKey struct
|
||||
func (m *SigningMethodECDSA) Sign(signingString string, key interface{}) (string, error) { |
||||
// Get the key
|
||||
var ecdsaKey *ecdsa.PrivateKey |
||||
switch k := key.(type) { |
||||
case *ecdsa.PrivateKey: |
||||
ecdsaKey = 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 r, s
|
||||
if r, s, err := ecdsa.Sign(rand.Reader, ecdsaKey, hasher.Sum(nil)); err == nil { |
||||
curveBits := ecdsaKey.Curve.Params().BitSize |
||||
|
||||
if m.CurveBits != curveBits { |
||||
return "", ErrInvalidKey |
||||
} |
||||
|
||||
keyBytes := curveBits / 8 |
||||
if curveBits%8 > 0 { |
||||
keyBytes += 1 |
||||
} |
||||
|
||||
// We serialize the outpus (r and s) into big-endian byte arrays and pad
|
||||
// them with zeros on the left to make sure the sizes work out. Both arrays
|
||||
// must be keyBytes long, and the output must be 2*keyBytes long.
|
||||
rBytes := r.Bytes() |
||||
rBytesPadded := make([]byte, keyBytes) |
||||
copy(rBytesPadded[keyBytes-len(rBytes):], rBytes) |
||||
|
||||
sBytes := s.Bytes() |
||||
sBytesPadded := make([]byte, keyBytes) |
||||
copy(sBytesPadded[keyBytes-len(sBytes):], sBytes) |
||||
|
||||
out := append(rBytesPadded, sBytesPadded...) |
||||
|
||||
return EncodeSegment(out), nil |
||||
} else { |
||||
return "", err |
||||
} |
||||
} |
@ -0,0 +1,100 @@ |
||||
package jwt_test |
||||
|
||||
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"}, |
||||
"eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiJ9.eyJmb28iOiJiYXIifQ.feG39E-bn8HXAKhzDZq7yEAPWYDhZlwTn3sePJnU9VrGMmwdXAIEyoOnrjreYlVM_Z4N13eK9-TmMTWyfKJtHQ", |
||||
"ES256", |
||||
map[string]interface{}{"foo": "bar"}, |
||||
true, |
||||
}, |
||||
{ |
||||
"Basic ES384", |
||||
map[string]string{"private": "test/ec384-private.pem", "public": "test/ec384-public.pem"}, |
||||
"eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzM4NCJ9.eyJmb28iOiJiYXIifQ.ngAfKMbJUh0WWubSIYe5GMsA-aHNKwFbJk_wq3lq23aPp8H2anb1rRILIzVR0gUf4a8WzDtrzmiikuPWyCS6CN4-PwdgTk-5nehC7JXqlaBZU05p3toM3nWCwm_LXcld", |
||||
"ES384", |
||||
map[string]interface{}{"foo": "bar"}, |
||||
true, |
||||
}, |
||||
{ |
||||
"Basic ES512", |
||||
map[string]string{"private": "test/ec512-private.pem", "public": "test/ec512-public.pem"}, |
||||
"eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzUxMiJ9.eyJmb28iOiJiYXIifQ.AAU0TvGQOcdg2OvrwY73NHKgfk26UDekh9Prz-L_iWuTBIBqOFCWwwLsRiHB1JOddfKAls5do1W0jR_F30JpVd-6AJeTjGKA4C1A1H6gIKwRY0o_tFDIydZCl_lMBMeG5VNFAjO86-WCSKwc3hqaGkq1MugPRq_qrF9AVbuEB4JPLyL5", |
||||
"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) |
||||
} |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,67 @@ |
||||
package jwt |
||||
|
||||
import ( |
||||
"crypto/ecdsa" |
||||
"crypto/x509" |
||||
"encoding/pem" |
||||
"errors" |
||||
) |
||||
|
||||
var ( |
||||
ErrNotECPublicKey = errors.New("Key is not a valid ECDSA public key") |
||||
ErrNotECPrivateKey = errors.New("Key is not a valid ECDSA private key") |
||||
) |
||||
|
||||
// Parse PEM encoded Elliptic Curve Private Key Structure
|
||||
func ParseECPrivateKeyFromPEM(key []byte) (*ecdsa.PrivateKey, 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.ParseECPrivateKey(block.Bytes); err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
var pkey *ecdsa.PrivateKey |
||||
var ok bool |
||||
if pkey, ok = parsedKey.(*ecdsa.PrivateKey); !ok { |
||||
return nil, ErrNotECPrivateKey |
||||
} |
||||
|
||||
return pkey, nil |
||||
} |
||||
|
||||
// Parse PEM encoded PKCS1 or PKCS8 public key
|
||||
func ParseECPublicKeyFromPEM(key []byte) (*ecdsa.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 *ecdsa.PublicKey |
||||
var ok bool |
||||
if pkey, ok = parsedKey.(*ecdsa.PublicKey); !ok { |
||||
return nil, ErrNotECPublicKey |
||||
} |
||||
|
||||
return pkey, nil |
||||
} |
@ -0,0 +1,43 @@ |
||||
package jwt |
||||
|
||||
import ( |
||||
"errors" |
||||
) |
||||
|
||||
// Error constants
|
||||
var ( |
||||
ErrInvalidKey = errors.New("key is invalid or of invalid type") |
||||
ErrHashUnavailable = errors.New("the requested hash function is unavailable") |
||||
ErrNoTokenInRequest = errors.New("no token present in request") |
||||
) |
||||
|
||||
// The errors that might occur when parsing and validating a token
|
||||
const ( |
||||
ValidationErrorMalformed uint32 = 1 << iota // Token is malformed
|
||||
ValidationErrorUnverifiable // Token could not be verified because of signing problems
|
||||
ValidationErrorSignatureInvalid // Signature validation failed
|
||||
ValidationErrorExpired // Exp validation failed
|
||||
ValidationErrorNotValidYet // NBF validation failed
|
||||
) |
||||
|
||||
// The error from Parse if token is not valid
|
||||
type ValidationError struct { |
||||
err string |
||||
Errors uint32 // bitfield. see ValidationError... constants
|
||||
} |
||||
|
||||
// Validation error is an error type
|
||||
func (e ValidationError) Error() string { |
||||
if e.err == "" { |
||||
return "token is invalid" |
||||
} |
||||
return e.err |
||||
} |
||||
|
||||
// No errors
|
||||
func (e *ValidationError) valid() bool { |
||||
if e.Errors > 0 { |
||||
return false |
||||
} |
||||
return true |
||||
} |
@ -0,0 +1,52 @@ |
||||
package jwt_test |
||||
|
||||
import ( |
||||
"fmt" |
||||
"github.com/dgrijalva/jwt-go" |
||||
"time" |
||||
) |
||||
|
||||
func ExampleParse(myToken string, myLookupKey func(interface{}) (interface{}, error)) { |
||||
token, err := jwt.Parse(myToken, func(token *jwt.Token) (interface{}, error) { |
||||
return myLookupKey(token.Header["kid"]) |
||||
}) |
||||
|
||||
if err == nil && token.Valid { |
||||
fmt.Println("Your token is valid. I like your style.") |
||||
} else { |
||||
fmt.Println("This token is terrible! I cannot accept this.") |
||||
} |
||||
} |
||||
|
||||
func ExampleNew(mySigningKey []byte) (string, error) { |
||||
// Create the token
|
||||
token := jwt.New(jwt.SigningMethodHS256) |
||||
// Set some claims
|
||||
token.Claims["foo"] = "bar" |
||||
token.Claims["exp"] = time.Now().Add(time.Hour * 72).Unix() |
||||
// Sign and get the complete encoded token as a string
|
||||
tokenString, err := token.SignedString(mySigningKey) |
||||
return tokenString, err |
||||
} |
||||
|
||||
func ExampleParse_errorChecking(myToken string, myLookupKey func(interface{}) (interface{}, error)) { |
||||
token, err := jwt.Parse(myToken, func(token *jwt.Token) (interface{}, error) { |
||||
return myLookupKey(token.Header["kid"]) |
||||
}) |
||||
|
||||
if token.Valid { |
||||
fmt.Println("You look nice today") |
||||
} else if ve, ok := err.(*jwt.ValidationError); ok { |
||||
if ve.Errors&jwt.ValidationErrorMalformed != 0 { |
||||
fmt.Println("That's not even a token") |
||||
} else if ve.Errors&(jwt.ValidationErrorExpired|jwt.ValidationErrorNotValidYet) != 0 { |
||||
// Token is either expired or not active yet
|
||||
fmt.Println("Timing is everything") |
||||
} else { |
||||
fmt.Println("Couldn't handle this token:", err) |
||||
} |
||||
} else { |
||||
fmt.Println("Couldn't handle this token:", err) |
||||
} |
||||
|
||||
} |
@ -0,0 +1,94 @@ |
||||
package jwt |
||||
|
||||
import ( |
||||
"crypto" |
||||
"crypto/hmac" |
||||
"errors" |
||||
) |
||||
|
||||
// Implements the HMAC-SHA family of signing methods signing methods
|
||||
type SigningMethodHMAC struct { |
||||
Name string |
||||
Hash crypto.Hash |
||||
} |
||||
|
||||
// Specific instances for HS256 and company
|
||||
var ( |
||||
SigningMethodHS256 *SigningMethodHMAC |
||||
SigningMethodHS384 *SigningMethodHMAC |
||||
SigningMethodHS512 *SigningMethodHMAC |
||||
ErrSignatureInvalid = errors.New("signature is invalid") |
||||
) |
||||
|
||||
func init() { |
||||
// HS256
|
||||
SigningMethodHS256 = &SigningMethodHMAC{"HS256", crypto.SHA256} |
||||
RegisterSigningMethod(SigningMethodHS256.Alg(), func() SigningMethod { |
||||
return SigningMethodHS256 |
||||
}) |
||||
|
||||
// HS384
|
||||
SigningMethodHS384 = &SigningMethodHMAC{"HS384", crypto.SHA384} |
||||
RegisterSigningMethod(SigningMethodHS384.Alg(), func() SigningMethod { |
||||
return SigningMethodHS384 |
||||
}) |
||||
|
||||
// HS512
|
||||
SigningMethodHS512 = &SigningMethodHMAC{"HS512", crypto.SHA512} |
||||
RegisterSigningMethod(SigningMethodHS512.Alg(), func() SigningMethod { |
||||
return SigningMethodHS512 |
||||
}) |
||||
} |
||||
|
||||
func (m *SigningMethodHMAC) Alg() string { |
||||
return m.Name |
||||
} |
||||
|
||||
// Verify the signature of HSXXX tokens. Returns nil if the signature is valid.
|
||||
func (m *SigningMethodHMAC) Verify(signingString, signature string, key interface{}) error { |
||||
// Verify the key is the right type
|
||||
keyBytes, ok := key.([]byte) |
||||
if !ok { |
||||
return ErrInvalidKey |
||||
} |
||||
|
||||
// Decode signature, for comparison
|
||||
sig, err := DecodeSegment(signature) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
|
||||
// Can we use the specified hashing method?
|
||||
if !m.Hash.Available() { |
||||
return ErrHashUnavailable |
||||
} |
||||
|
||||
// This signing method is symmetric, so we validate the signature
|
||||
// by reproducing the signature from the signing string and key, then
|
||||
// comparing that against the provided signature.
|
||||
hasher := hmac.New(m.Hash.New, keyBytes) |
||||
hasher.Write([]byte(signingString)) |
||||
if !hmac.Equal(sig, hasher.Sum(nil)) { |
||||
return ErrSignatureInvalid |
||||
} |
||||
|
||||
// No validation errors. Signature is good.
|
||||
return nil |
||||
} |
||||
|
||||
// Implements the Sign method from SigningMethod for this signing method.
|
||||
// Key must be []byte
|
||||
func (m *SigningMethodHMAC) Sign(signingString string, key interface{}) (string, error) { |
||||
if keyBytes, ok := key.([]byte); ok { |
||||
if !m.Hash.Available() { |
||||
return "", ErrHashUnavailable |
||||
} |
||||
|
||||
hasher := hmac.New(m.Hash.New, keyBytes) |
||||
hasher.Write([]byte(signingString)) |
||||
|
||||
return EncodeSegment(hasher.Sum(nil)), nil |
||||
} |
||||
|
||||
return "", ErrInvalidKey |
||||
} |
@ -0,0 +1,91 @@ |
||||
package jwt_test |
||||
|
||||
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) |
||||
} |
@ -0,0 +1,113 @@ |
||||
package jwt |
||||
|
||||
import ( |
||||
"bytes" |
||||
"encoding/json" |
||||
"fmt" |
||||
"strings" |
||||
) |
||||
|
||||
type Parser struct { |
||||
ValidMethods []string // If populated, only these methods will be considered valid
|
||||
UseJSONNumber bool // Use JSON Number format in JSON decoder
|
||||
} |
||||
|
||||
// Parse, validate, and return a token.
|
||||
// keyFunc will receive the parsed token and should return the key for validating.
|
||||
// If everything is kosher, err will be nil
|
||||
func (p *Parser) Parse(tokenString string, keyFunc Keyfunc) (*Token, error) { |
||||
parts := strings.Split(tokenString, ".") |
||||
if len(parts) != 3 { |
||||
return nil, &ValidationError{err: "token contains an invalid number of segments", Errors: ValidationErrorMalformed} |
||||
} |
||||
|
||||
var err error |
||||
token := &Token{Raw: tokenString} |
||||
// parse Header
|
||||
var headerBytes []byte |
||||
if headerBytes, err = DecodeSegment(parts[0]); err != nil { |
||||
return token, &ValidationError{err: err.Error(), Errors: ValidationErrorMalformed} |
||||
} |
||||
if err = json.Unmarshal(headerBytes, &token.Header); err != nil { |
||||
return token, &ValidationError{err: err.Error(), Errors: ValidationErrorMalformed} |
||||
} |
||||
|
||||
// parse Claims
|
||||
var claimBytes []byte |
||||
if claimBytes, err = DecodeSegment(parts[1]); err != nil { |
||||
return token, &ValidationError{err: err.Error(), Errors: ValidationErrorMalformed} |
||||
} |
||||
dec := json.NewDecoder(bytes.NewBuffer(claimBytes)) |
||||
if p.UseJSONNumber { |
||||
dec.UseNumber() |
||||
} |
||||
if err = dec.Decode(&token.Claims); err != nil { |
||||
return token, &ValidationError{err: err.Error(), Errors: ValidationErrorMalformed} |
||||
} |
||||
|
||||
// Lookup signature method
|
||||
if method, ok := token.Header["alg"].(string); ok { |
||||
if token.Method = GetSigningMethod(method); token.Method == nil { |
||||
return token, &ValidationError{err: "signing method (alg) is unavailable.", Errors: ValidationErrorUnverifiable} |
||||
} |
||||
} else { |
||||
return token, &ValidationError{err: "signing method (alg) is unspecified.", Errors: ValidationErrorUnverifiable} |
||||
} |
||||
|
||||
// Verify signing method is in the required set
|
||||
if p.ValidMethods != nil { |
||||
var signingMethodValid = false |
||||
var alg = token.Method.Alg() |
||||
for _, m := range p.ValidMethods { |
||||
if m == alg { |
||||
signingMethodValid = true |
||||
break |
||||
} |
||||
} |
||||
if !signingMethodValid { |
||||
// signing method is not in the listed set
|
||||
return token, &ValidationError{err: fmt.Sprintf("signing method %v is invalid", alg), Errors: ValidationErrorSignatureInvalid} |
||||
} |
||||
} |
||||
|
||||
// Lookup key
|
||||
var key interface{} |
||||
if keyFunc == nil { |
||||
// keyFunc was not provided. short circuiting validation
|
||||
return token, &ValidationError{err: "no Keyfunc was provided.", Errors: ValidationErrorUnverifiable} |
||||
} |
||||
if key, err = keyFunc(token); err != nil { |
||||
// keyFunc returned an error
|
||||
return token, &ValidationError{err: err.Error(), Errors: ValidationErrorUnverifiable} |
||||
} |
||||
|
||||
// Check expiration times
|
||||
vErr := &ValidationError{} |
||||
now := TimeFunc().Unix() |
||||
if exp, ok := token.Claims["exp"].(float64); ok { |
||||
if now > int64(exp) { |
||||
vErr.err = "token is expired" |
||||
vErr.Errors |= ValidationErrorExpired |
||||
} |
||||
} |
||||
if nbf, ok := token.Claims["nbf"].(float64); ok { |
||||
if now < int64(nbf) { |
||||
vErr.err = "token is not valid yet" |
||||
vErr.Errors |= ValidationErrorNotValidYet |
||||
} |
||||
} |
||||
|
||||
// Perform validation
|
||||
token.Signature = parts[2] |
||||
if err = token.Method.Verify(strings.Join(parts[0:2], "."), token.Signature, key); err != nil { |
||||
vErr.err = err.Error() |
||||
vErr.Errors |= ValidationErrorSignatureInvalid |
||||
} |
||||
|
||||
if vErr.valid() { |
||||
token.Valid = true |
||||
return token, nil |
||||
} |
||||
|
||||
return token, vErr |
||||
} |
@ -0,0 +1,239 @@ |
||||
package jwt_test |
||||
|
||||
import ( |
||||
"encoding/json" |
||||
"fmt" |
||||
"github.com/dgrijalva/jwt-go" |
||||
"io/ioutil" |
||||
"net/http" |
||||
"reflect" |
||||
"testing" |
||||
"time" |
||||
) |
||||
|
||||
var ( |
||||
jwtTestDefaultKey []byte |
||||
defaultKeyFunc jwt.Keyfunc = func(t *jwt.Token) (interface{}, error) { return jwtTestDefaultKey, nil } |
||||
emptyKeyFunc jwt.Keyfunc = func(t *jwt.Token) (interface{}, error) { return nil, nil } |
||||
errorKeyFunc jwt.Keyfunc = func(t *jwt.Token) (interface{}, error) { return nil, fmt.Errorf("error loading key") } |
||||
nilKeyFunc jwt.Keyfunc = nil |
||||
) |
||||
|
||||
var jwtTestData = []struct { |
||||
name string |
||||
tokenString string |
||||
keyfunc jwt.Keyfunc |
||||
claims map[string]interface{} |
||||
valid bool |
||||
errors uint32 |
||||
parser *jwt.Parser |
||||
}{ |
||||
{ |
||||
"basic", |
||||
"eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJmb28iOiJiYXIifQ.FhkiHkoESI_cG3NPigFrxEk9Z60_oXrOT2vGm9Pn6RDgYNovYORQmmA0zs1AoAOf09ly2Nx2YAg6ABqAYga1AcMFkJljwxTT5fYphTuqpWdy4BELeSYJx5Ty2gmr8e7RonuUztrdD5WfPqLKMm1Ozp_T6zALpRmwTIW0QPnaBXaQD90FplAg46Iy1UlDKr-Eupy0i5SLch5Q-p2ZpaL_5fnTIUDlxC3pWhJTyx_71qDI-mAA_5lE_VdroOeflG56sSmDxopPEG3bFlSu1eowyBfxtu0_CuVd-M42RU75Zc4Gsj6uV77MBtbMrf4_7M_NUTSgoIF3fRqxrj0NzihIBg", |
||||
defaultKeyFunc, |
||||
map[string]interface{}{"foo": "bar"}, |
||||
true, |
||||
0, |
||||
nil, |
||||
}, |
||||
{ |
||||
"basic expired", |
||||
"", // autogen
|
||||
defaultKeyFunc, |
||||
map[string]interface{}{"foo": "bar", "exp": float64(time.Now().Unix() - 100)}, |
||||
false, |
||||
jwt.ValidationErrorExpired, |
||||
nil, |
||||
}, |
||||
{ |
||||
"basic nbf", |
||||
"", // autogen
|
||||
defaultKeyFunc, |
||||
map[string]interface{}{"foo": "bar", "nbf": float64(time.Now().Unix() + 100)}, |
||||
false, |
||||
jwt.ValidationErrorNotValidYet, |
||||
nil, |
||||
}, |
||||
{ |
||||
"expired and nbf", |
||||
"", // autogen
|
||||
defaultKeyFunc, |
||||
map[string]interface{}{"foo": "bar", "nbf": float64(time.Now().Unix() + 100), "exp": float64(time.Now().Unix() - 100)}, |
||||
false, |
||||
jwt.ValidationErrorNotValidYet | jwt.ValidationErrorExpired, |
||||
nil, |
||||
}, |
||||
{ |
||||
"basic invalid", |
||||
"eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJmb28iOiJiYXIifQ.EhkiHkoESI_cG3NPigFrxEk9Z60_oXrOT2vGm9Pn6RDgYNovYORQmmA0zs1AoAOf09ly2Nx2YAg6ABqAYga1AcMFkJljwxTT5fYphTuqpWdy4BELeSYJx5Ty2gmr8e7RonuUztrdD5WfPqLKMm1Ozp_T6zALpRmwTIW0QPnaBXaQD90FplAg46Iy1UlDKr-Eupy0i5SLch5Q-p2ZpaL_5fnTIUDlxC3pWhJTyx_71qDI-mAA_5lE_VdroOeflG56sSmDxopPEG3bFlSu1eowyBfxtu0_CuVd-M42RU75Zc4Gsj6uV77MBtbMrf4_7M_NUTSgoIF3fRqxrj0NzihIBg", |
||||
defaultKeyFunc, |
||||
map[string]interface{}{"foo": "bar"}, |
||||
false, |
||||
jwt.ValidationErrorSignatureInvalid, |
||||
nil, |
||||
}, |
||||
{ |
||||
"basic nokeyfunc", |
||||
"eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJmb28iOiJiYXIifQ.FhkiHkoESI_cG3NPigFrxEk9Z60_oXrOT2vGm9Pn6RDgYNovYORQmmA0zs1AoAOf09ly2Nx2YAg6ABqAYga1AcMFkJljwxTT5fYphTuqpWdy4BELeSYJx5Ty2gmr8e7RonuUztrdD5WfPqLKMm1Ozp_T6zALpRmwTIW0QPnaBXaQD90FplAg46Iy1UlDKr-Eupy0i5SLch5Q-p2ZpaL_5fnTIUDlxC3pWhJTyx_71qDI-mAA_5lE_VdroOeflG56sSmDxopPEG3bFlSu1eowyBfxtu0_CuVd-M42RU75Zc4Gsj6uV77MBtbMrf4_7M_NUTSgoIF3fRqxrj0NzihIBg", |
||||
nilKeyFunc, |
||||
map[string]interface{}{"foo": "bar"}, |
||||
false, |
||||
jwt.ValidationErrorUnverifiable, |
||||
nil, |
||||
}, |
||||
{ |
||||
"basic nokey", |
||||
"eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJmb28iOiJiYXIifQ.FhkiHkoESI_cG3NPigFrxEk9Z60_oXrOT2vGm9Pn6RDgYNovYORQmmA0zs1AoAOf09ly2Nx2YAg6ABqAYga1AcMFkJljwxTT5fYphTuqpWdy4BELeSYJx5Ty2gmr8e7RonuUztrdD5WfPqLKMm1Ozp_T6zALpRmwTIW0QPnaBXaQD90FplAg46Iy1UlDKr-Eupy0i5SLch5Q-p2ZpaL_5fnTIUDlxC3pWhJTyx_71qDI-mAA_5lE_VdroOeflG56sSmDxopPEG3bFlSu1eowyBfxtu0_CuVd-M42RU75Zc4Gsj6uV77MBtbMrf4_7M_NUTSgoIF3fRqxrj0NzihIBg", |
||||
emptyKeyFunc, |
||||
map[string]interface{}{"foo": "bar"}, |
||||
false, |
||||
jwt.ValidationErrorSignatureInvalid, |
||||
nil, |
||||
}, |
||||
{ |
||||
"basic errorkey", |
||||
"eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJmb28iOiJiYXIifQ.FhkiHkoESI_cG3NPigFrxEk9Z60_oXrOT2vGm9Pn6RDgYNovYORQmmA0zs1AoAOf09ly2Nx2YAg6ABqAYga1AcMFkJljwxTT5fYphTuqpWdy4BELeSYJx5Ty2gmr8e7RonuUztrdD5WfPqLKMm1Ozp_T6zALpRmwTIW0QPnaBXaQD90FplAg46Iy1UlDKr-Eupy0i5SLch5Q-p2ZpaL_5fnTIUDlxC3pWhJTyx_71qDI-mAA_5lE_VdroOeflG56sSmDxopPEG3bFlSu1eowyBfxtu0_CuVd-M42RU75Zc4Gsj6uV77MBtbMrf4_7M_NUTSgoIF3fRqxrj0NzihIBg", |
||||
errorKeyFunc, |
||||
map[string]interface{}{"foo": "bar"}, |
||||
false, |
||||
jwt.ValidationErrorUnverifiable, |
||||
nil, |
||||
}, |
||||
{ |
||||
"invalid signing method", |
||||
"", |
||||
defaultKeyFunc, |
||||
map[string]interface{}{"foo": "bar"}, |
||||
false, |
||||
jwt.ValidationErrorSignatureInvalid, |
||||
&jwt.Parser{ValidMethods: []string{"HS256"}}, |
||||
}, |
||||
{ |
||||
"valid signing method", |
||||
"", |
||||
defaultKeyFunc, |
||||
map[string]interface{}{"foo": "bar"}, |
||||
true, |
||||
0, |
||||
&jwt.Parser{ValidMethods: []string{"RS256", "HS256"}}, |
||||
}, |
||||
{ |
||||
"JSON Number", |
||||
"", |
||||
defaultKeyFunc, |
||||
map[string]interface{}{"foo": json.Number("123.4")}, |
||||
true, |
||||
0, |
||||
&jwt.Parser{UseJSONNumber: true}, |
||||
}, |
||||
} |
||||
|
||||
func init() { |
||||
var e error |
||||
if jwtTestDefaultKey, e = ioutil.ReadFile("test/sample_key.pub"); e != nil { |
||||
panic(e) |
||||
} |
||||
} |
||||
|
||||
func makeSample(c map[string]interface{}) string { |
||||
key, e := ioutil.ReadFile("test/sample_key") |
||||
if e != nil { |
||||
panic(e.Error()) |
||||
} |
||||
|
||||
token := jwt.New(jwt.SigningMethodRS256) |
||||
token.Claims = c |
||||
s, e := token.SignedString(key) |
||||
|
||||
if e != nil { |
||||
panic(e.Error()) |
||||
} |
||||
|
||||
return s |
||||
} |
||||
|
||||
func TestParser_Parse(t *testing.T) { |
||||
for _, data := range jwtTestData { |
||||
if data.tokenString == "" { |
||||
data.tokenString = makeSample(data.claims) |
||||
} |
||||
|
||||
var token *jwt.Token |
||||
var err error |
||||
if data.parser != nil { |
||||
token, err = data.parser.Parse(data.tokenString, data.keyfunc) |
||||
} else { |
||||
token, err = jwt.Parse(data.tokenString, data.keyfunc) |
||||
} |
||||
|
||||
if !reflect.DeepEqual(data.claims, token.Claims) { |
||||
t.Errorf("[%v] Claims mismatch. Expecting: %v Got: %v", data.name, data.claims, token.Claims) |
||||
} |
||||
if data.valid && err != nil { |
||||
t.Errorf("[%v] Error while verifying token: %T:%v", data.name, err, err) |
||||
} |
||||
if !data.valid && err == nil { |
||||
t.Errorf("[%v] Invalid token passed validation", data.name) |
||||
} |
||||
if data.errors != 0 { |
||||
if err == nil { |
||||
t.Errorf("[%v] Expecting error. Didn't get one.", data.name) |
||||
} else { |
||||
// compare the bitfield part of the error
|
||||
if e := err.(*jwt.ValidationError).Errors; e != data.errors { |
||||
t.Errorf("[%v] Errors don't match expectation. %v != %v", data.name, e, data.errors) |
||||
} |
||||
} |
||||
} |
||||
if data.valid && token.Signature == "" { |
||||
t.Errorf("[%v] Signature is left unpopulated after parsing", data.name) |
||||
} |
||||
} |
||||
} |
||||
|
||||
func TestParseRequest(t *testing.T) { |
||||
// Bearer token request
|
||||
for _, data := range jwtTestData { |
||||
// FIXME: custom parsers are not supported by this helper. skip tests that require them
|
||||
if data.parser != nil { |
||||
t.Logf("Skipping [%v]. Custom parsers are not supported by ParseRequest", data.name) |
||||
continue |
||||
} |
||||
|
||||
if data.tokenString == "" { |
||||
data.tokenString = makeSample(data.claims) |
||||
} |
||||
|
||||
r, _ := http.NewRequest("GET", "/", nil) |
||||
r.Header.Set("Authorization", fmt.Sprintf("Bearer %v", data.tokenString)) |
||||
token, err := jwt.ParseFromRequest(r, data.keyfunc) |
||||
|
||||
if token == nil { |
||||
t.Errorf("[%v] Token was not found: %v", data.name, err) |
||||
continue |
||||
} |
||||
if !reflect.DeepEqual(data.claims, token.Claims) { |
||||
t.Errorf("[%v] Claims mismatch. Expecting: %v Got: %v", data.name, data.claims, token.Claims) |
||||
} |
||||
if data.valid && err != nil { |
||||
t.Errorf("[%v] Error while verifying token: %v", data.name, err) |
||||
} |
||||
if !data.valid && err == nil { |
||||
t.Errorf("[%v] Invalid token passed validation", data.name) |
||||
} |
||||
} |
||||
} |
||||
|
||||
// Helper method for benchmarking various methods
|
||||
func benchmarkSigning(b *testing.B, method jwt.SigningMethod, key interface{}) { |
||||
t := jwt.New(method) |
||||
b.RunParallel(func(pb *testing.PB) { |
||||
for pb.Next() { |
||||
if _, err := t.SignedString(key); err != nil { |
||||
b.Fatal(err) |
||||
} |
||||
} |
||||
}) |
||||
|
||||
} |
@ -0,0 +1,114 @@ |
||||
package jwt |
||||
|
||||
import ( |
||||
"crypto" |
||||
"crypto/rand" |
||||
"crypto/rsa" |
||||
) |
||||
|
||||
// Implements the RSA family of signing methods signing methods
|
||||
type SigningMethodRSA struct { |
||||
Name string |
||||
Hash crypto.Hash |
||||
} |
||||
|
||||
// Specific instances for RS256 and company
|
||||
var ( |
||||
SigningMethodRS256 *SigningMethodRSA |
||||
SigningMethodRS384 *SigningMethodRSA |
||||
SigningMethodRS512 *SigningMethodRSA |
||||
) |
||||
|
||||
func init() { |
||||
// RS256
|
||||
SigningMethodRS256 = &SigningMethodRSA{"RS256", crypto.SHA256} |
||||
RegisterSigningMethod(SigningMethodRS256.Alg(), func() SigningMethod { |
||||
return SigningMethodRS256 |
||||
}) |
||||
|
||||
// RS384
|
||||
SigningMethodRS384 = &SigningMethodRSA{"RS384", crypto.SHA384} |
||||
RegisterSigningMethod(SigningMethodRS384.Alg(), func() SigningMethod { |
||||
return SigningMethodRS384 |
||||
}) |
||||
|
||||
// RS512
|
||||
SigningMethodRS512 = &SigningMethodRSA{"RS512", crypto.SHA512} |
||||
RegisterSigningMethod(SigningMethodRS512.Alg(), func() SigningMethod { |
||||
return SigningMethodRS512 |
||||
}) |
||||
} |
||||
|
||||
func (m *SigningMethodRSA) Alg() string { |
||||
return m.Name |
||||
} |
||||
|
||||
// Implements the Verify method from SigningMethod
|
||||
// For this signing method, must be either a PEM encoded PKCS1 or PKCS8 RSA public key as
|
||||
// []byte, or an rsa.PublicKey structure.
|
||||
func (m *SigningMethodRSA) 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 []byte: |
||||
if rsaKey, err = ParseRSAPublicKeyFromPEM(k); err != nil { |
||||
return err |
||||
} |
||||
case *rsa.PublicKey: |
||||
rsaKey = k |
||||
default: |
||||
return ErrInvalidKey |
||||
} |
||||
|
||||
// Create hasher
|
||||
if !m.Hash.Available() { |
||||
return ErrHashUnavailable |
||||
} |
||||
hasher := m.Hash.New() |
||||
hasher.Write([]byte(signingString)) |
||||
|
||||
// Verify the signature
|
||||
return rsa.VerifyPKCS1v15(rsaKey, m.Hash, hasher.Sum(nil), sig) |
||||
} |
||||
|
||||
// Implements the Sign method from SigningMethod
|
||||
// For this signing method, must be either a PEM encoded PKCS1 or PKCS8 RSA private key as
|
||||
// []byte, or an rsa.PrivateKey structure.
|
||||
func (m *SigningMethodRSA) Sign(signingString string, key interface{}) (string, error) { |
||||
var err error |
||||
var rsaKey *rsa.PrivateKey |
||||
|
||||
switch k := key.(type) { |
||||
case []byte: |
||||
if rsaKey, err = ParseRSAPrivateKeyFromPEM(k); err != nil { |
||||
return "", err |
||||
} |
||||
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.SignPKCS1v15(rand.Reader, rsaKey, m.Hash, hasher.Sum(nil)); err == nil { |
||||
return EncodeSegment(sigBytes), nil |
||||
} else { |
||||
return "", err |
||||
} |
||||
} |
@ -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.PublicKey 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.PrivateKey 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 |
||||
} |
||||
} |
@ -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]) |
||||
} |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,174 @@ |
||||
package jwt_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) { |
||||
key, _ := ioutil.ReadFile("test/sample_key.pub") |
||||
|
||||
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) { |
||||
key, _ := ioutil.ReadFile("test/sample_key") |
||||
|
||||
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) |
||||
} |
@ -0,0 +1,68 @@ |
||||
package jwt |
||||
|
||||
import ( |
||||
"crypto/rsa" |
||||
"crypto/x509" |
||||
"encoding/pem" |
||||
"errors" |
||||
) |
||||
|
||||
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") |
||||
) |
||||
|
||||
// Parse 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 |
||||
} |
||||
|
||||
// Parse 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 |
||||
} |
@ -0,0 +1,24 @@ |
||||
package jwt |
||||
|
||||
var signingMethods = map[string]func() SigningMethod{} |
||||
|
||||
// Implement SigningMethod to add new methods for signing or verifying tokens.
|
||||
type SigningMethod interface { |
||||
Verify(signingString, signature string, key interface{}) error // Returns nil if signature is valid
|
||||
Sign(signingString string, key interface{}) (string, error) // Returns encoded signature or error
|
||||
Alg() string // returns the alg identifier for this method (example: 'HS256')
|
||||
} |
||||
|
||||
// Register the "alg" name and a factory function for signing method.
|
||||
// This is typically done during init() in the method's implementation
|
||||
func RegisterSigningMethod(alg string, f func() SigningMethod) { |
||||
signingMethods[alg] = f |
||||
} |
||||
|
||||
// Get a signing method from an "alg" string
|
||||
func GetSigningMethod(alg string) (method SigningMethod) { |
||||
if methodF, ok := signingMethods[alg]; ok { |
||||
method = methodF() |
||||
} |
||||
return |
||||
} |
@ -0,0 +1,126 @@ |
||||
package jwt |
||||
|
||||
import ( |
||||
"encoding/base64" |
||||
"encoding/json" |
||||
"net/http" |
||||
"strings" |
||||
"time" |
||||
) |
||||
|
||||
// TimeFunc provides the current time when parsing token to validate "exp" claim (expiration time).
|
||||
// You can override it to use another time value. This is useful for testing or if your
|
||||
// server uses a different time zone than your tokens.
|
||||
var TimeFunc = time.Now |
||||
|
||||
// Parse methods use this callback function to supply
|
||||
// the key for verification. The function receives the parsed,
|
||||
// but unverified Token. This allows you to use propries in the
|
||||
// Header of the token (such as `kid`) to identify which key to use.
|
||||
type Keyfunc func(*Token) (interface{}, error) |
||||
|
||||
// A JWT Token. Different fields will be used depending on whether you're
|
||||
// creating or parsing/verifying a token.
|
||||
type Token struct { |
||||
Raw string // The raw token. Populated when you Parse a token
|
||||
Method SigningMethod // The signing method used or to be used
|
||||
Header map[string]interface{} // The first segment of the token
|
||||
Claims map[string]interface{} // The second segment of the token
|
||||
Signature string // The third segment of the token. Populated when you Parse a token
|
||||
Valid bool // Is the token valid? Populated when you Parse/Verify a token
|
||||
} |
||||
|
||||
// Create a new Token. Takes a signing method
|
||||
func New(method SigningMethod) *Token { |
||||
return &Token{ |
||||
Header: map[string]interface{}{ |
||||
"typ": "JWT", |
||||
"alg": method.Alg(), |
||||
}, |
||||
Claims: make(map[string]interface{}), |
||||
Method: method, |
||||
} |
||||
} |
||||
|
||||
// Get the complete, signed token
|
||||
func (t *Token) SignedString(key interface{}) (string, error) { |
||||
var sig, sstr string |
||||
var err error |
||||
if sstr, err = t.SigningString(); err != nil { |
||||
return "", err |
||||
} |
||||
if sig, err = t.Method.Sign(sstr, key); err != nil { |
||||
return "", err |
||||
} |
||||
return strings.Join([]string{sstr, sig}, "."), nil |
||||
} |
||||
|
||||
// Generate the signing string. This is the
|
||||
// most expensive part of the whole deal. Unless you
|
||||
// need this for something special, just go straight for
|
||||
// the SignedString.
|
||||
func (t *Token) SigningString() (string, error) { |
||||
var err error |
||||
parts := make([]string, 2) |
||||
for i, _ := range parts { |
||||
var source map[string]interface{} |
||||
if i == 0 { |
||||
source = t.Header |
||||
} else { |
||||
source = t.Claims |
||||
} |
||||
|
||||
var jsonValue []byte |
||||
if jsonValue, err = json.Marshal(source); err != nil { |
||||
return "", err |
||||
} |
||||
|
||||
parts[i] = EncodeSegment(jsonValue) |
||||
} |
||||
return strings.Join(parts, "."), nil |
||||
} |
||||
|
||||
// Parse, validate, and return a token.
|
||||
// keyFunc will receive the parsed token and should return the key for validating.
|
||||
// If everything is kosher, err will be nil
|
||||
func Parse(tokenString string, keyFunc Keyfunc) (*Token, error) { |
||||
return new(Parser).Parse(tokenString, keyFunc) |
||||
} |
||||
|
||||
// Try to find the token in an http.Request.
|
||||
// This method will call ParseMultipartForm if there's no token in the header.
|
||||
// Currently, it looks in the Authorization header as well as
|
||||
// looking for an 'access_token' request parameter in req.Form.
|
||||
func ParseFromRequest(req *http.Request, keyFunc Keyfunc) (token *Token, err error) { |
||||
|
||||
// Look for an Authorization header
|
||||
if ah := req.Header.Get("Authorization"); ah != "" { |
||||
// Should be a bearer token
|
||||
if len(ah) > 6 && strings.ToUpper(ah[0:6]) == "BEARER" { |
||||
return Parse(ah[7:], keyFunc) |
||||
} |
||||
} |
||||
|
||||
// Look for "access_token" parameter
|
||||
req.ParseMultipartForm(10e6) |
||||
if tokStr := req.Form.Get("access_token"); tokStr != "" { |
||||
return Parse(tokStr, keyFunc) |
||||
} |
||||
|
||||
return nil, ErrNoTokenInRequest |
||||
|
||||
} |
||||
|
||||
// Encode JWT specific base64url encoding with padding stripped
|
||||
func EncodeSegment(seg []byte) string { |
||||
return strings.TrimRight(base64.URLEncoding.EncodeToString(seg), "=") |
||||
} |
||||
|
||||
// Decode JWT specific base64url encoding with padding stripped
|
||||
func DecodeSegment(seg string) ([]byte, error) { |
||||
if l := len(seg) % 4; l > 0 { |
||||
seg += strings.Repeat("=", 4-l) |
||||
} |
||||
|
||||
return base64.URLEncoding.DecodeString(seg) |
||||
} |
@ -0,0 +1,35 @@ |
||||
// Copyright 2011 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package bcrypt |
||||
|
||||
import "encoding/base64" |
||||
|
||||
const alphabet = "./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" |
||||
|
||||
var bcEncoding = base64.NewEncoding(alphabet) |
||||
|
||||
func base64Encode(src []byte) []byte { |
||||
n := bcEncoding.EncodedLen(len(src)) |
||||
dst := make([]byte, n) |
||||
bcEncoding.Encode(dst, src) |
||||
for dst[n-1] == '=' { |
||||
n-- |
||||
} |
||||
return dst[:n] |
||||
} |
||||
|
||||
func base64Decode(src []byte) ([]byte, error) { |
||||
numOfEquals := 4 - (len(src) % 4) |
||||
for i := 0; i < numOfEquals; i++ { |
||||
src = append(src, '=') |
||||
} |
||||
|
||||
dst := make([]byte, bcEncoding.DecodedLen(len(src))) |
||||
n, err := bcEncoding.Decode(dst, src) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
return dst[:n], nil |
||||
} |
@ -0,0 +1,294 @@ |
||||
// Copyright 2011 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package bcrypt implements Provos and Mazières's bcrypt adaptive hashing
|
||||
// algorithm. See http://www.usenix.org/event/usenix99/provos/provos.pdf
|
||||
package bcrypt // import "golang.org/x/crypto/bcrypt"
|
||||
|
||||
// The code is a port of Provos and Mazières's C implementation.
|
||||
import ( |
||||
"crypto/rand" |
||||
"crypto/subtle" |
||||
"errors" |
||||
"fmt" |
||||
"golang.org/x/crypto/blowfish" |
||||
"io" |
||||
"strconv" |
||||
) |
||||
|
||||
const ( |
||||
MinCost int = 4 // the minimum allowable cost as passed in to GenerateFromPassword
|
||||
MaxCost int = 31 // the maximum allowable cost as passed in to GenerateFromPassword
|
||||
DefaultCost int = 10 // the cost that will actually be set if a cost below MinCost is passed into GenerateFromPassword
|
||||
) |
||||
|
||||
// The error returned from CompareHashAndPassword when a password and hash do
|
||||
// not match.
|
||||
var ErrMismatchedHashAndPassword = errors.New("crypto/bcrypt: hashedPassword is not the hash of the given password") |
||||
|
||||
// The error returned from CompareHashAndPassword when a hash is too short to
|
||||
// be a bcrypt hash.
|
||||
var ErrHashTooShort = errors.New("crypto/bcrypt: hashedSecret too short to be a bcrypted password") |
||||
|
||||
// The error returned from CompareHashAndPassword when a hash was created with
|
||||
// a bcrypt algorithm newer than this implementation.
|
||||
type HashVersionTooNewError byte |
||||
|
||||
func (hv HashVersionTooNewError) Error() string { |
||||
return fmt.Sprintf("crypto/bcrypt: bcrypt algorithm version '%c' requested is newer than current version '%c'", byte(hv), majorVersion) |
||||
} |
||||
|
||||
// The error returned from CompareHashAndPassword when a hash starts with something other than '$'
|
||||
type InvalidHashPrefixError byte |
||||
|
||||
func (ih InvalidHashPrefixError) Error() string { |
||||
return fmt.Sprintf("crypto/bcrypt: bcrypt hashes must start with '$', but hashedSecret started with '%c'", byte(ih)) |
||||
} |
||||
|
||||
type InvalidCostError int |
||||
|
||||
func (ic InvalidCostError) Error() string { |
||||
return fmt.Sprintf("crypto/bcrypt: cost %d is outside allowed range (%d,%d)", int(ic), int(MinCost), int(MaxCost)) |
||||
} |
||||
|
||||
const ( |
||||
majorVersion = '2' |
||||
minorVersion = 'a' |
||||
maxSaltSize = 16 |
||||
maxCryptedHashSize = 23 |
||||
encodedSaltSize = 22 |
||||
encodedHashSize = 31 |
||||
minHashSize = 59 |
||||
) |
||||
|
||||
// magicCipherData is an IV for the 64 Blowfish encryption calls in
|
||||
// bcrypt(). It's the string "OrpheanBeholderScryDoubt" in big-endian bytes.
|
||||
var magicCipherData = []byte{ |
||||
0x4f, 0x72, 0x70, 0x68, |
||||
0x65, 0x61, 0x6e, 0x42, |
||||
0x65, 0x68, 0x6f, 0x6c, |
||||
0x64, 0x65, 0x72, 0x53, |
||||
0x63, 0x72, 0x79, 0x44, |
||||
0x6f, 0x75, 0x62, 0x74, |
||||
} |
||||
|
||||
type hashed struct { |
||||
hash []byte |
||||
salt []byte |
||||
cost int // allowed range is MinCost to MaxCost
|
||||
major byte |
||||
minor byte |
||||
} |
||||
|
||||
// GenerateFromPassword returns the bcrypt hash of the password at the given
|
||||
// cost. If the cost given is less than MinCost, the cost will be set to
|
||||
// DefaultCost, instead. Use CompareHashAndPassword, as defined in this package,
|
||||
// to compare the returned hashed password with its cleartext version.
|
||||
func GenerateFromPassword(password []byte, cost int) ([]byte, error) { |
||||
p, err := newFromPassword(password, cost) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
return p.Hash(), nil |
||||
} |
||||
|
||||
// CompareHashAndPassword compares a bcrypt hashed password with its possible
|
||||
// plaintext equivalent. Returns nil on success, or an error on failure.
|
||||
func CompareHashAndPassword(hashedPassword, password []byte) error { |
||||
p, err := newFromHash(hashedPassword) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
|
||||
otherHash, err := bcrypt(password, p.cost, p.salt) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
|
||||
otherP := &hashed{otherHash, p.salt, p.cost, p.major, p.minor} |
||||
if subtle.ConstantTimeCompare(p.Hash(), otherP.Hash()) == 1 { |
||||
return nil |
||||
} |
||||
|
||||
return ErrMismatchedHashAndPassword |
||||
} |
||||
|
||||
// Cost returns the hashing cost used to create the given hashed
|
||||
// password. When, in the future, the hashing cost of a password system needs
|
||||
// to be increased in order to adjust for greater computational power, this
|
||||
// function allows one to establish which passwords need to be updated.
|
||||
func Cost(hashedPassword []byte) (int, error) { |
||||
p, err := newFromHash(hashedPassword) |
||||
if err != nil { |
||||
return 0, err |
||||
} |
||||
return p.cost, nil |
||||
} |
||||
|
||||
func newFromPassword(password []byte, cost int) (*hashed, error) { |
||||
if cost < MinCost { |
||||
cost = DefaultCost |
||||
} |
||||
p := new(hashed) |
||||
p.major = majorVersion |
||||
p.minor = minorVersion |
||||
|
||||
err := checkCost(cost) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
p.cost = cost |
||||
|
||||
unencodedSalt := make([]byte, maxSaltSize) |
||||
_, err = io.ReadFull(rand.Reader, unencodedSalt) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
p.salt = base64Encode(unencodedSalt) |
||||
hash, err := bcrypt(password, p.cost, p.salt) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
p.hash = hash |
||||
return p, err |
||||
} |
||||
|
||||
func newFromHash(hashedSecret []byte) (*hashed, error) { |
||||
if len(hashedSecret) < minHashSize { |
||||
return nil, ErrHashTooShort |
||||
} |
||||
p := new(hashed) |
||||
n, err := p.decodeVersion(hashedSecret) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
hashedSecret = hashedSecret[n:] |
||||
n, err = p.decodeCost(hashedSecret) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
hashedSecret = hashedSecret[n:] |
||||
|
||||
// The "+2" is here because we'll have to append at most 2 '=' to the salt
|
||||
// when base64 decoding it in expensiveBlowfishSetup().
|
||||
p.salt = make([]byte, encodedSaltSize, encodedSaltSize+2) |
||||
copy(p.salt, hashedSecret[:encodedSaltSize]) |
||||
|
||||
hashedSecret = hashedSecret[encodedSaltSize:] |
||||
p.hash = make([]byte, len(hashedSecret)) |
||||
copy(p.hash, hashedSecret) |
||||
|
||||
return p, nil |
||||
} |
||||
|
||||
func bcrypt(password []byte, cost int, salt []byte) ([]byte, error) { |
||||
cipherData := make([]byte, len(magicCipherData)) |
||||
copy(cipherData, magicCipherData) |
||||
|
||||
c, err := expensiveBlowfishSetup(password, uint32(cost), salt) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
for i := 0; i < 24; i += 8 { |
||||
for j := 0; j < 64; j++ { |
||||
c.Encrypt(cipherData[i:i+8], cipherData[i:i+8]) |
||||
} |
||||
} |
||||
|
||||
// Bug compatibility with C bcrypt implementations. We only encode 23 of
|
||||
// the 24 bytes encrypted.
|
||||
hsh := base64Encode(cipherData[:maxCryptedHashSize]) |
||||
return hsh, nil |
||||
} |
||||
|
||||
func expensiveBlowfishSetup(key []byte, cost uint32, salt []byte) (*blowfish.Cipher, error) { |
||||
|
||||
csalt, err := base64Decode(salt) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
// Bug compatibility with C bcrypt implementations. They use the trailing
|
||||
// NULL in the key string during expansion.
|
||||
ckey := append(key, 0) |
||||
|
||||
c, err := blowfish.NewSaltedCipher(ckey, csalt) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
var i, rounds uint64 |
||||
rounds = 1 << cost |
||||
for i = 0; i < rounds; i++ { |
||||
blowfish.ExpandKey(ckey, c) |
||||
blowfish.ExpandKey(csalt, c) |
||||
} |
||||
|
||||
return c, nil |
||||
} |
||||
|
||||
func (p *hashed) Hash() []byte { |
||||
arr := make([]byte, 60) |
||||
arr[0] = '$' |
||||
arr[1] = p.major |
||||
n := 2 |
||||
if p.minor != 0 { |
||||
arr[2] = p.minor |
||||
n = 3 |
||||
} |
||||
arr[n] = '$' |
||||
n += 1 |
||||
copy(arr[n:], []byte(fmt.Sprintf("%02d", p.cost))) |
||||
n += 2 |
||||
arr[n] = '$' |
||||
n += 1 |
||||
copy(arr[n:], p.salt) |
||||
n += encodedSaltSize |
||||
copy(arr[n:], p.hash) |
||||
n += encodedHashSize |
||||
return arr[:n] |
||||
} |
||||
|
||||
func (p *hashed) decodeVersion(sbytes []byte) (int, error) { |
||||
if sbytes[0] != '$' { |
||||
return -1, InvalidHashPrefixError(sbytes[0]) |
||||
} |
||||
if sbytes[1] > majorVersion { |
||||
return -1, HashVersionTooNewError(sbytes[1]) |
||||
} |
||||
p.major = sbytes[1] |
||||
n := 3 |
||||
if sbytes[2] != '$' { |
||||
p.minor = sbytes[2] |
||||
n++ |
||||
} |
||||
return n, nil |
||||
} |
||||
|
||||
// sbytes should begin where decodeVersion left off.
|
||||
func (p *hashed) decodeCost(sbytes []byte) (int, error) { |
||||
cost, err := strconv.Atoi(string(sbytes[0:2])) |
||||
if err != nil { |
||||
return -1, err |
||||
} |
||||
err = checkCost(cost) |
||||
if err != nil { |
||||
return -1, err |
||||
} |
||||
p.cost = cost |
||||
return 3, nil |
||||
} |
||||
|
||||
func (p *hashed) String() string { |
||||
return fmt.Sprintf("&{hash: %#v, salt: %#v, cost: %d, major: %c, minor: %c}", string(p.hash), p.salt, p.cost, p.major, p.minor) |
||||
} |
||||
|
||||
func checkCost(cost int) error { |
||||
if cost < MinCost || cost > MaxCost { |
||||
return InvalidCostError(cost) |
||||
} |
||||
return nil |
||||
} |
@ -0,0 +1,226 @@ |
||||
// Copyright 2011 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package bcrypt |
||||
|
||||
import ( |
||||
"bytes" |
||||
"fmt" |
||||
"testing" |
||||
) |
||||
|
||||
func TestBcryptingIsEasy(t *testing.T) { |
||||
pass := []byte("mypassword") |
||||
hp, err := GenerateFromPassword(pass, 0) |
||||
if err != nil { |
||||
t.Fatalf("GenerateFromPassword error: %s", err) |
||||
} |
||||
|
||||
if CompareHashAndPassword(hp, pass) != nil { |
||||
t.Errorf("%v should hash %s correctly", hp, pass) |
||||
} |
||||
|
||||
notPass := "notthepass" |
||||
err = CompareHashAndPassword(hp, []byte(notPass)) |
||||
if err != ErrMismatchedHashAndPassword { |
||||
t.Errorf("%v and %s should be mismatched", hp, notPass) |
||||
} |
||||
} |
||||
|
||||
func TestBcryptingIsCorrect(t *testing.T) { |
||||
pass := []byte("allmine") |
||||
salt := []byte("XajjQvNhvvRt5GSeFk1xFe") |
||||
expectedHash := []byte("$2a$10$XajjQvNhvvRt5GSeFk1xFeyqRrsxkhBkUiQeg0dt.wU1qD4aFDcga") |
||||
|
||||
hash, err := bcrypt(pass, 10, salt) |
||||
if err != nil { |
||||
t.Fatalf("bcrypt blew up: %v", err) |
||||
} |
||||
if !bytes.HasSuffix(expectedHash, hash) { |
||||
t.Errorf("%v should be the suffix of %v", hash, expectedHash) |
||||
} |
||||
|
||||
h, err := newFromHash(expectedHash) |
||||
if err != nil { |
||||
t.Errorf("Unable to parse %s: %v", string(expectedHash), err) |
||||
} |
||||
|
||||
// This is not the safe way to compare these hashes. We do this only for
|
||||
// testing clarity. Use bcrypt.CompareHashAndPassword()
|
||||
if err == nil && !bytes.Equal(expectedHash, h.Hash()) { |
||||
t.Errorf("Parsed hash %v should equal %v", h.Hash(), expectedHash) |
||||
} |
||||
} |
||||
|
||||
func TestVeryShortPasswords(t *testing.T) { |
||||
key := []byte("k") |
||||
salt := []byte("XajjQvNhvvRt5GSeFk1xFe") |
||||
_, err := bcrypt(key, 10, salt) |
||||
if err != nil { |
||||
t.Errorf("One byte key resulted in error: %s", err) |
||||
} |
||||
} |
||||
|
||||
func TestTooLongPasswordsWork(t *testing.T) { |
||||
salt := []byte("XajjQvNhvvRt5GSeFk1xFe") |
||||
// One byte over the usual 56 byte limit that blowfish has
|
||||
tooLongPass := []byte("012345678901234567890123456789012345678901234567890123456") |
||||
tooLongExpected := []byte("$2a$10$XajjQvNhvvRt5GSeFk1xFe5l47dONXg781AmZtd869sO8zfsHuw7C") |
||||
hash, err := bcrypt(tooLongPass, 10, salt) |
||||
if err != nil { |
||||
t.Fatalf("bcrypt blew up on long password: %v", err) |
||||
} |
||||
if !bytes.HasSuffix(tooLongExpected, hash) { |
||||
t.Errorf("%v should be the suffix of %v", hash, tooLongExpected) |
||||
} |
||||
} |
||||
|
||||
type InvalidHashTest struct { |
||||
err error |
||||
hash []byte |
||||
} |
||||
|
||||
var invalidTests = []InvalidHashTest{ |
||||
{ErrHashTooShort, []byte("$2a$10$fooo")}, |
||||
{ErrHashTooShort, []byte("$2a")}, |
||||
{HashVersionTooNewError('3'), []byte("$3a$10$sssssssssssssssssssssshhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh")}, |
||||
{InvalidHashPrefixError('%'), []byte("%2a$10$sssssssssssssssssssssshhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh")}, |
||||
{InvalidCostError(32), []byte("$2a$32$sssssssssssssssssssssshhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh")}, |
||||
} |
||||
|
||||
func TestInvalidHashErrors(t *testing.T) { |
||||
check := func(name string, expected, err error) { |
||||
if err == nil { |
||||
t.Errorf("%s: Should have returned an error", name) |
||||
} |
||||
if err != nil && err != expected { |
||||
t.Errorf("%s gave err %v but should have given %v", name, err, expected) |
||||
} |
||||
} |
||||
for _, iht := range invalidTests { |
||||
_, err := newFromHash(iht.hash) |
||||
check("newFromHash", iht.err, err) |
||||
err = CompareHashAndPassword(iht.hash, []byte("anything")) |
||||
check("CompareHashAndPassword", iht.err, err) |
||||
} |
||||
} |
||||
|
||||
func TestUnpaddedBase64Encoding(t *testing.T) { |
||||
original := []byte{101, 201, 101, 75, 19, 227, 199, 20, 239, 236, 133, 32, 30, 109, 243, 30} |
||||
encodedOriginal := []byte("XajjQvNhvvRt5GSeFk1xFe") |
||||
|
||||
encoded := base64Encode(original) |
||||
|
||||
if !bytes.Equal(encodedOriginal, encoded) { |
||||
t.Errorf("Encoded %v should have equaled %v", encoded, encodedOriginal) |
||||
} |
||||
|
||||
decoded, err := base64Decode(encodedOriginal) |
||||
if err != nil { |
||||
t.Fatalf("base64Decode blew up: %s", err) |
||||
} |
||||
|
||||
if !bytes.Equal(decoded, original) { |
||||
t.Errorf("Decoded %v should have equaled %v", decoded, original) |
||||
} |
||||
} |
||||
|
||||
func TestCost(t *testing.T) { |
||||
suffix := "XajjQvNhvvRt5GSeFk1xFe5l47dONXg781AmZtd869sO8zfsHuw7C" |
||||
for _, vers := range []string{"2a", "2"} { |
||||
for _, cost := range []int{4, 10} { |
||||
s := fmt.Sprintf("$%s$%02d$%s", vers, cost, suffix) |
||||
h := []byte(s) |
||||
actual, err := Cost(h) |
||||
if err != nil { |
||||
t.Errorf("Cost, error: %s", err) |
||||
continue |
||||
} |
||||
if actual != cost { |
||||
t.Errorf("Cost, expected: %d, actual: %d", cost, actual) |
||||
} |
||||
} |
||||
} |
||||
_, err := Cost([]byte("$a$a$" + suffix)) |
||||
if err == nil { |
||||
t.Errorf("Cost, malformed but no error returned") |
||||
} |
||||
} |
||||
|
||||
func TestCostValidationInHash(t *testing.T) { |
||||
if testing.Short() { |
||||
return |
||||
} |
||||
|
||||
pass := []byte("mypassword") |
||||
|
||||
for c := 0; c < MinCost; c++ { |
||||
p, _ := newFromPassword(pass, c) |
||||
if p.cost != DefaultCost { |
||||
t.Errorf("newFromPassword should default costs below %d to %d, but was %d", MinCost, DefaultCost, p.cost) |
||||
} |
||||
} |
||||
|
||||
p, _ := newFromPassword(pass, 14) |
||||
if p.cost != 14 { |
||||
t.Errorf("newFromPassword should default cost to 14, but was %d", p.cost) |
||||
} |
||||
|
||||
hp, _ := newFromHash(p.Hash()) |
||||
if p.cost != hp.cost { |
||||
t.Errorf("newFromHash should maintain the cost at %d, but was %d", p.cost, hp.cost) |
||||
} |
||||
|
||||
_, err := newFromPassword(pass, 32) |
||||
if err == nil { |
||||
t.Fatalf("newFromPassword: should return a cost error") |
||||
} |
||||
if err != InvalidCostError(32) { |
||||
t.Errorf("newFromPassword: should return cost error, got %#v", err) |
||||
} |
||||
} |
||||
|
||||
func TestCostReturnsWithLeadingZeroes(t *testing.T) { |
||||
hp, _ := newFromPassword([]byte("abcdefgh"), 7) |
||||
cost := hp.Hash()[4:7] |
||||
expected := []byte("07$") |
||||
|
||||
if !bytes.Equal(expected, cost) { |
||||
t.Errorf("single digit costs in hash should have leading zeros: was %v instead of %v", cost, expected) |
||||
} |
||||
} |
||||
|
||||
func TestMinorNotRequired(t *testing.T) { |
||||
noMinorHash := []byte("$2$10$XajjQvNhvvRt5GSeFk1xFeyqRrsxkhBkUiQeg0dt.wU1qD4aFDcga") |
||||
h, err := newFromHash(noMinorHash) |
||||
if err != nil { |
||||
t.Fatalf("No minor hash blew up: %s", err) |
||||
} |
||||
if h.minor != 0 { |
||||
t.Errorf("Should leave minor version at 0, but was %d", h.minor) |
||||
} |
||||
|
||||
if !bytes.Equal(noMinorHash, h.Hash()) { |
||||
t.Errorf("Should generate hash %v, but created %v", noMinorHash, h.Hash()) |
||||
} |
||||
} |
||||
|
||||
func BenchmarkEqual(b *testing.B) { |
||||
b.StopTimer() |
||||
passwd := []byte("somepasswordyoulike") |
||||
hash, _ := GenerateFromPassword(passwd, 10) |
||||
b.StartTimer() |
||||
for i := 0; i < b.N; i++ { |
||||
CompareHashAndPassword(hash, passwd) |
||||
} |
||||
} |
||||
|
||||
func BenchmarkGeneration(b *testing.B) { |
||||
b.StopTimer() |
||||
passwd := []byte("mylongpassword1234") |
||||
b.StartTimer() |
||||
for i := 0; i < b.N; i++ { |
||||
GenerateFromPassword(passwd, 10) |
||||
} |
||||
} |
@ -0,0 +1,159 @@ |
||||
// Copyright 2010 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package blowfish |
||||
|
||||
// getNextWord returns the next big-endian uint32 value from the byte slice
|
||||
// at the given position in a circular manner, updating the position.
|
||||
func getNextWord(b []byte, pos *int) uint32 { |
||||
var w uint32 |
||||
j := *pos |
||||
for i := 0; i < 4; i++ { |
||||
w = w<<8 | uint32(b[j]) |
||||
j++ |
||||
if j >= len(b) { |
||||
j = 0 |
||||
} |
||||
} |
||||
*pos = j |
||||
return w |
||||
} |
||||
|
||||
// ExpandKey performs a key expansion on the given *Cipher. Specifically, it
|
||||
// performs the Blowfish algorithm's key schedule which sets up the *Cipher's
|
||||
// pi and substitution tables for calls to Encrypt. This is used, primarily,
|
||||
// by the bcrypt package to reuse the Blowfish key schedule during its
|
||||
// set up. It's unlikely that you need to use this directly.
|
||||
func ExpandKey(key []byte, c *Cipher) { |
||||
j := 0 |
||||
for i := 0; i < 18; i++ { |
||||
// Using inlined getNextWord for performance.
|
||||
var d uint32 |
||||
for k := 0; k < 4; k++ { |
||||
d = d<<8 | uint32(key[j]) |
||||
j++ |
||||
if j >= len(key) { |
||||
j = 0 |
||||
} |
||||
} |
||||
c.p[i] ^= d |
||||
} |
||||
|
||||
var l, r uint32 |
||||
for i := 0; i < 18; i += 2 { |
||||
l, r = encryptBlock(l, r, c) |
||||
c.p[i], c.p[i+1] = l, r |
||||
} |
||||
|
||||
for i := 0; i < 256; i += 2 { |
||||
l, r = encryptBlock(l, r, c) |
||||
c.s0[i], c.s0[i+1] = l, r |
||||
} |
||||
for i := 0; i < 256; i += 2 { |
||||
l, r = encryptBlock(l, r, c) |
||||
c.s1[i], c.s1[i+1] = l, r |
||||
} |
||||
for i := 0; i < 256; i += 2 { |
||||
l, r = encryptBlock(l, r, c) |
||||
c.s2[i], c.s2[i+1] = l, r |
||||
} |
||||
for i := 0; i < 256; i += 2 { |
||||
l, r = encryptBlock(l, r, c) |
||||
c.s3[i], c.s3[i+1] = l, r |
||||
} |
||||
} |
||||
|
||||
// This is similar to ExpandKey, but folds the salt during the key
|
||||
// schedule. While ExpandKey is essentially expandKeyWithSalt with an all-zero
|
||||
// salt passed in, reusing ExpandKey turns out to be a place of inefficiency
|
||||
// and specializing it here is useful.
|
||||
func expandKeyWithSalt(key []byte, salt []byte, c *Cipher) { |
||||
j := 0 |
||||
for i := 0; i < 18; i++ { |
||||
c.p[i] ^= getNextWord(key, &j) |
||||
} |
||||
|
||||
j = 0 |
||||
var l, r uint32 |
||||
for i := 0; i < 18; i += 2 { |
||||
l ^= getNextWord(salt, &j) |
||||
r ^= getNextWord(salt, &j) |
||||
l, r = encryptBlock(l, r, c) |
||||
c.p[i], c.p[i+1] = l, r |
||||
} |
||||
|
||||
for i := 0; i < 256; i += 2 { |
||||
l ^= getNextWord(salt, &j) |
||||
r ^= getNextWord(salt, &j) |
||||
l, r = encryptBlock(l, r, c) |
||||
c.s0[i], c.s0[i+1] = l, r |
||||
} |
||||
|
||||
for i := 0; i < 256; i += 2 { |
||||
l ^= getNextWord(salt, &j) |
||||
r ^= getNextWord(salt, &j) |
||||
l, r = encryptBlock(l, r, c) |
||||
c.s1[i], c.s1[i+1] = l, r |
||||
} |
||||
|
||||
for i := 0; i < 256; i += 2 { |
||||
l ^= getNextWord(salt, &j) |
||||
r ^= getNextWord(salt, &j) |
||||
l, r = encryptBlock(l, r, c) |
||||
c.s2[i], c.s2[i+1] = l, r |
||||
} |
||||
|
||||
for i := 0; i < 256; i += 2 { |
||||
l ^= getNextWord(salt, &j) |
||||
r ^= getNextWord(salt, &j) |
||||
l, r = encryptBlock(l, r, c) |
||||
c.s3[i], c.s3[i+1] = l, r |
||||
} |
||||
} |
||||
|
||||
func encryptBlock(l, r uint32, c *Cipher) (uint32, uint32) { |
||||
xl, xr := l, r |
||||
xl ^= c.p[0] |
||||
xr ^= ((c.s0[byte(xl>>24)] + c.s1[byte(xl>>16)]) ^ c.s2[byte(xl>>8)]) + c.s3[byte(xl)] ^ c.p[1] |
||||
xl ^= ((c.s0[byte(xr>>24)] + c.s1[byte(xr>>16)]) ^ c.s2[byte(xr>>8)]) + c.s3[byte(xr)] ^ c.p[2] |
||||
xr ^= ((c.s0[byte(xl>>24)] + c.s1[byte(xl>>16)]) ^ c.s2[byte(xl>>8)]) + c.s3[byte(xl)] ^ c.p[3] |
||||
xl ^= ((c.s0[byte(xr>>24)] + c.s1[byte(xr>>16)]) ^ c.s2[byte(xr>>8)]) + c.s3[byte(xr)] ^ c.p[4] |
||||
xr ^= ((c.s0[byte(xl>>24)] + c.s1[byte(xl>>16)]) ^ c.s2[byte(xl>>8)]) + c.s3[byte(xl)] ^ c.p[5] |
||||
xl ^= ((c.s0[byte(xr>>24)] + c.s1[byte(xr>>16)]) ^ c.s2[byte(xr>>8)]) + c.s3[byte(xr)] ^ c.p[6] |
||||
xr ^= ((c.s0[byte(xl>>24)] + c.s1[byte(xl>>16)]) ^ c.s2[byte(xl>>8)]) + c.s3[byte(xl)] ^ c.p[7] |
||||
xl ^= ((c.s0[byte(xr>>24)] + c.s1[byte(xr>>16)]) ^ c.s2[byte(xr>>8)]) + c.s3[byte(xr)] ^ c.p[8] |
||||
xr ^= ((c.s0[byte(xl>>24)] + c.s1[byte(xl>>16)]) ^ c.s2[byte(xl>>8)]) + c.s3[byte(xl)] ^ c.p[9] |
||||
xl ^= ((c.s0[byte(xr>>24)] + c.s1[byte(xr>>16)]) ^ c.s2[byte(xr>>8)]) + c.s3[byte(xr)] ^ c.p[10] |
||||
xr ^= ((c.s0[byte(xl>>24)] + c.s1[byte(xl>>16)]) ^ c.s2[byte(xl>>8)]) + c.s3[byte(xl)] ^ c.p[11] |
||||
xl ^= ((c.s0[byte(xr>>24)] + c.s1[byte(xr>>16)]) ^ c.s2[byte(xr>>8)]) + c.s3[byte(xr)] ^ c.p[12] |
||||
xr ^= ((c.s0[byte(xl>>24)] + c.s1[byte(xl>>16)]) ^ c.s2[byte(xl>>8)]) + c.s3[byte(xl)] ^ c.p[13] |
||||
xl ^= ((c.s0[byte(xr>>24)] + c.s1[byte(xr>>16)]) ^ c.s2[byte(xr>>8)]) + c.s3[byte(xr)] ^ c.p[14] |
||||
xr ^= ((c.s0[byte(xl>>24)] + c.s1[byte(xl>>16)]) ^ c.s2[byte(xl>>8)]) + c.s3[byte(xl)] ^ c.p[15] |
||||
xl ^= ((c.s0[byte(xr>>24)] + c.s1[byte(xr>>16)]) ^ c.s2[byte(xr>>8)]) + c.s3[byte(xr)] ^ c.p[16] |
||||
xr ^= c.p[17] |
||||
return xr, xl |
||||
} |
||||
|
||||
func decryptBlock(l, r uint32, c *Cipher) (uint32, uint32) { |
||||
xl, xr := l, r |
||||
xl ^= c.p[17] |
||||
xr ^= ((c.s0[byte(xl>>24)] + c.s1[byte(xl>>16)]) ^ c.s2[byte(xl>>8)]) + c.s3[byte(xl)] ^ c.p[16] |
||||
xl ^= ((c.s0[byte(xr>>24)] + c.s1[byte(xr>>16)]) ^ c.s2[byte(xr>>8)]) + c.s3[byte(xr)] ^ c.p[15] |
||||
xr ^= ((c.s0[byte(xl>>24)] + c.s1[byte(xl>>16)]) ^ c.s2[byte(xl>>8)]) + c.s3[byte(xl)] ^ c.p[14] |
||||
xl ^= ((c.s0[byte(xr>>24)] + c.s1[byte(xr>>16)]) ^ c.s2[byte(xr>>8)]) + c.s3[byte(xr)] ^ c.p[13] |
||||
xr ^= ((c.s0[byte(xl>>24)] + c.s1[byte(xl>>16)]) ^ c.s2[byte(xl>>8)]) + c.s3[byte(xl)] ^ c.p[12] |
||||
xl ^= ((c.s0[byte(xr>>24)] + c.s1[byte(xr>>16)]) ^ c.s2[byte(xr>>8)]) + c.s3[byte(xr)] ^ c.p[11] |
||||
xr ^= ((c.s0[byte(xl>>24)] + c.s1[byte(xl>>16)]) ^ c.s2[byte(xl>>8)]) + c.s3[byte(xl)] ^ c.p[10] |
||||
xl ^= ((c.s0[byte(xr>>24)] + c.s1[byte(xr>>16)]) ^ c.s2[byte(xr>>8)]) + c.s3[byte(xr)] ^ c.p[9] |
||||
xr ^= ((c.s0[byte(xl>>24)] + c.s1[byte(xl>>16)]) ^ c.s2[byte(xl>>8)]) + c.s3[byte(xl)] ^ c.p[8] |
||||
xl ^= ((c.s0[byte(xr>>24)] + c.s1[byte(xr>>16)]) ^ c.s2[byte(xr>>8)]) + c.s3[byte(xr)] ^ c.p[7] |
||||
xr ^= ((c.s0[byte(xl>>24)] + c.s1[byte(xl>>16)]) ^ c.s2[byte(xl>>8)]) + c.s3[byte(xl)] ^ c.p[6] |
||||
xl ^= ((c.s0[byte(xr>>24)] + c.s1[byte(xr>>16)]) ^ c.s2[byte(xr>>8)]) + c.s3[byte(xr)] ^ c.p[5] |
||||
xr ^= ((c.s0[byte(xl>>24)] + c.s1[byte(xl>>16)]) ^ c.s2[byte(xl>>8)]) + c.s3[byte(xl)] ^ c.p[4] |
||||
xl ^= ((c.s0[byte(xr>>24)] + c.s1[byte(xr>>16)]) ^ c.s2[byte(xr>>8)]) + c.s3[byte(xr)] ^ c.p[3] |
||||
xr ^= ((c.s0[byte(xl>>24)] + c.s1[byte(xl>>16)]) ^ c.s2[byte(xl>>8)]) + c.s3[byte(xl)] ^ c.p[2] |
||||
xl ^= ((c.s0[byte(xr>>24)] + c.s1[byte(xr>>16)]) ^ c.s2[byte(xr>>8)]) + c.s3[byte(xr)] ^ c.p[1] |
||||
xr ^= c.p[0] |
||||
return xr, xl |
||||
} |
@ -0,0 +1,274 @@ |
||||
// Copyright 2010 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package blowfish |
||||
|
||||
import "testing" |
||||
|
||||
type CryptTest struct { |
||||
key []byte |
||||
in []byte |
||||
out []byte |
||||
} |
||||
|
||||
// Test vector values are from http://www.schneier.com/code/vectors.txt.
|
||||
var encryptTests = []CryptTest{ |
||||
{ |
||||
[]byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, |
||||
[]byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, |
||||
[]byte{0x4E, 0xF9, 0x97, 0x45, 0x61, 0x98, 0xDD, 0x78}}, |
||||
{ |
||||
[]byte{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, |
||||
[]byte{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, |
||||
[]byte{0x51, 0x86, 0x6F, 0xD5, 0xB8, 0x5E, 0xCB, 0x8A}}, |
||||
{ |
||||
[]byte{0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, |
||||
[]byte{0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01}, |
||||
[]byte{0x7D, 0x85, 0x6F, 0x9A, 0x61, 0x30, 0x63, 0xF2}}, |
||||
{ |
||||
[]byte{0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11}, |
||||
[]byte{0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11}, |
||||
[]byte{0x24, 0x66, 0xDD, 0x87, 0x8B, 0x96, 0x3C, 0x9D}}, |
||||
|
||||
{ |
||||
[]byte{0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF}, |
||||
[]byte{0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11}, |
||||
[]byte{0x61, 0xF9, 0xC3, 0x80, 0x22, 0x81, 0xB0, 0x96}}, |
||||
{ |
||||
[]byte{0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11}, |
||||
[]byte{0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF}, |
||||
[]byte{0x7D, 0x0C, 0xC6, 0x30, 0xAF, 0xDA, 0x1E, 0xC7}}, |
||||
{ |
||||
[]byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, |
||||
[]byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, |
||||
[]byte{0x4E, 0xF9, 0x97, 0x45, 0x61, 0x98, 0xDD, 0x78}}, |
||||
{ |
||||
[]byte{0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10}, |
||||
[]byte{0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF}, |
||||
[]byte{0x0A, 0xCE, 0xAB, 0x0F, 0xC6, 0xA0, 0xA2, 0x8D}}, |
||||
{ |
||||
[]byte{0x7C, 0xA1, 0x10, 0x45, 0x4A, 0x1A, 0x6E, 0x57}, |
||||
[]byte{0x01, 0xA1, 0xD6, 0xD0, 0x39, 0x77, 0x67, 0x42}, |
||||
[]byte{0x59, 0xC6, 0x82, 0x45, 0xEB, 0x05, 0x28, 0x2B}}, |
||||
{ |
||||
[]byte{0x01, 0x31, 0xD9, 0x61, 0x9D, 0xC1, 0x37, 0x6E}, |
||||
[]byte{0x5C, 0xD5, 0x4C, 0xA8, 0x3D, 0xEF, 0x57, 0xDA}, |
||||
[]byte{0xB1, 0xB8, 0xCC, 0x0B, 0x25, 0x0F, 0x09, 0xA0}}, |
||||
{ |
||||
[]byte{0x07, 0xA1, 0x13, 0x3E, 0x4A, 0x0B, 0x26, 0x86}, |
||||
[]byte{0x02, 0x48, 0xD4, 0x38, 0x06, 0xF6, 0x71, 0x72}, |
||||
[]byte{0x17, 0x30, 0xE5, 0x77, 0x8B, 0xEA, 0x1D, 0xA4}}, |
||||
{ |
||||
[]byte{0x38, 0x49, 0x67, 0x4C, 0x26, 0x02, 0x31, 0x9E}, |
||||
[]byte{0x51, 0x45, 0x4B, 0x58, 0x2D, 0xDF, 0x44, 0x0A}, |
||||
[]byte{0xA2, 0x5E, 0x78, 0x56, 0xCF, 0x26, 0x51, 0xEB}}, |
||||
{ |
||||
[]byte{0x04, 0xB9, 0x15, 0xBA, 0x43, 0xFE, 0xB5, 0xB6}, |
||||
[]byte{0x42, 0xFD, 0x44, 0x30, 0x59, 0x57, 0x7F, 0xA2}, |
||||
[]byte{0x35, 0x38, 0x82, 0xB1, 0x09, 0xCE, 0x8F, 0x1A}}, |
||||
{ |
||||
[]byte{0x01, 0x13, 0xB9, 0x70, 0xFD, 0x34, 0xF2, 0xCE}, |
||||
[]byte{0x05, 0x9B, 0x5E, 0x08, 0x51, 0xCF, 0x14, 0x3A}, |
||||
[]byte{0x48, 0xF4, 0xD0, 0x88, 0x4C, 0x37, 0x99, 0x18}}, |
||||
{ |
||||
[]byte{0x01, 0x70, 0xF1, 0x75, 0x46, 0x8F, 0xB5, 0xE6}, |
||||
[]byte{0x07, 0x56, 0xD8, 0xE0, 0x77, 0x47, 0x61, 0xD2}, |
||||
[]byte{0x43, 0x21, 0x93, 0xB7, 0x89, 0x51, 0xFC, 0x98}}, |
||||
{ |
||||
[]byte{0x43, 0x29, 0x7F, 0xAD, 0x38, 0xE3, 0x73, 0xFE}, |
||||
[]byte{0x76, 0x25, 0x14, 0xB8, 0x29, 0xBF, 0x48, 0x6A}, |
||||
[]byte{0x13, 0xF0, 0x41, 0x54, 0xD6, 0x9D, 0x1A, 0xE5}}, |
||||
{ |
||||
[]byte{0x07, 0xA7, 0x13, 0x70, 0x45, 0xDA, 0x2A, 0x16}, |
||||
[]byte{0x3B, 0xDD, 0x11, 0x90, 0x49, 0x37, 0x28, 0x02}, |
||||
[]byte{0x2E, 0xED, 0xDA, 0x93, 0xFF, 0xD3, 0x9C, 0x79}}, |
||||
{ |
||||
[]byte{0x04, 0x68, 0x91, 0x04, 0xC2, 0xFD, 0x3B, 0x2F}, |
||||
[]byte{0x26, 0x95, 0x5F, 0x68, 0x35, 0xAF, 0x60, 0x9A}, |
||||
[]byte{0xD8, 0x87, 0xE0, 0x39, 0x3C, 0x2D, 0xA6, 0xE3}}, |
||||
{ |
||||
[]byte{0x37, 0xD0, 0x6B, 0xB5, 0x16, 0xCB, 0x75, 0x46}, |
||||
[]byte{0x16, 0x4D, 0x5E, 0x40, 0x4F, 0x27, 0x52, 0x32}, |
||||
[]byte{0x5F, 0x99, 0xD0, 0x4F, 0x5B, 0x16, 0x39, 0x69}}, |
||||
{ |
||||
[]byte{0x1F, 0x08, 0x26, 0x0D, 0x1A, 0xC2, 0x46, 0x5E}, |
||||
[]byte{0x6B, 0x05, 0x6E, 0x18, 0x75, 0x9F, 0x5C, 0xCA}, |
||||
[]byte{0x4A, 0x05, 0x7A, 0x3B, 0x24, 0xD3, 0x97, 0x7B}}, |
||||
{ |
||||
[]byte{0x58, 0x40, 0x23, 0x64, 0x1A, 0xBA, 0x61, 0x76}, |
||||
[]byte{0x00, 0x4B, 0xD6, 0xEF, 0x09, 0x17, 0x60, 0x62}, |
||||
[]byte{0x45, 0x20, 0x31, 0xC1, 0xE4, 0xFA, 0xDA, 0x8E}}, |
||||
{ |
||||
[]byte{0x02, 0x58, 0x16, 0x16, 0x46, 0x29, 0xB0, 0x07}, |
||||
[]byte{0x48, 0x0D, 0x39, 0x00, 0x6E, 0xE7, 0x62, 0xF2}, |
||||
[]byte{0x75, 0x55, 0xAE, 0x39, 0xF5, 0x9B, 0x87, 0xBD}}, |
||||
{ |
||||
[]byte{0x49, 0x79, 0x3E, 0xBC, 0x79, 0xB3, 0x25, 0x8F}, |
||||
[]byte{0x43, 0x75, 0x40, 0xC8, 0x69, 0x8F, 0x3C, 0xFA}, |
||||
[]byte{0x53, 0xC5, 0x5F, 0x9C, 0xB4, 0x9F, 0xC0, 0x19}}, |
||||
{ |
||||
[]byte{0x4F, 0xB0, 0x5E, 0x15, 0x15, 0xAB, 0x73, 0xA7}, |
||||
[]byte{0x07, 0x2D, 0x43, 0xA0, 0x77, 0x07, 0x52, 0x92}, |
||||
[]byte{0x7A, 0x8E, 0x7B, 0xFA, 0x93, 0x7E, 0x89, 0xA3}}, |
||||
{ |
||||
[]byte{0x49, 0xE9, 0x5D, 0x6D, 0x4C, 0xA2, 0x29, 0xBF}, |
||||
[]byte{0x02, 0xFE, 0x55, 0x77, 0x81, 0x17, 0xF1, 0x2A}, |
||||
[]byte{0xCF, 0x9C, 0x5D, 0x7A, 0x49, 0x86, 0xAD, 0xB5}}, |
||||
{ |
||||
[]byte{0x01, 0x83, 0x10, 0xDC, 0x40, 0x9B, 0x26, 0xD6}, |
||||
[]byte{0x1D, 0x9D, 0x5C, 0x50, 0x18, 0xF7, 0x28, 0xC2}, |
||||
[]byte{0xD1, 0xAB, 0xB2, 0x90, 0x65, 0x8B, 0xC7, 0x78}}, |
||||
{ |
||||
[]byte{0x1C, 0x58, 0x7F, 0x1C, 0x13, 0x92, 0x4F, 0xEF}, |
||||
[]byte{0x30, 0x55, 0x32, 0x28, 0x6D, 0x6F, 0x29, 0x5A}, |
||||
[]byte{0x55, 0xCB, 0x37, 0x74, 0xD1, 0x3E, 0xF2, 0x01}}, |
||||
{ |
||||
[]byte{0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01}, |
||||
[]byte{0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF}, |
||||
[]byte{0xFA, 0x34, 0xEC, 0x48, 0x47, 0xB2, 0x68, 0xB2}}, |
||||
{ |
||||
[]byte{0x1F, 0x1F, 0x1F, 0x1F, 0x0E, 0x0E, 0x0E, 0x0E}, |
||||
[]byte{0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF}, |
||||
[]byte{0xA7, 0x90, 0x79, 0x51, 0x08, 0xEA, 0x3C, 0xAE}}, |
||||
{ |
||||
[]byte{0xE0, 0xFE, 0xE0, 0xFE, 0xF1, 0xFE, 0xF1, 0xFE}, |
||||
[]byte{0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF}, |
||||
[]byte{0xC3, 0x9E, 0x07, 0x2D, 0x9F, 0xAC, 0x63, 0x1D}}, |
||||
{ |
||||
[]byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, |
||||
[]byte{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, |
||||
[]byte{0x01, 0x49, 0x33, 0xE0, 0xCD, 0xAF, 0xF6, 0xE4}}, |
||||
{ |
||||
[]byte{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, |
||||
[]byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, |
||||
[]byte{0xF2, 0x1E, 0x9A, 0x77, 0xB7, 0x1C, 0x49, 0xBC}}, |
||||
{ |
||||
[]byte{0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF}, |
||||
[]byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, |
||||
[]byte{0x24, 0x59, 0x46, 0x88, 0x57, 0x54, 0x36, 0x9A}}, |
||||
{ |
||||
[]byte{0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10}, |
||||
[]byte{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, |
||||
[]byte{0x6B, 0x5C, 0x5A, 0x9C, 0x5D, 0x9E, 0x0A, 0x5A}}, |
||||
} |
||||
|
||||
func TestCipherEncrypt(t *testing.T) { |
||||
for i, tt := range encryptTests { |
||||
c, err := NewCipher(tt.key) |
||||
if err != nil { |
||||
t.Errorf("NewCipher(%d bytes) = %s", len(tt.key), err) |
||||
continue |
||||
} |
||||
ct := make([]byte, len(tt.out)) |
||||
c.Encrypt(ct, tt.in) |
||||
for j, v := range ct { |
||||
if v != tt.out[j] { |
||||
t.Errorf("Cipher.Encrypt, test vector #%d: cipher-text[%d] = %#x, expected %#x", i, j, v, tt.out[j]) |
||||
break |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
func TestCipherDecrypt(t *testing.T) { |
||||
for i, tt := range encryptTests { |
||||
c, err := NewCipher(tt.key) |
||||
if err != nil { |
||||
t.Errorf("NewCipher(%d bytes) = %s", len(tt.key), err) |
||||
continue |
||||
} |
||||
pt := make([]byte, len(tt.in)) |
||||
c.Decrypt(pt, tt.out) |
||||
for j, v := range pt { |
||||
if v != tt.in[j] { |
||||
t.Errorf("Cipher.Decrypt, test vector #%d: plain-text[%d] = %#x, expected %#x", i, j, v, tt.in[j]) |
||||
break |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
func TestSaltedCipherKeyLength(t *testing.T) { |
||||
if _, err := NewSaltedCipher(nil, []byte{'a'}); err != KeySizeError(0) { |
||||
t.Errorf("NewSaltedCipher with short key, gave error %#v, expected %#v", err, KeySizeError(0)) |
||||
} |
||||
|
||||
// A 57-byte key. One over the typical blowfish restriction.
|
||||
key := []byte("012345678901234567890123456789012345678901234567890123456") |
||||
if _, err := NewSaltedCipher(key, []byte{'a'}); err != nil { |
||||
t.Errorf("NewSaltedCipher with long key, gave error %#v", err) |
||||
} |
||||
} |
||||
|
||||
// Test vectors generated with Blowfish from OpenSSH.
|
||||
var saltedVectors = [][8]byte{ |
||||
{0x0c, 0x82, 0x3b, 0x7b, 0x8d, 0x01, 0x4b, 0x7e}, |
||||
{0xd1, 0xe1, 0x93, 0xf0, 0x70, 0xa6, 0xdb, 0x12}, |
||||
{0xfc, 0x5e, 0xba, 0xde, 0xcb, 0xf8, 0x59, 0xad}, |
||||
{0x8a, 0x0c, 0x76, 0xe7, 0xdd, 0x2c, 0xd3, 0xa8}, |
||||
{0x2c, 0xcb, 0x7b, 0xee, 0xac, 0x7b, 0x7f, 0xf8}, |
||||
{0xbb, 0xf6, 0x30, 0x6f, 0xe1, 0x5d, 0x62, 0xbf}, |
||||
{0x97, 0x1e, 0xc1, 0x3d, 0x3d, 0xe0, 0x11, 0xe9}, |
||||
{0x06, 0xd7, 0x4d, 0xb1, 0x80, 0xa3, 0xb1, 0x38}, |
||||
{0x67, 0xa1, 0xa9, 0x75, 0x0e, 0x5b, 0xc6, 0xb4}, |
||||
{0x51, 0x0f, 0x33, 0x0e, 0x4f, 0x67, 0xd2, 0x0c}, |
||||
{0xf1, 0x73, 0x7e, 0xd8, 0x44, 0xea, 0xdb, 0xe5}, |
||||
{0x14, 0x0e, 0x16, 0xce, 0x7f, 0x4a, 0x9c, 0x7b}, |
||||
{0x4b, 0xfe, 0x43, 0xfd, 0xbf, 0x36, 0x04, 0x47}, |
||||
{0xb1, 0xeb, 0x3e, 0x15, 0x36, 0xa7, 0xbb, 0xe2}, |
||||
{0x6d, 0x0b, 0x41, 0xdd, 0x00, 0x98, 0x0b, 0x19}, |
||||
{0xd3, 0xce, 0x45, 0xce, 0x1d, 0x56, 0xb7, 0xfc}, |
||||
{0xd9, 0xf0, 0xfd, 0xda, 0xc0, 0x23, 0xb7, 0x93}, |
||||
{0x4c, 0x6f, 0xa1, 0xe4, 0x0c, 0xa8, 0xca, 0x57}, |
||||
{0xe6, 0x2f, 0x28, 0xa7, 0x0c, 0x94, 0x0d, 0x08}, |
||||
{0x8f, 0xe3, 0xf0, 0xb6, 0x29, 0xe3, 0x44, 0x03}, |
||||
{0xff, 0x98, 0xdd, 0x04, 0x45, 0xb4, 0x6d, 0x1f}, |
||||
{0x9e, 0x45, 0x4d, 0x18, 0x40, 0x53, 0xdb, 0xef}, |
||||
{0xb7, 0x3b, 0xef, 0x29, 0xbe, 0xa8, 0x13, 0x71}, |
||||
{0x02, 0x54, 0x55, 0x41, 0x8e, 0x04, 0xfc, 0xad}, |
||||
{0x6a, 0x0a, 0xee, 0x7c, 0x10, 0xd9, 0x19, 0xfe}, |
||||
{0x0a, 0x22, 0xd9, 0x41, 0xcc, 0x23, 0x87, 0x13}, |
||||
{0x6e, 0xff, 0x1f, 0xff, 0x36, 0x17, 0x9c, 0xbe}, |
||||
{0x79, 0xad, 0xb7, 0x40, 0xf4, 0x9f, 0x51, 0xa6}, |
||||
{0x97, 0x81, 0x99, 0xa4, 0xde, 0x9e, 0x9f, 0xb6}, |
||||
{0x12, 0x19, 0x7a, 0x28, 0xd0, 0xdc, 0xcc, 0x92}, |
||||
{0x81, 0xda, 0x60, 0x1e, 0x0e, 0xdd, 0x65, 0x56}, |
||||
{0x7d, 0x76, 0x20, 0xb2, 0x73, 0xc9, 0x9e, 0xee}, |
||||
} |
||||
|
||||
func TestSaltedCipher(t *testing.T) { |
||||
var key, salt [32]byte |
||||
for i := range key { |
||||
key[i] = byte(i) |
||||
salt[i] = byte(i + 32) |
||||
} |
||||
for i, v := range saltedVectors { |
||||
c, err := NewSaltedCipher(key[:], salt[:i]) |
||||
if err != nil { |
||||
t.Fatal(err) |
||||
} |
||||
var buf [8]byte |
||||
c.Encrypt(buf[:], buf[:]) |
||||
if v != buf { |
||||
t.Errorf("%d: expected %x, got %x", i, v, buf) |
||||
} |
||||
} |
||||
} |
||||
|
||||
func BenchmarkExpandKeyWithSalt(b *testing.B) { |
||||
key := make([]byte, 32) |
||||
salt := make([]byte, 16) |
||||
c, _ := NewCipher(key) |
||||
for i := 0; i < b.N; i++ { |
||||
expandKeyWithSalt(key, salt, c) |
||||
} |
||||
} |
||||
|
||||
func BenchmarkExpandKey(b *testing.B) { |
||||
key := make([]byte, 32) |
||||
c, _ := NewCipher(key) |
||||
for i := 0; i < b.N; i++ { |
||||
ExpandKey(key, c) |
||||
} |
||||
} |
@ -0,0 +1,91 @@ |
||||
// Copyright 2010 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package blowfish implements Bruce Schneier's Blowfish encryption algorithm.
|
||||
package blowfish // import "golang.org/x/crypto/blowfish"
|
||||
|
||||
// The code is a port of Bruce Schneier's C implementation.
|
||||
// See http://www.schneier.com/blowfish.html.
|
||||
|
||||
import "strconv" |
||||
|
||||
// The Blowfish block size in bytes.
|
||||
const BlockSize = 8 |
||||
|
||||
// A Cipher is an instance of Blowfish encryption using a particular key.
|
||||
type Cipher struct { |
||||
p [18]uint32 |
||||
s0, s1, s2, s3 [256]uint32 |
||||
} |
||||
|
||||
type KeySizeError int |
||||
|
||||
func (k KeySizeError) Error() string { |
||||
return "crypto/blowfish: invalid key size " + strconv.Itoa(int(k)) |
||||
} |
||||
|
||||
// NewCipher creates and returns a Cipher.
|
||||
// The key argument should be the Blowfish key, from 1 to 56 bytes.
|
||||
func NewCipher(key []byte) (*Cipher, error) { |
||||
var result Cipher |
||||
if k := len(key); k < 1 || k > 56 { |
||||
return nil, KeySizeError(k) |
||||
} |
||||
initCipher(&result) |
||||
ExpandKey(key, &result) |
||||
return &result, nil |
||||
} |
||||
|
||||
// NewSaltedCipher creates a returns a Cipher that folds a salt into its key
|
||||
// schedule. For most purposes, NewCipher, instead of NewSaltedCipher, is
|
||||
// sufficient and desirable. For bcrypt compatiblity, the key can be over 56
|
||||
// bytes.
|
||||
func NewSaltedCipher(key, salt []byte) (*Cipher, error) { |
||||
if len(salt) == 0 { |
||||
return NewCipher(key) |
||||
} |
||||
var result Cipher |
||||
if k := len(key); k < 1 { |
||||
return nil, KeySizeError(k) |
||||
} |
||||
initCipher(&result) |
||||
expandKeyWithSalt(key, salt, &result) |
||||
return &result, nil |
||||
} |
||||
|
||||
// BlockSize returns the Blowfish block size, 8 bytes.
|
||||
// It is necessary to satisfy the Block interface in the
|
||||
// package "crypto/cipher".
|
||||
func (c *Cipher) BlockSize() int { return BlockSize } |
||||
|
||||
// Encrypt encrypts the 8-byte buffer src using the key k
|
||||
// and stores the result in dst.
|
||||
// Note that for amounts of data larger than a block,
|
||||
// it is not safe to just call Encrypt on successive blocks;
|
||||
// instead, use an encryption mode like CBC (see crypto/cipher/cbc.go).
|
||||
func (c *Cipher) Encrypt(dst, src []byte) { |
||||
l := uint32(src[0])<<24 | uint32(src[1])<<16 | uint32(src[2])<<8 | uint32(src[3]) |
||||
r := uint32(src[4])<<24 | uint32(src[5])<<16 | uint32(src[6])<<8 | uint32(src[7]) |
||||
l, r = encryptBlock(l, r, c) |
||||
dst[0], dst[1], dst[2], dst[3] = byte(l>>24), byte(l>>16), byte(l>>8), byte(l) |
||||
dst[4], dst[5], dst[6], dst[7] = byte(r>>24), byte(r>>16), byte(r>>8), byte(r) |
||||
} |
||||
|
||||
// Decrypt decrypts the 8-byte buffer src using the key k
|
||||
// and stores the result in dst.
|
||||
func (c *Cipher) Decrypt(dst, src []byte) { |
||||
l := uint32(src[0])<<24 | uint32(src[1])<<16 | uint32(src[2])<<8 | uint32(src[3]) |
||||
r := uint32(src[4])<<24 | uint32(src[5])<<16 | uint32(src[6])<<8 | uint32(src[7]) |
||||
l, r = decryptBlock(l, r, c) |
||||
dst[0], dst[1], dst[2], dst[3] = byte(l>>24), byte(l>>16), byte(l>>8), byte(l) |
||||
dst[4], dst[5], dst[6], dst[7] = byte(r>>24), byte(r>>16), byte(r>>8), byte(r) |
||||
} |
||||
|
||||
func initCipher(c *Cipher) { |
||||
copy(c.p[0:], p[0:]) |
||||
copy(c.s0[0:], s0[0:]) |
||||
copy(c.s1[0:], s1[0:]) |
||||
copy(c.s2[0:], s2[0:]) |
||||
copy(c.s3[0:], s3[0:]) |
||||
} |
@ -0,0 +1,199 @@ |
||||
// Copyright 2010 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// The startup permutation array and substitution boxes.
|
||||
// They are the hexadecimal digits of PI; see:
|
||||
// http://www.schneier.com/code/constants.txt.
|
||||
|
||||
package blowfish |
||||
|
||||
var s0 = [256]uint32{ |
||||
0xd1310ba6, 0x98dfb5ac, 0x2ffd72db, 0xd01adfb7, 0xb8e1afed, 0x6a267e96, |
||||
0xba7c9045, 0xf12c7f99, 0x24a19947, 0xb3916cf7, 0x0801f2e2, 0x858efc16, |
||||
0x636920d8, 0x71574e69, 0xa458fea3, 0xf4933d7e, 0x0d95748f, 0x728eb658, |
||||
0x718bcd58, 0x82154aee, 0x7b54a41d, 0xc25a59b5, 0x9c30d539, 0x2af26013, |
||||
0xc5d1b023, 0x286085f0, 0xca417918, 0xb8db38ef, 0x8e79dcb0, 0x603a180e, |
||||
0x6c9e0e8b, 0xb01e8a3e, 0xd71577c1, 0xbd314b27, 0x78af2fda, 0x55605c60, |
||||
0xe65525f3, 0xaa55ab94, 0x57489862, 0x63e81440, 0x55ca396a, 0x2aab10b6, |
||||
0xb4cc5c34, 0x1141e8ce, 0xa15486af, 0x7c72e993, 0xb3ee1411, 0x636fbc2a, |
||||
0x2ba9c55d, 0x741831f6, 0xce5c3e16, 0x9b87931e, 0xafd6ba33, 0x6c24cf5c, |
||||
0x7a325381, 0x28958677, 0x3b8f4898, 0x6b4bb9af, 0xc4bfe81b, 0x66282193, |
||||
0x61d809cc, 0xfb21a991, 0x487cac60, 0x5dec8032, 0xef845d5d, 0xe98575b1, |
||||
0xdc262302, 0xeb651b88, 0x23893e81, 0xd396acc5, 0x0f6d6ff3, 0x83f44239, |
||||
0x2e0b4482, 0xa4842004, 0x69c8f04a, 0x9e1f9b5e, 0x21c66842, 0xf6e96c9a, |
||||
0x670c9c61, 0xabd388f0, 0x6a51a0d2, 0xd8542f68, 0x960fa728, 0xab5133a3, |
||||
0x6eef0b6c, 0x137a3be4, 0xba3bf050, 0x7efb2a98, 0xa1f1651d, 0x39af0176, |
||||
0x66ca593e, 0x82430e88, 0x8cee8619, 0x456f9fb4, 0x7d84a5c3, 0x3b8b5ebe, |
||||
0xe06f75d8, 0x85c12073, 0x401a449f, 0x56c16aa6, 0x4ed3aa62, 0x363f7706, |
||||
0x1bfedf72, 0x429b023d, 0x37d0d724, 0xd00a1248, 0xdb0fead3, 0x49f1c09b, |
||||
0x075372c9, 0x80991b7b, 0x25d479d8, 0xf6e8def7, 0xe3fe501a, 0xb6794c3b, |
||||
0x976ce0bd, 0x04c006ba, 0xc1a94fb6, 0x409f60c4, 0x5e5c9ec2, 0x196a2463, |
||||
0x68fb6faf, 0x3e6c53b5, 0x1339b2eb, 0x3b52ec6f, 0x6dfc511f, 0x9b30952c, |
||||
0xcc814544, 0xaf5ebd09, 0xbee3d004, 0xde334afd, 0x660f2807, 0x192e4bb3, |
||||
0xc0cba857, 0x45c8740f, 0xd20b5f39, 0xb9d3fbdb, 0x5579c0bd, 0x1a60320a, |
||||
0xd6a100c6, 0x402c7279, 0x679f25fe, 0xfb1fa3cc, 0x8ea5e9f8, 0xdb3222f8, |
||||
0x3c7516df, 0xfd616b15, 0x2f501ec8, 0xad0552ab, 0x323db5fa, 0xfd238760, |
||||
0x53317b48, 0x3e00df82, 0x9e5c57bb, 0xca6f8ca0, 0x1a87562e, 0xdf1769db, |
||||
0xd542a8f6, 0x287effc3, 0xac6732c6, 0x8c4f5573, 0x695b27b0, 0xbbca58c8, |
||||
0xe1ffa35d, 0xb8f011a0, 0x10fa3d98, 0xfd2183b8, 0x4afcb56c, 0x2dd1d35b, |
||||
0x9a53e479, 0xb6f84565, 0xd28e49bc, 0x4bfb9790, 0xe1ddf2da, 0xa4cb7e33, |
||||
0x62fb1341, 0xcee4c6e8, 0xef20cada, 0x36774c01, 0xd07e9efe, 0x2bf11fb4, |
||||
0x95dbda4d, 0xae909198, 0xeaad8e71, 0x6b93d5a0, 0xd08ed1d0, 0xafc725e0, |
||||
0x8e3c5b2f, 0x8e7594b7, 0x8ff6e2fb, 0xf2122b64, 0x8888b812, 0x900df01c, |
||||
0x4fad5ea0, 0x688fc31c, 0xd1cff191, 0xb3a8c1ad, 0x2f2f2218, 0xbe0e1777, |
||||
0xea752dfe, 0x8b021fa1, 0xe5a0cc0f, 0xb56f74e8, 0x18acf3d6, 0xce89e299, |
||||
0xb4a84fe0, 0xfd13e0b7, 0x7cc43b81, 0xd2ada8d9, 0x165fa266, 0x80957705, |
||||
0x93cc7314, 0x211a1477, 0xe6ad2065, 0x77b5fa86, 0xc75442f5, 0xfb9d35cf, |
||||
0xebcdaf0c, 0x7b3e89a0, 0xd6411bd3, 0xae1e7e49, 0x00250e2d, 0x2071b35e, |
||||
0x226800bb, 0x57b8e0af, 0x2464369b, 0xf009b91e, 0x5563911d, 0x59dfa6aa, |
||||
0x78c14389, 0xd95a537f, 0x207d5ba2, 0x02e5b9c5, 0x83260376, 0x6295cfa9, |
||||
0x11c81968, 0x4e734a41, 0xb3472dca, 0x7b14a94a, 0x1b510052, 0x9a532915, |
||||
0xd60f573f, 0xbc9bc6e4, 0x2b60a476, 0x81e67400, 0x08ba6fb5, 0x571be91f, |
||||
0xf296ec6b, 0x2a0dd915, 0xb6636521, 0xe7b9f9b6, 0xff34052e, 0xc5855664, |
||||
0x53b02d5d, 0xa99f8fa1, 0x08ba4799, 0x6e85076a, |
||||
} |
||||
|
||||
var s1 = [256]uint32{ |
||||
0x4b7a70e9, 0xb5b32944, 0xdb75092e, 0xc4192623, 0xad6ea6b0, 0x49a7df7d, |
||||
0x9cee60b8, 0x8fedb266, 0xecaa8c71, 0x699a17ff, 0x5664526c, 0xc2b19ee1, |
||||
0x193602a5, 0x75094c29, 0xa0591340, 0xe4183a3e, 0x3f54989a, 0x5b429d65, |
||||
0x6b8fe4d6, 0x99f73fd6, 0xa1d29c07, 0xefe830f5, 0x4d2d38e6, 0xf0255dc1, |
||||
0x4cdd2086, 0x8470eb26, 0x6382e9c6, 0x021ecc5e, 0x09686b3f, 0x3ebaefc9, |
||||
0x3c971814, 0x6b6a70a1, 0x687f3584, 0x52a0e286, 0xb79c5305, 0xaa500737, |
||||
0x3e07841c, 0x7fdeae5c, 0x8e7d44ec, 0x5716f2b8, 0xb03ada37, 0xf0500c0d, |
||||
0xf01c1f04, 0x0200b3ff, 0xae0cf51a, 0x3cb574b2, 0x25837a58, 0xdc0921bd, |
||||
0xd19113f9, 0x7ca92ff6, 0x94324773, 0x22f54701, 0x3ae5e581, 0x37c2dadc, |
||||
0xc8b57634, 0x9af3dda7, 0xa9446146, 0x0fd0030e, 0xecc8c73e, 0xa4751e41, |
||||
0xe238cd99, 0x3bea0e2f, 0x3280bba1, 0x183eb331, 0x4e548b38, 0x4f6db908, |
||||
0x6f420d03, 0xf60a04bf, 0x2cb81290, 0x24977c79, 0x5679b072, 0xbcaf89af, |
||||
0xde9a771f, 0xd9930810, 0xb38bae12, 0xdccf3f2e, 0x5512721f, 0x2e6b7124, |
||||
0x501adde6, 0x9f84cd87, 0x7a584718, 0x7408da17, 0xbc9f9abc, 0xe94b7d8c, |
||||
0xec7aec3a, 0xdb851dfa, 0x63094366, 0xc464c3d2, 0xef1c1847, 0x3215d908, |
||||
0xdd433b37, 0x24c2ba16, 0x12a14d43, 0x2a65c451, 0x50940002, 0x133ae4dd, |
||||
0x71dff89e, 0x10314e55, 0x81ac77d6, 0x5f11199b, 0x043556f1, 0xd7a3c76b, |
||||
0x3c11183b, 0x5924a509, 0xf28fe6ed, 0x97f1fbfa, 0x9ebabf2c, 0x1e153c6e, |
||||
0x86e34570, 0xeae96fb1, 0x860e5e0a, 0x5a3e2ab3, 0x771fe71c, 0x4e3d06fa, |
||||
0x2965dcb9, 0x99e71d0f, 0x803e89d6, 0x5266c825, 0x2e4cc978, 0x9c10b36a, |
||||
0xc6150eba, 0x94e2ea78, 0xa5fc3c53, 0x1e0a2df4, 0xf2f74ea7, 0x361d2b3d, |
||||
0x1939260f, 0x19c27960, 0x5223a708, 0xf71312b6, 0xebadfe6e, 0xeac31f66, |
||||
0xe3bc4595, 0xa67bc883, 0xb17f37d1, 0x018cff28, 0xc332ddef, 0xbe6c5aa5, |
||||
0x65582185, 0x68ab9802, 0xeecea50f, 0xdb2f953b, 0x2aef7dad, 0x5b6e2f84, |
||||
0x1521b628, 0x29076170, 0xecdd4775, 0x619f1510, 0x13cca830, 0xeb61bd96, |
||||
0x0334fe1e, 0xaa0363cf, 0xb5735c90, 0x4c70a239, 0xd59e9e0b, 0xcbaade14, |
||||
0xeecc86bc, 0x60622ca7, 0x9cab5cab, 0xb2f3846e, 0x648b1eaf, 0x19bdf0ca, |
||||
0xa02369b9, 0x655abb50, 0x40685a32, 0x3c2ab4b3, 0x319ee9d5, 0xc021b8f7, |
||||
0x9b540b19, 0x875fa099, 0x95f7997e, 0x623d7da8, 0xf837889a, 0x97e32d77, |
||||
0x11ed935f, 0x16681281, 0x0e358829, 0xc7e61fd6, 0x96dedfa1, 0x7858ba99, |
||||
0x57f584a5, 0x1b227263, 0x9b83c3ff, 0x1ac24696, 0xcdb30aeb, 0x532e3054, |
||||
0x8fd948e4, 0x6dbc3128, 0x58ebf2ef, 0x34c6ffea, 0xfe28ed61, 0xee7c3c73, |
||||
0x5d4a14d9, 0xe864b7e3, 0x42105d14, 0x203e13e0, 0x45eee2b6, 0xa3aaabea, |
||||
0xdb6c4f15, 0xfacb4fd0, 0xc742f442, 0xef6abbb5, 0x654f3b1d, 0x41cd2105, |
||||
0xd81e799e, 0x86854dc7, 0xe44b476a, 0x3d816250, 0xcf62a1f2, 0x5b8d2646, |
||||
0xfc8883a0, 0xc1c7b6a3, 0x7f1524c3, 0x69cb7492, 0x47848a0b, 0x5692b285, |
||||
0x095bbf00, 0xad19489d, 0x1462b174, 0x23820e00, 0x58428d2a, 0x0c55f5ea, |
||||
0x1dadf43e, 0x233f7061, 0x3372f092, 0x8d937e41, 0xd65fecf1, 0x6c223bdb, |
||||
0x7cde3759, 0xcbee7460, 0x4085f2a7, 0xce77326e, 0xa6078084, 0x19f8509e, |
||||
0xe8efd855, 0x61d99735, 0xa969a7aa, 0xc50c06c2, 0x5a04abfc, 0x800bcadc, |
||||
0x9e447a2e, 0xc3453484, 0xfdd56705, 0x0e1e9ec9, 0xdb73dbd3, 0x105588cd, |
||||
0x675fda79, 0xe3674340, 0xc5c43465, 0x713e38d8, 0x3d28f89e, 0xf16dff20, |
||||
0x153e21e7, 0x8fb03d4a, 0xe6e39f2b, 0xdb83adf7, |
||||
} |
||||
|
||||
var s2 = [256]uint32{ |
||||
0xe93d5a68, 0x948140f7, 0xf64c261c, 0x94692934, 0x411520f7, 0x7602d4f7, |
||||
0xbcf46b2e, 0xd4a20068, 0xd4082471, 0x3320f46a, 0x43b7d4b7, 0x500061af, |
||||
0x1e39f62e, 0x97244546, 0x14214f74, 0xbf8b8840, 0x4d95fc1d, 0x96b591af, |
||||
0x70f4ddd3, 0x66a02f45, 0xbfbc09ec, 0x03bd9785, 0x7fac6dd0, 0x31cb8504, |
||||
0x96eb27b3, 0x55fd3941, 0xda2547e6, 0xabca0a9a, 0x28507825, 0x530429f4, |
||||
0x0a2c86da, 0xe9b66dfb, 0x68dc1462, 0xd7486900, 0x680ec0a4, 0x27a18dee, |
||||
0x4f3ffea2, 0xe887ad8c, 0xb58ce006, 0x7af4d6b6, 0xaace1e7c, 0xd3375fec, |
||||
0xce78a399, 0x406b2a42, 0x20fe9e35, 0xd9f385b9, 0xee39d7ab, 0x3b124e8b, |
||||
0x1dc9faf7, 0x4b6d1856, 0x26a36631, 0xeae397b2, 0x3a6efa74, 0xdd5b4332, |
||||
0x6841e7f7, 0xca7820fb, 0xfb0af54e, 0xd8feb397, 0x454056ac, 0xba489527, |
||||
0x55533a3a, 0x20838d87, 0xfe6ba9b7, 0xd096954b, 0x55a867bc, 0xa1159a58, |
||||
0xcca92963, 0x99e1db33, 0xa62a4a56, 0x3f3125f9, 0x5ef47e1c, 0x9029317c, |
||||
0xfdf8e802, 0x04272f70, 0x80bb155c, 0x05282ce3, 0x95c11548, 0xe4c66d22, |
||||
0x48c1133f, 0xc70f86dc, 0x07f9c9ee, 0x41041f0f, 0x404779a4, 0x5d886e17, |
||||
0x325f51eb, 0xd59bc0d1, 0xf2bcc18f, 0x41113564, 0x257b7834, 0x602a9c60, |
||||
0xdff8e8a3, 0x1f636c1b, 0x0e12b4c2, 0x02e1329e, 0xaf664fd1, 0xcad18115, |
||||
0x6b2395e0, 0x333e92e1, 0x3b240b62, 0xeebeb922, 0x85b2a20e, 0xe6ba0d99, |
||||
0xde720c8c, 0x2da2f728, 0xd0127845, 0x95b794fd, 0x647d0862, 0xe7ccf5f0, |
||||
0x5449a36f, 0x877d48fa, 0xc39dfd27, 0xf33e8d1e, 0x0a476341, 0x992eff74, |
||||
0x3a6f6eab, 0xf4f8fd37, 0xa812dc60, 0xa1ebddf8, 0x991be14c, 0xdb6e6b0d, |
||||
0xc67b5510, 0x6d672c37, 0x2765d43b, 0xdcd0e804, 0xf1290dc7, 0xcc00ffa3, |
||||
0xb5390f92, 0x690fed0b, 0x667b9ffb, 0xcedb7d9c, 0xa091cf0b, 0xd9155ea3, |
||||
0xbb132f88, 0x515bad24, 0x7b9479bf, 0x763bd6eb, 0x37392eb3, 0xcc115979, |
||||
0x8026e297, 0xf42e312d, 0x6842ada7, 0xc66a2b3b, 0x12754ccc, 0x782ef11c, |
||||
0x6a124237, 0xb79251e7, 0x06a1bbe6, 0x4bfb6350, 0x1a6b1018, 0x11caedfa, |
||||
0x3d25bdd8, 0xe2e1c3c9, 0x44421659, 0x0a121386, 0xd90cec6e, 0xd5abea2a, |
||||
0x64af674e, 0xda86a85f, 0xbebfe988, 0x64e4c3fe, 0x9dbc8057, 0xf0f7c086, |
||||
0x60787bf8, 0x6003604d, 0xd1fd8346, 0xf6381fb0, 0x7745ae04, 0xd736fccc, |
||||
0x83426b33, 0xf01eab71, 0xb0804187, 0x3c005e5f, 0x77a057be, 0xbde8ae24, |
||||
0x55464299, 0xbf582e61, 0x4e58f48f, 0xf2ddfda2, 0xf474ef38, 0x8789bdc2, |
||||
0x5366f9c3, 0xc8b38e74, 0xb475f255, 0x46fcd9b9, 0x7aeb2661, 0x8b1ddf84, |
||||
0x846a0e79, 0x915f95e2, 0x466e598e, 0x20b45770, 0x8cd55591, 0xc902de4c, |
||||
0xb90bace1, 0xbb8205d0, 0x11a86248, 0x7574a99e, 0xb77f19b6, 0xe0a9dc09, |
||||
0x662d09a1, 0xc4324633, 0xe85a1f02, 0x09f0be8c, 0x4a99a025, 0x1d6efe10, |
||||
0x1ab93d1d, 0x0ba5a4df, 0xa186f20f, 0x2868f169, 0xdcb7da83, 0x573906fe, |
||||
0xa1e2ce9b, 0x4fcd7f52, 0x50115e01, 0xa70683fa, 0xa002b5c4, 0x0de6d027, |
||||
0x9af88c27, 0x773f8641, 0xc3604c06, 0x61a806b5, 0xf0177a28, 0xc0f586e0, |
||||
0x006058aa, 0x30dc7d62, 0x11e69ed7, 0x2338ea63, 0x53c2dd94, 0xc2c21634, |
||||
0xbbcbee56, 0x90bcb6de, 0xebfc7da1, 0xce591d76, 0x6f05e409, 0x4b7c0188, |
||||
0x39720a3d, 0x7c927c24, 0x86e3725f, 0x724d9db9, 0x1ac15bb4, 0xd39eb8fc, |
||||
0xed545578, 0x08fca5b5, 0xd83d7cd3, 0x4dad0fc4, 0x1e50ef5e, 0xb161e6f8, |
||||
0xa28514d9, 0x6c51133c, 0x6fd5c7e7, 0x56e14ec4, 0x362abfce, 0xddc6c837, |
||||
0xd79a3234, 0x92638212, 0x670efa8e, 0x406000e0, |
||||
} |
||||
|
||||
var s3 = [256]uint32{ |
||||
0x3a39ce37, 0xd3faf5cf, 0xabc27737, 0x5ac52d1b, 0x5cb0679e, 0x4fa33742, |
||||
0xd3822740, 0x99bc9bbe, 0xd5118e9d, 0xbf0f7315, 0xd62d1c7e, 0xc700c47b, |
||||
0xb78c1b6b, 0x21a19045, 0xb26eb1be, 0x6a366eb4, 0x5748ab2f, 0xbc946e79, |
||||
0xc6a376d2, 0x6549c2c8, 0x530ff8ee, 0x468dde7d, 0xd5730a1d, 0x4cd04dc6, |
||||
0x2939bbdb, 0xa9ba4650, 0xac9526e8, 0xbe5ee304, 0xa1fad5f0, 0x6a2d519a, |
||||
0x63ef8ce2, 0x9a86ee22, 0xc089c2b8, 0x43242ef6, 0xa51e03aa, 0x9cf2d0a4, |
||||
0x83c061ba, 0x9be96a4d, 0x8fe51550, 0xba645bd6, 0x2826a2f9, 0xa73a3ae1, |
||||
0x4ba99586, 0xef5562e9, 0xc72fefd3, 0xf752f7da, 0x3f046f69, 0x77fa0a59, |
||||
0x80e4a915, 0x87b08601, 0x9b09e6ad, 0x3b3ee593, 0xe990fd5a, 0x9e34d797, |
||||
0x2cf0b7d9, 0x022b8b51, 0x96d5ac3a, 0x017da67d, 0xd1cf3ed6, 0x7c7d2d28, |
||||
0x1f9f25cf, 0xadf2b89b, 0x5ad6b472, 0x5a88f54c, 0xe029ac71, 0xe019a5e6, |
||||
0x47b0acfd, 0xed93fa9b, 0xe8d3c48d, 0x283b57cc, 0xf8d56629, 0x79132e28, |
||||
0x785f0191, 0xed756055, 0xf7960e44, 0xe3d35e8c, 0x15056dd4, 0x88f46dba, |
||||
0x03a16125, 0x0564f0bd, 0xc3eb9e15, 0x3c9057a2, 0x97271aec, 0xa93a072a, |
||||
0x1b3f6d9b, 0x1e6321f5, 0xf59c66fb, 0x26dcf319, 0x7533d928, 0xb155fdf5, |
||||
0x03563482, 0x8aba3cbb, 0x28517711, 0xc20ad9f8, 0xabcc5167, 0xccad925f, |
||||
0x4de81751, 0x3830dc8e, 0x379d5862, 0x9320f991, 0xea7a90c2, 0xfb3e7bce, |
||||
0x5121ce64, 0x774fbe32, 0xa8b6e37e, 0xc3293d46, 0x48de5369, 0x6413e680, |
||||
0xa2ae0810, 0xdd6db224, 0x69852dfd, 0x09072166, 0xb39a460a, 0x6445c0dd, |
||||
0x586cdecf, 0x1c20c8ae, 0x5bbef7dd, 0x1b588d40, 0xccd2017f, 0x6bb4e3bb, |
||||
0xdda26a7e, 0x3a59ff45, 0x3e350a44, 0xbcb4cdd5, 0x72eacea8, 0xfa6484bb, |
||||
0x8d6612ae, 0xbf3c6f47, 0xd29be463, 0x542f5d9e, 0xaec2771b, 0xf64e6370, |
||||
0x740e0d8d, 0xe75b1357, 0xf8721671, 0xaf537d5d, 0x4040cb08, 0x4eb4e2cc, |
||||
0x34d2466a, 0x0115af84, 0xe1b00428, 0x95983a1d, 0x06b89fb4, 0xce6ea048, |
||||
0x6f3f3b82, 0x3520ab82, 0x011a1d4b, 0x277227f8, 0x611560b1, 0xe7933fdc, |
||||
0xbb3a792b, 0x344525bd, 0xa08839e1, 0x51ce794b, 0x2f32c9b7, 0xa01fbac9, |
||||
0xe01cc87e, 0xbcc7d1f6, 0xcf0111c3, 0xa1e8aac7, 0x1a908749, 0xd44fbd9a, |
||||
0xd0dadecb, 0xd50ada38, 0x0339c32a, 0xc6913667, 0x8df9317c, 0xe0b12b4f, |
||||
0xf79e59b7, 0x43f5bb3a, 0xf2d519ff, 0x27d9459c, 0xbf97222c, 0x15e6fc2a, |
||||
0x0f91fc71, 0x9b941525, 0xfae59361, 0xceb69ceb, 0xc2a86459, 0x12baa8d1, |
||||
0xb6c1075e, 0xe3056a0c, 0x10d25065, 0xcb03a442, 0xe0ec6e0e, 0x1698db3b, |
||||
0x4c98a0be, 0x3278e964, 0x9f1f9532, 0xe0d392df, 0xd3a0342b, 0x8971f21e, |
||||
0x1b0a7441, 0x4ba3348c, 0xc5be7120, 0xc37632d8, 0xdf359f8d, 0x9b992f2e, |
||||
0xe60b6f47, 0x0fe3f11d, 0xe54cda54, 0x1edad891, 0xce6279cf, 0xcd3e7e6f, |
||||
0x1618b166, 0xfd2c1d05, 0x848fd2c5, 0xf6fb2299, 0xf523f357, 0xa6327623, |
||||
0x93a83531, 0x56cccd02, 0xacf08162, 0x5a75ebb5, 0x6e163697, 0x88d273cc, |
||||
0xde966292, 0x81b949d0, 0x4c50901b, 0x71c65614, 0xe6c6c7bd, 0x327a140a, |
||||
0x45e1d006, 0xc3f27b9a, 0xc9aa53fd, 0x62a80f00, 0xbb25bfe2, 0x35bdd2f6, |
||||
0x71126905, 0xb2040222, 0xb6cbcf7c, 0xcd769c2b, 0x53113ec0, 0x1640e3d3, |
||||
0x38abbd60, 0x2547adf0, 0xba38209c, 0xf746ce76, 0x77afa1c5, 0x20756060, |
||||
0x85cbfe4e, 0x8ae88dd8, 0x7aaaf9b0, 0x4cf9aa7e, 0x1948c25c, 0x02fb8a8c, |
||||
0x01c36ae4, 0xd6ebe1f9, 0x90d4f869, 0xa65cdea0, 0x3f09252d, 0xc208e69f, |
||||
0xb74e6132, 0xce77e25b, 0x578fdfe3, 0x3ac372e6, |
||||
} |
||||
|
||||
var p = [18]uint32{ |
||||
0x243f6a88, 0x85a308d3, 0x13198a2e, 0x03707344, 0xa4093822, 0x299f31d0, |
||||
0x082efa98, 0xec4e6c89, 0x452821e6, 0x38d01377, 0xbe5466cf, 0x34e90c6c, |
||||
0xc0ac29b7, 0xc97c50dd, 0x3f84d5b5, 0xb5470917, 0x9216d5d9, 0x8979fb1b, |
||||
} |
@ -0,0 +1,104 @@ |
||||
package main |
||||
|
||||
import ( |
||||
"encoding/json" |
||||
"fmt" |
||||
"net/http" |
||||
|
||||
"github.com/dgrijalva/jwt-go" |
||||
) |
||||
|
||||
func srvLogin(requestUser *User) (int, []byte) { |
||||
authBackend := InitJWT() |
||||
if authBackend.Authenticate(requestUser) { |
||||
token, err := authBackend.GenerateToken(requestUser.Username) |
||||
if err != nil { |
||||
return http.StatusInternalServerError, nil |
||||
} |
||||
response, err := json.Marshal(AuthToken{token}) |
||||
if err != nil { |
||||
return http.StatusInternalServerError, nil |
||||
} |
||||
return http.StatusOK, response |
||||
} |
||||
return http.StatusUnauthorized, nil |
||||
} |
||||
|
||||
func srvRefreshToken(req *http.Request) (int, []byte) { |
||||
authBackend := InitJWT() |
||||
tokenRequest, err := jwt.ParseFromRequest(req, func(token *jwt.Token) (interface{}, error) { |
||||
if _, ok := token.Method.(*jwt.SigningMethodRSA); !ok { |
||||
return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"]) |
||||
} |
||||
return authBackend.PublicKey, nil |
||||
}) |
||||
if err != nil { |
||||
return http.StatusInternalServerError, nil |
||||
} |
||||
if tokenRequest.Valid { |
||||
userName, ok := tokenRequest.Claims["sub"].(string) |
||||
if !ok { |
||||
return http.StatusUnauthorized, nil |
||||
} |
||||
token, err := authBackend.GenerateToken(userName) |
||||
if err != nil { |
||||
return http.StatusInternalServerError, nil |
||||
} |
||||
response, err := json.Marshal(AuthToken{token}) |
||||
if err != nil { |
||||
return http.StatusInternalServerError, nil |
||||
} |
||||
return http.StatusOK, response |
||||
} |
||||
return http.StatusUnauthorized, nil |
||||
} |
||||
|
||||
func srvLogout(req *http.Request) int { |
||||
authBackend := InitJWT() |
||||
tokenRequest, err := jwt.ParseFromRequest(req, func(token *jwt.Token) (interface{}, error) { |
||||
if _, ok := token.Method.(*jwt.SigningMethodRSA); !ok { |
||||
return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"]) |
||||
} |
||||
return authBackend.PublicKey, nil |
||||
}) |
||||
if err != nil { |
||||
return http.StatusInternalServerError |
||||
} |
||||
if tokenRequest.Valid { |
||||
tokenString := req.Header.Get("Authorization") |
||||
if err = authBackend.Logout(tokenString, tokenRequest); err != nil { |
||||
return http.StatusInternalServerError |
||||
} |
||||
return http.StatusOK |
||||
} |
||||
return http.StatusUnauthorized |
||||
} |
||||
|
||||
// LoginHandler - user login handler.
|
||||
func (web WebAPI) LoginHandler(w http.ResponseWriter, r *http.Request) { |
||||
requestUser := new(User) |
||||
decoder := json.NewDecoder(r.Body) |
||||
if err := decoder.Decode(&requestUser); err != nil { |
||||
w.WriteHeader(http.StatusInternalServerError) |
||||
return |
||||
} |
||||
responseStatus, token := srvLogin(requestUser) |
||||
w.Header().Set("Content-Type", "application/json") |
||||
w.WriteHeader(responseStatus) |
||||
w.Write(token) |
||||
} |
||||
|
||||
// RefreshTokenHandler - refresh token handler.
|
||||
func (web WebAPI) RefreshTokenHandler(w http.ResponseWriter, r *http.Request) { |
||||
responseStatus, token := srvRefreshToken(r) |
||||
w.Header().Set("Content-Type", "application/json") |
||||
w.WriteHeader(responseStatus) |
||||
w.Write(token) |
||||
} |
||||
|
||||
// LogoutHandler - user logout handler.
|
||||
func (web WebAPI) LogoutHandler(w http.ResponseWriter, r *http.Request) { |
||||
responseStatus := srvLogout(r) |
||||
w.Header().Set("Content-Type", "application/json") |
||||
w.WriteHeader(responseStatus) |
||||
} |
@ -0,0 +1,54 @@ |
||||
package main |
||||
|
||||
import ( |
||||
"os" |
||||
"path/filepath" |
||||
|
||||
"github.com/minio/minio-xl/pkg/probe" |
||||
"github.com/minio/minio/pkg/user" |
||||
) |
||||
|
||||
var customWebConfigDir = "" |
||||
|
||||
// getWebConfigDir get web config dir.
|
||||
func getWebConfigDir() (string, *probe.Error) { |
||||
if customWebConfigDir != "" { |
||||
return customWebConfigDir, nil |
||||
} |
||||
homeDir, e := user.HomeDir() |
||||
if e != nil { |
||||
return "", probe.NewError(e) |
||||
} |
||||
webConfigDir := filepath.Join(homeDir, ".minio", "web") |
||||
return webConfigDir, nil |
||||
} |
||||
|
||||
func mustGetWebConfigDir() string { |
||||
webConfigDir, err := getWebConfigDir() |
||||
fatalIf(err.Trace(), "Unable to get config path.", nil) |
||||
return webConfigDir |
||||
} |
||||
|
||||
// createWebConfigDir create users config path
|
||||
func createWebConfigDir() *probe.Error { |
||||
webConfigDir, err := getWebConfigDir() |
||||
if err != nil { |
||||
return err.Trace() |
||||
} |
||||
if err := os.MkdirAll(webConfigDir, 0700); err != nil { |
||||
return probe.NewError(err) |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
func mustGetPrivateKeyPath() string { |
||||
webConfigDir, err := getWebConfigDir() |
||||
fatalIf(err.Trace(), "Unable to get config path.", nil) |
||||
return webConfigDir + "/private.key" |
||||
} |
||||
|
||||
func mustGetPublicKeyPath() string { |
||||
webConfigDir, err := getWebConfigDir() |
||||
fatalIf(err.Trace(), "Unable to get config path.", nil) |
||||
return webConfigDir + "/public.key" |
||||
} |
@ -0,0 +1,12 @@ |
||||
package main |
||||
|
||||
// AuthToken - auth token
|
||||
type AuthToken struct { |
||||
Token string `json:"token" form:"token"` |
||||
} |
||||
|
||||
// User users.
|
||||
type User struct { |
||||
Username string `json:"username" form:"username"` |
||||
Password string `json:"password" form:"password"` |
||||
} |
Loading…
Reference in new issue