shikigrid/crypto/keypair.go

174 lines
3.9 KiB
Go

package crypto
import (
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"encoding/pem"
"fmt"
"github.com/evilsocket/islazy/fs"
"github.com/evilsocket/islazy/log"
"os"
"path"
"strings"
)
type KeyPair struct {
Path string
Bits int
PrivatePath string
Private *rsa.PrivateKey
PrivatePEM []byte
PublicPath string
Public *rsa.PublicKey
PublicPEM []byte
// sha256 of PublicSSH
Fingerprint []byte
FingerprintHex string
}
func pubKeyToPEM(key *rsa.PublicKey) ([]byte, error) {
bytes, err := x509.MarshalPKIXPublicKey(key)
if err != nil {
return nil, err
}
return pem.EncodeToMemory(
&pem.Block{
Type: "RSA PUBLIC KEY",
Bytes: bytes,
},
), nil
}
func FromPublicPEM(pubPEM string) (pair *KeyPair, err error) {
block, _ := pem.Decode([]byte(pubPEM))
if block == nil {
return nil, fmt.Errorf("failed to parse PEM block containing the public key")
}
pub, err := x509.ParsePKIXPublicKey(block.Bytes)
if err != nil {
return nil, err
}
pair = &KeyPair{}
ok := false
if pair.Public, ok = pub.(*rsa.PublicKey); !ok {
return nil, fmt.Errorf("not an RSA key")
}
return pair, pair.setupPublic()
}
func PrivatePath(keysPath string) string {
return path.Join(keysPath, "id_rsa")
}
func Load(keysPath string) (pair *KeyPair, err error) {
privFile := PrivatePath(keysPath)
pair = &KeyPair{
Path: keysPath,
PrivatePath: privFile,
PublicPath: privFile + ".pub",
}
return pair, pair.Load()
}
func KeysExist(keysPath string) bool {
return fs.Exists(keysPath) && fs.Exists(PrivatePath(keysPath))
}
func LoadOrCreate(keysPath string, bits int) (pair *KeyPair, err error) {
privFile := PrivatePath(keysPath)
pair = &KeyPair{
Path: keysPath,
Bits: bits,
PrivatePath: privFile,
PublicPath: privFile + ".pub",
}
if !fs.Exists(pair.PrivatePath) {
if !fs.Exists(keysPath) {
log.Debug("creating %s", keysPath)
if err := os.MkdirAll(keysPath, os.ModePerm); err != nil {
return nil, fmt.Errorf("could not create %s: %v", keysPath, err)
}
}
log.Info("%s not found, generating keypair ...", pair.PrivatePath)
if pair.Private, err = rsa.GenerateKey(rand.Reader, bits); err != nil {
return nil, fmt.Errorf("could not generate private key: %v", err)
}
pair.Public = &pair.Private.PublicKey
if err = pair.Save(); err != nil {
return nil, fmt.Errorf("could not save keypair: %v", err)
}
} else if err = pair.Load(); err != nil {
return nil, fmt.Errorf("could not load keypair: %v", err)
}
return pair, nil
}
func (pair *KeyPair) setupPublic() (err error) {
if pair.PublicPEM, err = pubKeyToPEM(pair.Public); err != nil {
return fmt.Errorf("failed converting public key to PEM: %v", err)
}
cleanPEM := strings.TrimRight(string(pair.PublicPEM), "\n")
hash := Hasher.New()
hash.Write([]byte(cleanPEM))
pair.Fingerprint = hash.Sum(nil)
pair.FingerprintHex = fmt.Sprintf("%02x", pair.Fingerprint)
return nil
}
func (pair *KeyPair) Save() (err error) {
prvKeyBytes := x509.MarshalPKCS1PrivateKey(pair.Private)
pair.PrivatePEM = pem.EncodeToMemory(
&pem.Block{
Type: "RSA PRIVATE KEY",
Bytes: prvKeyBytes,
},
)
if err = os.WriteFile(pair.PrivatePath, pair.PrivatePEM, os.ModePerm); err != nil {
return
}
log.Debug("%s created", pair.PrivatePath)
if err = pair.setupPublic(); err != nil {
return err
}
err = os.WriteFile(pair.PublicPath, pair.PublicPEM, os.ModePerm)
log.Debug("%s created", pair.PublicPath)
return
}
func (pair *KeyPair) Load() (err error) {
log.Debug("reading %s ...", pair.PrivatePath)
if pair.PrivatePEM, err = os.ReadFile(pair.PrivatePath); err != nil {
return
}
block, _ := pem.Decode(pair.PrivatePEM)
if block == nil {
return fmt.Errorf("failed decoding PEM from %s", pair.PrivatePath)
}
if pair.Private, err = x509.ParsePKCS1PrivateKey(block.Bytes); err != nil {
return fmt.Errorf("failed parsing %s: %v", pair.PrivatePath, err)
}
pair.Public = &pair.Private.PublicKey
return pair.setupPublic()
}