package main
// This file handles the complete parameter assignment, as some parameters are
// often used by multiple functions.
import (
"crypto/elliptic"
"encoding/base64"
"encoding/pem"
"fmt"
"io"
"io/ioutil"
"net"
"os"
"reflect"
"strings"
"github.com/gibheer/pki"
)
const (
RsaLowerLength = 2048
RsaUpperLength = 16384
)
var (
EcdsaCurves = []int{224, 256, 384, 521}
)
type (
// holds all certificate related flags, which need parsing afterwards
certFlagsContainer 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
}
}
// 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 certFlagsContainer // container for certificate related flags
signature string // a base64 encoded signature
}
privateKeyGenerationFlags struct {
Type string // type of the private key (rsa, ecdsa)
Curve elliptic.Curve // curve for ecdsa
Size int // bitsize for rsa
}
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.`,
}
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,
}
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,
}
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-path=foo.csr",
Run: create_cert,
}
// 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
// private key specific stuff
FlagPrivateKeyGeneration privateKeyGenerationFlags
// a certificate filled with the parameters
FlagCertificateRequestData *pki.CertificateData
// the certificate sign request
FlagCertificateSignRequest *pki.CertificateRequest
)
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)
InitFlagPrivateKey(CmdVerifyInput)
InitFlagOutput(CmdVerifyInput)
InitFlagSignature(CmdVerifyInput)
// create-sign-request
InitFlagPrivateKey(CmdCreateSignRequest)
InitFlagOutput(CmdCreateSignRequest)
InitFlagCertificateFields(CmdCreateSignRequest)
// create-certificate
InitFlagPrivateKey(CmdCreateCert)
InitFlagOutput(CmdCreateCert)
InitFlagCSR(CmdCreateCert)
}
func checkFlags(checks... flagCheck) error {
for _, check := range checks {
if err := check(); err != nil {
return err
}
}
return nil
}
//// print a message with the usage part
//func (f *Flags) Usagef(message string, args ...interface{}) {
// fmt.Fprintf(os.Stderr, "error: " + message + "\n", args...)
// f.flagset.Flags().Usage()
//}
// 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
}
// 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
}
// add flag to load certificate sign request
func InitFlagCSR(cmd *Command) {
cmd.Flags().StringVar(&flagContainer.signRequestPath, "csr-path", "", "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 == "CERTIFICATE REQUEST" {
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
}
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
}
// 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)
}
default: return fmt.Errorf("Type %s is unknown!", pk_type)
}
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
}