rework program flow
This commit is a complete rebuild of pkictl. Before everything was all over the place and adding new commands was kind of a hassle. Now each command has its own file and can be adjusted on a command basis. Options are still used by the same name, but can now use different descriptions.
This commit is contained in:
parent
faaf7d8859
commit
d01892150e
323
certificate.go
323
certificate.go
|
@ -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)
|
|
||||||
}
|
|
124
command.go
124
command.go
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
320
flags.go
320
flags.go
|
@ -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 = ¶mContainer{}
|
|
||||||
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
|
|
||||||
}
|
|
68
io.go
68
io.go
|
@ -1,45 +1,39 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
// handle all io and de/encoding of data
|
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/pem"
|
"fmt"
|
||||||
"errors"
|
"io"
|
||||||
"io/ioutil"
|
"os"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
// Open a path for writing
|
||||||
ErrBlockNotFound = errors.New("block not found")
|
func openOutput(path string) (io.WriteCloser, error) {
|
||||||
)
|
var (
|
||||||
|
err error
|
||||||
// load a pem section from a file
|
out io.WriteCloser
|
||||||
func readSectionFromFile(path, btype string) ([]byte, error) {
|
)
|
||||||
raw, err := readFile(path)
|
if path == "stdout" {
|
||||||
if err != nil {
|
out = os.Stdout
|
||||||
return raw, err
|
} else {
|
||||||
}
|
out, err = os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_EXCL|os.O_SYNC, 0700)
|
||||||
|
if err != nil {
|
||||||
return decodeSection(raw, btype)
|
return nil, err
|
||||||
}
|
|
||||||
|
|
||||||
// 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
|
|
||||||
}
|
|
||||||
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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return EmptyByteArray, ErrBlockNotFound
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Open a path for reading the content
|
||||||
|
func openInput(path string) (io.ReadCloser, error) {
|
||||||
|
if path == "" {
|
||||||
|
return nil, fmt.Errorf("empty path is invalid")
|
||||||
|
}
|
||||||
|
var err error
|
||||||
|
var in io.ReadCloser
|
||||||
|
if path == "stdin" {
|
||||||
|
in = os.Stdin
|
||||||
|
} else {
|
||||||
|
in, err = os.Open(path)
|
||||||
|
}
|
||||||
|
return in, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
145
main.go
145
main.go
|
@ -4,130 +4,53 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto"
|
|
||||||
"encoding/base64"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/gibheer/pki"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
ErrorProgram int = iota
|
COMMAND = "pkictl"
|
||||||
ErrorFlagInput
|
|
||||||
ErrorInput
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
EmptyByteArray = make([]byte, 0)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
InitFlags()
|
if len(os.Args) == 1 {
|
||||||
CmdRoot.Execute()
|
printHelp()
|
||||||
}
|
return
|
||||||
|
|
||||||
// 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)
|
|
||||||
}
|
}
|
||||||
|
command, args := os.Args[1], os.Args[2:]
|
||||||
var pub_key pki.Pemmer
|
var err error
|
||||||
pub_key = FlagPrivateKey.Public()
|
switch command {
|
||||||
marsh_pem, err := pub_key.MarshalPem()
|
case "create-private":
|
||||||
if err != nil {
|
err = CreatePrivateKey(args)
|
||||||
crash_with_help(cmd, ErrorProgram, "Error when marshalling to pem: %s", err)
|
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()
|
||||||
}
|
}
|
||||||
_, err = marsh_pem.WriteTo(FlagOutput)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
crash_with_help(cmd, ErrorProgram, "Error when writing output: %s", err)
|
fmt.Printf("error: %s\n", err)
|
||||||
|
os.Exit(2)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// sign a message using he private key
|
func printHelp() {
|
||||||
func sign_input(cmd *Command, args []string) {
|
fmt.Printf(`Usage: %s command [flags]
|
||||||
err := checkFlags(checkPrivateKey, checkInput, checkOutput)
|
|
||||||
if err != nil {
|
|
||||||
crash_with_help(cmd, ErrorFlagInput, "Flags invalid: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
message, err := ioutil.ReadAll(FlagInput)
|
where 'command' is one of:
|
||||||
if err != nil {
|
create-private create a private key
|
||||||
crash_with_help(cmd, ErrorProgram, "Error reading input: %s", err)
|
create-public create a public key derived from a private key
|
||||||
}
|
sign-input sign a message using a private key
|
||||||
signature, err := FlagPrivateKey.Sign(message, crypto.SHA256)
|
verify-input verify a message using a signature and a public key
|
||||||
if err != nil {
|
create-sign-request create a certificate sign request
|
||||||
crash_with_help(cmd, ErrorProgram, "Could not compute signature: %s", err)
|
create-cert create a certificate from a certificate sign request
|
||||||
}
|
diff show the differences between two certificates
|
||||||
_, err = io.WriteString(FlagOutput, base64.StdEncoding.EncodeToString(signature))
|
`, COMMAND)
|
||||||
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")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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:]
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
186
private_key.go
186
private_key.go
|
@ -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
|
|
||||||
}
|
|
|
@ -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
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -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")
|
||||||
|
}
|
Loading…
Reference in New Issue