diff --git a/go.mod b/go.mod index ebb5b844..fc61dda4 100644 --- a/go.mod +++ b/go.mod @@ -32,6 +32,7 @@ require ( ) require ( + filippo.io/edwards25519 v1.0.0-rc.1 github.com/go-ping/ping v0.0.0-20211130115550-779d1e919534 github.com/guumaster/hostctl v1.1.2 github.com/kr/pretty v0.3.0 diff --git a/go.sum b/go.sum index 6da1c087..380fe453 100644 --- a/go.sum +++ b/go.sum @@ -1,6 +1,8 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0 h1:eOI3/cP2VTU6uZLDYAoic+eyzzB9YyGmJ7eIjl8rOPg= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +filippo.io/edwards25519 v1.0.0-rc.1 h1:m0VOOB23frXZvAOK44usCgLWvtsxIoMCTBGJZlpmGfU= +filippo.io/edwards25519 v1.0.0-rc.1/go.mod h1:N1IkdkCkiLB6tki+MYJoSx2JTY9NUlxZE7eHn5EwJns= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/Microsoft/go-winio v0.4.14 h1:+hMXMk01us9KgxGb7ftKQt2Xpf5hH/yky+TDA+qxleU= github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA= diff --git a/tls/tls.go b/tls/tls.go new file mode 100644 index 00000000..6a5f9a09 --- /dev/null +++ b/tls/tls.go @@ -0,0 +1,255 @@ +package tls + +import ( + "crypto/ed25519" + "crypto/rand" + "crypto/x509" + "crypto/x509/pkix" + "encoding/base64" + "encoding/pem" + "errors" + "fmt" + "log" + "math/big" + "os" + "time" + + "filippo.io/edwards25519" + "golang.zx2c4.com/wireguard/wgctrl/wgtypes" +) + +type ( + Key struct { + point *edwards25519.Point + } +) + +// NewKey generates a new key. +func NewKey() *Key { + seed := make([]byte, 64) + rand.Reader.Read(seed) + s, _ := (&edwards25519.Scalar{}).SetUniformBytes(seed) + return &Key{(&edwards25519.Point{}).ScalarBaseMult(s)} +} + +// Ed25519PrivateKey returns the private key in Edwards form used for EdDSA. +func (n *Key) Ed25519PrivateKey() (ed25519.PrivateKey, error) { + if n.point == nil { + return ed25519.PrivateKey{}, errors.New("nil point") + } + if len(n.point.Bytes()) != ed25519.SeedSize { + return ed25519.PrivateKey{}, errors.New("incorrect seed size") + } + return ed25519.NewKeyFromSeed(n.point.Bytes()), nil +} + +// Curve25519PrivateKey returns the private key in Montogomery form used for ECDH. +func (n *Key) Curve25519PrivateKey() (wgtypes.Key, error) { + if n.point == nil { + return wgtypes.Key{}, errors.New("nil point") + } + if len(n.point.Bytes()) != ed25519.SeedSize { + return wgtypes.Key{}, errors.New("incorrect seed size") + } + return wgtypes.ParseKey(base64.StdEncoding.EncodeToString(n.point.BytesMontgomery())) +} + +// Save : saves the private key to path. +func (n *Key) Save(path string) error { + f, err := os.Create(path) + if err != nil { + return err + } + defer f.Close() + f.Write(n.point.Bytes()) + return nil +} + +// Reads the private key from path. +func ReadFrom(path string) (*Key, error) { + key, err := os.ReadFile(path) + if err != nil { + return nil, err + } + point, err := (&edwards25519.Point{}).SetBytes(key) + if err != nil { + return nil, err + } + return &Key{point}, nil +} + +// creates a new pkix.Name +func NewName(commonName, country, org string) pkix.Name { + res := NewCName(commonName) + res.Country = []string{country} + res.Organization = []string{org} + return res +} + +// creates a new pkix.Name with only a common name +func NewCName(commonName string) pkix.Name { + return pkix.Name{ + CommonName: commonName, + } +} + +// creates a new certificate signing request for a +func NewCSR(key ed25519.PrivateKey, name pkix.Name) (*x509.CertificateRequest, error) { + derCertRequest, err := x509.CreateCertificateRequest(rand.Reader, &x509.CertificateRequest{ + Subject: name, + PublicKey: key.Public(), + }, key) + if err != nil { + return nil, err + } + csr, err := x509.ParseCertificateRequest(derCertRequest) + if err != nil { + return nil, err + } + return csr, nil +} + +// returns a new self-signed certificate +func SelfSignedCA(key ed25519.PrivateKey, req *x509.CertificateRequest, days int) (*x509.Certificate, error) { + + template := &x509.Certificate{ + BasicConstraintsValid: true, + IsCA: true, + Version: req.Version, + KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageCRLSign, + NotAfter: time.Now().Add(duration(days)), + NotBefore: time.Now(), + SerialNumber: serialNumber(), + PublicKey: key.Public(), + Subject: pkix.Name{ + CommonName: req.Subject.CommonName, + Organization: req.Subject.Organization, + Country: req.Subject.Country, + }, + } + rootCa, err := x509.CreateCertificate(rand.Reader, template, template, req.PublicKey, key) + if err != nil { + return nil, err + } + result, err := x509.ParseCertificate(rootCa) + if err != nil { + return nil, err + } + return result, nil +} + +// issues a new certificate from a parent certificate authority +func NewEndEntityCert(key ed25519.PrivateKey, req *x509.CertificateRequest, parent *x509.Certificate, days int) (*x509.Certificate, error) { + template := &x509.Certificate{ + Version: req.Version, + NotBefore: time.Now(), + NotAfter: time.Now().Add(duration(days)), + SerialNumber: serialNumber(), + SignatureAlgorithm: req.SignatureAlgorithm, + PublicKeyAlgorithm: req.PublicKeyAlgorithm, + PublicKey: req.PublicKey, + Subject: req.Subject, + SubjectKeyId: req.RawSubject, + Issuer: parent.Subject, + } + rootCa, err := x509.CreateCertificate(rand.Reader, template, parent, req.PublicKey, key) + if err != nil { + return nil, err + } + result, err := x509.ParseCertificate(rootCa) + if err != nil { + return nil, err + } + return result, nil +} + +func SaveCert(path, name string, cert *x509.Certificate) error { + //certbytes, err := x509.ParseCertificate(cert) + if err := os.MkdirAll(path, 0644); err != nil { + return fmt.Errorf("failed to create dir %s %w", path, err) + } + certOut, err := os.Create(path + name) + if err != nil { + return fmt.Errorf("failed to open certficate file for writing: %v", err) + } + defer certOut.Close() + if err := pem.Encode(certOut, &pem.Block{ + Type: "CERTIFICATE", + Bytes: cert.Raw, + }); err != nil { + return fmt.Errorf("failed to write certificate to file %v", err) + } + return nil +} + +func SaveKey(path, name string, key ed25519.PrivateKey) error { + //func SaveKey(name string, key *ecdsa.PrivateKey) error { + if err := os.MkdirAll(path, 0644); err != nil { + return fmt.Errorf("failed to create dir %s %w", path, err) + } + keyOut, err := os.Create(path + name) + if err != nil { + return fmt.Errorf("failed open key file for writing: %v", err) + } + defer keyOut.Close() + privBytes, err := x509.MarshalPKCS8PrivateKey(key) + if err != nil { + return fmt.Errorf("failedto marshal key %v ", err) + } + if err := pem.Encode(keyOut, &pem.Block{ + Type: "PRIVATE KEY", + Bytes: privBytes, + }); err != nil { + return fmt.Errorf("failed to write key to file %v", err) + } + return nil +} + +func ReadCert(name string) (*x509.Certificate, error) { + contents, err := os.ReadFile(name) + if err != nil { + return nil, fmt.Errorf("unable to read file %w", err) + } + block, _ := pem.Decode(contents) + if block == nil || block.Type != "CERTIFICATE" { + return nil, errors.New("not a cert " + block.Type) + } + cert, err := x509.ParseCertificate(block.Bytes) + if err != nil { + return nil, fmt.Errorf("unable to parse cert %w", err) + } + return cert, nil +} + +func ReadKey(name string) (*ed25519.PrivateKey, error) { + bytes, err := os.ReadFile(name) + if err != nil { + return nil, fmt.Errorf("unable to read file %w", err) + } + keyBytes, _ := pem.Decode(bytes) + log.Println(keyBytes.Type) + key, err := x509.ParsePKCS8PrivateKey(keyBytes.Bytes) + if err != nil { + return nil, fmt.Errorf("unable to parse file %w", err) + } + private := key.(ed25519.PrivateKey) + return &private, nil +} + +func serialNumber() *big.Int { + serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128) + serialNumber, err := rand.Int(rand.Reader, serialNumberLimit) + if err != nil { + return nil + } + return serialNumber +} + +func duration(days int) time.Duration { + hours := days * 24 + duration, err := time.ParseDuration(fmt.Sprintf("%dh", hours)) + if err != nil { + duration = time.Until(time.Now().Add(time.Hour * 24)) + } + return duration +}