Add tests, comments and rewrite some code

This commit is contained in:
Melon 2023-06-19 16:12:47 +01:00
parent 0fb4cb7e67
commit ad80ee9ede
Signed by: melon
GPG Key ID: 6C9D970C50D26A25
13 changed files with 219 additions and 40 deletions

View File

@ -1,4 +1,4 @@
Copyright 2022 OnPointCoding Copyright 2023 MrMelon54
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

View File

@ -1,4 +1,4 @@
certgen CertGen
======= =======
Some certificate generation utilities Some certificate generation utilities

33
ca.go
View File

@ -6,42 +6,57 @@ import (
"crypto/tls" "crypto/tls"
"crypto/x509" "crypto/x509"
"crypto/x509/pkix" "crypto/x509/pkix"
"log" "fmt"
"math/big" "math/big"
"time" "time"
) )
func MakeCaTls(name pkix.Name, serialNumber *big.Int) (*CertGen, error) { // MakeCaTls generates a CA TLS certificate
func MakeCaTls(bits int, name pkix.Name, serialNumber *big.Int, future Future) (*CertGen, error) {
// base certificate data
now := time.Now()
ca := &x509.Certificate{ ca := &x509.Certificate{
SerialNumber: serialNumber, SerialNumber: serialNumber,
Subject: name, Subject: name,
NotBefore: time.Now(), NotBefore: now,
NotAfter: time.Now().AddDate(10, 0, 0), NotAfter: future(now),
IsCA: true, IsCA: true,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth}, ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth},
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign, KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
BasicConstraintsValid: true, BasicConstraintsValid: true,
} }
caPrivKey, err := rsa.GenerateKey(rand.Reader, 4096) // generate rsa private key
caPrivKey, err := rsa.GenerateKey(rand.Reader, bits)
if err != nil { if err != nil {
log.Fatalln("Failed to generate CA private key:", err) return nil, fmt.Errorf("Failed to generate CA private key: %w", err)
} }
// create certificate bytes
caBytes, err := x509.CreateCertificate(rand.Reader, ca, ca, caPrivKey.Public(), caPrivKey) caBytes, err := x509.CreateCertificate(rand.Reader, ca, ca, caPrivKey.Public(), caPrivKey)
if err != nil { if err != nil {
log.Fatalln("Failed to generate CA certificate bytes:", err) return nil, fmt.Errorf("Failed to generate CA certificate bytes: %w", err)
} }
// add the raw certificate bytes so `*x509.Certificate.Equal(*x509.Certificate)` is valid
ca.Raw = caBytes
// get private key bytes
privKeyBytes := x509.MarshalPKCS1PrivateKey(caPrivKey) privKeyBytes := x509.MarshalPKCS1PrivateKey(caPrivKey)
gen := &CertGen{cert: ca, certBytes: caBytes, key: caPrivKey, keyBytes: privKeyBytes} gen := &CertGen{cert: ca, certBytes: caBytes, key: caPrivKey, keyBytes: privKeyBytes}
// generate pem blocks
err = gen.generatePem() err = gen.generatePem()
if err != nil { if err != nil {
return nil, err return nil, fmt.Errorf("Failed to generate PEM encoding: %w", err)
} }
// generate key pair
caKeyPair, err := tls.X509KeyPair(gen.certPem, gen.keyPem) caKeyPair, err := tls.X509KeyPair(gen.certPem, gen.keyPem)
if err != nil { if err != nil {
log.Fatalln("Failed to generate CA key pair:", err) return nil, fmt.Errorf("Failed to generate CA key pair: %w", err)
} }
gen.tlsCert = caKeyPair gen.tlsCert = caKeyPair
return gen, nil return gen, nil
} }

20
ca_test.go Normal file
View File

