Skip to content

Commit

Permalink
Merge branch 'main' into feature/cheqd-anoncreds-revocation-registry
Browse files Browse the repository at this point in the history
  • Loading branch information
TimoGlastra committed Jul 17, 2024
2 parents 1d697d5 + 7d5ebba commit 71a4192
Show file tree
Hide file tree
Showing 41 changed files with 2,357 additions and 1,900 deletions.
6 changes: 6 additions & 0 deletions .changeset/nice-meals-rest.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@credo-ts/core": patch
"@credo-ts/openid4vc": patch
---

Adds support for issuance and verification of SD-JWT VCs using x509 certificates over OpenID4VC, as well as adds support for the `x509_san_uri` and `x509_san_dns` values for `client_id_scheme`. It also adds support for OpenID4VP Draft 20
5 changes: 5 additions & 0 deletions .changeset/pink-icons-hope.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@credo-ts/core': patch
---

Treat an empty received handshake_protocols array as undefined
32 changes: 23 additions & 9 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,14 @@ on:
push:
branches:
- main
- '**-pre'
pull_request:
branches:
- main
types:
- opened
- synchronize
- reopened
- labeled

permissions:
pull-requests: write
Expand All @@ -14,7 +21,7 @@ jobs:
release:
name: Release
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
steps:
- name: Checkout Repo
uses: actions/checkout@v4
Expand Down Expand Up @@ -57,8 +64,15 @@ jobs:
release-unstable:
name: Release Unstable
runs-on: ubuntu-latest
if: "!startsWith(github.event.head_commit.message, 'chore(release): version')"
if: "(github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == 'openwallet-foundation/credo-ts') || (github.event_name == 'push' && !startsWith(github.event.head_commit.message, 'chore(release): version'))"
steps:
- uses: snnaplab/get-labels-action@v1
if: github.event_name == 'pull_request'

# exit if pull request and no alpha-release tag
- if: github.event_name == 'pull_request' && !contains(fromJSON(env.LABELS), 'alpha-release')
run: exit 0

- name: Checkout Repo
uses: actions/checkout@v4

Expand All @@ -84,6 +98,10 @@ jobs:
NPM_TOKEN: ${{ secrets.NPM_PUBLISH }}

