Initial cloud-services repo - gateway service + pkg modules
This commit is contained in:
176
pkg/tmobtokengen/encryption.go
Normal file
176
pkg/tmobtokengen/encryption.go
Normal file
@@ -0,0 +1,176 @@
|
||||
package tmobtokengen
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"encoding/pem"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/go-jose/go-jose/v4"
|
||||
"github.com/google/uuid"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/youmark/pkcs8"
|
||||
)
|
||||
|
||||
func buildClientSecret(clientID, secret string) string {
|
||||
return base64.StdEncoding.EncodeToString([]byte(clientID + ":" + secret))
|
||||
}
|
||||
|
||||
var (
|
||||
ErrFailedToDecodePem = errors.New("failed to decode privateKey")
|
||||
ErrNilSigner = errors.New("signer cannot be nil")
|
||||
)
|
||||
|
||||
func ParsePrivateKey(pemFileBytes []byte, pwd []byte) (any, error) {
|
||||
block, _ := pem.Decode(pemFileBytes)
|
||||
if block == nil {
|
||||
return nil, ErrFailedToDecodePem
|
||||
}
|
||||
key, _, err := pkcs8.ParsePrivateKey(block.Bytes, pwd)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to parse pkcs8 private key")
|
||||
}
|
||||
|
||||
return key, nil
|
||||
}
|
||||
|
||||
type jwtClaims struct {
|
||||
EDTS string `json:"edts,omitempty"`
|
||||
V string `json:"v,omitempty"`
|
||||
Exp int64 `json:"exp,omitempty"`
|
||||
EHTS string `json:"ehts,omitempty"`
|
||||
IAT int64 `json:"iat,omitempty"`
|
||||
UniqueStr string `json:"jti,omitempty"`
|
||||
}
|
||||
|
||||
func buildClaims(
|
||||
ehts map[EhtsKey]string,
|
||||
curTime time.Time,
|
||||
expDuration time.Duration,
|
||||
uuidFunc func() string,
|
||||
) ([]byte, error) {
|
||||
curTime = curTime.UTC()
|
||||
ehtsKeys, ehtsValues := ehtsToString(ehts)
|
||||
encoded := b64EncodeEHTS([]byte(ehtsValues))
|
||||
expTime := curTime.Add(expDuration)
|
||||
uniqStr := uuidFunc()
|
||||
|
||||
objClaims := jwtClaims{
|
||||
EDTS: string(encoded),
|
||||
V: "1",
|
||||
Exp: expTime.Unix(),
|
||||
EHTS: ehtsKeys,
|
||||
IAT: curTime.Unix(),
|
||||
UniqueStr: uniqStr,
|
||||
}
|
||||
|
||||
c, err := json.Marshal(objClaims)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to marshal claims during build")
|
||||
}
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func sign(signer jose.Signer, claims []byte) (string, error) {
|
||||
if signer == nil {
|
||||
return "", ErrNilSigner
|
||||
}
|
||||
|
||||
signature, err := signer.Sign(claims)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "failed to sign claims during pop token build")
|
||||
}
|
||||
|
||||
signed, err := signature.CompactSerialize()
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "failed to sign claims during pop token build")
|
||||
}
|
||||
|
||||
return signed, nil
|
||||
}
|
||||
|
||||
type Generator interface {
|
||||
Generate(ehts EHTSMap) (string, error)
|
||||
ClientSecretAsAuthVal() string
|
||||
}
|
||||
|
||||
type PopTokenGenerator struct {
|
||||
signer jose.Signer
|
||||
clientSecret string // client secret composed from client id and secret via base64.StdEncoding
|
||||
expDuration time.Duration // token expiration duration
|
||||
genUuid func() string // uniq string is set as generator field for testing purposes
|
||||
}
|
||||
|
||||
func GenerateUUID() string {
|
||||
return uuid.New().String()
|
||||
}
|
||||
|
||||
// NewTokenGenerator creates a new PopTokenGenerator.
|
||||
func NewTokenGenerator(
|
||||
clientID string,
|
||||
secret string,
|
||||
expDuration time.Duration,
|
||||
pkValue string,
|
||||
pkPwd ...[]byte,
|
||||
) (*PopTokenGenerator, error) {
|
||||
// Removing the read from file. We read our key from the environment
|
||||
pkValue = strings.ReplaceAll(pkValue, "\\n", "\n")
|
||||
keyBytes := pkValue
|
||||
|
||||
var pwd []byte
|
||||
if len(pkPwd) != 0 {
|
||||
pwd = pkPwd[0]
|
||||
}
|
||||
|
||||
pk, err := ParsePrivateKey([]byte(keyBytes), pwd)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to parse private key file")
|
||||
}
|
||||
|
||||
opts := new(jose.SignerOptions)
|
||||
opts.WithType("JWT")
|
||||
signer, err := jose.NewSigner(jose.SigningKey{Algorithm: jose.RS256, Key: pk}, opts)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to create signer")
|
||||
}
|
||||
|
||||
return &PopTokenGenerator{
|
||||
signer: signer,
|
||||
clientSecret: buildClientSecret(clientID, secret),
|
||||
expDuration: expDuration,
|
||||
genUuid: GenerateUUID,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (g *PopTokenGenerator) ClientSecretAsAuthVal() string {
|
||||
return "Basic " + g.clientSecret
|
||||
}
|
||||
|
||||
// Generate generates a pop token.
|
||||
// It expects EHTSMap to be populated with the following keys, order is changed intrinsically, so don't worry:
|
||||
// - ContentType
|
||||
// - Authorization
|
||||
// - URI
|
||||
// - HTTPMethod
|
||||
// - Body
|
||||
func (g *PopTokenGenerator) Generate(ehts EHTSMap) (string, error) {
|
||||
return g.generate(ehts, time.Now())
|
||||
}
|
||||
|
||||
// generate generates a pop token.
|
||||
// It takes the current time as parameter for testing.
|
||||
func (g *PopTokenGenerator) generate(ehts EHTSMap, curTime time.Time) (string, error) {
|
||||
c, err := buildClaims(ehts, curTime, g.expDuration, g.genUuid)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "failed to build claims")
|
||||
}
|
||||
|
||||
signed, err := sign(g.signer, c)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "failed to sign claims")
|
||||
}
|
||||
|
||||
return signed, nil
|
||||
}
|
||||
Reference in New Issue
Block a user