feat: Command to generate a CA.

This commit is contained in:
jmug committed 2026-10-21 00:13:31 -07:00
1 parent b593b083de
commit 76854f4e4b
5 files changed
+120 -1

No files matched your search

+4
View File
@@ -4,8 +4,12 @@ import (
"github.com/spf13/cobra"
)
// Needed by all subcommands.
var Output string
// Needed by some subcommands.
var KeyFile string
func init() {
rootCmd.AddCommand(generateCmd)
+107
View File
@@ -0,0 +1,107 @@
package hmpki
import (
"crypto/ecdsa"
"crypto/rand"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"errors"
"math"
"math/big"
"os"
"time"
"git.handmadecities.com/HMC/shhh/pkg/pki"
"github.com/spf13/cobra"
)
func init() {
generateCmd.AddCommand(generateCACmd)
generateCACmd.Flags().StringVar(
&KeyFile,
"key-file",
"",
"File that contains the private key that will be associated to the CA",
)
generateCACmd.MarkFlagRequired("key-file")
generateCACmd.MarkFlagFilename("key-file")
}
var generateCACmd = &cobra.Command{
Use: "ca",
Short: "Generate a Certificate Authority",
RunE: func(cmd *cobra.Command, args []string) error {
// Read key from KeyFile.
keyPemBytes, err := os.ReadFile(KeyFile)
if err != nil {
return err
}
privBlock, _ := pem.Decode(keyPemBytes)
if privBlock == nil || privBlock.Type != pki.PEMTypePrivateKey {
return errors.New("Invalid pem key file, or first block is not a private key")
}
rawKey, err := x509.ParsePKCS8PrivateKey(privBlock.Bytes)
key, ok := rawKey.(*ecdsa.PrivateKey)
if !ok {
return errors.New("key file did not contain an ecdsa private key")
}
// Cryptographically random 64bit number as the serial.
sn, err := rand.Int(rand.Reader, big.NewInt(math.MaxInt64))
if err != nil {
errors.New("Could not generate a ca serial number")
}
caTmpl := &x509.Certificate{
SerialNumber: sn,
Subject: pkix.Name{
// TODO: Accept common name as a flag.
CommonName: "Some Common Name",
},
// Valid starting now and for 10 years.
NotBefore: time.Now(),
NotAfter: time.Now().AddDate(10, 0, 0),
// This cert is a CA.
IsCA: true,
// No intermeddiate CAs, just one root that can issue leaf certs.
MaxPathLen: 0,
MaxPathLenZero: true,
// Disambiguate `IsCA` and path len settings from their zero value.
BasicConstraintsValid: true,
// Only allow to sign new certs, and cert revokation lists.
// Can't be used as a server or client cert itself.
KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageCRLSign,
}
caBytes, err := x509.CreateCertificate(
rand.Reader,
// Same template and parent, i.e. self-signed.
caTmpl,
caTmpl,
&key.PublicKey,
key,
)
caPEMBytes := pem.EncodeToMemory(&pem.Block{
Type: pki.PEMTypeCertificate,
Bytes: caBytes,
})
caf, err := os.OpenFile(
Output,
// Write only, and fail if it alreay exists.
os.O_WRONLY|os.O_CREATE|os.O_EXCL,
pki.CertFilePerms,
)
if err != nil {
return err
}
defer caf.Close()
if _, err = caf.Write(caPEMBytes); err != nil {
return err
}
// Write ca.crt to Output
return nil
},
}
+1 -1
View File
@@ -31,7 +31,7 @@ var generateKeyCmd = &cobra.Command{
}
pemBytes := pem.EncodeToMemory(&pem.Block{
Type: "PRIVATE KEY",
Type: pki.PEMTypePrivateKey,
Bytes: der,
})
+4
View File
@@ -2,4 +2,8 @@ package pki
import "os"
// RW only by the owner.
const PrivateKeyFilePerms os.FileMode = 0600
// W by owner, R by others.
const CertFilePerms os.FileMode = 0644
+4
View File
@@ -0,0 +1,4 @@
package pki
const PEMTypePrivateKey = "PRIVATE KEY"
const PEMTypeCertificate = "CERTIFICATE"