authelia/internal/oidc/keys.go
James Elliott fcac438637
feat(commands): enhance crypto generation capabilities (#2842)
This expands the functionality of the certificates and rsa commands and merges them into one command called cypto which can either use the cert or pair subcommands to generate certificates or key-pairs respectively. The rsa, ecdsa, and ed25519 subcommands exist for both the cert and pair commands. A new --ca-path argument for the cert subcommand allows Authelia to sign other certs with CA certs.

Co-authored-by: Amir Zarrinkafsh <nightah@me.com>
2022-06-27 18:27:57 +10:00

204 lines
5.9 KiB
Go

package oidc
import (
"context"
"crypto"
"crypto/rsa"
"errors"
"fmt"
"strings"
"github.com/ory/fosite/token/jwt"
"gopkg.in/square/go-jose.v2"
"github.com/authelia/authelia/v4/internal/configuration/schema"
"github.com/authelia/authelia/v4/internal/utils"
)
// NewKeyManagerWithConfiguration when provided a schema.OpenIDConnectConfiguration creates a new KeyManager and adds an
// initial key to the manager.
func NewKeyManagerWithConfiguration(configuration *schema.OpenIDConnectConfiguration) (manager *KeyManager, err error) {
manager = NewKeyManager()
_, _, err = manager.AddActivePrivateKeyData(configuration.IssuerPrivateKey)
if err != nil {
return nil, err
}
return manager, nil
}
// NewKeyManager creates a new empty KeyManager.
func NewKeyManager() (manager *KeyManager) {
manager = new(KeyManager)
manager.keys = map[string]*rsa.PrivateKey{}
manager.keySet = new(jose.JSONWebKeySet)
return manager
}
// Strategy returns the RS256JWTStrategy.
func (m KeyManager) Strategy() (strategy *RS256JWTStrategy) {
return m.strategy
}
// GetKeySet returns the joseJSONWebKeySet containing the rsa.PublicKey types.
func (m KeyManager) GetKeySet() (keySet *jose.JSONWebKeySet) {
return m.keySet
}
// GetActiveWebKey obtains the currently active jose.JSONWebKey.
func (m KeyManager) GetActiveWebKey() (webKey *jose.JSONWebKey, err error) {
webKeys := m.keySet.Key(m.activeKeyID)
if len(webKeys) == 1 {
return &webKeys[0], nil
}
if len(webKeys) == 0 {
return nil, errors.New("could not find a key with the active key id")
}
return &webKeys[0], errors.New("multiple keys with the same key id")
}
// GetActiveKeyID returns the key id of the currently active key.
func (m KeyManager) GetActiveKeyID() (keyID string) {
return m.activeKeyID
}
// GetActiveKey returns the rsa.PublicKey of the currently active key.
func (m KeyManager) GetActiveKey() (key *rsa.PublicKey, err error) {
if key, ok := m.keys[m.activeKeyID]; ok {
return &key.PublicKey, nil
}
return nil, errors.New("failed to retrieve active public key")
}
// GetActivePrivateKey returns the rsa.PrivateKey of the currently active key.
func (m KeyManager) GetActivePrivateKey() (key *rsa.PrivateKey, err error) {
if key, ok := m.keys[m.activeKeyID]; ok {
return key, nil
}
return nil, errors.New("failed to retrieve active private key")
}
// AddActivePrivateKeyData adds a rsa.PublicKey given the key in the PEM string format, then sets it to the active key.
func (m *KeyManager) AddActivePrivateKeyData(data string) (key *rsa.PrivateKey, webKey *jose.JSONWebKey, err error) {
ikey, err := utils.ParseX509FromPEM([]byte(data))
if err != nil {
return nil, nil, err
}
var ok bool
if key, ok = ikey.(*rsa.PrivateKey); !ok {
return nil, nil, errors.New("key must be an RSA private key")
}
webKey, err = m.AddActivePrivateKey(key)
return key, webKey, err
}
// AddActivePrivateKey adds a rsa.PublicKey, then sets it to the active key.
func (m *KeyManager) AddActivePrivateKey(key *rsa.PrivateKey) (webKey *jose.JSONWebKey, err error) {
wk := jose.JSONWebKey{
Key: &key.PublicKey,
Algorithm: "RS256",
Use: "sig",
}
keyID, err := wk.Thumbprint(crypto.SHA1)
if err != nil {
return nil, err
}
strKeyID := strings.ToLower(fmt.Sprintf("%x", keyID))
if len(strKeyID) >= 7 {
// Shorten the key if it's greater than 7 to a length of exactly 7.
strKeyID = strKeyID[0:6]
}
if _, ok := m.keys[strKeyID]; ok {
return nil, fmt.Errorf("key id %s already exists", strKeyID)
}
// TODO: Add Mutex here when implementing key rotation.
wk.KeyID = strKeyID
m.keySet.Keys = append(m.keySet.Keys, wk)
m.keys[strKeyID] = key
m.activeKeyID = strKeyID
m.strategy, err = NewRS256JWTStrategy(wk.KeyID, key)
if err != nil {
return &wk, err
}
return &wk, nil
}
// NewRS256JWTStrategy returns a new RS256JWTStrategy.
func NewRS256JWTStrategy(id string, key *rsa.PrivateKey) (strategy *RS256JWTStrategy, err error) {
strategy = new(RS256JWTStrategy)
strategy.JWTStrategy = new(jwt.RS256JWTStrategy)
strategy.SetKey(id, key)
return strategy, nil
}
// RS256JWTStrategy is a decorator struct for the fosite RS256JWTStrategy.
type RS256JWTStrategy struct {
JWTStrategy *jwt.RS256JWTStrategy
keyID string
}
// KeyID returns the key id.
func (s RS256JWTStrategy) KeyID() (id string) {
return s.keyID
}
// SetKey sets the provided key id and key as the active key (this is what triggers fosite to use it).
func (s *RS256JWTStrategy) SetKey(id string, key *rsa.PrivateKey) {
s.keyID = id
s.JWTStrategy.PrivateKey = key
}
// Hash is a decorator func for the underlying fosite RS256JWTStrategy.
func (s *RS256JWTStrategy) Hash(ctx context.Context, in []byte) ([]byte, error) {
return s.JWTStrategy.Hash(ctx, in)
}
// GetSigningMethodLength is a decorator func for the underlying fosite RS256JWTStrategy.
func (s *RS256JWTStrategy) GetSigningMethodLength() int {
return s.JWTStrategy.GetSigningMethodLength()
}
// GetSignature is a decorator func for the underlying fosite RS256JWTStrategy.
func (s *RS256JWTStrategy) GetSignature(ctx context.Context, token string) (string, error) {
return s.JWTStrategy.GetSignature(ctx, token)
}
// Generate is a decorator func for the underlying fosite RS256JWTStrategy.
func (s *RS256JWTStrategy) Generate(ctx context.Context, claims jwt.MapClaims, header jwt.Mapper) (string, string, error) {
return s.JWTStrategy.Generate(ctx, claims, header)
}
// Validate is a decorator func for the underlying fosite RS256JWTStrategy.
func (s *RS256JWTStrategy) Validate(ctx context.Context, token string) (string, error) {
return s.JWTStrategy.Validate(ctx, token)
}
// Decode is a decorator func for the underlying fosite RS256JWTStrategy.
func (s *RS256JWTStrategy) Decode(ctx context.Context, token string) (*jwt.Token, error) {
return s.JWTStrategy.Decode(ctx, token)
}
// GetPublicKeyID is a decorator func for the underlying fosite RS256JWTStrategy.
func (s *RS256JWTStrategy) GetPublicKeyID(_ context.Context) (string, error) {
return s.keyID, nil
}