-
Notifications
You must be signed in to change notification settings - Fork 15
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Signed-off-by: Berend Sliedrecht <[email protected]>
- Loading branch information
Berend Sliedrecht
committed
Aug 6, 2024
1 parent
233f8cd
commit 12ad004
Showing
8 changed files
with
1,134 additions
and
821 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,125 @@ | ||
import { type Key, KeyType, getJwkFromKey } from "@credo-ts/core"; | ||
import type { FullAppAgent } from "@package/agent"; | ||
import { | ||
createPinDerivedEphKeyPop, | ||
deriveKeypairFromPin, | ||
} from "../crypto/bPrime"; | ||
|
||
/** | ||
* | ||
* Start point of the flow described in {@link https://gitlab.opencode.de/bmi/eudi-wallet/eidas-2.0-architekturkonzept/-/blob/main/flows/PID-AuthenticatedChannel-cloud.md#pid-seed-credential-issuance-1 | Architecture Proposal 2.1 - B'} | ||
* | ||
* This flow starts after the eID flow is finished. The authorization code in the input is received from the eID flow | ||
* | ||
*/ | ||
export const receivePidViaAuthenticatedChannel = async ({ | ||
agent, | ||
deviceKey, | ||
eIdFlowCallback, | ||
requestUri, | ||
pinSetup, | ||
clientId, | ||
redirectUri, | ||
}: { | ||
agent: FullAppAgent; | ||
/** | ||
* | ||
* @todo Can we just have a static ID for the device key or do we just pass in an instance like so? | ||
* | ||
*/ | ||
deviceKey: Key; | ||
/** | ||
* | ||
* @todo Does this need input? | ||
* | ||
*/ | ||
eIdFlowCallback: () => Promise<{ authorizationCode: string }>; | ||
pinSetup: () => Promise<Array<number>>; | ||
requestUri: string; | ||
redirectUri: string; | ||
clientId: string; | ||
}) => { | ||
const resolvedCredentialOffer = | ||
await agent.modules.openId4VcHolder.resolveCredentialOffer(requestUri); | ||
|
||
const uniqueScopes = Array.from( | ||
new Set( | ||
resolvedCredentialOffer.offeredCredentials | ||
.map((o) => o.scope) | ||
.filter((s): s is string => s !== undefined) | ||
) | ||
); | ||
|
||
const resolvedAuthorizationRequest = | ||
await agent.modules.openId4VcHolder.resolveIssuanceAuthorizationRequest( | ||
resolvedCredentialOffer, | ||
{ | ||
scope: uniqueScopes, | ||
redirectUri: redirectUri, | ||
clientId: clientId, | ||
} | ||
); | ||
|
||
const { authorizationCode } = await eIdFlowCallback(); | ||
|
||
// TODO: when passing the `code: authorizationCode` in here we also have to input the `resolveAuthorizationRequest`, how can we do that? | ||
const { accessToken, cNonce } = | ||
// @ts-expect-error: how do we get the resolveAuthorizationRequest? | ||
await agent.modules.openId4VcHolder.requestToken({ | ||
code: authorizationCode, | ||
resolvedCredentialOffer, | ||
}); | ||
|
||
if (!cNonce) { | ||
throw new Error( | ||
"cNonce must be returned from the token request. Maybe an invalid (non-compatible) token request was used" | ||
); | ||
} | ||
|
||
const newPin = await pinSetup(); | ||
|
||
const pinDerivedEph = await deriveKeypairFromPin(agent.context, newPin); | ||
|
||
// TODO: how do we get the audience? | ||
const pinDerivedEphKeyPop = await createPinDerivedEphKeyPop(agent, { | ||
aud: "https://example.org", | ||
pinDerivedEph, | ||
deviceKey, | ||
cNonce, | ||
}); | ||
|
||
const credentialAndNotifications = | ||
await agent.modules.openId4VcHolder.requestCredentials({ | ||
customFormat: "seed_credential", | ||
additionalCredentialRequestPayloadClaims: { | ||
pin_derived_eph_key_pop: pinDerivedEphKeyPop, | ||
}, | ||
additionalProofOfPossessionPayloadClaims: { | ||
pin_derived_eph_pub: getJwkFromKey(pinDerivedEph).toJson(), | ||
}, | ||
cNonce, | ||
accessToken, | ||
resolvedCredentialOffer, | ||
credentialBindingResolver: async ({ keyType, supportsJwk }) => { | ||
if (!supportsJwk) { | ||
throw Error("Issuer does not support JWK"); | ||
} | ||
|
||
if (keyType !== KeyType.P256) { | ||
throw new Error( | ||
`invalid key type used '${keyType}' and only ${KeyType.P256} is allowed.` | ||
); | ||
} | ||
return { | ||
method: "jwk", | ||
jwk: getJwkFromKey(deviceKey), | ||
}; | ||
}, | ||
}); | ||
|
||
const credentials = credentialAndNotifications.map( | ||
({ credential }) => credential | ||
); | ||
|
||
return credentials; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -28,11 +28,9 @@ | |
"typescript": "~5.3.3", | ||
"@unimodules/react-native-adapter": "./noop", | ||
"@unimodules/core": "./noop", | ||
|
||
"@hyperledger/anoncreds-react-native": "^0.2.2", | ||
"@hyperledger/aries-askar-react-native": "^0.2.3", | ||
"@hyperledger/indy-vdr-react-native": "^0.2.0", | ||
|
||
"@credo-ts/anoncreds": "0.5.10-alpha-20240805102402", | ||
"@credo-ts/askar": "0.5.10-alpha-20240805102402", | ||
"@credo-ts/cheqd": "0.5.10-alpha-20240805102402", | ||
|
@@ -48,7 +46,9 @@ | |
"patchedDependencies": { | ||
"@credo-ts/[email protected]": "patches/@[email protected]", | ||
"@hyperledger/[email protected]": "patches/@[email protected]", | ||
"@hyperledger/[email protected]": "patches/@[email protected]" | ||
"@hyperledger/[email protected]": "patches/@[email protected]", | ||
"@credo-ts/[email protected]": "patches/@[email protected]", | ||
"@sphereon/[email protected]": "patches/@[email protected]" | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
diff --git a/build/openid4vc-holder/OpenId4VcHolderApi.js b/build/openid4vc-holder/OpenId4VcHolderApi.js | ||
index 9605e9a095d780e666fd6afbd0b95c41b44fbd69..3ba413b3926c08b59b833e661c6c236d006bd984 100644 | ||
--- a/build/openid4vc-holder/OpenId4VcHolderApi.js | ||
+++ b/build/openid4vc-holder/OpenId4VcHolderApi.js | ||
@@ -140,7 +140,7 @@ let OpenId4VcHolderApi = class OpenId4VcHolderApi { | ||
* @param options.tokenResponse Obtained through @see requestAccessToken | ||
*/ | ||
async requestCredentials(options) { | ||
- const { resolvedCredentialOffer, cNonce, accessToken, dpop, clientId } = options, credentialRequestOptions = __rest(options, ["resolvedCredentialOffer", "cNonce", "accessToken", "dpop", "clientId"]); | ||
+ const { resolvedCredentialOffer, cNonce, accessToken, dpop, clientId, additionalProofOfPossessionPayloadClaims, additionalCredentialRequestPayloadClaims, customFormat } = options, credentialRequestOptions = __rest(options, ["resolvedCredentialOffer", "cNonce", "accessToken", "dpop", "clientId"]); | ||
return this.openId4VciHolderService.acceptCredentialOffer(this.agentContext, { | ||
resolvedCredentialOffer, | ||
acceptCredentialOfferOptions: credentialRequestOptions, | ||
@@ -148,6 +148,9 @@ let OpenId4VcHolderApi = class OpenId4VcHolderApi { | ||
cNonce, | ||
dpop, | ||
clientId, | ||
+ additionalCredentialRequestPayloadClaims, | ||
+ additionalProofOfPossessionPayloadClaims, | ||
+ customFormat | ||
}); | ||
} | ||
/** | ||
diff --git a/build/openid4vc-holder/OpenId4VciHolderService.js b/build/openid4vc-holder/OpenId4VciHolderService.js | ||
index 1ae2282727ee5fb52eeab32a8245a9a7316e236d..f2c47e0096fc9635e4c3a76fbbc24eaa2ae6fccb 100644 | ||
--- a/build/openid4vc-holder/OpenId4VciHolderService.js | ||
+++ b/build/openid4vc-holder/OpenId4VciHolderService.js | ||
@@ -187,7 +187,7 @@ let OpenId4VciHolderService = class OpenId4VciHolderService { | ||
} | ||
async requestAccessToken(agentContext, options) { | ||
var _a, _b, _c, _d; | ||
- const { resolvedCredentialOffer, txCode, resolvedAuthorizationRequest, code } = options; | ||
+ const { resolvedCredentialOffer, txCode, resolvedAuthorizationRequest, code, customFormat } = options; | ||
const { metadata, credentialOfferRequestWithBaseUrl } = resolvedCredentialOffer; | ||
// acquire the access token | ||
let accessTokenResponse; | ||
@@ -228,7 +228,7 @@ let OpenId4VciHolderService = class OpenId4VciHolderService { | ||
} | ||
async acceptCredentialOffer(agentContext, options) { | ||
var _a, _b, _c, _d, _e, _f; | ||
- const { resolvedCredentialOffer, acceptCredentialOfferOptions } = options; | ||
+ const { resolvedCredentialOffer, acceptCredentialOfferOptions, customFormat, additionalProofOfPossessionPayloadClaims, additionalCredentialRequestPayloadClaims } = options; | ||
const { metadata, version, offeredCredentialConfigurations } = resolvedCredentialOffer; | ||
const { credentialsToRequest, credentialBindingResolver, verifyCredentialStatus } = acceptCredentialOfferOptions; | ||
if ((credentialsToRequest === null || credentialsToRequest === void 0 ? void 0 : credentialsToRequest.length) === 0) { | ||
@@ -288,6 +288,9 @@ let OpenId4VciHolderService = class OpenId4VciHolderService { | ||
}) | ||
.withEndpointMetadata(metadata) | ||
.withAlg(signatureAlgorithm); | ||
+ if(additionalProofOfPossessionPayloadClaims !== null && additionalProofOfPossessionPayloadClaims !== void 0) { | ||
+ proofOfPossessionBuilder.withJwt({header: {}, payload: additionalProofOfPossessionPayloadClaims }) | ||
+ } | ||
// TODO: what if auth flow using did, and the did is different from client id. We now use the client_id | ||
if (credentialBinding.method === 'did') { | ||
proofOfPossessionBuilder.withClientId((0, core_1.parseDid)(credentialBinding.didUrl).did).withKid(credentialBinding.didUrl); | ||
@@ -313,6 +316,7 @@ let OpenId4VciHolderService = class OpenId4VciHolderService { | ||
.withVersion(version) | ||
.withCredentialEndpoint(metadata.credential_endpoint) | ||
.withToken(tokenResponse.access_token); | ||
+ if(customFormat) { credentialRequestBuilder.withFormat(customFormat) } | ||
const credentialRequestClient = credentialRequestBuilder.build(); | ||
const createDpopOpts = tokenResponse.dpop | ||
? await this.getCreateDpopOptions(agentContext, metadata, { | ||
@@ -331,7 +335,8 @@ let OpenId4VciHolderService = class OpenId4VciHolderService { | ||
const credential = await this.handleCredentialResponse(agentContext, credentialResponse, { | ||
verifyCredentialStatus: verifyCredentialStatus !== null && verifyCredentialStatus !== void 0 ? verifyCredentialStatus : false, | ||
credentialIssuerMetadata: metadata.credentialIssuerMetadata, | ||
- format: offeredCredentialConfiguration.format, | ||
+ format: customFormat !== null && customFormat !== void 0 ? customFormat : offeredCredentialConfiguration.format, | ||
+ additionalRequestClaims: additionalCredentialRequestPayloadClaims | ||
}); | ||
this.logger.debug('Full credential', credential); | ||
receivedCredentials.push(credential); | ||
diff --git a/build/openid4vc-holder/OpenId4VciHolderServiceOptions.d.ts b/build/openid4vc-holder/OpenId4VciHolderServiceOptions.d.ts | ||
index 157a48fc081a43127891fbbf272990f3b62c1a72..6584c09cc6c39566c8bf09c5bac54fd1a9f8bf4c 100644 | ||
--- a/build/openid4vc-holder/OpenId4VciHolderServiceOptions.d.ts | ||
+++ b/build/openid4vc-holder/OpenId4VciHolderServiceOptions.d.ts | ||
@@ -88,6 +88,10 @@ export interface OpenId4VciCredentialRequestOptions extends Omit<OpenId4VciAccep | ||
* The client id used for authorization. Only required if authorization_code flow was used. | ||
*/ | ||
clientId?: string; | ||
+ | ||
+ additionalProofOfPossessionPayloadClaims?: Record<string, unknown> | ||
+ additionalCredentialRequestPayloadClaims?: Record<string, unknown> | ||
+ customFormat?: string | ||
} | ||
/** | ||
* Options that are used to accept a credential offer for both the pre-authorized code flow and authorization code flow. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
diff --git a/dist/CredentialRequestClient.js b/dist/CredentialRequestClient.js | ||
index 616811cf81ab05139bab5a87e4e11ceb9beb821e..d7c2934bdf53e3601e43f6d6c24ca2c5ca0ef99b 100644 | ||
--- a/dist/CredentialRequestClient.js | ||
+++ b/dist/CredentialRequestClient.js | ||
@@ -53,7 +53,7 @@ class CredentialRequestClient { | ||
} | ||
acquireCredentialsUsingProof(opts) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
- const { credentialIdentifier, credentialTypes, proofInput, format, context, subjectIssuance } = opts; | ||
+ const { credentialIdentifier, credentialTypes, proofInput, format, context, subjectIssuance, additionalRequestClaims } = opts; | ||
const request = yield this.createCredentialRequest({ | ||
proofInput, | ||
credentialTypes, | ||
@@ -62,6 +62,7 @@ class CredentialRequestClient { | ||
version: this.version(), | ||
credentialIdentifier, | ||
subjectIssuance, | ||
+ additionalRequestClaims | ||
}); | ||
return yield this.acquireCredentialsUsingRequest(request, opts.createDPoPOpts); | ||
}); | ||
@@ -137,7 +138,7 @@ class CredentialRequestClient { | ||
createCredentialRequest(opts) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
var _a; | ||
- const { proofInput, credentialIdentifier: credential_identifier } = opts; | ||
+ const { proofInput, credentialIdentifier: credential_identifier, additionalRequestClaims } = opts; | ||
const proof = yield buildProof(proofInput, opts); | ||
if (credential_identifier) { | ||
if (opts.format || opts.credentialTypes || opts.context) { | ||
@@ -148,11 +149,10 @@ class CredentialRequestClient { | ||
proof, | ||
}; | ||
} | ||
- const formatSelection = (_a = opts.format) !== null && _a !== void 0 ? _a : this.credentialRequestOpts.format; | ||
- if (!formatSelection) { | ||
+ const format = (_a = opts.format) !== null && _a !== void 0 ? _a : this.credentialRequestOpts.format; | ||
+ if (!format) { | ||
throw Error(`Format of credential to be issued is missing`); | ||
} | ||
- const format = (0, oid4vci_common_1.getUniformFormat)(formatSelection); | ||
const typesSelection = (opts === null || opts === void 0 ? void 0 : opts.credentialTypes) && (typeof opts.credentialTypes === 'string' || opts.credentialTypes.length > 0) | ||
? opts.credentialTypes | ||
: this.credentialRequestOpts.credentialTypes; | ||
@@ -164,7 +164,12 @@ class CredentialRequestClient { | ||
throw Error(`Credential type(s) need to be provided`); | ||
} | ||
// TODO: we should move format specific logic | ||
- if (format === 'jwt_vc_json' || format === 'jwt_vc') { | ||
+ if(format === 'seed_credential') { | ||
+ return Object.assign(additionalRequestClaims, { | ||
+ format, | ||
+ proof | ||
+ }) | ||
+ } else if (format === 'jwt_vc_json' || format === 'jwt_vc') { | ||
return Object.assign({ credential_definition: { | ||
type: types, | ||
}, format, |
Oops, something went wrong.