Skip to content

Commit

Permalink
feat: attachment proof (#1740)
Browse files Browse the repository at this point in the history
* feat: more work on attachments

* feat: proof test

* fix: get

* fix: lint

* fix: test

* feat: data url

* fix: test data

* feat: ensure hash correct
  • Loading branch information
skynet2 authored Jul 4, 2024
1 parent 238e801 commit b7a9286
Show file tree
Hide file tree
Showing 19 changed files with 603 additions and 92 deletions.
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

0 comments on commit b7a9286

Please sign in to comment.