forked from TrueCloudLab/lego
Merge remote-tracking branch 'upstream/master'
This commit is contained in:
commit
b5e0b91c05
17 changed files with 158 additions and 84 deletions
16
account.go
16
account.go
|
@ -1,7 +1,7 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/rsa"
|
"crypto"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
|
@ -13,7 +13,7 @@ import (
|
||||||
// Account represents a users local saved credentials
|
// Account represents a users local saved credentials
|
||||||
type Account struct {
|
type Account struct {
|
||||||
Email string `json:"email"`
|
Email string `json:"email"`
|
||||||
key *rsa.PrivateKey
|
key crypto.PrivateKey
|
||||||
Registration *acme.RegistrationResource `json:"registration"`
|
Registration *acme.RegistrationResource `json:"registration"`
|
||||||
|
|
||||||
conf *Configuration
|
conf *Configuration
|
||||||
|
@ -28,16 +28,18 @@ func NewAccount(email string, conf *Configuration) *Account {
|
||||||
logger().Fatalf("Could not check/create directory for account %s: %v", email, err)
|
logger().Fatalf("Could not check/create directory for account %s: %v", email, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var privKey *rsa.PrivateKey
|
var privKey crypto.PrivateKey
|
||||||
if _, err := os.Stat(accKeyPath); os.IsNotExist(err) {
|
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)
|
logger().Printf("No key found for account %s. Generating a curve P384 EC key.", email)
|
||||||
|
privKey, err = generatePrivateKey(accKeyPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger().Fatalf("Could not generate RSA private account key for account %s: %v", email, err)
|
logger().Fatalf("Could not generate RSA private account key for account %s: %v", email, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
logger().Printf("Saved key to %s", accKeyPath)
|
logger().Printf("Saved key to %s", accKeyPath)
|
||||||
} else {
|
} else {
|
||||||
privKey, err = loadRsaKey(accKeyPath)
|
privKey, err = loadPrivateKey(accKeyPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger().Fatalf("Could not load RSA private key from file %s: %v", accKeyPath, err)
|
logger().Fatalf("Could not load RSA private key from file %s: %v", accKeyPath, err)
|
||||||
}
|
}
|
||||||
|
@ -73,7 +75,7 @@ func (a *Account) GetEmail() string {
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetPrivateKey returns the private RSA account key.
|
// GetPrivateKey returns the private RSA account key.
|
||||||
func (a *Account) GetPrivateKey() *rsa.PrivateKey {
|
func (a *Account) GetPrivateKey() crypto.PrivateKey {
|
||||||
return a.key
|
return a.key
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,6 @@ package acme
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto"
|
"crypto"
|
||||||
"crypto/rsa"
|
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
@ -38,7 +37,7 @@ func logf(format string, args ...interface{}) {
|
||||||
type User interface {
|
type User interface {
|
||||||
GetEmail() string
|
GetEmail() string
|
||||||
GetRegistration() *RegistrationResource
|
GetRegistration() *RegistrationResource
|
||||||
GetPrivateKey() *rsa.PrivateKey
|
GetPrivateKey() crypto.PrivateKey
|
||||||
}
|
}
|
||||||
|
|
||||||
// Interface for all challenge solvers to implement.
|
// Interface for all challenge solvers to implement.
|
||||||
|
@ -53,7 +52,7 @@ type Client struct {
|
||||||
directory directory
|
directory directory
|
||||||
user User
|
user User
|
||||||
jws *jws
|
jws *jws
|
||||||
keyBits int
|
keyType KeyType
|
||||||
issuerCert []byte
|
issuerCert []byte
|
||||||
solvers map[Challenge]solver
|
solvers map[Challenge]solver
|
||||||
}
|
}
|
||||||
|
@ -61,16 +60,12 @@ type Client struct {
|
||||||
// NewClient creates a new ACME client on behalf of the user. The client will depend on
|
// NewClient creates a new ACME client on behalf of the user. The client will depend on
|
||||||
// the ACME directory located at caDirURL for the rest of its actions. It will
|
// the ACME directory located at caDirURL for the rest of its actions. It will
|
||||||
// generate private keys for certificates of size keyBits.
|
// generate private keys for certificates of size keyBits.
|
||||||
func NewClient(caDirURL string, user User, keyBits int) (*Client, error) {
|
func NewClient(caDirURL string, user User, keyType KeyType) (*Client, error) {
|
||||||
privKey := user.GetPrivateKey()
|
privKey := user.GetPrivateKey()
|
||||||
if privKey == nil {
|
if privKey == nil {
|
||||||
return nil, errors.New("private key was nil")
|
return nil, errors.New("private key was nil")
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := privKey.Validate(); err != nil {
|
|
||||||
return nil, fmt.Errorf("invalid private key: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
var dir directory
|
var dir directory
|
||||||
if _, err := getJSON(caDirURL, &dir); err != nil {
|
if _, err := getJSON(caDirURL, &dir); err != nil {
|
||||||
return nil, fmt.Errorf("get directory at '%s': %v", caDirURL, err)
|
return nil, fmt.Errorf("get directory at '%s': %v", caDirURL, err)
|
||||||
|
@ -98,7 +93,7 @@ func NewClient(caDirURL string, user User, keyBits int) (*Client, error) {
|
||||||
solvers[HTTP01] = &httpChallenge{jws: jws, validate: validate, provider: &HTTPProviderServer{}}
|
solvers[HTTP01] = &httpChallenge{jws: jws, validate: validate, provider: &HTTPProviderServer{}}
|
||||||
solvers[TLSSNI01] = &tlsSNIChallenge{jws: jws, validate: validate, provider: &TLSProviderServer{}}
|
solvers[TLSSNI01] = &tlsSNIChallenge{jws: jws, validate: validate, provider: &TLSProviderServer{}}
|
||||||
|
|
||||||
return &Client{directory: dir, user: user, jws: jws, keyBits: keyBits, solvers: solvers}, nil
|
return &Client{directory: dir, user: user, jws: jws, keyType: keyType, solvers: solvers}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetChallengeProvider specifies a custom provider that will make the solution available
|
// SetChallengeProvider specifies a custom provider that will make the solution available
|
||||||
|
@ -197,8 +192,10 @@ func (c *Client) Register() (*RegistrationResource, error) {
|
||||||
// AgreeToTOS updates the Client registration and sends the agreement to
|
// AgreeToTOS updates the Client registration and sends the agreement to
|
||||||
// the server.
|
// the server.
|
||||||
func (c *Client) AgreeToTOS() error {
|
func (c *Client) AgreeToTOS() error {
|
||||||
c.user.GetRegistration().Body.Agreement = c.user.GetRegistration().TosURL
|
reg := c.user.GetRegistration()
|
||||||
c.user.GetRegistration().Body.Resource = "reg"
|
|
||||||
|
reg.Body.Agreement = c.user.GetRegistration().TosURL
|
||||||
|
reg.Body.Resource = "reg"
|
||||||
_, err := postJSON(c.jws, c.user.GetRegistration().URI, c.user.GetRegistration().Body, nil)
|
_, err := postJSON(c.jws, c.user.GetRegistration().URI, c.user.GetRegistration().Body, nil)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -456,7 +453,7 @@ func (c *Client) requestCertificate(authz []authorizationResource, bundle bool,
|
||||||
commonName := authz[0]
|
commonName := authz[0]
|
||||||
var err error
|
var err error
|
||||||
if privKey == nil {
|
if privKey == nil {
|
||||||
privKey, err = generatePrivateKey(rsakey, c.keyBits)
|
privKey, err = generatePrivateKey(c.keyType)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return CertificateResource{}, err
|
return CertificateResource{}, err
|
||||||
}
|
}
|
||||||
|
@ -470,7 +467,7 @@ func (c *Client) requestCertificate(authz []authorizationResource, bundle bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: should the CSR be customizable?
|
// TODO: should the CSR be customizable?
|
||||||
csr, err := generateCsr(privKey.(*rsa.PrivateKey), commonName.Domain, san)
|
csr, err := generateCsr(privKey, commonName.Domain, san)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return CertificateResource{}, err
|
return CertificateResource{}, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package acme
|
package acme
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto"
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"crypto/rsa"
|
"crypto/rsa"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
@ -13,6 +14,7 @@ import (
|
||||||
|
|
||||||
func TestNewClient(t *testing.T) {
|
func TestNewClient(t *testing.T) {
|
||||||
keyBits := 32 // small value keeps test fast
|
keyBits := 32 // small value keeps test fast
|
||||||
|
keyType := RSA2048
|
||||||
key, err := rsa.GenerateKey(rand.Reader, keyBits)
|
key, err := rsa.GenerateKey(rand.Reader, keyBits)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal("Could not generate test key:", err)
|
t.Fatal("Could not generate test key:", err)
|
||||||
|
@ -28,7 +30,7 @@ func TestNewClient(t *testing.T) {
|
||||||
w.Write(data)
|
w.Write(data)
|
||||||
}))
|
}))
|
||||||
|
|
||||||
client, err := NewClient(ts.URL, user, keyBits)
|
client, err := NewClient(ts.URL, user, keyType)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Could not create client: %v", err)
|
t.Fatalf("Could not create client: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -40,8 +42,8 @@ func TestNewClient(t *testing.T) {
|
||||||
t.Errorf("Expected jws.privKey to be %p but was %p", expected, actual)
|
t.Errorf("Expected jws.privKey to be %p but was %p", expected, actual)
|
||||||
}
|
}
|
||||||
|
|
||||||
if client.keyBits != keyBits {
|
if client.keyType != keyType {
|
||||||
t.Errorf("Expected keyBits to be %d but was %d", keyBits, client.keyBits)
|
t.Errorf("Expected keyType to be %s but was %s", keyType, client.keyType)
|
||||||
}
|
}
|
||||||
|
|
||||||
if expected, actual := 2, len(client.solvers); actual != expected {
|
if expected, actual := 2, len(client.solvers); actual != expected {
|
||||||
|
@ -68,7 +70,7 @@ func TestClientOptPort(t *testing.T) {
|
||||||
|
|
||||||
optPort := "1234"
|
optPort := "1234"
|
||||||
optHost := ""
|
optHost := ""
|
||||||
client, err := NewClient(ts.URL, user, keyBits)
|
client, err := NewClient(ts.URL, user, RSA2048)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Could not create client: %v", err)
|
t.Fatalf("Could not create client: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -140,8 +142,8 @@ func TestValidate(t *testing.T) {
|
||||||
}))
|
}))
|
||||||
defer ts.Close()
|
defer ts.Close()
|
||||||
|
|
||||||
privKey, _ := generatePrivateKey(rsakey, 512)
|
privKey, _ := rsa.GenerateKey(rand.Reader, 512)
|
||||||
j := &jws{privKey: privKey.(*rsa.PrivateKey), directoryURL: ts.URL}
|
j := &jws{privKey: privKey, directoryURL: ts.URL}
|
||||||
|
|
||||||
tsts := []struct {
|
tsts := []struct {
|
||||||
name string
|
name string
|
||||||
|
@ -193,4 +195,4 @@ type mockUser struct {
|
||||||
|
|
||||||
func (u mockUser) GetEmail() string { return u.email }
|
func (u mockUser) GetEmail() string { return u.email }
|
||||||
func (u mockUser) GetRegistration() *RegistrationResource { return u.regres }
|
func (u mockUser) GetRegistration() *RegistrationResource { return u.regres }
|
||||||
func (u mockUser) GetPrivateKey() *rsa.PrivateKey { return u.privatekey }
|
func (u mockUser) GetPrivateKey() crypto.PrivateKey { return u.privatekey }
|
||||||
|
|
|
@ -23,12 +23,17 @@ import (
|
||||||
"golang.org/x/crypto/ocsp"
|
"golang.org/x/crypto/ocsp"
|
||||||
)
|
)
|
||||||
|
|
||||||
type keyType int
|
// KeyType represents the key algo as well as the key size or curve to use.
|
||||||
|
type KeyType string
|
||||||
type derCertificateBytes []byte
|
type derCertificateBytes []byte
|
||||||
|
|
||||||
|
// Constants for all key types we support.
|
||||||
const (
|
const (
|
||||||
eckey keyType = iota
|
EC256 = KeyType("P256")
|
||||||
rsakey
|
EC384 = KeyType("P384")
|
||||||
|
RSA2048 = KeyType("2048")
|
||||||
|
RSA4096 = KeyType("4096")
|
||||||
|
RSA8192 = KeyType("8192")
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -121,8 +126,16 @@ func GetOCSPForCert(bundle []byte) ([]byte, *ocsp.Response, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func getKeyAuthorization(token string, key interface{}) (string, error) {
|
func getKeyAuthorization(token string, key interface{}) (string, error) {
|
||||||
|
var publicKey crypto.PublicKey
|
||||||
|
switch k := key.(type) {
|
||||||
|
case *ecdsa.PrivateKey:
|
||||||
|
publicKey = k.Public()
|
||||||
|
case *rsa.PrivateKey:
|
||||||
|
publicKey = k.Public()
|
||||||
|
}
|
||||||
|
|
||||||
// Generate the Key Authorization for the challenge
|
// Generate the Key Authorization for the challenge
|
||||||
jwk := keyAsJWK(key)
|
jwk := keyAsJWK(publicKey)
|
||||||
if jwk == nil {
|
if jwk == nil {
|
||||||
return "", errors.New("Could not generate JWK from key.")
|
return "", errors.New("Could not generate JWK from key.")
|
||||||
}
|
}
|
||||||
|
@ -182,18 +195,25 @@ func parsePEMPrivateKey(key []byte) (crypto.PrivateKey, error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func generatePrivateKey(t keyType, keyLength int) (crypto.PrivateKey, error) {
|
func generatePrivateKey(keyType KeyType) (crypto.PrivateKey, error) {
|
||||||
switch t {
|
|
||||||
case eckey:
|
switch keyType {
|
||||||
|
case EC256:
|
||||||
|
return ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||||
|
case EC384:
|
||||||
return ecdsa.GenerateKey(elliptic.P384(), rand.Reader)
|
return ecdsa.GenerateKey(elliptic.P384(), rand.Reader)
|
||||||
case rsakey:
|
case RSA2048:
|
||||||
return rsa.GenerateKey(rand.Reader, keyLength)
|
return rsa.GenerateKey(rand.Reader, 2048)
|
||||||
|
case RSA4096:
|
||||||
|
return rsa.GenerateKey(rand.Reader, 4096)
|
||||||
|
case RSA8192:
|
||||||
|
return rsa.GenerateKey(rand.Reader, 8192)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, fmt.Errorf("Invalid keytype: %d", t)
|
return nil, fmt.Errorf("Invalid KeyType: %s", keyType)
|
||||||
}
|
}
|
||||||
|
|
||||||
func generateCsr(privateKey *rsa.PrivateKey, domain string, san []string) ([]byte, error) {
|
func generateCsr(privateKey crypto.PrivateKey, domain string, san []string) ([]byte, error) {
|
||||||
template := x509.CertificateRequest{
|
template := x509.CertificateRequest{
|
||||||
Subject: pkix.Name{
|
Subject: pkix.Name{
|
||||||
CommonName: domain,
|
CommonName: domain,
|
||||||
|
@ -210,6 +230,9 @@ func generateCsr(privateKey *rsa.PrivateKey, domain string, san []string) ([]byt
|
||||||
func pemEncode(data interface{}) []byte {
|
func pemEncode(data interface{}) []byte {
|
||||||
var pemBlock *pem.Block
|
var pemBlock *pem.Block
|
||||||
switch key := data.(type) {
|
switch key := data.(type) {
|
||||||
|
case *ecdsa.PrivateKey:
|
||||||
|
keyBytes, _ := x509.MarshalECPrivateKey(key)
|
||||||
|
pemBlock = &pem.Block{Type: "EC PRIVATE KEY", Bytes: keyBytes}
|
||||||
case *rsa.PrivateKey:
|
case *rsa.PrivateKey:
|
||||||
pemBlock = &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(key)}
|
pemBlock = &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(key)}
|
||||||
break
|
break
|
||||||
|
|
|
@ -2,13 +2,14 @@ package acme
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"crypto/rand"
|
||||||
"crypto/rsa"
|
"crypto/rsa"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestGeneratePrivateKey(t *testing.T) {
|
func TestGeneratePrivateKey(t *testing.T) {
|
||||||
key, err := generatePrivateKey(rsakey, 32)
|
key, err := generatePrivateKey(RSA2048)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error("Error generating private key:", err)
|
t.Error("Error generating private key:", err)
|
||||||
}
|
}
|
||||||
|
@ -18,12 +19,12 @@ func TestGeneratePrivateKey(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGenerateCSR(t *testing.T) {
|
func TestGenerateCSR(t *testing.T) {
|
||||||
key, err := generatePrivateKey(rsakey, 512)
|
key, err := rsa.GenerateKey(rand.Reader, 512)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal("Error generating private key:", err)
|
t.Fatal("Error generating private key:", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
csr, err := generateCsr(key.(*rsa.PrivateKey), "fizz.buzz", nil)
|
csr, err := generateCsr(key, "fizz.buzz", nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error("Error generating CSR:", err)
|
t.Error("Error generating CSR:", err)
|
||||||
}
|
}
|
||||||
|
@ -52,7 +53,7 @@ func TestPEMEncode(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPEMCertExpiration(t *testing.T) {
|
func TestPEMCertExpiration(t *testing.T) {
|
||||||
privKey, err := generatePrivateKey(rsakey, 2048)
|
privKey, err := generatePrivateKey(RSA2048)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal("Error generating private key:", err)
|
t.Fatal("Error generating private key:", err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -45,7 +45,7 @@ func (s *dnsChallenge) Solve(chlng challenge, domain string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate the Key Authorization for the challenge
|
// Generate the Key Authorization for the challenge
|
||||||
keyAuth, err := getKeyAuthorization(chlng.Token, &s.jws.privKey.PublicKey)
|
keyAuth, err := getKeyAuthorization(chlng.Token, s.jws.privKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ package acme
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
|
"crypto/rand"
|
||||||
"crypto/rsa"
|
"crypto/rsa"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
|
@ -76,7 +77,7 @@ func TestDNSValidServerResponse(t *testing.T) {
|
||||||
preCheckDNS = func(fqdn, value string) (bool, error) {
|
preCheckDNS = func(fqdn, value string) (bool, error) {
|
||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
privKey, _ := generatePrivateKey(rsakey, 512)
|
privKey, _ := rsa.GenerateKey(rand.Reader, 512)
|
||||||
|
|
||||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
w.Header().Add("Replay-Nonce", "12345")
|
w.Header().Add("Replay-Nonce", "12345")
|
||||||
|
@ -84,7 +85,7 @@ func TestDNSValidServerResponse(t *testing.T) {
|
||||||
}))
|
}))
|
||||||
|
|
||||||
manualProvider, _ := NewDNSProviderManual()
|
manualProvider, _ := NewDNSProviderManual()
|
||||||
jws := &jws{privKey: privKey.(*rsa.PrivateKey), directoryURL: ts.URL}
|
jws := &jws{privKey: privKey, directoryURL: ts.URL}
|
||||||
solver := &dnsChallenge{jws: jws, validate: validate, provider: manualProvider}
|
solver := &dnsChallenge{jws: jws, validate: validate, provider: manualProvider}
|
||||||
clientChallenge := challenge{Type: "dns01", Status: "pending", URI: ts.URL, Token: "http8"}
|
clientChallenge := challenge{Type: "dns01", Status: "pending", URI: ts.URL, Token: "http8"}
|
||||||
|
|
||||||
|
|
|
@ -21,7 +21,7 @@ func (s *httpChallenge) Solve(chlng challenge, domain string) error {
|
||||||
logf("[INFO][%s] acme: Trying to solve HTTP-01", domain)
|
logf("[INFO][%s] acme: Trying to solve HTTP-01", domain)
|
||||||
|
|
||||||
// Generate the Key Authorization for the challenge
|
// Generate the Key Authorization for the challenge
|
||||||
keyAuth, err := getKeyAuthorization(chlng.Token, &s.jws.privKey.PublicKey)
|
keyAuth, err := getKeyAuthorization(chlng.Token, s.jws.privKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package acme
|
package acme
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/rand"
|
||||||
"crypto/rsa"
|
"crypto/rsa"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"strings"
|
"strings"
|
||||||
|
@ -8,8 +9,8 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestHTTPChallenge(t *testing.T) {
|
func TestHTTPChallenge(t *testing.T) {
|
||||||
privKey, _ := generatePrivateKey(rsakey, 512)
|
privKey, _ := rsa.GenerateKey(rand.Reader, 512)
|
||||||
j := &jws{privKey: privKey.(*rsa.PrivateKey)}
|
j := &jws{privKey: privKey}
|
||||||
clientChallenge := challenge{Type: HTTP01, Token: "http1"}
|
clientChallenge := challenge{Type: HTTP01, Token: "http1"}
|
||||||
mockValidate := func(_ *jws, _, _ string, chlng challenge) error {
|
mockValidate := func(_ *jws, _, _ string, chlng challenge) error {
|
||||||
uri := "http://localhost:23457/.well-known/acme-challenge/" + chlng.Token
|
uri := "http://localhost:23457/.well-known/acme-challenge/" + chlng.Token
|
||||||
|
@ -43,8 +44,8 @@ func TestHTTPChallenge(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestHTTPChallengeInvalidPort(t *testing.T) {
|
func TestHTTPChallengeInvalidPort(t *testing.T) {
|
||||||
privKey, _ := generatePrivateKey(rsakey, 128)
|
privKey, _ := rsa.GenerateKey(rand.Reader, 128)
|
||||||
j := &jws{privKey: privKey.(*rsa.PrivateKey)}
|
j := &jws{privKey: privKey}
|
||||||
clientChallenge := challenge{Type: HTTP01, Token: "http2"}
|
clientChallenge := challenge{Type: HTTP01, Token: "http2"}
|
||||||
solver := &httpChallenge{jws: j, validate: stubValidate, provider: &HTTPProviderServer{port: "123456"}}
|
solver := &httpChallenge{jws: j, validate: stubValidate, provider: &HTTPProviderServer{port: "123456"}}
|
||||||
|
|
||||||
|
|
20
acme/jws.go
20
acme/jws.go
|
@ -2,7 +2,9 @@ package acme
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"crypto"
|
||||||
"crypto/ecdsa"
|
"crypto/ecdsa"
|
||||||
|
"crypto/elliptic"
|
||||||
"crypto/rsa"
|
"crypto/rsa"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
@ -12,7 +14,7 @@ import (
|
||||||
|
|
||||||
type jws struct {
|
type jws struct {
|
||||||
directoryURL string
|
directoryURL string
|
||||||
privKey *rsa.PrivateKey
|
privKey crypto.PrivateKey
|
||||||
nonces []string
|
nonces []string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -46,8 +48,20 @@ func (j *jws) post(url string, content []byte) (*http.Response, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (j *jws) signContent(content []byte) (*jose.JsonWebSignature, error) {
|
func (j *jws) signContent(content []byte) (*jose.JsonWebSignature, error) {
|
||||||
// TODO: support other algorithms - RS512
|
|
||||||
signer, err := jose.NewSigner(jose.RS256, j.privKey)
|
var alg jose.SignatureAlgorithm
|
||||||
|
switch k := j.privKey.(type) {
|
||||||
|
case *rsa.PrivateKey:
|
||||||
|
alg = jose.RS256
|
||||||
|
case *ecdsa.PrivateKey:
|
||||||
|
if k.Curve == elliptic.P256() {
|
||||||
|
alg = jose.ES256
|
||||||
|
} else if k.Curve == elliptic.P384() {
|
||||||
|
alg = jose.ES384
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
signer, err := jose.NewSigner(alg, j.privKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,17 +28,13 @@ type registrationMessage struct {
|
||||||
// Registration is returned by the ACME server after the registration
|
// Registration is returned by the ACME server after the registration
|
||||||
// The client implementation should save this registration somewhere.
|
// The client implementation should save this registration somewhere.
|
||||||
type Registration struct {
|
type Registration struct {
|
||||||
Resource string `json:"resource,omitempty"`
|
Resource string `json:"resource,omitempty"`
|
||||||
ID int `json:"id"`
|
ID int `json:"id"`
|
||||||
Key struct {
|
Key jose.JsonWebKey `json:"key"`
|
||||||
Kty string `json:"kty"`
|
Contact []string `json:"contact"`
|
||||||
N string `json:"n"`
|
Agreement string `json:"agreement,omitempty"`
|
||||||
E string `json:"e"`
|
Authorizations string `json:"authorizations,omitempty"`
|
||||||
} `json:"key"`
|
Certificates string `json:"certificates,omitempty"`
|
||||||
Contact []string `json:"contact"`
|
|
||||||
Agreement string `json:"agreement,omitempty"`
|
|
||||||
Authorizations string `json:"authorizations,omitempty"`
|
|
||||||
Certificates string `json:"certificates,omitempty"`
|
|
||||||
// RecoveryKey recoveryKeyMessage `json:"recoveryKey,omitempty"`
|
// RecoveryKey recoveryKeyMessage `json:"recoveryKey,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -22,7 +22,7 @@ func (t *tlsSNIChallenge) Solve(chlng challenge, domain string) error {
|
||||||
logf("[INFO][%s] acme: Trying to solve TLS-SNI-01", domain)
|
logf("[INFO][%s] acme: Trying to solve TLS-SNI-01", domain)
|
||||||
|
|
||||||
// Generate the Key Authorization for the challenge
|
// Generate the Key Authorization for the challenge
|
||||||
keyAuth, err := getKeyAuthorization(chlng.Token, &t.jws.privKey.PublicKey)
|
keyAuth, err := getKeyAuthorization(chlng.Token, t.jws.privKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -43,7 +43,7 @@ func (t *tlsSNIChallenge) Solve(chlng challenge, domain string) error {
|
||||||
// TLSSNI01ChallengeCert returns a certificate for the `tls-sni-01` challenge
|
// TLSSNI01ChallengeCert returns a certificate for the `tls-sni-01` challenge
|
||||||
func TLSSNI01ChallengeCert(keyAuth string) (tls.Certificate, error) {
|
func TLSSNI01ChallengeCert(keyAuth string) (tls.Certificate, error) {
|
||||||
// generate a new RSA key for the certificates
|
// generate a new RSA key for the certificates
|
||||||
tempPrivKey, err := generatePrivateKey(rsakey, 2048)
|
tempPrivKey, err := generatePrivateKey(RSA2048)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return tls.Certificate{}, err
|
return tls.Certificate{}, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package acme
|
package acme
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/rand"
|
||||||
"crypto/rsa"
|
"crypto/rsa"
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
|
@ -11,8 +12,8 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestTLSSNIChallenge(t *testing.T) {
|
func TestTLSSNIChallenge(t *testing.T) {
|
||||||
privKey, _ := generatePrivateKey(rsakey, 512)
|
privKey, _ := rsa.GenerateKey(rand.Reader, 512)
|
||||||
j := &jws{privKey: privKey.(*rsa.PrivateKey)}
|
j := &jws{privKey: privKey}
|
||||||
clientChallenge := challenge{Type: TLSSNI01, Token: "tlssni1"}
|
clientChallenge := challenge{Type: TLSSNI01, Token: "tlssni1"}
|
||||||
mockValidate := func(_ *jws, _, _ string, chlng challenge) error {
|
mockValidate := func(_ *jws, _, _ string, chlng challenge) error {
|
||||||
conn, err := tls.Dial("tcp", "localhost:23457", &tls.Config{
|
conn, err := tls.Dial("tcp", "localhost:23457", &tls.Config{
|
||||||
|
@ -51,8 +52,8 @@ func TestTLSSNIChallenge(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTLSSNIChallengeInvalidPort(t *testing.T) {
|
func TestTLSSNIChallengeInvalidPort(t *testing.T) {
|
||||||
privKey, _ := generatePrivateKey(rsakey, 128)
|
privKey, _ := rsa.GenerateKey(rand.Reader, 128)
|
||||||
j := &jws{privKey: privKey.(*rsa.PrivateKey)}
|
j := &jws{privKey: privKey}
|
||||||
clientChallenge := challenge{Type: TLSSNI01, Token: "tlssni2"}
|
clientChallenge := challenge{Type: TLSSNI01, Token: "tlssni2"}
|
||||||
solver := &tlsSNIChallenge{jws: j, validate: stubValidate, provider: &TLSProviderServer{port: "123456"}}
|
solver := &tlsSNIChallenge{jws: j, validate: stubValidate, provider: &TLSProviderServer{port: "123456"}}
|
||||||
|
|
||||||
|
|
8
cli.go
8
cli.go
|
@ -102,10 +102,10 @@ func main() {
|
||||||
Name: "accept-tos, a",
|
Name: "accept-tos, a",
|
||||||
Usage: "By setting this flag to true you indicate that you accept the current Let's Encrypt terms of service.",
|
Usage: "By setting this flag to true you indicate that you accept the current Let's Encrypt terms of service.",
|
||||||
},
|
},
|
||||||
cli.IntFlag{
|
cli.StringFlag{
|
||||||
Name: "rsa-key-size, B",
|
Name: "key-type, k",
|
||||||
Value: 2048,
|
Value: "rsa2048",
|
||||||
Usage: "Size of the RSA key.",
|
Usage: "Key type to use for private keys. Supported: rsa2048, rsa4096, rsa8192, ec256, ec384",
|
||||||
},
|
},
|
||||||
cli.StringFlag{
|
cli.StringFlag{
|
||||||
Name: "path",
|
Name: "path",
|
||||||
|
|
|
@ -34,7 +34,12 @@ func setup(c *cli.Context) (*Configuration, *Account, *acme.Client) {
|
||||||
//TODO: move to account struct? Currently MUST pass email.
|
//TODO: move to account struct? Currently MUST pass email.
|
||||||
acc := NewAccount(c.GlobalString("email"), conf)
|
acc := NewAccount(c.GlobalString("email"), conf)
|
||||||
|
|
||||||
client, err := acme.NewClient(c.GlobalString("server"), acc, conf.RsaBits())
|
keyType, err := conf.KeyType()
|
||||||
|
if err != nil {
|
||||||
|
logger().Fatal(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
client, err := acme.NewClient(c.GlobalString("server"), acc, keyType)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger().Fatalf("Could not create client: %s", err.Error())
|
logger().Fatalf("Could not create client: %s", err.Error())
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
|
@ -20,9 +21,22 @@ func NewConfiguration(c *cli.Context) *Configuration {
|
||||||
return &Configuration{context: c}
|
return &Configuration{context: c}
|
||||||
}
|
}
|
||||||
|
|
||||||
// RsaBits returns the current set RSA bit length for private keys
|
// KeyType the type from which private keys should be generated
|
||||||
func (c *Configuration) RsaBits() int {
|
func (c *Configuration) KeyType() (acme.KeyType, error) {
|
||||||
return c.context.GlobalInt("rsa-key-size")
|
switch strings.ToUpper(c.context.GlobalString("key-type")) {
|
||||||
|
case "RSA2048":
|
||||||
|
return acme.RSA2048, nil
|
||||||
|
case "RSA4096":
|
||||||
|
return acme.RSA4096, nil
|
||||||
|
case "RSA8192":
|
||||||
|
return acme.RSA8192, nil
|
||||||
|
case "EC256":
|
||||||
|
return acme.EC256, nil
|
||||||
|
case "EC384":
|
||||||
|
return acme.EC384, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return "", fmt.Errorf("Unsupported KeyType: %s", c.context.GlobalString("key-type"))
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExcludedSolvers is a list of solvers that are to be excluded.
|
// ExcludedSolvers is a list of solvers that are to be excluded.
|
||||||
|
|
29
crypto.go
29
crypto.go
|
@ -1,21 +1,30 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto"
|
||||||
|
"crypto/ecdsa"
|
||||||
|
"crypto/elliptic"
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"crypto/rsa"
|
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"encoding/pem"
|
"encoding/pem"
|
||||||
|
"errors"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
)
|
)
|
||||||
|
|
||||||
func generateRsaKey(length int, file string) (*rsa.PrivateKey, error) {
|
func generatePrivateKey(file string) (crypto.PrivateKey, error) {
|
||||||
privateKey, err := rsa.GenerateKey(rand.Reader, length)
|
|
||||||
|
privateKey, err := ecdsa.GenerateKey(elliptic.P384(), rand.Reader)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
pemKey := pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(privateKey)}
|
keyBytes, err := x509.MarshalECPrivateKey(privateKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
pemKey := pem.Block{Type: "EC PRIVATE KEY", Bytes: keyBytes}
|
||||||
|
|
||||||
certOut, err := os.Create(file)
|
certOut, err := os.Create(file)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -28,12 +37,20 @@ func generateRsaKey(length int, file string) (*rsa.PrivateKey, error) {
|
||||||
return privateKey, nil
|
return privateKey, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadRsaKey(file string) (*rsa.PrivateKey, error) {
|
func loadPrivateKey(file string) (crypto.PrivateKey, error) {
|
||||||
keyBytes, err := ioutil.ReadFile(file)
|
keyBytes, err := ioutil.ReadFile(file)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
keyBlock, _ := pem.Decode(keyBytes)
|
keyBlock, _ := pem.Decode(keyBytes)
|
||||||
return x509.ParsePKCS1PrivateKey(keyBlock.Bytes)
|
|
||||||
|
switch keyBlock.Type {
|
||||||
|
case "RSA PRIVATE KEY":
|
||||||
|
return x509.ParsePKCS1PrivateKey(keyBlock.Bytes)
|
||||||
|
case "EC PRIVATE KEY":
|
||||||
|
return x509.ParseECPrivateKey(keyBlock.Bytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, errors.New("Unknown private key type.")
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue