add basic CA support

This is the intial CA support, but not yet connected with the
certificate signing.
This commit is contained in:
Gibheer 2017-07-14 20:41:03 +02:00
parent 99e4d60516
commit 70515f6fa6
6 changed files with 360 additions and 0 deletions

88
ca.go Normal file
View File

@ -0,0 +1,88 @@
package pkiadm
import (
"strings"
)
const (
CALocal CAType = iota
CALetsEncrypt
CAUnknown
)
type (
CAType uint
CA struct {
ID string
Type CAType
Certificate ResourceName
}
ResultCA struct {
Result Result
CAs []CA
}
CAChange struct {
CA CA
FieldList []string
}
)
// CreateCA sends a RPC request to create a new private key.
func (c *Client) CreateCA(pk CA) error {
return c.exec("CreateCA", pk)
}
func (c *Client) SetCA(pk CA, fieldList []string) error {
changeset := CAChange{pk, fieldList}
return c.exec("SetCA", changeset)
}
func (c *Client) DeleteCA(id string) error {
pk := ResourceName{ID: id, Type: RTCA}
return c.exec("DeleteCA", pk)
}
func (c *Client) ListCA() ([]CA, error) {
result := &ResultCA{}
if err := c.query("ListCA", Filter{}, result); err != nil {
return []CA{}, err
}
if result.Result.HasError {
return []CA{}, result.Result.Error
}
return result.CAs, nil
}
func (c *Client) ShowCA(id string) (CA, error) {
ca := ResourceName{ID: id, Type: RTCA}
result := &ResultCA{}
if err := c.query("ShowCA", ca, result); err != nil {
return CA{}, err
}
if result.Result.HasError {
return CA{}, result.Result.Error
}
for _, privateKey := range result.CAs {
return privateKey, nil
}
return CA{}, nil
}
func (ct CAType) String() string {
switch ct {
case CALocal:
return "local"
case CALetsEncrypt:
return "LetsEncrypt"
default:
return "unknown"
}
}
func StringToCAType(in string) CAType {
switch strings.ToLower(in) {
case "local":
return CALocal
case "letsencrypt":
return CALetsEncrypt
default:
return CAUnknown
}
}

96
cmd/pkiadm/ca.go Normal file
View File

@ -0,0 +1,96 @@
package main
import (
"fmt"
"os"
"text/tabwriter"
"github.com/gibheer/pkiadm"
"github.com/pkg/errors"
flag "github.com/spf13/pflag"
)
func createCA(args []string, client *pkiadm.Client) error {
fs := flag.NewFlagSet("pkiadm create-public", flag.ExitOnError)
id := fs.String("id", "", "the id to set for the CA")
ct := fs.String("type", "local", "the type of CA to create (local, LetsEncrypt)")
cert := fs.String("certificate", "", "the id of the certificate to use for CA creation")
fs.Parse(args)
caType := pkiadm.StringToCAType(*ct)
if caType == pkiadm.CAUnknown {
return errors.New("unknown ca type")
}
caName := pkiadm.ResourceName{ID: *cert, Type: pkiadm.RTCertificate}
if err := client.CreateCA(
pkiadm.CA{ID: *id, Type: caType, Certificate: caName},
); err != nil {
return errors.Wrap(err, "Could not create CA")
}
return nil
}
func setCA(args []string, client *pkiadm.Client) error {
fs := flag.NewFlagSet("pkiadm set-public", flag.ExitOnError)
id := fs.String("id", "", "the id of the CA to change")
pk := fs.String("private-key", "", "the id of the new private key to use for CA generation")
fs.Parse(args)
if !fs.Lookup("private-key").Changed {
return nil
}
caName := pkiadm.ResourceName{ID: *pk, Type: pkiadm.RTPrivateKey}
if err := client.SetCA(
pkiadm.CA{ID: *id, Certificate: caName},
[]string{"private-key"},
); err != nil {
return errors.Wrap(err, "Could not change CA")
}
return nil
}
func deleteCA(args []string, client *pkiadm.Client) error {
fs := flag.NewFlagSet("pkiadm delete-public", flag.ExitOnError)
id := fs.String("id", "", "the id of the CA to delete")
fs.Parse(args)
if err := client.DeleteCA(*id); err != nil {
return errors.Wrap(err, "Could not delete CA")
}
return nil
}
func listCA(args []string, client *pkiadm.Client) error {
fs := flag.NewFlagSet("list-private", flag.ExitOnError)
fs.Parse(args)
cas, err := client.ListCA()
if err != nil {
return err
}
if len(cas) == 0 {
return nil
}
out := tabwriter.NewWriter(os.Stdout, 2, 2, 1, ' ', tabwriter.AlignRight)
fmt.Fprintf(out, "%s\t%s\t%s\t\n", "id", "type", "certificate")
for _, ca := range cas {
fmt.Fprintf(out, "%s\t%s\t%s\t\n", ca.ID, ca.Type.String(), ca.Certificate.ID)
}
out.Flush()
return nil
}
func showCA(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)
ca, err := client.ShowCA(*id)
if err != nil {
return err
}
out := tabwriter.NewWriter(os.Stdout, 2, 2, 1, ' ', tabwriter.AlignRight)
fmt.Fprintf(out, "ID:\t%s\t\n", ca.ID)
fmt.Fprintf(out, "type:\t%s\t\n", ca.Type.String())
fmt.Fprintf(out, "certificate:\t%s\t\n", ca.Certificate.ID)
out.Flush()
return nil
}

