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) }