Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add more secure token formatters #11

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 40 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,28 @@ Authorization Server crypts the token using the Token Formatter and Authorizatio
This library contains a default implementation of the formatter interface called _SHA256RC4TokenSecureFormatter_ based on the algorithms SHA256 and RC4.
Programmers can develop their Token Formatter implementing the interface _TokenSecureFormatter_ and this is really recommended before publishing the API in a production environment.

There are additional Token Formatters using several algorithms
* _AES128GCMTokenSecureFormatter_
* _AES256GCMTokenSecureFormatter_
* _RSATokenSecureFormatter_
* _JWTHS256TokenSecureFormatter_

Benchmark
```
go test -bench=. -benchtime 5s
goos: linux
goarch: amd64
pkg: github.com/go-chi/oauth
cpu: Intel(R) Xeon(R) CPU E5-2630 v4 @ 2.20GHz
BenchmarkRC4-4 534931 12012 ns/op
BenchmarkSHA256-4 383150 13230 ns/op
BenchmarkAES128GCM-4 724194 7403 ns/op
BenchmarkAES256GCM-4 870612 7387 ns/op
BenchmarkRSA-4 1882 3093425 ns/op
BenchmarkJWTHS256-4 564812 10076 ns/op
PASS
ok github.com/go-chi/oauth 38.633s
```
## Credentials Verifier
The interface _CredentialsVerifier_ defines the hooks called during the token generation process.
The methods are called in this order:
Expand All @@ -56,17 +78,25 @@ func main() {
r.Use(middleware.Logger)
r.Use(middleware.Recoverer)

s := oauth.NewOAuthBearerServer(
"mySecretKey-10101",
time.Second*120,
&TestUserVerifier{},
nil)
s := oauth.NewBearerServer(
"mySecretKey-10101",
time.Second*120,
&TestUserVerifier{},
nil)

r.Post("/token", s.UserCredentials)
r.Post("/auth", s.ClientCredentials)
http.ListenAndServe(":8080", r)
}
```
If you want to use [AES256 GCM](https://en.wikipedia.org/wiki/Galois/Counter_Mode) cipher, initialize the server with the corresponding formatter
```Go
s := oauth.NewBearerServer(
"testkeytestkeytestkeytestkey1234", // needs to be exactly 32 byte for AES256
time.Second*120,
&TestUserVerifier{},
oauth.NewAES256GCMTokenSecurityProvider([]byte("testkeytestkeytestkeytestkey1234")))
```
See [/test/authserver/main.go](https://github.com/go-chi/oauth/blob/master/test/authserver/main.go) for the full example.

## Authorization Middleware usage example
Expand All @@ -80,6 +110,11 @@ This snippet shows how to use the middleware
r.Get("/customers/{id}/orders", GetOrders)
}
```
If you want to use [AES256 GCM](https://en.wikipedia.org/wiki/Galois/Counter_Mode) cipher, initialize the server with the corresponding formatter
```Go
// Key must be exactly 32 byte for AES256
r.Use(oauth.Authorize("testkeytestkeytestkeytestkey1234", oauth.NewAES256GCMTokenSecurityProvider([]byte("testkeytestkeytestkeytestkey1234"))))
```
See [/test/resourceserver/main.go](https://github.com/go-chi/oauth/blob/master/test/resourceserver/main.go) for the full example.

Note that the authorization server and the authorization middleware are both using the same token formatter and the same secret key for encryption/decryption.
Expand Down
196 changes: 196 additions & 0 deletions security.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,18 @@
package oauth

import (
"crypto/aes"
"crypto/cipher"
"crypto/hmac"
"crypto/rand"
"crypto/rc4"
"crypto/rsa"
"crypto/sha256"
"encoding/base64"
"encoding/json"
"errors"
"io"
"strings"
)

type TokenSecureFormatter interface {
Expand Down Expand Up @@ -151,3 +158,192 @@ func (sc *SHA256RC4TokenSecureFormatter) DecryptToken(source []byte) ([]byte, er
}
return dest[32:], nil
}

type AES128GCMTokenSecureFormatter struct {
key []byte
}

func NewAES128GCMTokenSecurityProvider(key []byte) *AES128GCMTokenSecureFormatter {
if len(key) != 16 {
panic("AES128 key must be exactly 16 bytes long")
}
var sc = &AES128GCMTokenSecureFormatter{key: key}
return sc
}

func (sc *AES128GCMTokenSecureFormatter) CryptToken(source []byte) ([]byte, error) {
hasher := sha256.New()
hasher.Write(source)
hash := hasher.Sum(nil)
newSource := append(hash, source...)

aes, err := aes.NewCipher(sc.key)
if err != nil {
return nil, err
}
gcm, err := cipher.NewGCM(aes)
if err != nil {
return nil, err
}
nonce := make([]byte, gcm.NonceSize())
if _, err = io.ReadFull(rand.Reader, nonce); err != nil {
return nil, err
}
return gcm.Seal(nonce, nonce, newSource, nil), nil
}

func (sc *AES128GCMTokenSecureFormatter) DecryptToken(source []byte) ([]byte, error) {
aes, err := aes.NewCipher(sc.key)
if err != nil {
panic(err)
}
gcm, err := cipher.NewGCM(aes)
if err != nil {
return nil, err
}
nonceSize := gcm.NonceSize()
nonce, ciphertext := source[:nonceSize], source[nonceSize:]
dest, err := gcm.Open(nil, nonce, ciphertext, nil)
if err != nil {
return nil, err
}
// hash check
hasher := sha256.New()
hasher.Write(dest[32:])
hash := hasher.Sum(nil)
for i, b := range hash {
if b != dest[i] {
return nil, errors.New("Invalid token")
}
}
return dest[32:], nil
}

type AES256GCMTokenSecureFormatter struct {
key []byte
}

func NewAES256GCMTokenSecurityProvider(key []byte) *AES256GCMTokenSecureFormatter {
if len(key) != 32 {
panic("AES256 key must be exactly 32 bytes long")
}
var sc = &AES256GCMTokenSecureFormatter{key: key}
return sc
}

func (sc *AES256GCMTokenSecureFormatter) CryptToken(source []byte) ([]byte, error) {
hasher := sha256.New()
hasher.Write(source)
hash := hasher.Sum(nil)
newSource := append(hash, source...)

aes, err := aes.NewCipher(sc.key)
if err != nil {
return nil, err
}
gcm, err := cipher.NewGCM(aes)
if err != nil {
return nil, err
}
nonce := make([]byte, gcm.NonceSize())
if _, err = io.ReadFull(rand.Reader, nonce); err != nil {
return nil, err
}
return gcm.Seal(nonce, nonce, newSource, nil), nil
}

func (sc *AES256GCMTokenSecureFormatter) DecryptToken(source []byte) ([]byte, error) {
aes, err := aes.NewCipher(sc.key)
if err != nil {
panic(err)
}
gcm, err := cipher.NewGCM(aes)
if err != nil {
return nil, err
}
nonceSize := gcm.NonceSize()
nonce, ciphertext := source[:nonceSize], source[nonceSize:]
dest, err := gcm.Open(nil, nonce, ciphertext, nil)
if err != nil {
return nil, err
}
// hash check
hasher := sha256.New()
hasher.Write(dest[32:])
hash := hasher.Sum(nil)
for i, b := range hash {
if b != dest[i] {
return nil, errors.New("Invalid token")
}
}
return dest[32:], nil
}

type RSATokenSecureFormatter struct {
key *rsa.PrivateKey
}

func NewRSATokenSecurityProvider(key *rsa.PrivateKey) *RSATokenSecureFormatter {
var sc = &RSATokenSecureFormatter{key: key}
return sc
}

func (sc *RSATokenSecureFormatter) CryptToken(source []byte) ([]byte, error) {
ciphertext, err := rsa.EncryptOAEP(sha256.New(), rand.Reader, &sc.key.PublicKey, source, nil)
if err != nil {
return nil, err
}
return ciphertext, nil
}

func (sc *RSATokenSecureFormatter) DecryptToken(source []byte) ([]byte, error) {
plaintext, err := rsa.DecryptOAEP(sha256.New(), rand.Reader, sc.key, source, nil)
if err != nil {
return nil, err
}
return plaintext, nil
}

type JWTHS256TokenSecureFormatter struct {
key []byte
}

func NewJWTHS256TokenSecurityProvider(key []byte) *JWTHS256TokenSecureFormatter {
var sc = &JWTHS256TokenSecureFormatter{key: key}
return sc
}

func (sc *JWTHS256TokenSecureFormatter) CryptToken(source []byte) ([]byte, error) {
header := []byte(`{"alg": "HS256", "typ": "JWT"}`)
b64header := base64.URLEncoding.EncodeToString(header)
b64payload := base64.URLEncoding.EncodeToString(source)
jwt := []byte(b64header + "." + b64payload)
hasher := hmac.New(sha256.New, sc.key)
hasher.Write(jwt)
jwt = append(jwt, '.')
jwt = append(jwt, hasher.Sum(nil)...)
return jwt, nil
}

func (sc *JWTHS256TokenSecureFormatter) DecryptToken(source []byte) ([]byte, error) {
jwt := string(source)
parts := strings.Split(jwt, ".")
if len(parts) != 3 {
return nil, errors.New("Invalid token")
}
b64header := parts[0]
b64payload := parts[1]
signature := []byte(parts[2])

hasher := hmac.New(sha256.New, sc.key)
hasher.Write([]byte(b64header + "." + b64payload))
// verify signature
if !hmac.Equal(signature, hasher.Sum(nil)) {
return nil, errors.New("Invalid token")
}
payload, err := base64.URLEncoding.DecodeString(b64payload)
if err != nil {
return nil, err
}
return payload, nil
}
Loading