浏览代码

cmd/stvanity: New utility to create vanity device IDs

A potential practical use is to encode a short version of the hostname
at the beginning of the device ID.

For example:

	jb@syno:~/s/g/s/s/c/stvanity $ stvanity abc
	Want 15 bits for prefix "ABC", about 3.3e+04 certs to test (statistically speaking)
	Found ABCFPWS-JKDIFV3-E5IUAQW-DK53WVR-HY7XWBS-56H33GR-CJQI67Q-VGXRMAW
	Saved to cert.pem, key.pem

	jb@syno:~/s/g/s/s/c/stvanity $ stvanity $(hostname)
	Want 20 bits for prefix "SYNO", about 1e+06 certs to test (statistically speaking)
	Trying 554 certs/s, tested 8307 so far in 15s, expect ~32m total time to complete
	Trying 543 certs/s, tested 16277 so far in 30s, expect ~32m total time to complete
	...

The rest is just a matter of patience.

	jb@syno:~/s/g/s/s/c/stvanity $ stvanity syncthing
	Want 50 bits for prefix "SYNCTHI-NG", about 1.1e+15 certs to test (statistically speaking)
	Trying 529 certs/s, tested 7941 so far in 15s, expect ~67443 years total time to complete
	...

GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/2986
Jakob Borg 9 年之前
父节点
当前提交
0d2fe320a7
共有 1 个文件被更改,包括 212 次插入0 次删除
  1. 212 0
      cmd/stvanity/main.go

+ 212 - 0
cmd/stvanity/main.go

