truly deterministic RSA keys

This commit is contained in:
AJ ONeal 2020-08-02 08:16:28 +00:00
parent bc7e9740d8
commit d80c8226b5
3 changed files with 77 additions and 44 deletions

View File

@ -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
} }

View File

@ -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
} }

View File

@ -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
}
} }
} }