@ -0,0 +1,20 @@
package certgen
import (
"crypto/x509/pkix"
"github.com/stretchr/testify/assert"
"math/big"
"testing"
)
func TestMakeCaTls(t *testing.T) {
ca, err := MakeCaTls(2048, pkix.Name{
Country: []string{"GB"},
Organization: []string{"certgen"},
OrganizationalUnit: []string{"test"},
SerialNumber: "1",
CommonName: "certgen.test",
}, big.NewInt(1))
assert.NoError(t, err)
assert.Equal(t, "certgen.test", ca.cert.Subject.CommonName)
}

View File

@ -3,47 +3,70 @@ package certgen
import ( import (
"crypto/rand" "crypto/rand"
"crypto/rsa" "crypto/rsa"
"crypto/sha1"
"crypto/tls" "crypto/tls"
"crypto/x509" "crypto/x509"
"crypto/x509/pkix" "crypto/x509/pkix"
"log" "fmt"
"math/big" "math/big"
"time" "time"
) )
func MakeClientTls(ca *CertGen, name pkix.Name, serialNumber *big.Int) (*CertGen, error) { // MakeClientTls generates a client TLS certificate using a CA to sign it
// If ca is nil then the client will sign its own certificate
func MakeClientTls(ca *CertGen, bits int, name pkix.Name, serialNumber *big.Int, future Future) (*CertGen, error) {
// generate rsa private key
clientPrivKey, err := rsa.GenerateKey(rand.Reader, bits)
if err != nil {
return nil, fmt.Errorf("failed to generate client private key: %w", err)
}
// generate SubjectKeyId from sha1 hash of public key bytes
pubKeyBytes := x509.MarshalPKCS1PublicKey(&clientPrivKey.PublicKey)
pubKeyHash := sha1.Sum(pubKeyBytes)
// base certificate data
now := time.Now()
cert := &x509.Certificate{ cert := &x509.Certificate{
SerialNumber: serialNumber, SerialNumber: serialNumber,
Subject: name, Subject: name,
NotBefore: time.Now(), NotBefore: now,
NotAfter: time.Now().AddDate(10, 0, 0), NotAfter: future(now),
SubjectKeyId: []byte{1, 2, 3, 4, 6}, SubjectKeyId: pubKeyHash[:],
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth}, ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
KeyUsage: x509.KeyUsageDigitalSignature, KeyUsage: x509.KeyUsageDigitalSignature,
} }
clientPrivKey, err := rsa.GenerateKey(rand.Reader, 4096) // use current certificate as CA if nil
if err != nil {
log.Fatalln("Failed to generate client private key:", err)
}
if ca == nil { if ca == nil {
ca = &CertGen{cert: cert, key: clientPrivKey} ca = &CertGen{cert: cert, key: clientPrivKey}
} }
// create certificate bytes
clientBytes, err := x509.CreateCertificate(rand.Reader, cert, ca.cert, clientPrivKey.Public(), ca.key) clientBytes, err := x509.CreateCertificate(rand.Reader, cert, ca.cert, clientPrivKey.Public(), ca.key)
if err != nil { if err != nil {
log.Fatalln("Failed to generate client certificate bytes:", err) return nil, fmt.Errorf("failed to generate client certificate bytes: %w", err)
} }
// add the raw certificate bytes so `*x509.Certificate.Equal(*x509.Certificate)` is valid
cert.Raw = clientBytes
// get private key bytes
privKeyBytes := x509.MarshalPKCS1PrivateKey(clientPrivKey) privKeyBytes := x509.MarshalPKCS1PrivateKey(clientPrivKey)
gen := &CertGen{cert: cert, certBytes: clientBytes, key: clientPrivKey, keyBytes: privKeyBytes} gen := &CertGen{cert: cert, certBytes: clientBytes, key: clientPrivKey, keyBytes: privKeyBytes}
// generate pem blocks
err = gen.generatePem() err = gen.generatePem()
if err != nil { if err != nil {
return nil, err return nil, fmt.Errorf("failed to generate PEM encoding: %w", err)
} }
// generate key pair
caKeyPair, err := tls.X509KeyPair(gen.certPem, gen.keyPem) caKeyPair, err := tls.X509KeyPair(gen.certPem, gen.keyPem)
if err != nil { if err != nil {
log.Fatalln("Failed to generate client key pair:", err) return nil, fmt.Errorf("failed to generate client key pair: %w", err)
} }
gen.tlsCert = caKeyPair gen.tlsCert = caKeyPair
return gen, nil return gen, nil
} }

