Last active
June 14, 2023 04:48
-
-
Save tony612/e451d1a47e251f3d568fd9ac102e763b to your computer and use it in GitHub Desktop.
generate x509 cert and key
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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