-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathmain.go
138 lines (115 loc) · 4.83 KB
/
main.go
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
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
package main
import (
"crypto/ed25519"
"encoding/pem"
"fmt"
"os"
"path/filepath"
"strings"
"golang.org/x/crypto/ssh"
// libp2p
libp2pCrypto "github.com/libp2p/go-libp2p/core/crypto"
"github.com/libp2p/go-libp2p/core/peer"
)
func main() {
// get ed25519 keypair (currently libp2p crypto is not compatible with some crypto/ssh functions, so instead of libp2p use crypto/ed25519)
/*
privKey, pubKey, err := libp2pCrypto.GenerateKeyPair(
libp2pCrypto.Ed25519,
-1, // this value is only used for RSA keys (https://github.com/libp2p/go-libp2p/blob/v0.35.1/core/crypto/key.go#L108)
)
*/
pubKey, privKey, err := ed25519.GenerateKey(nil) // nil will use crypto/rand.Reader
if err != nil {
panic(fmt.Sprintf("genkey - %v", err))
}
// even if you derive libp2p privkey from privkey, you will still get the same error 'unsupported key type *crypto.Ed25519PrivateKey' with MarshalPrivateKeyWithPassphrase()
//libp2pPrivkey, err := libp2pCrypto.UnmarshalEd25519PrivateKey(privKey)
//if err != nil {
// panic(err)
//}
myPassword := "badpassword"
keyOutputPath := filepath.Join(".", "privkey.key")
comment := "no comment" // purely cosmetic (but commands like 'ssh-keygen -y -f privkey.key' will display the comment)
EncryptKeyAndWriteToFile(privKey, myPassword, keyOutputPath, comment)
// now read this encrypted key again from filesystem
retrievedKey := ReadKeyFromFileAndDecrypt(myPassword, keyOutputPath)
// compare with original, should be true if everything went well
if string(retrievedKey) != string(privKey) {
panic("Original and retrieved keys are not identical, something went wrong!")
}
// derive ssh pubkey
sshPubkey, err := ssh.NewPublicKey(pubKey)
if err != nil {
panic(fmt.Sprintf("NewSignerFromSigner - %v", err))
}
// derive libp2p pubkey from pubkey
libp2pPubkey, err := libp2pCrypto.UnmarshalEd25519PublicKey(pubKey)
if err != nil {
panic(err)
}
// derive libp2p nodeid from libp2p pubkey
peerID, err := peer.IDFromPublicKey(libp2pPubkey)
if err != nil {
panic(err)
}
pubKeyString := string(ssh.MarshalAuthorizedKey(sshPubkey))
pubKeyStringWithComment := strings.TrimRight(pubKeyString, "\n\r") + " " + comment
fmt.Printf("Public Key: %v\n", pubKeyStringWithComment)
fmt.Printf("Libp2p Node ID: %v\n", peerID.String())
// verify correctness with external tool (enter correct password)
// ssh-keygen -y -f privkey.key
// will print same pubkey
}
// EncryptKeyAndWriteToFile takes a private ed25519 key, a password and a filepath string and writes the encrypted key in OpenSSH format to that location.
func EncryptKeyAndWriteToFile(privkey ed25519.PrivateKey, password string, outputLocation string, comment string) {
// encrypt private key into OpenSSH format
pwBytes := []byte(password)
encryptedPEM, err := ssh.MarshalPrivateKeyWithPassphrase(privkey, comment, pwBytes)
if err != nil {
panic(fmt.Sprintf("encrypt - %v", err))
}
// encode PEM to bytes
//encryptedPEMBytes := pem.EncodeToMemory(encryptedPEM)
// check if file exists already, warn user that it will be deleted (technically truncated). in production maybe require explicit confirmation before doing this
_, err = os.Stat(outputLocation)
if err == nil { // this is useful for automated testing, change in production
fmt.Printf("Warning: There already exists a keyfile at %v. It will be overwritten!\n", outputLocation)
}
// write pem to file
// open file
file, err := os.Create(outputLocation)
if err != nil {
panic(fmt.Sprintf("Failed to create key file: %v\n", err))
}
defer file.Close()
// write to file
err = pem.Encode(file, encryptedPEM)
if err != nil {
panic(fmt.Sprintf("Failed to write PEM key to file: %v\n", err))
}
// set file permission to 600 (otherwise tools like ssh-keygen will complain that permissions are too open and refuse to do anything)
err = os.Chmod(outputLocation, 0600)
if err != nil {
panic(fmt.Sprintf("Failed to set private key file permission: %v", err))
}
}
// ReadKeyFromFileAndDecrypt takes the password that the key was encrypted with and the location of the key and returns the decrypted ed25519.PrivateKey)
func ReadKeyFromFileAndDecrypt(password string, keyLocation string) ed25519.PrivateKey {
// try to read encrypted key from file if it exists
encryptedPEMBytes, err := os.ReadFile(keyLocation)
if err != nil {
panic(fmt.Sprintf("Failed to read the keyfile %v: %v", keyLocation, err))
}
// decrypt encrypted private key
decryptedPrivInterface, err := ssh.ParseRawPrivateKeyWithPassphrase(encryptedPEMBytes, []byte(password))
if err != nil {
panic(err) // false password triggers: 'x509: decryption password incorrect'
}
// cast from interface to ed25519 key
decryptedPrivPtr, ok := decryptedPrivInterface.(*ed25519.PrivateKey)
if !ok {
panic(fmt.Sprintf("Key is not of type ed25519.PrivateKey, instead it is of type: %T\n", decryptedPrivInterface))
}
return *decryptedPrivPtr
}