Skip to content

Commit

Permalink
Add ability to generate a CSR re-using an existing key
Browse files Browse the repository at this point in the history
  • Loading branch information
ghjm committed Jan 20, 2021
1 parent ca33ade commit 24d547e
Show file tree
Hide file tree
Showing 3 changed files with 112 additions and 51 deletions.
35 changes: 21 additions & 14 deletions pkg/certificates/ca.go
Original file line number Diff line number Diff line change
Expand Up @@ -228,10 +228,23 @@ func CreateCA(opts *CertOptions) (*CA, error) {
return ca, nil
}

// CreateCertReq creates a new x.509 certificate request, potentially containing Receptor node ID names.
func CreateCertReq(opts *CertOptions) (*x509.CertificateRequest, *rsa.PrivateKey, error) {
// CreateCertReqWithKey creates a new x.509 certificate request with a newly generated private key.
func CreateCertReqWithKey(opts *CertOptions) (*x509.CertificateRequest, *rsa.PrivateKey, error) {
key, err := rsa.GenerateKey(rand.Reader, opts.Bits)
if err != nil {
return nil, nil, err
}
req, err := CreateCertReq(opts, key)
if err != nil {
return nil, nil, err
}
return req, key, nil
}

// CreateCertReq creates a new x.509 certificate request for an existing private key.
func CreateCertReq(opts *CertOptions, privateKey *rsa.PrivateKey) (*x509.CertificateRequest, error) {
if opts.CommonName == "" {
return nil, nil, fmt.Errorf("must provide CommonName")
return nil, fmt.Errorf("must provide CommonName")
}
if opts.Bits == 0 {
opts.Bits = 2048
Expand All @@ -241,7 +254,7 @@ func CreateCertReq(opts *CertOptions) (*x509.CertificateRequest, *rsa.PrivateKey
var san *pkix.Extension
san, err = utils.MakeReceptorSAN(opts.DNSNames, opts.IPAddresses, opts.NodeIDs)
if err != nil {
return nil, nil, err
return nil, err
}
reqTemplate := &x509.CertificateRequest{
Subject: pkix.Name{
Expand All @@ -250,25 +263,19 @@ func CreateCertReq(opts *CertOptions) (*x509.CertificateRequest, *rsa.PrivateKey
ExtraExtensions: []pkix.Extension{*san},
}

var reqPrivKey *rsa.PrivateKey
reqPrivKey, err = rsa.GenerateKey(rand.Reader, opts.Bits)
if err != nil {
return nil, nil, err
}

var reqBytes []byte
reqBytes, err = x509.CreateCertificateRequest(rand.Reader, reqTemplate, reqPrivKey)
reqBytes, err = x509.CreateCertificateRequest(rand.Reader, reqTemplate, privateKey)
if err != nil {
return nil, nil, err
return nil, err
}

var req *x509.CertificateRequest
req, err = x509.ParseCertificateRequest(reqBytes)
if err != nil {
return nil, nil, err
return nil, err
}

return req, reqPrivKey, nil
return req, nil
}

// GetReqNames returns the names coded into a certificate request, including Receptor node IDs.
Expand Down
124 changes: 89 additions & 35 deletions pkg/certificates/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,12 +52,29 @@ func (ica initCA) Run() error {

type makeReq struct {
CommonName string `description:"Common name to assign to the certificate" required:"Yes"`
Bits int `description:"Bit length of the encryption keys of the certificate" required:"Yes"`
Bits int `description:"Bit length of the encryption keys of the certificate"`
DNSName []string `description:"DNS names to add to the certificate"`
IPAddress []string `description:"IP addresses to add to the certificate"`
NodeID []string `description:"Receptor node IDs to add to the certificate"`
OutReq string `description:"File to save the certificate request to" required:"Yes"`
OutKey string `description:"File to save the private key to" required:"Yes"`
InKey string `description:"Private key to use for the request"`
OutKey string `description:"File to save the private key to (new key will be generated)"`
}

func (mr makeReq) Prepare() error {
if mr.InKey == "" && mr.OutKey == "" {
return fmt.Errorf("must provide either InKey or OutKey")
}
if mr.InKey != "" && mr.OutKey != "" {
return fmt.Errorf("cannot use both InKey and OutKey")
}
if mr.InKey != "" && mr.Bits != 0 {
return fmt.Errorf("cannot specify key bits when reading an already-existing key")
}
if mr.OutKey != "" && mr.Bits == 0 {
return fmt.Errorf("must specify key bits when creating a new key")
}
return nil
}

func (mr makeReq) Run() error {
Expand All @@ -77,14 +94,48 @@ func (mr makeReq) Run() error {
}
opts.IPAddresses = append(opts.IPAddresses, ip)
}
req, key, err := CreateCertReq(opts)
if err == nil {
err = SaveToPEMFile(mr.OutReq, []interface{}{req})
var req *x509.CertificateRequest
var key *rsa.PrivateKey
if mr.InKey != "" {
data, err := LoadFromPEMFile(mr.InKey)
if err != nil {
return err
}
for _, elem := range data {
ckey, ok := elem.(*rsa.PrivateKey)
if !ok {
continue
}
if key != nil {
return fmt.Errorf("multiple private keys in file %s", mr.InKey)
}
key = ckey
}
if key == nil {
return fmt.Errorf("no private keys in file %s", mr.InKey)
}
req, err = CreateCertReq(opts, key)
if err != nil {
return err
}
} else {
var err error
req, key, err = CreateCertReqWithKey(opts)
if err != nil {
return err
}
}
if err == nil {
err := SaveToPEMFile(mr.OutReq, []interface{}{req})
if err != nil {
return err
}
if mr.OutKey != "" {
err = SaveToPEMFile(mr.OutKey, []interface{}{key})
if err != nil {
return err
}
}
return err
return nil
}

type signReq struct {
Expand All @@ -94,6 +145,7 @@ type signReq struct {
NotBefore string `description:"Effective (NotBefore) date/time, in RFC3339 format"`
NotAfter string `description:"Expiration (NotAfter) date/time, in RFC3339 format"`
OutCert string `description:"File to save the signed certificate to" required:"Yes"`
Verify bool `description:"If true, do not prompt the user for verification" default:"False"`
}

func (sr signReq) Run() error {
Expand Down Expand Up @@ -135,34 +187,36 @@ func (sr signReq) Run() error {
if len(names.DNSNames) == 0 && len(names.IPAddresses) == 0 && len(names.NodeIDs) == 0 {
return fmt.Errorf("cannot sign: no names found in certificate")
}
fmt.Printf("Requested certificate:\n")
fmt.Printf(" Subject: %s\n", req.Subject)
algo := req.PublicKeyAlgorithm.String()
if algo == "RSA" {
rpk := req.PublicKey.(*rsa.PublicKey)
algo = fmt.Sprintf("%s (%d bits)", algo, rpk.Size()*8)
}
fmt.Printf(" Encryption Algorithm: %s\n", algo)
fmt.Printf(" Signature Algorithm: %s\n", req.SignatureAlgorithm.String())
fmt.Printf(" Names:\n")
for _, name := range names.DNSNames {
fmt.Printf(" DNS Name: %s\n", name)
}
for _, ip := range names.IPAddresses {
fmt.Printf(" IP Address: %v\n", ip)
}
for _, node := range names.NodeIDs {
fmt.Printf(" Receptor Node ID: %s\n", node)
}
fmt.Printf("Sign certificate (yes/no)? ")
var response string
_, err = fmt.Scanln(&response)
if err != nil {
return err
}
response = strings.ToLower(response)
if response != "y" && response != "yes" {
return fmt.Errorf("user declined")
if !sr.Verify {
fmt.Printf("Requested certificate:\n")
fmt.Printf(" Subject: %s\n", req.Subject)
algo := req.PublicKeyAlgorithm.String()
if algo == "RSA" {
rpk := req.PublicKey.(*rsa.PublicKey)
algo = fmt.Sprintf("%s (%d bits)", algo, rpk.Size()*8)
}
fmt.Printf(" Encryption Algorithm: %s\n", algo)
fmt.Printf(" Signature Algorithm: %s\n", req.SignatureAlgorithm.String())
fmt.Printf(" Names:\n")
for _, name := range names.DNSNames {
fmt.Printf(" DNS Name: %s\n", name)
}
for _, ip := range names.IPAddresses {
fmt.Printf(" IP Address: %v\n", ip)
}
for _, node := range names.NodeIDs {
fmt.Printf(" Receptor Node ID: %s\n", node)
}
fmt.Printf("Sign certificate (yes/no)? ")
var response string
_, err = fmt.Scanln(&response)
if err != nil {
return err
}
response = strings.ToLower(response)
if response != "y" && response != "yes" {
return fmt.Errorf("user declined")
}
}
var cert *x509.Certificate
cert, err = SignCertReq(req, ca, opts)
Expand Down
4 changes: 2 additions & 2 deletions tests/functional/lib/utils/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@ func GenerateCert(name, commonName string, DNSNames, NodeIDs []string) (string,
return "", "", err
}
// Create certificate request
req, key, err := certificates.CreateCertReq(&certificates.CertOptions{
req, key, err := certificates.CreateCertReqWithKey(&certificates.CertOptions{
CommonName: commonName,
Bits: 2048,
CertNames: certificates.CertNames{
Expand Down Expand Up @@ -225,7 +225,7 @@ func GenerateCertWithCA(name, caKeyPath, caCrtPath, commonName string, DNSNames,
keyPath := filepath.Join(dir, name+".key")
crtPath := filepath.Join(dir, name+".crt")
// Create certificate request
req, key, err := certificates.CreateCertReq(&certificates.CertOptions{
req, key, err := certificates.CreateCertReqWithKey(&certificates.CertOptions{
CommonName: commonName,
Bits: 2048,
CertNames: certificates.CertNames{
Expand Down

0 comments on commit 24d547e

Please sign in to comment.