File: host_keys.go

package info (click to toggle)
golang-github-jaksi-sshutils 0.0.15-2
  • links: PTS, VCS
  • area: main
  • in suites: bookworm, forky, sid, trixie
  • size: 180 kB
  • sloc: makefile: 2
file content (119 lines) | stat: -rw-r--r-- 2,453 bytes parent folder | download
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
}