- name: Create unstable release
env:
TAG: ${{ github.event_name == 'push' && 'alpha' || format('pr-{0}', github.event.number) }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NPM_TOKEN: ${{ secrets.NPM_PUBLISH }}
run: |
# this ensures there's always a patch release created
cat << 'EOF' > .changeset/snapshot-template-changeset.md
Expand All @@ -94,16 +112,12 @@ jobs:
snapshot release
EOF
pnpm changeset version --snapshot alpha
pnpm changeset version --snapshot ${{ env.TAG }}
pnpm build
pnpm changeset publish --tag alpha
pnpm changeset publish --tag ${{ env.TAG }}
CURRENT_PACKAGE_VERSION=$(node -p "require('./packages/core/package.json').version")
git config --global user.name "github-actions[bot]"
git config --global user.email "41898282+github-actions[bot]@users.noreply.github.com"
git tag v$CURRENT_PACKAGE_VERSION
git push origin v$CURRENT_PACKAGE_VERSION --no-verify
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NPM_TOKEN: ${{ secrets.NPM_PUBLISH }}
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
"demo-openid",
"samples/*"
],
"packageManager": "[email protected]",
"repository": {
"url": "https://github.com/openwallet-foundation/credo-ts",
"type": "git"
Expand Down
3 changes: 1 addition & 2 deletions packages/core/src/agent/Agent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { Subject } from 'rxjs'
import { concatMap, takeUntil } from 'rxjs/operators'

import { InjectionSymbols } from '../constants'
import { SigningProviderToken, X509Service } from '../crypto'
import { SigningProviderToken } from '../crypto'
import { JwsService } from '../crypto/JwsService'
import { CredoError } from '../error'
import { DependencyManager } from '../plugins'
Expand Down Expand Up @@ -59,7 +59,6 @@ export class Agent<AgentModules extends AgentModulesInput = any> extends BaseAge
dependencyManager.registerSingleton(DidCommMessageRepository)
dependencyManager.registerSingleton(StorageVersionRepository)
dependencyManager.registerSingleton(StorageUpdateService)
dependencyManager.registerSingleton(X509Service)

// This is a really ugly hack to make tsyringe work without any SigningProviders registered
// It is currently impossible to use @injectAll if there are no instances registered for the
Expand Down
2 changes: 2 additions & 0 deletions packages/core/src/agent/AgentModules.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { ProofsModule } from '../modules/proofs'
import { MediationRecipientModule, MediatorModule } from '../modules/routing'
import { SdJwtVcModule } from '../modules/sd-jwt-vc'
import { W3cCredentialsModule } from '../modules/vc'
import { X509Module } from '../modules/x509'
import { WalletModule } from '../wallet'

/**
Expand Down Expand Up @@ -135,6 +136,7 @@ function getDefaultAgentModules() {
cache: () => new CacheModule(),
pex: () => new DifPresentationExchangeModule(),
sdJwtVc: () => new SdJwtVcModule(),
x509: () => new X509Module(),
} as const
}

Expand Down
4 changes: 4 additions & 0 deletions packages/core/src/agent/BaseAgent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { ProofsApi } from '../modules/proofs'
import { MediatorApi, MediationRecipientApi } from '../modules/routing'
import { SdJwtVcApi } from '../modules/sd-jwt-vc'
import { W3cCredentialsApi } from '../modules/vc/W3cCredentialsApi'
import { X509Api } from '../modules/x509'
import { StorageUpdateService } from '../storage'
import { UpdateAssistant } from '../storage/migration/UpdateAssistant'
import { WalletApi } from '../wallet'
Expand Down Expand Up @@ -59,6 +60,7 @@ export abstract class BaseAgent<AgentModules extends ModulesMap = EmptyModuleMap
public readonly oob: OutOfBandApi
public readonly w3cCredentials: W3cCredentialsApi
public readonly sdJwtVc: SdJwtVcApi
public readonly x509: X509Api

public readonly modules: AgentApi<WithoutDefaultModules<AgentModules>>

Expand Down Expand Up @@ -108,6 +110,7 @@ export abstract class BaseAgent<AgentModules extends ModulesMap = EmptyModuleMap
this.oob = this.dependencyManager.resolve(OutOfBandApi)
this.w3cCredentials = this.dependencyManager.resolve(W3cCredentialsApi)
this.sdJwtVc = this.dependencyManager.resolve(SdJwtVcApi)
this.x509 = this.dependencyManager.resolve(X509Api)

const defaultApis = [
this.connections,
Expand All @@ -124,6 +127,7 @@ export abstract class BaseAgent<AgentModules extends ModulesMap = EmptyModuleMap
this.oob,
this.w3cCredentials,
this.sdJwtVc,
this.x509,
]

// Set the api of the registered modules on the agent, excluding the default apis
Expand Down
4 changes: 4 additions & 0 deletions packages/core/src/agent/__tests__/AgentModules.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { ProofsModule } from '../../modules/proofs'
import { MediationRecipientModule, MediatorModule } from '../../modules/routing'
import { SdJwtVcModule } from '../../modules/sd-jwt-vc'
import { W3cCredentialsModule } from '../../modules/vc'
import { X509Module } from '../../modules/x509'
import { DependencyManager, injectable } from '../../plugins'
import { WalletModule } from '../../wallet'
import { extendModulesWithDefaultModules, getAgentApi } from '../AgentModules'
Expand Down Expand Up @@ -72,6 +73,7 @@ describe('AgentModules', () => {
oob: expect.any(OutOfBandModule),
w3cCredentials: expect.any(W3cCredentialsModule),
sdJwtVc: expect.any(SdJwtVcModule),
x509: expect.any(X509Module),
cache: expect.any(CacheModule),
})
})
Expand Down Expand Up @@ -99,6 +101,7 @@ describe('AgentModules', () => {
w3cCredentials: expect.any(W3cCredentialsModule),
cache: expect.any(CacheModule),
sdJwtVc: expect.any(SdJwtVcModule),
x509: expect.any(X509Module),
myModule,
})
})
Expand Down Expand Up @@ -128,6 +131,7 @@ describe('AgentModules', () => {
w3cCredentials: expect.any(W3cCredentialsModule),
cache: expect.any(CacheModule),
sdJwtVc: expect.any(SdJwtVcModule),
x509: expect.any(X509Module),
myModule,
})
})
Expand Down
51 changes: 36 additions & 15 deletions packages/core/src/crypto/JwsService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,28 @@ import { injectable } from '../plugins'
import { isJsonObject, JsonEncoder, TypedArrayEncoder } from '../utils'
import { WalletError } from '../wallet/error'

import { X509Service } from './../modules/x509/X509Service'
import { JWS_COMPACT_FORMAT_MATCHER } from './JwsTypes'
import { getJwkFromJson, getJwkFromKey } from './jose/jwk'
import { JwtPayload } from './jose/jwt'

@injectable()
export class JwsService {
private async createJwsBase(agentContext: AgentContext, options: CreateJwsBaseOptions) {
const { jwk, alg } = options.protectedHeaderOptions
const { jwk, alg, x5c } = options.protectedHeaderOptions
const keyJwk = getJwkFromKey(options.key)

// Make sure the options.x5c and x5c from protectedHeader are the same.
if (x5c) {
const certificate = X509Service.getLeafCertificate(agentContext, { certificateChain: x5c })
if (
certificate.publicKey.keyType !== options.key.keyType ||
!certificate.publicKey.publicKey.equals(options.key.publicKey)
) {
throw new CredoError(`Protected header x5c does not match key for signing.`)
}
}

// Make sure the options.key and jwk from protectedHeader are the same.
if (jwk && (jwk.key.keyType !== options.key.keyType || !jwk.key.publicKey.equals(options.key.publicKey))) {
throw new CredoError(`Protected header JWK does not match key for signing.`)
Expand Down Expand Up @@ -141,7 +153,7 @@ export class JwsService {
throw new CredoError('Unable to verify JWS, protected header alg is not provided or not a string.')
}

const jwk = await this.jwkFromJws({
const jwk = await this.jwkFromJws(agentContext, {
jws,
payload,
protectedHeader: {
Expand Down Expand Up @@ -191,11 +203,8 @@ export class JwsService {
}

private buildProtected(options: JwsProtectedHeaderOptions) {
if (!options.jwk && !options.kid) {
throw new CredoError('Both JWK and kid are undefined. Please provide one or the other.')
}
if (options.jwk && options.kid) {
throw new CredoError('Both JWK and kid are provided. Please only provide one of the two.')
if ([options.jwk, options.kid, options.x5c].filter(Boolean).length != 1) {
throw new CredoError('Only one of JWK, kid or x5c can and must be provided.')
}

return {
Expand All @@ -206,16 +215,28 @@ export class JwsService {
}
}

private async jwkFromJws(options: {
jws: JwsDetachedFormat
protectedHeader: { alg: string; [key: string]: unknown }
payload: string
jwkResolver?: JwsJwkResolver
}): Promise<Jwk> {
private async jwkFromJws(
agentContext: AgentContext,
options: {
jws: JwsDetachedFormat
protectedHeader: { alg: string; [key: string]: unknown }
payload: string
jwkResolver?: JwsJwkResolver
}
): Promise<Jwk> {
const { protectedHeader, jwkResolver, jws, payload } = options

if (protectedHeader.jwk && protectedHeader.kid) {
throw new CredoError('Both JWK and kid are defined in the protected header. Only one of the two is allowed.')
if ([protectedHeader.jwk, protectedHeader.kid, protectedHeader.x5c].filter(Boolean).length > 1) {
throw new CredoError('Only one of jwk, kid and x5c headers can and must be provided.')
}

if (protectedHeader.x5c) {
if (!Array.isArray(protectedHeader.x5c) || typeof protectedHeader.x5c[0] !== 'string') {
throw new CredoError('x5c header is not a valid JSON array of string.')
}

const certificate = X509Service.getLeafCertificate(agentContext, { certificateChain: protectedHeader.x5c })
return getJwkFromKey(certificate.publicKey)
}

// Jwk
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/crypto/JwsTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export interface JwsProtectedHeaderOptions {
alg: JwaSignatureAlgorithm | string
kid?: Kid
jwk?: Jwk
x5c?: string[]
[key: string]: unknown
}

Expand Down
3 changes: 1 addition & 2 deletions packages/core/src/crypto/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
export { JwsService } from './JwsService'

export { JwsDetachedFormat } from './JwsTypes'
export { JwsDetachedFormat, JwsProtectedHeaderOptions } from './JwsTypes'
export * from './keyUtils'

export { KeyBackend } from './KeyBackend'
Expand All @@ -13,4 +13,3 @@ export * from './signing-provider'

export * from './webcrypto'
export * from './hashes'
export * from './x509'
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ describe('CredoWebCrypto', () => {
{ hash: 'SHA-256', name: 'ECDSA', namedCurve: 'P-256' },
{ hash: 'SHA-256', name: 'ECDSA', namedCurve: 'P-384' },
{ hash: 'SHA-256', name: 'ECDSA', namedCurve: 'K-256' },
'Ed25519',
{ name: 'Ed25519' },
]

beforeAll(async () => {
Expand Down
6 changes: 3 additions & 3 deletions packages/core/src/crypto/webcrypto/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,15 @@ export type EcdsaParams = {
hash: { name: HashAlgorithmIdentifier } | HashAlgorithmIdentifier
}

export type Ed25519Params = 'Ed25519'
export type Ed25519Params = { name: 'Ed25519' }

/*
*
* Key Generation Parameters
*
*/

export type Ed25519KeyGenParams = 'Ed25119'
export type Ed25519KeyGenParams = { name: 'Ed25519' }

export type EcKeyGenParams = {
name: 'ECDSA'
Expand All @@ -45,7 +45,7 @@ export type EcKeyGenParams = {
*
*/

export type Ed25519KeyImportParams = 'Ed25519'
export type Ed25519KeyImportParams = { name: 'Ed25519' }

export type EcKeyImportParams = {
name: 'ECDSA'
Expand Down
20 changes: 17 additions & 3 deletions packages/core/src/crypto/webcrypto/utils/keyAlgorithmConversion.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,23 @@ import {
x25519AlgorithmIdentifier,
} from '../algorithmIdentifiers'

export const credoKeyTypeIntoCryptoKeyAlgorithm = (keyType: KeyType): KeyGenAlgorithm => {
switch (keyType) {
case KeyType.Ed25519:
return { name: 'Ed25519' }
case KeyType.P256:
return { name: 'ECDSA', namedCurve: 'P-256' }
case KeyType.P384:
return { name: 'ECDSA', namedCurve: 'P-384' }
case KeyType.K256:
return { name: 'ECDSA', namedCurve: 'K-256' }
default:
throw new CredoWebCryptoError(`Unsupported key type: ${keyType}`)
}
}

export const cryptoKeyAlgorithmToCredoKeyType = (algorithm: KeyGenAlgorithm): KeyType => {
const algorithmName = typeof algorithm === 'string' ? algorithm.toUpperCase() : algorithm.name.toUpperCase()
const algorithmName = algorithm.name.toUpperCase()
switch (algorithmName) {
case 'ED25519':
return KeyType.Ed25519
Expand All @@ -29,9 +44,8 @@ export const cryptoKeyAlgorithmToCredoKeyType = (algorithm: KeyGenAlgorithm): Ke
default:
throw new CredoWebCryptoError(`Unsupported curve for ECDSA: ${(algorithm as EcKeyGenParams).namedCurve}`)
}
default:
throw new CredoWebCryptoError(`Unsupported algorithm: ${algorithmName}`)
}
throw new CredoWebCryptoError(`Unsupported algorithm: ${algorithmName}`)
}

export const spkiAlgorithmIntoCredoKeyType = (algorithm: AlgorithmIdentifier): KeyType => {
Expand Down
3 changes: 0 additions & 3 deletions packages/core/src/crypto/x509/index.ts

This file was deleted.

2 changes: 2 additions & 0 deletions packages/core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ export { ReturnRouteTypes } from './decorators/transport/TransportDecorator'
export * from './plugins'
export * from './transport'
export * from './modules/basic-messages'
export * from './modules/x509'
export * from './modules/common'
export * from './modules/credentials'
export * from './modules/discover-features'
Expand Down Expand Up @@ -95,6 +96,7 @@ export { encodeAttachment, isLinkedAttachment } from './utils/attachment'
export type { Optional } from './utils'
export { MessageValidator } from './utils/MessageValidator'
export { LinkedAttachment, LinkedAttachmentOptions } from './utils/LinkedAttachment'
export { getDomainFromUrl } from './utils/domain'
import { parseInvitationUrl } from './utils/parseInvitation'
import { uuid, isValidUuid } from './utils/uuid'

Expand Down
Loading

0 comments on commit 71a4192

Please sign in to comment.