Skip to content
Snippets Groups Projects
Commit 3e0a562f authored by Niall Sheridan's avatar Niall Sheridan Committed by GitHub
Browse files

Merge pull request #39 from nsheridan/opts2

Simplify key generation
parents 6dfe350c fc190076
No related branches found
No related tags found
No related merge requests found
package client
import (
"crypto"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/rsa"
"fmt"
"strings"
"golang.org/x/crypto/ed25519"
"golang.org/x/crypto/ssh"
)
// Key is a private key.
type Key interface{}
type keyfunc func(int) (Key, ssh.PublicKey, error)
type Key crypto.Signer
var (
keytypes = map[string]keyfunc{
"rsa": generateRSAKey,
"ecdsa": generateECDSAKey,
"ed25519": generateED25519Key,
// Options for key generation.
// Defaults will generate a 2048 bit RSA key.
type options struct {
keytype string
size int
}
)
func generateED25519Key(bits int) (Key, ssh.PublicKey, error) {
p, k, err := ed25519.GenerateKey(rand.Reader)
if err != nil {
return nil, nil, err
var defaultOptions = options{
keytype: "rsa",
size: 0, // Different key types have different default sizes.
}
pub, err := ssh.NewPublicKey(p)
if err != nil {
return nil, nil, err
// A KeyOption is used to generate keys of different types and sizes.
type KeyOption func(*options)
// KeyType sets the type of key to generate.
// Valid types are: "rsa", "ecdsa", "ed25519".
// Default is "rsa"
func KeyType(keyType string) KeyOption {
return func(o *options) {
o.keytype = keyType
}
return &k, pub, nil
}
func generateRSAKey(bits int) (Key, ssh.PublicKey, error) {
k, err := rsa.GenerateKey(rand.Reader, bits)
if err != nil {
return nil, nil, err
// KeySize sets the size of the key in bits.
// RSA keys must be a minimum of 1024 bits. The default is 2048 bits.
// ECDSA keys must be one of 256, 384, or 521 bits. The default is 256 bits.
// Ed25519 keys are of a fixed size. This option is ignored.
func KeySize(size int) KeyOption {
return func(o *options) {
o.size = size
}
pub, err := ssh.NewPublicKey(&k.PublicKey)
if err != nil {
return nil, nil, err
}
return k, pub, nil
func generateED25519Key() (Key, error) {
_, k, err := ed25519.GenerateKey(rand.Reader)
return &k, err
}
func generateRSAKey(size int) (Key, error) {
return rsa.GenerateKey(rand.Reader, size)
}
func generateECDSAKey(bits int) (Key, ssh.PublicKey, error) {
func generateECDSAKey(size int) (Key, error) {
var curve elliptic.Curve
switch bits {
switch size {
case 256:
curve = elliptic.P256()
case 384:
......@@ -58,28 +68,41 @@ func generateECDSAKey(bits int) (Key, ssh.PublicKey, error) {
case 521:
curve = elliptic.P521()
default:
return nil, nil, fmt.Errorf("Unsupported key size. Valid sizes are '256', '384', '521'")
return nil, fmt.Errorf("Unsupported key size: %d. Valid sizes are '256', '384', '521'", size)
}
k, err := ecdsa.GenerateKey(curve, rand.Reader)
if err != nil {
return nil, nil, err
}
pub, err := ssh.NewPublicKey(&k.PublicKey)
if err != nil {
return nil, nil, err
}
return k, pub, nil
return ecdsa.GenerateKey(curve, rand.Reader)
}
// GenerateKey generates a ssh key-pair according to the type and size specified.
func GenerateKey(keytype string, bits int) (Key, ssh.PublicKey, error) {
f, ok := keytypes[keytype]
if !ok {
var valid []string
for k := range keytypes {
valid = append(valid, k)
func GenerateKey(options ...func(*options)) (Key, ssh.PublicKey, error) {
var privkey Key
var pubkey ssh.PublicKey
var err error
config := defaultOptions
for _, o := range options {
o(&config)
}
return nil, nil, fmt.Errorf("Unsupported key type %s. Valid choices are %s", keytype, strings.Join(valid, "|"))
switch config.keytype {
case "rsa":
if config.size == 0 {
config.size = 2048
}
privkey, err = generateRSAKey(config.size)
case "ecdsa":
if config.size == 0 {
config.size = 256
}
privkey, err = generateECDSAKey(config.size)
case "ed25519":
privkey, err = generateED25519Key()
default:
privkey, err = generateRSAKey(config.size)
}
if err != nil {
return nil, nil, err
}
return f(bits)
pubkey, err = ssh.NewPublicKey(privkey.Public())
return privkey, pubkey, err
}
package client
import (
"crypto/rsa"
"reflect"
"testing"
"golang.org/x/crypto/ed25519"
)
func TestGenerateKeys(t *testing.T) {
var tests = []struct {
key string
size int
keytype string
keysize int
want string
}{
{"ecdsa", 256, "*ecdsa.PrivateKey"},
{"rsa", 1024, "*rsa.PrivateKey"},
{"ed25519", 256, "*ed25519.PrivateKey"},
{"rsa", 0, "*rsa.PrivateKey"},
{"ecdsa", 0, "*ecdsa.PrivateKey"},
{"ecdsa", 384, "*ecdsa.PrivateKey"},
{"ed25519", 0, "*ed25519.PrivateKey"},
}
for _, tst := range tests {
k, _, err := GenerateKey(tst.key, tst.size)
var k Key
var err error
k, _, err = GenerateKey(KeyType(tst.keytype), KeySize(tst.keysize))
if err != nil {
t.Error(err)
}
......@@ -26,3 +33,36 @@ func TestGenerateKeys(t *testing.T) {
}
}
}
func TestDefaultOptions(t *testing.T) {
k, _, err := GenerateKey()
if err != nil {
t.Error(err)
}
_, ok := k.(*rsa.PrivateKey)
if !ok {
t.Errorf("Unexpected key type %T, wanted *rsa.PrivateKey", k)
}
}
func TestGenerateKeyType(t *testing.T) {
k, _, err := GenerateKey(KeyType("ed25519"))
if err != nil {
t.Error(err)
}
_, ok := k.(*ed25519.PrivateKey)
if !ok {
t.Errorf("Unexpected key type %T, wanted *ed25519.PrivateKey", k)
}
}
func TestGenerateKeySize(t *testing.T) {
k, _, err := GenerateKey(KeySize(1024))
if err != nil {
t.Error(err)
}
_, ok := k.(*rsa.PrivateKey)
if !ok {
t.Errorf("Unexpected key type %T, wanted *rsa.PrivateKey", k)
}
}
......@@ -36,7 +36,7 @@ func main() {
fmt.Println("Error launching web browser. Go to the link in your web browser")
}
fmt.Println("Generating new key pair")
priv, pub, err := client.GenerateKey(c.Keytype, c.Keysize)
priv, pub, err := client.GenerateKey(client.KeyType(c.Keytype), client.KeySize(c.Keysize))
if err != nil {
log.Fatalln("Error generating key pair: ", err)
}
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment