477 lines
12 KiB
Go
477 lines
12 KiB
Go
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"))
|
|
}
|
|
}
|