Skip to content

Instantly share code, notes, and snippets.

@tony612
Last active June 14, 2023 04:48
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save tony612/e451d1a47e251f3d568fd9ac102e763b to your computer and use it in GitHub Desktop.
Save tony612/e451d1a47e251f3d568fd9ac102e763b to your computer and use it in GitHub Desktop.
generate x509 cert and key
package main
import (
"bytes"
"crypto"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"flag"
"fmt"
"io"
"math/big"
"net"
"os"
"strings"
"time"
)
var serialNumberLimit = new(big.Int).Lsh(big.NewInt(1), 128)
var cn = flag.String("cn", "", "subject CN")
var sanDNS = flag.String("san-dns", "", "DNS SAN. separated by ,")
var sanIP = flag.String("san-ip", "", "IP SAN. separated by ,")
var ca = flag.Bool("ca", false, "gen CA cert")
var caCert = flag.String("ca-cert", "", "ca cert path")
var caKey = flag.String("ca-key", "", "ca cert path")
func main() {
flag.Parse()
name := pkix.Name{}
if len(*cn) != 0 {
name.CommonName = *cn
}
var ips []net.IP
for _, i := range strings.Split(*sanIP, ",") {
ip := net.ParseIP(i)
if ip != nil {
ips = append(ips, ip)
}
}
var certTmpl *x509.Certificate
var err error
if *ca {
certTmpl, err = BuildCaCert(pkix.Name{
CommonName: *cn,
}, strings.Split(*sanDNS, ","), ips)
} else {
certTmpl, err = BuildCert(pkix.Name{
CommonName: *cn,
}, strings.Split(*sanDNS, ","), ips)
}
check(err)
pk, err := GeneratePrivateKey()
check(err)
var certPem []byte
var cert *x509.Certificate
if len(*caCert) != 0 && len(*caKey) != 0 {
// read ca
caPem, err := os.ReadFile(*caCert)
check(err)
pemBlock, _ := pem.Decode(caPem)
caCert, err := x509.ParseCertificate(pemBlock.Bytes)
check(err)
caKeyPem, err := os.ReadFile(*caKey)
check(err)
caKey, err := DecodePrivateKey(caKeyPem)
check(err)
// Using existed CA to sign
certPem, cert, err = SignCertificate(certTmpl, caCert, pk.Public(), caKey)
check(err)
} else if len(*caCert) == 0 && len(*caKey) == 0 {
// Self sign
certPem, cert, err = SignCertificate(certTmpl, certTmpl, pk.Public(), pk)
check(err)
} else {
panic("ca-cert and ca-key should be provided or not provided at the same time")
}
roots := x509.NewCertPool()
ok := roots.AppendCertsFromPEM(certPem)
if !ok {
panic("failed to parse root certificate")
}
_, err = cert.Verify(x509.VerifyOptions{
Roots: roots,
})
if err != nil {
check(err)
}
// write pk
file, err := os.OpenFile("key.pem", os.O_CREATE|os.O_RDWR, 0400)
check(err)
err = EncodePrivateKey(pk, file)
check(err)
// write cert
err = os.WriteFile("cert.pem", certPem, 0444)
check(err)
}
func BuildCaCert(subject pkix.Name, dns []string, ips []net.IP) (*x509.Certificate, error) {
cert, err := CertTemplate(subject)
if err != nil {
return nil, err
}
cert.IsCA = true
cert.KeyUsage = cert.KeyUsage | x509.KeyUsageCertSign
cert.ExtKeyUsage = []x509.ExtKeyUsage{}
cert.DNSNames = dns
cert.IPAddresses = ips
return cert, nil
}
func BuildCert(subject pkix.Name, dns []string, ips []net.IP) (*x509.Certificate, error) {
cert, err := CertTemplate(subject)
if err != nil {
return nil, err
}
cert.DNSNames = dns
cert.IPAddresses = ips
return cert, nil
}
func CertTemplate(subject pkix.Name) (*x509.Certificate, error) {
serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
if err != nil {
return nil, err
}
cert := &x509.Certificate{
Version: 2,
BasicConstraintsValid: true,
SerialNumber: serialNumber,
Subject: subject,
PublicKeyAlgorithm: x509.ECDSA,
NotBefore: time.Now(),
NotAfter: time.Now().Add(time.Hour * 24 * 365),
// If CA, change below later
IsCA: false,
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDataEncipherment | x509.KeyUsageDigitalSignature,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth},
}
return cert, nil
}
func GeneratePrivateKey() (*ecdsa.PrivateKey, error) {
ecCurve := elliptic.P256()
return ecdsa.GenerateKey(ecCurve, rand.Reader)
}
func SignCertificate(template *x509.Certificate, issuerCert *x509.Certificate, publicKey crypto.PublicKey, signerKey interface{}) ([]byte, *x509.Certificate, error) {
derBytes, err := x509.CreateCertificate(rand.Reader, template, issuerCert, publicKey, signerKey)
if err != nil {
return nil, nil, fmt.Errorf("error creating x509 certificate: %s", err.Error())
}
cert, err := x509.ParseCertificate(derBytes)
if err != nil {
return nil, nil, fmt.Errorf("error decoding DER certificate bytes: %s", err.Error())
}
pemBytes := bytes.NewBuffer([]byte{})
err = EncodeCert(derBytes, pemBytes)
if err != nil {
return nil, nil, fmt.Errorf("error encoding certificate PEM: %s", err.Error())
}
return pemBytes.Bytes(), cert, err
}
func EncodeCert(data []byte, out io.Writer) error {
err := pem.Encode(out, &pem.Block{Type: "CERTIFICATE", Bytes: data})
if err != nil {
return err
}
return nil
}
func EncodePrivateKey(key *ecdsa.PrivateKey, out io.Writer) error {
data, err := x509.MarshalECPrivateKey(key)
if err != nil {
return err
}
err = pem.Encode(out, &pem.Block{Type: "EC PRIVATE KEY", Bytes: data})
if err != nil {
return err
}
return nil
}
func DecodePrivateKey(bytes []byte) (*ecdsa.PrivateKey, error) {
pemBlock, _ := pem.Decode(bytes)
if pemBlock == nil {
return nil, fmt.Errorf("fail to decode PK to pem")
}
pk, err := x509.ParseECPrivateKey(pemBlock.Bytes)
if err != nil {
return nil, err
}
return pk, err
}
func BundlePem(pems ...[]byte) []byte {
return bytes.Join(pems, []byte{})
}
func check(err error) {
if err != nil {
panic(err)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment