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 = ¶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 -} 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" + "fmt" + "io" + "os" ) -var ( - ErrBlockNotFound = errors.New("block not found") -) - -// load a pem section from a file -func readSectionFromFile(path, btype string) ([]byte, error) { - raw, err := readFile(path) - if err != nil { - return raw, err - } - - return decodeSection(raw, btype) -} - -// 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 +// 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 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 } 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) + if len(os.Args) == 1 { + printHelp() + return } - - 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) + 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() } - _, err = marsh_pem.WriteTo(FlagOutput) 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 sign_input(cmd *Command, args []string) { - err := checkFlags(checkPrivateKey, checkInput, checkOutput) - if err != nil { - crash_with_help(cmd, ErrorFlagInput, "Flags invalid: %s", err) - } +func printHelp() { + fmt.Printf(`Usage: %s command [flags] - 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") - } -} - -// 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:] +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") +}