View File

@ -43,6 +43,16 @@ func main() {
err = setSerial(args, client)
case `show-serial`:
err = showSerial(args, client)
case `create-ca`:
err = createCA(args, client)
case `delete-ca`:
err = deleteCA(args, client)
case `list-ca`:
err = listCA(args, client)
case `set-ca`:
err = setCA(args, client)
case `show-ca`:
err = showCA(args, client)
case `create-subj`:
err = createSubject(args, client)
case `delete-subj`:
@ -118,6 +128,7 @@ func printCommands() {
fmt.Println(`Usage: pkiadm <subcommand> [options]
where subcommand is one of:`)
out := tabwriter.NewWriter(os.Stdout, 0, 4, 1, ' ', 0)
fmt.Fprintf(out, " %s\t%s\n", "create-ca", "create a new CA")
fmt.Fprintf(out, " %s\t%s\n", "create-cert", "create a new certificate")
fmt.Fprintf(out, " %s\t%s\n", "create-csr", "create a new certificate sign request")
fmt.Fprintf(out, " %s\t%s\n", "create-location", "create a new file export")
@ -126,6 +137,7 @@ where subcommand is one of:`)
fmt.Fprintf(out, " %s\t%s\n", "create-serial", "")
fmt.Fprintf(out, " %s\t%s\n", "create-subj", "")
fmt.Fprintf(out, " %s\t%s\n", "delete-ca", "delete a CA")
fmt.Fprintf(out, " %s\t%s\n", "delete-cert", "")
fmt.Fprintf(out, " %s\t%s\n", "delete-csr", "")
fmt.Fprintf(out, " %s\t%s\n", "delete-location", "")
@ -135,6 +147,7 @@ where subcommand is one of:`)
fmt.Fprintf(out, " %s\t%s\n", "delete-subj", "")
fmt.Fprintf(out, " %s\t%s\n", "list", "")
fmt.Fprintf(out, " %s\t%s\n", "list-ca", "list all available CAs")
fmt.Fprintf(out, " %s\t%s\n", "list-cert", "list all available certificates")
fmt.Fprintf(out, " %s\t%s\n", "list-csr", "list all available certificate sign requests")
fmt.Fprintf(out, " %s\t%s\n", "list-location", "list all file exports")
@ -143,6 +156,7 @@ where subcommand is one of:`)
fmt.Fprintf(out, " %s\t%s\n", "list-serial", "")
fmt.Fprintf(out, " %s\t%s\n", "list-subj", "")
fmt.Fprintf(out, " %s\t%s\n", "set-ca", "change attributes of a CA")
fmt.Fprintf(out, " %s\t%s\n", "set-cert", "change attributes of a certificate")
fmt.Fprintf(out, " %s\t%s\n", "set-csr", "change attributes of a certificate sign request")
fmt.Fprintf(out, " %s\t%s\n", "set-location", "change attributes of a location")
@ -151,6 +165,7 @@ where subcommand is one of:`)
fmt.Fprintf(out, " %s\t%s\n", "set-serial", "")
fmt.Fprintf(out, " %s\t%s\n", "set-subj", "")
fmt.Fprintf(out, " %s\t%s\n", "show-ca", "")
fmt.Fprintf(out, " %s\t%s\n", "show-cert", "")
fmt.Fprintf(out, " %s\t%s\n", "show-csr", "")
fmt.Fprintf(out, " %s\t%s\n", "show-location", "")

