Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: json ld fixes and aca-py interop fixes #1865

Open
wants to merge 19 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
103 changes: 56 additions & 47 deletions packages/core/src/modules/connections/DidExchangeProtocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -523,64 +523,73 @@ export class DidExchangeProtocol {
if (message instanceof DidExchangeResponseMessage) {
const didRotateAttachment = message.didRotate

if (!didRotateAttachment) {
throw new DidExchangeProblemReportError('DID Rotate attachment is missing.', {
problemCode: DidExchangeProblemReportReason.ResponseNotAccepted,
})
}

const jws = didRotateAttachment.data.jws

if (!jws) {
throw new DidExchangeProblemReportError('DID Rotate signature is missing.', {
problemCode: DidExchangeProblemReportReason.ResponseNotAccepted,
})
}
if (didRotateAttachment) {
try {
const jws = didRotateAttachment.data.jws

if (!jws) {
throw new DidExchangeProblemReportError('DID Rotate signature is missing.', {
problemCode: DidExchangeProblemReportReason.ResponseNotAccepted,
})
}

if (!didRotateAttachment.data.base64) {
throw new CredoError('DID Rotate attachment is missing base64 property for signed did.')
}
if (!didRotateAttachment.data.base64) {
throw new CredoError('DID Rotate attachment is missing base64 property for signed did.')
}

// JWS payload must be base64url encoded
const base64UrlPayload = base64ToBase64URL(didRotateAttachment.data.base64)
const signedDid = TypedArrayEncoder.fromBase64(base64UrlPayload).toString()
// JWS payload must be base64url encoded
const base64UrlPayload = base64ToBase64URL(didRotateAttachment.data.base64)
const signedDid = TypedArrayEncoder.fromBase64(base64UrlPayload).toString()

if (signedDid !== message.did) {
throw new CredoError(
`DID Rotate attachment's did ${message.did} does not correspond to message did ${message.did}`
)
}

const { isValid, signerKeys } = await this.jwsService.verifyJws(agentContext, {
jws: {
...jws,
payload: base64UrlPayload,
},
jwkResolver: ({ jws: { header } }) => {
if (typeof header.kid !== 'string' || !isDid(header.kid, 'key')) {
throw new CredoError('JWS header kid must be a did:key DID.')
if (signedDid !== message.did) {
throw new CredoError(
`DID Rotate attachment's did ${message.did} does not correspond to message did ${message.did}`
)
}

const didKey = DidKey.fromDid(header.kid)
return getJwkFromKey(didKey.key)
},
})

if (!isValid || !signerKeys.every((key) => invitationKeysBase58?.includes(key.publicKeyBase58))) {
throw new DidExchangeProblemReportError(
`DID Rotate signature is invalid. isValid: ${isValid} signerKeys: ${JSON.stringify(
signerKeys
)} invitationKeys:${JSON.stringify(invitationKeysBase58)}`,
{
problemCode: DidExchangeProblemReportReason.ResponseNotAccepted,
const { isValid, signerKeys } = await this.jwsService.verifyJws(agentContext, {
jws: {
...jws,
payload: base64UrlPayload,
},
jwkResolver: ({ jws: { header } }) => {
if (typeof header.kid !== 'string' || !isDid(header.kid, 'key')) {
throw new CredoError('JWS header kid must be a did:key DID.')
}

const didKey = DidKey.fromDid(header.kid)
return getJwkFromKey(didKey.key)
},
})

if (!isValid || !signerKeys.every((key) => invitationKeysBase58?.includes(key.publicKeyBase58))) {
throw new DidExchangeProblemReportError(
`DID Rotate signature is invalid. isValid: ${isValid} signerKeys: ${JSON.stringify(
signerKeys
)} invitationKeys:${JSON.stringify(invitationKeysBase58)}`,
{
problemCode: DidExchangeProblemReportReason.ResponseNotAccepted,
}
)
}
)
} catch (e) {
this.logger.warn(`Document does not contain didRotate. Error: ${e.message}`)
KolbyRKunz marked this conversation as resolved.
Show resolved Hide resolved
}
}
}

// Now resolve the document related to the did (which can be either a public did or an inline did)
try {
return await agentContext.dependencyManager.resolve(DidsApi).resolveDidDocument(message.did)
if (message.did.startsWith('did:'))
KolbyRKunz marked this conversation as resolved.
Show resolved Hide resolved
return await agentContext.dependencyManager.resolve(DidsApi).resolveDidDocument(message.did)
else {
const did = message.didDoc?.getDataAsJson<DidDocument>().id
if (!did)
throw new DidExchangeProblemReportError('Cannot resolve did', {
problemCode: DidExchangeProblemReportReason.ResponseNotAccepted,
})
return await agentContext.dependencyManager.resolve(DidsApi).resolveDidDocument(did)
}
} catch (error) {
const problemCode =
message instanceof DidExchangeRequestMessage
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ export class V2OfferCredentialMessage extends AgentMessage {

@Expose({ name: 'credential_preview' })
@Type(() => V2CredentialPreview)
@IsOptional()
@ValidateNested()
@IsInstance(V2CredentialPreview)
public credentialPreview?: V2CredentialPreview
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,17 @@ export function isBls12381G1Key2020(verificationMethod: VerificationMethod): ver
* Get a key from a Bls12381G1Key2020 verification method.
*/
export function getKeyFromBls12381G1Key2020(verificationMethod: Bls12381G1Key2020) {
if (!verificationMethod.publicKeyBase58) {
throw new CredoError('verification method is missing publicKeyBase58')
if (verificationMethod.publicKeyBase58) {
return Key.fromPublicKeyBase58(verificationMethod.publicKeyBase58, KeyType.Bls12381g1)
}
if (verificationMethod.publicKeyMultibase) {
const key = Key.fromFingerprint(verificationMethod.publicKeyMultibase)
if (key.keyType === KeyType.Bls12381g1) return key
else
throw new CredoError(
`Unexpected key type from resolving multibase encoding, key type was ${key.keyType} but expected ${KeyType.Bls12381g1}}`
)
}

return Key.fromPublicKeyBase58(verificationMethod.publicKeyBase58, KeyType.Bls12381g1)
throw new CredoError('verification method is missing publicKeyBase58')
KolbyRKunz marked this conversation as resolved.
Show resolved Hide resolved
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,17 @@ export function isBls12381G2Key2020(verificationMethod: VerificationMethod): ver
* Get a key from a Bls12381G2Key2020 verification method.
*/
export function getKeyFromBls12381G2Key2020(verificationMethod: Bls12381G2Key2020) {
if (!verificationMethod.publicKeyBase58) {
throw new CredoError('verification method is missing publicKeyBase58')
if (verificationMethod.publicKeyBase58) {
return Key.fromPublicKeyBase58(verificationMethod.publicKeyBase58, KeyType.Bls12381g2)
}
if (verificationMethod.publicKeyMultibase) {
const key = Key.fromFingerprint(verificationMethod.publicKeyMultibase)
if (key.keyType === KeyType.Bls12381g2) return key
else
throw new CredoError(
`Unexpected key type from resolving multibase encoding, key type was ${key.keyType} but expected ${KeyType.Bls12381g2}}`
)
}

return Key.fromPublicKeyBase58(verificationMethod.publicKeyBase58, KeyType.Bls12381g2)
throw new CredoError('verification method is missing publicKeyBase58')
}
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,17 @@ export function isEcdsaSecp256k1VerificationKey2019(
* Get a key from a EcdsaSecp256k1VerificationKey2019 verification method.
*/
export function getKeyFromEcdsaSecp256k1VerificationKey2019(verificationMethod: EcdsaSecp256k1VerificationKey2019) {
if (!verificationMethod.publicKeyBase58) {
throw new CredoError('verification method is missing publicKeyBase58')
if (verificationMethod.publicKeyBase58) {
return Key.fromPublicKeyBase58(verificationMethod.publicKeyBase58, KeyType.K256)
}
if (verificationMethod.publicKeyMultibase) {
KolbyRKunz marked this conversation as resolved.
Show resolved Hide resolved
const key = Key.fromFingerprint(verificationMethod.publicKeyMultibase)
if (key.keyType === KeyType.K256) return key
else
throw new CredoError(
`Unexpected key type from resolving multibase encoding, key type was ${key.keyType} but expected ${KeyType.K256}}`
)
}

return Key.fromPublicKeyBase58(verificationMethod.publicKeyBase58, KeyType.K256)
throw new CredoError('verification method is missing publicKeyBase58')
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,17 @@ export function isEd25519VerificationKey2018(
* Get a key from a Ed25519VerificationKey2018 verification method.
*/
export function getKeyFromEd25519VerificationKey2018(verificationMethod: Ed25519VerificationKey2018) {
if (!verificationMethod.publicKeyBase58) {
throw new CredoError('verification method is missing publicKeyBase58')
if (verificationMethod.publicKeyBase58) {
return Key.fromPublicKeyBase58(verificationMethod.publicKeyBase58, KeyType.Ed25519)
}
if (verificationMethod.publicKeyMultibase) {
const key = Key.fromFingerprint(verificationMethod.publicKeyMultibase)
if (key.keyType === KeyType.Ed25519) return key
else
throw new CredoError(
`Unexpected key type from resolving multibase encoding, key type was ${key.keyType} but expected ${KeyType.Ed25519}`
)
}

return Key.fromPublicKeyBase58(verificationMethod.publicKeyBase58, KeyType.Ed25519)
throw new CredoError('verification method is missing publicKeyBase58')
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,17 @@ export function isX25519KeyAgreementKey2019(
* Get a key from a X25519KeyAgreementKey2019 verification method.
*/
export function getKeyFromX25519KeyAgreementKey2019(verificationMethod: X25519KeyAgreementKey2019) {
if (!verificationMethod.publicKeyBase58) {
throw new CredoError('verification method is missing publicKeyBase58')
if (verificationMethod.publicKeyBase58) {
return Key.fromPublicKeyBase58(verificationMethod.publicKeyBase58, KeyType.X25519)
}
if (verificationMethod.publicKeyMultibase) {
const key = Key.fromFingerprint(verificationMethod.publicKeyMultibase)
if (key.keyType === KeyType.X25519) return key
else
throw new CredoError(
`Unexpected key type from resolving multibase encoding, key type was ${key.keyType} but expected ${KeyType.X25519}`
)
}

return Key.fromPublicKeyBase58(verificationMethod.publicKeyBase58, KeyType.X25519)
throw new CredoError('verification method is missing publicKeyBase58')
}
2 changes: 1 addition & 1 deletion packages/core/src/modules/dids/methods/peer/didPeer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { CredoError } from '../../../../error'
import { getAlternativeDidsForNumAlgo4Did } from './peerDidNumAlgo4'

const PEER_DID_REGEX = new RegExp(
'^did:peer:(([01](z)([1-9a-km-zA-HJ-NP-Z]{5,200}))|(2((.[AEVID](z)([1-9a-km-zA-HJ-NP-Z]{5,200}))+(.(S)[0-9a-zA-Z=]*)?))|([4](z[1-9a-km-zA-HJ-NP-Z]{46})(:z[1-9a-km-zA-HJ-NP-Z]{6,}){0,1}))$'
'^did:peer:(([01](z)([1-9a-km-zA-HJ-NP-Z]{5,200}))|(2((.[AEVID](z)([1-9a-km-zA-HJ-NP-Z]{5,200}))+(.(S)[0-9a-zA-Z=]*)*))|([4](z[1-9a-km-zA-HJ-NP-Z]{46})(:z[1-9a-km-zA-HJ-NP-Z]{6,}){0,1}))$'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does this change in regex mean?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This regex is specifically changed for did:peer:2. With the updates to the protocol of did:peer:2 the services on the encoded version are not always a list and can be multiple service appended instead.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you have an example did:peer so I can add a test covering this?

)

export function isValidPeerDid(did: string): boolean {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,10 +69,13 @@ export function didToNumAlgo2DidDocument(did: string) {
for (let service of services) {
// Expand abbreviations used for service key/values
service = expandServiceAbbreviations(service)

service.id = `${did}#${service.type.toLowerCase()}-${serviceIndex++}`

didDocument.addService(JsonTransformer.fromJSON(service, DidDocumentService))
try {
didDocument.addService(JsonTransformer.fromJSON(service, DidDocumentService))
} catch (e) {
//Ignore a service if we do not recognize it
serviceIndex--
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should nog skip the service index i think, as then the index references will be messed up. I think we can skip adding it to the document, but also skip the index

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also furious when does this happen? If a json transform fails? I don't think we should just swallow such an error. Can you explain why this is needed?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking at this I realize this was a bit of a hold over from implementing this functionality in another library that had much stricter serialization rules than typescript does, specifically with unknown additional keys appearing causing errors. With the regex adjustment this is redundant for Credo.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed this logic as it is not needed in Credo because serialization is more fault tolerant than I thought at development time.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code is still here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With my comment below about did:peer:2 adding multiple services this was added because some of the agents and mediator I tested against included didCommV2 services that were intended to be ignored by the agent if they do not use didCommV2. Have this try catch here successfully ignores it and keeps the indexes of the services we do recognize consistent.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But by doing this the references between implementations will mess up. We would skip didcomm v2 and others would not skip it and thus the same id could point to different services. I think we should not do this

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AFter looking at this in more depth -- we don't handle the DIDComm v2 serviceEndpoint being an object. So instead of doing this, we need to fix that parsing of serviceEndpoint instead

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you have example did:peer:2 document with DIDComm v2 service for test? I have a PR locally that correctly fixes #1789, and thus should also fix this issue

}
}
}
// Otherwise we can be sure it is a key
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -592,7 +592,7 @@ export class DifPresentationExchangeService {
for (const inputDescriptor of pd.input_descriptors) {
for (const schema of inputDescriptor.schema) {
w3cQuery.push({
$or: [{ expandedType: [schema.uri] }, { contexts: [schema.uri] }, { type: [schema.uri] }],
$or: [{ expandedTypes: [schema.uri] }, { contexts: [schema.uri] }, { types: [schema.uri] }],
})
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import { createWalletKeyPairClass } from '../../../crypto/WalletKeyPair'
import { CredoError } from '../../../error'
import { injectable } from '../../../plugins'
import { asArray, JsonTransformer } from '../../../utils'
import { VerificationMethod } from '../../dids'
import { DidsApi, VerificationMethod } from '../../dids'
import { getKeyFromVerificationMethod } from '../../dids/domain/key-type'
import { W3cCredentialsModuleConfig } from '../W3cCredentialsModuleConfig'
import { w3cDate } from '../util'
Expand Down Expand Up @@ -339,12 +339,23 @@ export class W3cJsonLdCredentialService {
agentContext: AgentContext,
verificationMethod: string
): Promise<Key> {
const documentLoader = this.w3cCredentialsModuleConfig.documentLoader(agentContext)
const verificationMethodObject = await documentLoader(verificationMethod)
const verificationMethodClass = JsonTransformer.fromJSON(verificationMethodObject.document, VerificationMethod)

const key = getKeyFromVerificationMethod(verificationMethodClass)
return key
if (!verificationMethod.startsWith('did:peer')) {
KolbyRKunz marked this conversation as resolved.
Show resolved Hide resolved
const documentLoader = this.w3cCredentialsModuleConfig.documentLoader(agentContext)
const verificationMethodObject = await documentLoader(verificationMethod)
const verificationMethodClass = JsonTransformer.fromJSON(verificationMethodObject.document, VerificationMethod)

const key = getKeyFromVerificationMethod(verificationMethodClass)
return key
} else {
const [did, keyid] = verificationMethod.split('#')
const didsApi = agentContext.dependencyManager.resolve(DidsApi)
const doc = await didsApi.resolve(did)
if (doc.didDocument) {
const verificationMethodClass = doc.didDocument.dereferenceKey(keyid)
return getKeyFromVerificationMethod(verificationMethodClass)
}
throw new CredoError(`Could not resolve verification method with id ${verificationMethod}`)
}
}

private getSignatureSuitesForCredential(agentContext: AgentContext, credential: W3cJsonLdVerifiableCredential) {
Expand Down
Loading