commit c60536eb4300270e36c06ca71c4b91d96d34fb9b Author: Stefan Goppelt Date: Thu Mar 17 10:14:40 2022 +0100 Initial commit diff --git a/Certificate.go b/Certificate.go new file mode 100644 index 0000000..cb0a5ec --- /dev/null +++ b/Certificate.go @@ -0,0 +1,137 @@ +package certs + +import ( + "crypto/ecdsa" + "crypto/ed25519" + "crypto/elliptic" + "crypto/rand" + "crypto/rsa" + "crypto/tls" + "crypto/x509" + "crypto/x509/pkix" + "encoding/pem" + "log" + "math/big" + "net" + "strings" + "time" +) + +// GenerateCertificate basic struct +type GenerateCertificate struct { + Organization string + Host string + ValidFrom string + ValidFor time.Duration + IsCA bool + RSABits int + EcdsaCurve string + Ed25519Key bool +} + +func (gc *GenerateCertificate) publicKey(priv interface{}) interface{} { + switch k := priv.(type) { + case *rsa.PrivateKey: + return &k.PublicKey + case *ecdsa.PrivateKey: + return &k.PublicKey + case ed25519.PrivateKey: + return k.Public().(ed25519.PublicKey) + default: + return nil + } +} + +// GenerateTLSConfig create a TLS Config +func (gc *GenerateCertificate) GenerateTLSConfig() (*tls.Config, error) { + var priv interface{} + var err error + switch gc.EcdsaCurve { + case "": + if gc.Ed25519Key { + _, priv, err = ed25519.GenerateKey(rand.Reader) + } else { + priv, err = rsa.GenerateKey(rand.Reader, gc.RSABits) + } + case "P224": + priv, err = ecdsa.GenerateKey(elliptic.P224(), rand.Reader) + case "P256": + priv, err = ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + case "P384": + priv, err = ecdsa.GenerateKey(elliptic.P384(), rand.Reader) + case "P521": + priv, err = ecdsa.GenerateKey(elliptic.P521(), rand.Reader) + default: + log.Fatalf("Unrecognized elliptic curve: %q", gc.EcdsaCurve) + return nil, err + } + if err != nil { + log.Fatalf("Failed to generate private key: %v", err) + return nil, err + } + + var notBefore time.Time + if len(gc.ValidFrom) == 0 { + notBefore = time.Now() + } else { + notBefore, err = time.Parse("Jan 2 15:04:05 2006", gc.ValidFrom) + if err != nil { + log.Fatalf("Failed to parse creation date: %v", err) + return nil, err + } + } + + notAfter := notBefore.Add(gc.ValidFor) + + serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128) + serialNumber, err := rand.Int(rand.Reader, serialNumberLimit) + if err != nil { + log.Fatalf("Failed to generate serial number: %v", err) + return nil, err + } + + template := x509.Certificate{ + SerialNumber: serialNumber, + Subject: pkix.Name{ + Organization: []string{gc.Organization}, + }, + NotBefore: notBefore, + NotAfter: notAfter, + + KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, + BasicConstraintsValid: true, + } + + hosts := strings.Split(gc.Host, ",") + for _, h := range hosts { + if ip := net.ParseIP(h); ip != nil { + template.IPAddresses = append(template.IPAddresses, ip) + } else { + template.DNSNames = append(template.DNSNames, h) + } + } + + if gc.IsCA { + template.IsCA = true + template.KeyUsage |= x509.KeyUsageCertSign + } + + derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, gc.publicKey(priv), priv) + if err != nil { + log.Fatalf("Failed to create certificate: %v", err) + return nil, err + } + + privBytes, err := x509.MarshalPKCS8PrivateKey(priv) + if err != nil { + log.Fatalf("Unable to marshal private key: %v", err) + return nil, err + } + + keyPEM := pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: privBytes}) + certPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: derBytes}) + tlsCert, err := tls.X509KeyPair(certPEM, keyPEM) + + return &tls.Config{Certificates: []tls.Certificate{tlsCert}}, nil +} diff --git a/Certificate_test.go b/Certificate_test.go new file mode 100644 index 0000000..3fcaaa4 --- /dev/null +++ b/Certificate_test.go @@ -0,0 +1,27 @@ +package certs + +import ( + "testing" + "time" + + "gotest.tools/assert" +) + +func TestGenerateTLSConfig(t *testing.T) { + gc := &GenerateCertificate{ + Organization: "yoorie.de", + Host: "127.0.0.1", + ValidFor: 10 * 365 * 24 * time.Hour, + IsCA: false, + EcdsaCurve: "P256", + Ed25519Key: true, + } + result, err := gc.GenerateTLSConfig() + + assert.Assert(t, err == nil) + assert.Assert(t, result != nil) + assert.Equal(t, 1, len(result.Certificates)) + cert := result.Certificates[0] + assert.Assert(t, len(cert.Certificate) > 0) + assert.Assert(t, len(cert.Certificate[0]) > 0) +} diff --git a/README.md b/README.md new file mode 100644 index 0000000..e788cdd --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +# TLS Library diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..2fe5879 --- /dev/null +++ b/go.mod @@ -0,0 +1,10 @@ +module scm.yoorie.de/go-lib/certs + +go 1.18 + +require gotest.tools v2.2.0+incompatible + +require ( + github.com/google/go-cmp v0.5.7 // indirect + github.com/pkg/errors v0.9.1 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..bf21e2d --- /dev/null +++ b/go.sum @@ -0,0 +1,8 @@ +github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o= +github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= +gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=