Skip to content

Commit

Permalink
Merge branch 'master' into feature/2504/vp_fo_s2s_flow
Browse files Browse the repository at this point in the history
  • Loading branch information
woutslakhorst authored Nov 10, 2023
2 parents 0977af2 + ec09115 commit 8d2706a
Show file tree
Hide file tree
Showing 83 changed files with 743 additions and 512 deletions.
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# golang alpine
FROM golang:1.21.3-alpine as builder
FROM golang:1.21.4-alpine as builder

ARG TARGETARCH
ARG TARGETOS
Expand Down
4 changes: 2 additions & 2 deletions api/ssi_types_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ func createDidDocument() did.Document {
}
verificationRelationship := did.VerificationRelationship{VerificationMethod: verificationMethod}
return did.Document{
Context: []ssi.URI{
Context: []interface{}{
ssi.MustParseURI("https://www.w3.org/ns/did/v1"),
ssi.MustParseURI("https://www.w3.org/ns/did/v2"),
},
Expand All @@ -172,7 +172,7 @@ func createDidDocument() did.Document {
KeyAgreement: did.VerificationRelationships{verificationRelationship},
VerificationMethod: did.VerificationMethods{verificationMethod},
Controller: []did.DID{did.MustParseDID("did:example:controller")},
ID: verificationMethod.ID,
ID: verificationMethod.ID.DID,
Service: []did.Service{
{
ID: ssi.MustParseURI("example"),
Expand Down
29 changes: 18 additions & 11 deletions auth/api/iam/openid4vp.go
Original file line number Diff line number Diff line change
Expand Up @@ -137,16 +137,18 @@ func (r *Wrapper) handlePresentationRequest(params map[string]string, session *S
}
}

// TODO: https://github.com/nuts-foundation/nuts-node/issues/2359
// TODO: What if multiple credentials of the same type match?
_, matchingCredentials, err := presentationDefinition.Match(credentials)
submissionBuilder := presentationDefinition.PresentationSubmissionBuilder()
submissionBuilder.AddWallet(session.OwnDID, ownCredentials)
_, signInstructions, err := submissionBuilder.Build("ldp_vp")
if err != nil {
return nil, fmt.Errorf("unable to match presentation definition: %w", err)
}
var credentialIDs []string
for _, matchingCredential := range matchingCredentials {
templateParams.Credentials = append(templateParams.Credentials, makeCredentialInfo(matchingCredential))
credentialIDs = append(credentialIDs, matchingCredential.ID.String())
for _, signInstruction := range signInstructions {
for _, matchingCredential := range signInstruction.VerifiableCredentials {
templateParams.Credentials = append(templateParams.Credentials, makeCredentialInfo(matchingCredential))
credentialIDs = append(credentialIDs, matchingCredential.ID.String())
}
}
session.ServerState["openid4vp_credentials"] = credentialIDs

Expand Down Expand Up @@ -205,16 +207,21 @@ func (r *Wrapper) handlePresentationRequestAccept(c echo.Context) error {
if presentationDefinition == nil {
return fmt.Errorf("unsupported scope for presentation exchange: %s", session.Scope)
}
// TODO: Options
// TODO: Options (including format)
resultParams := map[string]string{}
presentationSubmission, credentials, err := presentationDefinition.Match(credentials)
submissionBuilder := presentationDefinition.PresentationSubmissionBuilder()
submissionBuilder.AddWallet(session.OwnDID, credentials)
submission, signInstructions, err := submissionBuilder.Build("ldp_vp")
if err != nil {
// Matched earlier, shouldn't happen
return err
}
presentationSubmissionJSON, _ := json.Marshal(presentationSubmission)
presentationSubmissionJSON, _ := json.Marshal(submission)
resultParams[presentationSubmissionParam] = string(presentationSubmissionJSON)
verifiablePresentation, err := r.vcr.Wallet().BuildPresentation(c.Request().Context(), credentials, holder.PresentationOptions{}, &session.OwnDID, false)
if len(signInstructions) != 1 {
// todo support multiple wallets (org + user)
return errors.New("expected to create exactly one presentation")
}
verifiablePresentation, err := r.vcr.Wallet().BuildPresentation(c.Request().Context(), signInstructions[0].VerifiableCredentials, holder.PresentationOptions{}, &signInstructions[0].Holder, false)
if err != nil {
return err
}
Expand Down
3 changes: 2 additions & 1 deletion auth/services/irma/signer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ package irma
import (
"context"
"errors"
"github.com/nuts-foundation/go-did/did"
"github.com/stretchr/testify/require"
"testing"

Expand Down Expand Up @@ -109,7 +110,7 @@ func TestService_StartSigningSession(t *testing.T) {
func TestService_SigningSessionStatus(t *testing.T) {
correctContractText := "EN:PractitionerLogin:v3 I hereby declare to act on behalf of verpleeghuis De nootjes located in Caretown. This declaration is valid from maandag 1 oktober 12:00:00 until maandag 1 oktober 13:00:00."
holder := vdr.TestDIDA
keyID := holder
keyID := did.DIDURL{DID: holder}
keyID.Fragment = keyID.ID
ctx := context.Background()

Expand Down
2 changes: 1 addition & 1 deletion auth/services/irma/validator.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import (
"strings"
"time"

"github.com/lestrrat-go/jwx/jwt"
"github.com/lestrrat-go/jwx/v2/jwt"
"github.com/nuts-foundation/go-did/vc"
"github.com/nuts-foundation/nuts-node/auth/contract"

Expand Down
2 changes: 1 addition & 1 deletion auth/services/oauth/authz_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import (
"fmt"
"time"

"github.com/lestrrat-go/jwx/jwt"
"github.com/lestrrat-go/jwx/v2/jwt"
"github.com/nuts-foundation/go-did/did"
vc2 "github.com/nuts-foundation/go-did/vc"
"github.com/nuts-foundation/nuts-node/auth/contract"
Expand Down
18 changes: 9 additions & 9 deletions auth/services/oauth/authz_server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,9 @@ import (
"testing"
"time"

"github.com/lestrrat-go/jwx/jwa"
"github.com/lestrrat-go/jwx/jws"
"github.com/lestrrat-go/jwx/jwt"
"github.com/lestrrat-go/jwx/v2/jwa"
"github.com/lestrrat-go/jwx/v2/jws"
"github.com/lestrrat-go/jwx/v2/jwt"
ssi "github.com/nuts-foundation/go-did"
"github.com/nuts-foundation/go-did/did"
"github.com/nuts-foundation/go-did/vc"
Expand Down Expand Up @@ -80,7 +80,7 @@ func getAuthorizerDIDDocument() *did.Document {
doc := did.Document{
ID: id,
}
signingKeyID := id
signingKeyID := did.DIDURL{DID: id}
signingKeyID.Fragment = "signing-key"
key, err := did.NewVerificationMethod(signingKeyID, ssi.JsonWebKey2020, id, authorizerSigningKey.Public())
if err != nil {
Expand Down Expand Up @@ -529,7 +529,7 @@ func TestService_validateAuthorizationCredentials(t *testing.T) {

err := ctx.oauthService.validateAuthorizationCredentials(tokenCtx)

assert.EqualError(t, err, "invalid jwt.vcs: cannot unmarshal authorization credential: failed to parse token: invalid character '}' looking for beginning of value")
assert.EqualError(t, err, "invalid jwt.vcs: cannot unmarshal authorization credential: invalid JWT")
})

t.Run("error - jwt.iss <> credentialSubject.ID mismatch", func(t *testing.T) {
Expand Down Expand Up @@ -596,7 +596,7 @@ func TestService_parseAndValidateJwtBearerToken(t *testing.T) {
token := jwt.New()
hdrs := jws.NewHeaders()
hdrs.Set(jws.KeyIDKey, keyID)
signedToken, err := jwt.Sign(token, jwa.RS256, privateKey, jwt.WithHeaders(hdrs))
signedToken, err := jwt.Sign(token, jwt.WithKey(jwa.RS256, privateKey, jws.WithProtectedHeaders(hdrs)))
require.NoError(t, err)

tokenCtx := &validationContext{
Expand Down Expand Up @@ -646,7 +646,7 @@ func TestService_parseAndValidateJwtBearerToken(t *testing.T) {
ctx.keyResolver.EXPECT().ResolveKeyByID(requesterSigningKeyID.String(), nil, resolver.NutsSigningKeyType).Return(requesterSigningKey.PublicKey, nil)

err := ctx.oauthService.parseAndValidateJwtBearerToken(tokenCtx)
assert.EqualError(t, err, "exp not satisfied")
assert.EqualError(t, err, "\"exp\" not satisfied")
})
}

Expand Down Expand Up @@ -728,7 +728,7 @@ func TestService_IntrospectAccessToken(t *testing.T) {
// Then validate it
claims, err := ctx.oauthService.IntrospectAccessToken(ctx.audit, tokenCtx.rawJwtBearerToken)

require.EqualError(t, err, "failed to verify jws signature: failed to verify message: failed to verify signature using ecdsa")
require.EqualError(t, err, "could not verify message using any of the signatures or keys")
require.Nil(t, claims)
})

Expand Down Expand Up @@ -846,7 +846,7 @@ func signTokenWithKey(context *validationContext, key *ecdsa.PrivateKey) {
if err != nil {
panic(err)
}
signedToken, err := jwt.Sign(context.jwtBearerToken, jwa.ES256, key, jwt.WithHeaders(hdrs))
signedToken, err := jwt.Sign(context.jwtBearerToken, jwt.WithKey(jwa.ES256, key, jws.WithProtectedHeaders(hdrs)))
if err != nil {
panic(err)
}
Expand Down
2 changes: 1 addition & 1 deletion auth/services/oauth/relying_party.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ import (
"strings"
"time"

"github.com/lestrrat-go/jwx/jwt"
"github.com/lestrrat-go/jwx/v2/jwt"
"github.com/nuts-foundation/go-did/did"
"github.com/nuts-foundation/nuts-node/auth/api/auth/v1/client"
"github.com/nuts-foundation/nuts-node/auth/client/iam"
Expand Down
2 changes: 1 addition & 1 deletion auth/services/x509/uzi_validator.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import (
"io/fs"
"strings"

"github.com/lestrrat-go/jwx/jwa"
"github.com/lestrrat-go/jwx/v2/jwa"

"github.com/nuts-foundation/nuts-node/auth/assets"
"github.com/nuts-foundation/nuts-node/auth/contract"
Expand Down
22 changes: 12 additions & 10 deletions auth/services/x509/x509_validator.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,14 @@ import (
"encoding/base64"
"encoding/json"
"fmt"
"github.com/lestrrat-go/jwx/v2/cert"
"time"

"github.com/nuts-foundation/nuts-node/pki"

"github.com/lestrrat-go/jwx/jwa"
"github.com/lestrrat-go/jwx/jws"
"github.com/lestrrat-go/jwx/jwt"
"github.com/lestrrat-go/jwx/v2/jwa"
"github.com/lestrrat-go/jwx/v2/jws"
"github.com/lestrrat-go/jwx/v2/jwt"
)

// JwtX509Token contains a parsed JWT signed with a x509 certificate.
Expand Down Expand Up @@ -118,7 +119,7 @@ func (validator JwtX509Validator) Parse(rawAuthToken string) (*JwtX509Token, err
}

certsFromHeader := headers.X509CertChain()
if len(certsFromHeader) == 0 {
if certsFromHeader == nil || certsFromHeader.Len() == 0 {
return nil, fmt.Errorf("the jwt x5c field should contain at least 1 certificate")
}

Expand All @@ -129,7 +130,7 @@ func (validator JwtX509Validator) Parse(rawAuthToken string) (*JwtX509Token, err

certUsedForSigning := chain[0]

payload, err := jws.Verify([]byte(rawAuthToken), headers.Algorithm(), certUsedForSigning.PublicKey)
payload, err := jws.Verify([]byte(rawAuthToken), jws.WithKey(headers.Algorithm(), certUsedForSigning.PublicKey))
if err != nil {
return nil, fmt.Errorf("could not verify jwt signature: %w", err)
}
Expand All @@ -148,12 +149,13 @@ func (validator JwtX509Validator) Parse(rawAuthToken string) (*JwtX509Token, err
}

// parseBase64EncodedCertList makes it convenient to parse an array of base64 certificates into x509.Certificates
func (validator JwtX509Validator) parseBase64EncodedCertList(certsFromHeader []string) ([]*x509.Certificate, error) {
func (validator JwtX509Validator) parseBase64EncodedCertList(certsFromHeader *cert.Chain) ([]*x509.Certificate, error) {
// Parse all the certificates from the header into a chain
chain := []*x509.Certificate{}
var chain []*x509.Certificate

for _, certStr := range certsFromHeader {
rawCert, err := base64.StdEncoding.Strict().DecodeString(certStr)
for i := 0; i < certsFromHeader.Len(); i++ {
certStr, _ := certsFromHeader.Get(i)
rawCert, err := base64.StdEncoding.Strict().DecodeString(string(certStr))
if err != nil {
return nil, fmt.Errorf("could not base64 decode certificate: %w", err)
}
Expand Down Expand Up @@ -202,7 +204,7 @@ func (validator JwtX509Validator) Verify(x509Token *JwtX509Token) error {
}

// parse the jwt and verify the jwt signature
token, err := jwt.ParseString(x509Token.raw, jwt.WithVerify(x509Token.sigAlg, leafCert.PublicKey))
token, err := jwt.ParseString(x509Token.raw, jwt.WithKey(x509Token.sigAlg, leafCert.PublicKey))
if err != nil {
return err
}
Expand Down
54 changes: 19 additions & 35 deletions auth/services/x509/x509_validator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,18 +25,18 @@ import (
"crypto/x509/pkix"
"encoding/asn1"
"encoding/base64"
"github.com/stretchr/testify/require"
"github.com/lestrrat-go/jwx/v2/cert"
"go.uber.org/mock/gomock"
"math/big"
"testing"
"time"

"github.com/lestrrat-go/jwx/v2/jwa"
"github.com/lestrrat-go/jwx/v2/jws"
"github.com/lestrrat-go/jwx/v2/jwt"
"github.com/nuts-foundation/nuts-node/pki"

"github.com/lestrrat-go/jwx/jwa"
"github.com/lestrrat-go/jwx/jws"
"github.com/lestrrat-go/jwx/jwt"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestNewJwtX509Validator(t *testing.T) {
Expand Down Expand Up @@ -113,14 +113,16 @@ func TestNewJwtX509Validator(t *testing.T) {

func TestJwtX509Validator_Parse(t *testing.T) {
validator := NewJwtX509Validator(nil, nil, nil, nil)
cert, privKey, err := createTestRootCert()
certificate, privKey, err := createTestRootCert()
require.NoError(t, err)
t.Run("ok - a valid jwt", func(t *testing.T) {
theJwt := jwt.New()
headers := jws.NewHeaders()
err := headers.Set(jws.X509CertChainKey, []string{base64.StdEncoding.EncodeToString(cert.Raw)})
chain := cert.Chain{}
_ = chain.AddString(base64.StdEncoding.Strict().EncodeToString(certificate.Raw))
err = headers.Set(jws.X509CertChainKey, &chain)
require.NoError(t, err)
rawToken, err := jwt.Sign(theJwt, jwa.RS256, privKey, jwt.WithHeaders(headers))
rawToken, err := jwt.Sign(theJwt, jwt.WithKey(jwa.RS256, privKey, jws.WithProtectedHeaders(headers)))
require.NoError(t, err)
token, err := validator.Parse(string(rawToken))
assert.NotNil(t, token)
Expand All @@ -144,35 +146,13 @@ func TestJwtX509Validator_Parse(t *testing.T) {

t.Run("nok - emtpy x5c header field", func(t *testing.T) {
theJwt := jwt.New()
signedJwt, err := jwt.Sign(theJwt, jwa.RS256, priv)
signedJwt, err := jwt.Sign(theJwt, jwt.WithKey(jwa.RS256, priv))
require.NoError(t, err)
token, err := validator.Parse(string(signedJwt))
assert.Nil(t, token)
assert.ErrorContains(t, err, "the jwt x5c field should contain at least 1 certificate")
})

t.Run("nok - invalid base64 data in x5c header", func(t *testing.T) {
theJwt := jwt.New()
headers := jws.NewHeaders()
assert.NoError(t, headers.Set(jws.X509CertChainKey, []string{"123"}))
signedJwt, err := jwt.Sign(theJwt, jwa.RS256, priv, jwt.WithHeaders(headers))
require.NoError(t, err)
token, err := validator.Parse(string(signedJwt))
assert.Nil(t, token)
assert.ErrorContains(t, err, "could not parse certificates from headers: could not base64 decode certificate: illegal base64 data at input byte 0")

})
t.Run("nok - invalid cert in x5c header", func(t *testing.T) {
theJwt := jwt.New()
headers := jws.NewHeaders()
assert.NoError(t, headers.Set(jws.X509CertChainKey, []string{"WvLTlMrX9NpYDQlEIFlnDA=="}))
signedJwt, err := jwt.Sign(theJwt, jwa.RS256, priv, jwt.WithHeaders(headers))
require.NoError(t, err)
token, err := validator.Parse(string(signedJwt))
assert.Nil(t, token)
assert.ErrorContains(t, err, "could not parse certificates from headers: could not parse certificate: x509: malformed certificate")
})

t.Run("nok - alg header different from actual signing algorithm", func(t *testing.T) {
t.SkipNow()
//b64Cert := base64.StdEncoding.EncodeToString(cert.Raw)
Expand Down Expand Up @@ -295,8 +275,10 @@ func TestJwtX509Validator_Verify(t *testing.T) {
theJwt := jwt.New()
theJwt.Set(jwt.IssuedAtKey, time.Now())
headers := jws.NewHeaders()
assert.NoError(t, headers.Set(jws.X509CertChainKey, []string{base64.StdEncoding.EncodeToString(leafCert.Raw)}))
rawJwt, err := jwt.Sign(theJwt, jwa.RS256, leafKey, jwt.WithHeaders(headers))
chain := cert.Chain{}
_ = chain.AddString(base64.StdEncoding.Strict().EncodeToString(leafCert.Raw))
require.NoError(t, headers.Set(jws.X509CertChainKey, &chain))
rawJwt, err := jwt.Sign(theJwt, jwt.WithKey(jwa.RS256, leafKey, jws.WithProtectedHeaders(headers)))
require.NoError(t, err)

x509Token := &JwtX509Token{
Expand All @@ -323,8 +305,10 @@ func TestJwtX509Validator_Verify(t *testing.T) {

theJwt := jwt.New()
headers := jws.NewHeaders()
assert.NoError(t, headers.Set(jws.X509CertChainKey, []string{base64.StdEncoding.EncodeToString(leafCert.Raw)}))
rawJwt, err := jwt.Sign(theJwt, jwa.RS256, leafKey, jwt.WithHeaders(headers))
chain := cert.Chain{}
_ = chain.AddString(base64.StdEncoding.Strict().EncodeToString(leafCert.Raw))
require.NoError(t, headers.Set(jws.X509CertChainKey, &chain))
rawJwt, err := jwt.Sign(theJwt, jwt.WithKey(jwa.RS256, leafKey, jws.WithProtectedHeaders(headers)))
require.NoError(t, err)

x509Token := &JwtX509Token{
Expand Down
8 changes: 4 additions & 4 deletions crypto/api/v1/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import (
"time"

"github.com/labstack/echo/v4"
"github.com/lestrrat-go/jwx/jws"
"github.com/lestrrat-go/jwx/v2/jws"
ssi "github.com/nuts-foundation/go-did"
"github.com/nuts-foundation/go-did/did"

Expand Down Expand Up @@ -191,8 +191,8 @@ func (w *Wrapper) EncryptJwe(ctx context.Context, request EncryptJweRequestObjec
return EncryptJwe200TextResponse(jwe), err
}

func (w *Wrapper) resolvePublicKey(id *did.DID) (key crypt.PublicKey, keyID ssi.URI, err error) {
if id.IsURL() {
func (w *Wrapper) resolvePublicKey(id *did.DIDURL) (key crypt.PublicKey, keyID ssi.URI, err error) {
if id.Fragment != "" {
// Assume it is a keyId
now := time.Now()
key, err = w.K.ResolveKeyByID(id.String(), &now, resolver.KeyAgreement)
Expand All @@ -202,7 +202,7 @@ func (w *Wrapper) resolvePublicKey(id *did.DID) (key crypt.PublicKey, keyID ssi.
keyID = id.URI()
} else {
// Assume it is a DID
keyID, key, err = w.K.ResolveKey(*id, nil, resolver.KeyAgreement)
keyID, key, err = w.K.ResolveKey(id.DID, nil, resolver.KeyAgreement)
if err != nil {
return nil, ssi.URI{}, err
}
Expand Down
Loading

0 comments on commit 8d2706a

Please sign in to comment.