Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: attachment proof #1740

Merged
merged 8 commits into from
Jul 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 17 additions & 3 deletions component/wallet-cli/cmd/oidc4vp_cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,15 @@ package cmd

import (
"context"
"encoding/json"
"fmt"
"github.com/trustbloc/vcs/component/wallet-cli/pkg/attestation"
"github.com/trustbloc/vcs/component/wallet-cli/pkg/trustregistry"
"net/http"
"net/url"
"strings"

"github.com/trustbloc/vcs/component/wallet-cli/pkg/attestation"
"github.com/trustbloc/vcs/component/wallet-cli/pkg/trustregistry"

"github.com/henvic/httpretty"
"github.com/piprate/json-gold/ld"
"github.com/spf13/cobra"
Expand All @@ -38,6 +40,7 @@ type oidc4vpCommandFlags struct {
trustRegistryHost string
attestationURL string
proxyURL string
attachments string
}

// NewOIDC4VPCommand returns a new command for running OIDC4VP flow.
Expand Down Expand Up @@ -87,7 +90,6 @@ func NewOIDC4VPCommand() *cobra.Command {

httpClient.Transport = httpLogger.RoundTripper(httpClient.Transport)
}

var walletDIDIndex int

if flags.walletDIDIndex != -1 {
Expand Down Expand Up @@ -150,6 +152,16 @@ func NewOIDC4VPCommand() *cobra.Command {
oidc4vp.WithWalletDIDIndex(walletDIDIndex),
}

if flags.attachments != "" {
targetAttachments := map[string]string{}

if err = json.Unmarshal([]byte(flags.attachments), &targetAttachments); err != nil {
return fmt.Errorf("can not unmarshal attachments: %w", err)
}

opts = append(opts, oidc4vp.WithAttachments(targetAttachments))
}

if flags.enableLinkedDomainVerification {
opts = append(opts, oidc4vp.WithLinkedDomainVerification())
}
Expand Down Expand Up @@ -186,6 +198,8 @@ func createFlags(cmd *cobra.Command, flags *oidc4vpCommandFlags) {
cmd.Flags().IntVar(&flags.walletDIDIndex, "wallet-did-index", -1, "index of wallet did, if not set the most recently created DID is used")
cmd.Flags().StringVar(&flags.attestationURL, "attestation-url", "", "attestation url, i.e. https://<host>/vcs/wallet/attestation")
cmd.Flags().StringVar(&flags.trustRegistryHost, "trust-registry-host", "", "trust registry host, i.e. https://<host>/trustregistry")
cmd.Flags().StringVar(&flags.attachments, "attachments", "",
`list of attachment. json expected. example {"some_id" : "data:image/svg;base64,YmFzZTY0Y29udGVudC1odHRwczovL2xvY2FsaG9zdC9jYXQucG5n"}`)

cmd.Flags().BoolVar(&flags.enableTracing, "enable-tracing", false, "enables http tracing")
cmd.Flags().StringVar(&flags.proxyURL, "proxy-url", "", "proxy url for http client")
Expand Down
1 change: 1 addition & 0 deletions component/wallet-cli/pkg/oidc4vp/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ type IDTokenClaims struct {
Nbf int64 `json:"nbf"`
Iat int64 `json:"iat"`
Jti string `json:"jti"`
Attachments map[string]string `json:"_attachments"`
}

type VPTokenClaims struct {
Expand Down
20 changes: 18 additions & 2 deletions component/wallet-cli/pkg/oidc4vp/oidc4vp_flow.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ type Flow struct {
disableSchemaValidation bool
perfInfo *PerfInfo
useMultiVPs bool
attachments map[string]string
}

type provider interface {
Expand Down Expand Up @@ -153,6 +154,7 @@ func NewFlow(p provider, opts ...Opt) (*Flow, error) {
disableSchemaValidation: o.disableSchemaValidation,
useMultiVPs: o.useMultiVPs,
perfInfo: &PerfInfo{},
attachments: o.attachments,
}, nil
}

Expand Down Expand Up @@ -439,8 +441,11 @@ func (f *Flow) sendAuthorizationResponse(

idToken, err := f.createIDToken(
ctx,
requestObject.ClientID, requestObject.Nonce, requestObject.Scope,
requestObject.ClientID,
requestObject.Nonce,
requestObject.Scope,
attestationRequired,
f.attachments,
)
if err != nil {
return fmt.Errorf("create id token: %w", err)
Expand Down Expand Up @@ -645,8 +650,11 @@ func (f *Flow) signPresentationLDP(

func (f *Flow) createIDToken(
ctx context.Context,
clientID, nonce, requestObjectScope string,
clientID string,
nonce string,
requestObjectScope string,
attestationRequired bool,
attachments map[string]string,
) (string, error) {
scopeAdditionalClaims, err := extractCustomScopeClaims(requestObjectScope)
if err != nil {
Expand All @@ -663,6 +671,7 @@ func (f *Flow) createIDToken(
Nbf: time.Now().Unix(),
Iat: time.Now().Unix(),
Jti: uuid.NewString(),
Attachments: attachments,
}

if attestationRequired {
Expand Down Expand Up @@ -782,6 +791,7 @@ type options struct {
disableDomainMatching bool
disableSchemaValidation bool
useMultiVPs bool
attachments map[string]string
}

type Opt func(opts *options)
Expand All @@ -792,6 +802,12 @@ func WithWalletDIDIndex(idx int) Opt {
}
}

func WithAttachments(attachments map[string]string) func(opts *options) {
return func(opts *options) {
opts.attachments = attachments
}
}

func WithRequestURI(uri string) Opt {
return func(opts *options) {
opts.requestURI = uri
Expand Down
13 changes: 13 additions & 0 deletions pkg/restapi/v1/verifier/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ type IDTokenClaims struct {
Nonce string `json:"nonce"`
Aud string `json:"aud"`
Exp int64 `json:"exp"`
Attachments map[string]string `json:"_attachments"`
}

type VPTokenClaims struct {
Expand Down Expand Up @@ -629,6 +630,7 @@ func (c *Controller) verifyAuthorizationResponseTokens(
CustomScopeClaims: idTokenClaims.CustomScopeClaims,
VPTokens: processedVPTokens,
AttestationVP: idTokenClaims.AttestationVP,
Attachments: idTokenClaims.Attachments,
}, nil
}

Expand Down Expand Up @@ -684,6 +686,17 @@ func validateIDToken(
Nonce: string(v.GetStringBytes("nonce")),
Aud: string(v.GetStringBytes("aud")),
Exp: v.GetInt64("exp"),
Attachments: map[string]string{},
}

if val := v.Get("_attachments"); val != nil {
o, _ := val.Object() //nolint

if o != nil {
o.Visit(func(k []byte, v *fastjson.Value) {
idTokenClaims.Attachments[string(k)] = string(v.GetStringBytes())
})
}
}

if idTokenClaims.Exp < time.Now().Unix() {
Expand Down
72 changes: 71 additions & 1 deletion pkg/restapi/v1/verifier/controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ import (
"github.com/trustbloc/kms-go/spi/kms"
"github.com/trustbloc/vc-go/presexch"
"github.com/trustbloc/vc-go/verifiable"
nooptracer "go.opentelemetry.io/otel/trace/noop"

vcsverifiable "github.com/trustbloc/vcs/pkg/doc/verifiable"
"github.com/trustbloc/vcs/pkg/event/spi"
"github.com/trustbloc/vcs/pkg/internal/testutil"
Expand All @@ -39,7 +41,6 @@ import (
"github.com/trustbloc/vcs/pkg/service/oidc4vp"
"github.com/trustbloc/vcs/pkg/service/verifycredential"
"github.com/trustbloc/vcs/pkg/service/verifypresentation"
nooptracer "go.opentelemetry.io/otel/trace/noop"
)

const (
Expand Down Expand Up @@ -643,6 +644,75 @@ func TestController_CheckAuthorizationResponse(t *testing.T) {
require.Contains(t, authorisationResponseParsed.VPTokens[0].Presentation.Type, "PresentationSubmission")
})

t.Run("Success LDP With Attachments", func(t *testing.T) {
signedClaimsJWTResult := testutil.SignedClaimsJWT(t,
&IDTokenClaims{
Nonce: validNonce,
Aud: validAud,
Exp: time.Now().Unix() + 1000,
Attachments: map[string]string{
"id1": "data:image/svg;base64,YmFzZTY0Y29udGVudC1odHRwczovL2xvY2FsaG9zdC9jYXQucG5n",
},
},
)

vpSigned := testutil.SignedVPWithExistingPrivateKey(t,
&verifiable.Presentation{
Context: []string{
"https://www.w3.org/2018/credentials/v1",
"https://identity.foundation/presentation-exchange/submission/v1",
"https://w3id.org/security/suites/jws-2020/v1",
},
Type: []string{
"VerifiablePresentation",
"PresentationSubmission",
},
},
vcsverifiable.Ldp,
signedClaimsJWTResult.VerMethodDIDKeyID,
signedClaimsJWTResult.KeyType,
signedClaimsJWTResult.Signer,
func(ldpc *verifiable.LinkedDataProofContext) {
ldpc.Domain = validAud
ldpc.Challenge = validNonce
})

vpToken, err := vpSigned.MarshalJSON()
require.NoError(t, err)

presentationSubmission, err := json.Marshal(map[string]interface{}{})
require.NoError(t, err)

mockEventSvc := NewMockeventService(gomock.NewController(t))
mockEventSvc.EXPECT().Publish(gomock.Any(), spi.VerifierEventTopic, gomock.Any()).Times(0)

c := NewController(&Config{
OIDCVPService: svc,
EventSvc: mockEventSvc,
EventTopic: spi.VerifierEventTopic,
VDR: signedClaimsJWTResult.VDR,
DocumentLoader: testutil.DocumentLoader(t),
})

authorisationResponseParsed, err := c.verifyAuthorizationResponseTokens(context.TODO(),
&rawAuthorizationResponse{
IDToken: signedClaimsJWTResult.JWT,
VPToken: []string{string(vpToken)},
PresentationSubmission: string(presentationSubmission),
State: "txid",
},
)

require.NoError(t, err)

require.Nil(t, authorisationResponseParsed.CustomScopeClaims)
require.Contains(t, authorisationResponseParsed.VPTokens[0].Presentation.Type, "PresentationSubmission")

require.Len(t, authorisationResponseParsed.Attachments, 1)
require.EqualValues(t, "data:image/svg;base64,YmFzZTY0Y29udGVudC1odHRwczovL2xvY2FsaG9zdC9jYXQucG5n",
authorisationResponseParsed.Attachments["id1"])
})

t.Run("Success JWT ID1", func(t *testing.T) {
customScopeClaims := map[string]oidc4vp.Claims{
"customScope": {
Expand Down
18 changes: 13 additions & 5 deletions pkg/service/oidc4vp/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ type AuthorizationResponseParsed struct {
CustomScopeClaims map[string]Claims
VPTokens []*ProcessedVPToken
AttestationVP string
Attachments map[string]string // Attachments from IDToken for AttachmentEvidence type
}

type ProcessedVPToken struct {
Expand All @@ -52,10 +53,10 @@ type CredentialMetadata struct {
ExpirationDate *util.TimeWrapper `json:"expirationDate,omitempty"`
CustomClaims map[string]Claims `json:"customClaims,omitempty"`

Name interface{} `json:"name,omitempty"`
AwardedDate interface{} `json:"awardedDate,omitempty"`
Description interface{} `json:"description,omitempty"`
Attachments []map[string]interface{} `json:"attachments"`
Name interface{} `json:"name,omitempty"`
AwardedDate interface{} `json:"awardedDate,omitempty"`
Description interface{} `json:"description,omitempty"`
Attachments []*Attachment `json:"attachments"`
}

type ServiceInterface interface {
Expand Down Expand Up @@ -152,7 +153,14 @@ type ClientMetadata struct {
LogoURI string `json:"logo_uri"`
}

type Attachment struct {
type attachmentData struct {
Type string
Claim map[string]interface{}
}

type Attachment struct {
ID string `json:"id"`
DataURI string `json:"data_uri"`
Description string `json:"description"`
Error string `json:"error,omitempty"`
}
Loading
Loading