Esempio n. 1
0
// 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
}
Esempio n. 2
0
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)
	}
}
Esempio n. 3
0
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)
}
Esempio n. 7
0
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
}
Esempio n. 9
0
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))
	}
}
Esempio n. 10
0
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))
	}
}
Esempio n. 11
0
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()
}
Esempio n. 12
0
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)
}
Esempio n. 13
0
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()
}
Esempio n. 15
0
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
}
Esempio n. 16
0
// 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
}
Esempio n. 17
0
// 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)
}