From 0586796bf5c36db9746e31c605150abe29236fc3 Mon Sep 17 00:00:00 2001 From: Gibheer Date: Tue, 6 Jun 2017 23:03:42 +0200 Subject: [PATCH] add basic certificate support This is the first draft of the basic certificate support. Some fields need to be shown when inspecting a certificate, but it should work fine for now. --- certificate.go | 70 +++++++++++++++++++++ cmd/pkiadm/certificate.go | 119 +++++++++++++++++++++++++++++++++++ cmd/pkiadmd/certificate.go | 124 ++++++++++++++++++++++++++++++++++++- 3 files changed, 311 insertions(+), 2 deletions(-) create mode 100644 certificate.go create mode 100644 cmd/pkiadm/certificate.go diff --git a/certificate.go b/certificate.go new file mode 100644 index 0000000..d934e9c --- /dev/null +++ b/certificate.go @@ -0,0 +1,70 @@ +package pkiadm + +import ( + "time" +) + +type ( + Certificate struct { + ID string + + IsCA bool + Duration time.Duration + + PrivateKey ResourceName + Serial ResourceName + CSR ResourceName + CA ResourceName + + // Checksum is filled by the server with the checksum of the currently valid + // certificate. + Checksum []byte + } + + CertificateChange struct { + Certificate Certificate + FieldList []string + } + + ResultCertificate struct { + Result Result + Certificates []Certificate + } +) + +// CreatePrivateKey sends a RPC request to create a new private key. +func (c *Client) CreateCertificate(pk Certificate) error { + return c.exec("CreateCertificate", pk) +} +func (c *Client) SetCertificate(pk Certificate, fieldList []string) error { + changeset := CertificateChange{pk, fieldList} + return c.exec("SetCertificate", changeset) +} +func (c *Client) DeleteCertificate(id string) error { + pk := ResourceName{ID: id, Type: RTCertificate} + return c.exec("DeleteCertificate", pk) +} +func (c *Client) ListCertificate() ([]Certificate, error) { + result := &ResultCertificate{} + if err := c.query("ListCertificate", Filter{}, result); err != nil { + return []Certificate{}, err + } + if result.Result.HasError { + return []Certificate{}, result.Result.Error + } + return result.Certificates, nil +} +func (c *Client) ShowCertificate(id string) (Certificate, error) { + pk := ResourceName{ID: id, Type: RTCertificate} + result := &ResultCertificate{} + if err := c.query("ShowCertificate", pk, result); err != nil { + return Certificate{}, err + } + if result.Result.HasError { + return Certificate{}, result.Result.Error + } + for _, privateKey := range result.Certificates { + return privateKey, nil + } + return Certificate{}, nil +} diff --git a/cmd/pkiadm/certificate.go b/cmd/pkiadm/certificate.go new file mode 100644 index 0000000..6c5167c --- /dev/null +++ b/cmd/pkiadm/certificate.go @@ -0,0 +1,119 @@ +package main + +import ( + "encoding/base64" + "fmt" + "os" + "text/tabwriter" + "time" + + "github.com/gibheer/pkiadm" + "github.com/pkg/errors" + flag "github.com/spf13/pflag" +) + +func createCertificate(args []string, client *pkiadm.Client) error { + fs := flag.NewFlagSet("create-cert", flag.ExitOnError) + fs.Usage = func() { + fmt.Printf("Usage of %s:\n", "pkiadm create-cert") + fmt.Println(` +This command creates a new certificate and signes it with the provided CA. If you want to buid your own CA, add the self-sign option and leave the ca option blank. +`) + fs.PrintDefaults() + } + cert := pkiadm.Certificate{} + fs.StringVar(&cert.ID, "id", "", "set the unique id for the new certificate") + parseCertificateArgs(fs, args, &cert) + + if err := client.CreateCertificate(cert); err != nil { + return errors.Wrap(err, "could not create certificate") + } + + return nil +} +func setCertificate(args []string, client *pkiadm.Client) error { + fs := flag.NewFlagSet("set-cert", flag.ExitOnError) + cert := pkiadm.Certificate{} + fs.StringVar(&cert.ID, "id", "", "set the id of the certificate to change") + parseCertificateArgs(fs, args, &cert) + + fieldList := []string{} + for _, field := range []string{"private", "csr", "ca", "serial", "duration", "self-sign"} { + flag := fs.Lookup(field) + if flag.Changed { + fieldList = append(fieldList, field) + } + } + + if err := client.SetCertificate(cert, fieldList); err != nil { + return err + } + return nil +} +func parseCertificateArgs(fs *flag.FlagSet, args []string, cert *pkiadm.Certificate) { + pk := flag.String("private", "", "the private key id to sign the certificate sign request") + csr := flag.String("csr", "", "the CSR to sign to get the resulting certificate") + ca := flag.String("ca", "", "the certificate to use to sign the certificate sign request") + serial := flag.String("serial", "", "the serial generator used to fetch a serial") + flag.DurationVar(&cert.Duration, "duration", time.Duration(1461190656), "the time the certificate is valid (in h, m, s)") // these are 360 days + flag.BoolVar(&cert.IsCA, "self-sign", false, "set this to true to create a self signed certificate (for CA usage)") + fs.Parse(args) + + cert.PrivateKey = pkiadm.ResourceName{*pk, pkiadm.RTPrivateKey} + cert.CSR = pkiadm.ResourceName{*csr, pkiadm.RTCSR} + cert.CA = pkiadm.ResourceName{*ca, pkiadm.RTCertificate} + cert.Serial = pkiadm.ResourceName{*serial, pkiadm.RTSerial} +} + +func deleteCertificate(args []string, client *pkiadm.Client) error { + fs := flag.NewFlagSet("delete-cert", flag.ExitOnError) + var id = fs.String("id", "", "set the id of the private key to delete") + fs.Parse(args) + + if err := client.DeleteCertificate(*id); err != nil { + return err + } + return nil +} +func listCertificate(args []string, client *pkiadm.Client) error { + fs := flag.NewFlagSet("list-cert", flag.ExitOnError) + fs.Parse(args) + + certs, err := client.ListCertificate() + if err != nil { + return err + } + + if len(certs) == 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%s\t\n", "id", "private", "csr", "ca", "serial", "duration", "self-signed") + for _, cert := range certs { + fmt.Fprintf(out, "%s\t%s\t%s\t%s\t%s\t%s\t%t\t\n", cert.ID, cert.PrivateKey.ID, cert.CSR.ID, cert.CA.ID, cert.Serial.ID, cert.Duration, cert.IsCA) + } + out.Flush() + + return nil +} +func showCertificate(args []string, client *pkiadm.Client) error { + fs := flag.NewFlagSet("show-cert", flag.ExitOnError) + var id = fs.String("id", "", "set the id of the private key to show") + fs.Parse(args) + + cert, err := client.ShowCertificate(*id) + if err != nil { + return err + } + out := tabwriter.NewWriter(os.Stdout, 2, 2, 1, ' ', tabwriter.AlignRight) + fmt.Fprintf(out, "id:\t%s\t\n", cert.ID) + fmt.Fprintf(out, "private:\t%s\t\n", cert.PrivateKey.ID) + fmt.Fprintf(out, "csr:\t%s\t\n", cert.CSR.ID) + fmt.Fprintf(out, "ca:\t%s\t\n", cert.CA.ID) + fmt.Fprintf(out, "serial:\t%s\t\n", cert.Serial.ID) + fmt.Fprintf(out, "duration:\t%s\t\n", cert.Duration) + fmt.Fprintf(out, "self-signed:\t%s\t\n", cert.IsCA) + fmt.Fprintf(out, "checksum:\t%s\t\n", base64.StdEncoding.EncodeToString(cert.Checksum)) + out.Flush() + return nil +} diff --git a/cmd/pkiadmd/certificate.go b/cmd/pkiadmd/certificate.go index 16b450f..3e04a29 100644 --- a/cmd/pkiadmd/certificate.go +++ b/cmd/pkiadmd/certificate.go @@ -2,6 +2,7 @@ package main import ( "encoding/pem" + "fmt" "time" "github.com/gibheer/pki" @@ -9,6 +10,10 @@ import ( ) type ( + Signer interface { + Sign(*CSR) (pki.Certificate, error) + } + Certificate struct { ID string @@ -24,14 +29,14 @@ type ( } ) -func NewCertificate(id string, privateKey, serial, csr, ca pkiadm.ResourceName, isCA bool, duration time.Duration) (*Certificate, error) { +func NewCertificate(id string, privateKey, serial, csr, ca pkiadm.ResourceName, selfSign bool, duration time.Duration) (*Certificate, error) { return &Certificate{ ID: id, PrivateKey: privateKey, Serial: serial, CSR: csr, CA: ca, - IsCA: isCA, + IsCA: selfSign, Duration: duration, }, nil } @@ -128,3 +133,118 @@ func (c *Certificate) DependsOn() []pkiadm.ResourceName { } return res } + +func (s *Server) CreateCertificate(inCert pkiadm.Certificate, res *pkiadm.Result) error { + s.lock() + defer s.unlock() + + cert, err := NewCertificate( + inCert.ID, + inCert.PrivateKey, + inCert.Serial, + inCert.CSR, + inCert.CA, + inCert.IsCA, + inCert.Duration, + ) + if err != nil { + res.SetError(err, "Could not create new certificate '%s'", inCert.ID) + return nil + } + if err := s.storage.AddCertificate(cert); err != nil { + res.SetError(err, "Could not add certificate '%s'", inCert.ID) + return nil + } + return s.store(res) +} + +func (s *Server) SetCertificate(changeset pkiadm.CertificateChange, res *pkiadm.Result) error { + s.lock() + defer s.unlock() + + cert, err := s.storage.GetCertificate(pkiadm.ResourceName{ID: changeset.Certificate.ID, Type: pkiadm.RTCertificate}) + if err != nil { + res.SetError(err, "Could not find certficate '%s'", changeset.Certificate.ID) + return nil + } + + change := changeset.Certificate + for _, field := range changeset.FieldList { + switch field { + case "duration": + cert.Duration = change.Duration + case "private": + cert.PrivateKey = change.PrivateKey + case "csr": + cert.CSR = change.CSR + case "serial": + cert.Serial = change.Serial + case "ca": + cert.CA = change.CA + case "self-sign": + cert.IsCA = change.IsCA + default: + res.SetError(fmt.Errorf("unknown field"), "unknown field '%s'", field) + return nil + } + } + if err := s.storage.Update(cert.Name()); err != nil { + res.SetError(err, "Could not update certificate '%s'", changeset.Certificate.ID) + return nil + } + return s.store(res) +} + +func (s *Server) DeleteCertificate(inCert pkiadm.ResourceName, res *pkiadm.Result) error { + s.lock() + defer s.unlock() + + cert, err := s.storage.GetCertificate(pkiadm.ResourceName{ID: inCert.ID, Type: pkiadm.RTCertificate}) + if err != nil { + res.SetError(err, "Could not find certificate '%s'", inCert.ID) + return nil + } + + if err := s.storage.Remove(cert); err != nil { + res.SetError(err, "Could not remove certificate '%s'", cert.ID) + return nil + } + return s.store(res) +} +func (s *Server) ShowCertificate(inCert pkiadm.ResourceName, res *pkiadm.ResultCertificate) error { + s.lock() + defer s.unlock() + + cert, err := s.storage.GetCertificate(pkiadm.ResourceName{ID: inCert.ID, Type: pkiadm.RTCertificate}) + if err != nil { + res.Result.SetError(err, "Could not find certificate '%s'", inCert.ID) + return nil + } + res.Certificates = []pkiadm.Certificate{pkiadm.Certificate{ + ID: cert.ID, + Duration: cert.Duration, + PrivateKey: cert.PrivateKey, + Serial: cert.Serial, + CA: cert.CA, + CSR: cert.CSR, + Checksum: cert.Checksum(), + }} + return nil +} +func (s *Server) ListCertificate(filter pkiadm.Filter, res *pkiadm.ResultCertificate) error { + s.lock() + defer s.unlock() + + for _, cert := range s.storage.Certificates { + res.Certificates = append(res.Certificates, pkiadm.Certificate{ + ID: cert.ID, + Duration: cert.Duration, + PrivateKey: cert.PrivateKey, + Serial: cert.Serial, + CA: cert.CA, + CSR: cert.CSR, + Checksum: cert.Checksum(), + }) + } + return nil +}