// OverrideHosts fills template's IPAddresses, EmailAddresses, and DNSNames with the // content of hosts, if it is not nil. func OverrideHosts(template *x509.Certificate, hosts []string) { if hosts != nil { template.IPAddresses = []net.IP{} template.EmailAddresses = []string{} template.DNSNames = []string{} } for i := range hosts { if ip := net.ParseIP(hosts[i]); ip != nil { template.IPAddresses = append(template.IPAddresses, ip) } else if email, err := mail.ParseAddress(hosts[i]); err == nil && email != nil { template.EmailAddresses = append(template.EmailAddresses, email.Address) } else { template.DNSNames = append(template.DNSNames, hosts[i]) } } }
func (s *Signer) sign(template *x509.Certificate, profile *config.SigningProfile) (cert []byte, err error) { var distPoints = template.CRLDistributionPoints err = signer.FillTemplate(template, s.policy.Default, profile) if distPoints != nil && len(distPoints) > 0 { template.CRLDistributionPoints = distPoints } if err != nil { return } var initRoot bool if s.ca == nil { if !template.IsCA { err = cferr.New(cferr.PolicyError, cferr.InvalidRequest) return } template.DNSNames = nil template.EmailAddresses = nil s.ca = template initRoot = true } else if template.IsCA { template.DNSNames = nil template.EmailAddresses = nil } derBytes, err := x509.CreateCertificate(rand.Reader, template, s.ca, template.PublicKey, s.priv) if err != nil { return nil, cferr.Wrap(cferr.CertificateError, cferr.Unknown, err) } if initRoot { s.ca, err = x509.ParseCertificate(derBytes) if err != nil { return nil, cferr.Wrap(cferr.CertificateError, cferr.ParseFailed, err) } } cert = pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: derBytes}) log.Infof("signed certificate with serial number %d", template.SerialNumber) return }
func GenerateFakeX509Certificate(certType string) (string, string) { priv, err := ecdsa.GenerateKey(elliptic.P521(), rand.Reader) if err != nil { logging.GetLogger().Fatal("ECDSA GenerateKey failed : " + err.Error()) } serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128) serialNumber, err := rand.Int(rand.Reader, serialNumberLimit) if err != nil { logging.GetLogger().Fatal("Serial number generation error : " + err.Error()) } template := x509.Certificate{ SerialNumber: serialNumber, Subject: pkix.Name{ Organization: []string{"Skydive Co"}, }, NotBefore: time.Now(), NotAfter: time.Now().Add(1 * time.Hour), KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, BasicConstraintsValid: true, } if certType == "server" { template.ExtKeyUsage = []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth} } else { template.ExtKeyUsage = []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth} } hosts := strings.Split("127.0.0.1,::1", ",") for _, h := range hosts { if ip := net.ParseIP(h); ip != nil { template.IPAddresses = append(template.IPAddresses, ip) } else { template.DNSNames = append(template.DNSNames, h) } } template.DNSNames = append(template.DNSNames, "localhost") template.EmailAddresses = append(template.EmailAddresses, "*****@*****.**") /* Generate CA */ template.IsCA = true template.KeyUsage |= x509.KeyUsageCertSign /* Certificate */ derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, publicKey(priv), priv) if err != nil { logging.GetLogger().Fatalf("Failed to create certificate: %s", err) } basedir, err := ioutil.TempDir("", "skydive-keys") if err != nil { logging.GetLogger().Fatal("can't create tempdir skydive-keys") } certFilename := filepath.Join(basedir, "cert.pem") certOut, err := os.Create(certFilename) if err != nil { logging.GetLogger().Fatalf("failed to open %s for writing: %s", certFilename, err) } pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes}) certOut.Close() /* Private Key */ privKeyFilename := filepath.Join(basedir, "key.pem") keyOut, err := os.OpenFile(privKeyFilename, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600) if err != nil { logging.GetLogger().Fatalf("failed to open %s for writing: %s", privKeyFilename, err) } pem.Encode(keyOut, pemBlockForKey(priv)) keyOut.Close() return certFilename, privKeyFilename }
// Sign signs a new certificate based on the PEM-encoded client // certificate or certificate request with the signing profile, // specified by profileName. func (s *Signer) Sign(req signer.SignRequest) (cert []byte, err error) { profile, err := signer.Profile(s, req.Profile) if err != nil { return } block, _ := pem.Decode([]byte(req.Request)) if block == nil { return nil, cferr.New(cferr.CSRError, cferr.DecodeFailed) } if block.Type != "CERTIFICATE REQUEST" { return nil, cferr.Wrap(cferr.CSRError, cferr.BadRequest, errors.New("not a certificate or csr")) } csrTemplate, err := signer.ParseCertificateRequest(s, block.Bytes) if err != nil { return nil, err } // Copy out only the fields from the CSR authorized by policy. safeTemplate := x509.Certificate{} // If the profile contains no explicit whitelist, assume that all fields // should be copied from the CSR. if profile.CSRWhitelist == nil { safeTemplate = *csrTemplate } else { if profile.CSRWhitelist.Subject { safeTemplate.Subject = csrTemplate.Subject } if profile.CSRWhitelist.PublicKeyAlgorithm { safeTemplate.PublicKeyAlgorithm = csrTemplate.PublicKeyAlgorithm } if profile.CSRWhitelist.PublicKey { safeTemplate.PublicKey = csrTemplate.PublicKey } if profile.CSRWhitelist.SignatureAlgorithm { safeTemplate.SignatureAlgorithm = csrTemplate.SignatureAlgorithm } if profile.CSRWhitelist.DNSNames { safeTemplate.DNSNames = csrTemplate.DNSNames } if profile.CSRWhitelist.IPAddresses { safeTemplate.IPAddresses = csrTemplate.IPAddresses } if profile.CSRWhitelist.EmailAddresses { safeTemplate.EmailAddresses = csrTemplate.EmailAddresses } } OverrideHosts(&safeTemplate, req.Hosts) safeTemplate.Subject = PopulateSubjectFromCSR(req.Subject, safeTemplate.Subject) // If there is a whitelist, ensure that both the Common Name and SAN DNSNames match if profile.NameWhitelist != nil { if safeTemplate.Subject.CommonName != "" { if profile.NameWhitelist.Find([]byte(safeTemplate.Subject.CommonName)) == nil { return nil, cferr.New(cferr.PolicyError, cferr.InvalidPolicy) } } for _, name := range safeTemplate.DNSNames { if profile.NameWhitelist.Find([]byte(name)) == nil { return nil, cferr.New(cferr.PolicyError, cferr.InvalidPolicy) } } for _, name := range safeTemplate.EmailAddresses { if profile.NameWhitelist.Find([]byte(name)) == nil { return nil, cferr.New(cferr.PolicyError, cferr.InvalidPolicy) } } } if profile.ClientProvidesSerialNumbers { if req.Serial == nil { return nil, cferr.New(cferr.CertificateError, cferr.MissingSerial) } safeTemplate.SerialNumber = req.Serial } else { // RFC 5280 4.1.2.2: // Certificate users MUST be able to handle serialNumber // values up to 20 octets. Conforming CAs MUST NOT use // serialNumber values longer than 20 octets. // // If CFSSL is providing the serial numbers, it makes // sense to use the max supported size. serialNumber := make([]byte, 20) _, err = io.ReadFull(rand.Reader, serialNumber) if err != nil { return nil, cferr.Wrap(cferr.CertificateError, cferr.Unknown, err) } // SetBytes interprets buf as the bytes of a big-endian // unsigned integer. The leading byte should be masked // off to ensure it isn't negative. serialNumber[0] &= 0x7F safeTemplate.SerialNumber = new(big.Int).SetBytes(serialNumber) } if len(req.Extensions) > 0 { for _, ext := range req.Extensions { oid := asn1.ObjectIdentifier(ext.ID) if !profile.ExtensionWhitelist[oid.String()] { return nil, cferr.New(cferr.CertificateError, cferr.InvalidRequest) } rawValue, err := hex.DecodeString(ext.Value) if err != nil { return nil, cferr.Wrap(cferr.CertificateError, cferr.InvalidRequest, err) } safeTemplate.ExtraExtensions = append(safeTemplate.ExtraExtensions, pkix.Extension{ Id: oid, Critical: ext.Critical, Value: rawValue, }) } } var certTBS = safeTemplate if len(profile.CTLogServers) > 0 { // Add a poison extension which prevents validation var poisonExtension = pkix.Extension{Id: signer.CTPoisonOID, Critical: true, Value: []byte{0x05, 0x00}} var poisonedPreCert = certTBS poisonedPreCert.ExtraExtensions = append(safeTemplate.ExtraExtensions, poisonExtension) cert, err = s.sign(&poisonedPreCert, profile) if err != nil { return } derCert, _ := pem.Decode(cert) prechain := []ct.ASN1Cert{derCert.Bytes, s.ca.Raw} var sctList []ct.SignedCertificateTimestamp for _, server := range profile.CTLogServers { log.Infof("submitting poisoned precertificate to %s", server) var ctclient = client.New(server) var resp *ct.SignedCertificateTimestamp resp, err = ctclient.AddPreChain(prechain) if err != nil { return nil, cferr.Wrap(cferr.CTError, cferr.PrecertSubmissionFailed, err) } sctList = append(sctList, *resp) } var serializedSCTList []byte serializedSCTList, err = serializeSCTList(sctList) if err != nil { return nil, cferr.Wrap(cferr.CTError, cferr.Unknown, err) } // Serialize again as an octet string before embedding serializedSCTList, err = asn1.Marshal(serializedSCTList) if err != nil { return nil, cferr.Wrap(cferr.CTError, cferr.Unknown, err) } var SCTListExtension = pkix.Extension{Id: signer.SCTListOID, Critical: false, Value: serializedSCTList} certTBS.ExtraExtensions = append(certTBS.ExtraExtensions, SCTListExtension) } var signedCert []byte signedCert, err = s.sign(&certTBS, profile) if err != nil { return nil, err } if s.dbAccessor != nil { var certRecord = &certdb.CertificateRecord{ Serial: certTBS.SerialNumber.String(), CALabel: req.Label, Status: "good", Expiry: certTBS.NotAfter, PEM: string(signedCert), } err = s.dbAccessor.InsertCertificate(certRecord) if err != nil { return nil, err } log.Debug("saved certificate with serial number ", certTBS.SerialNumber) } return signedCert, nil }
func main() { flag.Parse() if *printVersion { fmt.Println("Quickcert v" + version) fmt.Println("https://github.com/andmarios/quickcert") os.Exit(0) } if len(*host) == 0 && !*isCA { fmt.Println("If you are not creating a CA pair, you need to set the -hosts parameter. Use -h for help.") os.Exit(1) } var cacert *x509.Certificate var cacertpem *pem.Block var cakey interface{} var err error // If not CA, read the CA key and cert if !*isCA { // Read CAcert log.Println("Reading CA certificate") data, err := readDecodePemFile(*CAcertFile) checkError("Could not read ca key file: ", err) cacert, err = x509.ParseCertificate(data.Bytes) checkError("Could not parse CA certificate: ", err) cacertpem = data // Read CAkey log.Println("Reading CA private key") data, err = readDecodePemFile(*CAkeyFile) checkError("Could not read ca key file: ", err) // If encrypted, decrypt it if x509.IsEncryptedPEMBlock(data) { password, err := readPassword("CA key is encrypted\nEnter password: "******"Error reading CA private key password: "******"Could not decrypt CA private key: ", err) } // Detect type and parse key if data.Type == "RSA PRIVATE KEY" { cakey, err = x509.ParsePKCS1PrivateKey(data.Bytes) checkError("Could not parse CA RSA private key: ", err) } else if data.Type == "EC PRIVATE KEY" { cakey, err = x509.ParseECPrivateKey(data.Bytes) checkError("Could not parse CA ECDSA key: ", err) } else { log.Fatalf("Could not find a compatible private key type (%s), only RSA and ECDSA are accepted", data.Type) } } // Create new key log.Println("Generating private key. This may take some time, depending on type and length.") var privkey interface{} switch *ecdsaCurve { case "": if *rsaBits < 2048 && !*isCA { log.Println("Consider upgrading your key to 2048 bits or better.") } else if *rsaBits < 4096 && *isCA { log.Println("Consider upgrading your CA key 4096 bits.") } privkey, err = rsa.GenerateKey(rand.Reader, *rsaBits) // I disabled P224 curve because Redhat patched their golang to // not support this curve due to patent law reasons. // I could leave it, but then quickcert won't compile on centos, rhel and fedora // case "P224": // privkey, err = ecdsa.GenerateKey(elliptic.P224(), rand.Reader) case "P256": privkey, err = ecdsa.GenerateKey(elliptic.P256(), rand.Reader) case "P384": privkey, err = ecdsa.GenerateKey(elliptic.P384(), rand.Reader) case "P521": privkey, err = ecdsa.GenerateKey(elliptic.P521(), rand.Reader) default: log.Fatalf("Unrecognized elliptic curve: %q", *ecdsaCurve) } checkError("Failed to generate private key: ", err) // Create certificate log.Println("Generating certificate.") var notBefore time.Time if len(*validFrom) == 0 { notBefore = time.Now() } else { notBefore, err = time.Parse("Jan 2 15:04:05 2006", *validFrom) checkError("Failed to parse creation date: ", err) } // time.Duration takes nanoseconds |--these are nsecs of a day--| duration := time.Duration(*validFor * 24 * 3600 * 1000 * 1000 * 1000) notAfter := notBefore.Add(duration) serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128) serialNumber, err := rand.Int(rand.Reader, serialNumberLimit) checkError("Failed to generate serial number: ", err) template := x509.Certificate{ SerialNumber: serialNumber, Subject: pkix.Name{ Country: []string{}, Organization: []string{}, OrganizationalUnit: []string{}, CommonName: "", }, NotBefore: notBefore, NotAfter: notAfter, KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth}, BasicConstraintsValid: true, } if len(*cnAttr) > 0 { template.Subject.CommonName = *cnAttr } if len(*cAttr) > 0 { template.Subject.Country = append(template.Subject.Country, *cAttr) } if len(*oAttr) > 0 { template.Subject.Organization = append(template.Subject.Organization, *oAttr) } if len(*ouAttr) > 0 { template.Subject.OrganizationalUnit = append(template.Subject.OrganizationalUnit, *ouAttr) } hosts := strings.Split(*host, ",") for _, h := range hosts { if ip := net.ParseIP(h); ip != nil { template.IPAddresses = append(template.IPAddresses, ip) } else { template.DNSNames = append(template.DNSNames, h) } } if len(*email) > 0 { emails := strings.Split(*email, ",") for _, e := range emails { template.EmailAddresses = append(template.EmailAddresses, e) } } if *isCA { cakey = privkey cacert = &template template.IsCA = true template.KeyUsage |= x509.KeyUsageCertSign } // Sign certificate log.Println("Signing certificate") derBytes, err := x509.CreateCertificate(rand.Reader, &template, cacert, publicKey(privkey), cakey) checkError("Failed to create certificate: ", err) // Check if files to be written exist outCrt := *outFile + "crt.pem" outKey := *outFile + "key.pem" if _, err := os.Stat(outCrt); err == nil { checkError("Certificate file exists: ", userConfirmation("Certificate file ("+outCrt+") exists. Overwrite? [Yn]: ")) } if _, err := os.Stat(outKey); err == nil { checkError("Key file exists: ", userConfirmation("Key file ("+outKey+") exists. Overwrite? [Yn]: ")) } // Save certificate to file log.Println("Writing certificate file: ", outCrt) certOut, err := os.Create(outCrt) checkError("Failed to open "+outCrt+" for writing: ", err) pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes}) if *chain { pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: cacertpem.Bytes}) } certOut.Close() // Save private key to file log.Println("Writing key file: ", outKey) keyOut, err := os.OpenFile(outKey, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600) checkError("Failed to open key.pem for writing:", err) keyPemBlock := pemBlockForKey(privkey) if *encryptKey { ASK_KEY: pass1, err := readPassword("Enter password for private key: ") checkError("Error reading private key password, attempt 1: ", err) pass2, err := readPassword("Please re-enter password for private key: ") checkError("Error reading private key password, attempt 2: ", err) if string(pass1) == string(pass2) { keyPemBlock, err = x509.EncryptPEMBlock(rand.Reader, keyPemBlock.Type, keyPemBlock.Bytes, pass1, x509.PEMCipher3DES) } else { fmt.Println("Passwords mismatch. Try again.") goto ASK_KEY } } pem.Encode(keyOut, keyPemBlock) keyOut.Close() log.Println("Files written succesfully. Exiting.") }
// FillTemplate is a utility function that tries to load as much of // the certificate template as possible from the profiles and current // template. It fills in the key uses, expiration, revocation URLs // and SKI. func FillTemplate(template *x509.Certificate, defaultProfile, profile *config.SigningProfile) error { ski, err := ComputeSKI(template) var ( eku []x509.ExtKeyUsage ku x509.KeyUsage backdate time.Duration expiry time.Duration notBefore time.Time notAfter time.Time crlURL, ocspURL string issuerURL = profile.IssuerURL ) // The third value returned from Usages is a list of unknown key usages. // This should be used when validating the profile at load, and isn't used // here. ku, eku, _ = profile.Usages() if profile.IssuerURL == nil { issuerURL = defaultProfile.IssuerURL } if ku == 0 && len(eku) == 0 { return cferr.New(cferr.PolicyError, cferr.NoKeyUsages) } if expiry = profile.Expiry; expiry == 0 { expiry = defaultProfile.Expiry } if crlURL = profile.CRL; crlURL == "" { crlURL = defaultProfile.CRL } if ocspURL = profile.OCSP; ocspURL == "" { ocspURL = defaultProfile.OCSP } if backdate = profile.Backdate; backdate == 0 { backdate = -5 * time.Minute } else { backdate = -1 * profile.Backdate } if !profile.NotBefore.IsZero() { notBefore = profile.NotBefore.UTC() } else { notBefore = time.Now().Round(time.Minute).Add(backdate).UTC() } if !profile.NotAfter.IsZero() { notAfter = profile.NotAfter.UTC() } else { notAfter = notBefore.Add(expiry).UTC() } template.NotBefore = notBefore template.NotAfter = notAfter template.KeyUsage = ku template.ExtKeyUsage = eku template.BasicConstraintsValid = true template.IsCA = profile.CAConstraint.IsCA if template.IsCA { template.MaxPathLen = profile.CAConstraint.MaxPathLen if template.MaxPathLen == 0 { template.MaxPathLenZero = profile.CAConstraint.MaxPathLenZero } template.DNSNames = nil template.EmailAddresses = nil } template.SubjectKeyId = ski if ocspURL != "" { template.OCSPServer = []string{ocspURL} } if crlURL != "" { template.CRLDistributionPoints = []string{crlURL} } if len(issuerURL) != 0 { template.IssuingCertificateURL = issuerURL } if len(profile.Policies) != 0 { err = addPolicies(template, profile.Policies) if err != nil { return cferr.Wrap(cferr.PolicyError, cferr.InvalidPolicy, err) } } if profile.OCSPNoCheck { ocspNoCheckExtension := pkix.Extension{ Id: asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 48, 1, 5}, Critical: false, Value: []byte{0x05, 0x00}, } template.ExtraExtensions = append(template.ExtraExtensions, ocspNoCheckExtension) } return nil }