2016-01-04 18:26:32 +00:00
|
|
|
package letsencrypt
|
|
|
|
|
|
|
|
import (
|
|
|
|
"encoding/json"
|
|
|
|
"io/ioutil"
|
|
|
|
"os"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/khlieng/dispatch/Godeps/_workspace/src/github.com/xenolf/lego/acme"
|
|
|
|
)
|
|
|
|
|
|
|
|
const URL = "https://acme-v01.api.letsencrypt.org/directory"
|
|
|
|
const KeySize = 2048
|
|
|
|
|
|
|
|
var directory Directory
|
|
|
|
|
|
|
|
func Run(dir, domain, email, port string, onChange func()) (string, string, error) {
|
|
|
|
directory = Directory(dir)
|
|
|
|
|
|
|
|
user, err := getUser(email)
|
|
|
|
if err != nil {
|
|
|
|
return "", "", nil
|
|
|
|
}
|
|
|
|
|
|
|
|
client, err := acme.NewClient(URL, &user, KeySize)
|
|
|
|
client.ExcludeChallenges([]string{"tls-sni-01"})
|
|
|
|
client.SetHTTPPort(port)
|
|
|
|
|
|
|
|
if user.Registration == nil {
|
|
|
|
user.Registration, err = client.Register()
|
|
|
|
if err != nil {
|
|
|
|
return "", "", err
|
|
|
|
}
|
|
|
|
|
|
|
|
err = client.AgreeToTOS()
|
|
|
|
if err != nil {
|
|
|
|
return "", "", err
|
|
|
|
}
|
|
|
|
|
|
|
|
err = saveUser(user)
|
|
|
|
if err != nil {
|
|
|
|
return "", "", err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if certExists(domain) {
|
|
|
|
renew(client, domain)
|
|
|
|
} else {
|
|
|
|
err = obtain(client, domain)
|
|
|
|
if err != nil {
|
|
|
|
return "", "", err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
go keepRenewed(client, domain, onChange)
|
|
|
|
|
|
|
|
return directory.Cert(domain), directory.Key(domain), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func obtain(client *acme.Client, domain string) error {
|
2016-01-05 19:41:17 +00:00
|
|
|
cert, errors := client.ObtainCertificate([]string{domain}, true)
|
2016-01-04 18:26:32 +00:00
|
|
|
if err := errors[domain]; err != nil {
|
|
|
|
if _, ok := err.(acme.TOSError); ok {
|
|
|
|
err := client.AgreeToTOS()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return obtain(client, domain)
|
|
|
|
}
|
|
|
|
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
err := saveCert(cert)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func renew(client *acme.Client, domain string) bool {
|
|
|
|
cert, err := ioutil.ReadFile(directory.Cert(domain))
|
|
|
|
if err != nil {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
exp, err := acme.GetPEMCertExpiration(cert)
|
|
|
|
if err != nil {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
daysLeft := int(exp.Sub(time.Now().UTC()).Hours() / 24)
|
|
|
|
|
|
|
|
if daysLeft <= 30 {
|
|
|
|
metaBytes, err := ioutil.ReadFile(directory.Meta(domain))
|
|
|
|
if err != nil {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
key, err := ioutil.ReadFile(directory.Key(domain))
|
|
|
|
if err != nil {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
var meta acme.CertificateResource
|
|
|
|
err = json.Unmarshal(metaBytes, &meta)
|
|
|
|
if err != nil {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
meta.Certificate = cert
|
|
|
|
meta.PrivateKey = key
|
|
|
|
|
|
|
|
Renew:
|
2016-01-05 19:41:17 +00:00
|
|
|
newMeta, err := client.RenewCertificate(meta, true)
|
2016-01-04 18:26:32 +00:00
|
|
|
if err != nil {
|
|
|
|
if _, ok := err.(acme.TOSError); ok {
|
|
|
|
err := client.AgreeToTOS()
|
|
|
|
if err != nil {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
goto Renew
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
err = saveCert(newMeta)
|
|
|
|
if err != nil {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
func keepRenewed(client *acme.Client, domain string, onChange func()) {
|
|
|
|
for {
|
|
|
|
time.Sleep(24 * time.Hour)
|
|
|
|
if renew(client, domain) {
|
|
|
|
onChange()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func certExists(domain string) bool {
|
|
|
|
if _, err := os.Stat(directory.Cert(domain)); err != nil {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
if _, err := os.Stat(directory.Key(domain)); err != nil {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
func saveCert(cert acme.CertificateResource) error {
|
|
|
|
err := os.MkdirAll(directory.Domain(cert.Domain), 0700)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
err = ioutil.WriteFile(directory.Cert(cert.Domain), cert.Certificate, 0600)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
err = ioutil.WriteFile(directory.Key(cert.Domain), cert.PrivateKey, 0600)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
jsonBytes, err := json.MarshalIndent(&cert, "", " ")
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
err = ioutil.WriteFile(directory.Meta(cert.Domain), jsonBytes, 0600)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|