Base implementation with registration support

This commit is contained in:
xenolf 2015-06-08 02:36:07 +02:00
commit ea47f1137a
10 changed files with 932 additions and 0 deletions

1
.gitignore vendored Normal file
View file

@ -0,0 +1 @@
lego.exe

96
account.go Normal file
View file

@ -0,0 +1,96 @@
package main
import (
"crypto/rsa"
"encoding/json"
"io/ioutil"
"os"
"path"
"github.com/xenolf/lego/acme"
)
// Account represents a users local saved credentials
type Account struct {
Email string `json:"email"`
key *rsa.PrivateKey
Registration *acme.RegistrationResource `json:"registration"`
conf *Configuration
}
// NewAccount creates a new account for an email address
func NewAccount(email string, conf *Configuration) *Account {
accKeysPath := conf.AccountKeysPath(email)
// TODO: move to function in configuration?
accKeyPath := accKeysPath + string(os.PathSeparator) + email + ".key"
if err := checkFolder(accKeysPath); err != nil {
logger().Fatalf("Could not check/create directory for account %s: %v", email, err)
}
var privKey *rsa.PrivateKey
if _, err := os.Stat(accKeyPath); os.IsNotExist(err) {
logger().Printf("No key found for account %s. Generating a %v bit key.", email, conf.RsaBits())
privKey, err = generateRsaKey(conf.RsaBits(), accKeyPath)
if err != nil {
logger().Fatalf("Could not generate RSA private account key for account %s: %v", email, err)
}
logger().Printf("Saved key to %s", accKeyPath)
} else {
privKey, err = loadRsaKey(accKeyPath)
if err != nil {
logger().Fatalf("Could not load RSA private key from file %s: %v", accKeyPath, err)
}
}
accountFile := path.Join(conf.AccountPath(email), "account.json")
if _, err := os.Stat(accountFile); os.IsNotExist(err) {
return &Account{Email: email, key: privKey, conf: conf}
}
fileBytes, err := ioutil.ReadFile(accountFile)
if err != nil {
logger().Fatalf("Could not load file for account %s -> %v", email, err)
}
var acc Account
err = json.Unmarshal(fileBytes, &acc)
if err != nil {
logger().Fatalf("Could not parse file for account %s -> %v", email, err)
}
acc.key = privKey
acc.conf = conf
return &acc
}
/** Implementation of the acme.User interface **/
// GetEmail returns the email address for the account
func (a *Account) GetEmail() string {
return a.Email
}
// GetPrivateKey returns the private RSA account key.
func (a *Account) GetPrivateKey() *rsa.PrivateKey {
return a.key
}
// GetRegistration returns the server registration
func (a *Account) GetRegistration() *acme.RegistrationResource {
return a.Registration
}
/** End **/
// Save the account to disk
func (a *Account) Save() error {
jsonBytes, err := json.Marshal(a)
if err != nil {
return err
}
return ioutil.WriteFile(path.Join(a.conf.AccountPath(a.Email), "account.json"), jsonBytes, 0700)
}

144
acme/client.go Normal file
View file