23
client_test.go Normal file
View File

@ -0,0 +1,23 @@
package certgen
import (
"crypto/x509/pkix"
"github.com/stretchr/testify/assert"
"math/big"
"testing"
"time"
)
func TestMakeClientTls(t *testing.T) {
client, err := MakeClientTls(nil, 2048, pkix.Name{
Country: []string{"GB"},
Organization: []string{"certgen"},
OrganizationalUnit: []string{"test"},
SerialNumber: "2",
CommonName: "certgen.client",
}, big.NewInt(2), func(now time.Time) time.Time {
return now.AddDate(10, 0, 0)
})
assert.NoError(t, err)
assert.Equal(t, "certgen.client", client.cert.Subject.CommonName)
}

6
future.go Normal file
View File

@ -0,0 +1,6 @@
package certgen
import "time"
// Future is a function for converting the current time to a future time
type Future func(now time.Time) time.Time

10
go.mod
View File

@ -1,3 +1,11 @@
module code.mrmelon54.com/melon/certgen module github.com/MrMelon54/certgen
go 1.18 go 1.18
require github.com/stretchr/testify v1.8.4
require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

10
go.sum
View File

@ -0,0 +1,10 @@
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@ -3,50 +3,74 @@ package certgen
import ( import (
"crypto/rand" "crypto/rand"
"crypto/rsa" "crypto/rsa"
"crypto/sha1"
"crypto/tls" "crypto/tls"
"crypto/x509" "crypto/x509"
"crypto/x509/pkix" "crypto/x509/pkix"
"log" "fmt"
"math/big" "math/big"
"net" "net"
"time" "time"
) )
func MakeServerTls(ca *CertGen, name pkix.Name, serialNumber *big.Int, dnsNames []string, ipAddresses []net.IP) (*CertGen, error) { // MakeServerTls generates a server TLS certificate using a CA to sign it
// If ca is nil then the server will sign its own certificate
// dnsNames and ipAddresses can be nil if they are not required on the certificate
func MakeServerTls(ca *CertGen, bits int, name pkix.Name, serialNumber *big.Int, future Future, dnsNames []string, ipAddresses []net.IP) (*CertGen, error) {
// generate rsa private key
serverPrivKey, err := rsa.GenerateKey(rand.Reader, bits)
if err != nil {
return nil, fmt.Errorf("failed to generate server private key: %w", err)
}
// generate SubjectKeyId from sha1 hash of public key bytes
pubKeyBytes := x509.MarshalPKCS1PublicKey(&serverPrivKey.PublicKey)
pubKeyHash := sha1.Sum(pubKeyBytes)
// base certificate data
now := time.Now()
cert := &x509.Certificate{ cert := &x509.Certificate{
SerialNumber: serialNumber, SerialNumber: serialNumber,
Subject: name, Subject: name,
DNSNames: dnsNames, DNSNames: dnsNames,
IPAddresses: ipAddresses, IPAddresses: ipAddresses,
NotBefore: time.Now(), NotBefore: now,
NotAfter: time.Now().AddDate(10, 0, 0), NotAfter: future(now),
SubjectKeyId: []byte{1, 2, 3, 4, 6}, SubjectKeyId: pubKeyHash[:],
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth}, ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
KeyUsage: x509.KeyUsageDigitalSignature, KeyUsage: x509.KeyUsageDigitalSignature,
} }
serverPrivKey, err := rsa.GenerateKey(rand.Reader, 4096) // use current certificate as CA if nil
if err != nil {
log.Fatalln("Failed to generate server private key:", err)
}
if ca == nil { if ca == nil {
ca = &CertGen{cert: cert, key: serverPrivKey} ca = &CertGen{cert: cert, key: serverPrivKey}
} }
// create certificate bytes
serverBytes, err := x509.CreateCertificate(rand.Reader, cert, ca.cert, serverPrivKey.Public(), ca.key) serverBytes, err := x509.CreateCertificate(rand.Reader, cert, ca.cert, serverPrivKey.Public(), ca.key)
if err != nil { if err != nil {
log.Fatalln("Failed to generate server certificate bytes:", err) return nil, fmt.Errorf("failed to generate server certificate bytes: %w", err)
} }
// add the raw certificate bytes so `*x509.Certificate.Equal(*x509.Certificate)` is valid
cert.Raw = serverBytes
// get private key bytes
privKeyBytes := x509.MarshalPKCS1PrivateKey(serverPrivKey) privKeyBytes := x509.MarshalPKCS1PrivateKey(serverPrivKey)
gen := &CertGen{cert: cert, certBytes: serverBytes, key: serverPrivKey, keyBytes: privKeyBytes} gen := &CertGen{cert: cert, certBytes: serverBytes, key: serverPrivKey, keyBytes: privKeyBytes}
// generate pem blocks
err = gen.generatePem() err = gen.generatePem()
if err != nil { if err != nil {
return nil, err return nil, fmt.Errorf("failed to generate PEM encoding: %w", err)
} }
// generate key pair
caKeyPair, err := tls.X509KeyPair(gen.certPem, gen.keyPem) caKeyPair, err := tls.X509KeyPair(gen.certPem, gen.keyPem)
if err != nil { if err != nil {
log.Fatalln("Failed to generate server key pair:", err) return nil, fmt.Errorf("failed to generate server key pair: %w", err)
} }
gen.tlsCert = caKeyPair gen.tlsCert = caKeyPair
return gen, nil return gen, nil
} }

