forked from TrueCloudLab/lego
Add OCSP must staple support
Introduces a new command line switch `--must-staple` to `run` and `renew`. Using this switch will add the must staple TLS extension to the CSR generated by lego and thus also to the generated certificate. This does not work with user specified CSRs! Fixes #270
This commit is contained in:
parent
4bb8bea031
commit
72914df00f
6 changed files with 34 additions and 11 deletions
|
@ -353,7 +353,7 @@ DNSNames:
|
||||||
// your issued certificate as a bundle.
|
// your issued certificate as a bundle.
|
||||||
// This function will never return a partial certificate. If one domain in the list fails,
|
// This function will never return a partial certificate. If one domain in the list fails,
|
||||||
// the whole certificate will fail.
|
// the whole certificate will fail.
|
||||||
func (c *Client) ObtainCertificate(domains []string, bundle bool, privKey crypto.PrivateKey) (CertificateResource, map[string]error) {
|
func (c *Client) ObtainCertificate(domains []string, bundle bool, privKey crypto.PrivateKey, mustStaple bool) (CertificateResource, map[string]error) {
|
||||||
if bundle {
|
if bundle {
|
||||||
logf("[INFO][%s] acme: Obtaining bundled SAN certificate", strings.Join(domains, ", "))
|
logf("[INFO][%s] acme: Obtaining bundled SAN certificate", strings.Join(domains, ", "))
|
||||||
} else {
|
} else {
|
||||||
|
@ -374,7 +374,7 @@ func (c *Client) ObtainCertificate(domains []string, bundle bool, privKey crypto
|
||||||
|
|
||||||
logf("[INFO][%s] acme: Validations succeeded; requesting certificates", strings.Join(domains, ", "))
|
logf("[INFO][%s] acme: Validations succeeded; requesting certificates", strings.Join(domains, ", "))
|
||||||
|
|
||||||
cert, err := c.requestCertificate(challenges, bundle, privKey)
|
cert, err := c.requestCertificate(challenges, bundle, privKey, mustStaple)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
for _, chln := range challenges {
|
for _, chln := range challenges {
|
||||||
failures[chln.Domain] = err
|
failures[chln.Domain] = err
|
||||||
|
@ -410,7 +410,7 @@ func (c *Client) RevokeCertificate(certificate []byte) error {
|
||||||
// If bundle is true, the []byte contains both the issuer certificate and
|
// If bundle is true, the []byte contains both the issuer certificate and
|
||||||
// your issued certificate as a bundle.
|
// your issued certificate as a bundle.
|
||||||
// For private key reuse the PrivateKey property of the passed in CertificateResource should be non-nil.
|
// For private key reuse the PrivateKey property of the passed in CertificateResource should be non-nil.
|
||||||
func (c *Client) RenewCertificate(cert CertificateResource, bundle bool) (CertificateResource, error) {
|
func (c *Client) RenewCertificate(cert CertificateResource, bundle, mustStaple bool) (CertificateResource, error) {
|
||||||
// Input certificate is PEM encoded. Decode it here as we may need the decoded
|
// Input certificate is PEM encoded. Decode it here as we may need the decoded
|
||||||
// cert later on in the renewal process. The input may be a bundle or a single certificate.
|
// cert later on in the renewal process. The input may be a bundle or a single certificate.
|
||||||
certificates, err := parsePEMBundle(cert.Certificate)
|
certificates, err := parsePEMBundle(cert.Certificate)
|
||||||
|
@ -462,7 +462,7 @@ func (c *Client) RenewCertificate(cert CertificateResource, bundle bool) (Certif
|
||||||
domains = append(domains, x509Cert.Subject.CommonName)
|
domains = append(domains, x509Cert.Subject.CommonName)
|
||||||
}
|
}
|
||||||
|
|
||||||
newCert, failures := c.ObtainCertificate(domains, bundle, privKey)
|
newCert, failures := c.ObtainCertificate(domains, bundle, privKey, mustStaple)
|
||||||
return newCert, failures[cert.Domain]
|
return newCert, failures[cert.Domain]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -563,7 +563,7 @@ func (c *Client) getChallenges(domains []string) ([]authorizationResource, map[s
|
||||||
return challenges, failures
|
return challenges, failures
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) requestCertificate(authz []authorizationResource, bundle bool, privKey crypto.PrivateKey) (CertificateResource, error) {
|
func (c *Client) requestCertificate(authz []authorizationResource, bundle bool, privKey crypto.PrivateKey, mustStaple bool) (CertificateResource, error) {
|
||||||
if len(authz) == 0 {
|
if len(authz) == 0 {
|
||||||
return CertificateResource{}, errors.New("Passed no authorizations to requestCertificate!")
|
return CertificateResource{}, errors.New("Passed no authorizations to requestCertificate!")
|
||||||
}
|
}
|
||||||
|
@ -584,7 +584,7 @@ func (c *Client) requestCertificate(authz []authorizationResource, bundle bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: should the CSR be customizable?
|
// TODO: should the CSR be customizable?
|
||||||
csr, err := generateCsr(privKey, commonName.Domain, san)
|
csr, err := generateCsr(privKey, commonName.Domain, san, mustStaple)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return CertificateResource{}, err
|
return CertificateResource{}, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,6 +20,8 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"encoding/asn1"
|
||||||
|
|
||||||
"golang.org/x/crypto/ocsp"
|
"golang.org/x/crypto/ocsp"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -47,6 +49,12 @@ const (
|
||||||
OCSPServerFailed = ocsp.ServerFailed
|
OCSPServerFailed = ocsp.ServerFailed
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Constants for OCSP must staple
|
||||||
|
var (
|
||||||
|
tlsFeatureExtensionOID = asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 1, 24}
|
||||||
|
ocspMustStapleFeature = []byte{0x30, 0x03, 0x02, 0x01, 0x05}
|
||||||
|
)
|
||||||
|
|
||||||
// GetOCSPForCert takes a PEM encoded cert or cert bundle returning the raw OCSP response,
|
// GetOCSPForCert takes a PEM encoded cert or cert bundle returning the raw OCSP response,
|
||||||
// the parsed response, and an error, if any. The returned []byte can be passed directly
|
// the parsed response, and an error, if any. The returned []byte can be passed directly
|
||||||
// into the OCSPStaple property of a tls.Certificate. If the bundle only contains the
|
// into the OCSPStaple property of a tls.Certificate. If the bundle only contains the
|
||||||
|
@ -206,7 +214,7 @@ func generatePrivateKey(keyType KeyType) (crypto.PrivateKey, error) {
|
||||||
return nil, fmt.Errorf("Invalid KeyType: %s", keyType)
|
return nil, fmt.Errorf("Invalid KeyType: %s", keyType)
|
||||||
}
|
}
|
||||||
|
|
||||||
func generateCsr(privateKey crypto.PrivateKey, domain string, san []string) ([]byte, error) {
|
func generateCsr(privateKey crypto.PrivateKey, domain string, san []string, mustStaple bool) ([]byte, error) {
|
||||||
template := x509.CertificateRequest{
|
template := x509.CertificateRequest{
|
||||||
Subject: pkix.Name{
|
Subject: pkix.Name{
|
||||||
CommonName: domain,
|
CommonName: domain,
|
||||||
|
@ -217,6 +225,13 @@ func generateCsr(privateKey crypto.PrivateKey, domain string, san []string) ([]b
|
||||||
template.DNSNames = san
|
template.DNSNames = san
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if mustStaple {
|
||||||
|
template.Extensions = append(template.Extensions, pkix.Extension{
|
||||||
|
Id: tlsFeatureExtensionOID,
|
||||||
|
Value: ocspMustStapleFeature,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
return x509.CreateCertificateRequest(rand.Reader, &template, privateKey)
|
return x509.CreateCertificateRequest(rand.Reader, &template, privateKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -24,7 +24,7 @@ func TestGenerateCSR(t *testing.T) {
|
||||||
t.Fatal("Error generating private key:", err)
|
t.Fatal("Error generating private key:", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
csr, err := generateCsr(key, "fizz.buzz", nil)
|
csr, err := generateCsr(key, "fizz.buzz", nil, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error("Error generating CSR:", err)
|
t.Error("Error generating CSR:", err)
|
||||||
}
|
}
|
||||||
|
|
8
cli.go
8
cli.go
|
@ -64,6 +64,10 @@ func main() {
|
||||||
Name: "no-bundle",
|
Name: "no-bundle",
|
||||||
Usage: "Do not create a certificate bundle by adding the issuers certificate to the new certificate.",
|
Usage: "Do not create a certificate bundle by adding the issuers certificate to the new certificate.",
|
||||||
},
|
},
|
||||||
|
cli.BoolFlag{
|
||||||
|
Name: "must-staple",
|
||||||
|
Usage: "Include the OCSP must staple TLS extension in the CSR and generated certificate. Only works if the CSR is generated by lego.",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -89,6 +93,10 @@ func main() {
|
||||||
Name: "no-bundle",
|
Name: "no-bundle",
|
||||||
Usage: "Do not create a certificate bundle by adding the issuers certificate to the new certificate.",
|
Usage: "Do not create a certificate bundle by adding the issuers certificate to the new certificate.",
|
||||||
},
|
},
|
||||||
|
cli.BoolFlag{
|
||||||
|
Name: "must-staple",
|
||||||
|
Usage: "Include the OCSP must staple TLS extension in the CSR and generated certificate. Only works if the CSR is generated by lego.",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
@ -339,7 +339,7 @@ func run(c *cli.Context) error {
|
||||||
|
|
||||||
if hasDomains {
|
if hasDomains {
|
||||||
// obtain a certificate, generating a new private key
|
// obtain a certificate, generating a new private key
|
||||||
cert, failures = client.ObtainCertificate(c.GlobalStringSlice("domains"), !c.Bool("no-bundle"), nil)
|
cert, failures = client.ObtainCertificate(c.GlobalStringSlice("domains"), !c.Bool("no-bundle"), nil, c.Bool("must-staple"))
|
||||||
} else {
|
} else {
|
||||||
// read the CSR
|
// read the CSR
|
||||||
csr, err := readCSRFile(c.GlobalString("csr"))
|
csr, err := readCSRFile(c.GlobalString("csr"))
|
||||||
|
@ -452,7 +452,7 @@ func renew(c *cli.Context) error {
|
||||||
|
|
||||||
certRes.Certificate = certBytes
|
certRes.Certificate = certBytes
|
||||||
|
|
||||||
newCert, err := client.RenewCertificate(certRes, !c.Bool("no-bundle"))
|
newCert, err := client.RenewCertificate(certRes, !c.Bool("no-bundle"), c.Bool("must-staple"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger().Fatalf("%s", err.Error())
|
logger().Fatalf("%s", err.Error())
|
||||||
}
|
}
|
||||||
|
|
|
@ -141,7 +141,7 @@ func TestDNSProviderLive(t *testing.T) {
|
||||||
}
|
}
|
||||||
// complete the challenge
|
// complete the challenge
|
||||||
bundle := false
|
bundle := false
|
||||||
_, failures := client.ObtainCertificate([]string{domain}, bundle, nil)
|
_, failures := client.ObtainCertificate([]string{domain}, bundle, nil, false)
|
||||||
if len(failures) > 0 {
|
if len(failures) > 0 {
|
||||||
t.Fatal(failures)
|
t.Fatal(failures)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue