|
package main |
|
|
|
import ( |
|
"bytes" |
|
"crypto/rand" |
|
"crypto/rsa" |
|
"crypto/sha1" |
|
"crypto/x509" |
|
"crypto/x509/pkix" |
|
"encoding/pem" |
|
"flag" |
|
"fmt" |
|
"io/ioutil" |
|
"log" |
|
"math/big" |
|
"net" |
|
"time" |
|
) |
|
|
|
// CertificateKeyPair contains the public x509 certificate and |
|
// the private RSA key. Signed certificates are stored as Bytes |
|
type CertificateKeyPair struct { |
|
Cert *x509.Certificate |
|
CertBytes []byte |
|
Key *rsa.PrivateKey |
|
} |
|
|
|
func main() { |
|
backendIP := flag.String( |
|
"backip", |
|
"127.0.0.1", |
|
"Backend server address (Go Server)", |
|
) |
|
backendDN := flag.String( |
|
"backdn", |
|
"localhost", |
|
"Backend server domain name (Go Server", |
|
) |
|
frontendIP := flag.String( |
|
"frontip", |
|
"127.0.0.1", |
|
"Frontend server address (proxy)", |
|
) |
|
frontendDN := flag.String( |
|
"frontdn", |
|
"localhost", |
|
"Frontend server domain name (proxy)", |
|
) |
|
flag.Parse() |
|
|
|
// Create a new Certificate Authority |
|
ca, err := NewCertificateAuthority("Local CA") |
|
if err != nil { |
|
log.Fatalln(err) |
|
} |
|
if err := ca.WritePublicCertificate("./ca.crt"); err != nil { |
|
log.Fatalln(err) |
|
} |
|
|
|
// Create a new public certificate and private key for |
|
// the backend server |
|
backend, err := NewServerCertificateKeyPair(*backendIP, *backendDN, ca) |
|
if err != nil { |
|
log.Fatalln(err) |
|
} |
|
if err := backend.WritePublicCertificate("./backend.crt"); err != nil { |
|
log.Fatalln(err) |
|
} |
|
if err := backend.WritePrivateKey("./backend.key"); err != nil { |
|
log.Fatalln(err) |
|
} |
|
|
|
// Create a new public certificate and private key for |
|
// the frontend proxy |
|
frontend, err := NewServerCertificateKeyPair(*frontendIP, *frontendDN, ca) |
|
if err != nil { |
|
log.Fatalln(err) |
|
} |
|
if err := frontend.WritePublicCertificate("./frontend.crt"); err != nil { |
|
log.Fatalln(err) |
|
} |
|
if err := frontend.WritePrivateKey("./frontend.key"); err != nil { |
|
log.Fatalln(err) |
|
} |
|
|
|
// Create a new public certificate and private key for |
|
// the proxy to authenticate to the backend server |
|
frontendClient, err := NewClientCertificateKeyPair("Frontend Client", ca) |
|
if err != nil { |
|
log.Fatalln(err) |
|
} |
|
if err := frontendClient.WritePublicCertificate("./client.crt"); err != nil { |
|
log.Fatalln(err) |
|
} |
|
if err := frontendClient.WritePrivateKey("./client.key"); err != nil { |
|
log.Fatalln(err) |
|
} |
|
} |
|
|
|
// WritePublicCertificate writes a PEM-encoded public |
|
// certificate to the specified file |
|
func (c *CertificateKeyPair) WritePublicCertificate(file string) error { |
|
return c.writeFile(file, &pem.Block{ |
|
Type: "CERTIFICATE", |
|
Bytes: c.CertBytes, |
|
}) |
|
} |
|
|
|
// WritePrivateKey writes a PEM-encoded private key to the |
|
// specified file |
|
func (c *CertificateKeyPair) WritePrivateKey(file string) error { |
|
return c.writeFile(file, &pem.Block{ |
|
Type: "RSA PRIVATE KEY", |
|
Bytes: x509.MarshalPKCS1PrivateKey(c.Key), |
|
}) |
|
} |
|
|
|
func (*CertificateKeyPair) writeFile(file string, block *pem.Block) error { |
|
enc := new(bytes.Buffer) |
|
pem.Encode(enc, block) |
|
err := ioutil.WriteFile(file, enc.Bytes(), 0777) |
|
if err != nil { |
|
return err |
|
} |
|
return nil |
|
} |
|
|
|
// NewCertificateAuthority generates a new certificate authority |
|
// for signing server and client certificates |
|
func NewCertificateAuthority(name string) (*CertificateKeyPair, error) { |
|
log.Println("Generating new CA certificate and key pair...") |
|
serial, err := newSerial() |
|
if err != nil { |
|
return nil, fmt.Errorf("Error generating CA serial: %w", err) |
|
} |
|
|
|
key, err := rsa.GenerateKey(rand.Reader, 4096) |
|
if err != nil { |
|
return nil, fmt.Errorf("Error generating CA private key: %w", err) |
|
} |
|
|
|
cert := &x509.Certificate{ |
|
SerialNumber: serial, |
|
Subject: pkix.Name{ |
|
CommonName: name, |
|
}, |
|
NotBefore: time.Now(), |
|
NotAfter: time.Now().AddDate(10, 0, 0), |
|
SubjectKeyId: hashKeyID(key.N), |
|
KeyUsage: x509.KeyUsageCertSign, |
|
BasicConstraintsValid: true, |
|
IsCA: true, |
|
MaxPathLenZero: true, |
|
} |
|
|
|
ca := &CertificateKeyPair{ |
|
Cert: cert, |
|
Key: key, |
|
} |
|
|
|
ca, err = SignCertificate(ca, ca) |
|
if err != nil { |
|
return ca, fmt.Errorf("Error signing CA certificate: %w", err) |
|
} |
|
return ca, nil |
|
} |
|
|
|
// NewServerCertificateKeyPair generates a new certificate key |
|
// pair for use with web servers |
|
func NewServerCertificateKeyPair(ip string, dn string, ca *CertificateKeyPair) (*CertificateKeyPair, error) { |
|
log.Printf("Generating new server certificate key pair for %s(%s)\n", dn, ip) |
|
serial, err := newSerial() |
|
if err != nil { |
|
return nil, fmt.Errorf("Error generating certificate serial: %w", err) |
|
} |
|
|
|
key, err := rsa.GenerateKey(rand.Reader, 2048) |
|
if err != nil { |
|
return nil, fmt.Errorf("Error generating private key: %w", err) |
|
} |
|
|
|
cert := &x509.Certificate{ |
|
SerialNumber: serial, |
|
Subject: pkix.Name{ |
|
CommonName: dn, |
|
}, |
|
IPAddresses: []net.IP{ |
|
net.ParseIP(ip), |
|
}, |
|
DNSNames: []string{ |
|
dn, |
|
}, |
|
NotBefore: time.Now(), |
|
NotAfter: time.Now().AddDate(1, 0, 0), |
|
SubjectKeyId: hashKeyID(key.N), |
|
AuthorityKeyId: hashKeyID(ca.Key.N), |
|
ExtKeyUsage: []x509.ExtKeyUsage{ |
|
x509.ExtKeyUsageServerAuth, |
|
}, |
|
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment, |
|
} |
|
|
|
pair := &CertificateKeyPair{ |
|
Cert: cert, |
|
Key: key, |
|
} |
|
|
|
pair, err = SignCertificate(pair, ca) |
|
if err != nil { |
|
return pair, fmt.Errorf("Error signing certificate: %w", err) |
|
} |
|
return pair, nil |
|
} |
|
|
|
// NewClientCertificateKeyPair generates a client certificate for |
|
// authenticating and encrypting communications to a server |
|
func NewClientCertificateKeyPair(name string, ca *CertificateKeyPair) (*CertificateKeyPair, error) { |
|
log.Printf("Generating new client certificate key pair for %s\n", name) |
|
serial, err := newSerial() |
|
if err != nil { |
|
return nil, fmt.Errorf("Error generating certificate serial: %w", err) |
|
} |
|
|
|
key, err := rsa.GenerateKey(rand.Reader, 2048) |
|
if err != nil { |
|
return nil, fmt.Errorf("Error generating private key: %w", err) |
|
} |
|
|
|
cert := &x509.Certificate{ |
|
SerialNumber: serial, |
|
Subject: pkix.Name{ |
|
CommonName: name, |
|
}, |
|
NotBefore: time.Now(), |
|
NotAfter: time.Now().AddDate(1, 0, 0), |
|
SubjectKeyId: hashKeyID(key.N), |
|
AuthorityKeyId: hashKeyID(ca.Key.N), |
|
ExtKeyUsage: []x509.ExtKeyUsage{ |
|
x509.ExtKeyUsageClientAuth, |
|
}, |
|
KeyUsage: x509.KeyUsageDigitalSignature, |
|
} |
|
|
|
pair := &CertificateKeyPair{ |
|
Cert: cert, |
|
Key: key, |
|
} |
|
|
|
pair, err = SignCertificate(pair, ca) |
|
if err != nil { |
|
return pair, fmt.Errorf("Error signing certificate: %w", err) |
|
} |
|
return pair, nil |
|
} |
|
|
|
// SignCertificate signs the provided certificate with the |
|
// provided certificate authority |
|
func SignCertificate(cert *CertificateKeyPair, ca *CertificateKeyPair) (*CertificateKeyPair, error) { |
|
bytes, err := x509.CreateCertificate( |
|
rand.Reader, |
|
cert.Cert, |
|
ca.Cert, |
|
&cert.Key.PublicKey, |
|
ca.Key, |
|
) |
|
if err != nil { |
|
return cert, fmt.Errorf("Error signing certificate: %w", err) |
|
} |
|
cert.CertBytes = bytes |
|
return cert, nil |
|
} |
|
|
|
func newSerial() (*big.Int, error) { |
|
max := new(big.Int) |
|
max.Exp( |
|
big.NewInt(2), |
|
big.NewInt(128), |
|
nil, |
|
) |
|
max.Sub( |
|
max, |
|
big.NewInt(1), |
|
) |
|
|
|
n, err := rand.Int(rand.Reader, max) |
|
if err != nil { |
|
return n, err |
|
} |
|
return n, nil |
|
} |
|
|
|
func hashKeyID(n *big.Int) []byte { |
|
h := sha1.New() |
|
h.Write(n.Bytes()) |
|
return h.Sum(nil) |
|
} |