135
cmd/pkiadmd/ca.go Normal file
View File

@ -0,0 +1,135 @@
package main
import (
"github.com/gibheer/pkiadm"
)
type (
// CA is an instance that can sign certificates. When a certificate needs an
// update, the given CSR is signed by the CA.
// A CA can be responsible for multiple certificates to sign.
CA struct {
ID string
Type pkiadm.CAType
Certificate pkiadm.ResourceName
}
)
func NewCA(id string, caType pkiadm.CAType, cert pkiadm.ResourceName) (*CA, error) {
ca := &CA{
ID: id,
Type: caType,
Certificate: cert,
}
return ca, nil
}
// Return the unique ResourceName
func (ca *CA) Name() pkiadm.ResourceName {
return pkiadm.ResourceName{ca.ID, pkiadm.RTCA}
}
// AddDependency registers a depending resource to be retuened by Dependencies()
// Refresh must trigger a rebuild of the resource.
func (ca *CA) Refresh(*Storage) error {
return nil
}
// Return the PEM output of the contained resource.
func (ca *CA) Pem() ([]byte, error) { return []byte{}, nil }
func (ca *CA) Checksum() []byte { return []byte{} }
// DependsOn must return the resource names it is depending on.
func (ca *CA) DependsOn() []pkiadm.ResourceName {
return []pkiadm.ResourceName{
ca.Certificate,
}
}
func (ca *CA) Sign(csr *CSR) (*Certificate, error) {
return nil, nil
}
func (s *Server) CreateCA(inCA pkiadm.CA, res *pkiadm.Result) error {
s.lock()
defer s.unlock()
ca, err := NewCA(inCA.ID, inCA.Type, inCA.Certificate)
if err != nil {
res.SetError(err, "could not create CA '%s'", inCA.ID)
return nil
}
if err := s.storage.AddCA(ca); err != nil {
res.SetError(err, "could not add CA '%s'", inCA.ID)
return nil
}
return s.store(res)
}
func (s *Server) SetCA(change pkiadm.CAChange, res *pkiadm.Result) error {
s.lock()
defer s.unlock()
ca, err := s.storage.GetCA(pkiadm.ResourceName{ID: change.CA.ID, Type: pkiadm.RTCA})
if err != nil {
res.SetError(err, "could not find CA '%s'", change.CA.ID)
return nil
}
for _, field := range change.FieldList {
switch field {
case "type":
ca.Type = change.CA.Type
case "certificate":
ca.Certificate = change.CA.Certificate
}
}
return s.store(res)
}
func (s *Server) DeleteCA(inCA pkiadm.CA, res *pkiadm.Result) error {
s.lock()
defer s.unlock()
ca, err := s.storage.GetCA(pkiadm.ResourceName{inCA.ID, pkiadm.RTCA})
if err != nil {
res.SetError(err, "Could not find ca '%s'", ca.ID)
return nil
}
if err := s.storage.Remove(ca); err != nil {
res.SetError(err, "Could not remove ca '%s'", ca.ID)
return nil
}
return s.store(res)
}
func (s *Server) ShowCA(inCA pkiadm.CA, res *pkiadm.ResultCA) error {
s.lock()
defer s.unlock()
ca, err := s.storage.GetCA(pkiadm.ResourceName{ID: inCA.ID, Type: pkiadm.RTCA})
if err != nil {
res.Result.SetError(err, "Could not find private key '%s'", inCA.ID)
return nil
}
res.CAs = []pkiadm.CA{pkiadm.CA{
ID: ca.ID,
Type: ca.Type,
Certificate: ca.Certificate,
}}
return nil
}
func (s *Server) ListCA(filter pkiadm.Filter, res *pkiadm.ResultCA) error {
s.lock()
defer s.unlock()
for _, ca := range s.storage.CAs {
res.CAs = append(res.CAs, pkiadm.CA{
ID: ca.ID,
Type: ca.Type,
Certificate: ca.Certificate,
})
}
return nil
}

