@@ -3,8 +3,8 @@ import type { Key, Buffer } from '@credo-ts/core'
33import type { RPRegistrationMetadataPayload } from '@sphereon/did-auth-siop'
44import type { Router , Response } from 'express'
55
6- import { getJwkFromKey , KeyType } from '@credo-ts/core'
7- import { createEntityConfiguration } from '@openid-federation/core'
6+ import { getJwkFromJson , getJwkFromKey , JwsService , KeyType } from '@credo-ts/core'
7+ import { createEntityConfiguration , createEntityStatement , fetchEntityConfiguration } from '@openid-federation/core'
88import { LanguageTagUtils , removeNullUndefined } from '@sphereon/did-auth-siop'
99
1010import { getRequestContext , sendErrorResponse } from '../../shared/router'
@@ -47,7 +47,10 @@ const createRPRegistrationMetadataPayload = (opts: any): RPRegistrationMetadataP
4747 return removeNullUndefined ( rpRegistrationMetadataPayload )
4848}
4949
50- export function configureFederationEndpoint ( router : Router ) {
50+ export function configureFederationEndpoint (
51+ router : Router ,
52+ federationConfig : OpenId4VcVerifierModuleConfig [ 'federation' ] = { }
53+ ) {
5154 // TODO: this whole result needs to be cached and the ttl should be the expires of this node
5255
5356 // TODO: This will not work for multiple instances so we have to save it in the database.
@@ -97,6 +100,11 @@ export function configureFederationEndpoint(router: Router) {
97100 const alg = jwk . supportedSignatureAlgorithms [ 0 ]
98101 const kid = federationKey . fingerprint
99102
103+ const authorityHints = await federationConfig . getAuthorityHints ?.( agentContext , {
104+ verifierId : verifier . verifierId ,
105+ issuerEntityId : verifierEntityId ,
106+ } )
107+
100108 const entityConfiguration = await createEntityConfiguration ( {
101109 header : {
102110 kid,
@@ -111,10 +119,12 @@ export function configureFederationEndpoint(router: Router) {
111119 jwks : {
112120 keys : [ { kid, alg, ...jwk . toJson ( ) } ] ,
113121 } ,
122+ authority_hints : authorityHints ,
114123 metadata : {
115124 federation_entity : {
116125 organization_name : rpMetadata . client_name ,
117126 logo_uri : rpMetadata . logo_uri ,
127+ federation_fetch_endpoint : `${ verifierEntityId } /openid-federation/fetch` ,
118128 } ,
119129 openid_relying_party : {
120130 ...rpMetadata ,
@@ -145,4 +155,101 @@ export function configureFederationEndpoint(router: Router) {
145155 next ( )
146156 }
147157 )
158+
159+ // TODO: Currently it will fetch everything in realtime and creates a entity statement without even checking if it is allowed.
160+ router . get ( '/openid-federation/fetch' , async ( request : OpenId4VcVerificationRequest , response : Response , next ) => {
161+ const { agentContext, verifier } = getRequestContext ( request )
162+
163+ const { sub } = request . query
164+ if ( ! sub || typeof sub !== 'string' ) {
165+ sendErrorResponse ( response , next , agentContext . config . logger , 400 , 'invalid_request' , 'sub is required' )
166+ return
167+ }
168+
169+ const verifierConfig = agentContext . dependencyManager . resolve ( OpenId4VcVerifierModuleConfig )
170+
171+ const entityId = `${ verifierConfig . baseUrl } /${ verifier . verifierId } `
172+
173+ const isSubordinateEntity = await federationConfig . isSubordinateEntity ?.( agentContext , {
174+ verifierId : verifier . verifierId ,
175+ issuerEntityId : entityId ,
176+ subjectEntityId : sub ,
177+ } )
178+ if ( ! isSubordinateEntity ) {
179+ if ( ! federationConfig . isSubordinateEntity ) {
180+ agentContext . config . logger . warn (
181+ 'isSubordinateEntity hook is not provided for the federation so we cannot check if this entity is a subordinate entity of the issuer' ,
182+ {
183+ verifierId : verifier . verifierId ,
184+ issuerEntityId : entityId ,
185+ subjectEntityId : sub ,
186+ }
187+ )
188+ }
189+
190+ sendErrorResponse (
191+ response ,
192+ next ,
193+ agentContext . config . logger ,
194+ 403 ,
195+ 'forbidden' ,
196+ 'This entity is not a subordinate entity of the issuer'
197+ )
198+ return
199+ }
200+
201+ const jwsService = agentContext . dependencyManager . resolve ( JwsService )
202+
203+ const subjectEntityConfiguration = await fetchEntityConfiguration ( {
204+ entityId : sub ,
205+ verifyJwtCallback : async ( { jwt, jwk } ) => {
206+ const res = await jwsService . verifyJws ( agentContext , {
207+ jws : jwt ,
208+ jwkResolver : ( ) => getJwkFromJson ( jwk ) ,
209+ } )
210+
211+ return res . isValid
212+ } ,
213+ } )
214+
215+ let federationKey = federationKeyMapping . get ( verifier . verifierId )
216+ if ( ! federationKey ) {
217+ federationKey = await agentContext . wallet . createKey ( {
218+ keyType : KeyType . Ed25519 ,
219+ } )
220+ federationKeyMapping . set ( verifier . verifierId , federationKey )
221+ }
222+
223+ const jwk = getJwkFromKey ( federationKey )
224+ const alg = jwk . supportedSignatureAlgorithms [ 0 ]
225+ const kid = federationKey . fingerprint
226+
227+ const entityStatement = await createEntityStatement ( {
228+ header : {
229+ kid,
230+ alg,
231+ typ : 'entity-statement+jwt' ,
232+ } ,
233+ jwk : {
234+ ...jwk . toJson ( ) ,
235+ kid,
236+ } ,
237+ claims : {
238+ sub : sub ,
239+ iss : entityId ,
240+ iat : new Date ( ) ,
241+ exp : new Date ( Date . now ( ) + 1000 * 60 * 60 * 24 ) , // 1 day TODO: Might needs to be a bit lower because a day is quite long for trust
242+ jwks : {
243+ keys : subjectEntityConfiguration . jwks . keys ,
244+ } ,
245+ } ,
246+ signJwtCallback : ( { toBeSigned } ) =>
247+ agentContext . wallet . sign ( {
248+ data : toBeSigned as Buffer ,
249+ key : federationKey ,
250+ } ) ,
251+ } )
252+
253+ response . writeHead ( 200 , { 'Content-Type' : 'application/entity-statement+jwt' } ) . end ( entityStatement )
254+ } )
148255}
0 commit comments