blob: 5079c548fe7f5aafc1db5c68e1825c7f4d9f2372 [file] [log] [blame]
package main
// Minimal GnuTLS certtool-like tool in Go. Implements only what's needed for
// compatibility with `swtpm_localca`.
import (
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"encoding/pem"
"log"
"math/big"
"os"
"strconv"
"strings"
"time"
"github.com/spf13/pflag"
)
var (
flagGeneratePrivkey bool
flagGenerateSelfSigned bool
flagGenerateCertificate bool
flagOutfile string
flagTemplate string
flagLoadPrivkey string
flagLoadCAPrivkey string
flagLoadCACertificate string
)
func main() {
pflag.BoolVar(&flagGeneratePrivkey, "generate-privkey", false, "Generate RSA private kay")
pflag.BoolVar(&flagGenerateSelfSigned, "generate-self-signed", false, "Generate self-signed certificate")
pflag.BoolVar(&flagGenerateCertificate, "generate-certificate", false, "Sign certificate")
pflag.StringVar(&flagOutfile, "outfile", "", "Output file for operation")
pflag.StringVar(&flagTemplate, "template", "", "Certificate template file (GnuTLS proprietary)")
pflag.StringVar(&flagLoadPrivkey, "load-privkey", "", "Path to private key")
pflag.StringVar(&flagLoadCAPrivkey, "load-ca-privkey", "", "Path to CA private key")
pflag.StringVar(&flagLoadCACertificate, "load-ca-certificate", "", "Path to CA certificate")
pflag.Parse()
modesActive := 0
for _, mode := range []bool{flagGeneratePrivkey, flagGenerateSelfSigned, flagGenerateCertificate} {
if mode {
modesActive++
}
}
if modesActive != 1 {
log.Fatalf("Exactly one of --generate-privkey, --generate-self-signed, --generate-certificate must be set")
}
if flagGeneratePrivkey || flagGenerateSelfSigned || flagGenerateCertificate {
if flagOutfile == "" {
log.Fatalf("--outfile must be set")
}
}
if flagGenerateSelfSigned || flagGenerateCertificate {
if flagTemplate == "" {
log.Fatalf("--template must be set")
}
if flagLoadPrivkey == "" {
log.Fatalf("--load-privkey must be set")
}
if flagOutfile == "" {
log.Fatalf("--outfile must be set")
}
}
if flagGenerateCertificate {
if flagLoadCAPrivkey == "" {
log.Fatalf("--load-ca-privkey must be set")
}
if flagLoadCACertificate == "" {
log.Fatalf("--load-ca-certificate must be set")
}
}
switch {
case flagGeneratePrivkey:
generatePrivkey(flagOutfile)
case flagGenerateSelfSigned:
generateSelfSigned(flagTemplate, flagLoadPrivkey, flagOutfile)
case flagGenerateCertificate:
generateCertificate(flagTemplate, flagLoadPrivkey, flagLoadCAPrivkey, flagLoadCACertificate, flagOutfile)
}
}
func generatePrivkey(outfile string) {
priv, err := rsa.GenerateKey(rand.Reader, 3072)
if err != nil {
log.Fatalf("Could not generate RSA key: %v", err)
}
block := pem.Block{
Type: "RSA PRIVATE KEY",
Bytes: x509.MarshalPKCS1PrivateKey(priv),
}
if err := os.WriteFile(outfile, pem.EncodeToMemory(&block), 0600); err != nil {
log.Fatalf("Could not write RSA key: %v", err)
}
}
// certificateFromTemplate parses a GnuTLS 'template' file. This template file is
// made up of newline-separated stanzas, with an optional 'data' part after a =
// character.
//
// Supported stanzas (on conflict last stanza wins):
//
// cn=data: set subject CN to data
// ca: mark certificate as CA
// cert_signing_key: enable keyCertSign KeyUsage
// expiration_days: number of days in which cert expires (if -1/default, no expiry date)
func certificateFromTemplate(template string) *x509.Certificate {
serial, err := rand.Int(rand.Reader, big.NewInt(1).Lsh(big.NewInt(1), 128))
if err != nil {
log.Fatalf("Could not generate serial: %v", err)
}
res := x509.Certificate{
SerialNumber: serial,
NotBefore: time.Now().Add(-time.Minute),
BasicConstraintsValid: true,
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment,
}
for _, line := range strings.Split(template, "\n") {
line = strings.TrimSpace(line)
if line == "" {
continue
}
parts := strings.SplitN(line, "=", 2)
for i := range parts {
parts[i] = strings.TrimSpace(parts[i])
}
switch parts[0] {
case "cn":
res.Subject.CommonName = parts[1]
case "ca":
res.IsCA = true
case "cert_signing_key":
res.KeyUsage |= x509.KeyUsageCertSign
case "expiration_days":
days, err := strconv.ParseInt(parts[1], 10, 64)
if err != nil {
log.Fatalf("Invalid expiration_days: %q", err)
}
if days != -1 {
res.NotAfter = time.Now().Add(time.Hour * 24 * time.Duration(days))
} else {
res.NotAfter = time.Unix(253402300799, 0)
}
default:
log.Fatalf("Unhandled template line %q", line)
}
}
return &res
}
func readPrivkey(path string) *rsa.PrivateKey {
bytes, err := os.ReadFile(path)
if err != nil {
log.Fatalf("Could not read private key: %v", err)
}
block, _ := pem.Decode(bytes)
if block.Type != "RSA PRIVATE KEY" {
log.Fatalf("Private key contains invalid PEM data")
}
res, err := x509.ParsePKCS1PrivateKey(block.Bytes)
if err != nil {
log.Fatalf("Could not parse private key: %v", err)
}
return res
}
func readCertificate(path string) *x509.Certificate {
bytes, err := os.ReadFile(path)
if err != nil {
log.Fatalf("Could not read certificate: %v", err)
}
block, _ := pem.Decode(bytes)
if block.Type != "CERTIFICATE" {
log.Fatalf("Certificate contains invalid PEM data")
}
res, err := x509.ParseCertificate(block.Bytes)
if err != nil {
log.Fatalf("Could not parse certificate: %v", err)
}
return res
}
func generateSelfSigned(templatePath, privkeyPath, outfile string) {
template, err := os.ReadFile(templatePath)
if err != nil {
log.Fatalf("Could not read template: %v", err)
}
priv := readPrivkey(privkeyPath)
cert := certificateFromTemplate(string(template))
derBytes, err := x509.CreateCertificate(rand.Reader, cert, cert, priv.Public(), priv)
if err != nil {
log.Fatalf("Could not generate self-signed certificate: %v", err)
}
block := pem.Block{
Type: "CERTIFICATE",
Bytes: derBytes,
}
if err := os.WriteFile(outfile, pem.EncodeToMemory(&block), 0600); err != nil {
log.Fatalf("Could not write self-signed certificate: %v", err)
}
}
func generateCertificate(templatePath, privkeyPath, caPrivkeyPath, caCertificatePath, outfile string) {
template, err := os.ReadFile(templatePath)
if err != nil {
log.Fatalf("Could not read template: %v", err)
}
priv := readPrivkey(privkeyPath)
caPriv := readPrivkey(caPrivkeyPath)
cert := certificateFromTemplate(string(template))
ca := readCertificate(caCertificatePath)
derBytes, err := x509.CreateCertificate(rand.Reader, cert, ca, priv.Public(), caPriv)
if err != nil {
log.Fatalf("Could not generate certificate: %v", err)
}
block := pem.Block{
Type: "CERTIFICATE",
Bytes: derBytes,
}
if err := os.WriteFile(outfile, pem.EncodeToMemory(&block), 0600); err != nil {
log.Fatalf("Could not write certificate: %v", err)
}
}