@ -0,0 +1,144 @@
package acme
import (
"bytes"
"crypto/rsa"
"encoding/json"
"errors"
"io/ioutil"
"log"
"net/http"
"os"
"regexp"
"strings"
"github.com/square/go-jose"
)
// Logger is used to log errors; if nil, the default log.Logger is used.
var Logger *log.Logger
// logger is an helper function to retrieve the available logger
func logger() *log.Logger {
if Logger == nil {
Logger = log.New(os.Stderr, "", log.LstdFlags)
}
return Logger
}
// User interface is to be implemented by users of this library.
// It is used by the client type to get user specific information.
type User interface {
GetEmail() string
GetRegistration() *RegistrationResource
GetPrivateKey() *rsa.PrivateKey
}
// Client is the user-friendy way to ACME
type Client struct {
regURL string
user User
}
// NewClient creates a new client for the set user.
func NewClient(caURL string, usr User) *Client {
if err := usr.GetPrivateKey().Validate(); err != nil {
logger().Fatalf("Could not validate the private account key of %s -> %v", usr.GetEmail(), err)
}
return &Client{regURL: caURL, user: usr}
}
// Posts a JWS signed message to the specified URL
func (c *Client) jwsPost(url string, content []byte) (*http.Response, error) {
signer, err := jose.NewSigner(jose.RS256, c.user.GetPrivateKey())
if err != nil {
return nil, err
}
signed, err := signer.Sign(content)
if err != nil {
return nil, err
}
signedContent := signed.FullSerialize()
resp, err := http.Post(url, "application/json", bytes.NewBuffer([]byte(signedContent)))
if err != nil {
return nil, err
}
return resp, err
}
// Register the current account to the ACME server.
func (c *Client) Register() (*RegistrationResource, error) {
logger().Print("Registering account ... ")
jsonBytes, err := json.Marshal(registrationMessage{Contact: []string{"mailto:" + c.user.GetEmail()}})
if err != nil {
return nil, err
}
resp, err := c.jwsPost(c.regURL, jsonBytes)
if err != nil {
return nil, err
}
if resp.StatusCode == http.StatusConflict {
// REVIEW: should this return an error?
return nil, errors.New("This account is already registered with this CA.")
}
var serverReg Registration
decoder := json.NewDecoder(resp.Body)
err = decoder.Decode(&serverReg)
if err != nil {
return nil, err
}
reg := &RegistrationResource{Body: serverReg}
links := parseLinks(resp.Header["Link"])
reg.URI = resp.Header.Get("Location")
if links["terms-of-service"] != "" {
reg.TosURL = links["terms-of-service"]
}
if links["next"] != "" {
reg.NewAuthzURL = links["next"]
} else {
return nil, errors.New("The server did not return enough information to proceed...")
}
return reg, nil
}
func logResponseHeaders(resp *http.Response) {
logger().Println(resp.Status)
for k, v := range resp.Header {
logger().Printf("-- %s: %s", k, v)
}
}
func logResponseBody(resp *http.Response) {
body, _ := ioutil.ReadAll(resp.Body)
logger().Printf("Returned json data: \n%s", body)
}
func parseLinks(links []string) map[string]string {
aBrkt := regexp.MustCompile("[<>]")
slver := regexp.MustCompile("(.+) *= *\"(.+)\"")
linkMap := make(map[string]string)
for _, link := range links {
link = aBrkt.ReplaceAllString(link, "")
parts := strings.Split(link, ";")
matches := slver.FindStringSubmatch(parts[1])
if len(matches) > 0 {
linkMap[matches[2]] = parts[0]
}
}
return linkMap
}

27
acme/messages.go Normal file
View file

@ -0,0 +1,27 @@
package acme
type registrationMessage struct {
Contact []string `json:"contact"`
}
// Registration is returned by the ACME server after the registration
// The client implementation should save this registration somewhere.
type Registration struct {
ID int `json:"id"`
Key struct {
Kty string `json:"kty"`
N string `json:"n"`
E string `json:"e"`
} `json:"key"`
Recoverytoken string `json:"recoveryToken"`
Contact []string `json:"contact"`
}
// RegistrationResource represents all important informations about a registration
// of which the client needs to keep track itself.
type RegistrationResource struct {
Body Registration
URI string
NewAuthzURL string
TosURL string
}

192
cli.go Normal file
View file

