add seed for random key generator (tested)
This commit is contained in:
parent
42f1089e6c
commit
da712abbb2
|
@ -13,6 +13,7 @@ import (
|
|||
"time"
|
||||
|
||||
"git.coolaj86.com/coolaj86/go-mockid/mockid"
|
||||
"git.coolaj86.com/coolaj86/go-mockid/xkeypairs"
|
||||
"git.rootprojects.org/root/keypairs"
|
||||
|
||||
_ "github.com/joho/godotenv/autoload"
|
||||
|
@ -91,7 +92,7 @@ func main() {
|
|||
}()
|
||||
|
||||
// TODO privB := keypairs.MarshalJWKPrivateKey(privkey)
|
||||
privB := mockid.MarshalJWKPrivateKey(privkey)
|
||||
privB := xkeypairs.MarshalJWKPrivateKey(privkey)
|
||||
fmt.Printf("Private Key:\n\t%s\n", string(privB))
|
||||
pubB := keypairs.MarshalJWKPublicKey(keypairs.NewPublicKey(privkey.Public()))
|
||||
fmt.Printf("Public Key:\n\t%s\n", string(pubB))
|
||||
|
|
|
@ -0,0 +1,62 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/sha256"
|
||||
"encoding/binary"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io"
|
||||
"log"
|
||||
"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:"-"`
|
||||
}
|
||||
|
||||
func getOpts(r *http.Request) (*options, error) {
|
||||
rndReader := RandomReader
|
||||
tok := make(map[string]interface{})
|
||||
decoder := json.NewDecoder(r.Body)
|
||||
err := decoder.Decode(&tok)
|
||||
if nil != err && io.EOF != err {
|
||||
log.Printf("json decode error: %s", err)
|
||||
return nil, errors.New("Bad Request: invalid json body")
|
||||
}
|
||||
defer r.Body.Close()
|
||||
|
||||
var seed int64
|
||||
seedStr, _ := tok["seed"].(string)
|
||||
if "" != seedStr {
|
||||
if len(seedStr) > 256 {
|
||||
return nil, errors.New("Bad Request: base64 seed should be <256 characters (and is truncated to 64-bits anyway)")
|
||||
}
|
||||
b := sha256.Sum256([]byte(seedStr))
|
||||
seed, _ = binary.ReadVarint(bytes.NewReader(b[0:8]))
|
||||
}
|
||||
|
||||
if 0 != seed {
|
||||
rndReader = rand.New(rand.NewSource(seed))
|
||||
}
|
||||
|
||||
kty, _ := tok["kty"].(string)
|
||||
if "" == kty {
|
||||
if 0 == rand.Intn(2) {
|
||||
kty = "RSA"
|
||||
} else {
|
||||
kty = "EC"
|
||||
}
|
||||
}
|
||||
|
||||
return &options{
|
||||
KeyType: kty,
|
||||
Seed: seed,
|
||||
rndReader: rndReader,
|
||||
}, nil
|
||||
}
|
|
@ -0,0 +1,88 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
|
||||
"git.coolaj86.com/coolaj86/go-mockid/xkeypairs"
|
||||
"git.rootprojects.org/root/keypairs"
|
||||
)
|
||||
|
||||
// RandomReader may be overwritten for testing
|
||||
var RandomReader io.Reader = rand.Reader
|
||||
|
||||
// GeneratePrivateJWK will create a new private key in JWK format
|
||||
func GeneratePrivateJWK(w http.ResponseWriter, r *http.Request) {
|
||||
log.Printf("%s %s", r.Method, r.URL.Path)
|
||||
if "POST" != r.Method {
|
||||
http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
|
||||
opts, err := getOpts(r)
|
||||
if nil != err {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
privkey := genPrivKey(opts)
|
||||
|
||||
jwk := xkeypairs.MarshalJWKPrivateKey(privkey)
|
||||
w.Write(append(jwk, '\n'))
|
||||
}
|
||||
|
||||
// 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)
|
||||
if "POST" != r.Method {
|
||||
http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
|
||||
opts, err := getOpts(r)
|
||||
if nil != err {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
privkey := genPrivKey(opts)
|
||||
|
||||
der, _ := xkeypairs.MarshalDERPrivateKey(privkey)
|
||||
w.Write(der)
|
||||
}
|
||||
|
||||
// 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)
|
||||
if "POST" != r.Method {
|
||||
http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
|
||||
opts, err := getOpts(r)
|
||||
if nil != err {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
privkey := genPrivKey(opts)
|
||||
|
||||
privpem, _ := xkeypairs.MarshalPEMPrivateKey(privkey)
|
||||
w.Write(privpem)
|
||||
}
|
||||
|
||||
func genPrivKey(opts *options) keypairs.PrivateKey {
|
||||
var privkey keypairs.PrivateKey
|
||||
if "RSA" == opts.KeyType {
|
||||
keylen := 2048
|
||||
privkey, _ = rsa.GenerateKey(opts.rndReader, keylen)
|
||||
} else {
|
||||
privkey, _ = ecdsa.GenerateKey(elliptic.P256(), opts.rndReader)
|
||||
}
|
||||
return privkey
|
||||
}
|
|
@ -18,6 +18,7 @@ import (
|
|||
"sync"
|
||||
"time"
|
||||
|
||||
"git.coolaj86.com/coolaj86/go-mockid/xkeypairs"
|
||||
"git.rootprojects.org/root/keypairs"
|
||||
//jwt "github.com/dgrijalva/jwt-go"
|
||||
)
|
||||
|
@ -94,7 +95,7 @@ func GenToken(host string, privkey keypairs.PrivateKey, query url.Values) (strin
|
|||
protected := fmt.Sprintf(`{"typ":"JWT","alg":%q,"kid":"%s"}`, alg, thumbprint)
|
||||
protected64 := base64.RawURLEncoding.EncodeToString([]byte(protected))
|
||||
|
||||
exp, err := parseExp(query.Get("exp"))
|
||||
exp, err := xkeypairs.ParseDuration(query.Get("exp"))
|
||||
if nil != err {
|
||||
// cryptic error code
|
||||
// TODO propagate error
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package mockid
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/rsa"
|
||||
|
@ -16,6 +17,8 @@ import (
|
|||
"os"
|
||||
"testing"
|
||||
|
||||
"git.coolaj86.com/coolaj86/go-mockid/mockid/api"
|
||||
"git.coolaj86.com/coolaj86/go-mockid/xkeypairs"
|
||||
"git.rootprojects.org/root/keypairs"
|
||||
//keypairs "github.com/big-squid/go-keypairs"
|
||||
//"github.com/big-squid/go-keypairs/keyfetch/uncached"
|
||||
|
@ -32,6 +35,7 @@ func (TestReader) Read(p []byte) (n int, err error) {
|
|||
var testrnd = TestReader{}
|
||||
|
||||
func init() {
|
||||
api.RandomReader = testrnd
|
||||
rndsrc = testrnd
|
||||
}
|
||||
|
||||
|
@ -110,6 +114,100 @@ func TestGenerateJWK(t *testing.T) {
|
|||
//fmt.Printf("%#v\n", jwk)
|
||||
}
|
||||
|
||||
func TestGenWithSeed(t *testing.T) {
|
||||
// Key A
|
||||
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
|
||||
}
|
||||
dataA, err := ioutil.ReadAll(res.Body)
|
||||
if nil != err {
|
||||
//t.Fatal(err)
|
||||
t.Error(err)
|
||||
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":"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
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenWithRand(t *testing.T) {
|
||||
// Key A
|
||||
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":""}`))),
|
||||
})
|
||||
if nil != err {
|
||||
//t.Fatal(err)
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
dataA, err := ioutil.ReadAll(res.Body)
|
||||
if nil != err {
|
||||
//t.Fatal(err)
|
||||
t.Error(err)
|
||||
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":""}`))),
|
||||
})
|
||||
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 string(dataA) == string(dataB) {
|
||||
t.Error(errors.New("keys with identical seeds should yield identical keys"))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func TestGeneratePEM(t *testing.T) {
|
||||
client := srv.Client()
|
||||
urlstr, _ := url.Parse(srv.URL + "/priv.pem")
|
||||
|
@ -131,7 +229,7 @@ func TestGeneratePEM(t *testing.T) {
|
|||
return
|
||||
}
|
||||
|
||||
key, err := ParsePEMPrivateKey(data)
|
||||
key, err := xkeypairs.ParsePEMPrivateKey(data)
|
||||
if nil != err {
|
||||
t.Error(err)
|
||||
return
|
||||
|
|
116
mockid/route.go
116
mockid/route.go
|
@ -1,20 +1,14 @@
|
|||
package mockid
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/rsa"
|
||||
"crypto/sha1"
|
||||
"crypto/sha256"
|
||||
"crypto/sha512"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
mathrand "math/rand"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
@ -22,11 +16,14 @@ import (
|
|||
"strings"
|
||||
"time"
|
||||
|
||||
"git.coolaj86.com/coolaj86/go-mockid/mockid/api"
|
||||
"git.coolaj86.com/coolaj86/go-mockid/xkeypairs"
|
||||
"git.rootprojects.org/root/keypairs"
|
||||
"git.rootprojects.org/root/keypairs/keyfetch"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
// Route returns an HTTP Mux containing the full API
|
||||
func Route(jwksPrefix string, privkey keypairs.PrivateKey) http.Handler {
|
||||
Init()
|
||||
|
||||
|
@ -44,12 +41,12 @@ func Route(jwksPrefix string, privkey keypairs.PrivateKey) http.Handler {
|
|||
// is this the expiration of the nonce itself? methinks maybe so
|
||||
//res.setHeader("Expires", "Sun, 10 Mar 2019 08:04:45 GMT");
|
||||
// TODO use one of the registered domains
|
||||
//var indexUrl = "https://acme-staging-v02.api.letsencrypt.org/index"
|
||||
//var indexURL = "https://acme-staging-v02.api.letsencrypt.org/index"
|
||||
*/
|
||||
//var port = (state.config.ipc && state.config.ipc.port || state._ipc.port || undefined);
|
||||
//var indexUrl = "http://localhost:" + port + "/index";
|
||||
indexUrl := baseURL + "/index"
|
||||
w.Header().Set("Link", "<"+indexUrl+">;rel=\"index\"")
|
||||
//var indexURL = "http://localhost:" + port + "/index";
|
||||
indexURL := baseURL + "/index"
|
||||
w.Header().Set("Link", "<"+indexURL+">;rel=\"index\"")
|
||||
w.Header().Set("Cache-Control", "max-age=0, no-cache, no-store")
|
||||
w.Header().Set("Pragma", "no-cache")
|
||||
//res.setHeader("Strict-Transport-Security", "max-age=604800");
|
||||
|
@ -184,101 +181,11 @@ func Route(jwksPrefix string, privkey keypairs.PrivateKey) http.Handler {
|
|||
fmt.Fprintf(w, token)
|
||||
})
|
||||
|
||||
getKty := func(r *http.Request) (string, error) {
|
||||
tok := make(map[string]interface{})
|
||||
decoder := json.NewDecoder(r.Body)
|
||||
err := decoder.Decode(&tok)
|
||||
if nil != err && io.EOF != err {
|
||||
log.Printf("json decode error: %s", err)
|
||||
return "", errors.New("Bad Request: invalid json body")
|
||||
}
|
||||
defer r.Body.Close()
|
||||
http.HandleFunc("/private.jwk.json", api.GeneratePrivateJWK)
|
||||
|
||||
kty, _ := tok["kty"].(string)
|
||||
if "" == kty {
|
||||
if 0 == mathrand.Intn(2) {
|
||||
kty = "RSA"
|
||||
} else {
|
||||
kty = "EC"
|
||||
}
|
||||
}
|
||||
return kty, nil
|
||||
}
|
||||
http.HandleFunc("/priv.der", api.GeneratePrivateDER)
|
||||
|
||||
http.HandleFunc("/private.jwk.json", func(w http.ResponseWriter, r *http.Request) {
|
||||
log.Printf("%s %s", r.Method, r.URL.Path)
|
||||
if "POST" != r.Method {
|
||||
http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
|
||||
kty, err := getKty(r)
|
||||
if nil != err {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
var privkey keypairs.PrivateKey
|
||||
if "RSA" == kty {
|
||||
keylen := 2048
|
||||
privkey, _ = rsa.GenerateKey(rndsrc, keylen)
|
||||
} else {
|
||||
privkey, _ = ecdsa.GenerateKey(elliptic.P256(), rndsrc)
|
||||
}
|
||||
|
||||
jwk := MarshalJWKPrivateKey(privkey)
|
||||
w.Write(append(jwk, '\n'))
|
||||
})
|
||||
|
||||
http.HandleFunc("/priv.der", func(w http.ResponseWriter, r *http.Request) {
|
||||
log.Printf("%s %s\n", r.Method, r.URL.Path)
|
||||
if "POST" != r.Method {
|
||||
http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
|
||||
kty, err := getKty(r)
|
||||
if nil != err {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
var privkey keypairs.PrivateKey
|
||||
if "RSA" == kty {
|
||||
keylen := 2048
|
||||
privkey, _ = rsa.GenerateKey(rndsrc, keylen)
|
||||
} else {
|
||||
privkey, _ = ecdsa.GenerateKey(elliptic.P256(), rndsrc)
|
||||
}
|
||||
|
||||
der, _ := MarshalDERPrivateKey(privkey)
|
||||
w.Write(der)
|
||||
})
|
||||
|
||||
http.HandleFunc("/priv.pem", func(w http.ResponseWriter, r *http.Request) {
|
||||
log.Printf("%s %s\n", r.Method, r.URL.Path)
|
||||
if "POST" != r.Method {
|
||||
http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
|
||||
kty, err := getKty(r)
|
||||
if nil != err {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
var privkey keypairs.PrivateKey
|
||||
if "RSA" == kty {
|
||||
keylen := 2048
|
||||
privkey, _ = rsa.GenerateKey(rndsrc, keylen)
|
||||
} else {
|
||||
privkey, _ = ecdsa.GenerateKey(elliptic.P256(), rndsrc)
|
||||
}
|
||||
|
||||
privpem, _ := MarshalPEMPrivateKey(privkey)
|
||||
w.Write(privpem)
|
||||
})
|
||||
http.HandleFunc("/priv.pem", api.GeneratePrivatePEM)
|
||||
|
||||
http.HandleFunc("/inspect_token", func(w http.ResponseWriter, r *http.Request) {
|
||||
token := r.Header.Get("Authorization")
|
||||
|
@ -404,7 +311,7 @@ func Route(jwksPrefix string, privkey keypairs.PrivateKey) http.Handler {
|
|||
|
||||
http.HandleFunc("/key.jwk.json", func(w http.ResponseWriter, r *http.Request) {
|
||||
log.Printf("%s %s", r.Method, r.URL.Path)
|
||||
jwk := string(MarshalJWKPrivateKey(privkey))
|
||||
jwk := string(xkeypairs.MarshalJWKPrivateKey(privkey))
|
||||
jwk = strings.Replace(jwk, `{"`, `{ "`, 1)
|
||||
jwk = strings.Replace(jwk, `",`, `", `, -1)
|
||||
jwk = jwk[0 : len(jwk)-1]
|
||||
|
@ -474,6 +381,7 @@ func getBaseURL(r *http.Request) string {
|
|||
)
|
||||
}
|
||||
|
||||
// HTTPError describes an error that should be propagated to the HTTP client
|
||||
type HTTPError struct {
|
||||
message string
|
||||
code int
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
package mockid
|
||||
package xkeypairs
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
|
@ -1,4 +1,4 @@
|
|||
package mockid
|
||||
package xkeypairs
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
@ -6,12 +6,13 @@ import (
|
|||
"git.rootprojects.org/root/keypairs"
|
||||
)
|
||||
|
||||
// ParsePEMPrivateKey will parse a PEM Private Key (or JWK or DER) but in future versions will fail to parse other key input types
|
||||
func ParsePEMPrivateKey(block []byte) (keypairs.PrivateKey, error) {
|
||||
// TODO do not parse DER or JWK
|
||||
return keypairs.ParsePrivateKey(block)
|
||||
}
|
||||
|
||||
func parseExp(exp string) (int, error) {
|
||||
func ParseDuration(exp string) (int, error) {
|
||||
if "" == exp {
|
||||
exp = "15m"
|
||||
}
|
Loading…
Reference in New Issue