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

feat: openid4vp #21

Merged
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
8b84e2c
feat: openid4vp alpha
auer-martin Jan 26, 2025
3413eb6
feat: dcql support
auer-martin Feb 4, 2025
5ec2bad
Merge branch 'main' of github.com:animo/oid4vc-ts
auer-martin Feb 5, 2025
84a8dec
fix: incorporate feedback
auer-martin Feb 10, 2025
7663c71
feat: web-origin support
auer-martin Feb 16, 2025
64623b4
fix: web-origin
auer-martin Feb 17, 2025
6c0ac2e
fix: allow dcql_query to be an object
auer-martin Feb 17, 2025
99cdec6
feat: add dc api response params
auer-martin Feb 17, 2025
53aec5b
fix: client-id-scheme validation
auer-martin Feb 17, 2025
7e08a19
fix: web-origin client-id handling
auer-martin Feb 17, 2025
52a3f3f
fix: remove watch
TimoGlastra Feb 17, 2025
65366cb
fix: export callbacks
TimoGlastra Feb 17, 2025
3a058b1
fix: pass origin
TimoGlastra Feb 18, 2025
e574612
fix: only use actual client_id, not inferred
TimoGlastra Feb 18, 2025
083b286
fix: error-handling
auer-martin Feb 18, 2025
c4172cf
Merge branch 'openid4vp-alpha' of https://github.com/auer-martin/oid4…
auer-martin Feb 18, 2025
2128263
fix: remove js import
auer-martin Feb 18, 2025
e152043
fix: v11 conversion
auer-martin Feb 18, 2025
f7a5276
fix: support web-origin scheme
TimoGlastra Feb 18, 2025
8828bfa
chore: export type
TimoGlastra Feb 18, 2025
9d7a5f0
fix: allow response with dc api
TimoGlastra Feb 23, 2025
223eb72
fix: allow any object for dcql and presentation definition
TimoGlastra Feb 19, 2025
841ce30
prepare for merge
TimoGlastra Feb 19, 2025
b4a7226
docs(changeset): renamed oid4vci to openid4vci. This includes the pac…
TimoGlastra Feb 19, 2025
127d91d
docs(changeset): feat: initial version of openid4vp
TimoGlastra Feb 19, 2025
58c5d8f
align version of oid4vp
TimoGlastra Feb 19, 2025
68885bb
styling
TimoGlastra Feb 19, 2025
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
2 changes: 1 addition & 1 deletion packages/oauth2/package.json
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
{
"name": "@openid4vc/oauth2",
"version": "0.2.0",
"exports": "./src/index.ts",
"files": ["dist"],
"license": "Apache-2.0",
"exports": "./src/index.ts",
"homepage": "https://github.com/openwallet-foundation-labs/oid4vc-ts/tree/main/packages/oauth2",
"repository": {
"type": "git",
Expand Down
2 changes: 1 addition & 1 deletion packages/oauth2/src/Oauth2AuthorizationServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ export interface Oauth2AuthorizationServerOptions {
/**
* Callbacks required for the oauth2 authorization server
*/
callbacks: CallbackContext
callbacks: Omit<CallbackContext, 'decryptJwt' | 'encryptJwe'>
}

export class Oauth2AuthorizationServer {
Expand Down
2 changes: 1 addition & 1 deletion packages/oauth2/src/Oauth2Client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ export interface Oauth2ClientOptions {
/**
* Callbacks required for the oauth2 client
*/
callbacks: Omit<CallbackContext, 'verifyJwt' | 'clientAuthentication'>
callbacks: Omit<CallbackContext, 'verifyJwt' | 'clientAuthentication' | 'decryptJwt' | 'encryptJwe'>
}

export class Oauth2Client {
Expand Down
52 changes: 51 additions & 1 deletion packages/oauth2/src/callbacks.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { Fetch, OrPromise } from '@openid4vc/utils'
import type { ClientAuthenticationCallback } from './client-authentication'
import type { Jwk } from './common/jwk/v-jwk'
import type { JwtHeader, JwtPayload, JwtSigner } from './common/jwt/v-jwt'
import type { JweEncryptor, JwtHeader, JwtPayload, JwtSigner } from './common/jwt/v-jwt'

/**
* Supported hashing algorithms
Expand Down Expand Up @@ -39,6 +39,36 @@ export type VerifyJwtCallback = (
}
>

export interface DecryptJwtCallbackOptions {
jwk: Jwk
}

export type DecryptJwtCallback = (
jwe: string,
options?: DecryptJwtCallbackOptions
) => OrPromise<
| {
decrypted: true
decryptionJwk: Jwk
payload: string
header: JwtHeader
}
| {
decrypted: false
decryptionJwk?: Jwk
payload?: string
header?: JwtHeader
}
>

export type EncryptJweCallback = (
jweEncryptor: JweEncryptor,
data: string
) => OrPromise<{
encryptionJwk: Jwk
jwe: string
}>

/**
* Callback context provides the callbacks that are required for the oid4vc library
*/
Expand All @@ -58,6 +88,16 @@ export interface CallbackContext {
*/
signJwt: SignJwtCallback

/**
* Decrypt jwe callback for decrypting of Json Web Encryptions
*/
decryptJwt: DecryptJwtCallback

/**
* Encrypt jwt callback for encrypting of Json Web Encryptions
*/
encryptJwe: EncryptJweCallback

/**
* Verify jwt callback for verification of Json Web Tokens
*/
Expand All @@ -83,4 +123,14 @@ export interface CallbackContext {
* scenarios where multiple authorization servers are supported.
*/
clientAuthentication: ClientAuthenticationCallback

/**
* Get the DNS names from a X.509 certificate
*/
getX509SanDnsNames?: (certificate: string) => string[]

/**
* Get the URI names from a X.509 certificate
*/
getX509SanUriNames?: (certificate: string) => string[]
}
6 changes: 6 additions & 0 deletions packages/oauth2/src/common/jwe/v-jwe.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import * as v from 'valibot'

export const vCompactJwe = v.pipe(
v.string(),
v.regex(/^[A-Za-z0-9_-]+\.[A-Za-z0-9_-]*\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$/, 'Not a valid compact jwe')
)
2 changes: 1 addition & 1 deletion packages/oauth2/src/common/jwk/v-jwk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export const vJwk = v.looseObject({
q: v.optional(v.string()),
qi: v.optional(v.string()),
use: v.optional(v.string()),
x5c: v.optional(v.string()),
x5c: v.optional(v.array(v.string())),
x5t: v.optional(v.string()),
'x5t#S256': v.optional(v.string()),
x5u: v.optional(v.string()),
Expand Down
52 changes: 52 additions & 0 deletions packages/oauth2/src/common/jwt/decode-jwt-header.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import {
type BaseSchema,
decodeBase64,
encodeToUtf8String,
parseWithErrorHandling,
stringToJsonWithErrorHandling,
} from '@openid4vc/utils'
import { Oauth2JwtParseError } from '../../error/Oauth2JwtParseError'
import type { InferSchemaOutput } from './decode-jwt'
import { vJwtHeader } from './v-jwt'

export interface DecodeJwtHeaderOptions<HeaderSchema extends BaseSchema | undefined> {
/**
* The comapct encoded jwt
*/
jwe: string

/**
* Schema to use for validating the header. If not provided the
* default `vJwtHeader` schema will be used
*/
headerSchema?: HeaderSchema
}

export type DecodeJweResult<HeaderSchema extends BaseSchema | undefined = undefined> = {
header: InferSchemaOutput<HeaderSchema, typeof vJwtHeader>
}

export function decodeJwtHeader<HeaderSchema extends BaseSchema | undefined = undefined>(
options: DecodeJwtHeaderOptions<HeaderSchema>
): DecodeJweResult<HeaderSchema> {
const jwtParts = options.jwe.split('.')
if (jwtParts.length <= 2) {
throw new Oauth2JwtParseError('Jwt is not a valid jwt, unable to decode')
}

let headerJson: Record<string, unknown>
try {
headerJson = stringToJsonWithErrorHandling(
encodeToUtf8String(decodeBase64(jwtParts[0])),
'Unable to parse jwt header to JSON'
)
} catch (error) {
throw new Oauth2JwtParseError('Error parsing JWT')
}

const header = parseWithErrorHandling(options.headerSchema ?? vJwtHeader, headerJson)

return {
header,
}
}
12 changes: 4 additions & 8 deletions packages/oauth2/src/common/jwt/decode-jwt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ import {
stringToJsonWithErrorHandling,
} from '@openid4vc/utils'
import { Oauth2JwtParseError } from '../../error/Oauth2JwtParseError'
import { type JwtSigner, vJwtHeader, vJwtPayload } from './v-jwt'
import { decodeJwtHeader } from './decode-jwt-header'
import { type JwtSigner, type vJwtHeader, vJwtPayload } from './v-jwt'

export interface DecodeJwtOptions<
HeaderSchema extends BaseSchema | undefined,
Expand Down Expand Up @@ -50,13 +51,8 @@ export function decodeJwt<
throw new Oauth2JwtParseError('Jwt is not a valid jwt, unable to decode')
}

let headerJson: Record<string, unknown>
let payloadJson: Record<string, unknown>
try {
headerJson = stringToJsonWithErrorHandling(
encodeToUtf8String(decodeBase64(jwtParts[0])),
'Unable to parse jwt header to JSON'
)
payloadJson = stringToJsonWithErrorHandling(
encodeToUtf8String(decodeBase64(jwtParts[1])),
'Unable to parse jwt payload to JSON'
Expand All @@ -65,7 +61,7 @@ export function decodeJwt<
throw new Oauth2JwtParseError('Error parsing JWT')
}

const header = parseWithErrorHandling(options.headerSchema ?? vJwtHeader, headerJson)
const { header } = decodeJwtHeader({ jwe: options.jwt, headerSchema: options.headerSchema })
const payload = parseWithErrorHandling(options.payloadSchema ?? vJwtPayload, payloadJson)

return {
Expand Down Expand Up @@ -175,7 +171,7 @@ export function jwtSignerFromJwt({ header, payload }: Pick<DecodeJwtResult, 'hea
type IsSchemaProvided<T> = T extends undefined ? false : true

// Helper type to infer the output type based on whether a schema is provided
type InferSchemaOutput<
export type InferSchemaOutput<
ProvidedSchema extends BaseSchema | undefined,
DefaultSchema extends BaseSchema,
> = IsSchemaProvided<ProvidedSchema> extends true
Expand Down
6 changes: 6 additions & 0 deletions packages/oauth2/src/common/jwt/v-jwe.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import * as v from 'valibot'

export const vCompactJwe = v.pipe(
v.string(),
v.regex(/^[A-Za-z0-9_-]+\.[A-Za-z0-9_-]*\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$/, 'Not a valid compact jwe')
)
6 changes: 6 additions & 0 deletions packages/oauth2/src/common/jwt/v-jwt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,12 @@ export type JwtSignerCustom = {

export type JwtSigner = JwtSignerDid | JwtSignerJwk | JwtSignerX5c | JwtSignerTrustChain | JwtSignerCustom

export type JweEncryptor = JwtSignerJwk & {
enc: string
apu?: string
apv?: string
}

export type JwtSignerWithJwk = JwtSigner & { publicJwk: Jwk }

export const vCompactJwt = v.pipe(
Expand Down
6 changes: 6 additions & 0 deletions packages/oauth2/src/common/v-oauth2-error.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,12 @@ export enum Oauth2ErrorCodes {
InvalidProof = 'invalid_proof',
InvalidNonce = 'invalid_nonce',
InvalidEncryptionParameters = 'invalid_encryption_parameters',

// Jar
InvalidRequestUri = 'invalid_request_uri',
InvalidRequestObject = 'invalid_request_object',
RequestNotSupported = 'request_not_supported',
RequestUriNotSupported = 'request_uri_not_supported',
}

export const vOauth2ErrorResponse = v.looseObject({
Expand Down
Loading