aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--certificate.go323
-rw-r--r--command.go124
-rw-r--r--create_cert.go195
-rw-r--r--create_private_key.go80
-rw-r--r--create_public_key.go26
-rw-r--r--create_sign_request.go89
-rw-r--r--flags.go320
-rw-r--r--io.go62
-rw-r--r--load_private_key.go53
-rw-r--r--main.go153
-rw-r--r--pem.go71
-rw-r--r--private_key.go186
-rw-r--r--sign_input.go50
-rw-r--r--type.go61
-rw-r--r--verify_input.go76
15 files changed, 767 insertions, 1102 deletions
diff --git a/certificate.go b/certificate.go
deleted file mode 100644
index 173fe75..0000000
--- a/certificate.go
+++ /dev/null
@@ -1,323 +0,0 @@
-package main
-
-// This file contains all code to create certificates.
-
-import (
- "crypto/x509"
- "encoding/pem"
- "fmt"
- "io/ioutil"
- "math/big"
- "strings"
- "time"
-
- "github.com/gibheer/pki"
-)
-
-var (
- // the possible valid key usages to check against the commandline
- ValidKeyUsages = map[string]x509.KeyUsage{
- "digitalsignature": x509.KeyUsageDigitalSignature,
- "contentcommitment": x509.KeyUsageContentCommitment,
- "keyencipherment": x509.KeyUsageKeyEncipherment,
- "dataencipherment": x509.KeyUsageDataEncipherment,
- "keyagreement": x509.KeyUsageKeyAgreement,
- "certsign": x509.KeyUsageCertSign,
- "crlsign": x509.KeyUsageCRLSign,
- "encipheronly": x509.KeyUsageEncipherOnly,
- "decipheronly": x509.KeyUsageDecipherOnly,
- }
- // the valid extended key usages, to check against the commandline
- ValidExtKeyUsages = map[string]x509.ExtKeyUsage{
- "any": x509.ExtKeyUsageAny,
- "serverauth": x509.ExtKeyUsageServerAuth,
- "clientauth": x509.ExtKeyUsageClientAuth,
- "codesigning": x509.ExtKeyUsageCodeSigning,
- "emailprotection": x509.ExtKeyUsageEmailProtection,
- "ipsecendsystem": x509.ExtKeyUsageIPSECEndSystem,
- "ipsectunnel": x509.ExtKeyUsageIPSECTunnel,
- "ipsecuser": x509.ExtKeyUsageIPSECUser,
- "timestamping": x509.ExtKeyUsageTimeStamping,
- "ocspsigning": x509.ExtKeyUsageOCSPSigning,
- "microsoftservergatedcrypto": x509.ExtKeyUsageMicrosoftServerGatedCrypto,
- "netscapeservergatedcrypto": x509.ExtKeyUsageNetscapeServerGatedCrypto,
- }
-
- CmdCreateCert = &Command{
- Use: "create-cert",
- Short: "create a certificate from a sign request",
- Long: "Create a certificate based on a certificate sign request.",
- Example: "create-cert -private-key=foo.ecdsa -csr=foo.csr",
- Run: create_cert,
- }
- // certificate specific creation stuff
- FlagCertificateGeneration pki.CertificateOptions
-)
-
-type (
- // holds all certificate related flags, which need parsing afterwards
- certiticateRequestRawFlags struct {
- manual struct {
- serialNumber string // the serial number for the cert
- commonName string // the common name used in the cert
- dnsNames string // all alternative names in the certificate (comma separated list)
- ipAddresses string // all IP addresses in the certificate (comma separated list)
- emailAddresses string // alternative email addresses
- }
- automatic struct {
- Country string // the country names which should end up in the cert (comma separated list)
- Organization string // the organization names (comma separated list)
- OrganizationalUnit string // the organizational units (comma separated list)
- Locality string // the city or locality (comma separated list)
- Province string // the province name (comma separated list)
- StreetAddress string // the street addresses of the organization (comma separated list)
- PostalCode string // the postal codes of the locality
- }
- }
-
- // the raw flags collected through flags
- certGenerationRaw struct {
- serial int64
- notBefore string
- notAfter string
- isCA bool
- length int
- caPath string // path to the ca file if isCA is false
- keyUsage string // comma separated list of key usages
- extKeyUsage string // comma separated list of extended key usages
- crlUrl string // comma separated list of crl urls
- }
-)
-
-// add flag to load certificate flags
-func InitFlagCert(cmd *Command) {
- cmd.Flags().Int64Var(&flagContainer.certGeneration.serial, "serial", 0, "serial number of all certificates")
- cmd.Flags().BoolVar(&flagContainer.certGeneration.isCA, "is-ca", false, "check if the resulting certificate should be a ca")
- cmd.Flags().IntVar(
- &flagContainer.certGeneration.
- length,
- "length",
- 0,
- "the number of certificates allowed in the chain between this cert and the end certificate",
- )
- cmd.Flags().StringVar(
- &flagContainer.certGeneration.notBefore,
- "not-before",
- time.Now().Format(time.RFC3339),
- "time before the certificate is not valid in RFC3339 format (default now)",
- )
- cmd.Flags().StringVar(
- &flagContainer.certGeneration.notAfter,
- "not-after",
- time.Now().Add(time.Duration(180*24*time.Hour)).Format(time.RFC3339),
- "time after which the certificate is not valid in RFC3339 format (default now + 180 days)",
- )
- cmd.Flags().StringVar(
- &flagContainer.certGeneration.keyUsage,
- "key-usage", "",
- "comma separated list of key usages",
- )
- cmd.Flags().StringVar(
- &flagContainer.certGeneration.extKeyUsage,
- "ext-key-usage", "",
- "comma separated list of extended key usage flags",
- )
- cmd.Flags().StringVar(
- &flagContainer.certGeneration.crlUrl,
- "crl-url", "",
- "comma separated list where crl lists can be found",
- )
-}
-
-// create a certificate
-func create_cert(cmd *Command, args []string) {
- err := checkFlags(checkPrivateKey, checkOutput, checkCSR, checkCertFlags, checkFlagsEither(checkIsCA, checkCA))
- if err != nil {
- crash_with_help(cmd, ErrorFlagInput, "Flags invalid: %s", err)
- }
-
- var cert *pki.Certificate
- if FlagCertificateGeneration.IsCA && FlagCertificate == nil {
- cert, err = FlagCertificateSignRequest.ToCertificate(
- FlagPrivateKey,
- FlagCertificateGeneration,
- nil,
- )
- } else if !FlagCertificateGeneration.IsCA && FlagCertificate != nil {
- cert, err = FlagCertificateSignRequest.ToCertificate(
- FlagPrivateKey,
- FlagCertificateGeneration,
- FlagCertificate,
- )
- } else {
- crash_with_help(cmd, ErrorFlagInput, "Usage of 'is-ca' and 'ca' is invalid.")
- }
-
- if err != nil {
- crash_with_help(cmd, ErrorProgram, "Error generating certificate: %s", err)
- }
- pem_block, err := cert.MarshalPem()
- if err != nil {
- crash_with_help(cmd, ErrorProgram, "Error when marshalling to pem: %s", err)
- }
- _, err = pem_block.WriteTo(FlagOutput)
- if err != nil {
- crash_with_help(cmd, ErrorProgram, "Could not write to output: %s", err)
- }
-}
-
-// parse the certificate data
-func checkCertFlags() error {
- FlagCertificateGeneration.IsCA = flagContainer.certGeneration.isCA
- FlagCertificateGeneration.CALength = flagContainer.certGeneration.length
- FlagCertificateGeneration.SerialNumber = big.NewInt(flagContainer.certGeneration.serial)
-
- var err error
- if notbefore := flagContainer.certGeneration.notBefore; notbefore != "" {
- FlagCertificateGeneration.NotBefore, err = parseTimeRFC3339(notbefore)
- if err != nil {
- return err
- }
- }
- if notafter := flagContainer.certGeneration.notAfter; notafter != "" {
- FlagCertificateGeneration.NotAfter, err = parseTimeRFC3339(notafter)
- if err != nil {
- return err
- }
- }
-
- if err := convertCertKeyUsage(); err != nil {
- return err
- }
- if err := convertCertExtKeyUsage(); err != nil {
- return err
- }
- if err := convertCertCrlUrl(); err != nil {
- return err
- }
- return nil
-}
-
-// check if the flag is enabled to make the certificate a ca
-func checkIsCA() error {
- if FlagCertificateGeneration.IsCA {
- return nil
- }
- return fmt.Errorf("Not selected to be a CA")
-}
-
-// parse the key usage string
-func convertCertKeyUsage() error {
- if keyUstr := flagContainer.certGeneration.keyUsage; keyUstr != "" {
- keyUarr := strings.Split(keyUstr, ",")
- var keyUresult x509.KeyUsage
- for _, usage := range keyUarr {
- if value, ok := ValidKeyUsages[strings.ToLower(usage)]; ok {
- keyUresult = keyUresult | value
- } else {
- return fmt.Errorf("unsupported key usage '%s'", usage)
- }
- }
- FlagCertificateGeneration.KeyUsage = keyUresult
- }
- return nil
-}
-
-// parse the extended key usage flags
-func convertCertExtKeyUsage() error {
- if eKeyUstr := flagContainer.certGeneration.extKeyUsage; eKeyUstr != "" {
- eKeyUarr := strings.Split(eKeyUstr, ",")
- eKeyUResult := make([]x509.ExtKeyUsage, 0)
- for _, usage := range eKeyUarr {
- if value, ok := ValidExtKeyUsages[strings.ToLower(usage)]; ok {
- eKeyUResult = append(eKeyUResult, value)
- } else {
- return fmt.Errorf("unsupported extended key usage '%s'", usage)
- }
- }
- FlagCertificateGeneration.KeyExtendedUsage = eKeyUResult
- }
- return nil
-}
-
-// parse the crl urls
-func convertCertCrlUrl() error {
- if str := flagContainer.certGeneration.crlUrl; str != "" {
- FlagCertificateGeneration.CRLUrls = strings.Split(str, ",")
- }
- return nil
-}
-
-// add flag to load a certificate
-func InitFlagCA(cmd *Command) {
- cmd.Flags().StringVar(&flagContainer.caPath, "ca", "", "path to the certificate authority")
-}
-
-// parse the certificate authority
-func checkCA() error {
- rest, err := ioutil.ReadFile(flagContainer.caPath)
- if err != nil {
- return fmt.Errorf("Error reading certificate authority: %s", err)
- }
-
- var ca_asn1 []byte
- var block *pem.Block
- for len(rest) > 0 {
- block, rest = pem.Decode(rest)
- if block != nil && block.Type == pki.PemLabelCertificate {
- ca_asn1 = block.Bytes
- break
- }
- }
- if len(ca_asn1) == 0 {
- return fmt.Errorf("No certificate in '%s' found.", flagContainer.caPath)
- }
-
- ca, err := pki.LoadCertificate(ca_asn1)
- if err != nil {
- return fmt.Errorf("Invalid certificate: %s", err)
- }
- FlagCertificate = ca
- return nil
-}
-
-// add flag to load certificate sign request
-func InitFlagCSR(cmd *Command) {
- cmd.Flags().StringVar(&flagContainer.signRequestPath, "csr", "", "path to the certificate sign request")
-}
-
-// parse the certificate sign request
-func checkCSR() error {
- rest, err := ioutil.ReadFile(flagContainer.signRequestPath)
- if err != nil {
- return fmt.Errorf("Error reading certificate sign request: %s", err)
- }
-
- var csr_asn1 []byte
- var block *pem.Block
- for len(rest) > 0 {
- block, rest = pem.Decode(rest)
- if block.Type == pki.PemLabelCertificateRequest {
- csr_asn1 = block.Bytes
- break
- }
- }
- if len(csr_asn1) == 0 {
- return fmt.Errorf(
- "No certificate sign request found in %s",
- flagContainer.signRequestPath,
- )
- }
-
- csr, err := pki.LoadCertificateSignRequest(csr_asn1)
- if err != nil {
- return fmt.Errorf("Invalid certificate sign request: %s", err)
- }
- FlagCertificateSignRequest = csr
- return nil
-}
-
-// parse the string as a RFC3339 time
-func parseTimeRFC3339(tr string) (time.Time, error) {
- return time.Parse(time.RFC3339, tr)
-}
diff --git a/command.go b/command.go
deleted file mode 100644
index 204c3be..0000000
--- a/command.go
+++ /dev/null
@@ -1,124 +0,0 @@
-package main
-
-// Handler to make management of subcommands easier.
-
-import (
- "flag"
- "fmt"
- "os"
-)
-
-type (
- Command struct {
- Use string // command name (used for matching)
- Short string // a short description to display
- Long string // a long help text
- Example string // an example string
- Run func(*Command, []string) // the command to run
-
- flagSet *flag.FlagSet // internal flagset with all flags
- commands []*Command // the list of subcommands
- }
-)
-
-// This function adds a new sub command.
-func (c *Command) AddCommand(cmds ...*Command) {
- res := c.commands
- for _, cmd := range cmds {
- res = append(res, cmd)
- }
- c.commands = res
-}
-
-// Evaluate the arguments and call either the subcommand or parse it as flags.
-func (c *Command) eval(args []string) error {
- var name string = ""
- var rest []string = []string{}
-
- if len(args) > 0 {
- name = args[0]
- }
- if len(args) > 1 {
- rest = args[1:]
- }
-
- if name == "help" {
- c.Help(rest)
- return nil
- }
-
- for _, cmd := range c.commands {
- if cmd.Use == name {
- return cmd.eval(rest)
- }
- }
- if err := c.Flags().Parse(args); err != nil {
- return err
- }
- if c.Run != nil {
- c.Run(c, rest)
- } else {
- c.Help(rest)
- }
- return nil
-}
-
-// Execute the command. It will fetch os.Args[1:] itself.
-func (c *Command) Execute() error {
- return c.eval(os.Args[1:])
-}
-
-// Return the flagset currently in use.
-func (c *Command) Flags() *flag.FlagSet {
- if c.flagSet == nil {
- c.flagSet = flag.NewFlagSet(c.Use, flag.ContinueOnError)
- }
- return c.flagSet
-}
-
-// Print the help for the current command or a subcommand.
-func (c *Command) Help(args []string) {
- if len(args) > 0 {
- for _, cmd := range c.commands {
- if args[0] == cmd.Use {
- cmd.Help([]string{})
- return
- }
- }
- }
- if c.Long != "" {
- fmt.Println(c.Long, "\n")
- }
- c.Usage()
-}
-
-// Print the usage information.
-func (c *Command) Usage() {
- usage := ""
- if c.Use != "" {
- usage = usage + " " + c.Use
- }
- if len(c.commands) > 0 {
- usage = usage + " command"
- }
- if c.flagSet != nil {
- usage = usage + " [flags]"
- }
- fmt.Printf("Usage: %s%s\n", os.Args[0], usage)
-
- if len(c.commands) > 0 {
- fmt.Printf("\nwhere command is one of:\n")
- for _, cmd := range c.commands {
- fmt.Printf("\t%s\t\t%s\n", cmd.Use, cmd.Short)
- }
- }
- if c.flagSet != nil {
- fmt.Printf("\nwhere flags is any of:\n")
- c.Flags().SetOutput(os.Stdout)
- c.Flags().PrintDefaults()
- }
- if c.Example != "" {
- fmt.Println("\nexample:")
- fmt.Printf("\t%s\n", c.Example)
- }
-}
diff --git a/create_cert.go b/create_cert.go
new file mode 100644
index 0000000..d47732a
--- /dev/null
+++ b/create_cert.go
@@ -0,0 +1,195 @@
+package main
+
+import (
+ "crypto/x509"
+ "flag"
+ "fmt"
+ "math/big"
+ "time"
+
+ "github.com/gibheer/pki"
+)
+
+var (
+ // the possible valid key usages to check against the commandline
+ ValidKeyUsages = map[string]x509.KeyUsage{
+ "digitalsignature": x509.KeyUsageDigitalSignature,
+ "contentcommitment": x509.KeyUsageContentCommitment,
+ "keyencipherment": x509.KeyUsageKeyEncipherment,
+ "dataencipherment": x509.KeyUsageDataEncipherment,
+ "keyagreement": x509.KeyUsageKeyAgreement,
+ "certsign": x509.KeyUsageCertSign,
+ "crlsign": x509.KeyUsageCRLSign,
+ "encipheronly": x509.KeyUsageEncipherOnly,
+ "decipheronly": x509.KeyUsageDecipherOnly,
+ }
+ // the valid extended key usages, to check against the commandline
+ ValidExtKeyUsages = map[string]x509.ExtKeyUsage{
+ "any": x509.ExtKeyUsageAny,
+ "serverauth": x509.ExtKeyUsageServerAuth,
+ "clientauth": x509.ExtKeyUsageClientAuth,
+ "codesigning": x509.ExtKeyUsageCodeSigning,
+ "emailprotection": x509.ExtKeyUsageEmailProtection,
+ "ipsecendsystem": x509.ExtKeyUsageIPSECEndSystem,
+ "ipsectunnel": x509.ExtKeyUsageIPSECTunnel,
+ "ipsecuser": x509.ExtKeyUsageIPSECUser,
+ "timestamping": x509.ExtKeyUsageTimeStamping,
+ "ocspsigning": x509.ExtKeyUsageOCSPSigning,
+ "microsoftservergatedcrypto": x509.ExtKeyUsageMicrosoftServerGatedCrypto,
+ "netscapeservergatedcrypto": x509.ExtKeyUsageNetscapeServerGatedCrypto,
+ }
+)
+
+func CreateCert(args []string) error {
+ var (
+ flagUsageTemplate string
+ flagKeyUsage string
+ flagKeyExtUsage stringList
+ flagNotBefore string
+ flagNotAfter string
+ flagSerial int64
+ flagLength int
+ flagIsCA bool
+ flagCA string
+ flagPrivate string
+ flagCSR string
+ flagOutput string
+ )
+ fs := flag.NewFlagSet("pkictl create-cert", flag.ExitOnError)
+ fs.StringVar(&flagPrivate, "private-key", "", "the private key to generate the request")
+ fs.StringVar(&flagCSR, "sign-request", "", "the certificate sign request")
+ fs.StringVar(&flagOutput, "output", "stdout", "path to the output file (default stdout)")
+ fs.BoolVar(&flagIsCA, "is-ca", false, "is the result a CA - when true ca is ignored")
+ fs.StringVar(&flagUsageTemplate, "usage", "", "templates for usage (all, server, client)")
+ fs.StringVar(&flagKeyUsage, "key-usage", "", "comma separated list of key usages")
+ fs.Var(&flagKeyExtUsage, "key-ext-usage", "comma separated list of further usages")
+ fs.Int64Var(&flagSerial, "serial", 0, "the serial for the issued certificate")
+ fs.IntVar(&flagLength, "length", 0, "the number of sub CAs allowed (-1 equals no limit)")
+ fs.StringVar(&flagCA, "ca", "", "path to the CA certificate")
+ fs.StringVar(
+ &flagNotBefore,
+ "not-before",
+ time.Now().Format(time.RFC3339),
+ "time before the certificate is not valid in RFC3339 format (default now)",
+ )
+ fs.StringVar(
+ &flagNotAfter,
+ "not-after",
+ time.Now().Format(time.RFC3339),
+ "time after the certificate is not valid in RFC3339 format (default now)",
+ )
+ fs.Parse(args)
+
+ if flagPrivate == "" {
+ return fmt.Errorf("missing private key")
+ }
+ if flagCSR == "" {
+ return fmt.Errorf("missing certificate sign request")
+ }
+
+ out, err := openOutput(flagOutput)
+ if err != nil {
+ return err
+ }
+ // FIXME check all other out.Close for stdout exception
+ if flagOutput != "stdout" {
+ defer out.Close()
+ }
+ pk, err := loadPrivateKey(flagPrivate)
+ if err != nil {
+ return err
+ }
+ csr, err := parseCSR(flagCSR)
+ if err != nil {
+ return err
+ }
+ var ca *pki.Certificate
+ if !flagIsCA {
+ ca, err = parseCA(flagCA)
+ if err != nil {
+ return err
+ }
+ }
+
+ notBefore, err := time.Parse(time.RFC3339, flagNotBefore)
+ if err != nil {
+ return err
+ }
+ notAfter, err := time.Parse(time.RFC3339, flagNotAfter)
+ if err != nil {
+ return err
+ }
+ if notBefore.After(notAfter) {
+ return fmt.Errorf("before and after range is wrong")
+ }
+ cert_opts := pki.CertificateOptions{
+ SerialNumber: big.NewInt(flagSerial),
+ NotBefore: notBefore,
+ NotAfter: notAfter,
+ IsCA: flagIsCA,
+ CALength: flagLength,
+ }
+ if flagKeyUsage != "" {
+ keyUsage, found := ValidKeyUsages[flagKeyUsage]
+ if !found {
+ return fmt.Errorf("unknown key usage")
+ }
+ cert_opts.KeyUsage = keyUsage
+ }
+
+ for pos, name := range flagKeyExtUsage {
+ if val, found := ValidExtKeyUsages[name]; !found {
+ return fmt.Errorf("%d ext key usage '%s' unknown", pos, name)
+ } else {
+ cert_opts.KeyExtendedUsage = append(cert_opts.KeyExtendedUsage, val)
+ }
+ }
+
+ cert, err := csr.ToCertificate(pk, cert_opts, ca)
+ if err != nil {
+ return err
+ }
+ return writePem(cert, out)
+}
+
+func parseCSR(path string) (*pki.CertificateRequest, error) {
+ pems_raw, err := openInput(path)
+ if err != nil {
+ return nil, err
+ }
+ defer pems_raw.Close()
+ pems, err := parseFile(pems_raw)
+ if err != nil {
+ return nil, err
+ }
+ csr_raw, err := getSectionFromPem(pems, pki.PemLabelCertificateRequest)
+ if err != nil {
+ return nil, err
+ }
+ csr, err := pki.LoadCertificateSignRequest(csr_raw)
+ if err != nil {
+ return nil, err
+ }
+ return csr, nil
+}
+
+func parseCA(path string) (*pki.Certificate, error) {
+ pems_raw, err := openInput(path)
+ if err != nil {
+ return nil, err
+ }
+ defer pems_raw.Close()
+ pems, err := parseFile(pems_raw)
+ if err != nil {
+ return nil, err
+ }
+ ca_raw, err := getSectionFromPem(pems, pki.PemLabelCertificate)
+ if err != nil {
+ return nil, err
+ }
+ ca, err := pki.LoadCertificate(ca_raw)
+ if err != nil {
+ return nil, err
+ }
+ return ca, nil
+}
diff --git a/create_private_key.go b/create_private_key.go
new file mode 100644
index 0000000..c13889f
--- /dev/null
+++ b/create_private_key.go
@@ -0,0 +1,80 @@
+package main
+
+import (
+ "crypto/elliptic"
+ "flag"
+ "fmt"
+ "io"
+ "os"
+
+ "github.com/gibheer/pki"
+)
+
+const (
+ // Lower boundary limit for RSA private keys
+ RsaLowerLength = 1024
+ // Upper boundary limit for RSA private keys
+ RsaUpperLength = 65536
+)
+
+var (
+ // the possible ecdsa curves allowed to be used
+ ecdsaCurves = map[uint]elliptic.Curve{
+ 224: elliptic.P224(),
+ 256: elliptic.P256(),
+ 384: elliptic.P384(),
+ 521: elliptic.P521(),
+ }
+)
+
+func CreatePrivateKey(args []string) error {
+ fs := flag.NewFlagSet("pkiadm create-private-key", flag.ExitOnError)
+ fs.Usage = func() {
+ fmt.Fprintf(os.Stderr, "The length depends on the key type. Possible values are:\n")
+ fmt.Fprintf(os.Stderr, " * ed25519 - 256\n")
+ fmt.Fprintf(os.Stderr, " * ecdsa - 224, 256, 384, 521\n")
+ fmt.Fprintf(os.Stderr, " * rsa - from %d up to %d\n", RsaLowerLength, RsaUpperLength)
+ fmt.Fprintf(os.Stderr, "Usage of %s %s:\n", COMMAND, "create-private")
+ fs.PrintDefaults()
+ }
+ flagType := fs.String("type", "ed25519", "the type of the private key (ed25519, ecdsa, rsa)")
+ flagLength := fs.Uint("length", 256, "the bit length for the private key")
+ flagOutput := fs.String("output", "stdout", "write private key to file")
+ fs.Parse(args)
+
+ var err error
+ var out io.WriteCloser
+ if *flagOutput == "stdout" {
+ out = os.Stdout
+ } else {
+ out, err = os.OpenFile(*flagOutput, os.O_WRONLY|os.O_CREATE|os.O_EXCL|os.O_SYNC, 0700)
+ if err != nil {
+ return err
+ }
+ }
+ defer out.Close()
+
+ var pk pki.Pemmer
+ switch *flagType {
+ case "ed25519":
+ if *flagLength != 256 {
+ return fmt.Errorf("ed25519 only supports bit length of 256")
+ }
+ pk, err = pki.NewPrivateKeyEd25519()
+ case "ecdsa":
+ if curve, found := ecdsaCurves[*flagLength]; !found {
+ return fmt.Errorf("unknown bit length for ecdsa")
+ } else {
+ pk, err = pki.NewPrivateKeyEcdsa(curve)
+ }
+ case "rsa":
+ if RsaLowerLength > *flagLength || *flagLength > RsaUpperLength {
+ return fmt.Errorf("bit length outside of range for rsa")
+ }
+ pk, err = pki.NewPrivateKeyRsa(int(*flagLength))
+ default:
+ return fmt.Errorf("unknown private key type")
+ }
+
+ return writePem(pk, out)
+}
diff --git a/create_public_key.go b/create_public_key.go
new file mode 100644
index 0000000..93c0677
--- /dev/null
+++ b/create_public_key.go
@@ -0,0 +1,26 @@
+package main
+
+import (
+ "flag"
+)
+
+func CreatePublicKey(args []string) error {
+ fs := flag.NewFlagSet("pkictl create-public-key", flag.ExitOnError)
+ flagPrivate := fs.String("private-key", "", "path to the private key or read from stdin")
+ flagOutput := fs.String("output", "stdout", "write private key to file")
+ fs.Parse(args)
+
+ pk, err := loadPrivateKey(*flagPrivate)
+ if err != nil {
+ return err
+ }
+
+ out, err := openOutput(*flagOutput)
+ if err != nil {
+ return err
+ }
+ defer out.Close()
+
+ pub := pk.Public()
+ return writePem(pub, out)
+}
diff --git a/create_sign_request.go b/create_sign_request.go
new file mode 100644
index 0000000..d2d2446
--- /dev/null
+++ b/create_sign_request.go
@@ -0,0 +1,89 @@
+package main
+
+import (
+ "crypto/x509/pkix"
+ "flag"
+ "fmt"
+
+ "github.com/gibheer/pki"
+)
+
+func CreateSignRequest(args []string) error {
+ var (
+ flagPrivate string
+ flagOutput string
+ // primary certificate fields
+ flagSerial string
+ flagCommonName string
+ flagDnsNames stringList
+ flagEmails stringList
+ flagIpAddresses ipList
+ // standard simple entry flags
+ flagCountry stringList
+ flagOrganization stringList
+ flagOrganizaionUnit stringList
+ flagLocality stringList
+ flagProvince stringList
+ flagStreetAddress stringList
+ flagPostalCode stringList
+ )
+ fs := flag.NewFlagSet("pkictl create-sign-request", flag.ExitOnError)
+ fs.StringVar(&flagPrivate, "private-key", "", "the private key to generate the request")
+ fs.StringVar(&flagOutput, "output", "stdout", "path to the output file (default stdout)")
+ // primary certificate info
+ fs.StringVar(&flagSerial, "serial", "", "the serial for the sign request")
+ fs.StringVar(&flagCommonName, "common-name", "", "the primary name of the certificate (or common name)")
+ fs.Var(&flagDnsNames, "names", "additional names accepted by the certificate")
+ fs.Var(&flagEmails, "mails", "mail addresses to add as contact addresses")
+ fs.Var(&flagIpAddresses, "ips", "IPs to accept by the certificate")
+ // standard simple entry flags
+ fs.Var(&flagCountry, "country", "country of residence of the requester")
+ fs.Var(&flagOrganization, "organization", "organization of the requester")
+ fs.Var(&flagOrganizaionUnit, "organization-unit", "the organization unit requesting the certificate")
+ fs.Var(&flagLocality, "locality", "locality of the requester")
+ fs.Var(&flagProvince, "province", "province of residence")
+ fs.Var(&flagStreetAddress, "street-address", "the street address of the requester")
+ fs.Var(&flagPostalCode, "postal-code", "the postal code of the requester")
+ fs.Parse(args)
+
+ if flagPrivate == "" || flagSerial == "" || flagCommonName == "" {
+ // TODO make the same for other parts?
+ // TODO find better way to handle the situation
+ fmt.Println("Error: missing private key, serial or common name")
+ fmt.Println("Usage of pkictl create-sign-request:")
+ fs.PrintDefaults()
+ return fmt.Errorf("missing private key, serial or common name")
+ }
+
+ data := pki.CertificateData{
+ Subject: pkix.Name{
+ SerialNumber: flagSerial,
+ CommonName: flagCommonName,
+ Country: flagCountry,
+ Organization: flagOrganization,
+ OrganizationalUnit: flagOrganizaionUnit,
+ Locality: flagLocality,
+ Province: flagProvince,
+ StreetAddress: flagStreetAddress,
+ PostalCode: flagPostalCode,
+ },
+ DNSNames: flagDnsNames,
+ IPAddresses: flagIpAddresses,
+ EmailAddresses: flagEmails,
+ }
+ pk, err := loadPrivateKey(flagPrivate)
+ if err != nil {
+ return err
+ }
+ out, err := openOutput(flagOutput)
+ if err != nil {
+ return err
+ }
+ defer out.Close()
+ var csr pki.Pemmer
+ csr, err = data.ToCertificateRequest(pk)
+ if err != nil {
+ return err
+ }
+ return writePem(csr, out)
+}
diff --git a/flags.go b/flags.go
deleted file mode 100644
index 31d4e67..0000000
--- a/flags.go
+++ /dev/null
@@ -1,320 +0,0 @@
-package main
-
-// This file handles the complete parameter assignment, as some parameters are
-// often used by multiple functions.
-
-import (
- "encoding/base64"
- "fmt"
- "io"
- "net"
- "os"
- "reflect"
- "strings"
-
- "github.com/gibheer/pki"
-)
-
-type (
- // a container go gather all incoming flags for further processing
- paramContainer struct {
- outputPath string // path to output whatever is generated
- inputPath string // path to an input resource
- cryptType string // type of something (private key)
- length int // the length of something (private key)
- privateKeyPath string // path to the private key
- publicKeyPath string // path to the public key
- signRequestPath string // path to the certificate sign request
- certificateFlags certiticateRequestRawFlags // container for certificate related flags
- signature string // a base64 encoded signature
- certGeneration certGenerationRaw // all certificate generation flags
- caPath string // path to a certificate authority
- }
-
- flagCheck func() error
-)
-
-var (
- CmdRoot = &Command{
- Short: "A tool to manage keys and certificates.",
- Long: `This tool provides a way to manage private and public keys, create
-certificate requests and certificates and sign/verify messages.`,
- }
-
- CmdCreatePublicKey = &Command{
- Use: "create-public",
- Short: "create a public key from a private key",
- Long: "Create a public key derived from a private key.",
- Example: "create-public -private-key=foo.ecdsa",
- Run: create_public_key,
- }
-
- CmdSignInput = &Command{
- Use: "sign-input",
- Short: "sign a text using a private key",
- Long: "Create a signature using a private key",
- Example: "sign-input -private-key=foo.ecdsa -input=textfile",
- Run: sign_input,
- }
-
- CmdVerifyInput = &Command{
- Use: "verify-input",
- Short: "verify a text using a signature",
- Long: "Verify a text using a signature and a public key.",
- Example: "verify-input -public-key=foo.ecdsa.pub -input=textfile -signature=abc456",
- Run: verify_input,
- }
-
- CmdCreateSignRequest = &Command{
- Use: "create-sign-request",
- Short: "create a certificate sign request",
- Long: "Create a certificate sign request.",
- Example: "create-sign-request -private-key=foo.ecdsa -common-name=foo -serial=1",
- Run: create_sign_request,
- }
-
- // variable to hold the raw arguments before checking
- flagContainer *paramContainer
-
- // loaded private key
- FlagPrivateKey pki.PrivateKey
- // loaded public key
- FlagPublicKey pki.PublicKey
- // the IO handler for input
- FlagInput io.ReadCloser
- // the IO handler for output
- FlagOutput io.WriteCloser
- // signature from the args
- FlagSignature []byte
- // a certificate filled with the parameters
- FlagCertificateRequestData *pki.CertificateData
- // the certificate sign request
- FlagCertificateSignRequest *pki.CertificateRequest
- // the ca/certificate to use
- FlagCertificate *pki.Certificate
-)
-
-func InitFlags() {
- flagContainer = &paramContainer{}
- CmdRoot.AddCommand(
- CmdCreatePrivateKey,
- CmdCreatePublicKey,
- CmdSignInput,
- CmdVerifyInput,
- CmdCreateSignRequest,
- CmdCreateCert,
- )
-
- // private-key
- InitFlagOutput(CmdCreatePrivateKey)
- InitFlagPrivateKeyGeneration(CmdCreatePrivateKey)
- // public-key
- InitFlagOutput(CmdCreatePublicKey)
- InitFlagPrivateKey(CmdCreatePublicKey)
- // sign-input
- InitFlagInput(CmdSignInput)
- InitFlagPrivateKey(CmdSignInput)
- InitFlagOutput(CmdSignInput)
- // verify-input
- InitFlagInput(CmdVerifyInput)
- InitFlagPublicKey(CmdVerifyInput)
- InitFlagOutput(CmdVerifyInput)
- InitFlagSignature(CmdVerifyInput)
- // create-sign-request
- InitFlagPrivateKey(CmdCreateSignRequest)
- InitFlagOutput(CmdCreateSignRequest)
- InitFlagCertificateFields(CmdCreateSignRequest)
- // create-certificate
- InitFlagCA(CmdCreateCert)
- InitFlagCert(CmdCreateCert)
- InitFlagCSR(CmdCreateCert)
- InitFlagOutput(CmdCreateCert)
- InitFlagPrivateKey(CmdCreateCert)
-}
-
-// check if all checks are successful
-func checkFlags(checks ...flagCheck) error {
- for _, check := range checks {
- if err := check(); err != nil {
- return err
- }
- }
- return nil
-}
-
-// check if either of the inserted checks is successful
-func checkFlagsEither(checks ...flagCheck) flagCheck {
- return func() error {
- errors := make([]error, 0)
- for _, check := range checks {
- if err := check(); err != nil {
- errors = append(errors, err)
- }
- }
- // it is a success, if any check is successful
- if len(checks)-len(errors) > 0 {
- return nil
- }
- return errors[0]
- }
-}
-
-// add the public key flag
-func InitFlagPublicKey(cmd *Command) {
- cmd.Flags().StringVar(&flagContainer.publicKeyPath, "public-key", "", "path to the public key (required)")
-}
-
-// parse public key flag
-func checkPublicKey() error {
- if flagContainer.publicKeyPath == "" {
- return fmt.Errorf("No public key given!")
- }
-
- pu, err := ReadPublicKeyFile(flagContainer.publicKeyPath)
- if err != nil {
- return fmt.Errorf("Error reading public key: %s", err)
- }
- FlagPublicKey = pu
- return nil
-}
-
-// initialize the output flag
-func InitFlagOutput(cmd *Command) {
- cmd.Flags().StringVar(&flagContainer.outputPath, "output", "STDOUT", "path to the output or STDOUT")
-}
-
-// parse the output parameter and open the file handle
-func checkOutput() error {
- if flagContainer.outputPath == "STDOUT" {
- FlagOutput = os.Stdout
- return nil
- }
- var err error
- FlagOutput, err = os.OpenFile(
- flagContainer.outputPath,
- os.O_WRONLY|os.O_APPEND|os.O_CREATE, // do not kill users files!
- 0600,
- )
- if err != nil {
- return err
- }
- return nil
-}
-
-// add the input parameter to load resources from
-func InitFlagInput(cmd *Command) {
- cmd.Flags().StringVar(&flagContainer.inputPath, "input", "STDIN", "path to the input or STDIN")
-}
-
-// parse the input parameter and open the file handle
-func checkInput() error {
- if flagContainer.inputPath == "STDIN" {
- FlagInput = os.Stdin
- return nil
- }
- var err error
- FlagInput, err = os.Open(flagContainer.inputPath)
- if err != nil {
- return err
- }
- return nil
-}
-
-// add the signature flag to load a signature from a signing process
-func InitFlagSignature(cmd *Command) {
- cmd.Flags().StringVar(&flagContainer.signature, "signature", "", "the base64 encoded signature to use for verification")
-}
-
-// parse the signature flag
-func checkSignature() error {
- var err error
- FlagSignature, err = base64.StdEncoding.DecodeString(flagContainer.signature)
- if err != nil {
- return err
- }
- return nil
-}
-
-// add the certificate fields to the flags
-func InitFlagCertificateFields(cmd *Command) {
- cmd.Flags().StringVar(
- &flagContainer.certificateFlags.manual.serialNumber,
- "serial", "1", "unique serial number of the CA")
- cmd.Flags().StringVar(
- &flagContainer.certificateFlags.manual.commonName,
- "common-name", "", "common name of the entity to certify")
- cmd.Flags().StringVar(
- &flagContainer.certificateFlags.manual.dnsNames,
- "dns-names", "", "comma separated list of alternative fqdn entries for the entity")
- cmd.Flags().StringVar(
- &flagContainer.certificateFlags.manual.emailAddresses,
- "email-address", "", "comma separated list of alternative email entries for the entity")
- cmd.Flags().StringVar(
- &flagContainer.certificateFlags.manual.ipAddresses,
- "ip-address", "", "comma separated list of alternative ip entries for the entity")
- cmd.Flags().StringVar(
- &flagContainer.certificateFlags.automatic.Country,
- "country", "", "comma separated list of countries the entitiy resides in")
- cmd.Flags().StringVar(
- &flagContainer.certificateFlags.automatic.Organization,
- "organization", "", "comma separated list of organizations the entity belongs to")
- cmd.Flags().StringVar(
- &flagContainer.certificateFlags.automatic.OrganizationalUnit,
- "organization-unit", "", "comma separated list of organization units or departments the entity belongs to")
- cmd.Flags().StringVar(
- &flagContainer.certificateFlags.automatic.Locality,
- "locality", "", "comma separated list of localities or cities the entity resides in")
- cmd.Flags().StringVar(
- &flagContainer.certificateFlags.automatic.Province,
- "province", "", "comma separated list of provinces the entity resides in")
- cmd.Flags().StringVar(
- &flagContainer.certificateFlags.automatic.StreetAddress,
- "street-address", "", "comma separated list of street addresses the entity resides in")
- cmd.Flags().StringVar(
- &flagContainer.certificateFlags.automatic.PostalCode,
- "postal-code", "", "comma separated list of postal codes of the localities")
-}
-
-// parse the certificate fields into a raw certificate
-func checkCertificateFields() error {
- FlagCertificateRequestData = pki.NewCertificateData()
- // convert the automatic flags
- container_type := reflect.ValueOf(&flagContainer.certificateFlags.automatic).Elem()
- cert_data_type := reflect.ValueOf(&FlagCertificateRequestData.Subject).Elem()
-
- for _, field := range []string{"Country", "Organization", "OrganizationalUnit",
- "Locality", "Province", "StreetAddress", "PostalCode"} {
- field_value := container_type.FieldByName(field).String()
- if field_value == "" {
- continue
- }
- target := cert_data_type.FieldByName(field)
- target.Set(reflect.ValueOf(strings.Split(field_value, ",")))
- }
-
- // convert the manual flags
- data := FlagCertificateRequestData
- raw_data := flagContainer.certificateFlags.manual
- data.Subject.SerialNumber = raw_data.serialNumber
- data.Subject.CommonName = raw_data.commonName
- if raw_data.dnsNames != "" {
- data.DNSNames = strings.Split(raw_data.dnsNames, ",")
- }
- if raw_data.emailAddresses != "" {
- data.EmailAddresses = strings.Split(raw_data.emailAddresses, ",")
- }
-
- if raw_data.ipAddresses == "" {
- return nil
- }
- raw_ips := strings.Split(raw_data.ipAddresses, ",")
- data.IPAddresses = make([]net.IP, len(raw_ips))
- for i, ip := range raw_ips {
- data.IPAddresses[i] = net.ParseIP(ip)
- if data.IPAddresses[i] == nil {
- return fmt.Errorf("'%s' is not a valid IP", ip)
- }
- }
-
- return nil
-}
diff --git a/io.go b/io.go
index 56cc689..0809259 100644
--- a/io.go
+++ b/io.go
@@ -1,45 +1,39 @@
package main
-// handle all io and de/encoding of data
-
import (
- "encoding/pem"
- "errors"
- "io/ioutil"
-)
-
-var (
- ErrBlockNotFound = errors.New("block not found")
+ "fmt"
+ "io"
+ "os"
)
-// load a pem section from a file
-func readSectionFromFile(path, btype string) ([]byte, error) {
- raw, err := readFile(path)
- if err != nil {
- return raw, err
+// Open a path for writing
+func openOutput(path string) (io.WriteCloser, error) {
+ var (
+ err error
+ out io.WriteCloser
+ )
+ if path == "stdout" {
+ out = os.Stdout
+ } else {
+ out, err = os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_EXCL|os.O_SYNC, 0700)
+ if err != nil {
+ return nil, err
+ }
}
-
- return decodeSection(raw, btype)
+ return out, nil
}
-// read a file completely and report possible errors
-func readFile(path string) ([]byte, error) {
- raw, err := ioutil.ReadFile(path)
- if err != nil {
- return EmptyByteArray, err
+// Open a path for reading the content
+func openInput(path string) (io.ReadCloser, error) {
+ if path == "" {
+ return nil, fmt.Errorf("empty path is invalid")
}
- return raw, nil
-}
-
-// decode a pem encoded file and search for the specified section
-func decodeSection(data []byte, btype string) ([]byte, error) {
- rest := data
- for len(rest) > 0 {
- var block *pem.Block
- block, rest = pem.Decode(rest)
- if block.Type == btype {
- return block.Bytes, nil
- }
+ var err error
+ var in io.ReadCloser
+ if path == "stdin" {
+ in = os.Stdin
+ } else {
+ in, err = os.Open(path)
}
- return EmptyByteArray, ErrBlockNotFound
+ return in, err
}
diff --git a/load_private_key.go b/load_private_key.go
new file mode 100644
index 0000000..4678862
--- /dev/null
+++ b/load_private_key.go
@@ -0,0 +1,53 @@
+package main
+
+import (
+ "fmt"
+ "os"
+
+ "github.com/gibheer/pki"
+)
+
+func loadPrivateKey(path string) (pki.PrivateKey, error) {
+ if path == "" {
+ return nil, fmt.Errorf("no path given")
+ }
+ var err error
+ file, err := os.Open(path)
+ if err != nil {
+ return nil, err
+ }
+ defer file.Close()
+ info, err := file.Stat()
+ if err != nil {
+ return nil, err
+ }
+ if info.Mode().Perm().String()[4:] != "------" {
+ return nil, fmt.Errorf("private key must not be readable for group or world")
+ }
+
+ pems, err := parseFile(file)
+ if err != nil {
+ return nil, err
+ }
+ if len(pems) > 1 {
+ return nil, fmt.Errorf("more than one object in file")
+ }
+
+ var pk pki.PrivateKey
+ for key, parts := range pems {
+ if len(parts) > 1 {
+ return nil, fmt.Errorf("more than one object found")
+ }
+ switch key {
+ case pki.PemLabelRsa:
+ pk, err = pki.LoadPrivateKeyRsa(parts[0])
+ case pki.PemLabelEd25519:
+ pk, err = pki.LoadPrivateKeyEd25519(parts[0])
+ case pki.PemLabelEcdsa:
+ pk, err = pki.LoadPrivateKeyEcdsa(parts[0])
+ default:
+ return nil, fmt.Errorf("unknown private key format %s", key)
+ }
+ }
+ return pk, err
+}
diff --git a/main.go b/main.go
index b1e43ab..6999c48 100644
--- a/main.go
+++ b/main.go
@@ -4,130 +4,53 @@
package main
import (
- "crypto"
- "encoding/base64"
"fmt"
- "io"
- "io/ioutil"
"os"
-
- "github.com/gibheer/pki"
)
const (
- ErrorProgram int = iota
- ErrorFlagInput
- ErrorInput
-)
-
-var (
- EmptyByteArray = make([]byte, 0)
+ COMMAND = "pkictl"
)
func main() {
- InitFlags()
- CmdRoot.Execute()
-}
-
-// create a public key derived from a private key
-func create_public_key(cmd *Command, args []string) {
- err := checkFlags(checkPrivateKey, checkOutput)
- if err != nil {
- crash_with_help(cmd, ErrorFlagInput, "Flags invalid: %s", err)
- }
-
- var pub_key pki.Pemmer
- pub_key = FlagPrivateKey.Public()
- marsh_pem, err := pub_key.MarshalPem()
- if err != nil {
- crash_with_help(cmd, ErrorProgram, "Error when marshalling to pem: %s", err)
- }
- _, err = marsh_pem.WriteTo(FlagOutput)
- if err != nil {
- crash_with_help(cmd, ErrorProgram, "Error when writing output: %s", err)
- }
-}
-
-// sign a message using he private key
-func sign_input(cmd *Command, args []string) {
- err := checkFlags(checkPrivateKey, checkInput, checkOutput)
- if err != nil {
- crash_with_help(cmd, ErrorFlagInput, "Flags invalid: %s", err)
- }
-
- message, err := ioutil.ReadAll(FlagInput)
- if err != nil {
- crash_with_help(cmd, ErrorProgram, "Error reading input: %s", err)
- }
- signature, err := FlagPrivateKey.Sign(message, crypto.SHA256)
- if err != nil {
- crash_with_help(cmd, ErrorProgram, "Could not compute signature: %s", err)
- }
- _, err = io.WriteString(FlagOutput, base64.StdEncoding.EncodeToString(signature))
- if err != nil {
- crash_with_help(cmd, ErrorProgram, "Could not write to output: %s", err)
- }
-
- // if we print to stderr, send a final line break to make the output nice
- if FlagOutput == os.Stdout {
- // we can ignore the result, as either Stdout did work or not
- _, _ = io.WriteString(FlagOutput, "\n")
+ if len(os.Args) == 1 {
+ printHelp()
+ return
+ }
+ command, args := os.Args[1], os.Args[2:]
+ var err error
+ switch command {
+ case "create-private":
+ err = CreatePrivateKey(args)
+ case "create-public":
+ err = CreatePublicKey(args)
+ case "sign-input":
+ err = SignInput(args)
+ case "verify-input":
+ err = VerifyInput(args)
+ case "create-sign-request":
+ err = CreateSignRequest(args)
+ case "create-cert":
+ err = CreateCert(args)
+ default:
+ printHelp()
+ }
+ if err != nil {
+ fmt.Printf("error: %s\n", err)
+ os.Exit(2)
}
}
-// verify a message using a signature and a public key
-func verify_input(cmd *Command, args []string) {
- err := checkFlags(checkPublicKey, checkInput, checkOutput, checkSignature)
- if err != nil {
- crash_with_help(cmd, ErrorFlagInput, "Flags invalid: %s", err)
- }
-
- signature := FlagSignature
- message, err := ioutil.ReadAll(FlagInput)
- if err != nil {
- crash_with_help(cmd, ErrorProgram, "Error reading input: %s", err)
- }
- valid, err := FlagPublicKey.Verify(message, signature, crypto.SHA256)
- if err != nil {
- crash_with_help(cmd, ErrorProgram, "Could not verify message using signature: %s", err)
- }
- if valid {
- fmt.Println("valid")
- os.Exit(0)
- }
- fmt.Println("invalid")
- os.Exit(1)
-}
-
-// create a certificate sign request
-func create_sign_request(cmd *Command, args []string) {
- err := checkFlags(checkPrivateKey, checkOutput, checkCertificateFields)
- if err != nil {
- crash_with_help(cmd, ErrorFlagInput, "Flags invalid: %s", err)
- }
-
- csr, err := FlagCertificateRequestData.ToCertificateRequest(FlagPrivateKey)
- if err != nil {
- crash_with_help(cmd, ErrorProgram, "Could not create certificate sign request: %s", err)
- }
- pem_block, err := csr.MarshalPem()
- if err != nil {
- crash_with_help(cmd, ErrorProgram, "Error when marshalling to pem: %s", err)
- }
- _, err = pem_block.WriteTo(FlagOutput)
- if err != nil {
- crash_with_help(cmd, ErrorProgram, "Could not write to output: %s", err)
- }
-}
-
-// crash and provide a helpful message
-func crash_with_help(cmd *Command, code int, message string, args ...interface{}) {
- fmt.Fprintf(os.Stderr, message+"\n", args...)
- cmd.Usage()
- os.Exit(code)
-}
-
-// return the arguments to the program
-func program_args() []string {
- return os.Args[2:]
+func printHelp() {
+ fmt.Printf(`Usage: %s command [flags]
+
+where 'command' is one of:
+ create-private create a private key
+ create-public create a public key derived from a private key
+ sign-input sign a message using a private key
+ verify-input verify a message using a signature and a public key
+ create-sign-request create a certificate sign request
+ create-cert create a certificate from a certificate sign request
+ diff show the differences between two certificates
+`, COMMAND)
}
diff --git a/pem.go b/pem.go
new file mode 100644
index 0000000..d3956f6
--- /dev/null
+++ b/pem.go
@@ -0,0 +1,71 @@
+package main
+
+// handle the pem decoding of files
+
+import (
+ "encoding/pem"
+ "fmt"
+ "io"
+ "io/ioutil"
+
+ "github.com/gibheer/pki"
+)
+
+type (
+ pemMap map[string][][]byte
+)
+
+// Return the content of a section from the pem part.
+//
+// To get this working, the section must only be contained one time and nothing
+// but the wanted section must exist.
+func getSectionFromPem(pems pemMap, label string) ([]byte, error) {
+ if len(pems) > 1 {
+ return []byte{}, fmt.Errorf("too many entries in sign request file")
+ }
+ if len(pems[label]) > 1 {
+ return []byte{}, fmt.Errorf("too many sign requests found in file")
+ }
+ return pems[label][0], nil
+}
+
+// parse the content of a file into a map of pem decoded bodies
+func parseFile(file io.Reader) (pemMap, error) {
+ raw, err := ioutil.ReadAll(file)
+ if err != nil {
+ return nil, err
+ }
+ return parsePem(raw)
+}
+
+// parse a pem encoded payload into a lookup map
+//
+// Returns a map of labels and content and the overall number of found items.
+func parsePem(payload []byte) (pemMap, error) {
+ res := pemMap{}
+ rest := payload
+ rest_len := len(rest)
+ for len(rest) > 0 {
+ var block *pem.Block
+ block, rest = pem.Decode(rest)
+ if block == nil && len(rest) == rest_len {
+ return nil, fmt.Errorf("no pem encoding found")
+ }
+ res[block.Type] = append(res[block.Type], block.Bytes)
+ rest_len = len(rest)
+ }
+ return res, nil
+}
+
+func writePem(o pki.Pemmer, w io.Writer) error {
+ marsh_pem, err := o.MarshalPem()
+ if err != nil {
+ return err
+ }
+
+ _, err = marsh_pem.WriteTo(w)
+ if err != nil {
+ return err
+ }
+ return nil
+}
diff --git a/private_key.go b/private_key.go
deleted file mode 100644
index aa28e78..0000000
--- a/private_key.go
+++ /dev/null
@@ -1,186 +0,0 @@
-package main
-
-import (
- "crypto/elliptic"
- "errors"
- "fmt"
- "os"
-
- "github.com/gibheer/pki"
-)
-
-const (
- RsaLowerLength = 2048
- RsaUpperLength = 16384
-)
-
-var (
- // error messages
- ErrNoPKFound = errors.New("no private key found")
- ErrNoPUFound = errors.New("no public key found")
- ErrUnknownFormat = errors.New("key is an unknown format")
-
- // the possible ecdsa curves allowed to be used
- EcdsaCurves = []int{224, 256, 384, 521}
-
- // Command to create a private key
- CmdCreatePrivateKey = &Command{
- Use: "create-private",
- Short: "create a private key",
- Long: "Create an ecdsa or rsa key with this command",
- Example: "create-private -type=ecdsa -length=521",
- Run: create_private_key,
- }
- // private key specific stuff
- FlagPrivateKeyGeneration privateKeyGenerationFlags
-)
-
-type (
- // The flags specific to create a private key
- privateKeyGenerationFlags struct {
- Type string // type of the private key (rsa, ecdsa)
- Curve elliptic.Curve // curve for ecdsa
- Size int // bitsize for rsa
- }
-)
-
-// create a new private key
-func create_private_key(cmd *Command, args []string) {
- err := checkFlags(checkOutput, checkPrivateKeyGeneration)
- if err != nil {
- crash_with_help(cmd, ErrorFlagInput, "Flags invalid: %s", err)
- }
-
- var pk pki.Pemmer
- switch FlagPrivateKeyGeneration.Type {
- case "ecdsa":
- pk, err = pki.NewPrivateKeyEcdsa(FlagPrivateKeyGeneration.Curve)
- case "rsa":
- pk, err = pki.NewPrivateKeyRsa(FlagPrivateKeyGeneration.Size)
- case "ed25519":
- pk, err = pki.NewPrivateKeyEd25519()
- default:
- crash_with_help(cmd, ErrorInput, "Unknown private key type '%s'", FlagPrivateKeyGeneration.Type)
- }
- if err != nil {
- crash_with_help(cmd, ErrorProgram, "Error creating private key: %s", err)
- }
- marsh_pem, err := pk.MarshalPem()
- if err != nil {
- crash_with_help(cmd, ErrorProgram, "Error when marshalling to pem: %s", err)
- }
- _, err = marsh_pem.WriteTo(FlagOutput)
- if err != nil {
- crash_with_help(cmd, ErrorProgram, "Error when writing output: %s", err)
- }
-}
-
-// This function adds the private key generation flags.
-func InitFlagPrivateKeyGeneration(cmd *Command) {
- cmd.Flags().StringVar(&flagContainer.cryptType, "type", "ecdsa", "the type of the private key (ecdsa, rsa)")
- cmd.Flags().IntVar(
- &flagContainer.length,
- "length", 521,
- fmt.Sprintf("%d - %d for rsa; one of %v for ecdsa", RsaLowerLength, RsaUpperLength, EcdsaCurves),
- )
-}
-
-// check the private key generation variables and move them to the work space
-func checkPrivateKeyGeneration() error {
- pk_type := flagContainer.cryptType
- FlagPrivateKeyGeneration.Type = pk_type
- switch pk_type {
- case "ecdsa":
- switch flagContainer.length {
- case 224:
- FlagPrivateKeyGeneration.Curve = elliptic.P224()
- case 256:
- FlagPrivateKeyGeneration.Curve = elliptic.P256()
- case 384:
- FlagPrivateKeyGeneration.Curve = elliptic.P384()
- case 521:
- FlagPrivateKeyGeneration.Curve = elliptic.P521()
- default:
- return fmt.Errorf("Curve %d unknown!", flagContainer.length)
- }
- case "rsa":
- size := flagContainer.length
- if RsaLowerLength <= size && size <= RsaUpperLength {
- FlagPrivateKeyGeneration.Size = size
- } else {
- return fmt.Errorf("Length of %d is not allowed for rsa!", size)
- }
- case "ed25519":
- default:
- return fmt.Errorf("Type %s is unknown!", pk_type)
- }
- return nil
-}
-
-// add the private key option to the requested flags
-func InitFlagPrivateKey(cmd *Command) {
- cmd.Flags().StringVar(&flagContainer.privateKeyPath, "private-key", "", "path to the private key (required)")
-}
-
-// check the private key flag and load the private key
-func checkPrivateKey() error {
- if flagContainer.privateKeyPath == "" {
- return fmt.Errorf("No private key given!")
- }
- // check permissions of private key file
- info, err := os.Stat(flagContainer.privateKeyPath)
- if err != nil {
- return fmt.Errorf("Error reading private key: %s", err)
- }
- if info.Mode().Perm().String()[4:] != "------" {
- return fmt.Errorf("private key file modifyable by others!")
- }
-
- pk, err := ReadPrivateKeyFile(flagContainer.privateKeyPath)
- if err != nil {
- return fmt.Errorf("Error reading private key: %s", err)
- }
- FlagPrivateKey = pk
- return nil
-}
-
-// Read the private key from the path and try to figure out which type of key it
-// might be.
-func ReadPrivateKeyFile(path string) (pki.PrivateKey, error) {
- raw_pk, err := readSectionFromFile(path, pki.PemLabelEcdsa)
- if err == nil {
- pk, err := pki.LoadPrivateKeyEcdsa(raw_pk)
- if err != nil {
- return nil, err
- }
- return pk, nil
- }
- raw_pk, err = readSectionFromFile(path, pki.PemLabelRsa)
- if err == nil {
- pk, err := pki.LoadPrivateKeyRsa(raw_pk)
- if err != nil {
- return nil, err
- }
- return pk, nil
- }
- return nil, ErrNoPKFound
-}
-
-// read the public key and try to figure out what kind of key it might be
-func ReadPublicKeyFile(path string) (pki.PublicKey, error) {
- raw_pu, err := readSectionFromFile(path, pki.PemLabelPublic)
- if err != nil {
- return nil, ErrNoPUFound
- }
-
- var public pki.PublicKey
- public, err = pki.LoadPublicKeyEcdsa(raw_pu)
- if err == nil {
- return public, nil
- }
- public, err = pki.LoadPublicKeyRsa(raw_pu)
- if err == nil {
- return public, nil
- }
- return nil, ErrUnknownFormat
-}
diff --git a/sign_input.go b/sign_input.go
new file mode 100644
index 0000000..e2c2320
--- /dev/null
+++ b/sign_input.go
@@ -0,0 +1,50 @@
+package main
+
+import (
+ "crypto"
+ "encoding/base64"
+ "flag"
+ "io"
+ "io/ioutil"
+)
+
+func SignInput(args []string) error {
+ fs := flag.NewFlagSet("pkictl sign-input", flag.ExitOnError)
+ flagPrivate := fs.String("private-key", "", "path to the private key or read from stdin")
+ flagInput := fs.String("input", "stdin", "path to the message to sign or stdin")
+ flagOutput := fs.String("output", "stdout", "write private key to file")
+ fs.Parse(args)
+
+ pk, err := loadPrivateKey(*flagPrivate)
+ if err != nil {
+ return err
+ }
+
+ out, err := openOutput(*flagOutput)
+ if err != nil {
+ return err
+ }
+ defer out.Close()
+
+ in, err := openInput(*flagInput)
+ if err != nil {
+ return err
+ }
+ defer in.Close()
+
+ message, err := ioutil.ReadAll(in)
+ if err != nil {
+ return err
+ }
+
+ signature, err := pk.Sign(message, crypto.SHA256)
+ if err != nil {
+ return err
+ }
+
+ _, err = io.WriteString(out, base64.StdEncoding.EncodeToString(signature))
+ if err != nil {
+ return err
+ }
+ return nil
+}
diff --git a/type.go b/type.go
new file mode 100644
index 0000000..c64d98d
--- /dev/null
+++ b/type.go
@@ -0,0 +1,61 @@
+package main
+
+import (
+ "fmt"
+ "net"
+ "strings"
+)
+
+type (
+ ipList []net.IP
+ stringList []string
+)
+
+// return list of IPs as comma separated list
+func (il *ipList) String() string {
+ var res string
+ list := ([]net.IP)(*il)
+ for i := range list {
+ if i > 0 {
+ res += ", "
+ }
+ res += list[i].String()
+ }
+ return res
+}
+
+// receive multiple ip lists as comma separated strings
+func (il *ipList) Set(value string) error {
+ if len(value) == 0 {
+ return nil
+ }
+ var ip net.IP
+
+ parts := strings.Split(value, ",")
+ for i := range parts {
+ ip = net.ParseIP(strings.Trim(parts[i], " \t"))
+ if ip == nil {
+ // TODO encapsulate error in meaningfull error
+ return fmt.Errorf("not a valid IP")
+ }
+ *il = append(*il, ip)
+ }
+ return nil
+}
+
+// return string list as a comma separated list
+func (al *stringList) String() string {
+ return strings.Join(([]string)(*al), ", ")
+}
+
+// receive multiple string lists as comma separated strings
+func (al *stringList) Set(value string) error {
+ if len(value) == 0 {
+ return nil
+ }
+ parts := strings.Split(value, ",")
+ for i := range parts {
+ *al = append(*al, strings.Trim(parts[i], " \t"))
+ }
+ return nil
+}
diff --git a/verify_input.go b/verify_input.go
new file mode 100644
index 0000000..2fd14b3
--- /dev/null
+++ b/verify_input.go
@@ -0,0 +1,76 @@
+package main
+
+import (
+ "crypto"
+ "encoding/base64"
+ "flag"
+ "fmt"
+ "io/ioutil"
+
+ "github.com/gibheer/pki"
+)
+
+func VerifyInput(args []string) error {
+ fs := flag.NewFlagSet("pkictl verify-input", flag.ExitOnError)
+ flagPublic := fs.String("public-key", "", "path to the public key or read from stdin")
+ flagInput := fs.String("input", "stdin", "path to the message or stdin")
+ flagSignature := fs.String("signature", "", "the signature to check the message against")
+ fs.Parse(args)
+
+ sig, err := base64.StdEncoding.DecodeString(*flagSignature)
+ if err != nil {
+ return err
+ }
+
+ in, err := openInput(*flagInput)
+ if err != nil {
+ return err
+ }
+ defer in.Close()
+ msg, err := ioutil.ReadAll(in)
+ if err != nil {
+ return err
+ }
+
+ pub_raw, err := openInput(*flagPublic)
+ if err != nil {
+ return err
+ }
+ defer pub_raw.Close()
+ pem, err := parseFile(pub_raw)
+ if err != nil {
+ return err
+ }
+ if len(pem) > 1 {
+ return fmt.Errorf("too many objects in public key file")
+ }
+ if len(pem[pki.PemLabelPublic]) > 1 {
+ return fmt.Errorf("too many public keys found")
+ }
+
+ public, err := loadPublicKey(pem[pki.PemLabelPublic][0])
+ if err != nil {
+ return err
+ }
+
+ valid, err := public.Verify(msg, sig, crypto.SHA256)
+ if valid {
+ fmt.Println("valid")
+ return nil
+ }
+ fmt.Println("invalid")
+ return err
+}
+
+func loadPublicKey(raw_pu []byte) (pki.PublicKey, error) {
+ if public, err := pki.LoadPublicKeyEd25519(raw_pu); err != nil {
+ return public, nil
+ }
+ if public, err := pki.LoadPublicKeyEcdsa(raw_pu); err == nil {
+ return public, nil
+ }
+ if public, err := pki.LoadPublicKeyRsa(raw_pu); err == nil {
+ return public, nil
+ }
+ return nil, fmt.Errorf("no valid public key found")
+}