Implement renewal. Fixes #7

This commit is contained in:
xenolf 2015-10-19 00:42:04 +02:00
parent 29a27ba807
commit 0cd31861d3
3 changed files with 102 additions and 0 deletions

View file

@ -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
View file

@ -50,6 +50,11 @@ func main() {
Usage: "Revoke a certificate",
Action: revoke,
},
{
Name: "renew",
Usage: "Renew a certificate",
Action: renew,
},
}
app.Flags = []cli.Flag{

View file

@ -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)
}
}