2015-10-18 10:17:24 +08:00
|
|
|
package letsencrypt
|
|
|
|
|
|
|
|
import (
|
2015-10-18 10:44:33 +08:00
|
|
|
"bufio"
|
2015-10-18 10:17:24 +08:00
|
|
|
"crypto/rand"
|
|
|
|
"crypto/rsa"
|
|
|
|
"encoding/json"
|
|
|
|
"errors"
|
2015-10-18 10:44:33 +08:00
|
|
|
"fmt"
|
2015-10-18 10:17:24 +08:00
|
|
|
"io/ioutil"
|
|
|
|
"os"
|
2015-10-18 10:44:33 +08:00
|
|
|
"strings"
|
2015-10-18 10:17:24 +08:00
|
|
|
|
2015-10-18 10:44:33 +08:00
|
|
|
"github.com/mholt/caddy/server"
|
2015-10-18 10:17:24 +08:00
|
|
|
"github.com/xenolf/lego/acme"
|
|
|
|
)
|
|
|
|
|
|
|
|
type User struct {
|
|
|
|
Email string
|
|
|
|
Registration *acme.RegistrationResource
|
|
|
|
KeyFile string
|
|
|
|
key *rsa.PrivateKey
|
|
|
|
}
|
|
|
|
|
|
|
|
func (u User) GetEmail() string {
|
|
|
|
return u.Email
|
|
|
|
}
|
|
|
|
func (u User) GetRegistration() *acme.RegistrationResource {
|
|
|
|
return u.Registration
|
|
|
|
}
|
|
|
|
func (u User) GetPrivateKey() *rsa.PrivateKey {
|
|
|
|
return u.key
|
|
|
|
}
|
|
|
|
|
|
|
|
// getUser loads the user with the given email from disk.
|
2015-10-18 10:44:33 +08:00
|
|
|
// If the user does not exist, it will create a new one.
|
2015-10-18 10:17:24 +08:00
|
|
|
func getUser(email string) (User, error) {
|
|
|
|
var user User
|
|
|
|
|
|
|
|
// open user file
|
|
|
|
regFile, err := os.Open(storage.UserRegFile(email))
|
|
|
|
if err != nil {
|
|
|
|
if os.IsNotExist(err) {
|
|
|
|
// create a new user
|
|
|
|
return newUser(email)
|
|
|
|
}
|
|
|
|
return user, err
|
|
|
|
}
|
|
|
|
defer regFile.Close()
|
|
|
|
|
|
|
|
// load user information
|
|
|
|
err = json.NewDecoder(regFile).Decode(&user)
|
|
|
|
if err != nil {
|
|
|
|
return user, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// load their private key
|
|
|
|
user.key, err = loadRSAPrivateKey(user.KeyFile)
|
|
|
|
if err != nil {
|
|
|
|
return user, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return user, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// saveUser persists a user's key and account registration
|
2015-10-18 10:44:33 +08:00
|
|
|
// to the file system. It does NOT register the user via ACME.
|
2015-10-18 10:17:24 +08:00
|
|
|
func saveUser(user User) error {
|
|
|
|
// make user account folder
|
|
|
|
err := os.MkdirAll(storage.User(user.Email), 0700)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// save private key file
|
|
|
|
user.KeyFile = storage.UserKeyFile(user.Email)
|
|
|
|
err = saveRSAPrivateKey(user.key, user.KeyFile)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// save registration file
|
|
|
|
jsonBytes, err := json.MarshalIndent(&user, "", "\t")
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
return ioutil.WriteFile(storage.UserRegFile(user.Email), jsonBytes, 0600)
|
|
|
|
}
|
|
|
|
|
|
|
|
// newUser creates a new User for the given email address
|
2015-10-18 10:44:33 +08:00
|
|
|
// with a new private key. This function does NOT save the
|
|
|
|
// user to disk or register it via ACME. If you want to use
|
|
|
|
// a user account that might already exist, call getUser
|
|
|
|
// instead.
|
2015-10-18 10:17:24 +08:00
|
|
|
func newUser(email string) (User, error) {
|
|
|
|
user := User{Email: email}
|
|
|
|
privateKey, err := rsa.GenerateKey(rand.Reader, rsaKeySize)
|
|
|
|
if err != nil {
|
|
|
|
return user, errors.New("error generating private key: " + err.Error())
|
|
|
|
}
|
|
|
|
user.key = privateKey
|
|
|
|
return user, nil
|
|
|
|
}
|
2015-10-18 10:44:33 +08:00
|
|
|
|
|
|
|
// getEmail does everything it can to obtain an email
|
|
|
|
// address from the user to use for TLS for cfg. If it
|
|
|
|
// cannot get an email address, it returns empty string.
|
|
|
|
func getEmail(cfg server.Config) string {
|
|
|
|
// First try the tls directive from the Caddyfile
|
|
|
|
leEmail := cfg.TLS.LetsEncryptEmail
|
|
|
|
if leEmail == "" {
|
|
|
|
// Then try memory (command line flag or typed by user previously)
|
|
|
|
leEmail = DefaultEmail
|
|
|
|
}
|
|
|
|
if leEmail == "" {
|
|
|
|
// Then try to get most recent user email ~/.caddy/users file
|
|
|
|
// TODO: Probably better to open the user's json file and read the email out of there...
|
|
|
|
userDirs, err := ioutil.ReadDir(storage.Users())
|
|
|
|
if err == nil {
|
|
|
|
var mostRecent os.FileInfo
|
|
|
|
for _, dir := range userDirs {
|
|
|
|
if !dir.IsDir() {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
if mostRecent == nil || dir.ModTime().After(mostRecent.ModTime()) {
|
|
|
|
mostRecent = dir
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if mostRecent != nil {
|
|
|
|
leEmail = mostRecent.Name()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if leEmail == "" {
|
|
|
|
// Alas, we must bother the user and ask for an email address
|
|
|
|
reader := bufio.NewReader(os.Stdin)
|
|
|
|
fmt.Print("Email address: ") // TODO: More explanation probably, and show ToS?
|
|
|
|
var err error
|
|
|
|
leEmail, err = reader.ReadString('\n')
|
|
|
|
if err != nil {
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
DefaultEmail = leEmail
|
|
|
|
}
|
|
|
|
return strings.TrimSpace(leEmail)
|
|
|
|
}
|