diff --git a/certificate.go b/certificate.go new file mode 100644 index 0000000..58f42b1 --- /dev/null +++ b/certificate.go @@ -0,0 +1,239 @@ +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-path=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 + } +) + +// 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, "ca", false, "check if the resulting certificate is 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", + ) +} + +// create a certificate +func create_cert(cmd *Command, args []string) { + err := checkFlags(checkPrivateKey, checkOutput, checkCSR, checkCertFlags) + if err != nil { + crash_with_help(cmd, ErrorFlagInput, "Flags invalid: %s", err) + } + + // TODO implement flags for all certificate options + cert, err := FlagCertificateSignRequest.ToCertificate( + FlagPrivateKey, + FlagCertificateGeneration, + nil, + ) + 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 + } + } + // parse the key usage string + 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 + } + // parse the extended key usage flags + 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 +} + +// 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 +} + +// parse the string as a RFC3339 time +func parseTimeRFC3339(tr string) (time.Time, error) { + return time.Parse(time.RFC3339, tr) +} diff --git a/flags.go b/flags.go index b93c156..cb69398 100644 --- a/flags.go +++ b/flags.go @@ -4,74 +4,18 @@ package main // often used by multiple functions. import ( - "crypto/elliptic" - "crypto/x509" "encoding/base64" - "encoding/pem" "fmt" "io" - "io/ioutil" - "math/big" "net" "os" "reflect" "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, - } -) - 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 - } - } - // a container go gather all incoming flags for further processing paramContainer struct { outputPath string // path to output whatever is generated @@ -87,17 +31,6 @@ type ( certificatePath string // path to a certificate } - 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 - } - flagCheck func() error ) @@ -140,14 +73,6 @@ certificate requests and certificates and sign/verify messages.`, 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 @@ -165,8 +90,6 @@ certificate requests and certificates and sign/verify messages.`, FlagCertificateRequestData *pki.CertificateData // the certificate sign request FlagCertificateSignRequest *pki.CertificateRequest - // certificate specific creation stuff - FlagCertificateGeneration pki.CertificateOptions ) func InitFlags() { @@ -234,129 +157,7 @@ func checkPublicKey() error { return nil } -// 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, "ca", false, "check if the resulting certificate is 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", - ) -} - -// 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 - } - } - // parse the key usage string - 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 - } - // parse the extended key usage flags - 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 -} - -func parseTimeRFC3339(tr string) (time.Time, error) { - return time.Parse(time.RFC3339, tr) -} - -// 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 -} - +// initialize the output flag func InitFlagOutput(cmd *Command) { cmd.Flags().StringVar(&flagContainer.outputPath, "output", "STDOUT", "path to the output or STDOUT") } diff --git a/main.go b/main.go index a4eec3f..9b9ab0a 100644 --- a/main.go +++ b/main.go @@ -117,31 +117,6 @@ func create_sign_request(cmd *Command, args []string) { } } -func create_cert(cmd *Command, args []string) { - err := checkFlags(checkPrivateKey, checkOutput, checkCSR, checkCertFlags) - if err != nil { - crash_with_help(cmd, ErrorFlagInput, "Flags invalid: %s", err) - } - - // TODO implement flags for all certificate options - cert, err := FlagCertificateSignRequest.ToCertificate( - FlagPrivateKey, - FlagCertificateGeneration, - nil, - ) - 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) - } -} - // 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...)