ory-am/hydra

View on GitHub
cmd/server/helper_cert.go

Summary

Maintainability
A
1 hr
Test Coverage
// Copyright © 2022 Ory Corp
// SPDX-License-Identifier: Apache-2.0

package server

import (
    "context"
    "crypto/sha1" // #nosec G505 - This is required for certificate chains alongside sha256
    "crypto/sha256"
    "crypto/tls"
    "crypto/x509"
    "encoding/pem"
    "sync"

    "github.com/gofrs/uuid"

    "github.com/go-jose/go-jose/v3"

    "github.com/ory/hydra/v2/driver"
    "github.com/ory/hydra/v2/driver/config"

    "github.com/pkg/errors"

    "github.com/ory/x/tlsx"

    "github.com/ory/hydra/v2/jwk"
)

const (
    TlsKeyName = "hydra.https-tls"
)

func AttachCertificate(priv *jose.JSONWebKey, cert *x509.Certificate) {
    priv.Certificates = []*x509.Certificate{cert}
    sig256 := sha256.Sum256(cert.Raw)
    // #nosec G401 - This is required for certificate chains alongside sha256
    sig1 := sha1.Sum(cert.Raw)
    priv.CertificateThumbprintSHA256 = sig256[:]
    priv.CertificateThumbprintSHA1 = sig1[:]
}

var lock sync.Mutex

// GetOrCreateTLSCertificate returns a function for use with
// "net/tls".Config.GetCertificate. If the certificate and key are read from
// disk, they will be automatically reloaded until stopReload is close()'d.
func GetOrCreateTLSCertificate(ctx context.Context, d driver.Registry, iface config.ServeInterface, stopReload <-chan struct{}) func(*tls.ClientHelloInfo) (*tls.Certificate, error) {
    lock.Lock()
    defer lock.Unlock()

    // check if certificates are configured
    certFunc, err := d.Config().TLS(ctx, iface).GetCertificateFunc(stopReload, d.Logger())
    if err == nil {
        return certFunc
    } else if !errors.Is(err, tlsx.ErrNoCertificatesConfigured) {
        d.Logger().WithError(err).Fatal("Unable to load HTTPS TLS Certificate")
        return nil // in case Fatal is hooked
    }

    // no certificates configured: self-sign a new cert
    priv, err := jwk.GetOrGenerateKeys(ctx, d, d.SoftwareKeyManager(), TlsKeyName, uuid.Must(uuid.NewV4()).String(), "RS256")
    if err != nil {
        d.Logger().WithError(err).Fatal("Unable to fetch or generate HTTPS TLS key pair")
        return nil // in case Fatal is hooked
    }

    if len(priv.Certificates) == 0 {
        cert, err := tlsx.CreateSelfSignedCertificate(priv.Key)
        if err != nil {
            d.Logger().WithError(err).Fatal(`Could not generate a self signed TLS certificate`)
            return nil // in case Fatal is hooked
        }

        AttachCertificate(priv, cert)
        if err := d.SoftwareKeyManager().DeleteKey(ctx, TlsKeyName, priv.KeyID); err != nil {
            d.Logger().WithError(err).Fatal(`Could not update (delete) the self signed TLS certificate`)
            return nil // in case Fatal is hooked
        }

        if err := d.SoftwareKeyManager().AddKey(ctx, TlsKeyName, priv); err != nil {
            d.Logger().WithError(err).Fatalf(`Could not update (add) the self signed TLS certificate: %s %x %d`, cert.SignatureAlgorithm, cert.Signature, len(cert.Signature))
            return nil // in case Fatalf is hooked
        }
    }

    block, err := jwk.PEMBlockForKey(priv.Key)
    if err != nil {
        d.Logger().WithError(err).Fatal("Could not encode key to PEM")
        return nil // in case Fatal is hooked
    }

    if len(priv.Certificates) == 0 {
        d.Logger().Fatal("TLS certificate chain can not be empty")
        return nil // in case Fatal is hooked
    }

    pemCert := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: priv.Certificates[0].Raw})
    pemKey := pem.EncodeToMemory(block)
    ct, err := tls.X509KeyPair(pemCert, pemKey)
    if err != nil {
        d.Logger().WithError(err).Fatal("Could not decode certificate")
        return nil // in case Fatal is hooked
    }

    return func(chi *tls.ClientHelloInfo) (*tls.Certificate, error) {
        return &ct, nil
    }
}