diff --git a/acme/client.go b/acme/client.go index 5bd7a039..bb7ed14e 100644 --- a/acme/client.go +++ b/acme/client.go @@ -91,7 +91,11 @@ func NewClient(caURL string, usr User, keyBits int, optPort string, devMode bool // Register the current account to the ACME server. func (c *Client) Register() (*RegistrationResource, error) { 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 { 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) { - privKey, err := generatePrivateKey(c.keyBits) + privKey, err := generatePrivateKey(rsakey, c.keyBits) if err != nil { errc <- err return } - csr, err := generateCsr(privKey, authz.Domain) + csr, err := generateCsr(privKey.(*rsa.PrivateKey), authz.Domain) if err != nil { errc <- err return diff --git a/acme/crypto.go b/acme/crypto.go index 89fcf9d9..5cf5ab88 100644 --- a/acme/crypto.go +++ b/acme/crypto.go @@ -1,20 +1,72 @@ package acme import ( + "crypto" + "crypto/ecdsa" + "crypto/elliptic" "crypto/rand" "crypto/rsa" "crypto/x509" "crypto/x509/pkix" + "encoding/binary" "encoding/pem" "fmt" "math/big" "time" + + "golang.org/x/crypto/sha3" ) +type keyType int type derCertificateBytes []byte -func generatePrivateKey(keyLength int) (*rsa.PrivateKey, error) { - return rsa.GenerateKey(rand.Reader, keyLength) +const ( + 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) { diff --git a/acme/crypto_test.go b/acme/crypto_test.go index 2e0bb32e..81de504e 100644 --- a/acme/crypto_test.go +++ b/acme/crypto_test.go @@ -8,7 +8,7 @@ import ( ) func TestGeneratePrivateKey(t *testing.T) { - key, err := generatePrivateKey(32) + key, err := generatePrivateKey(rsakey, 32) if err != nil { t.Error("Error generating private key:", err) } @@ -18,12 +18,12 @@ func TestGeneratePrivateKey(t *testing.T) { } func TestGenerateCSR(t *testing.T) { - key, err := generatePrivateKey(512) + key, err := generatePrivateKey(rsakey, 512) if err != nil { t.Fatal("Error generating private key:", err) } - csr, err := generateCsr(key, "fizz.buzz") + csr, err := generateCsr(key.(*rsa.PrivateKey), "fizz.buzz") if err != nil { t.Error("Error generating CSR:", err) } @@ -52,14 +52,14 @@ func TestPEMEncode(t *testing.T) { } func TestPEMCertExpiration(t *testing.T) { - privKey, err := generatePrivateKey(2048) + privKey, err := generatePrivateKey(rsakey, 2048) if err != nil { t.Fatal("Error generating private key:", err) } expiration := time.Now().Add(365) expiration = expiration.Round(time.Second) - certBytes, err := generateDerCert(privKey, expiration, "test.com") + certBytes, err := generateDerCert(privKey.(*rsa.PrivateKey), expiration, "test.com") if err != nil { t.Fatal("Error generating cert:", err) } diff --git a/acme/jws.go b/acme/jws.go index e0eec145..b58f7d17 100644 --- a/acme/jws.go +++ b/acme/jws.go @@ -2,6 +2,7 @@ package acme import ( "bytes" + "crypto/ecdsa" "crypto/rsa" "fmt" "net/http" @@ -14,6 +15,13 @@ type jws struct { 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 func (j *jws) post(url string, content []byte) (*http.Response, error) { if len(j.nonces) == 0 { diff --git a/acme/messages.go b/acme/messages.go index 69a34a71..e25cd9b2 100644 --- a/acme/messages.go +++ b/acme/messages.go @@ -1,6 +1,10 @@ package acme -import "time" +import ( + "time" + + "github.com/letsencrypt/go-jose" +) type directory struct { NewAuthzURL string `json:"new-authz"` @@ -9,9 +13,16 @@ type directory struct { 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 { Resource string `json:"resource"` Contact []string `json:"contact"` + // RecoveryKey recoveryKeyMessage `json:"recoveryKey,omitempty"` } // Registration is returned by the ACME server after the registration @@ -28,6 +39,7 @@ type Registration struct { Agreement string `json:"agreement,omitempty"` Authorizations string `json:"authorizations,omitempty"` Certificates string `json:"certificates,omitempty"` + // RecoveryKey recoveryKeyMessage `json:"recoveryKey,omitempty"` } // RegistrationResource represents all important informations about a registration diff --git a/acme/simple_http_challenge.go b/acme/simple_http_challenge.go index 0193bdf2..fd098a90 100644 --- a/acme/simple_http_challenge.go +++ b/acme/simple_http_challenge.go @@ -2,6 +2,7 @@ package acme import ( "crypto/rand" + "crypto/rsa" "crypto/tls" "crypto/x509" "encoding/json" @@ -117,15 +118,16 @@ Loop: func (s *simpleHTTPChallenge) startHTTPSServer(domain string, token string) (net.Listener, error) { // 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 { return nil, err } - tempCertPEM, err := generatePemCert(tempPrivKey, domain) + tempCertPEM, err := generatePemCert(rsaPrivKey, domain) if err != nil { 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( tempCertPEM, pemBytes) diff --git a/acme/simple_http_challenge_test.go b/acme/simple_http_challenge_test.go index ddd7ac24..03f30abf 100644 --- a/acme/simple_http_challenge_test.go +++ b/acme/simple_http_challenge_test.go @@ -1,6 +1,7 @@ package acme import ( + "crypto/rsa" "crypto/tls" "encoding/json" "io/ioutil" @@ -39,11 +40,11 @@ func TestSimpleHTTPCanSolve(t *testing.T) { } func TestSimpleHTTP(t *testing.T) { - privKey, err := generatePrivateKey(512) + privKey, err := generatePrivateKey(rsakey, 512) if err != nil { 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) { 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) return } - output, err := j.Verify(&privKey.PublicKey) + output, err := j.Verify(&privKey.(*rsa.PrivateKey).PublicKey) if err != nil { 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) return } - _, err = clientResponse.Verify(&privKey.PublicKey) + _, err = clientResponse.Verify(&privKey.(*rsa.PrivateKey).PublicKey) if err != nil { t.Errorf("Unable to verify client data -> %v", err) }