Skip to content

Commit

Permalink
feat: bPrime flow functionality
Browse files Browse the repository at this point in the history
Signed-off-by: Berend Sliedrecht <[email protected]>
  • Loading branch information
Berend Sliedrecht committed Aug 5, 2024
1 parent e201857 commit 66da8ea
Show file tree
Hide file tree
Showing 7 changed files with 2,003 additions and 1,699 deletions.
94 changes: 94 additions & 0 deletions apps/funke/authenticated-channel/requestPid.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
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,
}: {
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
}) => {
const resolvedCredentialOffer = await agent.modules.openId4VcHolder.resolveCredentialOffer(requestUri)

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
}
53 changes: 46 additions & 7 deletions apps/funke/crypto/bPrime.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
import { type AgentContext, TypedArrayEncoder } from '@credo-ts/core'
import { Key, KeyAlgs, KeyMethod } from '@hyperledger/aries-askar-react-native'
import {
type AgentContext,
type JwsProtectedHeaderOptions,
JwsService,
JwtPayload,
type Key,
KeyType,
TypedArrayEncoder,
getJwkFromKey,
} from '@credo-ts/core'
import type { FullAppAgent } from '@package/agent/src'
import { kdf } from '@package/secure-store/kdf'
import { funkeAes128Gcm } from './aes'

Expand All @@ -14,7 +23,7 @@ import { funkeAes128Gcm } from './aes'
*/
export const deriveKeypairFromPin = async (agentContext: AgentContext, pin: Array<number>) => {
if (!(await funkeAes128Gcm.aes128GcmHasKey({ agentContext }))) {
throw new Error('No AES key found in storage. Flow is called in an incorrect way!')
await funkeAes128Gcm.aes128GcmGenerateAndStoreKey({ agentContext })
}

const pinSecret = await funkeAes128Gcm.aes128GcmEncrypt({
Expand All @@ -27,9 +36,39 @@ export const deriveKeypairFromPin = async (agentContext: AgentContext, pin: Arra
TypedArrayEncoder.toUtf8String(pinSecret)
)

return Key.fromSeed({
seed: new Uint8Array(TypedArrayEncoder.fromHex(pinSeed)),
method: KeyMethod.None,
algorithm: KeyAlgs.EcSecp256r1,
return agentContext.wallet.createKey({
seed: TypedArrayEncoder.fromHex(pinSeed),
keyType: KeyType.P256,
})
}

export const createPinDerivedEphKeyPop = async (
agent: FullAppAgent,
{ aud, cNonce, deviceKey, pinDerivedEph }: { pinDerivedEph: Key; deviceKey: Key; cNonce: string; aud: string }
) => {
const deviceKeyClaim = getJwkFromKey(deviceKey).toJson()

const payload = new JwtPayload({
aud,
additionalClaims: {
nonce: cNonce,
device_key: { jwk: deviceKeyClaim },
},
})

const protectedHeaderOptions: JwsProtectedHeaderOptions = {
alg: 'ES256',
typ: 'pin_derived_eph_key_pop',
jwk: getJwkFromKey(pinDerivedEph),
}

const jwsService = agent.dependencyManager.resolve<JwsService>(JwsService)

const compact = await jwsService.createJwsCompact(agent.context, {
key: pinDerivedEph,
payload,
protectedHeaderOptions,
})

return compact
}
22 changes: 11 additions & 11 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,24 +28,24 @@
"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-20240730111842",
"@credo-ts/askar": "0.5.10-alpha-20240730111842",
"@credo-ts/cheqd": "0.5.10-alpha-20240730111842",
"@credo-ts/core": "0.5.10-alpha-20240730111842",
"@credo-ts/indy-vdr": "0.5.10-alpha-20240730111842",
"@credo-ts/openid4vc": "0.5.10-alpha-20240730111842",
"@credo-ts/question-answer": "0.5.10-alpha-20240730111842",
"@credo-ts/anoncreds": "0.5.10-alpha-20240805102402",
"@credo-ts/askar": "0.5.10-alpha-20240805102402",
"@credo-ts/cheqd": "0.5.10-alpha-20240805102402",
"@credo-ts/core": "0.5.10-alpha-20240805102402",
"@credo-ts/indy-vdr": "0.5.10-alpha-20240805102402",
"@credo-ts/openid4vc": "0.5.10-alpha-20240805102402",
"@credo-ts/question-answer": "0.5.10-alpha-20240805102402",
"@credo-ts/react-hooks": "0.6.1",
"@credo-ts/react-native": "0.5.10-alpha-20240730111842"
"@credo-ts/react-native": "0.5.10-alpha-20240805102402"
},
"patchedDependencies": {
"@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]"
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,12 +42,12 @@ export function OpenIdPresentationNotificationScreen() {
useEffect(() => {
async function handleRequest() {
try {
const credentialsForRequest = await getCredentialsForProofRequest({
const cfr = await getCredentialsForProofRequest({
agent,
data: params.data,
uri: params.uri,
})
setCredentialsForRequest(credentialsForRequest)
setCredentialsForRequest(cfr)
} catch (error: unknown) {
toast.show('Presentation information could not be extracted.')
agent.config.logger.error('Error getting credentials for request', {
Expand Down
88 changes: 88 additions & 0 deletions patches/@[email protected]
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.
58 changes: 58 additions & 0 deletions patches/@[email protected]
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,
Loading

0 comments on commit 66da8ea

Please sign in to comment.