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…
Reference in New Issue