160 lines
3.9 KiB
Go
160 lines
3.9 KiB
Go
package encrypt
|
|
|
|
import (
|
|
"bytes"
|
|
"crypto/rand"
|
|
"crypto/rsa"
|
|
"crypto/x509"
|
|
"crypto/x509/pkix"
|
|
"encoding/json"
|
|
"encoding/pem"
|
|
"fmt"
|
|
"io/fs"
|
|
"log"
|
|
"math/big"
|
|
"os"
|
|
"time"
|
|
)
|
|
|
|
// ParsePeerPublicKey umí vzít buď PEM PUBLIC KEY, nebo PEM CERT a vrátí *rsa.PublicKey
|
|
func ParsePeerPublicKey(pemOrCert string) (*rsa.PublicKey, error) {
|
|
block, _ := pem.Decode([]byte(pemOrCert))
|
|
if block == nil {
|
|
return nil, errf("nenašel jsem PEM blok")
|
|
}
|
|
switch block.Type {
|
|
case "PUBLIC KEY":
|
|
k, err := x509.ParsePKIXPublicKey(block.Bytes)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
rsaPub, ok := k.(*rsa.PublicKey)
|
|
if !ok {
|
|
return nil, errf("očekávám RSA PUBLIC KEY")
|
|
}
|
|
return rsaPub, nil
|
|
case "CERTIFICATE":
|
|
c, err := x509.ParseCertificate(block.Bytes)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
rsaPub, ok := c.PublicKey.(*rsa.PublicKey)
|
|
if !ok {
|
|
return nil, errf("cert neobsahuje RSA klíč")
|
|
}
|
|
return rsaPub, nil
|
|
default:
|
|
return nil, errf("nepodporovaný PEM typ: %s", block.Type)
|
|
}
|
|
}
|
|
|
|
func generateSelfSignedCert(pub *rsa.PublicKey, priv *rsa.PrivateKey) []byte {
|
|
tpl := x509.Certificate{
|
|
SerialNumber: big.NewInt(1),
|
|
Subject: pkix.Name{
|
|
CommonName: "Encryptor Local Identity",
|
|
},
|
|
NotBefore: time.Now(),
|
|
NotAfter: time.Now().AddDate(1, 0, 0),
|
|
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment,
|
|
// Tenhle cert je jen „vizitka“ (není pro TLS).
|
|
BasicConstraintsValid: true,
|
|
}
|
|
der, _ := x509.CreateCertificate(rand.Reader, &tpl, &tpl, pub, priv)
|
|
buf := &bytes.Buffer{}
|
|
_ = pem.Encode(buf, &pem.Block{Type: "CERTIFICATE", Bytes: der})
|
|
return buf.Bytes()
|
|
}
|
|
|
|
// malá helper chyba
|
|
type strErr string
|
|
|
|
func (e strErr) Error() string { return string(e) }
|
|
func errf(s string, a ...any) error { return strErr(fmtS(s, a...)) }
|
|
func fmtS(format string, a ...any) string {
|
|
var b bytes.Buffer
|
|
b.WriteString(format)
|
|
if len(a) > 0 {
|
|
b.WriteString(": ")
|
|
}
|
|
for i, v := range a {
|
|
if i > 0 {
|
|
b.WriteString(", ")
|
|
}
|
|
b.WriteString(anyToString(v))
|
|
}
|
|
return b.String()
|
|
}
|
|
func anyToString(v any) string {
|
|
switch t := v.(type) {
|
|
case string:
|
|
return t
|
|
default:
|
|
j, _ := json.Marshal(t)
|
|
return string(j)
|
|
}
|
|
}
|
|
|
|
func LoadOrGenerateKeys(privPath, pubPath, certPath string) (priv *rsa.PrivateKey, pubPEM, certPEM []byte, err error) {
|
|
|
|
// existuje private key?
|
|
if fileExists(privPath) && fileExists(pubPath) && fileExists(certPath) {
|
|
// načti privátní klíč
|
|
pkBytes, err := os.ReadFile(privPath)
|
|
if err != nil {
|
|
return nil, nil, nil, err
|
|
}
|
|
block, _ := pem.Decode(pkBytes)
|
|
if block == nil || block.Type != "RSA PRIVATE KEY" {
|
|
return nil, nil, nil, fmt.Errorf("invalid private key PEM")
|
|
}
|
|
key, err := x509.ParsePKCS1PrivateKey(block.Bytes)
|
|
if err != nil {
|
|
return nil, nil, nil, err
|
|
}
|
|
priv = key
|
|
|
|
// načti public & cert
|
|
pubPEM, _ = os.ReadFile(pubPath)
|
|
certPEM, _ = os.ReadFile(certPath)
|
|
|
|
log.Println("Načtena existující identita z disku.")
|
|
return priv, pubPEM, certPEM, nil
|
|
}
|
|
|
|
// jinak vygeneruj nové
|
|
key, err := rsa.GenerateKey(rand.Reader, 2048)
|
|
if err != nil {
|
|
return nil, nil, nil, err
|
|
}
|
|
priv = key
|
|
|
|
// public
|
|
pubASN1, _ := x509.MarshalPKIXPublicKey(&priv.PublicKey)
|
|
pubPEM = pem.EncodeToMemory(&pem.Block{Type: "PUBLIC KEY", Bytes: pubASN1})
|
|
|
|
// cert
|
|
certPEM = generateSelfSignedCert(&priv.PublicKey, priv)
|
|
|
|
// ulož
|
|
if err := os.WriteFile(privPath,
|
|
pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(priv)}),
|
|
fs.FileMode(0600)); err != nil {
|
|
return nil, nil, nil, err
|
|
}
|
|
if err := os.WriteFile(pubPath, pubPEM, 0644); err != nil {
|
|
return nil, nil, nil, err
|
|
}
|
|
if err := os.WriteFile(certPath, certPEM, 0644); err != nil {
|
|
return nil, nil, nil, err
|
|
}
|
|
|
|
log.Println("Vygenerována nová identita a uložena na disk.")
|
|
return priv, pubPEM, certPEM, nil
|
|
}
|
|
|
|
func fileExists(p string) bool {
|
|
info, err := os.Stat(p)
|
|
return err == nil && !info.IsDir()
|
|
}
|