mirror of
https://github.com/kataras/iris.git
synced 2025-03-14 08:26:26 +01:00
jwt: add more helpers (DefaultRSA and DefaultHMAC)
Former-commit-id: fe06c0e0f4d7e121c678ffda7ac702ae865abd00
This commit is contained in:
parent
cbe336a583
commit
b2fddc7d68
|
@ -14,19 +14,12 @@ type UserClaims struct {
|
|||
}
|
||||
|
||||
func main() {
|
||||
// hmac
|
||||
key := []byte("secret")
|
||||
j, err := jwt.New(1*time.Minute, jwt.HS256, key)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// OPTIONAL encryption:
|
||||
encryptionKey := []byte("itsa16bytesecret")
|
||||
err = j.WithEncryption(jwt.A128GCM, jwt.DIRECT, encryptionKey)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
// Get keys from system's environment variables
|
||||
// JWT_SECRET (for signing and verification) and JWT_SECRET_ENC(for encryption and decryption),
|
||||
// or defaults to "secret" and "itsa16bytesecret" respectfully.
|
||||
//
|
||||
// Use the `jwt.New` instead for more flexibility, if necessary.
|
||||
j := jwt.DefaultHMAC(15*time.Minute, "secret", "itsa16bytesecret")
|
||||
|
||||
app := iris.New()
|
||||
app.Logger().SetLevel("debug")
|
||||
|
@ -74,12 +67,62 @@ func main() {
|
|||
}
|
||||
|
||||
/*
|
||||
func load_From_File_Example() {
|
||||
func default_RSA_Example() {
|
||||
j := jwt.DefaultRSA(1 * time.Minute)
|
||||
}
|
||||
|
||||
Same as:
|
||||
|
||||
func load_File_Or_Generate_RSA_Example() {
|
||||
signKey, err := jwt.LoadRSA("jwt_sign.key", 2048)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
j, err := jwt.New(15*time.Minute, jwt.RS256, signKey)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
encKey, err := jwt.LoadRSA("jwt_enc.key", 2048)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
err = j.WithEncryption(jwt.A128CBCHS256, jwt.RSA15, encKey)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
/*
|
||||
func hmac_Example() {
|
||||
// hmac
|
||||
key := []byte("secret")
|
||||
j, err := jwt.New(15*time.Minute, jwt.HS256, key)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// OPTIONAL encryption:
|
||||
encryptionKey := []byte("itsa16bytesecret")
|
||||
err = j.WithEncryption(jwt.A128GCM, jwt.DIRECT, encryptionKey)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
/*
|
||||
func load_From_File_With_Password_Example() {
|
||||
b, err := ioutil.ReadFile("./private_rsa.pem")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
signKey := jwt.MustParseRSAPrivateKey(b, []byte("pass"))
|
||||
signKey,err := jwt.ParseRSAPrivateKey(b, []byte("pass"))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
j, err := jwt.New(15*time.Minute, jwt.RS256, signKey)
|
||||
if err != nil {
|
||||
|
@ -89,23 +132,18 @@ func load_From_File_Example() {
|
|||
*/
|
||||
|
||||
/*
|
||||
func random_RSA_Sign_And_Encrypt_Example() {
|
||||
j := jwt.Random(1 * time.Minute)
|
||||
}
|
||||
*/
|
||||
|
||||
/*
|
||||
func random_manually_generate_RSA_Example() {
|
||||
signey, err := rsa.GenerateKey(rand.Reader, 2048)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
encryptionKey, err := rsa.GenerateKey(rand.Reader, 2048)
|
||||
func generate_RSA_Example() {
|
||||
signKey, err := rsa.GenerateKey(rand.Reader, 4096)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
j, err := jwt.New(1*time.Minute, jwt.RS256, signey)
|
||||
encryptionKey, err := rsa.GenerateKey(rand.Reader, 4096)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
j, err := jwt.New(15*time.Minute, jwt.RS512, signKey)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
|
|
@ -1946,7 +1946,7 @@ func (ctx *context) GetDomain() string {
|
|||
if host, _, err := net.SplitHostPort(hostport); err == nil {
|
||||
// has port.
|
||||
switch host {
|
||||
case "127.0.0.1", "0.0.0.0", "::1", "[::1]", "0:0:0:0:0:0:0", "0:0:0:0:0:0:1":
|
||||
case "127.0.0.1", "0.0.0.0", "::1", "[::1]", "0:0:0:0:0:0:0:0", "0:0:0:0:0:0:0:1":
|
||||
// loopback.
|
||||
return "localhost"
|
||||
default:
|
||||
|
|
|
@ -2,10 +2,9 @@ package jwt
|
|||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
|
@ -112,35 +111,6 @@ type JWT struct {
|
|||
DecriptionKey interface{}
|
||||
}
|
||||
|
||||
// Random returns a new `JWT` instance
|
||||
// with in-memory generated rsa256 signing and encryption keys (development).
|
||||
// It panics on errors. Next server ran will invalidate all request tokens.
|
||||
//
|
||||
// Use the `New` package-level function for production use.
|
||||
func Random(maxAge time.Duration) *JWT {
|
||||
sigKey, err := rsa.GenerateKey(rand.Reader, 2048)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
j, err := New(maxAge, RS256, sigKey)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
encKey, err := rsa.GenerateKey(rand.Reader, 2048)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err = j.WithEncryption(A128CBCHS256, RSA15, encKey)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return j
|
||||
}
|
||||
|
||||
type privateKey interface{ Public() crypto.PublicKey }
|
||||
|
||||
// New returns a new JWT instance.
|
||||
|
@ -157,7 +127,7 @@ type privateKey interface{ Public() crypto.PublicKey }
|
|||
// 1. Generate key file, e.g:
|
||||
// $ openssl genrsa -des3 -out private.pem 2048
|
||||
// 2. Read file contents with io.ReadFile("./private.pem")
|
||||
// 3. Pass the []byte result to the `MustParseRSAPrivateKey(contents, password)` package-level helper
|
||||
// 3. Pass the []byte result to the `ParseRSAPrivateKey(contents, password)` package-level helper
|
||||
// 4. Use the result *rsa.PrivateKey as "key" input parameter of this `New` function.
|
||||
//
|
||||
// See aliases.go file for available algorithms.
|
||||
|
@ -185,6 +155,99 @@ func New(maxAge time.Duration, alg SignatureAlgorithm, key interface{}) (*JWT, e
|
|||
return j, nil
|
||||
}
|
||||
|
||||
// RSA filenames for `DefaultRSA`.
|
||||
const (
|
||||
SignFilename = "jwt_sign.key"
|
||||
EncFilename = "jwt_enc.key"
|
||||
)
|
||||
|
||||
// DefaultRSA returns a new `JWT` instance.
|
||||
// It tries to parse RSA256 keys from "jwt_sign.key" and (optionally) "jwt_enc.key" files
|
||||
// in the current working directory, and if not found, it generates and exports the keys.
|
||||
//
|
||||
// It panics on errors.
|
||||
// Use the `New` package-level function instead for more options.
|
||||
func DefaultRSA(maxAge time.Duration) *JWT {
|
||||
// Do not try to create or load enc key if only sign key already exists.
|
||||
withEncryption := true
|
||||
if fileExists(SignFilename) {
|
||||
withEncryption = fileExists(EncFilename)
|
||||
}
|
||||
|
||||
sigKey, err := LoadRSA(SignFilename, 2048)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
j, err := New(maxAge, RS256, sigKey)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if withEncryption {
|
||||
encKey, err := LoadRSA(EncFilename, 2048)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
err = j.WithEncryption(A128CBCHS256, RSA15, encKey)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
return j
|
||||
}
|
||||
|
||||
const (
|
||||
signEnv = "JWT_SECRET"
|
||||
encEnv = "JWT_SECRET_ENC"
|
||||
)
|
||||
|
||||
func getenv(key string, def string) string {
|
||||
v := os.Getenv(key)
|
||||
if v == "" {
|
||||
return def
|
||||
}
|
||||
|
||||
return v
|
||||
}
|
||||
|
||||
// DefaultHMAC returns a new `JWT` instance.
|
||||
// It tries to read hmac256 secret keys from system environment variables:
|
||||
// * JWT_SECRET for signing and verification key and
|
||||
// * JWT_SECRET_ENC for encryption and decryption key
|
||||
// and defaults them to the given "keys" respectfully.
|
||||
//
|
||||
// It panics on errors.
|
||||
// Use the `New` package-level function instead for more options.
|
||||
func DefaultHMAC(maxAge time.Duration, keys ...string) *JWT {
|
||||
var defaultSignSecret, defaultEncSecret string
|
||||
|
||||
switch len(keys) {
|
||||
case 1:
|
||||
defaultSignSecret = keys[0]
|
||||
case 2:
|
||||
defaultEncSecret = keys[1]
|
||||
}
|
||||
|
||||
signSecret := getenv(signEnv, defaultSignSecret)
|
||||
encSecret := getenv(encEnv, defaultEncSecret)
|
||||
|
||||
j, err := New(maxAge, HS256, []byte(signSecret))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if encSecret != "" {
|
||||
err = j.WithEncryption(A128GCM, DIRECT, []byte(encSecret))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
return j
|
||||
}
|
||||
|
||||
// WithEncryption method enables encryption and decryption of the token.
|
||||
// It sets an appropriate encrypter(`Encrypter` and the `DecriptionKey` fields) based on the key type.
|
||||
func (j *JWT) WithEncryption(contentEncryption ContentEncryption, alg KeyAlgorithm, key interface{}) error {
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
package jwt_test
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
|
@ -18,12 +19,21 @@ type userClaims struct {
|
|||
const testMaxAge = 3 * time.Second
|
||||
|
||||
// Random RSA verification and encryption.
|
||||
func TestRSA(t *testing.T) {
|
||||
j := jwt.Random(testMaxAge)
|
||||
func TestDefaultRSA(t *testing.T) {
|
||||
j := jwt.DefaultRSA(testMaxAge)
|
||||
t.Cleanup(func() {
|
||||
os.Remove(jwt.SignFilename)
|
||||
os.Remove(jwt.EncFilename)
|
||||
})
|
||||
testWriteVerifyToken(t, j)
|
||||
}
|
||||
|
||||
// HMAC verification and encryption.
|
||||
func TestDefaultHMAC(t *testing.T) {
|
||||
j := jwt.DefaultHMAC(testMaxAge, "secret", "itsa16bytesecret")
|
||||
testWriteVerifyToken(t, j)
|
||||
}
|
||||
|
||||
func TestHMAC(t *testing.T) {
|
||||
j, err := jwt.New(testMaxAge, jwt.HS256, []byte("secret"))
|
||||
if err != nil {
|
||||
|
|
106
middleware/jwt/rsa_util.go
Normal file
106
middleware/jwt/rsa_util.go
Normal file
|
@ -0,0 +1,106 @@
|
|||
package jwt
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
)
|
||||
|
||||
// LoadRSA tries to read RSA Private Key from "fname" system file,
|
||||
// if does not exist then it generates a new random one based on "bits" (e.g. 2048, 4096)
|
||||
// and exports it to a new "fname" file.
|
||||
func LoadRSA(fname string, bits int) (key *rsa.PrivateKey, err error) {
|
||||
exists := fileExists(fname)
|
||||
if exists {
|
||||
key, err = importFromFile(fname)
|
||||
} else {
|
||||
key, err = rsa.GenerateKey(rand.Reader, bits)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if !exists {
|
||||
err = exportToFile(key, fname)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func exportToFile(key *rsa.PrivateKey, filename string) error {
|
||||
b := x509.MarshalPKCS1PrivateKey(key)
|
||||
encoded := pem.EncodeToMemory(
|
||||
&pem.Block{
|
||||
Type: "RSA PRIVATE KEY",
|
||||
Bytes: b,
|
||||
},
|
||||
)
|
||||
|
||||
return ioutil.WriteFile(filename, encoded, 0644)
|
||||
}
|
||||
|
||||
func importFromFile(filename string) (*rsa.PrivateKey, error) {
|
||||
b, err := ioutil.ReadFile(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return ParseRSAPrivateKey(b, nil)
|
||||
}
|
||||
|
||||
func fileExists(filename string) bool {
|
||||
info, err := os.Stat(filename)
|
||||
if os.IsNotExist(err) {
|
||||
return false
|
||||
}
|
||||
return !info.IsDir()
|
||||
}
|
||||
|
||||
var (
|
||||
// ErrNotPEM is an error type of the `ParseXXX` function(s) fired
|
||||
// when the data are not PEM-encoded.
|
||||
ErrNotPEM = errors.New("key must be PEM encoded")
|
||||
// ErrInvalidKey is an error type of the `ParseXXX` function(s) fired
|
||||
// when the contents are not type of rsa private key.
|
||||
ErrInvalidKey = errors.New("key is not of type *rsa.PrivateKey")
|
||||
)
|
||||
|
||||
// ParseRSAPrivateKey encodes a PEM-encoded PKCS1 or PKCS8 private key protected with a password.
|
||||
func ParseRSAPrivateKey(key, password []byte) (*rsa.PrivateKey, error) {
|
||||
block, _ := pem.Decode(key)
|
||||
if block == nil {
|
||||
return nil, ErrNotPEM
|
||||
}
|
||||
|
||||
var (
|
||||
parsedKey interface{}
|
||||
err error
|
||||
)
|
||||
|
||||
var blockDecrypted []byte
|
||||
if len(password) > 0 {
|
||||
if blockDecrypted, err = x509.DecryptPEMBlock(block, password); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
blockDecrypted = block.Bytes
|
||||
}
|
||||
|
||||
if parsedKey, err = x509.ParsePKCS1PrivateKey(blockDecrypted); err != nil {
|
||||
if parsedKey, err = x509.ParsePKCS8PrivateKey(blockDecrypted); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
privateKey, ok := parsedKey.(*rsa.PrivateKey)
|
||||
if !ok {
|
||||
return nil, ErrInvalidKey
|
||||
}
|
||||
|
||||
return privateKey, nil
|
||||
}
|
|
@ -1,98 +0,0 @@
|
|||
package jwt
|
||||
|
||||
import (
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
)
|
||||
|
||||
// ErrNotPEM is a panic error of the MustParseXXX functions when the data are not PEM-encoded.
|
||||
var ErrNotPEM = errors.New("key must be PEM encoded")
|
||||
|
||||
// MustParseRSAPrivateKey encodes a PEM-encoded PKCS1 or PKCS8 private key protected with a password.
|
||||
func MustParseRSAPrivateKey(key, password []byte) *rsa.PrivateKey {
|
||||
block, _ := pem.Decode(key)
|
||||
if block == nil {
|
||||
panic(ErrNotPEM)
|
||||
}
|
||||
|
||||
var (
|
||||
parsedKey interface{}
|
||||
err error
|
||||
)
|
||||
|
||||
var blockDecrypted []byte
|
||||
if blockDecrypted, err = x509.DecryptPEMBlock(block, password); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if parsedKey, err = x509.ParsePKCS1PrivateKey(blockDecrypted); err != nil {
|
||||
if parsedKey, err = x509.ParsePKCS8PrivateKey(blockDecrypted); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
privateKey, ok := parsedKey.(*rsa.PrivateKey)
|
||||
if !ok {
|
||||
panic("key is not of type *rsa.PrivateKey")
|
||||
}
|
||||
|
||||
return privateKey
|
||||
}
|
||||
|
||||
// MustParseRSAPublicKey encodes a PEM encoded PKCS1 or PKCS8 public key.
|
||||
func MustParseRSAPublicKey(key []byte) *rsa.PublicKey {
|
||||
var err error
|
||||
|
||||
// Parse PEM block
|
||||
var block *pem.Block
|
||||
if block, _ = pem.Decode(key); block == nil {
|
||||
panic(ErrNotPEM)
|
||||
}
|
||||
|
||||
// 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 {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
var pkey *rsa.PublicKey
|
||||
var ok bool
|
||||
if pkey, ok = parsedKey.(*rsa.PublicKey); !ok {
|
||||
panic("key is not of type *rsa.PublicKey")
|
||||
}
|
||||
|
||||
return pkey
|
||||
}
|
||||
|
||||
/*
|
||||
// MustParseEd25519 PEM encoded Ed25519.
|
||||
func MustParseEd25519(key []byte) ed25519.PrivateKey {
|
||||
// Parse PEM block
|
||||
block, _ := pem.Decode(key)
|
||||
if block == nil {
|
||||
panic(ErrNotPEM)
|
||||
}
|
||||
|
||||
type ed25519PrivKey struct {
|
||||
Version int
|
||||
ObjectIdentifier struct {
|
||||
ObjectIdentifier asn1.ObjectIdentifier
|
||||
}
|
||||
PrivateKey []byte
|
||||
}
|
||||
|
||||
var asn1PrivKey ed25519PrivKey
|
||||
if _, err := asn1.Unmarshal(block.Bytes, &asn1PrivKey); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
privateKey := ed25519.NewKeyFromSeed(asn1PrivKey.PrivateKey[2:])
|
||||
return privateKey
|
||||
}
|
||||
*/
|
Loading…
Reference in New Issue
Block a user