add csr support

This adds support for certificate sign requsts.
This commit is contained in:
Gibheer 2017-06-03 22:01:55 +02:00
parent b9456bfd8b
commit 08f39fad0a
4 changed files with 332 additions and 4 deletions

130
cmd/pkiadm/csr.go Normal file
View File

@ -0,0 +1,130 @@
package main
import (
"encoding/base64"
"fmt"
"net"
"os"
"strings"
"text/tabwriter"
"github.com/gibheer/pkiadm"
"github.com/pkg/errors"
flag "github.com/spf13/pflag"
)
func createCSR(args []string, client *pkiadm.Client) error {
fs := flag.NewFlagSet("create-csr", flag.ExitOnError)
fs.Usage = func() {
fmt.Printf("Usage of %s:\n", "pkiadm create-csr")
fmt.Println(`
Create a new certificate sign request. This request can be signed by a CA to create a new certificate.
FQDNs, mail addresses and ips can be set multiple times or once as a comma separated list.
`)
fs.PrintDefaults()
}
csr := pkiadm.CSR{}
fs.StringVar(&csr.ID, "id", "", "set the unique id for the new private key")
parseSubject(fs, args, &csr)
if err := client.CreateCSR(csr); err != nil {
return errors.Wrap(err, "could not create private key")
}
return nil
}
func setCSR(args []string, client *pkiadm.Client) error {
fs := flag.NewFlagSet("set-csr", flag.ExitOnError)
csr := pkiadm.CSR{}
fs.StringVar(&csr.ID, "id", "", "set the id of the CSR to adjust")
parseSubject(fs, args, &csr)
fieldList := []string{}
for _, field := range []string{"private-key", "subject", "ip", "fqdn", "mail"} {
flag := fs.Lookup(field)
if flag.Changed {
fieldList = append(fieldList, field)
}
}
if err := client.SetCSR(csr, fieldList); err != nil {
return err
}
return nil
}
func parseSubject(fs *flag.FlagSet, args []string, csr *pkiadm.CSR) {
fs.StringSliceVar(&csr.DNSNames, "fqdn", []string{}, "assign the FQDNs")
fs.StringSliceVar(&csr.EmailAddresses, "mail", []string{}, "assign the mail addresses")
fs.IPSliceVar(&csr.IPAddresses, "ip", []net.IP{}, "assign the ips")
pk := fs.String("private-key", "", "set the id of the private key to sign the request")
subject := fs.String("subject", "", "set the id of the subject to use for this request")
fs.Parse(args)
csr.PrivateKey = pkiadm.ResourceName{*pk, pkiadm.RTPrivateKey}
csr.Subject = pkiadm.ResourceName{*subject, pkiadm.RTSubject}
}
func deleteCSR(args []string, client *pkiadm.Client) error {
fs := flag.NewFlagSet("delete-csr", flag.ExitOnError)
var id = fs.String("id", "", "set the id of the csr to delete")
fs.Parse(args)
if err := client.DeleteCSR(*id); err != nil {
return err
}
return nil
}
func listCSR(args []string, client *pkiadm.Client) error {
fs := flag.NewFlagSet("list-csr", flag.ExitOnError)
fs.Parse(args)
csrs, err := client.ListCSR()
if err != nil {
return err
}
if len(csrs) == 0 {
return nil
}
out := tabwriter.NewWriter(os.Stdout, 2, 2, 1, ' ', tabwriter.AlignRight)
fmt.Fprintf(out, "%s\t%s\t%s\t%s\t%s\t%s\t\n", "id", "private-key", "subject", "names", "ips", "mails")
for _, csr := range csrs {
fmt.Fprintf(
out,
"%s\t%s\t%s\t%d\t%d\t%d\t\n",
csr.ID,
csr.PrivateKey.ID,
csr.Subject.ID,
len(csr.DNSNames),
len(csr.IPAddresses),
len(csr.EmailAddresses),
)
}
out.Flush()
return nil
}
func showCSR(args []string, client *pkiadm.Client) error {
fs := flag.NewFlagSet("show-private", flag.ExitOnError)
var id = fs.String("id", "", "set the id of the private key to show")
fs.Parse(args)
csr, err := client.ShowCSR(*id)
if err != nil {
return err
}
ips := []string{}
for _, ip := range csr.IPAddresses {
ips = append(ips, ip.String())
}
out := tabwriter.NewWriter(os.Stdout, 2, 2, 1, ' ', tabwriter.AlignRight)
fmt.Fprintf(out, "ID:\t%s\t\n", csr.ID)
fmt.Fprintf(out, "private:\t%s\t\n", csr.PrivateKey.ID)
fmt.Fprintf(out, "subject:\t%s\t\n", csr.Subject.ID)
fmt.Fprintf(out, "fqdn:\t%s\t\n", ReplaceEmpty(strings.Join(csr.DNSNames, ", ")))
fmt.Fprintf(out, "ip:\t%s\t\n", ReplaceEmpty(strings.Join(ips, ", ")))
fmt.Fprintf(out, "mail:\t%s\t\n", ReplaceEmpty(strings.Join(csr.EmailAddresses, ", ")))
fmt.Fprintf(out, "checksum:\t%s\t\n", base64.StdEncoding.EncodeToString(csr.Checksum))
out.Flush()
return nil
}