25
server_test.go Normal file
View File

@ -0,0 +1,25 @@
package certgen
import (
"crypto/x509/pkix"
"github.com/stretchr/testify/assert"
"math/big"
"net"
"testing"
"time"
)
func TestMakeServerTls(t *testing.T) {
server, err := MakeServerTls(nil, 2048, pkix.Name{
Country: []string{"GB"},
Organization: []string{"certgen"},
OrganizationalUnit: []string{"test"},
SerialNumber: "2",
CommonName: "certgen.server",
}, big.NewInt(2), func(now time.Time) time.Time {
return now.AddDate(10, 0, 0)
}, []string{"certgen.server", "*.certgen.server"}, []net.IP{net.IPv4(1, 1, 1, 1), net.IPv6loopback})
assert.NoError(t, err)
assert.Equal(t, "certgen.server", server.cert.Subject.CommonName)
assert.Equal(t, []string{"certgen.server", "*.certgen.server"}, server.cert.DNSNames)
}

View File

@ -6,10 +6,13 @@ import (
) )
func TlsLeaf(cert *tls.Certificate) *x509.Certificate { func TlsLeaf(cert *tls.Certificate) *x509.Certificate {
// return the existing leaf
if cert.Leaf != nil { if cert.Leaf != nil {
return cert.Leaf return cert.Leaf
} }
if len(cert.Certificate) >= 1 { if len(cert.Certificate) >= 1 {
// if there is a certificate then validate, parse and set the leaf
if a, err := x509.ParseCertificate(cert.Certificate[0]); err == nil { if a, err := x509.ParseCertificate(cert.Certificate[0]); err == nil {
cert.Leaf = a cert.Leaf = a
} }

22
tls-leaf_test.go Normal file
View File

@ -0,0 +1,22 @@
package certgen
import (
"crypto/x509/pkix"
"github.com/stretchr/testify/assert"
"math/big"
"testing"
)
func TestTlsLeaf(t *testing.T) {
ca, err := MakeCaTls(2048, pkix.Name{
Country: []string{"GB"},
Organization: []string{"certgen"},
OrganizationalUnit: []string{"test"},
SerialNumber: "1",
CommonName: "certgen.test",
}, big.NewInt(1))
assert.NoError(t, err)
leaf := TlsLeaf(&ca.tlsCert)
assert.True(t, leaf.Equal(ca.cert))
}