@ -0,0 +1,192 @@
package main
import (
"log"
"os"
"github.com/codegangsta/cli"
"github.com/xenolf/lego/acme"
)
// Logger is used to log errors; if nil, the default log.Logger is used.
var Logger *log.Logger
// logger is an helper function to retrieve the available logger
func logger() *log.Logger {
if Logger == nil {
Logger = log.New(os.Stderr, "", log.LstdFlags)
}
return Logger
}
func main() {
app := cli.NewApp()
app.Name = "lego"
app.Usage = "Let's encrypt client to go!"
app.Version = "0.0.1"
app.Commands = []cli.Command{
{
Name: "run",
Usage: "Create and install a certificate",
Action: run,
},
{
Name: "auth",
Usage: "Create a certificate",
Action: func(c *cli.Context) {
logger().Fatal("Not implemented")
},
},
{
Name: "install",
Usage: "Install a certificate",
Action: func(c *cli.Context) {
logger().Fatal("Not implemented")
},
},
{
Name: "revoke",
Usage: "Revoke a certificate",
Action: func(c *cli.Context) {
logger().Fatal("Not implemented")
},
Flags: []cli.Flag{
cli.StringFlag{
Name: "certificate",
Usage: "Revoke a specific certificate",
},
cli.StringFlag{
Name: "key",
Usage: "Revoke all certs generated by the provided authorized key.",
},
},
},
{
Name: "rollback",
Usage: "Rollback a certificate",
Action: func(c *cli.Context) {
logger().Fatal("Not implemented")
},
Flags: []cli.Flag{
cli.IntFlag{
Name: "checkpoints",
Usage: "Revert configuration N number of checkpoints",
},
},
},
}
app.Flags = []cli.Flag{
cli.StringSliceFlag{
Name: "domains, d",
Usage: "Add domains to the process",
},
cli.StringFlag{
Name: "server, s",
Value: "https://www.letsencrypt-demo.org/acme/new-reg",
Usage: "CA hostname (and optionally :port). The server certificate must be trusted in order to avoid further modifications to the client.",
},
cli.StringFlag{
Name: "authkey, k",
Usage: "Path to the authorized key file",
},
cli.StringFlag{
Name: "email, m",
Usage: "Email used for registration and recovery contact.",
},
cli.IntFlag{
Name: "rsa-key-size, B",
Value: 2048,
Usage: "Size of the RSA key.",
},
cli.BoolFlag{
Name: "no-confirm",
Usage: "Turn off confirmation screens.",
},
cli.BoolFlag{
Name: "agree-tos, e",
Usage: "Skip the end user license agreement screen.",
},
cli.StringFlag{
Name: "config-dir",
Value: configDir,
Usage: "Configuration directory.",
},
cli.StringFlag{
Name: "work-dir",
Value: workDir,
Usage: "Working directory.",
},
cli.StringFlag{
Name: "backup-dir",
Value: backupDir,
Usage: "Configuration backups directory.",
},
cli.StringFlag{
Name: "key-dir",
Value: keyDir,
Usage: "Keys storage.",
},
cli.StringFlag{
Name: "cert-dir",
Value: certDir,
Usage: "Certificates storage.",
},
}
app.Run(os.Args)
}
func checkFolder(path string) error {
if _, err := os.Stat(path); os.IsNotExist(err) {
return os.MkdirAll(path, 0700)
}
return nil
}
func run(c *cli.Context) {
err := checkFolder(c.GlobalString("config-dir"))
if err != nil {
logger().Fatalf("Cound not check/create path: %v", err)
}
conf := NewConfiguration(c)
//TODO: move to account struct? Currently MUST pass email.
if !c.GlobalIsSet("email") {
logger().Fatal("You have to pass an account (email address) to the program using --email or -m")
}
acc := NewAccount(c.GlobalString("email"), conf)
client := acme.NewClient(c.GlobalString("server"), acc)
if acc.Registration == nil {
reg, err := client.Register()
if err != nil {
logger().Fatalf("Could not complete registration -> %v", err)
}
acc.Registration = reg
acc.Save()
logger().Print("!!!! HEADS UP !!!!")
logger().Printf(`
Your account credentials have been saved in your Let's Encrypt
configuration directory at "%s".
You should make a secure backup of this folder now. This
configuration directory will also contain certificates and
private keys obtained from Let's Encrypt so making regular
backups of this folder is ideal.
If you lose your account credentials, you can recover
them using the token
"%s".
You must write that down and put it in a safe place.`, c.GlobalString("config-dir"), reg.Body.Recoverytoken)
}
if !c.GlobalIsSet("domains") {
logger().Fatal("Please specify --domains")
}
}

361
cli/main_unix.go Normal file
View file