@@ -0,0 +1,212 @@
+// Copyright (C) 2016 The Syncthing Authors.
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this file,
+// You can obtain one at http://mozilla.org/MPL/2.0/.
+
+package main
+
+import (
+	"crypto/ecdsa"
+	"crypto/elliptic"
+	"crypto/rand"
+	"crypto/rsa"
+	"crypto/x509"
+	"crypto/x509/pkix"
+	"encoding/pem"
+	"flag"
+	"fmt"
+	"math/big"
+	mr "math/rand"
+	"os"
+	"runtime"
+	"strings"
+	"sync"
+	"sync/atomic"
+	"time"
+
+	"github.com/syncthing/syncthing/lib/protocol"
+)
+
+type result struct {
+	id       protocol.DeviceID
+	priv     *ecdsa.PrivateKey
+	derBytes []byte
+}
+
+func main() {
+	flag.Parse()
+	prefix := strings.ToUpper(strings.Replace(flag.Arg(0), "-", "", -1))
+	if len(prefix) > 7 {
+		prefix = prefix[:7] + "-" + prefix[7:]
+	}
+
+	found := make(chan result)
+	stop := make(chan struct{})
+	var count int64
+
+	// Print periodic progress reports.
+	go printProgress(prefix, &count)
+
+	// Run one certificate generator per CPU core.
+	var wg sync.WaitGroup
+	for i := 0; i < runtime.GOMAXPROCS(-1); i++ {
+		wg.Add(1)
+		go func() {
+			generatePrefixed(prefix, &count, found, stop)
+			wg.Done()
+		}()
+	}
+
+	// Save the result, when one has been found.
+	res := <-found
+	close(stop)
+	wg.Wait()
+
+	fmt.Println("Found", res.id)
+	saveCert(res.priv, res.derBytes)
+	fmt.Println("Saved to cert.pem, key.pem")
+}
+
+// Try certificates until one is found that has the prefix at the start of
+// the resulting device ID. Increments count atomically, sends the result to
+// found, returns when stop is closed.
+func generatePrefixed(prefix string, count *int64, found chan<- result, stop <-chan struct{}) {
+	notBefore := time.Now()
+	notAfter := time.Date(2049, 12, 31, 23, 59, 59, 0, time.UTC)
+
+	template := x509.Certificate{
+		SerialNumber: new(big.Int).SetInt64(mr.Int63()),
+		Subject: pkix.Name{
+			CommonName: "syncthing",
+		},
+		NotBefore: notBefore,
+		NotAfter:  notAfter,
+
+		KeyUsage:              x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
+		ExtKeyUsage:           []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth},
+		BasicConstraintsValid: true,
+	}
+
+	priv, err := ecdsa.GenerateKey(elliptic.P384(), rand.Reader)
+	if err != nil {
+		fmt.Println(err)
+		os.Exit(1)
+	}
+
+	for {
+		select {
+		case <-stop:
+			return
+		default:
+		}
+
+		derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, priv.Public(), priv)
+		if err != nil {
+			fmt.Println(err)
+			os.Exit(1)
+		}
+
+		id := protocol.NewDeviceID(derBytes)
+		atomic.AddInt64(count, 1)
+
+		if strings.HasPrefix(id.String(), prefix) {
+			select {
+			case found <- result{id, priv, derBytes}:
+			case <-stop:
+			}
+			return
+		}
+	}
+}
+
+func printProgress(prefix string, count *int64) {
+	started := time.Now()
+	wantBits := 5 * len(prefix)
+	if wantBits > 63 {
+		fmt.Printf("Want %d bits for prefix %q, refusing to boil the ocean.\n", wantBits, prefix)
+		os.Exit(1)
+	}
+	expectedIterations := float64(int(1) << uint(wantBits))
+	fmt.Printf("Want %d bits for prefix %q, about %.2g certs to test (statistically speaking)\n", wantBits, prefix, expectedIterations)
+
+	for _ = range time.NewTicker(15 * time.Second).C {
+		tried := atomic.LoadInt64(count)
+		elapsed := time.Since(started)
+		rate := float64(tried) / elapsed.Seconds()
+		expected := timeStr(expectedIterations / rate)
+		fmt.Printf("Trying %.0f certs/s, tested %d so far in %v, expect ~%s total time to complete\n", rate, tried, elapsed/time.Second*time.Second, expected)
+	}
+}
+
+func saveCert(priv interface{}, derBytes []byte) {
+	certOut, err := os.Create("cert.pem")
+	if err != nil {
+		fmt.Println(err)
+		os.Exit(1)
+	}
+	err = pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes})
+	if err != nil {
+		fmt.Println(err)
+		os.Exit(1)
+	}
+	err = certOut.Close()
+	if err != nil {
+		fmt.Println(err)
+		os.Exit(1)
+	}
+
+	keyOut, err := os.OpenFile("key.pem", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
+	if err != nil {
+		fmt.Println(err)
+		os.Exit(1)
+	}
+
+	block, err := pemBlockForKey(priv)
+	if err != nil {
+		fmt.Println(err)
+		os.Exit(1)
+	}
+
+	err = pem.Encode(keyOut, block)
+	if err != nil {
+		fmt.Println(err)
+		os.Exit(1)
+	}
+	err = keyOut.Close()
+	if err != nil {
+		fmt.Println(err)
+		os.Exit(1)
+	}
+}
+
+func pemBlockForKey(priv interface{}) (*pem.Block, error) {
+	switch k := priv.(type) {
+	case *rsa.PrivateKey:
+		return &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(k)}, nil
+	case *ecdsa.PrivateKey:
+		b, err := x509.MarshalECPrivateKey(k)
+		if err != nil {
+			return nil, err
+		}
+		return &pem.Block{Type: "EC PRIVATE KEY", Bytes: b}, nil
+	default:
+		return nil, fmt.Errorf("unknown key type")
+	}
+}
+
+func timeStr(seconds float64) string {
+	if seconds < 60 {
+		return fmt.Sprintf("%.0fs", seconds)
+	}
+	if seconds < 3600 {
+		return fmt.Sprintf("%.0fm", seconds/60)
+	}
+	if seconds < 86400 {
+		return fmt.Sprintf("%.0fh", seconds/3600)
+	}
+	if seconds < 86400*365 {
+		return fmt.Sprintf("%.0f days", seconds/3600)
+	}
+	return fmt.Sprintf("%.0f years", seconds/86400/365)
+}