package ssh import ( "crypto/md5" "crypto/rand" "crypto/rsa" "crypto/x509" "encoding/base64" "encoding/pem" "errors" "fmt" "io" "os" "runtime" gossh "golang.org/x/crypto/ssh" ) var ( ErrKeyGeneration = errors.New("Unable to generate key") ErrValidation = errors.New("Unable to validate key") ErrPublicKey = errors.New("Unable to convert public key") ErrUnableToWriteFile = errors.New("Unable to write file") ) type KeyPair struct { PrivateKey []byte PublicKey []byte } // Generate a new SSH keypair // This will return a private & public key encoded as DER. func NewKeyPair() (keyPair *KeyPair, err error) { priv, err := rsa.GenerateKey(rand.Reader, 2048) if err != nil { return nil, ErrKeyGeneration } if err := priv.Validate(); err != nil { return nil, ErrValidation } privDer := x509.MarshalPKCS1PrivateKey(priv) pubSSH, err := gossh.NewPublicKey(&priv.PublicKey) if err != nil { return nil, ErrPublicKey } return &KeyPair{ PrivateKey: privDer, PublicKey: gossh.MarshalAuthorizedKey(pubSSH), }, nil } // Write keypair to files func (kp *KeyPair) WriteToFile(privateKeyPath string, publicKeyPath string) error { files := []struct { File string Type string Value []byte }{ { File: privateKeyPath, Value: pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Headers: nil, Bytes: kp.PrivateKey}), }, { File: publicKeyPath, Value: kp.PublicKey, }, } for _, v := range files { f, err := os.Create(v.File) if err != nil { return ErrUnableToWriteFile } if _, err := f.Write(v.Value); err != nil { return ErrUnableToWriteFile } // windows does not support chmod switch runtime.GOOS { case "darwin", "linux": if err := f.Chmod(0600); err != nil { return err } } } return nil } // Calculate the fingerprint of the public key func (kp *KeyPair) Fingerprint() string { b, _ := base64.StdEncoding.DecodeString(string(kp.PublicKey)) h := md5.New() io.WriteString(h, string(b)) return fmt.Sprintf("%x", h.Sum(nil)) } // Generate SSH keypair based on path of the private key // The public key would be generated to the same path with ".pub" added func GenerateSSHKey(path string) error { if _, err := os.Stat(path); err != nil { if !os.IsNotExist(err) { return err } kp, err := NewKeyPair() if err != nil { return err } if err := kp.WriteToFile(path, fmt.Sprintf("%s.pub", path)); err != nil { return err } } return nil }