1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119
|
package sshutils
import (
"crypto/ecdsa"
"crypto/ed25519"
"crypto/elliptic"
"crypto/rsa"
"crypto/x509"
"encoding/pem"
"errors"
"fmt"
"io"
"os"
"golang.org/x/crypto/ssh"
)
const (
rsaKeyBitSize = 2048
hostKeyFilePerms = 0o600
)
var (
ErrInvalidKey = errors.New("invalid key")
ErrInvalidKeyFile = errors.New("invalid key file")
ErrUnsupportedKeyType = errors.New("unsupported key type")
)
type KeyType int
const (
RSA = iota
ECDSA
Ed25519
)
func (t KeyType) String() string {
switch t {
case RSA:
return "rsa"
case ECDSA:
return "ecdsa"
case Ed25519:
return "ed25519"
default:
return fmt.Sprintf("unknown type (%d)", t)
}
}
type HostKey struct {
ssh.Signer
key interface{}
}
func (key *HostKey) String() string {
return ssh.FingerprintSHA256(key.PublicKey())
}
func hostKeyFromKey(key interface{}) (*HostKey, error) {
signer, err := ssh.NewSignerFromKey(key)
if err != nil {
return nil, fmt.Errorf("%w: %v", ErrInvalidKey, err)
}
return &HostKey{
Signer: signer,
key: key,
}, nil
}
func GenerateHostKey(rand io.Reader, t KeyType) (*HostKey, error) {
var key interface{}
var err error
switch t {
case RSA:
key, err = rsa.GenerateKey(rand, rsaKeyBitSize)
case ECDSA:
key, err = ecdsa.GenerateKey(elliptic.P256(), rand)
case Ed25519:
_, key, err = ed25519.GenerateKey(rand)
default:
return nil, fmt.Errorf("%w: %v", ErrUnsupportedKeyType, t)
}
if err != nil {
return nil, fmt.Errorf("%w: %v", ErrInvalidKey, err)
}
return hostKeyFromKey(key)
}
func LoadHostKey(fileName string) (*HostKey, error) {
keyBytes, err := os.ReadFile(fileName)
if err != nil {
return nil, fmt.Errorf("%w: %v", ErrInvalidKeyFile, err)
}
key, err := ssh.ParseRawPrivateKey(keyBytes)
if err != nil {
return nil, fmt.Errorf("%w: %v", ErrInvalidKeyFile, err)
}
return hostKeyFromKey(key)
}
func (key *HostKey) Save(fileName string) error {
file, err := os.OpenFile(fileName, os.O_WRONLY|os.O_CREATE|os.O_EXCL, hostKeyFilePerms) //nolint:nosnakecase
if err != nil {
return fmt.Errorf("%w: %v", ErrInvalidKeyFile, err)
}
defer file.Close()
keyBytes, err := x509.MarshalPKCS8PrivateKey(key.key)
if err != nil {
return fmt.Errorf("%w: %v", ErrInvalidKey, err)
}
if _, err = file.Write(pem.EncodeToMemory(&pem.Block{
Type: "PRIVATE KEY",
Headers: nil,
Bytes: keyBytes,
})); err != nil {
return fmt.Errorf("%w: %v", ErrInvalidKeyFile, err)
}
return nil
}
|