@ -0,0 +1,361 @@
package cli
import (
"bytes"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/rsa"
"crypto/tls"
"crypto/x509"
"crypto/x509/pkix"
"encoding/base64"
"encoding/json"
"encoding/pem"
"flag"
"io/ioutil"
"log"
"math/big"
"net/http"
"os"
"strings"
"time"
"github.com/square/go-jose"
)
var (
newReg = flag.String("new-reg", "http://192.168.10.22:4000/acme/new-reg", "New Registration URL")
accountKeyFile = flag.String("certKey", "account.key", "Private key file. Created if it does not exist.")
accountTmpCrtFile = flag.String("acc-crt-file", "account-tmp.pem", "Temporary self signed certificate for challenges.")
email = flag.String("email", "", "Email address used for certificate retrieval.")
domain = flag.String("domain", "", "The domain to request a certificate for")
ecdsaCurve = flag.String("ecdsa-curve", "", "ECDSA curve to use to generate a key. Valid values are P224, P256, P384, P521")
bits = flag.Int("bits", 2048, "The size of the RSA keys in bits. Default 4096")
)
// Logger is used to log errors; if nil, the default log.Logger is used.
var Logger *log.Logger
// logger is an helper function to retrieve the available logger
func logger() *log.Logger {
if Logger == nil {
Logger = log.New(os.Stderr, "", log.LstdFlags)
}
return Logger
}
type Registration struct {
Contact []string `json:"contact"`
}
type Tos struct {
Agreement string `json:"agreement"`
}
type GetChallenges struct {
Identifier `json:"identifier"`
}
type Identifier struct {
Type string `json:"type"`
Value string `json:"value"`
}
type ChallengesResponse struct {
Identifier `json:"identifier"`
Status string `json:"status"`
Expires time.Time `json:"expires"`
Challenges []Challenge `json:"challenges"`
Combinations [][]int `json:"combinations"`
}
type Challenge struct {
Type string `json:"type"`
Status string `json:"status"`
URI string `json:"uri"`
Token string `json:"token"`
}
type SimpleHttpsMessage struct {
Path string `json:"path"`
}
type CsrMessage struct {
Csr string `json:"csr"`
Authorizations []string `json:"authorizations"`
}
func execute() {
flag.Parse()
accountKey := generateKeyPair(*accountKeyFile).(*rsa.PrivateKey)
jsonBytes, _ := json.Marshal(Registration{Contact: []string{"mailto:" + *email}})
logger().Printf("Posting registration to %s", *newReg)
resp, _ := jwsPost(*newReg, jsonBytes)
links := parseLinks(resp.Header["Link"])
if links["next"] == "" {
logger().Fatalln("The server did not provide enough information to proceed.")
}
logger().Printf("Got agreement URL: %s", links["terms-of-service"])
logger().Printf("Got new auth URL: %s", links["next"])
jsonBytes, _ = json.Marshal(Tos{Agreement: links["terms-of-service"]})
logger().Printf("Posting agreement to %s", resp.Header.Get("Location"))
resp, _ = jwsPost(resp.Header.Get("Location"), jsonBytes)
logResponse(resp)
jsonBytes, _ = json.Marshal(GetChallenges{Identifier{Type: "dns", Value: *domain}})
logger().Printf("Getting challenges for type %s and domain %s", "dns", *domain)
resp, _ = jwsPost(links["next"], jsonBytes)
logResponse(resp)
links = parseLinks(resp.Header["Link"])
if links["next"] == "" {
logger().Fatalln("The server did not provide enough information to proceed.")
}
logger().Printf("Got new cert URL: %s", links["next"])
logger().Printf("Got new authorization URL: %s", resp.Header.Get("Location"))
newCertUrl := links["next"]
authUrl := resp.Header.Get("Location")
body, _ := ioutil.ReadAll(resp.Body)
var challenges ChallengesResponse
_ = json.Unmarshal(body, &challenges)
for _, challenge := range challenges.Challenges {
logger().Printf("Got challenge %s", challenge.Type)
}
logger().Printf("Challenge combinations are: %v", challenges.Combinations)
logger().Printf("Choosing first challenge combination and starting with %s", challenges.Challenges[challenges.Combinations[0][0]].Type)
firstChallenge := challenges.Challenges[challenges.Combinations[0][0]]
if firstChallenge.Type == "simpleHttps" {
generateSelfSignedCert(accountKey)
path := getRandomString(8) + ".txt"
challengePath := "/.well-known/acme-challenge/" + path
startChallengeTlsServer(challengePath, firstChallenge.Token)
logger().Print("Waiting for domain validation...")
jsonBytes, _ = json.Marshal(SimpleHttpsMessage{Path: path})
logger().Printf("Sending challenge response for path %s", path)
resp, _ = jwsPost(firstChallenge.URI, jsonBytes)
logResponse(resp)
// Loop until status is verified or error.
var challengeResponse Challenge
loop:
for {
decoder := json.NewDecoder(resp.Body)
decoder.Decode(&challengeResponse)
switch challengeResponse.Status {
case "valid":
logger().Print("The CA validated our credentials. Continue...")
break loop
case "pending":
logger().Print("The data is still being validated. Please stand by...")
case "invalid":
logger().Fatalf("The CA could not validate the provided file. - %v", challengeResponse)
default:
logger().Fatalf("The CA returned an unexpected state. - %v", challengeResponse)
}
time.Sleep(1000 * time.Millisecond)
resp, _ = http.Get(authUrl)
}
}
logger().Print("Getting certificate...")
privateSslKey := generateKeyPair("ssl-priv.key")
csr := generateCsr(privateSslKey)
csrString := base64.URLEncoding.EncodeToString(csr)
jsonBytes, _ = json.Marshal(CsrMessage{Csr: csrString, Authorizations: []string{authUrl}})
resp, _ = jwsPost(newCertUrl, jsonBytes)
logResponse(resp)
body, _ = ioutil.ReadAll(resp.Body)
ioutil.WriteFile("ssl-crt.crt", body, 0644)
}
func startChallengeTlsServer(path string, token string) {
cert, err := tls.LoadX509KeyPair(*accountTmpCrtFile, *accountKeyFile)
tlsConf := new(tls.Config)
tlsConf.Certificates = []tls.Certificate{cert}
tlsListener, err := tls.Listen("tcp", ":443", tlsConf)
if err != nil {
logger().Fatalf("Could not start TLS listener on %s for challenge handling! - %v", ":443", err)
}
http.HandleFunc(path, func(w http.ResponseWriter, r *http.Request) {
if r.Host == *domain && r.Method == "GET" {
w.Write([]byte(token))
tlsListener.Close()
}
})
srv := http.Server{Addr: ":443", Handler: nil}
go func() {
srv.Serve(tlsListener)
logger().Print("TLS Server exited.")
}()
}
func getRandomString(length int) string {
const alphanum = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
var bytes = make([]byte, length)
rand.Read(bytes)
for i, b := range bytes {
bytes[i] = alphanum[b%byte(len(alphanum))]
}
return string(bytes)
}
func logResponse(resp *http.Response) {
logger().Println(resp.Status)
for k, v := range resp.Header {
logger().Printf("-- %s: %s", k, v)
}
}
func jwsPost(url string, content []byte) (*http.Response, error) {
url = strings.Replace(url, "localhost", "192.168.10.22", -1)
privKeyBytes, err := ioutil.ReadFile(*accountKeyFile)
key, err := jose.LoadPrivateKey(privKeyBytes)
if err != nil {
panic(err)
}
signer, err := jose.NewSigner(jose.RS256, key)
if err != nil {
panic(err)
}
signed, err := signer.Sign(content)
if err != nil {
panic(err)
}
signedContent := signed.FullSerialize()
resp, err := http.Post(url, "application/json", bytes.NewBuffer([]byte(signedContent)))
if err != nil {
logger().Fatalf("Error posting content: %s", err)
}
return resp, err
}
func generateCsr(privateKey interface{}) []byte {
template := x509.CertificateRequest{
Subject: pkix.Name{
CommonName: *domain,
},
EmailAddresses: []string{*email},
}
csrBytes, err := x509.CreateCertificateRequest(rand.Reader, &template, privateKey)
csrOut, err := os.Create("csr.pem")
if err != nil {
logger().Fatalf("Could not create certificate request file: %s", err)
}
pem.Encode(csrOut, &pem.Block{Type: "CERTIFICATE REQUEST", Bytes: csrBytes})
return csrBytes
}
func generateKeyPair(fileName string) interface{} {
logger().Println("Generating key pair ...")
var privateKey interface{}
var err error
switch *ecdsaCurve {
case "":
privateKey, err = rsa.GenerateKey(rand.Reader, *bits)
case "P224":
privateKey, err = ecdsa.GenerateKey(elliptic.P224(), rand.Reader)
case "P256":
privateKey, err = ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
case "P384":
privateKey, err = ecdsa.GenerateKey(elliptic.P384(), rand.Reader)
case "P521":
privateKey, err = ecdsa.GenerateKey(elliptic.P521(), rand.Reader)
}
if err != nil {
logger().Fatalf("Failed to generate private key: %s", err)
}
var pemKey pem.Block
switch key := privateKey.(type) {
case *rsa.PrivateKey:
pemKey = pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(key)}
case *ecdsa.PrivateKey:
privateBytes, err := x509.MarshalECPrivateKey(key)
if err != nil {
logger().Fatalf("Could not marshal ECDSA private key: %v", err)
}
pemKey = pem.Block{Type: "EC PRIVATE KEY", Bytes: privateBytes}
}
certOut, err := os.Create(fileName)
if err != nil {
logger().Fatalf("Could not create private key file: %s", err)
}
pem.Encode(certOut, &pemKey)
certOut.Close()
return privateKey
}
func generateSelfSignedCert(privKey *rsa.PrivateKey) {
serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
if err != nil {
log.Fatalf("failed to generate serial number: %s", err)
}
template := x509.Certificate{
SerialNumber: serialNumber,
Subject: pkix.Name{
CommonName: *email,
Organization: []string{*domain},
},
NotBefore: time.Now(),
NotAfter: time.Now().Add(365),
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
BasicConstraintsValid: true,
DNSNames: []string{*domain},
IsCA: true,
}
derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, &privKey.PublicKey, privKey)
if err != nil {
log.Fatalf("Failed to create certificate: %s", err)
}
certOut, err := os.Create(*accountTmpCrtFile)
if err != nil {
log.Fatalf("failed to open cert.pem for writing: %s", err)
}
pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes})
certOut.Close()
}

