truly deterministic RSA keys
This commit is contained in:
		
							parent
							
								
									bc7e9740d8
								
							
						
					
					
						commit
						d80c8226b5
					
				| @ -9,19 +9,34 @@ import ( | |||||||
| 	"io" | 	"io" | ||||||
| 	"log" | 	"log" | ||||||
| 	"math/rand" | 	"math/rand" | ||||||
|  | 	mathrand "math/rand" | ||||||
| 	"net/http" | 	"net/http" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| // options are the things that we may need to know about a request to fulfill it properly | // options are the things that we may need to know about a request to fulfill it properly | ||||||
| type options struct { | type options struct { | ||||||
| 	KeyType   string    `json:"kty"` | 	KeyType string `json:"kty"` | ||||||
| 	Seed      int64     `json:"-"` | 	Seed    int64  `json:"-"` | ||||||
| 	SeedStr   string    `json:"seed"` | 	SeedStr string `json:"seed"` | ||||||
| 	rndReader io.Reader `json:"-"` | } | ||||||
|  | 
 | ||||||
|  | // this shananigans is only for testing and debug API stuff | ||||||
|  | func (o *options) nextRand() *mathrand.Rand { | ||||||
|  | 	if 0 == o.Seed { | ||||||
|  | 		panic(errors.New("called nextRand impossibly")) | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  | 	return rand.New(rand.NewSource(o.Seed)) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (o *options) nextReader() io.Reader { | ||||||
|  | 	if 0 == o.Seed { | ||||||
|  | 		return RandomReader | ||||||
|  | 	} | ||||||
|  | 	return rand.New(rand.NewSource(o.Seed)) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func getOpts(r *http.Request) (*options, error) { | func getOpts(r *http.Request) (*options, error) { | ||||||
| 	rndReader := RandomReader |  | ||||||
| 	tok := make(map[string]interface{}) | 	tok := make(map[string]interface{}) | ||||||
| 	decoder := json.NewDecoder(r.Body) | 	decoder := json.NewDecoder(r.Body) | ||||||
| 	err := decoder.Decode(&tok) | 	err := decoder.Decode(&tok) | ||||||
| @ -41,27 +56,25 @@ func getOpts(r *http.Request) (*options, error) { | |||||||
| 		seed, _ = binary.ReadVarint(bytes.NewReader(b[0:8])) | 		seed, _ = binary.ReadVarint(bytes.NewReader(b[0:8])) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | 	opts := &options{ | ||||||
|  | 		Seed: seed, | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
| 	var n int | 	var n int | ||||||
| 	if 0 != seed { | 	if 0 != seed { | ||||||
| 		rnd := rand.New(rand.NewSource(seed)) | 		n = opts.nextRand().Intn(2) | ||||||
| 		rndReader = rnd |  | ||||||
| 		n = rnd.Intn(2) |  | ||||||
| 	} else { | 	} else { | ||||||
| 		n = rand.Intn(2) | 		n = rand.Intn(2) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	kty, _ := tok["kty"].(string) | 	opts.KeyType, _ = tok["kty"].(string) | ||||||
| 	if "" == kty { | 	if "" == opts.KeyType { | ||||||
| 		if 0 == n { | 		if 0 == n { | ||||||
| 			kty = "RSA" | 			opts.KeyType = "RSA" | ||||||
| 		} else { | 		} else { | ||||||
| 			kty = "EC" | 			opts.KeyType = "EC" | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	return &options{ | 	return opts, nil | ||||||
| 		KeyType:   kty, |  | ||||||
| 		Seed:      seed, |  | ||||||
| 		rndReader: rndReader, |  | ||||||
| 	}, nil |  | ||||||
| } | } | ||||||
|  | |||||||
| @ -38,7 +38,7 @@ func GeneratePrivateJWK(w http.ResponseWriter, r *http.Request) { | |||||||
| 
 | 
 | ||||||
| // GeneratePrivateDER will create a new private key in a valid DER encoding | // GeneratePrivateDER will create a new private key in a valid DER encoding | ||||||
| func GeneratePrivateDER(w http.ResponseWriter, r *http.Request) { | func GeneratePrivateDER(w http.ResponseWriter, r *http.Request) { | ||||||
| 	log.Printf("%s %s\n", r.Method, r.URL.Path) | 	log.Printf("%s %s", r.Method, r.URL.Path) | ||||||
| 	if "POST" != r.Method { | 	if "POST" != r.Method { | ||||||
| 		http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed) | 		http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed) | ||||||
| 		return | 		return | ||||||
| @ -58,7 +58,7 @@ func GeneratePrivateDER(w http.ResponseWriter, r *http.Request) { | |||||||
| 
 | 
 | ||||||
| // GeneratePrivatePEM will create a new private key in a valid PEM encoding | // GeneratePrivatePEM will create a new private key in a valid PEM encoding | ||||||
| func GeneratePrivatePEM(w http.ResponseWriter, r *http.Request) { | func GeneratePrivatePEM(w http.ResponseWriter, r *http.Request) { | ||||||
| 	log.Printf("%s %s\n", r.Method, r.URL.Path) | 	log.Printf("%s %s", r.Method, r.URL.Path) | ||||||
| 	if "POST" != r.Method { | 	if "POST" != r.Method { | ||||||
| 		http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed) | 		http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed) | ||||||
| 		return | 		return | ||||||
| @ -80,9 +80,24 @@ func genPrivKey(opts *options) keypairs.PrivateKey { | |||||||
| 	var privkey keypairs.PrivateKey | 	var privkey keypairs.PrivateKey | ||||||
| 	if "RSA" == opts.KeyType { | 	if "RSA" == opts.KeyType { | ||||||
| 		keylen := 2048 | 		keylen := 2048 | ||||||
| 		privkey, _ = rsa.GenerateKey(opts.rndReader, keylen) | 		rndReader := opts.nextReader() | ||||||
|  | 		privkey, _ = rsa.GenerateKey(rndReader, keylen) | ||||||
|  | 		if rndReader != RandomReader { | ||||||
|  | 			for i := 0; i < 16; i++ { | ||||||
|  | 				otherkey, _ := rsa.GenerateKey(opts.nextRand(), keylen) | ||||||
|  | 				otherCmp := otherkey.D.Cmp(privkey.(*rsa.PrivateKey).D) | ||||||
|  | 				if 0 != otherCmp { | ||||||
|  | 					// There are two possible keys, choose the lesser D value | ||||||
|  | 					// See https://github.com/square/go-jose/issues/189 | ||||||
|  | 					if otherCmp < 0 { | ||||||
|  | 						privkey = otherkey | ||||||
|  | 					} | ||||||
|  | 					break | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
| 	} else { | 	} else { | ||||||
| 		privkey, _ = ecdsa.GenerateKey(elliptic.P256(), opts.rndReader) | 		privkey, _ = ecdsa.GenerateKey(elliptic.P256(), opts.nextReader()) | ||||||
| 	} | 	} | ||||||
| 	return privkey | 	return privkey | ||||||
| } | } | ||||||
|  | |||||||
| @ -121,7 +121,7 @@ func TestGenWithSeed(t *testing.T) { | |||||||
| 	res, err := client.Do(&http.Request{ | 	res, err := client.Do(&http.Request{ | ||||||
| 		Method: "POST", | 		Method: "POST", | ||||||
| 		URL:    urlstr, | 		URL:    urlstr, | ||||||
| 		Body:   ioutil.NopCloser(bytes.NewReader([]byte(`{"seed":"c"}`))), | 		Body:   ioutil.NopCloser(bytes.NewReader([]byte(`{"seed":"test"}`))), | ||||||
| 	}) | 	}) | ||||||
| 	if nil != err { | 	if nil != err { | ||||||
| 		//t.Fatal(err) | 		//t.Fatal(err) | ||||||
| @ -135,29 +135,34 @@ func TestGenWithSeed(t *testing.T) { | |||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	// Key B | 	// See https://github.com/square/go-jose/issues/189 | ||||||
| 	client = srv.Client() | 	for i := 0; i < 8; i++ { | ||||||
| 	urlstr, _ = url.Parse(srv.URL + "/private.jwk.json") | 		// Key B | ||||||
| 	res, err = client.Do(&http.Request{ | 		client = srv.Client() | ||||||
| 		Method: "POST", | 		urlstr, _ = url.Parse(srv.URL + "/private.jwk.json") | ||||||
| 		URL:    urlstr, | 		res, err = client.Do(&http.Request{ | ||||||
| 		Body:   ioutil.NopCloser(bytes.NewReader([]byte(`{"seed":"c"}`))), | 			Method: "POST", | ||||||
| 	}) | 			URL:    urlstr, | ||||||
| 	if nil != err { | 			Body:   ioutil.NopCloser(bytes.NewReader([]byte(`{"seed":"test"}`))), | ||||||
| 		//t.Fatal(err) | 		}) | ||||||
| 		t.Error(err) | 		if nil != err { | ||||||
| 		return | 			//t.Fatal(err) | ||||||
| 	} | 			t.Error(err) | ||||||
| 	dataB, err := ioutil.ReadAll(res.Body) | 			return | ||||||
| 	if nil != err { | 		} | ||||||
| 		//t.Fatal(err) | 		dataB, err := ioutil.ReadAll(res.Body) | ||||||
| 		t.Error(err) | 		if nil != err { | ||||||
| 		return | 			//t.Fatal(err) | ||||||
| 	} | 			t.Error(err) | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
| 
 | 
 | ||||||
| 	if '{' != dataA[0] || len(dataA) < 100 || string(dataA) != string(dataB) { | 		if '{' != dataA[0] || len(dataA) < 100 || string(dataA) != string(dataB) { | ||||||
| 		t.Error(errors.New("keys with identical seeds should be identical")) | 			log.Println(string(dataA)) | ||||||
| 		return | 			log.Println(string(dataB)) | ||||||
|  | 			t.Error(errors.New("keys with identical seeds should be identical")) | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user