Implement renewal. Fixes #7
This commit is contained in:
parent
29a27ba807
commit
0cd31861d3
3 changed files with 102 additions and 0 deletions
|
@ -2,6 +2,7 @@ package acme
|
|||
|
||||
import (
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
|
@ -187,6 +188,53 @@ func (c *Client) RevokeCertificate(certificate []byte) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// RenewCertificate takes a CertificateResource and tries to renew the certificate.
|
||||
// If the renewal process succeeds, the new certificate will replace the old one in the CertResource.
|
||||
// Please be aware that this function will return a new certificate in ANY case that is not an error.
|
||||
// If the server does not provide us with a new cert on a GET request to the CertURL
|
||||
// this function will start a new-cert flow where the old (provided) cert will get REVOKED
|
||||
// and a new certificate gets generated.
|
||||
func (c *Client) RenewCertificate(cert *CertificateResource) (*CertificateResource, error) {
|
||||
// Input certificate is PEM encoded. Decode it here as we may need the decoded
|
||||
// cert later on in the renewal process.
|
||||
x509Cert, err := pemDecodeTox509(cert.Certificate)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// This is just meant to be informal for the user.
|
||||
timeLeft := x509Cert.NotAfter.Sub(time.Now().UTC())
|
||||
logger().Printf("[%s] Trying to renew certificate with %d hours remaining.", cert.Domain, int(timeLeft.Hours()))
|
||||
|
||||
// The first step of renewal is to check if we get a renewed cert
|
||||
// directly from the cert URL.
|
||||
resp, err := http.Get(cert.CertURL)
|
||||
serverCertBytes, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
serverCert, err := x509.ParseCertificate(serverCertBytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// If the server responds with a different certificate we are effectively renewed.
|
||||
// TODO: Further test if we can actually use the new certificate (Our private key works)
|
||||
if !x509Cert.Equal(serverCert) {
|
||||
logger().Printf("[%s] The server responded with a renewed certificate.", cert.Domain)
|
||||
cert.Certificate = pemEncode(derCertificateBytes(serverCertBytes))
|
||||
return cert, nil
|
||||
}
|
||||
|
||||
newCerts, err := c.ObtainCertificates([]string{cert.Domain})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &newCerts[0], nil
|
||||
}
|
||||
|
||||
// Looks through the challenge combinations to find a solvable match.
|
||||
// Then solves the challenges in series and returns.
|
||||
func (c *Client) solveChallenges(challenges []*authorizationResource) error {
|
||||
|
|
5
cli.go
5
cli.go
|
@ -50,6 +50,11 @@ func main() {
|
|||
Usage: "Revoke a certificate",
|
||||
Action: revoke,
|
||||
},
|
||||
{
|
||||
Name: "renew",
|
||||
Usage: "Renew a certificate",
|
||||
Action: renew,
|
||||
},
|
||||
}
|
||||
|
||||
app.Flags = []cli.Flag{
|
||||
|
|
|
@ -158,3 +158,52 @@ func revoke(c *cli.Context) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
func renew(c *cli.Context) {
|
||||
conf, _, client := setup(c)
|
||||
|
||||
for _, domain := range c.GlobalStringSlice("domains") {
|
||||
// load the cert resource from files.
|
||||
// We store the certificate, private key and metadata in different files
|
||||
// as web servers would not be able to work with a combined file.
|
||||
certPath := path.Join(conf.CertPath(), domain+".crt")
|
||||
privPath := path.Join(conf.CertPath(), domain+".key")
|
||||
metaPath := path.Join(conf.CertPath(), domain+".json")
|
||||
|
||||
certBytes, err := ioutil.ReadFile(certPath)
|
||||
if err != nil {
|
||||
logger().Printf("Error while loading the certificate for domain %s\n\t%v", domain, err)
|
||||
return
|
||||
}
|
||||
|
||||
keyBytes, err := ioutil.ReadFile(privPath)
|
||||
if err != nil {
|
||||
logger().Printf("Error while loading the private key for domain %s\n\t%v", domain, err)
|
||||
return
|
||||
}
|
||||
|
||||
metaBytes, err := ioutil.ReadFile(metaPath)
|
||||
if err != nil {
|
||||
logger().Printf("Error while loading the meta data for domain %s\n\t%v", domain, err)
|
||||
return
|
||||
}
|
||||
|
||||
var certRes acme.CertificateResource
|
||||
err = json.Unmarshal(metaBytes, &certRes)
|
||||
if err != nil {
|
||||
logger().Printf("Error while marshalling the meta data for domain %s\n\t%v", domain, err)
|
||||
return
|
||||
}
|
||||
|
||||
certRes.PrivateKey = keyBytes
|
||||
certRes.Certificate = certBytes
|
||||
|
||||
newCert, err := client.RenewCertificate(&certRes)
|
||||
if err != nil {
|
||||
logger().Printf("%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
saveCertRes(newCert, conf)
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue