package mockid import ( "bytes" "crypto/ecdsa" "crypto/elliptic" "crypto/rsa" "encoding/json" "errors" "fmt" "io/ioutil" "log" mathrand "math/rand" "net/http" "net/http/httptest" "net/url" "os" "testing" "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" ) var srv *httptest.Server type TestReader struct{} func (TestReader) Read(p []byte) (n int, err error) { return mathrand.Read(p) } var testrnd = TestReader{} func init() { xkeypairs.RandomReader = testrnd rndsrc = testrnd } func TestMain(m *testing.M) { mathrand.Seed(0) // Predictable results os.Setenv("SALT", "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX") jwksPrefix := "public-jwks" err := os.MkdirAll(jwksPrefix, 0755) if nil != err { fmt.Fprintf(os.Stderr, "couldn't write %q: %s", jwksPrefix, err) os.Exit(1) } privkey, _ := ecdsa.GenerateKey(elliptic.P256(), rndsrc) mux := Route(jwksPrefix, privkey) srv = httptest.NewServer(mux) //fs := http.FileServer(http.Dir("public")) //http.Handle("/", fs) os.Exit(m.Run()) } //func TestSelfSignWithoutExp(t *testing.T) //func TestSelfSignWithJTIWithoutExp(t *testing.T) func TestVerifyExpired(t *testing.T) { jwt := "eyJfc2VlZCI6LTEzMDY3NDU1MDQxNDQsImFsZyI6IlJTMjU2IiwiandrIjp7ImUiOiJBUUFCIiwia2lkIjoiSEZ4ZTlGV1dVc2N3bjltaVozSXNJeWMwMjMtbEJ1UmtvOEJpVV9IRG9KOCIsImt0eSI6IlJTQSIsIm4iOiJ2NUZkSTdYaC0wekxWVEVQZl94ekdIUVpDcEZ2MWR2N2h3eHhrVjctYmxpYmt6LXIxUG9lZ3lQYzFXMjZlWFBvd0xQQXQ3a3dHQnVOdjdMVjh5MEtvMkxOZklaXzRILW54SkJPaWIybXlHOVVfQ29WRDBiM3NBWTdmcDd2QlV1bTBXYVM4R3hZOGtYU0ZOS0VTY0NDNVBpSmFyblNISk1PcUdIVm51YmpsSjl5c1NyNmNsaGpxc0R4dU9qOHpxamF2MUFxek1STWVpRl9CREJsOUFoUGNZSHpHN0JtaXB5UEo2XzBwdWNLTi0tUDZDRk92d05SVGx2ek41RmlRM3VHcy1fMHcwQzVMZWJ6N21BNmJNTFdXc0tRRFBvb3cxallCWHJKdVF1WkZoSmxLMmdidm9ZcV85dWhfLUM1Z3pPZnR4UHBCNnhtY3RfelVaeUdwUUxnQlEiLCJ1c2UiOiJzaWcifSwidHlwIjoiSldUIn0.eyJleHAiOjE1OTY2MTQ3NTYsInN1YiI6ImJhbmFuYXMifQ.qHpzlglOfZMzE3CTNAUXld_wC62JTAJuoQfMaNeFa-XPtYB2Maj8_w3YmRZg_q5S6y9ToCmZ8nWd1kuMheA5qBKOUQeQH47Jts5zWLd0UBckIHo5lK4mk0bUWuiNgr7c9DY6k1DIdFaavyWCXbhFwG0X83qlMhQlPh02dDpCuU78Nn2hF3mZETQKpBIVESYtfeU1Xy3OU_am0kwcN2klLcdweOcrLx_ONfcvAGY3KiIdFiz0ViySAsQ39BiSSvoDYqOOOi41Hky67bnyZQOdalQC_95McTeXApzmGXRUE74Gj-S8c9e5it5d4QZLPaQ1JHzUKz1s7TPvThIn58NA-g" client := srv.Client() urlstr, _ := url.Parse(srv.URL + "/debug/verify") req := &http.Request{ Method: "POST", URL: urlstr, Header: http.Header{}, } req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", jwt)) res, err := client.Do(req) if nil != err { t.Error(err) return } data, err := ioutil.ReadAll(res.Body) if nil != err { t.Error(err) return } if 200 == res.StatusCode { log.Printf(string(data)) t.Error(fmt.Errorf("did not expect successful status code: %d", res.StatusCode)) return } } func TestVerifySelfSignedJWT(t *testing.T) { jwt := "eyJfc2VlZCI6LTEzMDY3NDU1MDQxNDQsImFsZyI6IlJTMjU2IiwiandrIjp7ImUiOiJBUUFCIiwia2lkIjoiSEZ4ZTlGV1dVc2N3bjltaVozSXNJeWMwMjMtbEJ1UmtvOEJpVV9IRG9KOCIsImt0eSI6IlJTQSIsIm4iOiJ2NUZkSTdYaC0wekxWVEVQZl94ekdIUVpDcEZ2MWR2N2h3eHhrVjctYmxpYmt6LXIxUG9lZ3lQYzFXMjZlWFBvd0xQQXQ3a3dHQnVOdjdMVjh5MEtvMkxOZklaXzRILW54SkJPaWIybXlHOVVfQ29WRDBiM3NBWTdmcDd2QlV1bTBXYVM4R3hZOGtYU0ZOS0VTY0NDNVBpSmFyblNISk1PcUdIVm51YmpsSjl5c1NyNmNsaGpxc0R4dU9qOHpxamF2MUFxek1STWVpRl9CREJsOUFoUGNZSHpHN0JtaXB5UEo2XzBwdWNLTi0tUDZDRk92d05SVGx2ek41RmlRM3VHcy1fMHcwQzVMZWJ6N21BNmJNTFdXc0tRRFBvb3cxallCWHJKdVF1WkZoSmxLMmdidm9ZcV85dWhfLUM1Z3pPZnR4UHBCNnhtY3RfelVaeUdwUUxnQlEiLCJ1c2UiOiJzaWcifSwidHlwIjoiSldUIn0.eyJleHAiOjE1OTY2MTQ3NTYsInN1YiI6ImJhbmFuYXMifQ.qHpzlglOfZMzE3CTNAUXld_wC62JTAJuoQfMaNeFa-XPtYB2Maj8_w3YmRZg_q5S6y9ToCmZ8nWd1kuMheA5qBKOUQeQH47Jts5zWLd0UBckIHo5lK4mk0bUWuiNgr7c9DY6k1DIdFaavyWCXbhFwG0X83qlMhQlPh02dDpCuU78Nn2hF3mZETQKpBIVESYtfeU1Xy3OU_am0kwcN2klLcdweOcrLx_ONfcvAGY3KiIdFiz0ViySAsQ39BiSSvoDYqOOOi41Hky67bnyZQOdalQC_95McTeXApzmGXRUE74Gj-S8c9e5it5d4QZLPaQ1JHzUKz1s7TPvThIn58NA-g" client := srv.Client() urlstr, _ := url.Parse(srv.URL + "/debug/verify?exp=false") req := &http.Request{ Method: "POST", URL: urlstr, //Body: ioutil.NopCloser(bytes.NewReader(jws)), Header: http.Header{}, } req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", jwt)) res, err := client.Do(req) if nil != err { t.Error(err) return } data, err := ioutil.ReadAll(res.Body) if nil != err { t.Error(err) return } if 200 != res.StatusCode { log.Printf(string(data)) t.Error(fmt.Errorf("bad status code: %d", res.StatusCode)) return } log.Printf("TODO: verify, and verify non-self-signed") log.Printf(string(data)) } func TestSelfSign(t *testing.T) { client := srv.Client() //urlstr, _ := url.Parse(srv.URL + "/debug/jose.jws.json") urlstr, _ := url.Parse(srv.URL + "/debug/jose.jws.jwt") //fmt.Println("URL:", srv.URL, urlstr) tokenRequest := []byte(`{"seed":"test","header":{"_jwk":true},"claims":{"sub":"bananas","exp":"10m"}}`) res, err := client.Do(&http.Request{ Method: "POST", URL: urlstr, Body: ioutil.NopCloser(bytes.NewReader(tokenRequest)), }) if nil != err { t.Error(err) return } if 200 != res.StatusCode { t.Error(fmt.Errorf("bad status code: %d", res.StatusCode)) return } data, err := ioutil.ReadAll(res.Body) if nil != err { t.Error(err) return } log.Printf("TODO: verify, and verify non-self-signed") log.Printf(string(data)) } func TestGenerateJWK(t *testing.T) { client := srv.Client() urlstr, _ := url.Parse(srv.URL + "/debug/private.jwk.json") //fmt.Println("URL:", srv.URL, urlstr) res, err := client.Do(&http.Request{ Method: "POST", URL: urlstr, }) if nil != err { //t.Fatal(err) t.Error(err) return } if 200 != res.StatusCode { t.Error(fmt.Errorf("bad status code: %d", res.StatusCode)) return } data, err := ioutil.ReadAll(res.Body) if nil != err { //t.Fatal(err) t.Error(err) return } jwk := map[string]string{} err = json.Unmarshal(data, &jwk) if nil != err { //t.Fatal(err) t.Error(err) return } if "" == jwk["d"] { t.Fatal("Missing key 'd' from supposed private key") } key, err := keypairs.ParsePrivateKey(data) if nil != err { t.Error(err) return } switch key.(type) { case *rsa.PrivateKey: // no-op //log.Println("is RSA") case *ecdsa.PrivateKey: // no-op //log.Println("is EC") default: t.Fatal(errors.New("impossible key type")) } //fmt.Printf("%#v\n", jwk) } func TestGenWithSeed(t *testing.T) { // Key A client := srv.Client() urlstr, _ := url.Parse(srv.URL + "/debug/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 } if 200 != res.StatusCode { t.Error(fmt.Errorf("bad status code: %d", res.StatusCode)) return } dataA, 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 + "/debug/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 } if 200 != res.StatusCode { t.Error(fmt.Errorf("bad status code: %d", res.StatusCode)) 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) { log.Println(string(dataA)) log.Println(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 + "/debug/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 } if 200 != res.StatusCode { t.Error(fmt.Errorf("bad status code: %d", res.StatusCode)) 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 + "/debug/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 } if 200 != res.StatusCode { t.Error(fmt.Errorf("bad status code: %d", res.StatusCode)) 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 + "/debug/priv.pem") //fmt.Println("URL:", srv.URL, urlstr) res, err := client.Do(&http.Request{ Method: "POST", URL: urlstr, }) if nil != err { //t.Fatal(err) t.Error(err) return } if 200 != res.StatusCode { t.Error(fmt.Errorf("bad status code: %d", res.StatusCode)) return } data, err := ioutil.ReadAll(res.Body) if nil != err { //t.Fatal(err) t.Error(err) return } key, err := xkeypairs.ParsePEMPrivateKey(data) if nil != err { t.Error(err) return } switch key.(type) { case *rsa.PrivateKey: // no-op //log.Println("is RSA") case *ecdsa.PrivateKey: // no-op //log.Println("is EC") default: t.Fatal(errors.New("impossible key type")) } } func TestPublicJWKWithKey(t *testing.T) { client := srv.Client() urlstr, _ := url.Parse(srv.URL + "/debug/public.jwk.json") //fmt.Println("URL:", srv.URL, urlstr) res, err := client.Do(&http.Request{ Method: "POST", URL: urlstr, Body: ioutil.NopCloser(bytes.NewReader([]byte(`{"key":"{\"crv\":\"P-256\",\"d\":\"s0YhjGUJpp6OvyuNS_4igrc7ddDZy5N2ANxoQm7E5sc\",\"kty\":\"EC\",\"x\":\"hPsE4OMhpd2TvrhjDgr1BhF-L1n4O-gPm1flwTh5kzo\",\"y\":\"BWZ1naEJuNOdnQ4HmbHavqdLKxoj77Fu8mkJPjSuh54\"}"}`))), }) if nil != err { //t.Fatal(err) t.Error(err) return } if 200 != res.StatusCode { t.Error(fmt.Errorf("bad status code: %d", res.StatusCode)) return } data, err := ioutil.ReadAll(res.Body) if nil != err { //t.Fatal(err) t.Error(err) return } jwk := map[string]string{} err = json.Unmarshal(data, &jwk) if nil != err { //t.Fatal(err) t.Error(err) return } if "" != jwk["d"] { t.Fatal("Has private key 'd' from supposed public key") } if "hPsE4OMhpd2TvrhjDgr1BhF-L1n4O-gPm1flwTh5kzo" != jwk["x"] { t.Fatal("Missing public key 'x' or 'e' from supposed public key") } key, err := keypairs.ParsePublicKey(data) if nil != err { t.Error(err) return } switch key.Key().(type) { case *ecdsa.PublicKey: // no-op //log.Println("is EC") default: t.Fatal(errors.New("impossible key type")) } } func TestPublicPEMWithSeed(t *testing.T) { client := srv.Client() urlstr, _ := url.Parse(srv.URL + "/debug/pub.pem") //fmt.Println("URL:", srv.URL, urlstr) 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 } if 200 != res.StatusCode { t.Error(fmt.Errorf("bad status code: %d", res.StatusCode)) return } data, err := ioutil.ReadAll(res.Body) if nil != err { //t.Fatal(err) t.Error(err) return } key, err := keypairs.ParsePublicKey(data) if nil != err { t.Error(err) return } switch key.Key().(type) { case *rsa.PublicKey: // no-op //log.Println("is RSA") case *ecdsa.PublicKey: // no-op //log.Println("is EC") default: t.Fatal(errors.New("impossible key type")) } }