View File

@ -24,6 +24,7 @@ type (
CSRs map[string]*CSR
Serials map[string]*Serial
Subjects map[string]*Subject
CAs map[string]*CA
// dependencies maps from a resource name to all resources which depend
// on it.
dependencies map[string]map[string]Resource
@ -42,6 +43,7 @@ func NewStorage(path string) (*Storage, error) {
CSRs: map[string]*CSR{},
Serials: map[string]*Serial{},
Subjects: map[string]*Subject{},
CAs: map[string]*CA{},
dependencies: map[string]map[string]Resource{},
}
if err := s.load(); err != nil {
@ -93,6 +95,9 @@ func (s *Storage) refreshDependencies() error {
for _, l := range s.Locations {
_ = s.addDependency(l)
}
for _, ca := range s.CAs {
_ = s.addDependency(ca)
}
return nil
}
@ -193,6 +198,14 @@ func (s *Storage) AddLocation(l *Location) error {
return s.addDependency(l)
}
func (s *Storage) AddCA(ca *CA) error {
if err := ca.Refresh(s); err != nil {
return err
}
s.CAs[ca.Name().ID] = ca
return s.addDependency(ca)
}
// Get figures out the resource to the ResourceName if available.
func (s *Storage) Get(r pkiadm.ResourceName) (Resource, error) {
if r.ID == "" {
@ -213,6 +226,8 @@ func (s *Storage) Get(r pkiadm.ResourceName) (Resource, error) {
return s.GetCertificate(r)
case pkiadm.RTLocation:
return s.GetLocation(r)
case pkiadm.RTCA:
return s.GetCA(r)
default:
return nil, EUnknownType
}
@ -274,6 +289,14 @@ func (s *Storage) GetLocation(r pkiadm.ResourceName) (*Location, error) {
return nil, errors.Wrapf(ENotFound, "no location with id '%s' found", r)
}
// GetCA returns the CA matching the resource name.
func (s *Storage) GetCA(r pkiadm.ResourceName) (*CA, error) {
if res, found := s.CAs[r.ID]; found {
return res, nil
}
return nil, errors.Wrapf(ENotFound, "no CA with id '%s' found", r)
}
// Remove takes a resource and removes it from the system.
func (s *Storage) Remove(r Resource) error {
// TODO implement unable to remove when having dependencies
@ -292,6 +315,8 @@ func (s *Storage) Remove(r Resource) error {
delete(s.Certificates, r.Name().ID)
case pkiadm.RTLocation:
delete(s.Locations, r.Name().ID)
case pkiadm.RTCA:
delete(s.CAs, r.Name().ID)
default:
return EUnknownType
}

View File

@ -38,6 +38,7 @@ const (
RTSerial
RTSubject
RTUnknown
RTCA
)
type ResourceName struct {