From ad947532248b8d80142cf0e5dc381f147484c767 Mon Sep 17 00:00:00 2001 From: FilippoTrotter Date: Wed, 22 Jan 2025 10:01:25 +0100 Subject: [PATCH 1/6] feat: add credimi first version --- cmd/credimi/credimi.go | 64 +++ go.mod | 1 + go.sum | 17 + pkg/metadata/metadata.go | 69 +++ .../openid-credential-issuer.schema.go | 435 ++++++++++++++++++ schemas/openid-credential-issuer.schema.json | 243 ++++++++++ 6 files changed, 829 insertions(+) create mode 100644 cmd/credimi/credimi.go create mode 100644 pkg/metadata/metadata.go create mode 100644 pkg/metadata/openid-credential-issuer.schema.go create mode 100644 schemas/openid-credential-issuer.schema.json diff --git a/cmd/credimi/credimi.go b/cmd/credimi/credimi.go new file mode 100644 index 0000000..fac5850 --- /dev/null +++ b/cmd/credimi/credimi.go @@ -0,0 +1,64 @@ +package main + +import ( + "fmt" + "io" + "os" + + "github.com/forkbombeu/didimo/pkg/metadata" + "github.com/spf13/cobra" +) + +//go:generate go run github.com/atombender/go-jsonschema@latest -p metadata ../../schemas/openid-credential-issuer.schema.json -o ../../pkg/metadata/openid-credential-issuer.schema.go +func main() { + var ( + url string + outputFile string + ) + + rootCmd := &cobra.Command{ + Use: "credimi", + Short: "Fetch and parse .well-known credential issuer metadata", + RunE: func(cmd *cobra.Command, args []string) error { + if url == "" { + return fmt.Errorf("error: URL is required") + } + + // Fetch metadata + issuerMetadata, err := metadata.FetchURL(url) + if err != nil { + return fmt.Errorf("error fetching metadata: %v", err) + } + + // Determine the output writer + var writer io.Writer + if outputFile == "" || outputFile == "stdout" { + writer = os.Stdout + } else { + file, err := os.Create(outputFile) + if err != nil { + return fmt.Errorf("error creating file: %v", err) + } + defer file.Close() + writer = file + } + + // Output metadata + if err := metadata.Output(issuerMetadata, writer); err != nil { + return fmt.Errorf("error writing metadata: %v", err) + } + + return nil + }, + } + + rootCmd.Flags().StringVarP(&url, "url", "u", "", "The URL of the credential issuer (required)") + rootCmd.Flags().StringVarP(&outputFile, "output", "o", "", "Output destination (e.g., stdout or file path)") + rootCmd.MarkFlagRequired("url") + + // Execute the root command + if err := rootCmd.Execute(); err != nil { + fmt.Printf("Error: %v\n", err) + os.Exit(1) + } +} diff --git a/go.mod b/go.mod index de6c02d..c1e9e26 100644 --- a/go.mod +++ b/go.mod @@ -71,6 +71,7 @@ require ( github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/ncruces/go-strftime v0.1.9 // indirect + github.com/neilotoole/jsoncolor v0.7.1 // indirect github.com/nexus-rpc/sdk-go v0.1.0 // indirect github.com/pborman/uuid v1.2.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect diff --git a/go.sum b/go.sum index 2b194df..94093df 100644 --- a/go.sum +++ b/go.sum @@ -95,6 +95,8 @@ github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1m github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/facebookgo/clock v0.0.0-20150410010913-600d898af40a h1:yDWHCSQ40h88yih2JAcL6Ls/kVkSE8GFACTGVnMPruw= github.com/facebookgo/clock v0.0.0-20150410010913-600d898af40a/go.mod h1:7Ga40egUymuWXxAe151lTNnCv97MddSOVsjpPPkityA= +github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= +github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= @@ -202,9 +204,13 @@ github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= @@ -214,8 +220,11 @@ github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyua github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4= github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= +github.com/neilotoole/jsoncolor v0.7.1 h1:/MoU7KPLcto+ykcy592Y8eX9WFQhoi3IBEbwrP89dgs= +github.com/neilotoole/jsoncolor v0.7.1/go.mod h1:KZ9hUYN5xMrvyhqlFQ3QTmu11OcoqFgSnWAcYkN6abg= github.com/nexus-rpc/sdk-go v0.1.0 h1:PUL/0vEY1//WnqyEHT5ao4LBRQ6MeNUihmnNGn0xMWY= github.com/nexus-rpc/sdk-go v0.1.0/go.mod h1:TpfkM2Cw0Rlk9drGkoiSMpFqflKTiQLWUNyKJjF8mKQ= +github.com/nwidger/jsoncolor v0.3.2/go.mod h1:Cs34umxLbJvgBMnVNVqhji9BhoT/N/KinHqZptQ7cf4= github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/pborman/uuid v1.2.1 h1:+ZZIw58t/ozdjRaXh/3awHfmWRbzYxJoAdNJxe/3pvw= github.com/pborman/uuid v1.2.1/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= @@ -234,6 +243,8 @@ github.com/robfig/cron v1.2.0/go.mod h1:JGuDeoQd7Z6yL4zQhZ3OPEVHB7fL6Ka6skscFHfm github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/segmentio/asm v1.1.3/go.mod h1:Ld3L4ZXGNcSLRg4JBsZ3//1+f/TjYl0Mzen/DQy1EJg= +github.com/segmentio/encoding v0.3.6/go.mod h1:n0JeuIqEQrQoPDGsjo8UNd1iA0U8d8+oHAA4E3G3OxM= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y= github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= @@ -255,6 +266,7 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= @@ -343,19 +355,24 @@ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211110154304-99a53858aa08/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.14.0/go.mod h1:TySc+nGkYR6qt8km8wUhuFRTVSMIX3XPR58y2lC8vww= golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg= golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= diff --git a/pkg/metadata/metadata.go b/pkg/metadata/metadata.go new file mode 100644 index 0000000..d9e491f --- /dev/null +++ b/pkg/metadata/metadata.go @@ -0,0 +1,69 @@ +package metadata + +import ( + "fmt" + "io" + "net/http" + "strings" + + json "github.com/neilotoole/jsoncolor" +) + +var credentialIssuerEndpoint string = "/.well-known/openid-credential-issuer" + +// FetchURL fetches the metadata from the provided URL and validates the credential issuer. +func FetchURL(baseURL string) (*OpenidCredentialIssuerSchemaJson, error) { + if !strings.HasPrefix(baseURL, "http://") && !strings.HasPrefix(baseURL, "https://") { + baseURL = "https://" + baseURL + } + + credentialIssuerURL := strings.TrimRight(baseURL, "/") + credentialIssuerEndpoint + + resp, err := http.Get(credentialIssuerURL) + if err != nil { + return nil, fmt.Errorf("failed to reach credential issuer URL: %v", err) + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("not a credential issuer: %d %s", resp.StatusCode, resp.Status) + } + + body, err := io.ReadAll(resp.Body) + if err != nil { + return nil, fmt.Errorf("failed to read response body: %v", err) + } + + var metadata OpenidCredentialIssuerSchemaJson + if err := json.Unmarshal(body, &metadata); err != nil { + return nil, fmt.Errorf("failed to parse JSON: %v", err) + } + + return &metadata, nil +} + +// Output metadata to the provided writer +func Output(metadata *OpenidCredentialIssuerSchemaJson, writer io.Writer) error { + var enc *json.Encoder + + // Note: this check will fail if running inside Goland (and + // other IDEs?) as IsColorTerminal will return false. + if json.IsColorTerminal(writer) { + // Safe to use color + enc = json.NewEncoder(writer) + + // DefaultColors are similar to jq + clrs := json.DefaultColors() + enc.SetColors(clrs) + } else { + // Can't use color; but the encoder will still work + enc = json.NewEncoder(writer) + } + enc.SetIndent("", " ") + // Encode the metadata into the writer + if err := enc.Encode(metadata); err != nil { + return fmt.Errorf("failed to write metadata: %v", err) + } + + return nil +} diff --git a/pkg/metadata/openid-credential-issuer.schema.go b/pkg/metadata/openid-credential-issuer.schema.go new file mode 100644 index 0000000..0e368ac --- /dev/null +++ b/pkg/metadata/openid-credential-issuer.schema.go @@ -0,0 +1,435 @@ +// Code generated by github.com/atombender/go-jsonschema, DO NOT EDIT. + +package metadata + +import "encoding/json" +import "fmt" +import "reflect" + +type CredentialSigningAlgValuesSupportedElem string + +const CredentialSigningAlgValuesSupportedElemES256 CredentialSigningAlgValuesSupportedElem = "ES256" +const CredentialSigningAlgValuesSupportedElemES256K CredentialSigningAlgValuesSupportedElem = "ES256K" +const CredentialSigningAlgValuesSupportedElemEdDSA CredentialSigningAlgValuesSupportedElem = "EdDSA" +const CredentialSigningAlgValuesSupportedElemRS256 CredentialSigningAlgValuesSupportedElem = "RS256" +const CredentialSigningAlgValuesSupportedElemRSA CredentialSigningAlgValuesSupportedElem = "RSA" +const CredentialSigningAlgValuesSupportedElemRsaSignature2018 CredentialSigningAlgValuesSupportedElem = "RsaSignature2018" + +var enumValues_CredentialSigningAlgValuesSupportedElem = []interface{}{ + "ES256", + "EdDSA", + "RS256", + "ES256K", + "RSA", + "RsaSignature2018", +} + +// UnmarshalJSON implements json.Unmarshaler. +func (j *CredentialSigningAlgValuesSupportedElem) UnmarshalJSON(b []byte) error { + var v string + if err := json.Unmarshal(b, &v); err != nil { + return err + } + var ok bool + for _, expected := range enumValues_CredentialSigningAlgValuesSupportedElem { + if reflect.DeepEqual(v, expected) { + ok = true + break + } + } + if !ok { + return fmt.Errorf("invalid value (expected one of %#v): %#v", enumValues_CredentialSigningAlgValuesSupportedElem, v) + } + *j = CredentialSigningAlgValuesSupportedElem(v) + return nil +} + +type CryptographicBindingMethodsSupportedElem string + +const CryptographicBindingMethodsSupportedElemCoseKey CryptographicBindingMethodsSupportedElem = "cose_key" +const CryptographicBindingMethodsSupportedElemDid CryptographicBindingMethodsSupportedElem = "did" +const CryptographicBindingMethodsSupportedElemDidDyne CryptographicBindingMethodsSupportedElem = "did:dyne" +const CryptographicBindingMethodsSupportedElemDidEbsi CryptographicBindingMethodsSupportedElem = "did:ebsi" +const CryptographicBindingMethodsSupportedElemDidJwk CryptographicBindingMethodsSupportedElem = "did:jwk" +const CryptographicBindingMethodsSupportedElemDidKey CryptographicBindingMethodsSupportedElem = "did:key" +const CryptographicBindingMethodsSupportedElemDidWeb CryptographicBindingMethodsSupportedElem = "did:web" +const CryptographicBindingMethodsSupportedElemJwk CryptographicBindingMethodsSupportedElem = "jwk" + +var enumValues_CryptographicBindingMethodsSupportedElem = []interface{}{ + "jwk", + "did", + "did:web", + "did:ebsi", + "did:jwk", + "did:dyne", + "did:key", + "cose_key", +} + +// UnmarshalJSON implements json.Unmarshaler. +func (j *CryptographicBindingMethodsSupportedElem) UnmarshalJSON(b []byte) error { + var v string + if err := json.Unmarshal(b, &v); err != nil { + return err + } + var ok bool + for _, expected := range enumValues_CryptographicBindingMethodsSupportedElem { + if reflect.DeepEqual(v, expected) { + ok = true + break + } + } + if !ok { + return fmt.Errorf("invalid value (expected one of %#v): %#v", enumValues_CryptographicBindingMethodsSupportedElem, v) + } + *j = CryptographicBindingMethodsSupportedElem(v) + return nil +} + +type DisplayElem struct { + // Locale corresponds to the JSON schema field "locale". + Locale *string `json:"locale,omitempty" yaml:"locale,omitempty" mapstructure:"locale,omitempty"` + + // Logo corresponds to the JSON schema field "logo". + Logo *DisplayElemLogo `json:"logo,omitempty" yaml:"logo,omitempty" mapstructure:"logo,omitempty"` + + // Name corresponds to the JSON schema field "name". + Name string `json:"name" yaml:"name" mapstructure:"name"` +} + +type DisplayElemLogo struct { + // AltText corresponds to the JSON schema field "alt_text". + AltText *string `json:"alt_text,omitempty" yaml:"alt_text,omitempty" mapstructure:"alt_text,omitempty"` + + // Uri corresponds to the JSON schema field "uri". + Uri string `json:"uri" yaml:"uri" mapstructure:"uri"` +} + +// UnmarshalJSON implements json.Unmarshaler. +func (j *DisplayElemLogo) UnmarshalJSON(b []byte) error { + var raw map[string]interface{} + if err := json.Unmarshal(b, &raw); err != nil { + return err + } + if _, ok := raw["uri"]; raw != nil && !ok { + return fmt.Errorf("field uri in DisplayElemLogo: required") + } + type Plain DisplayElemLogo + var plain Plain + if err := json.Unmarshal(b, &plain); err != nil { + return err + } + *j = DisplayElemLogo(plain) + return nil +} + +// UnmarshalJSON implements json.Unmarshaler. +func (j *DisplayElem) UnmarshalJSON(b []byte) error { + var raw map[string]interface{} + if err := json.Unmarshal(b, &raw); err != nil { + return err + } + if _, ok := raw["name"]; raw != nil && !ok { + return fmt.Errorf("field name in DisplayElem: required") + } + type Plain DisplayElem + var plain Plain + if err := json.Unmarshal(b, &plain); err != nil { + return err + } + *j = DisplayElem(plain) + return nil +} + +type KeyAttestationsRequired struct { + // KeyStorage corresponds to the JSON schema field "key_storage". + KeyStorage []string `json:"key_storage,omitempty" yaml:"key_storage,omitempty" mapstructure:"key_storage,omitempty"` + + // UserAuthentication corresponds to the JSON schema field "user_authentication". + UserAuthentication []string `json:"user_authentication,omitempty" yaml:"user_authentication,omitempty" mapstructure:"user_authentication,omitempty"` +} + +type OpenidCredentialIssuerSchemaJson struct { + // Array of OAuth 2.0 Authorization Server identifiers. + AuthorizationServers []string `json:"authorization_servers,omitempty" yaml:"authorization_servers,omitempty" mapstructure:"authorization_servers,omitempty"` + + // BatchCredentialIssuance corresponds to the JSON schema field + // "batch_credential_issuance". + BatchCredentialIssuance *OpenidCredentialIssuerSchemaJsonBatchCredentialIssuance `json:"batch_credential_issuance,omitempty" yaml:"batch_credential_issuance,omitempty" mapstructure:"batch_credential_issuance,omitempty"` + + // CredentialConfigurationsSupported corresponds to the JSON schema field + // "credential_configurations_supported". + CredentialConfigurationsSupported OpenidCredentialIssuerSchemaJsonCredentialConfigurationsSupported `json:"credential_configurations_supported" yaml:"credential_configurations_supported" mapstructure:"credential_configurations_supported"` + + // URL of the Credential Issuer's Credential Endpoint. + CredentialEndpoint string `json:"credential_endpoint" yaml:"credential_endpoint" mapstructure:"credential_endpoint"` + + // The Credential Issuer's identifier + CredentialIssuer string `json:"credential_issuer" yaml:"credential_issuer" mapstructure:"credential_issuer"` + + // CredentialResponseEncryption corresponds to the JSON schema field + // "credential_response_encryption". + CredentialResponseEncryption *OpenidCredentialIssuerSchemaJsonCredentialResponseEncryption `json:"credential_response_encryption,omitempty" yaml:"credential_response_encryption,omitempty" mapstructure:"credential_response_encryption,omitempty"` + + // URL of the Credential Issuer's Deferred Credential Endpoint. + DeferredCredentialEndpoint *string `json:"deferred_credential_endpoint,omitempty" yaml:"deferred_credential_endpoint,omitempty" mapstructure:"deferred_credential_endpoint,omitempty"` + + // Display corresponds to the JSON schema field "display". + Display []OpenidCredentialIssuerSchemaJsonDisplayElem `json:"display,omitempty" yaml:"display,omitempty" mapstructure:"display,omitempty"` + + // URL of the Credential Issuer's Nonce Endpoint. + NonceEndpoint *string `json:"nonce_endpoint,omitempty" yaml:"nonce_endpoint,omitempty" mapstructure:"nonce_endpoint,omitempty"` + + // URL of the Credential Issuer's Notification Endpoint. + NotificationEndpoint *string `json:"notification_endpoint,omitempty" yaml:"notification_endpoint,omitempty" mapstructure:"notification_endpoint,omitempty"` + + // JWT containing Credential Issuer metadata parameters as claims. + SignedMetadata *string `json:"signed_metadata,omitempty" yaml:"signed_metadata,omitempty" mapstructure:"signed_metadata,omitempty"` +} + +type OpenidCredentialIssuerSchemaJsonBatchCredentialIssuance struct { + // BatchSize corresponds to the JSON schema field "batch_size". + BatchSize int `json:"batch_size" yaml:"batch_size" mapstructure:"batch_size"` +} + +// UnmarshalJSON implements json.Unmarshaler. +func (j *OpenidCredentialIssuerSchemaJsonBatchCredentialIssuance) UnmarshalJSON(b []byte) error { + var raw map[string]interface{} + if err := json.Unmarshal(b, &raw); err != nil { + return err + } + if _, ok := raw["batch_size"]; raw != nil && !ok { + return fmt.Errorf("field batch_size in OpenidCredentialIssuerSchemaJsonBatchCredentialIssuance: required") + } + type Plain OpenidCredentialIssuerSchemaJsonBatchCredentialIssuance + var plain Plain + if err := json.Unmarshal(b, &plain); err != nil { + return err + } + if 1 > plain.BatchSize { + return fmt.Errorf("field %s: must be >= %v", "batch_size", 1) + } + *j = OpenidCredentialIssuerSchemaJsonBatchCredentialIssuance(plain) + return nil +} + +type OpenidCredentialIssuerSchemaJsonCredentialConfigurationsSupported map[string]struct { + // CredentialSigningAlgValuesSupported corresponds to the JSON schema field + // "credential_signing_alg_values_supported". + CredentialSigningAlgValuesSupported []CredentialSigningAlgValuesSupportedElem `json:"credential_signing_alg_values_supported,omitempty" yaml:"credential_signing_alg_values_supported,omitempty" mapstructure:"credential_signing_alg_values_supported,omitempty"` + + // CryptographicBindingMethodsSupported corresponds to the JSON schema field + // "cryptographic_binding_methods_supported". + CryptographicBindingMethodsSupported []CryptographicBindingMethodsSupportedElem `json:"cryptographic_binding_methods_supported,omitempty" yaml:"cryptographic_binding_methods_supported,omitempty" mapstructure:"cryptographic_binding_methods_supported,omitempty"` + + // Display corresponds to the JSON schema field "display". + Display []DisplayElem `json:"display,omitempty" yaml:"display,omitempty" mapstructure:"display,omitempty"` + + // Format corresponds to the JSON schema field "format". + Format string `json:"format" yaml:"format" mapstructure:"format"` + + // ProofTypesSupported corresponds to the JSON schema field + // "proof_types_supported". + ProofTypesSupported ProofTypesSupported `json:"proof_types_supported,omitempty" yaml:"proof_types_supported,omitempty" mapstructure:"proof_types_supported,omitempty"` + + // Scope corresponds to the JSON schema field "scope". + Scope *string `json:"scope,omitempty" yaml:"scope,omitempty" mapstructure:"scope,omitempty"` +} + +type OpenidCredentialIssuerSchemaJsonCredentialResponseEncryption struct { + // AlgValuesSupported corresponds to the JSON schema field "alg_values_supported". + AlgValuesSupported []OpenidCredentialIssuerSchemaJsonCredentialResponseEncryptionAlgValuesSupportedElem `json:"alg_values_supported" yaml:"alg_values_supported" mapstructure:"alg_values_supported"` + + // EncValuesSupported corresponds to the JSON schema field "enc_values_supported". + EncValuesSupported []OpenidCredentialIssuerSchemaJsonCredentialResponseEncryptionEncValuesSupportedElem `json:"enc_values_supported" yaml:"enc_values_supported" mapstructure:"enc_values_supported"` + + // EncryptionRequired corresponds to the JSON schema field "encryption_required". + EncryptionRequired bool `json:"encryption_required" yaml:"encryption_required" mapstructure:"encryption_required"` +} + +type OpenidCredentialIssuerSchemaJsonCredentialResponseEncryptionAlgValuesSupportedElem string + +const OpenidCredentialIssuerSchemaJsonCredentialResponseEncryptionAlgValuesSupportedElemES256 OpenidCredentialIssuerSchemaJsonCredentialResponseEncryptionAlgValuesSupportedElem = "ES256" +const OpenidCredentialIssuerSchemaJsonCredentialResponseEncryptionAlgValuesSupportedElemEdDSA OpenidCredentialIssuerSchemaJsonCredentialResponseEncryptionAlgValuesSupportedElem = "EdDSA" +const OpenidCredentialIssuerSchemaJsonCredentialResponseEncryptionAlgValuesSupportedElemRS256 OpenidCredentialIssuerSchemaJsonCredentialResponseEncryptionAlgValuesSupportedElem = "RS256" + +var enumValues_OpenidCredentialIssuerSchemaJsonCredentialResponseEncryptionAlgValuesSupportedElem = []interface{}{ + "ES256", + "EdDSA", + "RS256", +} + +// UnmarshalJSON implements json.Unmarshaler. +func (j *OpenidCredentialIssuerSchemaJsonCredentialResponseEncryptionAlgValuesSupportedElem) UnmarshalJSON(b []byte) error { + var v string + if err := json.Unmarshal(b, &v); err != nil { + return err + } + var ok bool + for _, expected := range enumValues_OpenidCredentialIssuerSchemaJsonCredentialResponseEncryptionAlgValuesSupportedElem { + if reflect.DeepEqual(v, expected) { + ok = true + break + } + } + if !ok { + return fmt.Errorf("invalid value (expected one of %#v): %#v", enumValues_OpenidCredentialIssuerSchemaJsonCredentialResponseEncryptionAlgValuesSupportedElem, v) + } + *j = OpenidCredentialIssuerSchemaJsonCredentialResponseEncryptionAlgValuesSupportedElem(v) + return nil +} + +type OpenidCredentialIssuerSchemaJsonCredentialResponseEncryptionEncValuesSupportedElem string + +const OpenidCredentialIssuerSchemaJsonCredentialResponseEncryptionEncValuesSupportedElemA128CBCHS256 OpenidCredentialIssuerSchemaJsonCredentialResponseEncryptionEncValuesSupportedElem = "A128CBC-HS256" +const OpenidCredentialIssuerSchemaJsonCredentialResponseEncryptionEncValuesSupportedElemA128GCM OpenidCredentialIssuerSchemaJsonCredentialResponseEncryptionEncValuesSupportedElem = "A128GCM" + +var enumValues_OpenidCredentialIssuerSchemaJsonCredentialResponseEncryptionEncValuesSupportedElem = []interface{}{ + "A128CBC-HS256", + "A128GCM", +} + +// UnmarshalJSON implements json.Unmarshaler. +func (j *OpenidCredentialIssuerSchemaJsonCredentialResponseEncryptionEncValuesSupportedElem) UnmarshalJSON(b []byte) error { + var v string + if err := json.Unmarshal(b, &v); err != nil { + return err + } + var ok bool + for _, expected := range enumValues_OpenidCredentialIssuerSchemaJsonCredentialResponseEncryptionEncValuesSupportedElem { + if reflect.DeepEqual(v, expected) { + ok = true + break + } + } + if !ok { + return fmt.Errorf("invalid value (expected one of %#v): %#v", enumValues_OpenidCredentialIssuerSchemaJsonCredentialResponseEncryptionEncValuesSupportedElem, v) + } + *j = OpenidCredentialIssuerSchemaJsonCredentialResponseEncryptionEncValuesSupportedElem(v) + return nil +} + +// UnmarshalJSON implements json.Unmarshaler. +func (j *OpenidCredentialIssuerSchemaJsonCredentialResponseEncryption) UnmarshalJSON(b []byte) error { + var raw map[string]interface{} + if err := json.Unmarshal(b, &raw); err != nil { + return err + } + if _, ok := raw["alg_values_supported"]; raw != nil && !ok { + return fmt.Errorf("field alg_values_supported in OpenidCredentialIssuerSchemaJsonCredentialResponseEncryption: required") + } + if _, ok := raw["enc_values_supported"]; raw != nil && !ok { + return fmt.Errorf("field enc_values_supported in OpenidCredentialIssuerSchemaJsonCredentialResponseEncryption: required") + } + if _, ok := raw["encryption_required"]; raw != nil && !ok { + return fmt.Errorf("field encryption_required in OpenidCredentialIssuerSchemaJsonCredentialResponseEncryption: required") + } + type Plain OpenidCredentialIssuerSchemaJsonCredentialResponseEncryption + var plain Plain + if err := json.Unmarshal(b, &plain); err != nil { + return err + } + *j = OpenidCredentialIssuerSchemaJsonCredentialResponseEncryption(plain) + return nil +} + +type OpenidCredentialIssuerSchemaJsonDisplayElem struct { + // Locale corresponds to the JSON schema field "locale". + Locale *string `json:"locale,omitempty" yaml:"locale,omitempty" mapstructure:"locale,omitempty"` + + // Logo corresponds to the JSON schema field "logo". + Logo *OpenidCredentialIssuerSchemaJsonDisplayElemLogo `json:"logo,omitempty" yaml:"logo,omitempty" mapstructure:"logo,omitempty"` + + // Name corresponds to the JSON schema field "name". + Name *string `json:"name,omitempty" yaml:"name,omitempty" mapstructure:"name,omitempty"` +} + +type OpenidCredentialIssuerSchemaJsonDisplayElemLogo struct { + // AltText corresponds to the JSON schema field "alt_text". + AltText *string `json:"alt_text,omitempty" yaml:"alt_text,omitempty" mapstructure:"alt_text,omitempty"` + + // Uri corresponds to the JSON schema field "uri". + Uri string `json:"uri" yaml:"uri" mapstructure:"uri"` +} + +// UnmarshalJSON implements json.Unmarshaler. +func (j *OpenidCredentialIssuerSchemaJsonDisplayElemLogo) UnmarshalJSON(b []byte) error { + var raw map[string]interface{} + if err := json.Unmarshal(b, &raw); err != nil { + return err + } + if _, ok := raw["uri"]; raw != nil && !ok { + return fmt.Errorf("field uri in OpenidCredentialIssuerSchemaJsonDisplayElemLogo: required") + } + type Plain OpenidCredentialIssuerSchemaJsonDisplayElemLogo + var plain Plain + if err := json.Unmarshal(b, &plain); err != nil { + return err + } + *j = OpenidCredentialIssuerSchemaJsonDisplayElemLogo(plain) + return nil +} + +// UnmarshalJSON implements json.Unmarshaler. +func (j *OpenidCredentialIssuerSchemaJson) UnmarshalJSON(b []byte) error { + var raw map[string]interface{} + if err := json.Unmarshal(b, &raw); err != nil { + return err + } + if _, ok := raw["credential_configurations_supported"]; raw != nil && !ok { + return fmt.Errorf("field credential_configurations_supported in OpenidCredentialIssuerSchemaJson: required") + } + if _, ok := raw["credential_endpoint"]; raw != nil && !ok { + return fmt.Errorf("field credential_endpoint in OpenidCredentialIssuerSchemaJson: required") + } + if _, ok := raw["credential_issuer"]; raw != nil && !ok { + return fmt.Errorf("field credential_issuer in OpenidCredentialIssuerSchemaJson: required") + } + type Plain OpenidCredentialIssuerSchemaJson + var plain Plain + if err := json.Unmarshal(b, &plain); err != nil { + return err + } + *j = OpenidCredentialIssuerSchemaJson(plain) + return nil +} + +type ProofSigningAlgValuesSupportedElem string + +const ProofSigningAlgValuesSupportedElemES256 ProofSigningAlgValuesSupportedElem = "ES256" +const ProofSigningAlgValuesSupportedElemEdDSA ProofSigningAlgValuesSupportedElem = "EdDSA" + +var enumValues_ProofSigningAlgValuesSupportedElem = []interface{}{ + "ES256", + "EdDSA", +} + +// UnmarshalJSON implements json.Unmarshaler. +func (j *ProofSigningAlgValuesSupportedElem) UnmarshalJSON(b []byte) error { + var v string + if err := json.Unmarshal(b, &v); err != nil { + return err + } + var ok bool + for _, expected := range enumValues_ProofSigningAlgValuesSupportedElem { + if reflect.DeepEqual(v, expected) { + ok = true + break + } + } + if !ok { + return fmt.Errorf("invalid value (expected one of %#v): %#v", enumValues_ProofSigningAlgValuesSupportedElem, v) + } + *j = ProofSigningAlgValuesSupportedElem(v) + return nil +} + +type ProofTypesSupported map[string]struct { + // KeyAttestationsRequired corresponds to the JSON schema field + // "key_attestations_required". + KeyAttestationsRequired *KeyAttestationsRequired `json:"key_attestations_required,omitempty" yaml:"key_attestations_required,omitempty" mapstructure:"key_attestations_required,omitempty"` + + // ProofSigningAlgValuesSupported corresponds to the JSON schema field + // "proof_signing_alg_values_supported". + ProofSigningAlgValuesSupported []ProofSigningAlgValuesSupportedElem `json:"proof_signing_alg_values_supported" yaml:"proof_signing_alg_values_supported" mapstructure:"proof_signing_alg_values_supported"` +} diff --git a/schemas/openid-credential-issuer.schema.json b/schemas/openid-credential-issuer.schema.json new file mode 100644 index 0000000..9df3578 --- /dev/null +++ b/schemas/openid-credential-issuer.schema.json @@ -0,0 +1,243 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "properties": { + "credential_issuer": { + "type": "string", + "format": "uri", + "description": "The Credential Issuer's identifier", + "examples": [ + "https://example.com/issuer" + ] + }, + "authorization_servers": { + "type": "array", + "items": { + "type": "string", + "format": "uri" + }, + "description": "Array of OAuth 2.0 Authorization Server identifiers." + }, + "credential_endpoint": { + "type": "string", + "format": "uri", + "description": "URL of the Credential Issuer's Credential Endpoint." + }, + "nonce_endpoint": { + "type": "string", + "format": "uri", + "description": "URL of the Credential Issuer's Nonce Endpoint." + }, + "deferred_credential_endpoint": { + "type": "string", + "format": "uri", + "description": "URL of the Credential Issuer's Deferred Credential Endpoint." + }, + "notification_endpoint": { + "type": "string", + "format": "uri", + "description": "URL of the Credential Issuer's Notification Endpoint." + }, + "credential_response_encryption": { + "type": "object", + "properties": { + "alg_values_supported": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "ES256", + "EdDSA", + "RS256" + ] + } + }, + "enc_values_supported": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "A128CBC-HS256", + "A128GCM" + ] + } + }, + "encryption_required": { + "type": "boolean" + } + }, + "required": [ + "alg_values_supported", + "enc_values_supported", + "encryption_required" + ] + }, + "batch_credential_issuance": { + "type": "object", + "properties": { + "batch_size": { + "type": "integer", + "minimum": 1 + } + }, + "required": [ + "batch_size" + ] + }, + "signed_metadata": { + "type": "string", + "description": "JWT containing Credential Issuer metadata parameters as claims." + }, + "display": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "locale": { + "type": "string" + }, + "logo": { + "type": "object", + "properties": { + "uri": { + "type": "string", + "format": "uri" + }, + "alt_text": { + "type": "string" + } + }, + "required": [ + "uri" + ] + } + } + } + }, + "credential_configurations_supported": { + "type": "object", + "additionalProperties": { + "type": "object", + "properties": { + "format": { + "type": "string" + }, + "scope": { + "type": "string" + }, + "cryptographic_binding_methods_supported": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "jwk", + "did", + "did:web", + "did:ebsi", + "did:jwk", + "did:dyne", + "did:key", + "cose_key" + ] + } + }, + "credential_signing_alg_values_supported": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "ES256", + "EdDSA", + "RS256", + "ES256K", + "RSA", + "RsaSignature2018" + ] + } + }, + "proof_types_supported": { + "type": "object", + "additionalProperties": { + "type": "object", + "properties": { + "proof_signing_alg_values_supported": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "ES256", + "EdDSA" + ] + } + }, + "key_attestations_required": { + "type": "object", + "properties": { + "key_storage": { + "type": "array", + "items": { + "type": "string" + } + }, + "user_authentication": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + }, + "required": [ + "proof_signing_alg_values_supported" + ] + } + }, + "display": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "locale": { + "type": "string" + }, + "logo": { + "type": "object", + "properties": { + "uri": { + "type": "string", + "format": "uri" + }, + "alt_text": { + "type": "string" + } + }, + "required": [ + "uri" + ] + } + }, + "required": [ + "name" + ] + } + } + }, + "required": [ + "format" + ] + } + } + }, + "required": [ + "credential_issuer", + "credential_endpoint", + "credential_configurations_supported" + ] +} From 7b2b7321327a9fc8c15d6ccf2c000e471356c4d9 Mon Sep 17 00:00:00 2001 From: FilippoTrotter Date: Wed, 22 Jan 2025 10:12:18 +0100 Subject: [PATCH 2/6] fix: remove duplicate error print --- cmd/credimi/credimi.go | 1 - 1 file changed, 1 deletion(-) diff --git a/cmd/credimi/credimi.go b/cmd/credimi/credimi.go index fac5850..d9eff74 100644 --- a/cmd/credimi/credimi.go +++ b/cmd/credimi/credimi.go @@ -58,7 +58,6 @@ func main() { // Execute the root command if err := rootCmd.Execute(); err != nil { - fmt.Printf("Error: %v\n", err) os.Exit(1) } } From f74acda1a4cdc7226dfe0d8de8f837db05ad8893 Mon Sep 17 00:00:00 2001 From: FilippoTrotter Date: Wed, 22 Jan 2025 10:44:58 +0100 Subject: [PATCH 3/6] fix: url must be an argument --- cmd/credimi/credimi.go | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/cmd/credimi/credimi.go b/cmd/credimi/credimi.go index d9eff74..9568e10 100644 --- a/cmd/credimi/credimi.go +++ b/cmd/credimi/credimi.go @@ -11,18 +11,15 @@ import ( //go:generate go run github.com/atombender/go-jsonschema@latest -p metadata ../../schemas/openid-credential-issuer.schema.json -o ../../pkg/metadata/openid-credential-issuer.schema.go func main() { - var ( - url string - outputFile string - ) + var outputFile string rootCmd := &cobra.Command{ - Use: "credimi", + Use: "credimi [url]", Short: "Fetch and parse .well-known credential issuer metadata", + Args: cobra.ExactArgs(1), // Ensure exactly one positional argument is provided RunE: func(cmd *cobra.Command, args []string) error { - if url == "" { - return fmt.Errorf("error: URL is required") - } + // Get the mandatory URL argument + url := args[0] // Fetch metadata issuerMetadata, err := metadata.FetchURL(url) @@ -52,9 +49,7 @@ func main() { }, } - rootCmd.Flags().StringVarP(&url, "url", "u", "", "The URL of the credential issuer (required)") rootCmd.Flags().StringVarP(&outputFile, "output", "o", "", "Output destination (e.g., stdout or file path)") - rootCmd.MarkFlagRequired("url") // Execute the root command if err := rootCmd.Execute(); err != nil { From 485088acefba3d28f297677586b43904bc929106 Mon Sep 17 00:00:00 2001 From: FilippoTrotter Date: Wed, 22 Jan 2025 10:50:46 +0100 Subject: [PATCH 4/6] refactor and add JWK to schema --- cmd/credimi/credimi.go | 9 ++++----- pkg/metadata/metadata.go | 2 +- pkg/metadata/openid-credential-issuer.schema.go | 4 ++-- schemas/openid-credential-issuer.schema.json | 2 +- 4 files changed, 8 insertions(+), 9 deletions(-) diff --git a/cmd/credimi/credimi.go b/cmd/credimi/credimi.go index 9568e10..3073bca 100644 --- a/cmd/credimi/credimi.go +++ b/cmd/credimi/credimi.go @@ -18,7 +18,6 @@ func main() { Short: "Fetch and parse .well-known credential issuer metadata", Args: cobra.ExactArgs(1), // Ensure exactly one positional argument is provided RunE: func(cmd *cobra.Command, args []string) error { - // Get the mandatory URL argument url := args[0] // Fetch metadata @@ -27,11 +26,11 @@ func main() { return fmt.Errorf("error fetching metadata: %v", err) } - // Determine the output writer var writer io.Writer - if outputFile == "" || outputFile == "stdout" { + switch outputFile { + case "", "stdout": writer = os.Stdout - } else { + default: file, err := os.Create(outputFile) if err != nil { return fmt.Errorf("error creating file: %v", err) @@ -41,7 +40,7 @@ func main() { } // Output metadata - if err := metadata.Output(issuerMetadata, writer); err != nil { + if err := metadata.PrintJSON(issuerMetadata, writer); err != nil { return fmt.Errorf("error writing metadata: %v", err) } diff --git a/pkg/metadata/metadata.go b/pkg/metadata/metadata.go index d9e491f..eb964fe 100644 --- a/pkg/metadata/metadata.go +++ b/pkg/metadata/metadata.go @@ -43,7 +43,7 @@ func FetchURL(baseURL string) (*OpenidCredentialIssuerSchemaJson, error) { } // Output metadata to the provided writer -func Output(metadata *OpenidCredentialIssuerSchemaJson, writer io.Writer) error { +func PrintJSON(metadata *OpenidCredentialIssuerSchemaJson, writer io.Writer) error { var enc *json.Encoder // Note: this check will fail if running inside Goland (and diff --git a/pkg/metadata/openid-credential-issuer.schema.go b/pkg/metadata/openid-credential-issuer.schema.go index 0e368ac..b484d46 100644 --- a/pkg/metadata/openid-credential-issuer.schema.go +++ b/pkg/metadata/openid-credential-issuer.schema.go @@ -53,10 +53,10 @@ const CryptographicBindingMethodsSupportedElemDidEbsi CryptographicBindingMethod const CryptographicBindingMethodsSupportedElemDidJwk CryptographicBindingMethodsSupportedElem = "did:jwk" const CryptographicBindingMethodsSupportedElemDidKey CryptographicBindingMethodsSupportedElem = "did:key" const CryptographicBindingMethodsSupportedElemDidWeb CryptographicBindingMethodsSupportedElem = "did:web" -const CryptographicBindingMethodsSupportedElemJwk CryptographicBindingMethodsSupportedElem = "jwk" +const CryptographicBindingMethodsSupportedElemJWK CryptographicBindingMethodsSupportedElem = "JWK" var enumValues_CryptographicBindingMethodsSupportedElem = []interface{}{ - "jwk", + "JWK", "did", "did:web", "did:ebsi", diff --git a/schemas/openid-credential-issuer.schema.json b/schemas/openid-credential-issuer.schema.json index 9df3578..9e42825 100644 --- a/schemas/openid-credential-issuer.schema.json +++ b/schemas/openid-credential-issuer.schema.json @@ -133,7 +133,7 @@ "items": { "type": "string", "enum": [ - "jwk", + "JWK", "did", "did:web", "did:ebsi", From 1fbd746ce88f882f45edf9fe5e8a0c9792676d4a Mon Sep 17 00:00:00 2001 From: FilippoTrotter Date: Wed, 22 Jan 2025 10:52:55 +0100 Subject: [PATCH 5/6] fix: restore jwk in well-known schema --- pkg/metadata/openid-credential-issuer.schema.go | 2 ++ schemas/openid-credential-issuer.schema.json | 1 + 2 files changed, 3 insertions(+) diff --git a/pkg/metadata/openid-credential-issuer.schema.go b/pkg/metadata/openid-credential-issuer.schema.go index b484d46..41ea3bf 100644 --- a/pkg/metadata/openid-credential-issuer.schema.go +++ b/pkg/metadata/openid-credential-issuer.schema.go @@ -54,9 +54,11 @@ const CryptographicBindingMethodsSupportedElemDidJwk CryptographicBindingMethods const CryptographicBindingMethodsSupportedElemDidKey CryptographicBindingMethodsSupportedElem = "did:key" const CryptographicBindingMethodsSupportedElemDidWeb CryptographicBindingMethodsSupportedElem = "did:web" const CryptographicBindingMethodsSupportedElemJWK CryptographicBindingMethodsSupportedElem = "JWK" +const CryptographicBindingMethodsSupportedElemJwk CryptographicBindingMethodsSupportedElem = "jwk" var enumValues_CryptographicBindingMethodsSupportedElem = []interface{}{ "JWK", + "jwk", "did", "did:web", "did:ebsi", diff --git a/schemas/openid-credential-issuer.schema.json b/schemas/openid-credential-issuer.schema.json index 9e42825..3855a15 100644 --- a/schemas/openid-credential-issuer.schema.json +++ b/schemas/openid-credential-issuer.schema.json @@ -134,6 +134,7 @@ "type": "string", "enum": [ "JWK", + "jwk", "did", "did:web", "did:ebsi", From 26f2d983485a115d67c68c398fa6146a7333db6b Mon Sep 17 00:00:00 2001 From: FilippoTrotter Date: Wed, 22 Jan 2025 11:16:29 +0100 Subject: [PATCH 6/6] add some tests --- pkg/metadata/metadata_test.go | 227 ++++++++++++++++++++++++++++++++++ 1 file changed, 227 insertions(+) create mode 100644 pkg/metadata/metadata_test.go diff --git a/pkg/metadata/metadata_test.go b/pkg/metadata/metadata_test.go new file mode 100644 index 0000000..657c0de --- /dev/null +++ b/pkg/metadata/metadata_test.go @@ -0,0 +1,227 @@ +package metadata + +import ( + "fmt" + "net/http" + "net/http/httptest" + "strings" + "testing" +) + +// TestFetchURL tests the FetchURL function with various scenarios. +func TestFetchURL(t *testing.T) { + tests := []struct { + name string + serverResponse string + statusCode int + expectedError string + }{ + { + name: "Valid Metadata Response", + serverResponse: `{ + "credential_configurations_supported": { + "pid_jwt_vc_json": { + "credential_signing_alg_values_supported": [ + "EdDSA", + "ES256", + "ES256K", + "RSA" + ], + "cryptographic_binding_methods_supported": [ + "did:ebsi", + "did:web", + "did:jwk" + ], + "display": [ + { + "name": "test ID" + }, + { + "locale": "nl-NL", + "name": "Test ID" + }, + { + "locale": "en-GB", + "name": "Personal ID" + } + ], + "format": "jwt_vc_json" + }, + "pid_vc+sd-jwt": { + "credential_signing_alg_values_supported": [ + "EdDSA", + "ES256", + "ES256K", + "RSA" + ], + "cryptographic_binding_methods_supported": [ + "did:ebsi", + "did:web", + "did:jwk" + ], + "display": [ + { + "name": "Test ID" + }, + { + "locale": "nl-NL", + "name": "Test ID" + }, + { + "locale": "en-GB", + "name": "Personal ID" + } + ], + "format": "vc+sd-jwt" + } + }, + "credential_endpoint": "https://example/credential", + "credential_issuer": "https://example.org", + "deferred_credential_endpoint": "https://example.org/credential_deferred", + "display": [ + { + "logo": { + "alt_text": "example logo", + "uri": "https://example/card_logo.png" + }, + "name": "example B.V." + }, + { + "locale": "nl-NL", + "logo": { + "alt_text": "example logo", + "uri": "https://example/card_logo.png" + }, + "name": "example B.V." + }, + { + "locale": "en-US", + "logo": { + "alt_text": "example logo", + "uri": "https://example/card_logo.png" + }, + "name": "example B.V." + } + ] +}`, + statusCode: http.StatusOK, + expectedError: "", + }, + { + name: "Invalid JSON Response", + serverResponse: `{"issuer": "https://example.com", "name": `, + statusCode: http.StatusOK, + expectedError: "failed to parse JSON", + }, + { + name: "Not conformant to JSON schema", + serverResponse: `{ + "credential_issuer": "https://dev.issuer1.forkbomb.eu/credential_issuer", + "credential_endpoint": "https://dev.issuer1.forkbomb.eu/credential_issuer/credential", + "authorization_servers": [ + "https://dev.authz-server1.forkbomb.eu/authz_server" + ], + "display": [ + { + "name": "Forkbomb Test Issuer", + "locale": "en-US" + } + ], + "jwks": { + "keys": [ + { + "kid": "did:dyne:sandbox.genericissuer:3suepGGjNHJmGDBebsCmapkdfBfXwFZzEQcEAMu7EdwA#es256_public_key", + "crv": "P-256", + "alg": "ES256", + "kty": "EC" + } + ] + }, + "credential_configurations_supported": { + "discount_from_voucher": { + "format": "vc+sd-jwt", + "cryptographic_binding_methods_supported": [ + "jwk", + "did:dyne:sandbox.signroom" + ], + "credential_signing_alg_values_supported": [ + "ES256" + ], + "proof_types_supported": { + "jwt": { + "proof_signing_alg_values_supported": [ + "ES256" + ] + } + }, + "display": [ + { + "name": "Get discount from Voucher dev", + "locale": "en-US", + "logo": { + "url": "https://avatars.githubusercontent.com/u/96812851?s=200&v=4", + "alt_text": "Get discount from Voucher dev logo", + "uri": "https://avatars.githubusercontent.com/u/96812851?s=200&v=4" + }, + "background_color": "#12107c", + "text_color": "#FFFFFF", + "description": "Get a special discount for all plans of DIDroom! Enter your voucher and get a discount credential." + } + ], + "vct": "discount_from_voucher", + "claims": { + "has_discount_from_voucher": { + "mandatory": true, + "display": [ + { + "locale": "en-US", + "name": "Has a discount from Voucher" + } + ] + } + } + } + } +}`, + statusCode: http.StatusOK, + expectedError: `failed to parse JSON: invalid value (expected one of []interface {}{"JWK", "jwk", "did", "did:web", "did:ebsi", "did:jwk", "did:dyne", "did:key", "cose_key"}): "did:dyne:sandbox.signroom"`, + }, + { + name: "Non-200 Status Code", + serverResponse: "", + statusCode: http.StatusNotFound, + expectedError: "not a credential issuer", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Create a test server + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.URL.Path != credentialIssuerEndpoint { + http.NotFound(w, r) + return + } + w.WriteHeader(tt.statusCode) + fmt.Fprint(w, tt.serverResponse) + })) + defer server.Close() + + // Call FetchURL + metadata, err := FetchURL(server.URL) + + // Validate result + if tt.expectedError == "" && err != nil { + t.Fatalf("unexpected error: %v", err) + } + + if tt.expectedError != "" && (err == nil || !strings.Contains(err.Error(), tt.expectedError)) { + t.Fatalf("expected error containing '%s', got: %v", tt.expectedError, err) + } + + if tt.expectedError == "" && metadata == nil { + t.Fatalf("expected valid metadata, got nil") + } + }) + } +}