View File

@ -50,6 +50,16 @@ func main() {
err = setPrivateKey(args, client)
case `show-private`:
err = showPrivateKey(args, client)
case `create-public`:
err = createPublicKey(args, client)
case `delete-public`:
err = deletePublicKey(args, client)
case `list-public`:
err = listPublicKey(args, client)
case `set-public`:
err = setPublicKey(args, client)
case `show-public`:
err = showPublicKey(args, client)
case `create-location`:
err = createLocation(args, client)
case `delete-location`:
@ -60,6 +70,16 @@ func main() {
err = setLocation(args, client)
case `show-location`:
err = showLocation(args, client)
case `create-csr`:
err = createCSR(args, client)
case `delete-csr`:
err = deleteCSR(args, client)
case `list-csr`:
err = listCSR(args, client)
case `set-csr`:
err = setCSR(args, client)
case `show-csr`:
err = showCSR(args, client)
default:
fmt.Printf("unknown subcommand '%s'\n", cmd)
printCommands()

View File

@ -2,6 +2,7 @@ package main
import (
"encoding/pem"
"fmt"
"net"
"github.com/gibheer/pki"
@ -14,7 +15,6 @@ type (
ID string
// The following options are used to generate the content of the CSR.
CommonName string
DNSNames []string
EmailAddresses []string
IPAddresses []net.IP
@ -29,12 +29,11 @@ type (
)
// NewCSR creates a new CSR.
func NewCSR(id string, pk, subject pkiadm.ResourceName, commonName string, dnsNames []string,
func NewCSR(id string, pk, subject pkiadm.ResourceName, dnsNames []string,
emailAddresses []string, iPAddresses []net.IP) (*CSR, error) {
return &CSR{
ID: id,
Subject: subject,
CommonName: commonName,
DNSNames: dnsNames,
EmailAddresses: emailAddresses,
IPAddresses: iPAddresses,
@ -63,7 +62,6 @@ func (c *CSR) Refresh(lookup *Storage) error {
return err
}
subject := subjRes.GetName()
subject.CommonName = c.CommonName
opts := pki.CertificateData{
Subject: subject,
@ -102,3 +100,113 @@ func (c *CSR) GetCSR() (*pki.CertificateRequest, error) {
}
return csr, nil
}
func (s *Server) CreateCSR(inCSR pkiadm.CSR, res *pkiadm.Result) error {
s.lock()
defer s.unlock()
csr, err := NewCSR(
inCSR.ID,
inCSR.PrivateKey,
inCSR.Subject,
inCSR.DNSNames,
inCSR.EmailAddresses,
inCSR.IPAddresses,
)
if err != nil {
res.SetError(err, "Could not create new private key '%s'", inCSR.ID)
return nil
}
if err := s.storage.AddCSR(csr); err != nil {
res.SetError(err, "Could not add private key '%s'", inCSR.ID)
return nil
}
return s.store(res)
}
func (s *Server) SetCSR(changeset pkiadm.CSRChange, res *pkiadm.Result) error {
s.lock()
defer s.unlock()
csr, err := s.storage.GetCSR(pkiadm.ResourceName{ID: changeset.CSR.ID, Type: pkiadm.RTCSR})
if err != nil {
res.SetError(err, "Could not find private key '%s'", changeset.CSR.ID)
return nil
}
change := changeset.CSR
for _, field := range changeset.FieldList {
switch field {
case "private-key":
csr.PrivateKey = change.PrivateKey
case "subject":
csr.Subject = change.Subject
case "ip":
csr.IPAddresses = change.IPAddresses
case "fqdn":
csr.DNSNames = change.DNSNames
case "mail":
csr.EmailAddresses = change.EmailAddresses
default:
res.SetError(fmt.Errorf("unknown field"), "unknown field '%s'", field)
return nil
}
}
if err := s.storage.Update(pkiadm.ResourceName{ID: csr.ID, Type: pkiadm.RTCSR}); err != nil {
res.SetError(err, "Could not update private key '%s'", changeset.CSR.ID)
return nil
}
return s.store(res)
}
func (s *Server) DeleteCSR(inCSR pkiadm.ResourceName, res *pkiadm.Result) error {
s.lock()
defer s.unlock()
csr, err := s.storage.GetCSR(pkiadm.ResourceName{ID: inCSR.ID, Type: pkiadm.RTCSR})
if err != nil {
res.SetError(err, "Could not find private key '%s'", inCSR.ID)
return nil
}
if err := s.storage.Remove(csr); err != nil {
res.SetError(err, "Could not remove private key '%s'", csr.ID)
return nil
}
return s.store(res)
}
func (s *Server) ShowCSR(inCSR pkiadm.ResourceName, res *pkiadm.ResultCSR) error {
s.lock()
defer s.unlock()
csr, err := s.storage.GetCSR(pkiadm.ResourceName{ID: inCSR.ID, Type: pkiadm.RTCSR})
if err != nil {
res.Result.SetError(err, "Could not find private key '%s'", inCSR.ID)
return nil
}
res.CSRs = []pkiadm.CSR{pkiadm.CSR{
ID: csr.ID,
Subject: csr.Subject,
PrivateKey: csr.PrivateKey,
EmailAddresses: csr.EmailAddresses,
DNSNames: csr.DNSNames,
IPAddresses: csr.IPAddresses,
Checksum: csr.Checksum(),
}}
return nil
}
func (s *Server) ListCSR(filter pkiadm.Filter, res *pkiadm.ResultCSR) error {
s.lock()
defer s.unlock()
for _, csr := range s.storage.CSRs {
res.CSRs = append(res.CSRs, pkiadm.CSR{
ID: csr.ID,
Subject: csr.Subject,
PrivateKey: csr.PrivateKey,
EmailAddresses: csr.EmailAddresses,
DNSNames: csr.DNSNames,
IPAddresses: csr.IPAddresses,
Checksum: csr.Checksum(),
})
}
return nil
}

70
csr.go Normal file
View File

@ -0,0 +1,70 @@
package pkiadm
import (
"net"
)
type (
CSR struct {
// ID is the unique identifier of the CSR.
ID string
// The following options are used to generate the content of the CSR.
DNSNames []string
EmailAddresses []string
IPAddresses []net.IP
// PrivateKey is needed to sign the certificate sign request.
PrivateKey ResourceName
Subject ResourceName
// Checksum provides the checksum of the CSR on the server.
Checksum []byte
}
CSRChange struct {
CSR CSR
FieldList []string
}
ResultCSR struct {
Result Result
CSRs []CSR
}
)
func (c *Client) CreateCSR(pk CSR) error {
return c.exec("CreateCSR", pk)
}
func (c *Client) SetCSR(pk CSR, fieldList []string) error {
changeset := CSRChange{pk, fieldList}
return c.exec("SetCSR", changeset)
}
func (c *Client) DeleteCSR(id string) error {
pk := ResourceName{ID: id, Type: RTCSR}
return c.exec("DeleteCSR", pk)
}
func (c *Client) ListCSR() ([]CSR, error) {
result := &ResultCSR{}
if err := c.query("ListCSR", Filter{}, result); err != nil {
return []CSR{}, err
}
if result.Result.HasError {
return []CSR{}, result.Result.Error
}
return result.CSRs, nil
}
func (c *Client) ShowCSR(id string) (CSR, error) {
csr := ResourceName{ID: id, Type: RTCSR}
result := &ResultCSR{}
if err := c.query("ShowCSR", csr, result); err != nil {
return CSR{}, err
}
if result.Result.HasError {
return CSR{}, result.Result.Error
}
for _, csr := range result.CSRs {
return csr, nil
}
return CSR{}, nil
}