Permalink
Browse files

Persist signing cert and key in database and allow custom

  • Loading branch information...
mraerino committed Aug 23, 2018
1 parent fdccc14 commit 7bcad9a9dea63deb59865be6ce2717f13d881836
Showing with 120 additions and 7 deletions.
  1. +1 −1 api/external.go
  2. +2 −2 api/external_saml.go
  3. +115 −4 api/provider/saml.go
  4. +2 −0 conf/configuration.go
View
@@ -277,7 +277,7 @@ func (a *API) Provider(ctx context.Context, name string) (provider.Provider, err
case "facebook":
return provider.NewFacebookProvider(config.External.Facebook)
case "saml":
return provider.NewSamlProvider(config.External.Saml)
return provider.NewSamlProvider(config.External.Saml, a.db, getInstanceID(ctx))
default:
return nil, fmt.Errorf("Provider %s could not be found", name)
}
View
@@ -22,7 +22,7 @@ func (a *API) loadSAMLState(w http.ResponseWriter, r *http.Request) (context.Con
func (a *API) samlCallback(r *http.Request, ctx context.Context) (*provider.UserProvidedData, error) {
config := a.getConfig(ctx)
samlProvider, err := provider.NewSamlProvider(config.External.Saml)
samlProvider, err := provider.NewSamlProvider(config.External.Saml, a.db, getInstanceID(ctx))
if err != nil {
return nil, badRequestError("Could not initialize SAML provider: %+v", err).WithInternalError(err)
}
@@ -59,7 +59,7 @@ func (a *API) SAMLMetadata(w http.ResponseWriter, r *http.Request) error {
ctx := r.Context()
config := getConfig(ctx)
samlProvider, err := provider.NewSamlProvider(config.External.Saml)
samlProvider, err := provider.NewSamlProvider(config.External.Saml, a.db, getInstanceID(ctx))
if err != nil {
return internalServerError("Could not create SAML Provider: %+v", err).WithInternalError(err)
}
View
@@ -1,27 +1,43 @@
package provider
import (
"crypto/rand"
"crypto/rsa"
"crypto/tls"
"crypto/x509"
"encoding/base64"
"encoding/pem"
"encoding/xml"
"errors"
"fmt"
"io/ioutil"
"math/big"
"net/http"
"net/url"
"strings"
"time"
"github.com/netlify/gotrue/models"
"github.com/netlify/gotrue/storage"
"github.com/netlify/gotrue/conf"
saml2 "github.com/russellhaering/gosaml2"
"github.com/russellhaering/gosaml2/types"
dsig "github.com/russellhaering/goxmldsig"
uuid "github.com/satori/go.uuid"
"golang.org/x/oauth2"
)
type SamlProvider struct {
ServiceProvider *saml2.SAMLServiceProvider
}
type ConfigX509KeyStore struct {
InstanceID uuid.UUID
DB *storage.Connection
Conf conf.SamlProviderConfiguration
}
func getMetadata(url string) (*types.EntityDescriptor, error) {
res, err := http.Get(url)
if err != nil {
@@ -45,7 +61,7 @@ func getMetadata(url string) (*types.EntityDescriptor, error) {
}
// NewSamlProvider creates a Saml account provider.
func NewSamlProvider(ext conf.SamlProviderConfiguration) (*SamlProvider, error) {
func NewSamlProvider(ext conf.SamlProviderConfiguration, db *storage.Connection, instanceId uuid.UUID) (*SamlProvider, error) {
if !ext.Enabled {
return nil, errors.New("SAML Provider is not enabled")
}
@@ -100,8 +116,11 @@ func NewSamlProvider(ext conf.SamlProviderConfiguration) (*SamlProvider, error)
}
}
// TODO: generate keys once, save them in the database and use here
randomKeyStore := dsig.RandomKeyStoreForTest()
keyStore := &ConfigX509KeyStore{
InstanceID: instanceId,
DB: db,
Conf: ext,
}
sp := &saml2.SAMLServiceProvider{
IdentityProviderSSOURL: ssoService.Location,
@@ -111,7 +130,7 @@ func NewSamlProvider(ext conf.SamlProviderConfiguration) (*SamlProvider, error)
SignAuthnRequests: true,
AudienceURI: baseURI.String() + "/saml",
IDPCertificateStore: &certStore,
SPKeyStore: randomKeyStore,
SPKeyStore: keyStore,
AllowMissingAttributes: true,
}
@@ -142,3 +161,95 @@ func (p SamlProvider) SPMetadata() ([]byte, error) {
return rawMetadata, nil
}
func (ks ConfigX509KeyStore) GetKeyPair() (*rsa.PrivateKey, []byte, error) {
if ks.Conf.SigningCert == "" && ks.Conf.SigningKey == "" {
return ks.CreateSigningCert()
}
keyPair, err := tls.X509KeyPair([]byte(ks.Conf.SigningCert), []byte(ks.Conf.SigningKey))
if err != nil {
return nil, nil, fmt.Errorf("Parsing key pair failed: %+v", err)
}
var privKey *rsa.PrivateKey
switch key := keyPair.PrivateKey.(type) {
case *rsa.PrivateKey:
privKey = key
default:
return nil, nil, errors.New("Private key is not an RSA key")
}
return privKey, keyPair.Certificate[0], nil
}
func (ks ConfigX509KeyStore) CreateSigningCert() (*rsa.PrivateKey, []byte, error) {
key, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
return nil, nil, err
}
currentTime := time.Now()
certBody := &x509.Certificate{
SerialNumber: big.NewInt(1),
NotBefore: currentTime.Add(-5 * time.Minute),
NotAfter: currentTime.Add(365 * 24 * time.Hour),
KeyUsage: x509.KeyUsageDigitalSignature,
ExtKeyUsage: []x509.ExtKeyUsage{},
BasicConstraintsValid: true,
}
cert, err := x509.CreateCertificate(rand.Reader, certBody, certBody, &key.PublicKey, key)
if err != nil {
return nil, nil, fmt.Errorf("Failed to create certificate: %+v", err)
}
if err := ks.SaveConfig(cert, key); err != nil {
return nil, nil, fmt.Errorf("Saving signing keypair failed: %+v", err)
}
return key, cert, nil
}
func (ks ConfigX509KeyStore) SaveConfig(cert []byte, key *rsa.PrivateKey) error {
if uuid.Equal(ks.InstanceID, uuid.Nil) {
return nil
}
pemCert := &pem.Block{
Type: "CERTIFICATE",
Bytes: cert,
}
certBytes := pem.EncodeToMemory(pemCert)
if certBytes == nil {
return errors.New("Could not encode certificate")
}
pemKey := &pem.Block{
Type: "PRIVATE KEY",
Bytes: x509.MarshalPKCS1PrivateKey(key),
}
keyBytes := pem.EncodeToMemory(pemKey)
if keyBytes == nil {
return errors.New("Could not encode key")
}
instance, err := models.GetInstance(ks.DB, ks.InstanceID)
if err != nil {
return err
}
conf := instance.BaseConfig
conf.External.Saml.SigningCert = string(certBytes)
conf.External.Saml.SigningKey = string(keyBytes)
if err := instance.UpdateConfig(ks.DB, conf); err != nil {
return err
}
return nil
}
View
@@ -30,6 +30,8 @@ type SamlProviderConfiguration struct {
MetadataURL string `json:"metadata_url" envconfig:"METADATA_URL"`
APIBase string `json:"api_base" envconfig:"API_BASE"`
Name string `json:"name"`
SigningCert string `json:"signing_cert" envconfig:"SIGNING_CERT"`
SigningKey string `json:"signing_key" envconfig:"SIGNING_KEY"`
}
// DBConfiguration holds all the database related configuration.

0 comments on commit 7bcad9a

Please sign in to comment.