Skip to content

Commit

Permalink
feat(sdk): support for oid4vp ID2 version (#782)
Browse files Browse the repository at this point in the history
Signed-off-by: Andrii Holovko <[email protected]>
  • Loading branch information
aholovko authored Jul 23, 2024
1 parent ef12d2c commit 1628cb6
Show file tree
Hide file tree
Showing 19 changed files with 201 additions and 140 deletions.
2 changes: 1 addition & 1 deletion cmd/wallet-sdk-gomobile/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ require (
github.com/stretchr/testify v1.8.2
github.com/trustbloc/did-go v1.2.1
github.com/trustbloc/kms-go v1.1.2
github.com/trustbloc/vc-go v1.1.2
github.com/trustbloc/vc-go v1.1.3-0.20240627155354-563ff5d6e098
github.com/trustbloc/wallet-sdk v0.0.0-00010101000000-000000000000
)

Expand Down
4 changes: 2 additions & 2 deletions cmd/wallet-sdk-gomobile/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -157,8 +157,8 @@ github.com/trustbloc/kms-go v1.1.2 h1:nAlhDoHkSyX1eQFRz/sJsdgmJuNadyX7FJEy/9ROwy
github.com/trustbloc/kms-go v1.1.2/go.mod h1:OKOtsLbE6W5s4mpjWkvk8XEqcmt9vTgVmDNkHELpWO0=
github.com/trustbloc/sidetree-go v1.1.0 h1:ZNCtYTut5MHVXJR26FvOPSo8uCGDR0YTNeA155s/QIo=
github.com/trustbloc/sidetree-go v1.1.0/go.mod h1:IQ1iX/gLe/YL+M6kzenc5Oi14uzaYqfL7KgMyNuSGvI=
github.com/trustbloc/vc-go v1.1.2 h1:P2s0Hcm1Rghvi4H9Us0RNq64RzTSlM9ZVd0ZcHTEwD0=
github.com/trustbloc/vc-go v1.1.2/go.mod h1:3TifHPFmaBqSY0SCFOzfj964aA8Xm2VmwY3VnYP7SRg=
github.com/trustbloc/vc-go v1.1.3-0.20240627155354-563ff5d6e098 h1:LHfRWVMMAgErdHRCwINZ0uBSUJimjH0HYZqNONPbFQs=
github.com/trustbloc/vc-go v1.1.3-0.20240627155354-563ff5d6e098/go.mod h1:FXDDzwuwOUJKTLjX9K+XbotQrW5b5cSgcwm+OAFsZ7c=
github.com/veraison/go-cose v1.1.0 h1:AalPS4VGiKavpAzIlBjrn7bhqXiXi4jbMYY/2+UC+4o=
github.com/veraison/go-cose v1.1.0/go.mod h1:7ziE85vSq4ScFTg6wyoMXjucIGOf4JkFEZi/an96Ct4=
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
Expand Down
5 changes: 3 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,13 @@ require (
github.com/trustbloc/did-go v1.2.1
github.com/trustbloc/kms-go v1.1.2
github.com/trustbloc/sidetree-go v1.1.0
github.com/trustbloc/vc-go v1.1.2
github.com/trustbloc/vc-go v1.1.3-0.20240627155354-563ff5d6e098
golang.org/x/oauth2 v0.13.0
)

require (
github.com/IBM/mathlib v0.0.3-0.20231011094432-44ee0eb539da // indirect
github.com/PaesslerAG/gval v1.1.0 // indirect
github.com/PaesslerAG/gval v1.2.2 // indirect
github.com/VictoriaMetrics/fastcache v1.5.7 // indirect
github.com/bits-and-blooms/bitset v1.7.0 // indirect
github.com/btcsuite/btcd/btcec/v2 v2.1.3 // indirect
Expand Down Expand Up @@ -55,6 +55,7 @@ require (
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/pquerna/cachecontrol v0.1.0 // indirect
github.com/shopspring/decimal v1.3.1 // indirect
github.com/spaolacci/murmur3 v1.1.0 // indirect
github.com/teserakt-io/golang-ed25519 v0.0.0-20210104091850-3888c087a4c8 // indirect
github.com/tidwall/gjson v1.14.3 // indirect
Expand Down
10 changes: 6 additions & 4 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
github.com/IBM/mathlib v0.0.3-0.20231011094432-44ee0eb539da h1:qqGozq4tF6EOVnWoTgBoJGudRKKZXSAYnEtDggzTnsw=
github.com/IBM/mathlib v0.0.3-0.20231011094432-44ee0eb539da/go.mod h1:Tco9QzE3fQzjMS7nPbHDeFfydAzctStf1Pa8hsh6Hjs=
github.com/PaesslerAG/gval v1.0.0/go.mod h1:y/nm5yEyTeX6av0OfKJNp9rBNj2XrGhAf5+v24IBN1I=
github.com/PaesslerAG/gval v1.1.0 h1:k3RuxeZDO3eejD4cMPSt+74tUSvTnbGvLx0df4mdwFc=
github.com/PaesslerAG/gval v1.1.0/go.mod h1:y/nm5yEyTeX6av0OfKJNp9rBNj2XrGhAf5+v24IBN1I=
github.com/PaesslerAG/gval v1.2.2 h1:Y7iBzhgE09IGTt5QgGQ2IdaYYYOU134YGHBThD+wm9E=
github.com/PaesslerAG/gval v1.2.2/go.mod h1:XRFLwvmkTEdYziLdaCeCa5ImcGVrfQbeNUbVR+C6xac=
github.com/PaesslerAG/jsonpath v0.1.0/go.mod h1:4BzmtoM/PI8fPO4aQGIusjGxGir2BzcV0grWtFzq1Y8=
github.com/PaesslerAG/jsonpath v0.1.1 h1:c1/AToHQMVsduPAa4Vh6xp2U0evy4t8SWp8imEsylIk=
github.com/PaesslerAG/jsonpath v0.1.1/go.mod h1:lVboNxFGal/VwW6d9JzIy56bUsYAP6tH/x80vjnCseY=
Expand Down Expand Up @@ -120,6 +120,8 @@ github.com/pquerna/cachecontrol v0.1.0 h1:yJMy84ti9h/+OEWa752kBTKv4XC30OtVVHYv/8
github.com/pquerna/cachecontrol v0.1.0/go.mod h1:NrUG3Z7Rdu85UNR3vm7SOsl1nFIeSiQnrHV5K9mBcUI=
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/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8=
github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI=
github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
Expand Down Expand Up @@ -152,8 +154,8 @@ github.com/trustbloc/kms-go v1.1.2 h1:nAlhDoHkSyX1eQFRz/sJsdgmJuNadyX7FJEy/9ROwy
github.com/trustbloc/kms-go v1.1.2/go.mod h1:OKOtsLbE6W5s4mpjWkvk8XEqcmt9vTgVmDNkHELpWO0=
github.com/trustbloc/sidetree-go v1.1.0 h1:ZNCtYTut5MHVXJR26FvOPSo8uCGDR0YTNeA155s/QIo=
github.com/trustbloc/sidetree-go v1.1.0/go.mod h1:IQ1iX/gLe/YL+M6kzenc5Oi14uzaYqfL7KgMyNuSGvI=
github.com/trustbloc/vc-go v1.1.2 h1:P2s0Hcm1Rghvi4H9Us0RNq64RzTSlM9ZVd0ZcHTEwD0=
github.com/trustbloc/vc-go v1.1.2/go.mod h1:3TifHPFmaBqSY0SCFOzfj964aA8Xm2VmwY3VnYP7SRg=
github.com/trustbloc/vc-go v1.1.3-0.20240627155354-563ff5d6e098 h1:LHfRWVMMAgErdHRCwINZ0uBSUJimjH0HYZqNONPbFQs=
github.com/trustbloc/vc-go v1.1.3-0.20240627155354-563ff5d6e098/go.mod h1:FXDDzwuwOUJKTLjX9K+XbotQrW5b5cSgcwm+OAFsZ7c=
github.com/veraison/go-cose v1.1.0 h1:AalPS4VGiKavpAzIlBjrn7bhqXiXi4jbMYY/2+UC+4o=
github.com/veraison/go-cose v1.1.0/go.mod h1:7ziE85vSq4ScFTg6wyoMXjucIGOf4JkFEZi/an96Ct4=
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
Expand Down
134 changes: 98 additions & 36 deletions pkg/openid4vp/openid4vp.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ package openid4vp
import (
"bytes"
"crypto/rand"
"encoding/gob"
"encoding/json"
"errors"
"fmt"
Expand Down Expand Up @@ -83,9 +84,10 @@ type Interaction struct {
}

type authorizedResponse struct {
IDTokenJWS string
VPTokenJWS string
State string
IDTokenJWS string
VPTokenJWS string
PresentationSubmission string
State string
}

// NewInteraction creates a new OpenID4VP interaction object.
Expand Down Expand Up @@ -113,7 +115,7 @@ func NewInteraction(
rawRequestObject = authorizationRequest
}

requestObject, err := verifyRequestObjectAndDecodeClaims(rawRequestObject, signatureVerifier)
reqObject, err := verifyRequestObjectAndDecodeClaims(rawRequestObject, signatureVerifier)
if err != nil {
return nil, walleterror.NewValidationError(
ErrorModule,
Expand All @@ -123,7 +125,7 @@ func NewInteraction(
}

return &Interaction{
requestObject: requestObject,
requestObject: reqObject,
httpClient: client,
activityLogger: activityLogger,
metricsLogger: metricsLogger,
Expand All @@ -136,7 +138,7 @@ func NewInteraction(

// GetQuery creates query based on authorization request data.
func (o *Interaction) GetQuery() *presexch.PresentationDefinition {
return o.requestObject.Claims.VPToken.PresentationDefinition
return o.requestObject.PresentationDefinition
}

// CustomScope returns vp integration scope.
Expand All @@ -156,9 +158,9 @@ func (o *Interaction) CustomScope() []string {
func (o *Interaction) VerifierDisplayData() *VerifierDisplayData {
return &VerifierDisplayData{
DID: o.requestObject.ClientID,
Name: o.requestObject.Registration.ClientName,
Purpose: o.requestObject.Registration.ClientPurpose,
LogoURI: o.requestObject.Registration.ClientLogoURI,
Name: o.requestObject.ClientMetadata.ClientName,
Purpose: o.requestObject.ClientMetadata.ClientPurpose,
LogoURI: o.requestObject.ClientMetadata.ClientLogoURI,
}
}

Expand Down Expand Up @@ -264,6 +266,7 @@ func (o *Interaction) presentCredentials(
data := url.Values{}
data.Set("id_token", response.IDTokenJWS)
data.Set("vp_token", response.VPTokenJWS)
data.Set("presentation_submission", response.PresentationSubmission)
data.Set("state", response.State)

err = o.sendAuthorizedResponse(data.Encode())
Expand All @@ -284,15 +287,15 @@ func (o *Interaction) presentCredentials(
Type: api.LogTypeCredentialActivity,
Time: time.Now(),
Data: api.Data{
Client: o.requestObject.Registration.ClientName,
Client: o.requestObject.ClientMetadata.ClientName,
Operation: activityLogOperation,
Status: api.ActivityLogStatusSuccess,
},
})
}

func (o *Interaction) PresentedClaims(credential *verifiable.Credential) (interface{}, error) {
pd := o.requestObject.Claims.VPToken.PresentationDefinition
pd := o.requestObject.PresentationDefinition

bbsProofCreator := &verifiable.BBSProofCreator{
ProofDerivation: bbs12381g2pub.New(),
Expand Down Expand Up @@ -324,9 +327,9 @@ func (o *Interaction) PresentedClaims(credential *verifiable.Credential) (interf

func (o *Interaction) sendAuthorizedResponse(responseBody string) error {
_, err := httprequest.New(o.httpClient, o.metricsLogger).Do(http.MethodPost,
o.requestObject.RedirectURI, "application/x-www-form-urlencoded",
o.requestObject.ResponseURI, "application/x-www-form-urlencoded",
bytes.NewBufferString(responseBody),
fmt.Sprintf(sendAuthorizedResponseEventText, o.requestObject.RedirectURI),
fmt.Sprintf(sendAuthorizedResponseEventText, o.requestObject.ResponseURI),
presentCredentialEventText, processAuthorizationErrorResponse)

return err
Expand Down Expand Up @@ -371,14 +374,29 @@ func verifyRequestObjectAndDecodeClaims(
rawRequestObject string,
signatureVerifier jwt.ProofChecker,
) (*requestObject, error) {
requestObject := &requestObject{}
reqObject := &requestObject{}

err := verifyTokenSignatureAndDecodeClaims(rawRequestObject, requestObject, signatureVerifier)
err := verifyTokenSignatureAndDecodeClaims(rawRequestObject, reqObject, signatureVerifier)
if err != nil {
return nil, err
}

return requestObject, nil
// temporary solution for backward compatibility
if reqObject.PresentationDefinition == nil && reqObject.Claims.VPToken.PresentationDefinition != nil {
reqObject.PresentationDefinition = reqObject.Claims.VPToken.PresentationDefinition
}
if reqObject.ClientMetadata.VPFormats == nil && reqObject.Registration.VPFormats != nil {
reqObject.ClientMetadata.ClientName = reqObject.Registration.ClientName
reqObject.ClientMetadata.ClientPurpose = reqObject.Registration.ClientPurpose
reqObject.ClientMetadata.ClientLogoURI = reqObject.Registration.LogoURI
reqObject.ClientMetadata.VPFormats = reqObject.Registration.VPFormats
reqObject.ClientMetadata.SubjectSyntaxTypesSupported = reqObject.Registration.SubjectSyntaxTypesSupported
}
if reqObject.ResponseURI == "" && reqObject.RedirectURI != "" {
reqObject.ResponseURI = reqObject.RedirectURI
}

return reqObject, nil
}

func verifyTokenSignatureAndDecodeClaims(rawJwt string, claims interface{}, proofChecker jwt.ProofChecker) error {
Expand Down Expand Up @@ -428,7 +446,7 @@ func createAuthorizedResponseOneCred( //nolint:funlen,gocyclo // Unable to decom
documentLoader ld.DocumentLoader,
opts *presentOpts,
) (*authorizedResponse, error) {
pd := requestObject.Claims.VPToken.PresentationDefinition
pd := requestObject.PresentationDefinition

if opts != nil && opts.ignoreConstraints {
for i := range pd.InputDescriptors {
Expand All @@ -450,7 +468,7 @@ func createAuthorizedResponseOneCred( //nolint:funlen,gocyclo // Unable to decom
verifiable.WithJSONLDDocumentLoader(documentLoader),
verifiable.WithProofChecker(defaults.NewDefaultProofChecker(common.NewVDRKeyResolver(didResolver))),
),
presexch.WithDefaultPresentationFormat("jwt_vp"),
presexch.WithDefaultPresentationFormat(presexch.FormatJWTVP),
)
if err != nil {
return nil, err
Expand Down Expand Up @@ -488,10 +506,6 @@ func createAuthorizedResponseOneCred( //nolint:funlen,gocyclo // Unable to decom
return nil, err
}

presentationSubmission := presentation.CustomFields["presentation_submission"]

presentation.CustomFields["presentation_submission"] = nil

var attestationVP string
if opts != nil && opts.attestationVC != "" {
attestationVP, err = createAttestationVP(
Expand All @@ -501,7 +515,10 @@ func createAuthorizedResponseOneCred( //nolint:funlen,gocyclo // Unable to decom
}
}

idTokenJWS, err := createIDToken(requestObject, presentationSubmission, did, customClaims, jwtSigner, attestationVP)
presentationSubmission := presentation.CustomFields["presentation_submission"]
presentation.CustomFields["presentation_submission"] = nil

idTokenJWS, err := createIDToken(requestObject, did, customClaims, jwtSigner, attestationVP, presentationSubmission)
if err != nil {
return nil, err
}
Expand All @@ -522,7 +539,17 @@ func createAuthorizedResponseOneCred( //nolint:funlen,gocyclo // Unable to decom
return nil, fmt.Errorf("sign vp_token: %w", err)
}

return &authorizedResponse{IDTokenJWS: idTokenJWS, VPTokenJWS: vpTokenJWS, State: requestObject.State}, nil
presentationSubmissionBytes, err := json.Marshal(presentationSubmission)
if err != nil {
return nil, fmt.Errorf("marshal presentation submission: %w", err)
}

return &authorizedResponse{
IDTokenJWS: idTokenJWS,
VPTokenJWS: vpTokenJWS,
PresentationSubmission: string(presentationSubmissionBytes),
State: requestObject.State,
}, nil
}

func createAuthorizedResponseMultiCred( //nolint:funlen,gocyclo // Unable to decompose without a major reworking
Expand All @@ -535,22 +562,22 @@ func createAuthorizedResponseMultiCred( //nolint:funlen,gocyclo // Unable to dec
signer wrapperapi.KMSCryptoSigner,
opts *presentOpts,
) (*authorizedResponse, error) {
pd := requestObject.Claims.VPToken.PresentationDefinition
pd := requestObject.PresentationDefinition

bbsProofCreator := &verifiable.BBSProofCreator{
ProofDerivation: bbs12381g2pub.New(),
VerificationMethodResolver: common.NewVDRKeyResolver(didResolver),
}

presentations, submission, err := pd.CreateVPArray(
presentations, presentationSubmission, err := pd.CreateVPArray(
credentials,
documentLoader,
presexch.WithSDBBSProofCreator(bbsProofCreator),
presexch.WithSDCredentialOptions(
verifiable.WithDisabledProofCheck(),
verifiable.WithJSONLDDocumentLoader(documentLoader),
),
presexch.WithDefaultPresentationFormat("jwt_vp"),
presexch.WithDefaultPresentationFormat(presexch.FormatJWTVP),
)
if err != nil {
return nil, err
Expand Down Expand Up @@ -630,16 +657,22 @@ func createAuthorizedResponseMultiCred( //nolint:funlen,gocyclo // Unable to dec
}
}

idTokenJWS, err := createIDToken(requestObject, submission, idTokenSigningDID,
customClaims, signers[idTokenSigningDID], attestationVP)
idTokenJWS, err := createIDToken(requestObject, idTokenSigningDID, customClaims,
signers[idTokenSigningDID], attestationVP, presentationSubmission)
if err != nil {
return nil, err
}

presentationSubmissionJSON, err := json.Marshal(presentationSubmission)
if err != nil {
return nil, fmt.Errorf("marshal presentation submission: %w", err)
}

return &authorizedResponse{
IDTokenJWS: idTokenJWS,
VPTokenJWS: string(vpTokenListJSON),
State: requestObject.State,
IDTokenJWS: idTokenJWS,
VPTokenJWS: string(vpTokenListJSON),
PresentationSubmission: string(presentationSubmissionJSON),
State: requestObject.State,
}, nil
}

Expand Down Expand Up @@ -672,16 +705,13 @@ func addDataIntegrityProof(did string, didResolver api.DIDResolver, documentLoad

func createIDToken(
req *requestObject,
submission interface{},
signingDID string,
customClaims CustomClaims,
signer api.JWTSigner,
attestationVP string,
presentationSubmission interface{},
) (string, error) {
idToken := &idTokenClaims{
VPToken: idTokenVPToken{
PresentationSubmission: submission,
},
Scope: customClaims.ScopeClaims,
AttestationVP: attestationVP,
Nonce: req.Nonce,
Expand All @@ -694,6 +724,28 @@ func createIDToken(
Jti: uuid.NewString(),
}

if ps, ok := presentationSubmission.(*presexch.PresentationSubmission); ok {
var psCopy presexch.PresentationSubmission

if err := deepCopy(ps, &psCopy); err != nil {
return "", fmt.Errorf("deep copy presentation submission: %w", err)
}

for _, descriptor := range psCopy.DescriptorMap {
if descriptor.PathNested != nil {
descriptor.PathNested.Path = strings.Replace(descriptor.PathNested.Path, "$.vp.", "$.", 1)
}
}

idToken.VPToken = idTokenVPToken{
PresentationSubmission: psCopy,
}
} else {
idToken.VPToken = idTokenVPToken{
PresentationSubmission: presentationSubmission,
}
}

idTokenJWS, err := signToken(idToken, signer)
if err != nil {
return "", fmt.Errorf("sign id_token: %w", err)
Expand All @@ -702,6 +754,16 @@ func createIDToken(
return idTokenJWS, nil
}

func deepCopy(src, dst interface{}) error {
var b bytes.Buffer

if err := gob.NewEncoder(&b).Encode(src); err != nil {
return err
}

return gob.NewDecoder(bytes.NewBuffer(b.Bytes())).Decode(dst)
}

func createAttestationVP(
attestationVCData string,
attestationVPSigner api.JWTSigner,
Expand Down
Loading

0 comments on commit 1628cb6

Please sign in to comment.