diff --git a/helper/communicator/sshkey/algorithm_enumer.go b/helper/communicator/sshkey/algorithm_enumer.go new file mode 100644 index 000000000..38fb6ae95 --- /dev/null +++ b/helper/communicator/sshkey/algorithm_enumer.go @@ -0,0 +1,52 @@ +// Code generated by "enumer -type Algorithm -transform snake"; DO NOT EDIT. + +// +package communicator + +import ( + "fmt" +) + +const _AlgorithmName = "rsadsaecdsaed25519" + +var _AlgorithmIndex = [...]uint8{0, 3, 6, 11, 18} + +func (i Algorithm) String() string { + if i < 0 || i >= Algorithm(len(_AlgorithmIndex)-1) { + return fmt.Sprintf("Algorithm(%d)", i) + } + return _AlgorithmName[_AlgorithmIndex[i]:_AlgorithmIndex[i+1]] +} + +var _AlgorithmValues = []Algorithm{0, 1, 2, 3} + +var _AlgorithmNameToValueMap = map[string]Algorithm{ + _AlgorithmName[0:3]: 0, + _AlgorithmName[3:6]: 1, + _AlgorithmName[6:11]: 2, + _AlgorithmName[11:18]: 3, +} + +// AlgorithmString retrieves an enum value from the enum constants string name. +// Throws an error if the param is not part of the enum. +func AlgorithmString(s string) (Algorithm, error) { + if val, ok := _AlgorithmNameToValueMap[s]; ok { + return val, nil + } + return 0, fmt.Errorf("%s does not belong to Algorithm values", s) +} + +// AlgorithmValues returns all values of the enum +func AlgorithmValues() []Algorithm { + return _AlgorithmValues +} + +// IsAAlgorithm returns "true" if the value is listed in the enum definition. "false" otherwise +func (i Algorithm) IsAAlgorithm() bool { + for _, v := range _AlgorithmValues { + if i == v { + return true + } + } + return false +} diff --git a/helper/communicator/sshkey/generate.go b/helper/communicator/sshkey/generate.go new file mode 100644 index 000000000..b75d277c9 --- /dev/null +++ b/helper/communicator/sshkey/generate.go @@ -0,0 +1,181 @@ +package communicator + +import ( + "crypto/dsa" + "crypto/ecdsa" + "crypto/ed25519" + "crypto/elliptic" + cryptorand "crypto/rand" + "crypto/rsa" + "crypto/x509" + "encoding/asn1" + "encoding/pem" + "fmt" + "io" + "math/big" + + "golang.org/x/crypto/ssh" +) + +type Algorithm int + +//go:generate enumer -type Algorithm -transform snake +const ( + RSA Algorithm = iota + DSA + ECDSA + ED25519 +) + +var ( + ErrUnknownAlgorithm = fmt.Errorf("sshkey: unknown private key algorithm") + ErrInvalidRSAKeySize = fmt.Errorf("sshkey: invalid private key rsa size: must be more than 1024") + ErrInvalidECDSAKeySize = fmt.Errorf("sshkey: invalid private key ecdsa size, must be one of 256, 384 or 521") + ErrInvalidDSAKeySize = fmt.Errorf("sshkey: invalid private key dsa size, must be one of 1024, 2048 or 3072") +) + +// Pair represents an ssh key pair, as in +type Pair struct { + Private []byte + Public []byte +} + +func NewPair(public, private interface{}) (*Pair, error) { + kb, err := x509.MarshalPKCS8PrivateKey(private) + if err != nil { + return nil, err + } + + privBlk := &pem.Block{ + Type: "", + Headers: nil, + Bytes: kb, + } + + publicKey, err := ssh.NewPublicKey(public) + if err != nil { + return nil, err + } + return &Pair{ + Private: pem.EncodeToMemory(privBlk), + Public: ssh.MarshalAuthorizedKey(publicKey), + }, nil +} + +func PairFromDSA(key *dsa.PrivateKey) (*Pair, error) { + // see https://github.com/golang/crypto/blob/7f63de1d35b0f77fa2b9faea3e7deb402a2383c8/ssh/keys.go#L1186-L1195 + // and https://linux.die.net/man/1/dsa + k := struct { + Version int + P *big.Int + Q *big.Int + G *big.Int + Pub *big.Int + Priv *big.Int + }{ + Version: 0, + P: key.P, + Q: key.Q, + G: key.G, + Pub: key.Y, + Priv: key.X, + } + kb, err := asn1.Marshal(k) + privBlk := &pem.Block{ + Type: "DSA PRIVATE KEY", + Headers: nil, + Bytes: kb, + } + publicKey, err := ssh.NewPublicKey(key.PublicKey) + if err != nil { + return nil, err + } + return &Pair{ + Private: pem.EncodeToMemory(privBlk), + Public: ssh.MarshalAuthorizedKey(publicKey), + }, nil +} + +// GeneratePair generates a Private/Public key pair using algorithm t. +// +// When rand is nil "crypto/rand".Reader will be used. +// +// bits specifies the number of bits in the key to create. For RSA keys, the +// minimum size is 1024 bits and the default is 3072 bits. Generally, 3072 bits +// is considered sufficient. DSA keys must be exactly 1024 bits - or 2 or 3 +// times that - as specified by FIPS 186-2. For ECDSA keys, bits determines the +// key length by selecting from one of three elliptic curve sizes: 256, 384 or +// 521 bits. Attempting to use bit lengths other than these three values for +// ECDSA keys will fail. Ed25519 keys have a fixed length and the bits will +// be ignored. +func GeneratePair(t Algorithm, rand io.Reader, bits int) (*Pair, error) { + if rand == nil { + rand = cryptorand.Reader + } + switch t { + case DSA: + var sizes dsa.ParameterSizes + switch bits { + case 1024: + sizes = dsa.L1024N160 + case 2048: + sizes = dsa.L2048N256 + case 3072: + sizes = dsa.L3072N256 + default: + return nil, ErrInvalidDSAKeySize + } + + params := dsa.Parameters{} + if err := dsa.GenerateParameters(¶ms, rand, sizes); err != nil { + return nil, err + } + + dsakey := &dsa.PrivateKey{ + PublicKey: dsa.PublicKey{ + Parameters: params, + }, + } + if err := dsa.GenerateKey(dsakey, rand); err != nil { + return nil, err + } + return PairFromDSA(dsakey) + case ECDSA: + var ecdsakey *ecdsa.PrivateKey + var err error + switch bits { + case 256: + ecdsakey, err = ecdsa.GenerateKey(elliptic.P256(), rand) + case 384: + ecdsakey, err = ecdsa.GenerateKey(elliptic.P384(), rand) + case 521: + ecdsakey, err = ecdsa.GenerateKey(elliptic.P521(), rand) + default: + ecdsakey, err = nil, ErrInvalidECDSAKeySize + } + if err != nil { + return nil, err + } + return NewPair(ecdsakey.PublicKey, ecdsakey) + case ED25519: + publicKey, privateKey, err := ed25519.GenerateKey(rand) + if err != nil { + return nil, err + } + return NewPair(publicKey, privateKey) + case RSA: + if bits == 0 { + bits = 3072 + } + if bits < 1024 { + return nil, ErrInvalidRSAKeySize + } + rsakey, err := rsa.GenerateKey(rand, bits) + if err != nil { + return nil, err + } + return NewPair(rsakey.PublicKey, rsakey) + default: + return nil, ErrUnknownAlgorithm + } +}