48
configuration.go Normal file
View file

@ -0,0 +1,48 @@
package main
import (
"net/url"
"os"
"path"
"strings"
"github.com/codegangsta/cli"
)
// Configuration type from CLI and config files.
type Configuration struct {
context *cli.Context
}
// NewConfiguration creates a new configuration from CLI data.
func NewConfiguration(c *cli.Context) *Configuration {
return &Configuration{context: c}
}
// RsaBits returns the current set RSA bit length for private keys
func (c *Configuration) RsaBits() int {
return c.context.GlobalInt("rsa-key-size")
}
// ServerPath returns the OS dependent path to the data for a specific CA
func (c *Configuration) ServerPath() string {
srv, _ := url.Parse(c.context.GlobalString("server"))
srvStr := strings.Replace(srv.Host, ":", "_", -1) + srv.Path
return strings.Replace(srvStr, "/", string(os.PathSeparator), -1)
}
// AccountsPath returns the OS dependent path to the
// local accounts for a specific CA
func (c *Configuration) AccountsPath() string {
return path.Join(c.context.GlobalString("config-dir"), "accounts", c.ServerPath())
}
// AccountPath returns the OS dependent path to a particular account
func (c *Configuration) AccountPath(acc string) string {
return path.Join(c.AccountsPath(), acc)
}
// AccountPath returns the OS dependent path to the keys of a particular account
func (c *Configuration) AccountKeysPath(acc string) string {
return path.Join(c.AccountPath(acc), "keys")
}

39
crypto.go Normal file
View file

@ -0,0 +1,39 @@
package main
import (
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"encoding/pem"
"io/ioutil"
"os"
)
func generateRsaKey(length int, file string) (*rsa.PrivateKey, error) {
privateKey, err := rsa.GenerateKey(rand.Reader, length)
if err != nil {
return nil, err
}
pemKey := pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(privateKey)}
certOut, err := os.Create(file)
if err != nil {
return nil, err
}
pem.Encode(certOut, &pemKey)
certOut.Close()
return privateKey, nil
}
func loadRsaKey(file string) (*rsa.PrivateKey, error) {
keyBytes, err := ioutil.ReadFile(file)
if err != nil {
return nil, err
}
keyBlock, _ := pem.Decode(keyBytes)
return x509.ParsePKCS1PrivateKey(keyBlock.Bytes)
}

11
path_unix.go Normal file
View file

@ -0,0 +1,11 @@
// +build !windows
package main
var (
configDir = "/etc/letsencrypt"
workDir = "/var/lib/letsencrypt"
backupDir = "/var/lib/letsencrypt/backups"
keyDir = "/etc/letsencrypt/keys"
certDir = "/etc/letsencrypt/certs"
)

13
path_windows.go Normal file
View file

@ -0,0 +1,13 @@
// +build !linux
package main
import "os"
var (
configDir = os.ExpandEnv("${PROGRAMDATA}\\letsencrypt")
workDir = os.ExpandEnv("${PROGRAMDATA}\\letsencrypt")
backupDir = os.ExpandEnv("${PROGRAMDATA}\\letsencrypt\\backups")
keyDir = os.ExpandEnv("${PROGRAMDATA}\\letsencrypt\\keys")
certDir = os.ExpandEnv("${PROGRAMDATA}\\letsencrypt\\certs")
)