// NewLog returns an initialized Log struct func NewLog(uri, b64PK string) (*Log, error) { url, err := url.Parse(uri) if err != nil { return nil, err } url.Path = strings.TrimSuffix(url.Path, "/") client := ctClient.New(url.String(), nil) pkBytes, err := base64.StdEncoding.DecodeString(b64PK) if err != nil { return nil, fmt.Errorf("Failed to decode base64 log public key") } pk, err := x509.ParsePKIXPublicKey(pkBytes) if err != nil { return nil, fmt.Errorf("Failed to parse log public key") } verifier, err := ct.NewSignatureVerifier(pk) if err != nil { return nil, err } // Replace slashes with dots for statsd logging sanitizedPath := strings.TrimPrefix(url.Path, "/") sanitizedPath = strings.Replace(sanitizedPath, "/", ".", -1) return &Log{ logID: b64PK, uri: uri, statName: fmt.Sprintf("%s.%s", url.Host, sanitizedPath), client: client, verifier: verifier, }, nil }
func main() { flag.Parse() logClient, err := client.New(*logUri, &http.Client{ Transport: &httpclient.Transport{ ConnectTimeout: 10 * time.Second, RequestTimeout: 30 * time.Second, ResponseHeaderTimeout: 30 * time.Second, MaxIdleConnsPerHost: 10, DisableKeepAlives: false, }, }, jsonclient.Options{}) if err != nil { log.Fatal(err) } matcher, err := createMatcherFromFlags() if err != nil { log.Fatal(err) } opts := scanner.ScannerOptions{ Matcher: matcher, BatchSize: *batchSize, NumWorkers: *numWorkers, ParallelFetch: *parallelFetch, StartIndex: *startIndex, Quiet: *quiet, } scanner := scanner.NewScanner(logClient, opts) if *printChains { scanner.Scan(logFullChain, logFullChain) } else { scanner.Scan(logCertInfo, logPrecertInfo) } }
func NewLog(db *Database, stats statsd.Statter, kl KnownLog, bufferSize, dbWorkers int) (*Log, error) { name, uri := kl.Description, kl.URL if !strings.HasPrefix(uri, "http") { uri = "https://" + uri } pkBytes, err := base64.StdEncoding.DecodeString(kl.Key) if err != nil { return nil, err } id := sha256.Sum256(pkBytes) log := &Log{ Name: strings.Replace(name, ".", " ", -1), ID: id[:], uri: uri, client: ctClient.New(uri), db: db, dbWorkers: dbWorkers, stats: stats, validRoots: make(map[string]struct{}), bufferSize: bufferSize, } err = log.populateRoots() if err != nil { return nil, err } err = log.UpdateLocalIndex() if err != nil { return nil, err } return log, nil }
func TestScannerEndToEnd(t *testing.T) { ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { switch r.URL.Path { case "/ct/v1/get-sth": log.Printf("GetSTH") if _, err := w.Write([]byte(FourEntrySTH)); err != nil { t.Fatal("Failed to write get-sth response") } case "/ct/v1/get-entries": log.Printf("GetEntries %s", r.URL.RawQuery) if _, err := w.Write([]byte(FourEntries)); err != nil { t.Fatal("Failed to write get-sth response") } default: t.Fatal("Unexpected request") } })) defer ts.Close() logClient := client.New(ts.URL) opts := ScannerOptions{ Matcher: &MatchSubjectRegex{regexp.MustCompile(".*\\.google\\.com"), nil}, BatchSize: 10, NumWorkers: 1, ParallelFetch: 1, StartIndex: 0, } scanner := NewScanner(logClient, opts) var matchedCerts list.List var matchedPrecerts list.List err := scanner.Scan(func(e *client.LogEntry) { // Annoyingly we can't t.Fatal() in here, as this is run in another go // routine matchedCerts.PushBack(*e.X509Cert) }, func(e *client.LogEntry) { matchedPrecerts.PushBack(*e.Precert) }) if err != nil { t.Fatal(err) } if matchedPrecerts.Len() != 0 { t.Fatal("Found unexpected Precert") } switch matchedCerts.Len() { case 0: t.Fatal("Failed to find mail.google.com cert") case 1: if matchedCerts.Front().Value.(x509.Certificate).Subject.CommonName != "mail.google.com" { t.Fatal("Matched unexpected cert") } default: t.Fatal("Found unexpected number of certs") } }
func main() { cli := client.New("https://ct.googleapis.com/pilot") opts := ScannerOptions{ Matcher: &scanner.MatchSubjectRegex{regexp.MustCompile(".*\\.google\\.com"), nil}, BatchSize: 10, NumWorkers: 1, ParallelFetch: 1, StartIndex: 0, } scan := scanner.NewScanner(cli, nil) }
func main() { flag.Parse() logClient := client.New(*logUri) matcher, err := createMatcherFromFlags() if err != nil { log.Fatal(err) } opts := scanner.ScannerOptions{ Matcher: matcher, BatchSize: *batchSize, NumWorkers: *numWorkers, ParallelFetch: *parallelFetch, StartIndex: *startIndex, Quiet: *quiet, } scanner := scanner.NewScanner(logClient, opts) scanner.Scan(logCertInfo, logPrecertInfo) }
func main() { flag.Parse() u, err := url.Parse(logURL) if err != nil { log.Fatal(err) } if u.Scheme == "" { u.Scheme = "https" } c := client.New(u.String()) opts := scanner.ScannerOptions{ Matcher: NewMatchEV(earliestString, latestString), BatchSize: 1000, NumWorkers: 100, ParallelFetch: 10, Quiet: !verbose, } s := scanner.NewScanner(c, opts) l, err := newLogger(csvFile) if err != nil { log.Fatal(err) } log.Printf("Scanning %s for EV certs...\n", u.String()) err = s.Scan(func(le *ct.LogEntry) { err := l.Write(append([]string{le.X509Cert.Issuer.CommonName, le.X509Cert.NotBefore.Format(format), le.X509Cert.Subject.CommonName}, le.X509Cert.DNSNames...)) if err != nil { log.Println(err) } }, func(le *ct.LogEntry) {}) if err != nil { log.Fatal(err) } l.Flush() fmt.Printf("Found %d EV Certs\n", l.count) }
// NewLog returns an initialized Log struct func NewLog(uri, b64PK string) (*Log, error) { if strings.HasSuffix(uri, "/") { uri = uri[0 : len(uri)-2] } client := ctClient.New(uri) pkBytes, err := base64.StdEncoding.DecodeString(b64PK) if err != nil { return nil, fmt.Errorf("Failed to decode base64 log public key") } pk, err := x509.ParsePKIXPublicKey(pkBytes) if err != nil { return nil, fmt.Errorf("Failed to parse log public key") } verifier, err := ct.NewSignatureVerifier(pk) if err != nil { return nil, err } return &Log{uri, client, verifier}, nil }
func main() { flag.Parse() httpClient := &http.Client{ Transport: &httpclient.Transport{ ConnectTimeout: 10 * time.Second, RequestTimeout: 30 * time.Second, ResponseHeaderTimeout: 30 * time.Second, MaxIdleConnsPerHost: 10, DisableKeepAlives: false, }} var opts jsonclient.Options if *pubKey != "" { pubkey, err := ioutil.ReadFile(*pubKey) if err != nil { log.Fatal(err) } opts.PublicKey = string(pubkey) } logClient, err := client.New(*logURI, httpClient, opts) if err != nil { log.Fatal(err) } args := flag.Args() if len(args) != 1 { dieWithUsage("Need command argument") } ctx := context.Background() cmd := args[0] switch cmd { case "sth": getSTH(ctx, logClient) case "upload": addChain(ctx, logClient) case "getroots", "get_roots", "get-roots": getRoots(ctx, logClient) default: dieWithUsage(fmt.Sprintf("Unknown command '%s'", cmd)) } }
func main() { flag.Parse() httpClient := &http.Client{ Transport: &httpclient.Transport{ ConnectTimeout: 10 * time.Second, RequestTimeout: 30 * time.Second, ResponseHeaderTimeout: 30 * time.Second, MaxIdleConnsPerHost: 10, DisableKeepAlives: false, }} var logClient *client.LogClient if *pubKey == "" { logClient = client.New(*logURI, httpClient) } else { pubkey, err := ioutil.ReadFile(*pubKey) if err != nil { log.Fatal(err) } logClient, err = client.NewWithPubKey(*logURI, httpClient, string(pubkey)) if err != nil { log.Fatal(err) } } args := flag.Args() if len(args) != 1 { dieWithUsage("Need command argument") } cmd := args[0] switch cmd { case "sth": getSTH(logClient) case "upload": addChain(logClient) default: dieWithUsage(fmt.Sprintf("Unknown command '%s'", cmd)) } }
func main() { flag.Parse() var sctFileWriter io.Writer var err error if *sctInputFile != "" { sctFileWriter, err = os.Create(*sctInputFile) if err != nil { log.Fatal(err) } } else { sctFileWriter = ioutil.Discard } sctWriter := zlib.NewWriter(sctFileWriter) defer func() { err := sctWriter.Close() if err != nil { log.Fatal(err) } }() transport := &httpclient.Transport{ ConnectTimeout: 10 * time.Second, RequestTimeout: 30 * time.Second, ResponseHeaderTimeout: 30 * time.Second, MaxIdleConnsPerHost: 10, DisableKeepAlives: false, } fetchLogClient, err := client.New(*sourceLogUri, &http.Client{ Transport: transport, }, jsonclient.Options{}) if err != nil { log.Fatal(err) } matcher, err := createMatcher() if err != nil { log.Fatal(err) } opts := scanner.ScannerOptions{ Matcher: matcher, BatchSize: *batchSize, NumWorkers: *numWorkers, ParallelFetch: *parallelFetch, StartIndex: *startIndex, Quiet: *quiet, } scanner := scanner.NewScanner(fetchLogClient, opts) certs := make(chan *ct.LogEntry, *batchSize**parallelFetch) precerts := make(chan *ct.LogEntry, *batchSize**parallelFetch) addedCerts := make(chan *preload.AddedCert, *batchSize**parallelFetch) var sctWriterWG sync.WaitGroup sctWriterWG.Add(1) go sctWriterJob(addedCerts, sctWriter, &sctWriterWG) submitLogClient, err := client.New(*targetLogUri, &http.Client{ Transport: transport, }, jsonclient.Options{}) if err != nil { log.Fatal(err) } backgroundCtx := context.Background() var submitterWG sync.WaitGroup for w := 0; w < *parallelSubmit; w++ { submitterWG.Add(2) go certSubmitterJob(backgroundCtx, addedCerts, submitLogClient, certs, &submitterWG) go precertSubmitterJob(backgroundCtx, addedCerts, submitLogClient, precerts, &submitterWG) } addChainFunc := func(entry *ct.LogEntry) { certs <- entry } addPreChainFunc := func(entry *ct.LogEntry) { precerts <- entry } scanner.Scan(addChainFunc, addPreChainFunc) close(certs) close(precerts) submitterWG.Wait() close(addedCerts) sctWriterWG.Wait() }
func main() { log.SetFlags(0) log.SetPrefix("") dbConnectStr, err := sqldb.RecombineURLForDB(*config.DbConnect) if err != nil { log.Printf("unable to parse %s: %s", *config.DbConnect, err) } if len(dbConnectStr) == 0 || (config.CensysPath == nil && config.LogUrl == nil) { config.Usage() os.Exit(2) } db, err := sql.Open("mysql", dbConnectStr) if err != nil { log.Fatalf("unable to open SQL: %s: %s", dbConnectStr, err) } if err = db.Ping(); err != nil { log.Fatalf("unable to ping SQL: %s: %s", dbConnectStr, err) } var certFolderDB *utils.FolderDatabase if config.CertPath != nil && len(*config.CertPath) > 0 { certFolderDB, err = utils.NewFolderDatabase(*config.CertPath, 0444, *config.CertsPerFolder) if err != nil { log.Fatalf("unable to open Certificate Path: %s: %s", config.CertPath, err) } } dialect := gorp.MySQLDialect{Engine: "InnoDB", Encoding: "UTF8"} dbMap := &gorp.DbMap{Db: db, Dialect: dialect} entriesDb := &sqldb.EntriesDatabase{DbMap: dbMap, Verbose: *config.Verbose, FullCerts: certFolderDB, KnownIssuers: make(map[string]int)} err = entriesDb.InitTables() if err != nil { log.Fatalf("unable to prepare SQL: %s: %s", dbConnectStr, err) } if config.LogUrl != nil && len(*config.LogUrl) > 5 { ctLogUrl, err := url.Parse(*config.LogUrl) if err != nil { log.Fatalf("unable to set Certificate Log: %s", err) } ctLog := client.New(*config.LogUrl, nil) log.Printf("Starting download from log %s, fullCerts=%t\n", ctLogUrl, (certFolderDB != nil)) err = downloadLog(ctLogUrl, ctLog, entriesDb) if err != nil { log.Fatalf("error while updating CT entries: %s", err) } os.Exit(0) } var importer censysdata.Importer if config.CensysUrl != nil && len(*config.CensysUrl) > 5 { urlImporter, err := censysdata.OpenURL(*config.CensysUrl) if err != nil { log.Fatalf("unable to open Censys URL: %s", err) } importer = urlImporter } else if config.CensysPath != nil && len(*config.CensysPath) > 5 { fileImporter, err := censysdata.OpenFile(*config.CensysPath) if err != nil { log.Fatalf("unable to open Censys file: %s", err) } importer = fileImporter defer fileImporter.Close() } else if *config.CensysStdin { stdinImporter, err := censysdata.OpenFileHandle(os.Stdin) if err != nil { log.Fatalf("unable to open stdin: %s", err) } importer = stdinImporter defer stdinImporter.Close() } if importer != nil { log.Printf("Starting Censys Import, using %s, fullCerts=%t\n", importer.String(), (certFolderDB != nil)) wg := new(sync.WaitGroup) err = processImporter(importer, entriesDb, wg) if err != nil { log.Fatalf("error while running importer: %s", err) } wg.Wait() os.Exit(0) } // Didn't include a mandatory action, so print usage and exit. config.Usage() os.Exit(2) }
func main() { var ( err error offset int ) // create a certificate transparency client ctLog := client.New("http://ct.googleapis.com/aviator", nil) httpCli := &http.Client{ Transport: &http.Transport{ DisableCompression: true, DisableKeepAlives: false, }, Timeout: 10 * time.Second, } if len(os.Args) > 1 { offset, err = strconv.Atoi(os.Args[1]) if err != nil { log.Fatal(err) } } for { log.Printf("retrieving CT logs %d to %d", offset, offset+100) rawEnts, err := ctLog.GetEntries(int64(offset), int64(offset+100)) if err != nil { log.Fatal(err) } // loop over CT records for i, ent := range rawEnts { log.Printf("CT index=%d", offset+i) var cert *x509.Certificate switch ent.Leaf.TimestampedEntry.EntryType { case ct.X509LogEntryType: cert, err = x509.ParseCertificate(ent.Leaf.TimestampedEntry.X509Entry) case ct.PrecertLogEntryType: cert, err = x509.ParseTBSCertificate(ent.Leaf.TimestampedEntry.PrecertEntry.TBSCertificate) } if err != nil { log.Fatal(err) } log.Printf("CN=%s", cert.Subject.CommonName) log.Printf("Not Before=%s", cert.NotBefore) log.Printf("Not After=%s", cert.NotAfter) // Format the PEM certificate payload := base64.StdEncoding.EncodeToString(cert.Raw) buf := new(bytes.Buffer) fmt.Fprintf(buf, "-----BEGIN CERTIFICATE-----\n") for len(payload) > 0 { chunkLen := len(payload) if chunkLen > 64 { chunkLen = 64 } fmt.Fprintf(buf, "%s\n", payload[0:chunkLen]) payload = payload[chunkLen:] } fmt.Fprintf(buf, "-----END CERTIFICATE-----") // create a mime/multipart form with the certificate var b bytes.Buffer w := multipart.NewWriter(&b) fw, err := w.CreateFormFile("certificate", certificate.SHA256Hash(cert.Raw)) if err != nil { log.Fatal(err) } _, err = io.Copy(fw, buf) if err != nil { log.Fatal(err) } w.Close() // post the form to the tls-observatory api r, err := http.NewRequest("POST", "https://tls-observatory.services.mozilla.com/api/v1/certificate", &b) if err != nil { log.Fatal(err) } r.Header.Set("Content-Type", w.FormDataContentType()) resp, err := httpCli.Do(r) if err != nil { log.Printf("%v\n\n", err) continue } defer resp.Body.Close() body, err := ioutil.ReadAll(resp.Body) if err != nil { log.Fatal(err) } if resp.StatusCode != http.StatusCreated { log.Fatalf("Expected HTTP 201 Created, got %q\n%s", resp.Status, body) } // parse the returned cert var tlsobs_cert certificate.Certificate err = json.Unmarshal(body, &tlsobs_cert) if err != nil { log.Fatal(err) } log.Printf("https://tls-observatory.services.mozilla.com/api/v1/certificate?id=%d\n\n", tlsobs_cert.ID) } offset += 100 } }
func main() { flag.Parse() var sctFileWriter io.Writer var err error if *sctInputFile != "" { sctFileWriter, err = os.Create(*sctInputFile) if err != nil { log.Fatal(err) } } else { sctFileWriter = ioutil.Discard } sctWriter := zlib.NewWriter(sctFileWriter) defer func() { err := sctWriter.Close() if err != nil { log.Fatal(err) } }() fetchLogClient := client.New(*sourceLogUri) matcher, err := createMatcher() if err != nil { log.Fatal(err) } opts := scanner.ScannerOptions{ Matcher: matcher, BatchSize: *batchSize, NumWorkers: *numWorkers, ParallelFetch: *parallelFetch, StartIndex: *startIndex, Quiet: *quiet, } scanner := scanner.NewScanner(fetchLogClient, opts) certs := make(chan *ct.LogEntry, *batchSize**parallelFetch) precerts := make(chan *ct.LogEntry, *batchSize**parallelFetch) addedCerts := make(chan *preload.AddedCert, *batchSize**parallelFetch) var sctWriterWG sync.WaitGroup sctWriterWG.Add(1) go sctWriterJob(addedCerts, sctWriter, &sctWriterWG) submitLogClient := client.New(*targetLogUri) var submitterWG sync.WaitGroup for w := 0; w < *parallelSubmit; w++ { submitterWG.Add(2) go certSubmitterJob(addedCerts, submitLogClient, certs, &submitterWG) go precertSubmitterJob(addedCerts, submitLogClient, precerts, &submitterWG) } addChainFunc := func(entry *ct.LogEntry) { certs <- entry } addPreChainFunc := func(entry *ct.LogEntry) { precerts <- entry } scanner.Scan(addChainFunc, addPreChainFunc) close(certs) close(precerts) submitterWG.Wait() close(addedCerts) sctWriterWG.Wait() }
func (m *MonCtx) Serve(ctx context.Context) error { logClient := client.New(m.conf.LogUri) CNset := make(map[string]bool) for _, v := range m.conf.CAWhitelist { CNset[v] = true } matcher, err := matcher.CreateMatcherFromFlags(m.conf.MatchSubjectRegex, CNset) if err != nil { log.Fatal(err) } opts := scanner.DefaultScannerOptions() opts.Matcher = matcher opts.BatchSize = m.conf.BatchSize opts.NumWorkers = m.conf.NumWorkers opts.ParallelFetch = m.conf.ParallelFetch opts.StartIndex = m.StartIndex opts.TickTime = time.Duration(m.conf.TickTime) * time.Second opts.Tickers = []scanner.Ticker{scanner.LogTicker{}} opts.Quiet = !m.conf.Verbose if m.db != nil { opts.Tickers = append(opts.Tickers, StateSaverTicker{mon: m}) } if m.db != nil && m.conf.StoreMatches { ch := make(chan models.MonEvent) m.Handlers = append(m.Handlers, ch) dbWorker := db.CertHandler{DB: m.db} go dbWorker.HandleEvents(ch) } if m.conf.NotifyMatches { ch := make(chan models.MonEvent) m.Handlers = append(m.Handlers, ch) smtpWorker := mail.CertHandler{Emails: m.conf.Emails, Host: m.conf.SMTPHost, Port: m.conf.SMTPPort, User: m.conf.SMTPUser, Password: m.conf.SMTPPasswd, From: m.conf.SMTPFrom, Subj: m.conf.SMTPSubj} go smtpWorker.HandleEvents(ch) } for { scanner := scanner.NewScanner(logClient, *opts) err = scanner.Scan(func(entry *ct.LogEntry) { for _, ch := range m.Handlers { e := models.MonEvent{Type: models.CT_CERT, LogEntry: entry} ch <- e } }, func(entry *ct.LogEntry) { for _, ch := range m.Handlers { e := models.MonEvent{Type: models.CT_PRECERT, LogEntry: entry} ch <- e } }) if m.conf.RescanPeriod <= 0 { break } if m.conf.Verbose { log.Print("Scan complete sleeping...") } /* do not fetch from old startindex in cycle */ opts.StartIndex = m.StartIndex + int64(scanner.CertsProcessed) time.Sleep(time.Duration(m.conf.RescanPeriod) * time.Second) } for _, ch := range m.Handlers { e := models.MonEvent{Type: models.CT_QUIT, LogEntry: nil} ch <- e } return nil }
// 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 } } 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) } } } if profile.ClientProvidesSerialNumbers { if req.Serial == nil { fmt.Printf("xx %#v\n", profile) 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.db != nil { var certRecord = &certdb.CertificateRecord{ Serial: certTBS.SerialNumber.String(), CALabel: req.Label, Status: "good", Expiry: certTBS.NotAfter, PEM: string(signedCert), } err = certdb.InsertCertificate(s.db, certRecord) if err != nil { return nil, err } log.Debug("saved certificate with serial number ", certTBS.SerialNumber) } return signedCert, nil }
// 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 } } 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) } } } if profile.ClientProvidesSerialNumbers { if req.Serial == nil { fmt.Printf("xx %#v\n", profile) return nil, cferr.New(cferr.CertificateError, cferr.MissingSerial) } safeTemplate.SerialNumber = req.Serial } else { serialNumber, err := rand.Int(rand.Reader, new(big.Int).SetInt64(math.MaxInt64)) if err != nil { return nil, cferr.Wrap(cferr.CertificateError, cferr.Unknown, err) } safeTemplate.SerialNumber = serialNumber } 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) } return s.sign(&certTBS, profile) }