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"
"log"
"math/rand"
mathrand "math/rand"
"net/http"
)
// options are the things that we may need to know about a request to fulfill it properly
type options struct {
KeyType string `json:"kty"`
Seed int64 `json:"-"`
SeedStr string `json:"seed"`
rndReader io.Reader `json:"-"`
KeyType string `json:"kty"`
Seed int64 `json:"-"`
SeedStr string `json:"seed"`
}
// 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) {
rndReader := RandomReader
tok := make(map[string]interface{})
decoder := json.NewDecoder(r.Body)
err := decoder.Decode(&tok)
@ -41,27 +56,25 @@ func getOpts(r *http.Request) (*options, error) {
seed, _ = binary.ReadVarint(bytes.NewReader(b[0:8]))
}
opts := &options{
Seed: seed,
}
var n int
if 0 != seed {
rnd := rand.New(rand.NewSource(seed))
rndReader = rnd
n = rnd.Intn(2)
n = opts.nextRand().Intn(2)
} else {
n = rand.Intn(2)
}
kty, _ := tok["kty"].(string)
if "" == kty {
opts.KeyType, _ = tok["kty"].(string)
if "" == opts.KeyType {
if 0 == n {
kty = "RSA"
opts.KeyType = "RSA"
} else {
kty = "EC"
opts.KeyType = "EC"
}
}
return &options{
KeyType: kty,
Seed: seed,
rndReader: rndReader,
}, nil
return opts, 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
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 {
http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed)
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
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 {
http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed)
return
@ -80,9 +80,24 @@ func genPrivKey(opts *options) keypairs.PrivateKey {
var privkey keypairs.PrivateKey
if "RSA" == opts.KeyType {
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 {
privkey, _ = ecdsa.GenerateKey(elliptic.P256(), opts.rndReader)
privkey, _ = ecdsa.GenerateKey(elliptic.P256(), opts.nextReader())
}
return privkey
}

View File

@ -121,7 +121,7 @@ func TestGenWithSeed(t *testing.T) {
res, err := client.Do(&http.Request{
Method: "POST",
URL: urlstr,
Body: ioutil.NopCloser(bytes.NewReader([]byte(`{"seed":"c"}`))),
Body: ioutil.NopCloser(bytes.NewReader([]byte(`{"seed":"test"}`))),
})
if nil != err {
//t.Fatal(err)
@ -135,29 +135,34 @@ func TestGenWithSeed(t *testing.T) {
return
}
// Key B
client = srv.Client()
urlstr, _ = url.Parse(srv.URL + "/private.jwk.json")
res, err = client.Do(&http.Request{
Method: "POST",
URL: urlstr,
Body: ioutil.NopCloser(bytes.NewReader([]byte(`{"seed":"c"}`))),
})
if nil != err {
//t.Fatal(err)
t.Error(err)
return
}
dataB, err := ioutil.ReadAll(res.Body)
if nil != err {
//t.Fatal(err)
t.Error(err)
return
}
// See https://github.com/square/go-jose/issues/189
for i := 0; i < 8; i++ {
// Key B
client = srv.Client()
urlstr, _ = url.Parse(srv.URL + "/private.jwk.json")
res, err = client.Do(&http.Request{
Method: "POST",
URL: urlstr,
Body: ioutil.NopCloser(bytes.NewReader([]byte(`{"seed":"test"}`))),
})
if nil != err {
//t.Fatal(err)
t.Error(err)
return
}
dataB, err := ioutil.ReadAll(res.Body)
if nil != err {
//t.Fatal(err)
t.Error(err)
return
}
if '{' != dataA[0] || len(dataA) < 100 || string(dataA) != string(dataB) {
t.Error(errors.New("keys with identical seeds should be identical"))
return
if '{' != dataA[0] || len(dataA) < 100 || string(dataA) != string(dataB) {
log.Println(string(dataA))
log.Println(string(dataB))
t.Error(errors.New("keys with identical seeds should be identical"))
return
}
}
}