truly deterministic RSA keys
This commit is contained in:
parent
bc7e9740d8
commit
d80c8226b5
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue