Skip to content

Commit fe1d98b

Browse files
committed
adds support for the same mDL certificate to be used in mobile wallet
Signed-off-by: nodirbek.parpibaev <nodirbek.parpibaev@dsr-corporation.com>
1 parent 77844f4 commit fe1d98b

10 files changed

Lines changed: 81 additions & 11 deletions

File tree

heka-identity-service/docs/swagger-spec.yaml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3652,8 +3652,6 @@ components:
36523652
type: boolean
36533653
presentationExchange:
36543654
type: object
3655-
dcql:
3656-
type: object
36573655
required:
36583656
- publicVerifierId
36593657
- requestSigner

heka-identity-service/src/common/agent/agent-modules.provider.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ function getTenantModulesMap(appConfig: ConfigType<typeof AppConfig>, agencyConf
130130
app: agencyConfig.oidConfig.app as any,
131131
issuer: {
132132
baseUrl: agencyConfig.oidConfig.issuanceEndpoint,
133-
credentialRequestToCredentialMapper: createCredentialRequestToCredentialMapper(agencyConfig.mdlIssuerCertificate),
133+
credentialRequestToCredentialMapper: createCredentialRequestToCredentialMapper(agencyConfig.mdlIssuerCertificate, agencyConfig.mdlIssuerPrivateKeyJwk),
134134
},
135135
verifier: {
136136
baseUrl: agencyConfig.oidConfig.verificationEndpoint,

heka-identity-service/src/common/agent/agent.provider.ts

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Agent as CredoAgent, BaseAgent, LogLevel } from '@credo-ts/core'
1+
import { Agent as CredoAgent, BaseAgent, Kms, LogLevel, X509ModuleConfig } from '@credo-ts/core'
22
import { DidCommHttpOutboundTransport, DidCommWsOutboundTransport } from '@credo-ts/didcomm'
33
import { agentDependencies, DidCommHttpInboundTransport, DidCommWsInboundTransport } from '@credo-ts/node'
44
import { OnApplicationShutdown } from '@nestjs/common'
@@ -50,6 +50,27 @@ export class Agent extends CredoAgent<AgencyModulesMap> implements OnApplication
5050

5151
await super.initialize()
5252

53+
if (this.agencyConfig.mdlIssuerCertificate) {
54+
const x509Config = this.context.resolve(X509ModuleConfig)
55+
x509Config.addTrustedCertificate(this.agencyConfig.mdlIssuerCertificate)
56+
logger.info('MDL issuer certificate added to trusted certificates')
57+
}
58+
59+
if (this.agencyConfig.mdlIssuerPrivateKeyJwk) {
60+
const kms = this.context.resolve(Kms.KeyManagementApi)
61+
try {
62+
await kms.importKey({ privateJwk: this.agencyConfig.mdlIssuerPrivateKeyJwk as unknown as Kms.KmsJwkPrivate })
63+
logger.info('MDL issuer private key imported into KMS')
64+
} catch (e) {
65+
const isDuplicateEntry = e instanceof Error && e.message === 'Duplicate entry'
66+
if (e instanceof Kms.KeyManagementKeyExistsError || isDuplicateEntry) {
67+
logger.debug('MDL issuer private key already present in KMS')
68+
} else {
69+
throw e
70+
}
71+
}
72+
}
73+
5374
logger.trace('<')
5475
}
5576

heka-identity-service/src/config/agent.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,9 @@ export default registerAs('agent', () => {
119119
}
120120

121121
const mdlIssuerCertificate = process.env.MDL_ISSUER_CERTIFICATE
122+
const mdlIssuerPrivateKeyJwk = process.env.MDL_ISSUER_PRIVATE_KEY
123+
? (JSON.parse(process.env.MDL_ISSUER_PRIVATE_KEY) as Record<string, string>)
124+
: undefined
122125

123126
const credentialsConfiguration: CredentialsConfiguration = {
124127
[ProtocolType.Oid4vc]: {
@@ -160,5 +163,6 @@ export default registerAs('agent', () => {
160163
hederaOperatorKey,
161164
credentialsConfiguration,
162165
mdlIssuerCertificate,
166+
mdlIssuerPrivateKeyJwk,
163167
}
164168
})

heka-identity-service/src/openid4vc/verification-sessions/verification-session.service.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ export class OpenId4VcVerificationSessionService {
132132
private static isJwtVcJsonPresentation(
133133
presentation: VerifiablePresentation,
134134
): presentation is W3cJwtVerifiablePresentation {
135-
return (presentation as W3cJwtVerifiablePresentation).jwt.header?.typ === 'JWT'
135+
return (presentation as W3cJwtVerifiablePresentation).jwt?.header?.typ === 'JWT'
136136
}
137137

138138
private static isMdocPresentation(presentation: VerifiablePresentation): presentation is MdocDeviceResponse {

heka-identity-service/src/utils/oid4vc/index.ts

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import type { JsonObject } from '@credo-ts/core'
22

3-
import { ClaimFormat, SdJwtVcPayload, W3cCredential, W3cCredentialSubject, X509Certificate, w3cDate } from '@credo-ts/core'
3+
import { ClaimFormat, Kms, SdJwtVcPayload, W3cCredential, W3cCredentialSubject, X509Certificate, w3cDate } from '@credo-ts/core'
44
import {
55
OpenId4VciCredentialFormatProfile,
66
OpenId4VciCredentialRequestToCredentialMapper,
@@ -34,8 +34,9 @@ export interface CredentialIssuanceMetadata {
3434

3535
export const createCredentialRequestToCredentialMapper = (
3636
mdlIssuerCertificate?: string,
37+
mdlIssuerPrivateKeyJwk?: Record<string, string>,
3738
): OpenId4VciCredentialRequestToCredentialMapper =>
38-
({ issuanceSession, holderBinding, credentialConfigurationId }): OpenId4VciSignCredentials => {
39+
async ({ agentContext, issuanceSession, holderBinding, credentialConfigurationId }): Promise<OpenId4VciSignCredentials> => {
3940
const credentials = issuanceSession.issuanceMetadata?.credentials as CredentialIssuanceMetadata[]
4041
if (!credentials) throw new Error('Not implemented')
4142

@@ -52,6 +53,16 @@ export const createCredentialRequestToCredentialMapper = (
5253
if (!issuanceMetadata.namespaces) throw new Error(`Invalid credential issuance metadata: 'namespaces' is missing`)
5354

5455
const issuerCertificate = X509Certificate.fromEncodedCertificate(mdlIssuerCertificate)
56+
if (mdlIssuerPrivateKeyJwk) {
57+
issuerCertificate.publicJwk.keyId = mdlIssuerPrivateKeyJwk.kid
58+
const kms = agentContext.resolve(Kms.KeyManagementApi)
59+
try {
60+
await kms.importKey({ privateJwk: mdlIssuerPrivateKeyJwk as unknown as Kms.KmsJwkPrivate })
61+
} catch (e) {
62+
const isDuplicateEntry = e instanceof Error && e.message === 'Duplicate entry'
63+
if (!(e instanceof Kms.KeyManagementKeyExistsError) && !isDuplicateEntry) throw e
64+
}
65+
}
5566
const holderKey = holderBinding.keys[0]?.jwk
5667
if (!holderKey) throw new Error('No holder key found for mdoc binding')
5768

heka-wallet/app/metro.config.js

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,12 @@ const workspaceDir = path.join(projectDir, '../')
77

88
const nodeModulesDir = path.join(workspaceDir, 'node_modules')
99

10+
// Resolve the single canonical copy of @peculiar/asn1-schema at config load time.
11+
// Multiple nested copies (2.3.8) exist under @credo-ts/core, @peculiar/x509,
12+
// webcrypto-core, and @peculiar/webcrypto. They each get a separate AsnSchemaStorage
13+
// instance causing "Cannot get schema for 'Certificate' target" during mDL validation.
14+
const asn1SchemaEntry = require.resolve('@peculiar/asn1-schema', { paths: [workspaceDir] })
15+
1016
const packageDirs = [
1117
fs.realpathSync(path.join(nodeModulesDir, '@hyperledger/aries-oca')),
1218
fs.realpathSync(path.join(nodeModulesDir, '@hyperledger/aries-bifold-core')),
@@ -55,7 +61,23 @@ const config = {
5561
resolver: {
5662
unstable_enablePackageExports: true,
5763
unstable_conditionNames: ['react-native', 'browser', 'import', 'require'],
58-
blacklistRE: exclusionList(extraExclusionlist.map((m) => new RegExp(`^${escape(m)}\\/.*$`))),
64+
blacklistRE: exclusionList([
65+
...extraExclusionlist.map((m) => new RegExp(`^${escape(m)}\\/.*$`)),
66+
// Block all nested copies of @peculiar/asn1-schema so only the workspace-root
67+
// version (2.3.13) is bundled — prevents split AsnSchemaStorage instances.
68+
new RegExp(`^${escape(path.join(nodeModulesDir, '@credo-ts/core/node_modules/@peculiar/asn1-schema'))}\\/.*$`),
69+
new RegExp(`^${escape(path.join(nodeModulesDir, '@peculiar/x509/node_modules/@peculiar/asn1-schema'))}\\/.*$`),
70+
new RegExp(`^${escape(path.join(nodeModulesDir, 'webcrypto-core/node_modules/@peculiar/asn1-schema'))}\\/.*$`),
71+
new RegExp(`^${escape(path.join(nodeModulesDir, '@peculiar/webcrypto/node_modules/@peculiar/asn1-schema'))}\\/.*$`),
72+
]),
73+
resolveRequest: (context, moduleName, platform) => {
74+
if (moduleName === '@peculiar/asn1-schema') {
75+
// Directly return the workspace-root version — bypasses Metro's default
76+
// hierarchical node_modules lookup which would find nested copies first.
77+
return { filePath: asn1SchemaEntry, type: 'sourceFile' }
78+
}
79+
return context.resolveRequest(context, moduleName, platform)
80+
},
5981
assetExts: assetExts.filter((ext) => ext !== 'svg'),
6082
sourceExts: [...sourceExts, 'svg', 'cjs'],
6183
extraNodeModules: {

heka-wallet/app/src/credentials/useCredentials.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { TOKENS, useServices, useStore } from '@hyperledger/aries-bifold-core'
66
import { isEqual } from 'lodash'
77
import { useCallback, useEffect, useState } from 'react'
88

9-
import { useSdJwtVcRecords, useW3cCredentialRecords } from '../contexts'
9+
import { useMdocRecords, useSdJwtVcRecords, useW3cCredentialRecords } from '../contexts'
1010

1111
import { mapCredentialRecord } from './mappers'
1212
import { Credential, CredentialRecord } from './types'
@@ -33,6 +33,7 @@ export const useCredentials = (maxCount?: number): CredentialsState => {
3333

3434
const { w3cCredentialRecords, isLoading: isW3cCredentialsLoading } = useW3cCredentialRecords()
3535
const { sdJwtVcRecords, isLoading: isSdJwtCredentialsLoading } = useSdJwtVcRecords()
36+
const { mdocCredentialRecords, isLoading: isMdocCredentialsLoading } = useMdocRecords()
3637

3738
const credentialExchangeRecords = useCredentialByState([CredentialState.CredentialReceived, CredentialState.Done])
3839
const previousCredentialExchangeRecords = usePrevious(credentialExchangeRecords)
@@ -53,7 +54,7 @@ export const useCredentials = (maxCount?: number): CredentialsState => {
5354
const [isLoading, setIsLoading] = useState(true)
5455

5556
const loadCredentials = useCallback(async () => {
56-
if (!agent?.wallet.isInitialized || isW3cCredentialsLoading || isSdJwtCredentialsLoading) return
57+
if (!agent?.wallet.isInitialized || isW3cCredentialsLoading || isSdJwtCredentialsLoading || isMdocCredentialsLoading) return
5758

5859
setIsLoading(true)
5960
try {
@@ -79,6 +80,7 @@ export const useCredentials = (maxCount?: number): CredentialsState => {
7980
// Filter W3C records that already presented by Anocreds (Credential exchange) records
8081
...w3cCredentialRecords.filter((record) => !w3cAnoncredsRecordIds.includes(record.id)),
8182
...sdJwtVcRecords,
83+
...mdocCredentialRecords,
8284
]
8385

8486
credentialRecords.sort(compareCredentialRecordsByMostRecent)
@@ -98,9 +100,11 @@ export const useCredentials = (maxCount?: number): CredentialsState => {
98100
agent,
99101
isSdJwtCredentialsLoading,
100102
isW3cCredentialsLoading,
103+
isMdocCredentialsLoading,
101104
anoncredsCredentials,
102105
w3cCredentialRecords,
103106
sdJwtVcRecords,
107+
mdocCredentialRecords,
104108
store.preferences.developerModeEnabled,
105109
credentialHideList,
106110
maxCount,

heka-wallet/app/src/credentials/useOpenIdHandlers.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import {
66
JwaSignatureAlgorithm,
77
JwkDidCreateOptions,
88
KeyDidCreateOptions,
9+
Mdoc,
10+
MdocRecord,
911
SdJwtVcRecord,
1012
W3cCredentialRecord,
1113
} from '@credo-ts/core'
@@ -234,13 +236,15 @@ export const useOpenIdHandlers = () => {
234236
const [firstCredential] = credentials
235237
if (!firstCredential) throw new Error('Error retrieving credential.')
236238

237-
let record: SdJwtVcRecord | W3cCredentialRecord
239+
let record: SdJwtVcRecord | W3cCredentialRecord | MdocRecord
238240

239241
// TODO: Add claimFormat to SdJwtVc
240242
if ('compact' in firstCredential.credential) {
241243
record = new SdJwtVcRecord({
242244
compactSdJwtVc: firstCredential.credential.compact,
243245
})
246+
} else if (firstCredential.credential instanceof Mdoc) {
247+
record = new MdocRecord({ mdoc: firstCredential.credential })
244248
} else {
245249
record = new W3cCredentialRecord({
246250
credential: firstCredential.credential,

heka-wallet/app/src/utils/agent.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import {
1414
PeerDidRegistrar,
1515
PeerDidResolver,
1616
WebDidResolver,
17+
X509Module,
1718
} from '@credo-ts/core'
1819
import { createPeerDidFromServices, routingToServices } from '@credo-ts/core/build/modules/connections/services/helpers'
1920
import { HederaAnonCredsRegistry, HederaDidRegistrar, HederaDidResolver, HederaModule } from '@credo-ts/hedera'
@@ -108,6 +109,11 @@ export async function createAgent({ credentials, indyLedgers, indyBesuConfig, wa
108109
},
109110
],
110111
}),
112+
x509: new X509Module({
113+
trustedCertificates: [
114+
'MIIBmTCCAT+gAwIBAgIUJeybJ59oAtHqC1RAo1ySrqCUdyEwCgYIKoZIzj0EAwIwIjELMAkGA1UEBhMCVVMxEzARBgNVBAMMCk1ETCBJc3N1ZXIwHhcNMjYwMzE2MTIxOTQxWhcNMjcwMzE2MTIxOTQxWjAiMQswCQYDVQQGEwJVUzETMBEGA1UEAwwKTURMIElzc3VlcjBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABBl3oLjMhrHR3uZxNUdyxEboo7OGsvqLdn5j3HGHFg+lL77U3yvUFZcYFtPr8Bc49tc8eRkbIBQaf3ebikmEIAKjUzBRMB0GA1UdDgQWBBRgERzGBBlp2rVChhxwubMS3rSP9jAfBgNVHSMEGDAWgBRgERzGBBlp2rVChhxwubMS3rSP9jAPBgNVHRMBAf8EBTADAQH/MAoGCCqGSM49BAMCA0gAMEUCIQDXrhjDDTiR3yMVD1q0yjadoqC3p/5Zc8RswG20M0IBDwIgPblEkraphygeYXDEzEnuIour1SeKHsf4JJuyn2mPkYo=',
115+
],
116+
}),
111117
},
112118
})
113119
}

0 commit comments

Comments
 (0)