diff --git a/cmd/pkiadm/csr.go b/cmd/pkiadm/csr.go new file mode 100644 index 0000000..c9dd45b --- /dev/null +++ b/cmd/pkiadm/csr.go @@ -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 +} diff --git a/cmd/pkiadm/main.go b/cmd/pkiadm/main.go index 85f98cc..9395836 100644 --- a/cmd/pkiadm/main.go +++ b/cmd/pkiadm/main.go @@ -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() diff --git a/cmd/pkiadmd/csr.go b/cmd/pkiadmd/csr.go index 3f08602..25de513 100644 --- a/cmd/pkiadmd/csr.go +++ b/cmd/pkiadmd/csr.go @@ -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 +} diff --git a/csr.go b/csr.go new file mode 100644 index 0000000..9a718dc --- /dev/null +++ b/csr.go @@ -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 +}