Commit 9e386fc9 authored by Matt Holt's avatar Matt Holt

Merge pull request #652 from elcore/patch-2

https: Support ECC keys
parents 36b440c0 9099375b
...@@ -34,16 +34,7 @@ var NewACMEClient = func(email string, allowPrompts bool) (*ACMEClient, error) { ...@@ -34,16 +34,7 @@ var NewACMEClient = func(email string, allowPrompts bool) (*ACMEClient, error) {
} }
// The client facilitates our communication with the CA server. // The client facilitates our communication with the CA server.
var kt acme.KeyType client, err := acme.NewClient(CAUrl, &leUser, KeyType)
if rsaKeySizeToUse == Rsa2048 {
kt = acme.RSA2048
} else if rsaKeySizeToUse == Rsa4096 {
kt = acme.RSA4096
} else {
// TODO(hkjn): Support more types? Current changes are quick fix for #640.
return nil, fmt.Errorf("https: unsupported keysize")
}
client, err := acme.NewClient(CAUrl, &leUser, kt)
if err != nil { if err != nil {
return nil, err return nil, err
} }
......
package https package https
import ( import (
"crypto"
"crypto/ecdsa"
"crypto/rsa" "crypto/rsa"
"crypto/x509" "crypto/x509"
"encoding/pem" "encoding/pem"
"errors"
"io/ioutil" "io/ioutil"
"os" "os"
) )
// loadRSAPrivateKey loads a PEM-encoded RSA private key from file. // loadPrivateKey loads a PEM-encoded ECC/RSA private key from file.
func loadRSAPrivateKey(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)
switch keyBlock.Type {
case "RSA PRIVATE KEY":
return x509.ParsePKCS1PrivateKey(keyBlock.Bytes) return x509.ParsePKCS1PrivateKey(keyBlock.Bytes)
case "EC PRIVATE KEY":
return x509.ParseECPrivateKey(keyBlock.Bytes)
}
return nil, errors.New("unknown private key type")
} }
// saveRSAPrivateKey saves a PEM-encoded RSA private key to file. // savePrivateKey saves a PEM-encoded ECC/RSA private key to file.
func saveRSAPrivateKey(key *rsa.PrivateKey, file string) error { func savePrivateKey(key crypto.PrivateKey, file string) error {
pemKey := pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(key)} var pemType string
var keyBytes []byte
switch key := key.(type) {
case *ecdsa.PrivateKey:
var err error
pemType = "EC"
keyBytes, err = x509.MarshalECPrivateKey(key)
if err != nil {
return err
}
case *rsa.PrivateKey:
pemType = "RSA"
keyBytes = x509.MarshalPKCS1PrivateKey(key)
}
pemKey := pem.Block{Type: pemType + " PRIVATE KEY", Bytes: keyBytes}
keyOut, err := os.Create(file) keyOut, err := os.Create(file)
if err != nil { if err != nil {
return err return err
......
...@@ -2,6 +2,9 @@ package https ...@@ -2,6 +2,9 @@ package https
import ( import (
"bytes" "bytes"
"crypto"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand" "crypto/rand"
"crypto/rsa" "crypto/rsa"
"crypto/x509" "crypto/x509"
...@@ -10,23 +13,57 @@ import ( ...@@ -10,23 +13,57 @@ import (
"testing" "testing"
) )
func init() { func TestSaveAndLoadRSAPrivateKey(t *testing.T) {
rsaKeySizeToUse = 2048 // TODO(hkjn): Bring back support for small keyFile := "test.key"
// keys to speed up tests? Current changes defer os.Remove(keyFile)
// are quick fix for #640.
privateKey, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
t.Fatal(err)
}
// test save
err = savePrivateKey(privateKey, keyFile)
if err != nil {
t.Fatal("error saving private key:", err)
}
// it doesn't make sense to test file permission on windows
if runtime.GOOS != "windows" {
// get info of the key file
info, err := os.Stat(keyFile)
if err != nil {
t.Fatal("error stating private key:", err)
}
// verify permission of key file is correct
if info.Mode().Perm() != 0600 {
t.Error("Expected key file to have permission 0600, but it wasn't")
}
}
// test load
loadedKey, err := loadPrivateKey(keyFile)
if err != nil {
t.Error("error loading private key:", err)
}
// verify loaded key is correct
if !PrivateKeysSame(privateKey, loadedKey) {
t.Error("Expected key bytes to be the same, but they weren't")
}
} }
func TestSaveAndLoadRSAPrivateKey(t *testing.T) { func TestSaveAndLoadECCPrivateKey(t *testing.T) {
keyFile := "test.key" keyFile := "test.key"
defer os.Remove(keyFile) defer os.Remove(keyFile)
privateKey, err := rsa.GenerateKey(rand.Reader, rsaKeySizeToUse) privateKey, err := ecdsa.GenerateKey(elliptic.P384(), rand.Reader)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
// test save // test save
err = saveRSAPrivateKey(privateKey, keyFile) err = savePrivateKey(privateKey, keyFile)
if err != nil { if err != nil {
t.Fatal("error saving private key:", err) t.Fatal("error saving private key:", err)
} }
...@@ -45,23 +82,30 @@ func TestSaveAndLoadRSAPrivateKey(t *testing.T) { ...@@ -45,23 +82,30 @@ func TestSaveAndLoadRSAPrivateKey(t *testing.T) {
} }
// test load // test load
loadedKey, err := loadRSAPrivateKey(keyFile) loadedKey, err := loadPrivateKey(keyFile)
if err != nil { if err != nil {
t.Error("error loading private key:", err) t.Error("error loading private key:", err)
} }
// verify loaded key is correct // verify loaded key is correct
if !rsaPrivateKeysSame(privateKey, loadedKey) { if !PrivateKeysSame(privateKey, loadedKey) {
t.Error("Expected key bytes to be the same, but they weren't") t.Error("Expected key bytes to be the same, but they weren't")
} }
} }
// rsaPrivateKeysSame compares the bytes of a and b and returns true if they are the same. // PrivateKeysSame compares the bytes of a and b and returns true if they are the same.
func rsaPrivateKeysSame(a, b *rsa.PrivateKey) bool { func PrivateKeysSame(a, b crypto.PrivateKey) bool {
return bytes.Equal(rsaPrivateKeyBytes(a), rsaPrivateKeyBytes(b)) return bytes.Equal(PrivateKeyBytes(a), PrivateKeyBytes(b))
} }
// rsaPrivateKeyBytes returns the bytes of DER-encoded key. // PrivateKeyBytes returns the bytes of DER-encoded key.
func rsaPrivateKeyBytes(key *rsa.PrivateKey) []byte { func PrivateKeyBytes(key crypto.PrivateKey) []byte {
return x509.MarshalPKCS1PrivateKey(key) var keyBytes []byte
switch key := key.(type) {
case *rsa.PrivateKey:
keyBytes = x509.MarshalPKCS1PrivateKey(key)
case *ecdsa.PrivateKey:
keyBytes, _ = x509.MarshalECPrivateKey(key)
}
return keyBytes
} }
...@@ -401,21 +401,10 @@ var ( ...@@ -401,21 +401,10 @@ var (
// default port for the challenge must be forwarded to this one. // default port for the challenge must be forwarded to this one.
const AlternatePort = "5033" const AlternatePort = "5033"
// KeySize represents the length of a key in bits. // KeyType is the type to use for new keys.
type KeySize int
// Key sizes are used to determine the strength of a key.
const (
Ecc224 KeySize = 224
Ecc256 = 256
Rsa2048 = 2048
Rsa4096 = 4096
)
// rsaKeySizeToUse is the size to use for new RSA keys.
// This shouldn't need to change except for in tests; // This shouldn't need to change except for in tests;
// the size can be drastically reduced for speed. // the size can be drastically reduced for speed.
var rsaKeySizeToUse = Rsa2048 var KeyType = acme.EC384
// stopChan is used to signal the maintenance goroutine // stopChan is used to signal the maintenance goroutine
// to terminate. // to terminate.
......
...@@ -3,8 +3,9 @@ package https ...@@ -3,8 +3,9 @@ package https
import ( import (
"bufio" "bufio"
"crypto" "crypto"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand" "crypto/rand"
"crypto/rsa"
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
...@@ -21,7 +22,7 @@ import ( ...@@ -21,7 +22,7 @@ import (
type User struct { type User struct {
Email string Email string
Registration *acme.RegistrationResource Registration *acme.RegistrationResource
key *rsa.PrivateKey key crypto.PrivateKey
} }
// GetEmail gets u's email. // GetEmail gets u's email.
...@@ -64,7 +65,7 @@ func getUser(email string) (User, error) { ...@@ -64,7 +65,7 @@ func getUser(email string) (User, error) {
} }
// load their private key // load their private key
user.key, err = loadRSAPrivateKey(storage.UserKeyFile(email)) user.key, err = loadPrivateKey(storage.UserKeyFile(email))
if err != nil { if err != nil {
return user, err return user, err
} }
...@@ -83,7 +84,7 @@ func saveUser(user User) error { ...@@ -83,7 +84,7 @@ func saveUser(user User) error {
} }
// save private key file // save private key file
err = saveRSAPrivateKey(user.key, storage.UserKeyFile(user.Email)) err = savePrivateKey(user.key, storage.UserKeyFile(user.Email))
if err != nil { if err != nil {
return err return err
} }
...@@ -104,7 +105,7 @@ func saveUser(user User) error { ...@@ -104,7 +105,7 @@ func saveUser(user User) error {
// instead. It does NOT prompt the user. // instead. It does NOT prompt the user.
func newUser(email string) (User, error) { func newUser(email string) (User, error) {
user := User{Email: email} user := User{Email: email}
privateKey, err := rsa.GenerateKey(rand.Reader, rsaKeySizeToUse) privateKey, err := ecdsa.GenerateKey(elliptic.P384(), rand.Reader)
if err != nil { if err != nil {
return user, errors.New("error generating private key: " + err.Error()) return user, errors.New("error generating private key: " + err.Error())
} }
......
...@@ -114,7 +114,7 @@ func TestGetUserAlreadyExists(t *testing.T) { ...@@ -114,7 +114,7 @@ func TestGetUserAlreadyExists(t *testing.T) {
} }
// Assert keys are the same // Assert keys are the same
if !rsaPrivateKeysSame(user.key, user2.key) { if !PrivateKeysSame(user.key, user2.key) {
t.Error("Expected private key to be the same after loading, but it wasn't") t.Error("Expected private key to be the same after loading, but it wasn't")
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment