feat: Command to generate a CA.
This commit is contained in:
1 parent
b593b083de
commit
76854f4e4b
5 files changed
+120
-1
No files matched your search
@@ -4,8 +4,12 @@ import (
|
|||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Needed by all subcommands.
|
||||||
var Output string
|
var Output string
|
||||||
|
|
||||||
|
// Needed by some subcommands.
|
||||||
|
var KeyFile string
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
rootCmd.AddCommand(generateCmd)
|
rootCmd.AddCommand(generateCmd)
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
},
|
||||||
|
}
|
||||||
@@ -31,7 +31,7 @@ var generateKeyCmd = &cobra.Command{
|
|||||||
}
|
}
|
||||||
|
|
||||||
pemBytes := pem.EncodeToMemory(&pem.Block{
|
pemBytes := pem.EncodeToMemory(&pem.Block{
|
||||||
Type: "PRIVATE KEY",
|
Type: pki.PEMTypePrivateKey,
|
||||||
Bytes: der,
|
Bytes: der,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -2,4 +2,8 @@ package pki
|
|||||||
|
|
||||||
import "os"
|
import "os"
|
||||||
|
|
||||||
|
// RW only by the owner.
|
||||||
const PrivateKeyFilePerms os.FileMode = 0600
|
const PrivateKeyFilePerms os.FileMode = 0600
|
||||||
|
|
||||||
|
// W by owner, R by others.
|
||||||
|
const CertFilePerms os.FileMode = 0644
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
package pki
|
||||||
|
|
||||||
|
const PEMTypePrivateKey = "PRIVATE KEY"
|
||||||
|
const PEMTypeCertificate = "CERTIFICATE"
|
||||||
Reference in new issue
Block a user