forked from TrueCloudLab/lego
Support for RecoveryKey (not enabled). But not supported server side...
This commit is contained in:
parent
3d311b9882
commit
4d99c9e543
7 changed files with 97 additions and 18 deletions
|
@ -91,7 +91,11 @@ func NewClient(caURL string, usr User, keyBits int, optPort string, devMode bool
|
||||||
// Register the current account to the ACME server.
|
// Register the current account to the ACME server.
|
||||||
func (c *Client) Register() (*RegistrationResource, error) {
|
func (c *Client) Register() (*RegistrationResource, error) {
|
||||||
logger().Print("Registering account ... ")
|
logger().Print("Registering account ... ")
|
||||||
jsonBytes, err := json.Marshal(registrationMessage{Resource: "new-reg", Contact: []string{"mailto:" + c.user.GetEmail()}})
|
|
||||||
|
jsonBytes, err := json.Marshal(registrationMessage{
|
||||||
|
Resource: "new-reg",
|
||||||
|
Contact: []string{"mailto:" + c.user.GetEmail()},
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -380,13 +384,13 @@ func (c *Client) requestCertificates(challenges []*authorizationResource) ([]Cer
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) requestCertificate(authz *authorizationResource, result chan CertificateResource, errc chan error) {
|
func (c *Client) requestCertificate(authz *authorizationResource, result chan CertificateResource, errc chan error) {
|
||||||
privKey, err := generatePrivateKey(c.keyBits)
|
privKey, err := generatePrivateKey(rsakey, c.keyBits)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errc <- err
|
errc <- err
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
csr, err := generateCsr(privKey, authz.Domain)
|
csr, err := generateCsr(privKey.(*rsa.PrivateKey), authz.Domain)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errc <- err
|
errc <- err
|
||||||
return
|
return
|
||||||
|
|
|
@ -1,20 +1,72 @@
|
||||||
package acme
|
package acme
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto"
|
||||||
|
"crypto/ecdsa"
|
||||||
|
"crypto/elliptic"
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"crypto/rsa"
|
"crypto/rsa"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"crypto/x509/pkix"
|
"crypto/x509/pkix"
|
||||||
|
"encoding/binary"
|
||||||
"encoding/pem"
|
"encoding/pem"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/big"
|
"math/big"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/crypto/sha3"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type keyType int
|
||||||
type derCertificateBytes []byte
|
type derCertificateBytes []byte
|
||||||
|
|
||||||
func generatePrivateKey(keyLength int) (*rsa.PrivateKey, error) {
|
const (
|
||||||
return rsa.GenerateKey(rand.Reader, keyLength)
|
eckey keyType = iota
|
||||||
|
rsakey
|
||||||
|
)
|
||||||
|
|
||||||
|
// Derive the shared secret according to acme spec 5.6
|
||||||
|
func performECDH(priv *ecdsa.PrivateKey, pub *ecdsa.PublicKey, outLen int, label string) []byte {
|
||||||
|
// Derive Z from the private and public keys according to SEC 1 Ver. 2.0 - 3.3.1
|
||||||
|
Z, _ := priv.PublicKey.ScalarMult(pub.X, pub.Y, priv.D.Bytes())
|
||||||
|
|
||||||
|
if len(Z.Bytes())+len(label)+4 > 384 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if outLen < 384*(2^32-1) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Derive the shared secret key using the ANS X9.63 KDF - SEC 1 Ver. 2.0 - 3.6.1
|
||||||
|
hasher := sha3.New384()
|
||||||
|
buffer := make([]byte, outLen)
|
||||||
|
bufferLen := 0
|
||||||
|
for i := 0; i < outLen/384; i++ {
|
||||||
|
hasher.Reset()
|
||||||
|
|
||||||
|
// Ki = Hash(Z || Counter || [SharedInfo])
|
||||||
|
hasher.Write(Z.Bytes())
|
||||||
|
binary.Write(hasher, binary.BigEndian, i)
|
||||||
|
hasher.Write([]byte(label))
|
||||||
|
|
||||||
|
hash := hasher.Sum(nil)
|
||||||
|
copied := copy(buffer[bufferLen:], hash)
|
||||||
|
bufferLen += copied
|
||||||
|
}
|
||||||
|
|
||||||
|
return buffer
|
||||||
|
}
|
||||||
|
|
||||||
|
func generatePrivateKey(t keyType, keyLength int) (crypto.PrivateKey, error) {
|
||||||
|
switch t {
|
||||||
|
case eckey:
|
||||||
|
return ecdsa.GenerateKey(elliptic.P384(), rand.Reader)
|
||||||
|
case rsakey:
|
||||||
|
return rsa.GenerateKey(rand.Reader, keyLength)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, fmt.Errorf("Invalid keytype: %d", t)
|
||||||
}
|
}
|
||||||
|
|
||||||
func generateCsr(privateKey *rsa.PrivateKey, domain string) ([]byte, error) {
|
func generateCsr(privateKey *rsa.PrivateKey, domain string) ([]byte, error) {
|
||||||
|
|
|
@ -8,7 +8,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestGeneratePrivateKey(t *testing.T) {
|
func TestGeneratePrivateKey(t *testing.T) {
|
||||||
key, err := generatePrivateKey(32)
|
key, err := generatePrivateKey(rsakey, 32)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error("Error generating private key:", err)
|
t.Error("Error generating private key:", err)
|
||||||
}
|
}
|
||||||
|
@ -18,12 +18,12 @@ func TestGeneratePrivateKey(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGenerateCSR(t *testing.T) {
|
func TestGenerateCSR(t *testing.T) {
|
||||||
key, err := generatePrivateKey(512)
|
key, err := generatePrivateKey(rsakey, 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, "fizz.buzz")
|
csr, err := generateCsr(key.(*rsa.PrivateKey), "fizz.buzz")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error("Error generating CSR:", err)
|
t.Error("Error generating CSR:", err)
|
||||||
}
|
}
|
||||||
|
@ -52,14 +52,14 @@ func TestPEMEncode(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPEMCertExpiration(t *testing.T) {
|
func TestPEMCertExpiration(t *testing.T) {
|
||||||
privKey, err := generatePrivateKey(2048)
|
privKey, err := generatePrivateKey(rsakey, 2048)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal("Error generating private key:", err)
|
t.Fatal("Error generating private key:", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
expiration := time.Now().Add(365)
|
expiration := time.Now().Add(365)
|
||||||
expiration = expiration.Round(time.Second)
|
expiration = expiration.Round(time.Second)
|
||||||
certBytes, err := generateDerCert(privKey, expiration, "test.com")
|
certBytes, err := generateDerCert(privKey.(*rsa.PrivateKey), expiration, "test.com")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal("Error generating cert:", err)
|
t.Fatal("Error generating cert:", err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ package acme
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"crypto/ecdsa"
|
||||||
"crypto/rsa"
|
"crypto/rsa"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
@ -14,6 +15,13 @@ type jws struct {
|
||||||
nonces []string
|
nonces []string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func keyAsJWK(key *ecdsa.PublicKey) jose.JsonWebKey {
|
||||||
|
return jose.JsonWebKey{
|
||||||
|
Key: key,
|
||||||
|
Algorithm: "EC",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Posts a JWS signed message to the specified URL
|
// Posts a JWS signed message to the specified URL
|
||||||
func (j *jws) post(url string, content []byte) (*http.Response, error) {
|
func (j *jws) post(url string, content []byte) (*http.Response, error) {
|
||||||
if len(j.nonces) == 0 {
|
if len(j.nonces) == 0 {
|
||||||
|
|
|
@ -1,6 +1,10 @@
|
||||||
package acme
|
package acme
|
||||||
|
|
||||||
import "time"
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/letsencrypt/go-jose"
|
||||||
|
)
|
||||||
|
|
||||||
type directory struct {
|
type directory struct {
|
||||||
NewAuthzURL string `json:"new-authz"`
|
NewAuthzURL string `json:"new-authz"`
|
||||||
|
@ -9,9 +13,16 @@ type directory struct {
|
||||||
RevokeCertURL string `json:"revoke-cert"`
|
RevokeCertURL string `json:"revoke-cert"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type recoveryKeyMessage struct {
|
||||||
|
Length int `json:"length,omitempty"`
|
||||||
|
Client jose.JsonWebKey `json:"client,omitempty"`
|
||||||
|
Server jose.JsonWebKey `json:"client,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
type registrationMessage struct {
|
type registrationMessage struct {
|
||||||
Resource string `json:"resource"`
|
Resource string `json:"resource"`
|
||||||
Contact []string `json:"contact"`
|
Contact []string `json:"contact"`
|
||||||
|
// RecoveryKey recoveryKeyMessage `json:"recoveryKey,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Registration is returned by the ACME server after the registration
|
// Registration is returned by the ACME server after the registration
|
||||||
|
@ -28,6 +39,7 @@ type Registration struct {
|
||||||
Agreement string `json:"agreement,omitempty"`
|
Agreement string `json:"agreement,omitempty"`
|
||||||
Authorizations string `json:"authorizations,omitempty"`
|
Authorizations string `json:"authorizations,omitempty"`
|
||||||
Certificates string `json:"certificates,omitempty"`
|
Certificates string `json:"certificates,omitempty"`
|
||||||
|
// RecoveryKey recoveryKeyMessage `json:"recoveryKey,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// RegistrationResource represents all important informations about a registration
|
// RegistrationResource represents all important informations about a registration
|
||||||
|
|
|
@ -2,6 +2,7 @@ package acme
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
|
"crypto/rsa"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
@ -117,15 +118,16 @@ Loop:
|
||||||
func (s *simpleHTTPChallenge) startHTTPSServer(domain string, token string) (net.Listener, error) {
|
func (s *simpleHTTPChallenge) startHTTPSServer(domain string, token string) (net.Listener, error) {
|
||||||
|
|
||||||
// Generate a new RSA key and a self-signed certificate.
|
// Generate a new RSA key and a self-signed certificate.
|
||||||
tempPrivKey, err := generatePrivateKey(2048)
|
tempPrivKey, err := generatePrivateKey(rsakey, 2048)
|
||||||
|
rsaPrivKey := tempPrivKey.(*rsa.PrivateKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
tempCertPEM, err := generatePemCert(tempPrivKey, domain)
|
tempCertPEM, err := generatePemCert(rsaPrivKey, domain)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
pemBytes := pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(tempPrivKey)})
|
pemBytes := pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(rsaPrivKey)})
|
||||||
tempKeyPair, err := tls.X509KeyPair(
|
tempKeyPair, err := tls.X509KeyPair(
|
||||||
tempCertPEM,
|
tempCertPEM,
|
||||||
pemBytes)
|
pemBytes)
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package acme
|
package acme
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/rsa"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
@ -39,11 +40,11 @@ func TestSimpleHTTPCanSolve(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSimpleHTTP(t *testing.T) {
|
func TestSimpleHTTP(t *testing.T) {
|
||||||
privKey, err := generatePrivateKey(512)
|
privKey, err := generatePrivateKey(rsakey, 512)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("Could not generate public key -> %v", err)
|
t.Errorf("Could not generate public key -> %v", err)
|
||||||
}
|
}
|
||||||
jws := &jws{privKey: privKey}
|
jws := &jws{privKey: privKey.(*rsa.PrivateKey)}
|
||||||
|
|
||||||
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")
|
||||||
|
@ -103,7 +104,7 @@ func TestSimpleHTTP(t *testing.T) {
|
||||||
t.Errorf("Client sent invalid JWS to the server.\n\t%v", err)
|
t.Errorf("Client sent invalid JWS to the server.\n\t%v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
output, err := j.Verify(&privKey.PublicKey)
|
output, err := j.Verify(&privKey.(*rsa.PrivateKey).PublicKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("Unable to verify client data -> %v", err)
|
t.Errorf("Unable to verify client data -> %v", err)
|
||||||
}
|
}
|
||||||
|
@ -129,7 +130,7 @@ func TestSimpleHTTP(t *testing.T) {
|
||||||
t.Errorf("Client answered with invalid JWS.\n\t%v", err)
|
t.Errorf("Client answered with invalid JWS.\n\t%v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
_, err = clientResponse.Verify(&privKey.PublicKey)
|
_, err = clientResponse.Verify(&privKey.(*rsa.PrivateKey).PublicKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("Unable to verify client data -> %v", err)
|
t.Errorf("Unable to verify client data -> %v", err)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue