diff --git a/cmd/policy/main.go b/cmd/policy/main.go index 982e3625abf22dc5ffb06c6c0f05bd6c1a69193b..3fd6a720ff8ea2691b8d01cc3aaa04e7bb18e77e 100644 --- a/cmd/policy/main.go +++ b/cmd/policy/main.go @@ -84,16 +84,17 @@ func main() { cacheFuncs := regofunc.NewCacheFuncs(cfg.Cache.Addr, httpClient) didResolverFuncs := regofunc.NewDIDResolverFuncs(cfg.DIDResolver.Addr, httpClient) taskFuncs := regofunc.NewTaskFuncs(cfg.Task.Addr, httpClient) - keysFuncs := regofunc.NewPubkeyFuncs(cfg.Signer.Addr, httpClient) + signerFuncs := regofunc.NewSignerFuncs(cfg.Signer.Addr, httpClient) regofunc.Register("cacheGet", rego.Function3(cacheFuncs.CacheGetFunc())) regofunc.Register("cacheSet", rego.Function4(cacheFuncs.CacheSetFunc())) regofunc.Register("didResolve", rego.Function1(didResolverFuncs.ResolveFunc())) regofunc.Register("taskCreate", rego.Function2(taskFuncs.CreateTaskFunc())) regofunc.Register("taskListCreate", rego.Function2(taskFuncs.CreateTaskListFunc())) - regofunc.Register("getKey", rego.Function1(keysFuncs.GetKeyFunc())) - regofunc.Register("getAllKeys", rego.FunctionDyn(keysFuncs.GetAllKeysFunc())) - regofunc.Register("getAllKeys", rego.FunctionDyn(keysFuncs.GetAllKeysFunc())) - regofunc.Register("issuer", rego.FunctionDyn(keysFuncs.IssuerDID())) + regofunc.Register("getKey", rego.Function1(signerFuncs.GetKeyFunc())) + regofunc.Register("getAllKeys", rego.FunctionDyn(signerFuncs.GetAllKeysFunc())) + regofunc.Register("issuer", rego.FunctionDyn(signerFuncs.IssuerDID())) + regofunc.Register("createProof", rego.Function1(signerFuncs.CreateProof())) + regofunc.Register("verifyProof", rego.Function1(signerFuncs.VerifyProof())) } // subscribe the cache for policy data changes diff --git a/internal/regofunc/pubkeys.go b/internal/regofunc/pubkeys.go deleted file mode 100644 index c9873c6b9c246df6205b2d0d781a60a8de5e1617..0000000000000000000000000000000000000000 --- a/internal/regofunc/pubkeys.go +++ /dev/null @@ -1,141 +0,0 @@ -package regofunc - -import ( - "fmt" - "net/http" - "net/url" - "strings" - - "github.com/open-policy-agent/opa/ast" - "github.com/open-policy-agent/opa/rego" - "github.com/open-policy-agent/opa/types" -) - -type PubkeyFuncs struct { - signerAddr string - httpClient *http.Client -} - -func NewPubkeyFuncs(signerAddr string, httpClient *http.Client) *PubkeyFuncs { - return &PubkeyFuncs{ - signerAddr: signerAddr, - httpClient: httpClient, - } -} - -func (pf *PubkeyFuncs) GetKeyFunc() (*rego.Function, rego.Builtin1) { - return ®o.Function{ - Name: "keys.get", - Decl: types.NewFunction(types.Args(types.S), types.A), - Memoize: true, - }, - func(bctx rego.BuiltinContext, keyname *ast.Term) (*ast.Term, error) { - var key string - if err := ast.As(keyname.Value, &key); err != nil { - return nil, fmt.Errorf("invalid keyname: %s", err) - } - - if strings.TrimSpace(key) == "" { - return nil, fmt.Errorf("empty keyname") - } - - uri, err := url.ParseRequestURI(pf.signerAddr + "/v1/keys/" + key) - if err != nil { - return nil, err - } - - req, err := http.NewRequest("GET", uri.String(), nil) - if err != nil { - return nil, err - } - - resp, err := pf.httpClient.Do(req.WithContext(bctx.Context)) - if err != nil { - return nil, err - } - defer resp.Body.Close() // nolint:errcheck - - if resp.StatusCode != http.StatusOK { - return nil, fmt.Errorf("unexpected response from signer: %s", resp.Status) - } - - v, err := ast.ValueFromReader(resp.Body) - if err != nil { - return nil, err - } - - return ast.NewTerm(v), nil - } -} - -func (pf *PubkeyFuncs) GetAllKeysFunc() (*rego.Function, rego.BuiltinDyn) { - return ®o.Function{ - Name: "keys.getAll", - Decl: types.NewFunction(nil, types.A), - Memoize: true, - }, - func(bctx rego.BuiltinContext, terms []*ast.Term) (*ast.Term, error) { - uri, err := url.ParseRequestURI(pf.signerAddr + "/v1/keys") - if err != nil { - return nil, err - } - - req, err := http.NewRequest("GET", uri.String(), nil) - if err != nil { - return nil, err - } - - resp, err := pf.httpClient.Do(req.WithContext(bctx.Context)) - if err != nil { - return nil, err - } - defer resp.Body.Close() // nolint:errcheck - - if resp.StatusCode != http.StatusOK { - return nil, fmt.Errorf("unexpected response from signer: %s", resp.Status) - } - - v, err := ast.ValueFromReader(resp.Body) - if err != nil { - return nil, err - } - - return ast.NewTerm(v), nil - } -} - -func (pf *PubkeyFuncs) IssuerDID() (*rego.Function, rego.BuiltinDyn) { - return ®o.Function{ - Name: "issuer", - Decl: types.NewFunction(nil, types.A), - Memoize: true, - }, - func(bctx rego.BuiltinContext, terms []*ast.Term) (*ast.Term, error) { - uri, err := url.ParseRequestURI(pf.signerAddr + "/v1/issuerDID") - if err != nil { - return nil, err - } - - req, err := http.NewRequest("GET", uri.String(), nil) - if err != nil { - return nil, err - } - - resp, err := pf.httpClient.Do(req.WithContext(bctx.Context)) - if err != nil { - return nil, err - } - defer resp.Body.Close() // nolint:errcheck - - if resp.StatusCode != http.StatusOK { - return nil, fmt.Errorf("unexpected response from signer: %s", resp.Status) - } - - v, err := ast.ValueFromReader(resp.Body) - if err != nil { - return nil, err - } - - return ast.NewTerm(v), nil - } -} diff --git a/internal/regofunc/pubkeys_test.go b/internal/regofunc/pubkeys_test.go deleted file mode 100644 index 11a7a7c5c084c99522ad4bb4febd865f980e0c49..0000000000000000000000000000000000000000 --- a/internal/regofunc/pubkeys_test.go +++ /dev/null @@ -1,98 +0,0 @@ -package regofunc_test - -import ( - "context" - "encoding/json" - "fmt" - "net/http" - "net/http/httptest" - "testing" - - "github.com/open-policy-agent/opa/rego" - "github.com/stretchr/testify/assert" - - "code.vereign.com/gaiax/tsa/policy/internal/regofunc" -) - -func TestGetKeyFunc(t *testing.T) { - expected := `{"key1":"key1 data"}` - signerSrv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - _, _ = fmt.Fprint(w, expected) - })) - defer signerSrv.Close() - - keysFuncs := regofunc.NewPubkeyFuncs(signerSrv.URL, http.DefaultClient) - r := rego.New( - rego.Query(`keys.get("key1")`), - rego.Function1(keysFuncs.GetKeyFunc()), - rego.StrictBuiltinErrors(true), - ) - resultSet, err := r.Eval(context.Background()) - assert.NoError(t, err) - - resultBytes, err := json.Marshal(resultSet[0].Expressions[0].Value) - assert.NoError(t, err) - assert.Equal(t, expected, string(resultBytes)) -} - -func TestGetKeyFuncError(t *testing.T) { - signerSrv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusNotFound) - })) - defer signerSrv.Close() - - keysFuncs := regofunc.NewPubkeyFuncs(signerSrv.URL, http.DefaultClient) - r := rego.New( - rego.Query(`keys.get("key1")`), - rego.Function1(keysFuncs.GetKeyFunc()), - rego.StrictBuiltinErrors(true), - ) - resultSet, err := r.Eval(context.Background()) - assert.Nil(t, resultSet) - assert.Error(t, err) - - expectedError := `keys.get("key1"): eval_builtin_error: keys.get: unexpected response from signer: 404 Not Found` - assert.Equal(t, expectedError, err.Error()) -} - -func TestGetAllKeysFunc(t *testing.T) { - expected := `[{"key1":"key1 data"},{"key2":"key2 data"}]` - signerSrv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - _, _ = fmt.Fprint(w, expected) - })) - defer signerSrv.Close() - - keysFuncs := regofunc.NewPubkeyFuncs(signerSrv.URL, http.DefaultClient) - r := rego.New( - rego.Query(`keys.getAll()`), - rego.FunctionDyn(keysFuncs.GetAllKeysFunc()), - rego.StrictBuiltinErrors(true), - ) - resultSet, err := r.Eval(context.Background()) - assert.NoError(t, err) - - resultBytes, err := json.Marshal(resultSet[0].Expressions[0].Value) - assert.NoError(t, err) - assert.Equal(t, expected, string(resultBytes)) -} - -func TestIssuerDID(t *testing.T) { - expected := `{"did":"did:web:123"}` - signerSrv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - _, _ = fmt.Fprint(w, expected) - })) - defer signerSrv.Close() - - keysFuncs := regofunc.NewPubkeyFuncs(signerSrv.URL, http.DefaultClient) - r := rego.New( - rego.Query(`issuer()`), - rego.FunctionDyn(keysFuncs.IssuerDID()), - rego.StrictBuiltinErrors(true), - ) - resultSet, err := r.Eval(context.Background()) - assert.NoError(t, err) - - resultBytes, err := json.Marshal(resultSet[0].Expressions[0].Value) - assert.NoError(t, err) - assert.Equal(t, expected, string(resultBytes)) -} diff --git a/internal/regofunc/signer.go b/internal/regofunc/signer.go new file mode 100644 index 0000000000000000000000000000000000000000..734585723aeb17ffaf8015ccef2a2b342fa87ad0 --- /dev/null +++ b/internal/regofunc/signer.go @@ -0,0 +1,275 @@ +package regofunc + +import ( + "bytes" + "encoding/json" + "fmt" + "net/http" + "net/url" + "strings" + + "github.com/open-policy-agent/opa/ast" + "github.com/open-policy-agent/opa/rego" + "github.com/open-policy-agent/opa/types" +) + +type SignerFuncs struct { + signerAddr string + httpClient *http.Client +} + +func NewSignerFuncs(signerAddr string, httpClient *http.Client) *SignerFuncs { + return &SignerFuncs{ + signerAddr: signerAddr, + httpClient: httpClient, + } +} + +func (sf *SignerFuncs) GetKeyFunc() (*rego.Function, rego.Builtin1) { + return ®o.Function{ + Name: "keys.get", + Decl: types.NewFunction(types.Args(types.S), types.A), + Memoize: true, + }, + func(bctx rego.BuiltinContext, keyname *ast.Term) (*ast.Term, error) { + var key string + if err := ast.As(keyname.Value, &key); err != nil { + return nil, fmt.Errorf("invalid keyname: %s", err) + } + + if strings.TrimSpace(key) == "" { + return nil, fmt.Errorf("empty keyname") + } + + uri, err := url.ParseRequestURI(sf.signerAddr + "/v1/keys/" + key) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("GET", uri.String(), nil) + if err != nil { + return nil, err + } + + resp, err := sf.httpClient.Do(req.WithContext(bctx.Context)) + if err != nil { + return nil, err + } + defer resp.Body.Close() // nolint:errcheck + + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("unexpected response from signer: %s", resp.Status) + } + + v, err := ast.ValueFromReader(resp.Body) + if err != nil { + return nil, err + } + + return ast.NewTerm(v), nil + } +} + +func (sf *SignerFuncs) GetAllKeysFunc() (*rego.Function, rego.BuiltinDyn) { + return ®o.Function{ + Name: "keys.getAll", + Decl: types.NewFunction(nil, types.A), + Memoize: true, + }, + func(bctx rego.BuiltinContext, terms []*ast.Term) (*ast.Term, error) { + uri, err := url.ParseRequestURI(sf.signerAddr + "/v1/keys") + if err != nil { + return nil, err + } + + req, err := http.NewRequest("GET", uri.String(), nil) + if err != nil { + return nil, err + } + + resp, err := sf.httpClient.Do(req.WithContext(bctx.Context)) + if err != nil { + return nil, err + } + defer resp.Body.Close() // nolint:errcheck + + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("unexpected response from signer: %s", resp.Status) + } + + v, err := ast.ValueFromReader(resp.Body) + if err != nil { + return nil, err + } + + return ast.NewTerm(v), nil + } +} + +func (sf *SignerFuncs) IssuerDID() (*rego.Function, rego.BuiltinDyn) { + return ®o.Function{ + Name: "issuer", + Decl: types.NewFunction(nil, types.A), + Memoize: true, + }, + func(bctx rego.BuiltinContext, terms []*ast.Term) (*ast.Term, error) { + uri, err := url.ParseRequestURI(sf.signerAddr + "/v1/issuerDID") + if err != nil { + return nil, err + } + + req, err := http.NewRequest("GET", uri.String(), nil) + if err != nil { + return nil, err + } + + resp, err := sf.httpClient.Do(req.WithContext(bctx.Context)) + if err != nil { + return nil, err + } + defer resp.Body.Close() // nolint:errcheck + + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("unexpected response from signer: %s", resp.Status) + } + + v, err := ast.ValueFromReader(resp.Body) + if err != nil { + return nil, err + } + + return ast.NewTerm(v), nil + } +} + +func (sf *SignerFuncs) CreateProof() (*rego.Function, rego.Builtin1) { + return ®o.Function{ + Name: "proof.create", + Decl: types.NewFunction(types.Args(types.S), types.A), + Memoize: true, + }, + func(bctx rego.BuiltinContext, credential *ast.Term) (*ast.Term, error) { + // cred represents verifiable credential or presentation + var cred map[string]interface{} + if err := ast.As(credential.Value, &cred); err != nil { + return nil, fmt.Errorf("invalid credential: %s", err) + } + + if cred["type"] == nil { + return nil, fmt.Errorf("credential data does not specify type: must be VerifiablePresentation or VerifiableCredential") + } + + credType, ok := cred["type"].(string) + if !ok { + return nil, fmt.Errorf("invalid credential type, string is expected") + } + + var createProofPath string + switch credType { + case "VerifiableCredential": + createProofPath = "/v1/credential/proof" + case "VerifiablePresentation": + createProofPath = "/v1/presentation/proof" + default: + return nil, fmt.Errorf("unknown credential type: %q", credType) + } + + jsonCred, err := json.Marshal(cred) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("POST", sf.signerAddr+createProofPath, bytes.NewReader(jsonCred)) + if err != nil { + return nil, err + } + + resp, err := sf.httpClient.Do(req.WithContext(bctx.Context)) + if err != nil { + return nil, err + } + defer resp.Body.Close() // nolint:errcheck + + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("unexpected response from signer: %s", resp.Status) + } + + v, err := ast.ValueFromReader(resp.Body) + if err != nil { + return nil, err + } + + return ast.NewTerm(v), nil + } +} + +func (sf *SignerFuncs) VerifyProof() (*rego.Function, rego.Builtin1) { + return ®o.Function{ + Name: "proof.verify", + Decl: types.NewFunction(types.Args(types.S), types.A), + Memoize: true, + }, + func(bctx rego.BuiltinContext, credential *ast.Term) (*ast.Term, error) { + // cred represents verifiable credential or presentation + var cred map[string]interface{} + if err := ast.As(credential.Value, &cred); err != nil { + return nil, fmt.Errorf("invalid credential: %s", err) + } + + if cred["type"] == nil { + return nil, fmt.Errorf("credential data does not specify type: must be VerifiablePresentation or VerifiableCredential") + } + + credType, ok := cred["type"].(string) + if !ok { + return nil, fmt.Errorf("invalid credential type, string is expected") + } + + if cred["proof"] == nil { + return nil, fmt.Errorf("credential data does contain proof section") + } + + var verifyProofPath string + switch credType { + case "VerifiableCredential": + verifyProofPath = "/v1/credential/verify" + case "VerifiablePresentation": + verifyProofPath = "/v1/presentation/verify" + default: + return nil, fmt.Errorf("unknown credential type: %q", credType) + } + + jsonCred, err := json.Marshal(cred) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("POST", sf.signerAddr+verifyProofPath, bytes.NewReader(jsonCred)) + if err != nil { + return nil, err + } + + resp, err := sf.httpClient.Do(req.WithContext(bctx.Context)) + if err != nil { + return nil, err + } + defer resp.Body.Close() // nolint:errcheck + + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("unexpected response from signer: %s", resp.Status) + } + + var result struct { + Valid bool `json:"valid"` + } + if err := json.NewDecoder(resp.Body).Decode(&result); err != nil { + return nil, fmt.Errorf("failed to decode response from signer: %v", err) + } + + if !result.Valid { + return nil, fmt.Errorf("proof is invalid") + } + + return ast.NewTerm(ast.Boolean(true)), nil + } +} diff --git a/internal/regofunc/signer_test.go b/internal/regofunc/signer_test.go new file mode 100644 index 0000000000000000000000000000000000000000..1adb9fb66abfc1152fe89faa3031a412fb0dbb9e --- /dev/null +++ b/internal/regofunc/signer_test.go @@ -0,0 +1,249 @@ +package regofunc_test + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "net/http/httptest" + "testing" + + "github.com/open-policy-agent/opa/rego" + "github.com/stretchr/testify/assert" + + "code.vereign.com/gaiax/tsa/policy/internal/regofunc" +) + +func TestGetKeyFunc(t *testing.T) { + expected := `{"key1":"key1 data"}` + signerSrv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + _, _ = fmt.Fprint(w, expected) + })) + defer signerSrv.Close() + + keysFuncs := regofunc.NewSignerFuncs(signerSrv.URL, http.DefaultClient) + r := rego.New( + rego.Query(`keys.get("key1")`), + rego.Function1(keysFuncs.GetKeyFunc()), + rego.StrictBuiltinErrors(true), + ) + resultSet, err := r.Eval(context.Background()) + assert.NoError(t, err) + + resultBytes, err := json.Marshal(resultSet[0].Expressions[0].Value) + assert.NoError(t, err) + assert.Equal(t, expected, string(resultBytes)) +} + +func TestGetKeyFuncError(t *testing.T) { + signerSrv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusNotFound) + })) + defer signerSrv.Close() + + keysFuncs := regofunc.NewSignerFuncs(signerSrv.URL, http.DefaultClient) + r := rego.New( + rego.Query(`keys.get("key1")`), + rego.Function1(keysFuncs.GetKeyFunc()), + rego.StrictBuiltinErrors(true), + ) + resultSet, err := r.Eval(context.Background()) + assert.Nil(t, resultSet) + assert.Error(t, err) + + expectedError := `keys.get("key1"): eval_builtin_error: keys.get: unexpected response from signer: 404 Not Found` + assert.Equal(t, expectedError, err.Error()) +} + +func TestGetAllKeysFunc(t *testing.T) { + expected := `[{"key1":"key1 data"},{"key2":"key2 data"}]` + signerSrv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + _, _ = fmt.Fprint(w, expected) + })) + defer signerSrv.Close() + + keysFuncs := regofunc.NewSignerFuncs(signerSrv.URL, http.DefaultClient) + r := rego.New( + rego.Query(`keys.getAll()`), + rego.FunctionDyn(keysFuncs.GetAllKeysFunc()), + rego.StrictBuiltinErrors(true), + ) + resultSet, err := r.Eval(context.Background()) + assert.NoError(t, err) + + resultBytes, err := json.Marshal(resultSet[0].Expressions[0].Value) + assert.NoError(t, err) + assert.Equal(t, expected, string(resultBytes)) +} + +func TestIssuerDID(t *testing.T) { + expected := `{"did":"did:web:123"}` + signerSrv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + _, _ = fmt.Fprint(w, expected) + })) + defer signerSrv.Close() + + keysFuncs := regofunc.NewSignerFuncs(signerSrv.URL, http.DefaultClient) + r := rego.New( + rego.Query(`issuer()`), + rego.FunctionDyn(keysFuncs.IssuerDID()), + rego.StrictBuiltinErrors(true), + ) + resultSet, err := r.Eval(context.Background()) + assert.NoError(t, err) + + resultBytes, err := json.Marshal(resultSet[0].Expressions[0].Value) + assert.NoError(t, err) + assert.Equal(t, expected, string(resultBytes)) +} + +func TestCreateProof(t *testing.T) { + tests := []struct { + name string + input map[string]interface{} + signerResponseCode int + errtext string + }{ + { + name: "missing credential type", + input: map[string]interface{}{"vc": "data"}, + errtext: "credential data does not specify type", + }, + { + name: "unknown credential type", + input: map[string]interface{}{"type": "non-existing-type"}, + errtext: "unknown credential type", + }, + { + name: "signer returns error for VC", + input: map[string]interface{}{"type": "VerifiableCredential"}, + signerResponseCode: http.StatusBadRequest, + errtext: "400 Bad Request", + }, + { + name: "signer returns error for VP", + input: map[string]interface{}{"type": "VerifiablePresentation"}, + signerResponseCode: http.StatusBadRequest, + errtext: "400 Bad Request", + }, + { + name: "signer returns successfully", + input: map[string]interface{}{"type": "VerifiableCredential"}, + signerResponseCode: http.StatusOK, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + expected := `{"vc":"data"}` + signerSrv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(test.signerResponseCode) + _, _ = fmt.Fprint(w, expected) + })) + defer signerSrv.Close() + + keysFuncs := regofunc.NewSignerFuncs(signerSrv.URL, http.DefaultClient) + query, err := rego.New( + rego.Query(`proof.create(input)`), + rego.Function1(keysFuncs.CreateProof()), + rego.StrictBuiltinErrors(true), + ).PrepareForEval(context.Background()) + assert.NoError(t, err) + + resultSet, err := query.Eval(context.Background(), rego.EvalInput(test.input)) + if err != nil { + assert.Contains(t, err.Error(), test.errtext) + } else { + assert.NotEmpty(t, resultSet) + assert.NotEmpty(t, resultSet[0].Expressions) + resultBytes, err := json.Marshal(resultSet[0].Expressions[0].Value) + assert.NoError(t, err) + assert.Equal(t, expected, string(resultBytes)) + } + }) + } +} + +func TestVerifyProof(t *testing.T) { + tests := []struct { + name string + input map[string]interface{} + signerResponseCode int + errtext string + }{ + { + name: "invalid credential", + input: nil, + errtext: "credential data does not specify type", + }, + { + name: "missing credential type", + input: map[string]interface{}{"vc": "data"}, + errtext: "credential data does not specify type", + }, + { + name: "credential type is not string", + input: map[string]interface{}{"type": 123}, + signerResponseCode: http.StatusBadRequest, + errtext: "invalid credential type, string is expected", + }, + { + name: "missing proof section", + input: map[string]interface{}{"type": "VerifiableCredential"}, + errtext: "credential data does contain proof section", + }, + { + name: "unknown credential type", + input: map[string]interface{}{"proof": "iamhere", "type": "non-existing-type"}, + errtext: "unknown credential type", + }, + { + name: "signer returns error for VC", + input: map[string]interface{}{"proof": "iamhere", "type": "VerifiableCredential"}, + signerResponseCode: http.StatusBadRequest, + errtext: "400 Bad Request", + }, + { + name: "signer returns error for VP", + input: map[string]interface{}{"proof": "iamhere", "type": "VerifiablePresentation"}, + signerResponseCode: http.StatusBadRequest, + errtext: "400 Bad Request", + }, + { + name: "signer returns successfully", + input: map[string]interface{}{"proof": "iamhere", "type": "VerifiableCredential"}, + signerResponseCode: http.StatusOK, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + expected := `{"valid":true}` + signerSrv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(test.signerResponseCode) + _, _ = fmt.Fprint(w, expected) + })) + defer signerSrv.Close() + + keysFuncs := regofunc.NewSignerFuncs(signerSrv.URL, http.DefaultClient) + query, err := rego.New( + rego.Query(`proof.verify(input)`), + rego.Function1(keysFuncs.VerifyProof()), + rego.StrictBuiltinErrors(true), + ).PrepareForEval(context.Background()) + assert.NoError(t, err) + + resultSet, err := query.Eval(context.Background(), rego.EvalInput(test.input)) + if err != nil { + assert.NotEmpty(t, test.errtext, "test case must contain error, but doesn't") + assert.Contains(t, err.Error(), test.errtext) + } else { + assert.NotEmpty(t, resultSet) + assert.NotEmpty(t, resultSet[0].Expressions) + valid, ok := resultSet[0].Expressions[0].Value.(bool) + assert.True(t, ok) + assert.True(t, valid) + } + }) + } +}