Skip to content

Commit

Permalink
acme_server: Configurable default lifetime for issued certificates (#…
Browse files Browse the repository at this point in the history
…5232)

* acme_server: add certificate lifetime configuration option

Signed-off-by: Kyle McCullough <kylemcc@gmail.com>

* pki: allow intermediate cert lifetime to be configured

Signed-off-by: Kyle McCullough <kylemcc@gmail.com>

Signed-off-by: Kyle McCullough <kylemcc@gmail.com>
  • Loading branch information
kylemcc authored Dec 6, 2022
1 parent fef9cb3 commit bfaf2a8
Show file tree
Hide file tree
Showing 8 changed files with 268 additions and 8 deletions.
18 changes: 15 additions & 3 deletions caddyconfig/httpcaddyfile/pkiapp.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
package httpcaddyfile

import (
"github.com/caddyserver/caddy/v2"
"github.com/caddyserver/caddy/v2/caddyconfig"
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
"github.com/caddyserver/caddy/v2/modules/caddypki"
Expand All @@ -28,9 +29,10 @@ func init() {
//
// pki {
// ca [<id>] {
// name <name>
// root_cn <name>
// intermediate_cn <name>
// name <name>
// root_cn <name>
// intermediate_cn <name>
// intermediate_lifetime <duration>
// root {
// cert <path>
// key <path>
Expand Down Expand Up @@ -83,6 +85,16 @@ func parsePKIApp(d *caddyfile.Dispenser, existingVal any) (any, error) {
}
pkiCa.IntermediateCommonName = d.Val()

case "intermediate_lifetime":
if !d.NextArg() {
return nil, d.ArgErr()
}
dur, err := caddy.ParseDuration(d.Val())
if err != nil {
return nil, err
}
pkiCa.IntermediateLifetime = caddy.Duration(dur)

case "root":
if pkiCa.Root == nil {
pkiCa.Root = new(caddypki.KeyPair)
Expand Down
108 changes: 108 additions & 0 deletions caddytest/integration/caddyfile_adapt/acme_server_lifetime.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
{
pki {
ca internal {
name "Internal"
root_cn "Internal Root Cert"
intermediate_cn "Internal Intermediate Cert"
}
ca internal-long-lived {
name "Long-lived"
root_cn "Internal Root Cert 2"
intermediate_cn "Internal Intermediate Cert 2"
}
}
}

acme-internal.example.com {
acme_server {
ca internal
}
}

acme-long-lived.example.com {
acme_server {
ca internal-long-lived
lifetime 7d
}
}
----------
{
"apps": {
"http": {
"servers": {
"srv0": {
"listen": [
":443"
],
"routes": [
{
"match": [
{
"host": [
"acme-long-lived.example.com"
]
}
],
"handle": [
{
"handler": "subroute",
"routes": [
{
"handle": [
{
"ca": "internal-long-lived",
"handler": "acme_server",
"lifetime": 604800000000000
}
]
}
]
}
],
"terminal": true
},
{
"match": [
{
"host": [
"acme-internal.example.com"
]
}
],
"handle": [
{
"handler": "subroute",
"routes": [
{
"handle": [
{
"ca": "internal",
"handler": "acme_server"
}
]
}
]
}
],
"terminal": true
}
]
}
}
},
"pki": {
"certificate_authorities": {
"internal": {
"name": "Internal",
"root_common_name": "Internal Root Cert",
"intermediate_common_name": "Internal Intermediate Cert"
},
"internal-long-lived": {
"name": "Long-lived",
"root_common_name": "Internal Root Cert 2",
"intermediate_common_name": "Internal Intermediate Cert 2"
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -165,4 +165,4 @@ acme-bar.example.com {
}
}
}
}
}
101 changes: 101 additions & 0 deletions caddytest/integration/pki_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
package integration

import (
"testing"

"github.com/caddyserver/caddy/v2/caddytest"
)

func TestLeafCertLifetimeLessThanIntermediate(t *testing.T) {
caddytest.AssertLoadError(t, `
{
"apps": {
"http": {
"servers": {
"srv0": {
"listen": [
":443"
],
"routes": [
{
"handle": [
{
"handler": "subroute",
"routes": [
{
"handle": [
{
"ca": "internal",
"handler": "acme_server",
"lifetime": 604800000000000
}
]
}
]
}
]
}
]
}
}
},
"pki": {
"certificate_authorities": {
"internal": {
"install_trust": false,
"intermediate_lifetime": 604800000000000,
"name": "Internal CA"
}
}
}
}
}
`, "json", "certificate lifetime (168h0m0s) should be less than intermediate certificate lifetime (168h0m0s)")
}

func TestIntermediateLifetimeLessThanRoot(t *testing.T) {
caddytest.AssertLoadError(t, `
{
"apps": {
"http": {
"servers": {
"srv0": {
"listen": [
":443"
],
"routes": [
{
"handle": [
{
"handler": "subroute",
"routes": [
{
"handle": [
{
"ca": "internal",
"handler": "acme_server",
"lifetime": 2592000000000000
}
]
}
]
}
]
}
]
}
}
},
"pki": {
"certificate_authorities": {
"internal": {
"install_trust": false,
"intermediate_lifetime": 311040000000000000,
"name": "Internal CA"
}
}
}
}
}
`, "json", "intermediate certificate lifetime must be less than root certificate lifetime (86400h0m0s)")
}
14 changes: 13 additions & 1 deletion modules/caddypki/acmeserver/acmeserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@ type Handler struct {
// the default ID is "local".
CA string `json:"ca,omitempty"`

// The lifetime for issued certificates
Lifetime caddy.Duration `json:"lifetime,omitempty"`

// The hostname or IP address by which ACME clients
// will access the server. This is used to populate
// the ACME directory endpoint. If not set, the Host
Expand Down Expand Up @@ -95,6 +98,9 @@ func (ash *Handler) Provision(ctx caddy.Context) error {
if ash.PathPrefix == "" {
ash.PathPrefix = defaultPathPrefix
}
if ash.Lifetime == 0 {
ash.Lifetime = caddy.Duration(12 * time.Hour)
}

// get a reference to the configured CA
appModule, err := ctx.App("pki")
Expand All @@ -107,6 +113,12 @@ func (ash *Handler) Provision(ctx caddy.Context) error {
return err
}

// make sure leaf cert lifetime is less than the intermediate cert lifetime. this check only
// applies for caddy-managed intermediate certificates
if ca.Intermediate == nil && ash.Lifetime >= ca.IntermediateLifetime {
return fmt.Errorf("certificate lifetime (%s) should be less than intermediate certificate lifetime (%s)", time.Duration(ash.Lifetime), time.Duration(ca.IntermediateLifetime))
}

database, err := ash.openDatabase()
if err != nil {
return err
Expand All @@ -122,7 +134,7 @@ func (ash *Handler) Provision(ctx caddy.Context) error {
Claims: &provisioner.Claims{
MinTLSDur: &provisioner.Duration{Duration: 5 * time.Minute},
MaxTLSDur: &provisioner.Duration{Duration: 24 * time.Hour * 365},
DefaultTLSDur: &provisioner.Duration{Duration: 12 * time.Hour},
DefaultTLSDur: &provisioner.Duration{Duration: time.Duration(ash.Lifetime)},
},
},
},
Expand Down
19 changes: 19 additions & 0 deletions modules/caddypki/acmeserver/caddyfile.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@
package acmeserver

import (
"time"

"github.com/caddyserver/caddy/v2"
"github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile"
"github.com/caddyserver/caddy/v2/modules/caddypki"
)
Expand All @@ -27,6 +30,7 @@ func init() {
//
// acme_server [<matcher>] {
// ca <id>
// lifetime <duration>
// }
func parseACMEServer(h httpcaddyfile.Helper) ([]httpcaddyfile.ConfigValue, error) {
if !h.Next() {
Expand Down Expand Up @@ -55,6 +59,21 @@ func parseACMEServer(h httpcaddyfile.Helper) ([]httpcaddyfile.ConfigValue, error
ca = new(caddypki.CA)
}
ca.ID = acmeServer.CA
case "lifetime":
if !h.NextArg() {
return nil, h.ArgErr()
}

dur, err := caddy.ParseDuration(h.Val())
if err != nil {
return nil, err
}

if d := time.Duration(ca.IntermediateLifetime); d > 0 && dur > d {
return nil, h.Errf("certificate lifetime (%s) exceeds intermediate certificate lifetime (%s)", dur, d)
}

acmeServer.Lifetime = caddy.Duration(dur)
}
}
}
Expand Down
10 changes: 9 additions & 1 deletion modules/caddypki/ca.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@ type CA struct {
// intermediate certificates.
IntermediateCommonName string `json:"intermediate_common_name,omitempty"`

// The lifetime for the intermediate certificates
IntermediateLifetime caddy.Duration `json:"intermediate_lifetime,omitempty"`

// Whether Caddy will attempt to install the CA's root
// into the system trust store, as well as into Java
// and Mozilla Firefox trust stores. Default: true.
Expand Down Expand Up @@ -118,6 +121,11 @@ func (ca *CA) Provision(ctx caddy.Context, id string, log *zap.Logger) error {
if ca.IntermediateCommonName == "" {
ca.IntermediateCommonName = defaultIntermediateCommonName
}
if ca.IntermediateLifetime == 0 {
ca.IntermediateLifetime = caddy.Duration(defaultIntermediateLifetime)
} else if time.Duration(ca.IntermediateLifetime) >= defaultRootLifetime {
return fmt.Errorf("intermediate certificate lifetime must be less than root certificate lifetime (%s)", defaultRootLifetime)
}

// load the certs and key that will be used for signing
var rootCert, interCert *x509.Certificate
Expand Down Expand Up @@ -341,7 +349,7 @@ func (ca CA) loadOrGenIntermediate(rootCert *x509.Certificate, rootKey crypto.Si
func (ca CA) genIntermediate(rootCert *x509.Certificate, rootKey crypto.Signer) (interCert *x509.Certificate, interKey crypto.Signer, err error) {
repl := ca.newReplacer()

interCert, interKey, err = generateIntermediate(repl.ReplaceAll(ca.IntermediateCommonName, ""), rootCert, rootKey)
interCert, interKey, err = generateIntermediate(repl.ReplaceAll(ca.IntermediateCommonName, ""), rootCert, rootKey, time.Duration(ca.IntermediateLifetime))
if err != nil {
return nil, nil, fmt.Errorf("generating CA intermediate: %v", err)
}
Expand Down
4 changes: 2 additions & 2 deletions modules/caddypki/certificates.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,8 @@ func generateRoot(commonName string) (*x509.Certificate, crypto.Signer, error) {
return root, signer, nil
}

func generateIntermediate(commonName string, rootCrt *x509.Certificate, rootKey crypto.Signer) (*x509.Certificate, crypto.Signer, error) {
template, signer, err := newCert(commonName, x509util.DefaultIntermediateTemplate, defaultIntermediateLifetime)
func generateIntermediate(commonName string, rootCrt *x509.Certificate, rootKey crypto.Signer, lifetime time.Duration) (*x509.Certificate, crypto.Signer, error) {
template, signer, err := newCert(commonName, x509util.DefaultIntermediateTemplate, lifetime)
if err != nil {
return nil, nil, err
}
Expand Down

0 comments on commit bfaf2a8

Please sign in to comment.