Skip to content

Commit

Permalink
refactor public key, remove it and use generated version. Switch priv…
Browse files Browse the repository at this point in the history
…ate key format ot pkcs7 PEM files
  • Loading branch information
erudenko committed Sep 10, 2021
1 parent ebe6ea2 commit d1a92bb
Show file tree
Hide file tree
Showing 17 changed files with 315 additions and 372 deletions.
11 changes: 2 additions & 9 deletions config/configurator.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
package config

import (
"fmt"

"github.com/madappgang/identifo/model"
"github.com/madappgang/identifo/server"
"github.com/madappgang/identifo/services/mail"
Expand Down Expand Up @@ -113,18 +111,13 @@ func NewServer(config model.ConfigurationStorage, restartChan chan<- bool) (mode
}

func NewTokenService(settings model.GeneralServerSettings, storages model.ServerStorageCollection) (model.TokenService, error) {
tokenServiceAlg, ok := model.StrToTokenSignAlg[settings.Algorithm]
if !ok {
return nil, fmt.Errorf("Unknown token service algorithm %s", settings.Algorithm)
}

keys, err := storages.Key.LoadKeys(tokenServiceAlg)
key, err := storages.Key.LoadPrivateKey()
if err != nil {
return nil, err
}

tokenService, err := jwt.NewJWTokenService(
keys,
key,
settings.Issuer,
storages.Token,
storages.App,
Expand Down
105 changes: 105 additions & 0 deletions jwt/eckey_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
package jwt_test

import (
"crypto/x509"
"encoding/base64"
"fmt"
"testing"

jwt "github.com/golang-jwt/jwt/v4"
jwti "github.com/madappgang/identifo/jwt"
)

// SEC 1, ASN.1, DER format
// privateKeyPEM := []byte(`-----BEGIN EC PRIVATE KEY-----
// MHcCAQEEIKcw4Osfw4a5G11fWprAjxrPLSAhKv5H5Gj27NBXsGDKoAoGCCqGSM49
// AwEHoUQDQgAED3DoOWZMbqYc0OO1Ih628hB2Odhv4mjl1vt0iBu3gTKz1XAk+YHG
// 8aoI+42TJle6hawmTzrSD6khaNRaDQAKbg==
// -----END EC PRIVATE KEY-----
// `)

var privateKeyPEM = []byte(`-----BEGIN PRIVATE KEY-----
MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgpzDg6x/DhrkbXV9a
msCPGs8tICEq/kfkaPbs0FewYMqhRANCAAQPcOg5ZkxuphzQ47UiHrbyEHY52G/i
aOXW+3SIG7eBMrPVcCT5gcbxqgj7jZMmV7qFrCZPOtIPqSFo1FoNAApu
-----END PRIVATE KEY-----
`)

var publicKeyPEM = []byte(`-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAED3DoOWZMbqYc0OO1Ih628hB2Odhv
4mjl1vt0iBu3gTKz1XAk+YHG8aoI+42TJle6hawmTzrSD6khaNRaDQAKbg==
-----END PUBLIC KEY-----
`)

func TestKeysManualSerializationService(t *testing.T) {
private, err := jwt.ParseECPrivateKeyFromPEM(privateKeyPEM)
if err != nil {
fmt.Println(string(privateKeyPEM))
t.Fatalf("Error parsing key from PEM: %v", err)
}

public, err := jwt.ParseECPublicKeyFromPEM(publicKeyPEM)
if err != nil {
t.Fatalf("Error parsing key from PEM: %v", err)
}

generatedPublic := private.Public()

if !public.Equal(generatedPublic) {
t.Fatalf("Generated public key and reference keys are not equal")
}

genPEM, err := x509.MarshalPKIXPublicKey(generatedPublic)
if err != nil {
t.Fatalf("Error creating PEM: %v", err)
}
// creating the same certificate as ssl tool is doing with the following command:
// openssl ec -in private.pem -pubout -out public.pem
pemString := fmt.Sprintf("-----BEGIN PUBLIC KEY-----\n%s\n%s\n-----END PUBLIC KEY-----\n",
base64.StdEncoding.EncodeToString(genPEM)[0:64],
base64.StdEncoding.EncodeToString(genPEM)[64:],
)
if pemString != string(publicKeyPEM) {
fmt.Printf("%s\n", pemString)
t.Fatalf("generated public key PEM and referenced are not equal.")
}

genPEMPrivate, err := x509.MarshalPKCS8PrivateKey(private)
// genPEMPrivate, err := x509.MarshalECPrivateKey(private)
if err != nil {
t.Fatalf("Error creating private PEM: %v", err)
}
pemStringPrivate := fmt.Sprintf("-----BEGIN PRIVATE KEY-----\n%s\n%s\n%s\n-----END PRIVATE KEY-----\n",
base64.StdEncoding.EncodeToString(genPEMPrivate)[0:64],
base64.StdEncoding.EncodeToString(genPEMPrivate)[64:128],
base64.StdEncoding.EncodeToString(genPEMPrivate)[128:],
)

if pemStringPrivate != string(privateKeyPEM) {
fmt.Printf("%s\n", pemStringPrivate)
t.Fatalf("generated private key PEM and referenced are not equal.")
}
}

func TestPemMarshalling(t *testing.T) {
private, err := jwt.ParseECPrivateKeyFromPEM(privateKeyPEM)
// private, alg, err := jwti.LoadPrivateKeyFromString(string(privateKeyPEM))
if err != nil {
fmt.Println(string(privateKeyPEM))
t.Fatalf("Error parsing key from PEM: %v", err)
}

// if alg != model.TokenSignatureAlgorithmES256 {
// t.Fatalf("wrong algorithm in PEM: %v", alg)
// }

result, err := jwti.MarshalPrivateKeyToPEM(private)
if err != nil {
t.Fatalf("Error marshaling key to PEM: %v", err)
}

if result != string(privateKeyPEM) {
fmt.Printf("%s\n", result)
t.Fatalf("generated private key PEM and referenced are not equal.")
}
}
7 changes: 6 additions & 1 deletion jwt/generate_token.sh
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,13 @@ cd "$(dirname "$0")"

#we are using secp256k1 curve
#Generate an EC private key, of size 256, and output it to a file named private.pem
openssl ecparam -name prime256v1 -genkey -noout -out private.pem
openssl ecparam -name prime256v1 -genkey -noout -out private_ec.pem

#Generate pkcs8 instead of SEC 1
openssl pkcs8 -topk8 -nocrypt -inform PEM -outform PEM -in private_ec.pem -out private.pem

#remove
rm private_ec.pem

#Extract the public key from the key pair, which can be used in a certificate:
openssl ec -in private.pem -pubout -out public.pem
Expand Down
144 changes: 69 additions & 75 deletions jwt/pem.go
Original file line number Diff line number Diff line change
@@ -1,114 +1,108 @@
package jwt

import (
"crypto/ecdsa"
"crypto/rsa"
"crypto/x509"
"encoding/base64"
"fmt"
"io/ioutil"

jwt "github.com/golang-jwt/jwt/v4"
"github.com/madappgang/identifo/model"
)

var supportedSignatureAlgorithms = []model.TokenSignatureAlgorithm{model.TokenSignatureAlgorithmES256, model.TokenSignatureAlgorithmRS256}

// LoadPrivateKeyFromPEM loads private key from PEM file.
func LoadPrivateKeyFromPEM(file string, alg model.TokenSignatureAlgorithm) (interface{}, error) {
prkb, err := ioutil.ReadFile(file)
func LoadPrivateKeyFromString(s string) (interface{}, model.TokenSignatureAlgorithm, error) {
pp, err := x509.ParsePKCS8PrivateKey([]byte(s))
if err != nil {
return nil, err
return nil, model.TokenSignatureAlgorithmInvalid, err
}

var privateKey interface{}
switch alg {
case model.TokenSignatureAlgorithmES256:
privateKey, err = jwt.ParseECPrivateKeyFromPEM(prkb)
case model.TokenSignatureAlgorithmRS256:
privateKey, err = jwt.ParseRSAPrivateKeyFromPEM(prkb)
switch private := pp.(type) {
case *rsa.PrivateKey:
return private, model.TokenSignatureAlgorithmRS256, nil
case *ecdsa.PrivateKey:
return private, model.TokenSignatureAlgorithmES256, nil
default:
return nil, model.ErrWrongSignatureAlgorithm
return nil, model.TokenSignatureAlgorithmInvalid, fmt.Errorf("could not load unsupported key type: %T\n", private)
}
}

// LoadPrivateKeyFromPEM loads private key from PEM file.
func LoadPrivateKeyFromPEM(file string) (interface{}, model.TokenSignatureAlgorithm, error) {
prkb, err := ioutil.ReadFile(file)
if err != nil {
return nil, err
return nil, model.TokenSignatureAlgorithmInvalid, err
}
return privateKey, nil
return LoadPrivateKeyFromString(string(prkb))
}

// LoadPublicKeyFromPEM loads public key from PEM file.
func LoadPublicKeyFromPEM(file string, alg model.TokenSignatureAlgorithm) (interface{}, error) {
if alg == model.TokenSignatureAlgorithmAuto {
k, _, e := LoadPublicKeyFromPEMAuto(file)
return k, e
}

pkb, err := ioutil.ReadFile(file)
// LoadPublicKeyFromString loads public key from string.
func LoadPublicKeyFromString(s string) (interface{}, model.TokenSignatureAlgorithm, error) {
pub, err := x509.ParsePKIXPublicKey([]byte(s))
if err != nil {
return nil, err
return nil, model.TokenSignatureAlgorithmInvalid, err
}

var publicKey interface{}
switch alg {
case model.TokenSignatureAlgorithmES256:
publicKey, err = jwt.ParseECPublicKeyFromPEM(pkb)
case model.TokenSignatureAlgorithmRS256:
publicKey, err = jwt.ParseRSAPublicKeyFromPEM(pkb)
switch pub := pub.(type) {
case *rsa.PublicKey:
return pub, model.TokenSignatureAlgorithmRS256, nil
case *ecdsa.PublicKey:
return pub, model.TokenSignatureAlgorithmES256, nil
default:
return nil, model.ErrWrongSignatureAlgorithm
return nil, model.TokenSignatureAlgorithmInvalid, fmt.Errorf("could not load unsupported key type: %T\n", pub)
}
}

// LoadPublicKeyFromPEM loads public key from file
func LoadPublicKeyFromPEM(file string) (interface{}, model.TokenSignatureAlgorithm, error) {
prkb, err := ioutil.ReadFile(file)
if err != nil {
return nil, err
return nil, model.TokenSignatureAlgorithmInvalid, err
}
return publicKey, nil

return LoadPublicKeyFromString(string(prkb))
}

// LoadPublicKeyFromPEMAuto loads keys from pem file with key algorithm auto detection
func LoadPublicKeyFromPEMAuto(file string) (interface{}, model.TokenSignatureAlgorithm, error) {
var err error
var key interface{}
alg := model.TokenSignatureAlgorithmAuto
for _, a := range supportedSignatureAlgorithms {
if key, err = LoadPublicKeyFromPEM(file, a); err == nil {
alg = a
break
}
func MarshalPrivateKeyToPEM(key interface{}) (string, error) {
pk, err := x509.MarshalPKCS8PrivateKey(key)
if err != nil {
return "", fmt.Errorf("error creating PEM: %v", err)
}
return key, alg, err
b64 := []byte(base64.RawStdEncoding.EncodeToString(pk))
return fmt.Sprintf("-----BEGIN PRIVATE KEY-----\n%s-----END PRIVATE KEY-----\n", make64ColsString(b64)), nil
}

// LoadPublicKeyFromString loads public key from string.
func LoadPublicKeyFromString(s string, alg model.TokenSignatureAlgorithm) (interface{}, error) {
if alg == model.TokenSignatureAlgorithmAuto {
k, _, e := LoadPublicKeyFromStringAuto(s)
return k, e
func MarshalPublicKeyToPEM(key interface{}) (string, error) {
pk, err := x509.MarshalPKIXPublicKey(key)
if err != nil {
return "", fmt.Errorf("error creating PEM: %v", err)
}
b64 := []byte(base64.RawStdEncoding.EncodeToString(pk))
return fmt.Sprintf("-----BEGIN PUBLIC KEY-----\n%s-----END PUBLIC KEY-----\n", make64ColsString(b64)), nil
}

var publicKey interface{}
var err error

switch alg {
case model.TokenSignatureAlgorithmES256:
publicKey, err = jwt.ParseECPublicKeyFromPEM([]byte(s))
case model.TokenSignatureAlgorithmRS256:
publicKey, err = jwt.ParseRSAPublicKeyFromPEM([]byte(s))
default:
return nil, model.ErrWrongSignatureAlgorithm
}
func make64ColsString(slice []byte) string {
chunks := chunkSlice(slice, 64)

if err != nil {
return nil, err
result := ""
for _, line := range chunks {
result = result + string(line) + "\n"
}
return publicKey, nil
return result
}

// LoadPublicKeyFromStringAuto loads keys from string with key algorithm auto detection
func LoadPublicKeyFromStringAuto(s string) (interface{}, model.TokenSignatureAlgorithm, error) {
var err error
var key interface{}
alg := model.TokenSignatureAlgorithmAuto
for _, a := range supportedSignatureAlgorithms {
if key, err = LoadPublicKeyFromString(s, a); err == nil {
alg = a
break
// chunkSlice split slices
func chunkSlice(slice []byte, chunkSize int) [][]byte {
var chunks [][]byte
for i := 0; i < len(slice); i += chunkSize {
end := i + chunkSize

// necessary check to avoid slicing beyond
// slice capacity
if end > len(slice) {
end = len(slice)
}
chunks = append(chunks, slice[i:end])
}
return key, alg, err

return chunks
}
Loading

0 comments on commit d1a92bb

Please sign in to comment.