diff --git a/CHANGES.ja.md b/CHANGES.ja.md index 5c1fa8960d..cc1bfc9e02 100644 --- a/CHANGES.ja.md +++ b/CHANGES.ja.md @@ -1,9 +1,463 @@ 変更点 ====== -4.21 (2025 年 07 月 18 日) +4.47 (2026 年 07 月 09 日) ----------------- +- 新しい型 + * `CursorResponse` クラス + + +4.46 (2026 年 06 月 15 日) +-------------------------- + +- `AuthorizationFailRequest.Reason` 列挙型 + * `INVALID_SCOPE` を追加。 + * `UNAUTHORIZED_CLIENT` を追加。 + * `TEMPORARILY_UNAVAILABLE` を追加。 + +- `TokenFailRequest.Reason` 列挙型 + * `INVALID_CLIENT` を追加。 + * `UNAUTHORIZED_CLIENT` を追加。 + * `INVALID_SCOPE` を追加。 + + +4.45 (2026 年 05 月 18 日) +-------------------------- + +- `AccessRight` 列挙型 + * `MODIFY_SERVICE`、`CREATE_CLIENT`、`ADMIN` が `USE_INTROSPECTION` を含むように修正。 + + +4.44 (2026 年 05 月 10 日) +-------------------------- + +- `AuthleteApi` インターフェース + * `backchannelLogoutToken(BackchannelLogoutTokenRequest)` メソッドを追加。 + * `backchannelLogoutToken(BackchannelLogoutTokenRequest, Options)` メソッドを追加。 + +- `Client` クラス + * `getBackchannelLogoutUri()` メソッドを追加。 + * `setBackchannelLogoutUri(URI)` メソッドを追加。 + * `isBackchannelLogoutSessionRequired()` メソッドを追加。 + * `setBackchannelLogoutSessionRequired(boolean)` メソッドを追加。 + +- `Service` クラス + * `isBackchannelLogoutSupported()` メソッドを追加。 + * `setBackchannelLogoutSupported(boolean)` メソッドを追加。 + * `isBackchannelLogoutSessionSupported()` メソッドを追加。 + * `setBackchannelLogoutSessionSupported(boolean)` メソッドを追加。 + +- 新しい型 + * `BackchannelLogoutTokenRequest` クラス + * `BackchannelLogoutTokenResponse` クラス + + +4.43 (2026 年 04 月 17 日) +-------------------------- + +- `AccessRight` 列挙型 + * `USE_INTROSPECTION` を追加。 + * `VIEW_AUDIT_LOG` を追加。 + + +4.42 (2026 年 04 月 14 日) +-------------------------- + +- `Service` クラス + * `getClientAttesterRoots()` メソッドを追加。 + * `setClientAttesterRoots(String[])` メソッドを追加。 + * `isClientAttesterRootsEnabled()` メソッドを追加。 + * `setClientAttesterRootsEnabled(boolean)` メソッドを追加。 + * `isClientAttesterRootsOnly()` メソッドを追加。 + * `setClientAttesterRootsOnly(boolean)` メソッドを追加。 + * `getKeyAttesterRoots()` メソッドを追加。 + * `setKeyAttesterRoots(String[])` メソッドを追加。 + * `isKeyAttesterRootsEnabled()` メソッドを追加。 + * `setKeyAttesterRootsEnabled(boolean)` メソッドを追加。 + * `isKeyAttesterRootsOnly()` メソッドを追加。 + * `setKeyAttesterRootsOnly(boolean)` メソッドを追加。 + + +4.41 (2026 年 03 月 28 日) +-------------------------- + +- `Client` クラス + * `getHaipVersion()` メソッドを追加。 + * `setHaipVersion(String)` メソッドを追加。 + +- `Service` クラス + * `getHaipVersion()` メソッドを追加。 + * `setHaipVersion(String)` メソッドを追加。 + + +4.40 (2026 年 03 月 24 日) +-------------------------- + +- `Service` クラス + * `getAuthorizationCodeDuration()` メソッドを追加。 + * `setAuthorizationCodeDuration(long)` メソッドを追加。 + + +4.39 (2026 年 03 月 20 日) +-------------------------- + +- `AuthleteApi` インターフェース + * `attestationChallenge(AttestationChallengeRequest)` メソッドを追加。 + * `attestationChallenge(AttestationChallengeRequest, Options)` メソッドを追加。 + +- `BackchannelAuthenticationResponse` クラス + * `getAttestationChallenge()` メソッドを追加。 + * `setAttestationChallenge(String)` メソッドを追加。 + +- `DeviceAuthorizationResponse` クラス + * `getAttestationChallenge()` メソッドを追加。 + * `setAttestationChallenge(String)` メソッドを追加。 + +- `ErrorCode` 列挙型 + * `use_attestation_challenge` を追加。 + * `use_fresh_attestation` を追加。 + * `invalid_client_attestation` を追加。 + +- `PushedAuthReqResponse` クラス + * `getAttestationChallenge()` メソッドを追加。 + * `setAttestationChallenge(String)` メソッドを追加。 + +- `RevocationResponse` クラス + * `getAttestationChallenge()` メソッドを追加。 + * `setAttestationChallenge(String)` メソッドを追加。 + +- `Service` クラス + * `getChallengeEndpoint()` メソッドを追加。 + * `setChallengeEndpoint(URI)` メソッドを追加。 + * `getAttestationChallengeTimeWindow()` メソッドを追加。 + * `setAttestationChallengeTimeWindow(long)` メソッドを追加。 + +- `TokenResponse` クラス + * `getAttestationChallenge()` メソッドを追加。 + * `setAttestationChallenge(String)` メソッドを追加。 + +- 新しい型 + * `AttestationChallengeRequest` クラス + * `AttestationChallengeResponse` クラス + + +4.38 (2026 年 03 月 04 日) +-------------------------- + +- `TokenUpdateRequest` クラス + * `getRefreshToken()` メソッドを追加。 + * `setRefreshToken(String)` メソッドを追加。 + * `getRefreshTokenHash()` メソッドを追加。 + * `setRefreshTokenHash(String)` メソッドを追加。 + + +4.37 (2026 年 03 月 02 日) +-------------------------- + +- `ClientAssertionType` 列挙型 + * `JWT_SPIFFE` を追加。 + + +4.36 (2026 年 02 月 26 日) +-------------------------- + +- `ClientAuthMethod` 列挙型 + * `SPIFFE_JWT` を追加。 + +- `Client` クラス + * `getSpiffeId()` メソッドを追加。 + * `setSpiffeId(URI)` メソッドを追加。 + * `getSpiffeBundleEndpoint()` メソッドを追加。 + * `setSpiffeBundleEndpoint(URI)` メソッドを追加。 + + +4.35 (2026 年 01 月 10 日) +-------------------------- + +- `CredentialDeferredIssueRequest` クラス + * `isDenied()` メソッドを追加。 + * `setDenied(boolean)` メソッドを追加。 + * `getInterval()` メソッドを追加。 + * `setInterval(int)` メソッドを追加。 + +- `CredentialDeferredIssueResponse.Action` 列挙型 + * `ACCEPTED` を追加。 + * `ACCEPTED_JWT` を追加。 + +- `CredentialSingleIssueRequest` クラス + * `isDenied()` メソッドを追加。 + * `setDenied(boolean)` メソッドを追加。 + * `getInterval()` メソッドを追加。 + * `setInterval(int)` メソッドを追加。 + + +4.34 (2026 年 01 月 03 日) +-------------------------- + +- `AuthzDetailsElement` クラス + * デフォルトコンストラクタを明示的に追加。 + * コピーコンストラクタ (`AuthzDetailsElement(AuthzDetailsElement)`) を追加。 + +- `CredentialRequestInfo` クラス + * `getCredentialConfigurationId()` メソッドを追加。 + * `setCredentialConfigurationId(String)` メソッドを追加。 + * `getCredentialIdentifier()` メソッドを追加。 + * `setCredentialIdentifier(String)` メソッドを追加。 + + +4.33 (2025 年 12 月 13 日) +-------------------------- + +- `Service` クラス + * `isCimdMetadataPolicyEnabled()` メソッドを追加。 + * `setCimdMetadataPolicyEnabled(boolean)` メソッドを追加。 + * `getCimdMetadataPolicy()` メソッドを追加。 + * `setCimdMetadataPolicy(String)` メソッドを追加。 + + +4.32 (2025 年 12 月 01 日) +-------------------------- + +- `Service` クラス + * `isHttpAliasProhibited()` メソッドを追加。 + * `setHttpAliasProhibited(boolean)` メソッドを追加。 + + +4.31 (2025 年 11 月 25 日) +-------------------------- + +- `Service` クラス + * `isCimdAllowlistEnabled()` メソッドを追加。 + * `setCimdAllowlistEnabled(boolean)` メソッドを追加。 + * `getCimdAllowlist()` メソッドを追加。 + * `setCimdAllowlist(String[])` メソッドを追加。 + * `isCimdWhitelistEnabled()` メソッドを削除。 + * `setCimdWhitelistEnabled(boolean)` メソッドを削除。 + * `getCimdWhitelist()` メソッドを削除。 + * `setCimdWhitelist(String[])` メソッドを削除。 + + +4.30 (2025 年 11 月 20 日) +-------------------------- + +- `AuthorizationRequest` クラス + * `getCimdOptions()` メソッドを追加。 + * `setCimdOptions(CimdOptions)` メソッドを追加。 + +- `BackchannelAuthenticationRequest` クラス + * `getCimdOptions()` メソッドを追加。 + * `setCimdOptions(CimdOptions)` メソッドを追加。 + +- `DeviceAuthorizationRequest` クラス + * `getCimdOptions()` メソッドを追加。 + * `setCimdOptions(CimdOptions)` メソッドを追加。 + +- `Service` クラス + * `isCimdWhitelistEnabled()` メソッドを追加。 + * `setCimdWhitelistEnabled(boolean)` メソッドを追加。 + * `getCimdWhitelist()` メソッドを追加。 + * `setCimdWhitelist(String[])` メソッドを追加。 + * `isCimdAlwaysRetrieved()` メソッドを追加。 + * `setCimdAlwaysRetrieved(boolean)` メソッドを追加。 + * `isCimdHttpPermitted()` メソッドを追加。 + * `setCimdHttpPermitted(boolean)` メソッドを追加。 + * `isCimdQueryPermitted()` メソッドを追加。 + * `setCimdQueryPermitted(boolean)` メソッドを追加。 + +- `TokenRequest` クラス + * `getCimdOptions()` メソッドを追加。 + * `setCimdOptions(CimdOptions)` メソッドを追加。 + +- 新しい型 + * `CimdOptions` クラス + + +4.29 (2025 年 11 月 18 日) +-------------------------- + +- `AuthorizationResponse` クラス + * `isMetadataDocumentUsed()` メソッドを追加。 + * `setMetadataDocumentUsed(boolean)` メソッドを追加。 + +- `BackchannelAuthenticationCompleteResponse` クラス + * `getMetadataDocumentLocation()` メソッドを追加。 + * `setMetadataDocumentLocation(URI)` メソッドを追加。 + * `isMetadataDocumentUsed()` メソッドを追加。 + * `setMetadataDocumentUsed(boolean)` メソッドを追加。 + +- `BackchannelAuthenticationResponse` クラス + * `getMetadataDocumentLocation()` メソッドを追加。 + * `setMetadataDocumentLocation(URI)` メソッドを追加。 + * `isMetadataDocumentUsed()` メソッドを追加。 + * `setMetadataDocumentUsed(boolean)` メソッドを追加。 + +- `Client` クラス + * `getMetadataDocumentLocation()` メソッドを追加。 + * `setMetadataDocumentLocation(URI)` メソッドを追加。 + * `getMetadataDocumentExpiresAt()` メソッドを追加。 + * `setMetadataDocumentExpiresAt(long)` メソッドを追加。 + * `getMetadataDocumentUpdatedAt()` メソッドを追加。 + * `setMetadataDocumentUpdatedAt(long)` メソッドを追加。 + * `isDiscoveredByMetadataDocument()` メソッドを追加。 + * `setDiscoveredByMetadataDocument(boolean)` メソッドを追加。 + * `getClientSource()` メソッドを追加。 + * `setClientSource(ClientSource)` メソッドを追加。 + * `updateClientSource()` メソッドを追加。 + +- `ClientMetadataControl` クラス + * `isMetadataDocumentLocationPreferred()` メソッドを追加。 + * `setMetadataDocumentLocationPreferred(boolean)` メソッドを追加。 + +- `DeviceAuthorizationResponse` クラス + * `getMetadataDocumentLocation()` メソッドを追加。 + * `setMetadataDocumentLocation(URI)` メソッドを追加。 + * `isMetadataDocumentUsed()` メソッドを追加。 + * `setMetadataDocumentUsed(boolean)` メソッドを追加。 + +- `DeviceVerificationResponse` クラス + * `getMetadataDocumentLocation()` メソッドを追加。 + * `setMetadataDocumentLocation(URI)` メソッドを追加。 + * `isMetadataDocumentUsed()` メソッドを追加。 + * `setMetadataDocumentUsed(boolean)` メソッドを追加。 + +- `IntrospectionResponse` クラス + * `getMetadataDocumentLocation()` メソッドを追加。 + * `setMetadataDocumentLocation(URI)` メソッドを追加。 + * `isMetadataDocumentUsed()` メソッドを追加。 + * `setMetadataDocumentUsed(boolean)` メソッドを追加。 + +- `Service` クラス + * `isClientIdMetadataDocumentSupported()` メソッドを追加。 + * `setClientIdMetadataDocumentSupported(boolean)` メソッドを追加。 + +- `TokenCreateRequest` クラス + * `isMetadataDocumentUsed()` メソッドを追加。 + * `setMetadataDocumentUsed(boolean)` メソッドを追加。 + +- `TokenInfo` クラス + * `getMetadataDocumentLocation()` メソッドを追加。 + * `setMetadataDocumentLocation(URI)` メソッドを追加。 + * `isMetadataDocumentUsed()` メソッドを追加。 + * `setMetadataDocumentUsed(boolean)` メソッドを追加。 + +- `TokenIssueResponse` クラス + * `getMetadataDocumentLocation()` メソッドを追加。 + * `setMetadataDocumentLocation(URI)` メソッドを追加。 + * `isMetadataDocumentUsed()` メソッドを追加。 + * `setMetadataDocumentUsed(boolean)` メソッドを追加。 + +- `TokenResponse` クラス + * `getMetadataDocumentLocation()` メソッドを追加。 + * `setMetadataDocumentLocation(URI)` メソッドを追加。 + * `isMetadataDocumentUsed()` メソッドを追加。 + * `setMetadataDocumentUsed(boolean)` メソッドを追加。 + +- `UserInfoResponse` クラス + * `getMetadataDocumentLocation()` メソッドを追加。 + * `setMetadataDocumentLocation(URI)` メソッドを追加。 + * `isMetadataDocumentUsed()` メソッドを追加。 + * `setMetadataDocumentUsed(boolean)` メソッドを追加。 + +- 新しい型 + * `ClientSource` 列挙型 + + +4.28 (2025 年 11 月 05 日) +-------------------------- + +- `ErrorCode` 列挙型 + * `unknown_credential_configuration` を追加。 + * `unknown_credential_identifier` を追加。 + * `invalid_nonce` を追加。 + * `credential_request_denied` を追加。 + * `invalid_notification_id` を追加。 + * `invalid_notification_request` を追加。 + + +4.27 (2025 年 11 月 04 日) +-------------------------- + +- `AuthleteApi` インターフェース + * `credentialNonce(CredentialNonceRequest)` メソッドを追加。 + * `credentialNonce(CredentialNonceRequest, Options)` メソッドを追加。 + +- 新しい型 + * `CredentialNonceRequest` クラス + * `CredentialNonceResponse` クラス + +- その他 + * PR #143: fix spotbugs-annotations dependency + + +4.26 (2025 年 11 月 03 日) +-------------------------- + +- `CredentialIssuerMetadata` クラス + * `getNotificationEndpoint()` メソッドを追加。 + * `setNotificationEndpoint(URI)` メソッドを追加。 + * `getNonceEndpoint()` メソッドを追加。 + * `setNonceEndpoint(URI)` メソッドを追加。 + * `getCredentialRequestEncryptionJwks()` メソッドを追加。 + * `setCredentialRequestEncryptionJwks(String)` メソッドを追加。 + * `getCredentialRequestEncryptionEncValuesSupported()` メソッドを追加。 + * `setCredentialRequestEncryptionEncValuesSupported(JWEEnc[])` メソッドを追加。 + * `getCredentialRequestEncryptionZipValuesSupported()` メソッドを追加。 + * `setCredentialRequestEncryptionZipValuesSupported(JWEEnc[])` メソッドを追加。 + * `isRequireCredentialRequestEncryption()` メソッドを追加。 + * `setRequireCredentialRequestEncryption(boolean)` メソッドを追加。 + * `getCredentialResponseEncryptionZipValuesSupported()` メソッドを追加。 + * `setCredentialResponseEncryptionZipValuesSupported(JWEEnc[])` メソッドを追加。 + * `getBatchSize()` メソッドを追加。 + * `setBatchSize(int)` メソッドを追加。 + * `getDisplay()` メソッドを追加。 + * `setDisplay(String)` メソッドを追加。 + +- 新しい型 + * `JWEZip` 列挙型 + + +4.25 (2025 年 10 月 30 日) +-------------------------- + +- `Service` クラス + * `getOid4vciVersion()` メソッドを追加。 + * `setOid4vciVersion(String)` メソッドを追加。 + + +4.24 (2025 年 10 月 06 日) +-------------------------- + +- `TokenResponse` クラス + * `getAdditionalClaims()` メソッドを追加 + * `setAdditionalClaims(String)` メソッドを追加 + * カスタムクレームを処理するために Native SSO のトークンレスポンスに追加されたクレームへのアクセスをサポート + + +4.23 (2025 年 10 月 03 日) +-------------------------- + +- `ApiResponse` クラス + * `getResponseHeaders()` メソッドを追加。 + * `setResponseHeaders(Map>)` メソッドを追加。 + * Authlete の API 呼び出しから HTTP レスポンスヘッダーを取り出す機能をサポート。 + +- `Client` クラス + * `isInScopeForTokenMigration()` メソッドを追加。 + * `setInScopeForTokenMigration(boolean)` メソッドを追加。 + +- `com.neovisionaries:nv-cipher` と `com.neovisionaries:nv-digest` を依存関係から削除。 + + +4.22 (2025 年 08 月 14 日) +-------------------------- + +- nimbus-jose-jwt を バージョン 10.0.2 に更新。 + + +4.21 (2025 年 07 月 18 日) +-------------------------- + - Authlete API V3 における HTTP URLConnection のサポートを追加。 - checkstyle 用の設定ファイルを追加。 - spotbugs 用の設定ファイルを追加。 diff --git a/CHANGES.md b/CHANGES.md index 134f0b1e8a..3f05fed0bd 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,460 @@ CHANGES ======= +4.47 (2026-07-09) +----------------- + +- New types + * `CursorResponse` class + + +4.46 (2026-06-15) +----------------- + +- `AuthorizationFailRequest.Reason` enum + * Added `INVALID_SCOPE` + * Added `UNAUTHORIZED_CLIENT` + * Added `TEMPORARILY_UNAVAILABLE` + +- `TokenFailRequest.Reason` enum + * Added `INVALID_CLIENT` + * Added `UNAUTHORIZED_CLIENT` + * Added `INVALID_SCOPE` + + +4.45 (2026-05-18) +----------------- + +- `AccessRight` enum + * Fixed `MODIFY_SERVICE`, `CREATE_CLIENT`, and `ADMIN` to include `USE_INTROSPECTION`. + + +4.44 (2026-05-10) +----------------- + +- `AuthleteApi` interface + * Added the `backchannelLogoutToken(BackchannelLogoutTokenRequest)` method. + * Added the `backchannelLogoutToken(BackchannelLogoutTokenRequest, Options)` method. + +- `Client` class + * Added the `getBackchannelLogoutUri()` method. + * Added the `setBackchannelLogoutUri(URI)` method. + * Added the `isBackchannelLogoutSessionRequired()` method. + * Added the `setBackchannelLogoutSessionRequired(boolean)` method. + +- `Service` class + * Added the `isBackchannelLogoutSupported()` method. + * Added the `setBackchannelLogoutSupported(boolean)` method. + * Added the `isBackchannelLogoutSessionSupported()` method. + * Added the `setBackchannelLogoutSessionSupported(boolean)` method. + +- New types + * `BackchannelLogoutTokenRequest` class + * `BackchannelLogoutTokenResponse` class + + +4.43 (2026-04-17) +----------------- + +- `AccessRight` enum + * Added `USE_INTROSPECTION`. + * Added `VIEW_AUDIT_LOG`. + + +4.42 (2026-04-14) +----------------- + +- `Service` class + * Added the `getClientAttesterRoots()` method. + * Added the `setClientAttesterRoots(String[])` method. + * Added the `isClientAttesterRootsEnabled()` method. + * Added the `setClientAttesterRootsEnabled(boolean)` method. + * Added the `isClientAttesterRootsOnly()` method. + * Added the `setClientAttesterRootsOnly(boolean)` method. + * Added the `getKeyAttesterRoots()` method. + * Added the `setKeyAttesterRoots(String[])` method. + * Added the `isKeyAttesterRootsEnabled()` method. + * Added the `setKeyAttesterRootsEnabled(boolean)` method. + * Added the `isKeyAttesterRootsOnly()` method. + * Added the `setKeyAttesterRootsOnly(boolean)` method. + + +4.41 (2026-03-28) +----------------- + +- `Client` class + * Added the `getHaipVersion()` method. + * Added the `setHaipVersion(String)` method. + +- `Service` class + * Added the `getHaipVersion()` method. + * Added the `setHaipVersion(String)` method. + + +4.40 (2026-03-24) +----------------- + +- `Service` class + * Added the `getAuthorizationCodeDuration()` method. + * Added the `setAuthorizationCodeDuration(long)` method. + + +4.39 (2026-03-20) +----------------- + +- `AuthleteApi` interface + * Added the `attestationChallenge(AttestationChallengeRequest)` method. + * Added the `attestationChallenge(AttestationChallengeRequest, Options)` method. + +- `BackchannelAuthenticationResponse` class + * Added the `getAttestationChallenge()` method. + * Added the `setAttestationChallenge(String)` method. + +- `DeviceAuthorizationResponse` class + * Added the `getAttestationChallenge()` method. + * Added the `setAttestationChallenge(String)` method. + +- `ErrorCode` enum + * Added `use_attestation_challenge`. + * Added `use_fresh_attestation`. + * Added `invalid_client_attestation`. + +- `PushedAuthReqResponse` class + * Added the `getAttestationChallenge()` method. + * Added the `setAttestationChallenge(String)` method. + +- `RevocationResponse` class + * Added the `getAttestationChallenge()` method. + * Added the `setAttestationChallenge(String)` method. + +- `Service` class + * Added the `getChallengeEndpoint()` method. + * Added the `setChallengeEndpoint(URI)` method. + * Added the `getAttestationChallengeTimeWindow()` method. + * Added the `setAttestationChallengeTimeWindow(long)` method. + +- `TokenResponse` class + * Added the `getAttestationChallenge()` method. + * Added the `setAttestationChallenge(String)` method. + +- New types + * `AttestationChallengeRequest` class + * `AttestationChallengeResponse` class + + +4.38 (2026-03-04) +----------------- + +- `TokenUpdateRequest` class + * Added the `getRefreshToken()` method. + * Added the `setRefreshToken(String)` method. + * Added the `getRefreshTokenHash()` method. + * Added the `setRefreshTokenHash(String)` method. + + +4.37 (2026-03-02) +----------------- + +- `ClientAssertionType` enum + * Added `JWT_SPIFFE`. + + +4.36 (2026-02-26) +----------------- + +- `ClientAuthMethod` enum + * Added `SPIFFE_JWT`. + +- `Client` class + * Added the `getSpiffeId()` method. + * Added the `setSpiffeId(URI)` method. + * Added the `getSpiffeBundleEndpoint()` method. + * Added the `setSpiffeBundleEndpoint(URI)` method. + + +4.35 (2026-01-10) +----------------- + +- `CredentialDeferredIssueRequest` class + * Added the `isDenied()` method. + * Added the `setDenied(boolean)` method. + * Added the `getInterval()` method. + * Added the `setInterval(int)` method. + +- `CredentialDeferredIssueResponse.Action` enum + * Added `ACCEPTED`. + * Added `ACCEPTED_JWT`. + +- `CredentialSingleIssueRequest` class + * Added the `isDenied()` method. + * Added the `setDenied(boolean)` method. + * Added the `getInterval()` method. + * Added the `setInterval(int)` method. + + +4.34 (2026-01-03) +----------------- + +- `AuthzDetailsElement` class + * Added the default constructor explicitly. + * Added a copy constructor (`AuthzDetailsElement(AuthzDetailsElement)`). + +- `CredentialRequestInfo` class + * Added the `getCredentialConfigurationId()` method. + * Added the `setCredentialConfigurationId(String)` method. + * Added the `getCredentialIdentifier()` method. + * Added the `setCredentialIdentifier(String)` method. + + +4.33 (2025-12-13) +----------------- + +- `Service` class + * Added the `isCimdMetadataPolicyEnabled()` method. + * Added the `setCimdMetadataPolicyEnabled(boolean)` method. + * Added the `getCimdMetadataPolicy()` method. + * Added the `setCimdMetadataPolicy(String)` method. + + +4.32 (2025-12-01) +----------------- + +- `Service` class + * Added the `isHttpAliasProhibited()` method. + * Added the `setHttpAliasProhibited(boolean)` method. + + +4.31 (2025-11-25) +----------------- + +- `Service` class + * Added the `isCimdAllowlistEnabled()` method. + * Added the `setCimdAllowlistEnabled(boolean)` method. + * Added the `getCimdAllowlist()` method. + * Added the `setCimdAllowlist(String[])` method. + * Removed the `isCimdWhitelistEnabled()` method. + * Removed the `setCimdWhitelistEnabled(boolean)` method. + * Removed the `getCimdWhitelist()` method. + * Removed the `setCimdWhitelist(String[])` method. + + +4.30 (2025-11-20) +----------------- + +- `AuthorizationRequest` class + * Added the `getCimdOptions()` method. + * Added the `setCimdOptions(CimdOptions)` method. + +- `BackchannelAuthenticationRequest` class + * Added the `getCimdOptions()` method. + * Added the `setCimdOptions(CimdOptions)` method. + +- `DeviceAuthorizationRequest` class + * Added the `getCimdOptions()` method. + * Added the `setCimdOptions(CimdOptions)` method. + +- `Service` class + * Added the `isCimdWhitelistEnabled()` method. + * Added the `setCimdWhitelistEnabled(boolean)` method. + * Added the `getCimdWhitelist()` method. + * Added the `setCimdWhitelist(String[])` method. + * Added the `isCimdAlwaysRetrieved()` method. + * Added the `setCimdAlwaysRetrieved(boolean)` method. + * Added the `isCimdHttpPermitted()` method. + * Added the `setCimdHttpPermitted(boolean)` method. + * Added the `isCimdQueryPermitted()` method. + * Added the `setCimdQueryPermitted(boolean)` method. + +- `TokenRequest` class + * Added the `getCimdOptions()` method. + * Added the `setCimdOptions(CimdOptions)` method. + +- New types + * `CimdOptions` class + + +4.29 (2025-11-18) +----------------- + +- `AuthorizationResponse` class + * Added the `isMetadataDocumentUsed()` method. + * Added the `setMetadataDocumentUsed(boolean)` method. + +- `BackchannelAuthenticationCompleteResponse` class + * Added the `getMetadataDocumentLocation()` method. + * Added the `setMetadataDocumentLocation(URI)` method. + * Added the `isMetadataDocumentUsed()` method. + * Added the `setMetadataDocumentUsed(boolean)` method. + +- `BackchannelAuthenticationResponse` class + * Added the `getMetadataDocumentLocation()` method. + * Added the `setMetadataDocumentLocation(URI)` method. + * Added the `isMetadataDocumentUsed()` method. + * Added the `setMetadataDocumentUsed(boolean)` method. + +- `Client` class + * Added the `getMetadataDocumentLocation()` method. + * Added the `setMetadataDocumentLocation(URI)` method. + * Added the `getMetadataDocumentExpiresAt()` method. + * Added the `setMetadataDocumentExpiresAt(long)` method. + * Added the `getMetadataDocumentUpdatedAt()` method. + * Added the `setMetadataDocumentUpdatedAt(long)` method. + * Added the `isDiscoveredByMetadataDocument()` method. + * Added the `setDiscoveredByMetadataDocument(boolean)` method. + * Added the `getClientSource()` method. + * Added the `setClientSource(ClientSource)` method. + * Added the `updateClientSource()` method. + +- `ClientMetadataControl` class + * Added the `isMetadataDocumentLocationPreferred()` method. + * Added the `setMetadataDocumentLocationPreferred(boolean)` method. + +- `DeviceAuthorizationResponse` class + * Added the `getMetadataDocumentLocation()` method. + * Added the `setMetadataDocumentLocation(URI)` method. + * Added the `isMetadataDocumentUsed()` method. + * Added the `setMetadataDocumentUsed(boolean)` method. + +- `DeviceVerificationResponse` class + * Added the `getMetadataDocumentLocation()` method. + * Added the `setMetadataDocumentLocation(URI)` method. + * Added the `isMetadataDocumentUsed()` method. + * Added the `setMetadataDocumentUsed(boolean)` method. + +- `IntrospectionResponse` class + * Added the `getMetadataDocumentLocation()` method. + * Added the `setMetadataDocumentLocation(URI)` method. + * Added the `isMetadataDocumentUsed()` method. + * Added the `setMetadataDocumentUsed(boolean)` method. + +- `Service` class + * Added the `isClientIdMetadataDocumentSupported()` method. + * Added the `setClientIdMetadataDocumentSupported(boolean)` method. + +- `TokenCreateRequest` class + * Added the `isMetadataDocumentUsed()` method. + * Added the `setMetadataDocumentUsed(boolean)` method. + +- `TokenInfo` class + * Added the `getMetadataDocumentLocation()` method. + * Added the `setMetadataDocumentLocation(URI)` method. + * Added the `isMetadataDocumentUsed()` method. + * Added the `setMetadataDocumentUsed(boolean)` method. + +- `TokenIssueResponse` class + * Added the `getMetadataDocumentLocation()` method. + * Added the `setMetadataDocumentLocation(URI)` method. + * Added the `isMetadataDocumentUsed()` method. + * Added the `setMetadataDocumentUsed(boolean)` method. + +- `TokenResponse` class + * Added the `getMetadataDocumentLocation()` method. + * Added the `setMetadataDocumentLocation(URI)` method. + * Added the `isMetadataDocumentUsed()` method. + * Added the `setMetadataDocumentUsed(boolean)` method. + +- `UserInfoResponse` class + * Added the `getMetadataDocumentLocation()` method. + * Added the `setMetadataDocumentLocation(URI)` method. + * Added the `isMetadataDocumentUsed()` method. + * Added the `setMetadataDocumentUsed(boolean)` method. + +- New types + * `ClientSource` enum + + +4.28 (2025-11-05) +----------------- + +- `ErrorCode` enum + * Added `unknown_credential_configuration` + * Added `unknown_credential_identifier` + * Added `invalid_nonce` + * Added `credential_request_denied` + * Added `invalid_notification_id` + * Added `invalid_notification_request` + + +4.27 (2025-11-04) +----------------- + +- `AuthleteApi` interface + * Added the `credentialNonce(CredentialNonceRequest)` method. + * Added the `credentialNonce(CredentialNonceRequest, Options)` method. + +- New types + * `CredentialNonceRequest` class + * `CredentialNonceResponse` class + +- Others + * PR #143: fix spotbugs-annotations dependency + + +4.26 (2025-11-03) +----------------- + +- `CredentialIssuerMetadata` class + * Added the `getNotificationEndpoint()` method. + * Added the `setNotificationEndpoint(URI)` method. + * Added the `getNonceEndpoint()` method. + * Added the `setNonceEndpoint(URI)` method. + * Added the `getCredentialRequestEncryptionJwks()` method. + * Added the `setCredentialRequestEncryptionJwks(String)` method. + * Added the `getCredentialRequestEncryptionEncValuesSupported()` method. + * Added the `setCredentialRequestEncryptionEncValuesSupported(JWEEnc[])` method. + * Added the `getCredentialRequestEncryptionZipValuesSupported()` method. + * Added the `setCredentialRequestEncryptionZipValuesSupported(JWEEnc[])` method. + * Added the `isRequireCredentialRequestEncryption()` method. + * Added the `setRequireCredentialRequestEncryption(boolean)` method. + * Added the `getCredentialResponseEncryptionZipValuesSupported()` method. + * Added the `setCredentialResponseEncryptionZipValuesSupported(JWEEnc[])` method. + * Added the `getBatchSize()` method. + * Added the `setBatchSize(int)` method. + * Added the `getDisplay()` method. + * Added the `setDisplay(String)` method. + +- New types + * `JWEZip` enum + + +4.25 (2025-10-30) +----------------- + +- `Service` class + * Added the `getOid4vciVersion()` method. + * Added the `setOid4vciVersion(String)` method. + + +4.24 (2025-10-06) +----------------- + +- `TokenResponse` class + * Added the `getAdditionalClaims()` method. + * Added the `setAdditionalClaims(String)` method. + * Enables access to additional claims data in Native SSO token responses for custom claim handling + + +4.23 (2025-10-03) +---------- + +- `ApiResponse` class + * Added the `getResponseHeaders()` method. + * Added the `setResponseHeaders(Map>)` method. + * Added support for extracting HTTP response headers from Authlete API calls. + +- `Client` class + * Added the `isInScopeForTokenMigration()` method. + * Added the `setInScopeForTokenMigration(boolean)` method. + +- Removed `com.neovisionaries:nv-cipher` and `com.neovisionaries:nv-digest` dependencies. + + +4.22 (2025-08-14) +----------------- + +- Updated nimbus-jose-jwt to v10.0.2. + + 4.21 (2025-07-18) ----------------- diff --git a/README.ja.md b/README.ja.md index c8ba4a3633..05c14a7c18 100644 --- a/README.ja.md +++ b/README.ja.md @@ -54,8 +54,6 @@ JavaDoc https://authlete.github.io/authlete-java-common/ - https://authlete.github.io/authlete-java-common/index.html?overview-summary.html [FRAMES] - 説明 ---- @@ -364,12 +362,21 @@ settings.setReadTimeout(5000); - `credentialBatchIssue(CredentialBatchIssueRequest)` - `credentialDeferredParse(CredentialDeferredParseRequest)` - `credentialDeferredIssue(CredentialDeferredIssueRequest)` + - `credentialNonce(CredentialNonceRequest)` 22. OpenID Connect Native SSO for Mobile Apps 1.0 に関するメソッド群 - `nativeSso(NativeSsoRequest)` - `nativeSsoLogout(NativeSsoLogoutRequest)` + 23. OAuth 2.0 Attestation-Based Client Authentication に関するメソッド群 + + - `attestationChallenge(AttestationChallengeRequest)` + + 24. OpenID Connect Back-Channel Logout 1.0 に関するメソッド群 + + - `backchannelLogoutToken(BackchannelLogoutTokenRequest)` + *例* 次のコードは既存のサービスのリストを取得する例です。 diff --git a/README.md b/README.md index 0fc3e0dd7f..79f92da732 100644 --- a/README.md +++ b/README.md @@ -55,8 +55,6 @@ JavaDoc https://authlete.github.io/authlete-java-common/ - https://authlete.github.io/authlete-java-common/index.html?overview-summary.html [FRAMES] - Description ----------- @@ -375,12 +373,21 @@ Methods in `AuthleteApi` interface can be divided into some categories. - `credentialBatchIssue(CredentialBatchIssueRequest)` - `credentialDeferredParse(CredentialDeferredParseRequest)` - `credentialDeferredIssue(CredentialDeferredIssueRequest)` + - `credentialNonce(CredentialNonceRequest)` 22. Methods for OpenID Connect Native SSO for Mobile Apps 1.0 - `nativeSso(NativeSsoRequest)` - `nativeSsoLogout(NativeSsoLogoutRequest)` + 23. Methods for OAuth 2.0 Attestation-Based Client Authentication + + - `attestationChallenge(AttestationChallengeRequest)` + + 24. Methods for OpenID Connect Back-Channel Logout 1.0 + + - `backchannelLogoutToken(BackchannelLogoutTokenRequest)` + *Examples* The following code snippet is an example to get the list of your existing diff --git a/pom.xml b/pom.xml index f23eb457cc..d7d6f96709 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ com.authlete authlete-java-common - 4.21-SNAPSHOT + 4.48-SNAPSHOT jar ${project.groupId}:${project.artifactId} @@ -32,7 +32,7 @@ UTF-8 scm:git:git@github.com:authlete/authlete-java-common.git git@github.com:authlete/authlete-java-common.git - 9.31 + 10.0.2 @@ -43,12 +43,6 @@ - - com.neovisionaries - nv-cipher - 1.3 - - com.google.code.gson gson @@ -68,23 +62,34 @@ test + + com.fasterxml.jackson.core + jackson-core + 2.19.2 + + + + com.fasterxml.jackson.core + jackson-databind + 2.19.2 + + com.fasterxml.jackson.core jackson-annotations - 2.18.2 + 2.19.2 - junit - junit - 4.13.1 - test + commons-codec + commons-codec + 1.19.0 - com.neovisionaries - nv-digest - 1.5 + junit + junit + 4.13.1 test @@ -128,7 +133,7 @@ com.github.spotbugs spotbugs-annotations - RELEASE + 4.9.6 compile @@ -213,6 +218,7 @@ true authlete + true diff --git a/src/main/java/com/authlete/common/api/AccessRight.java b/src/main/java/com/authlete/common/api/AccessRight.java index 5897387237..49441f104c 100644 --- a/src/main/java/com/authlete/common/api/AccessRight.java +++ b/src/main/java/com/authlete/common/api/AccessRight.java @@ -49,20 +49,24 @@ public enum AccessRight MODIFY_CLIENT(true, true, VIEW_CLIENT), /** can view the details of this service */ VIEW_SERVICE(false, true, VIEW_CLIENT), + /** can use the introspection endpoint */ + USE_INTROSPECTION(false, true), /** can use the non-destructive service API calls (auth endpoint, token endpoint, etc.) */ - USE_SERVICE(false, true, VIEW_SERVICE, VIEW_CLIENT), + USE_SERVICE(false, true, USE_INTROSPECTION, VIEW_SERVICE, VIEW_CLIENT), /** can create new clients on this service */ - CREATE_CLIENT(false, true, USE_SERVICE, VIEW_SERVICE, MODIFY_CLIENT, VIEW_CLIENT), + CREATE_CLIENT(false, true, USE_INTROSPECTION, USE_SERVICE, VIEW_SERVICE, MODIFY_CLIENT, VIEW_CLIENT), /** can modify this service */ - MODIFY_SERVICE(false, true, USE_SERVICE, VIEW_SERVICE, CREATE_CLIENT, MODIFY_CLIENT, VIEW_CLIENT), + MODIFY_SERVICE(false, true, USE_INTROSPECTION, USE_SERVICE, VIEW_SERVICE, CREATE_CLIENT, MODIFY_CLIENT, VIEW_CLIENT), /** can view default service parameters */ VIEW_DEFAULT_SERVICE(false, false), /** can create additional services */ CREATE_SERVICE(false, false, VIEW_DEFAULT_SERVICE), + /** can view audit logs */ + VIEW_AUDIT_LOG(false, false), /** can delete a specific service */ DELETE_SERVICE(false, true), /** can call administrative functions on the Authlete server */ - ADMIN(false, false, VIEW_DEFAULT_SERVICE, CREATE_SERVICE, DELETE_SERVICE, USE_SERVICE, VIEW_SERVICE, MODIFY_SERVICE, CREATE_CLIENT, MODIFY_CLIENT, VIEW_CLIENT) + ADMIN(false, false, VIEW_DEFAULT_SERVICE, CREATE_SERVICE, DELETE_SERVICE, USE_INTROSPECTION, USE_SERVICE, VIEW_SERVICE, MODIFY_SERVICE, CREATE_CLIENT, MODIFY_CLIENT, VIEW_CLIENT) ; diff --git a/src/main/java/com/authlete/common/api/AuthleteApi.java b/src/main/java/com/authlete/common/api/AuthleteApi.java index c29d265040..d77cd7fcc1 100644 --- a/src/main/java/com/authlete/common/api/AuthleteApi.java +++ b/src/main/java/com/authlete/common/api/AuthleteApi.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014-2025 Authlete, Inc. + * Copyright (C) 2014-2026 Authlete, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,6 +23,8 @@ import com.authlete.common.dto.AuthorizationIssueResponse; import com.authlete.common.dto.AuthorizationRequest; import com.authlete.common.dto.AuthorizationResponse; +import com.authlete.common.dto.AttestationChallengeRequest; +import com.authlete.common.dto.AttestationChallengeResponse; import com.authlete.common.dto.AuthorizationTicketInfoRequest; import com.authlete.common.dto.AuthorizationTicketInfoResponse; import com.authlete.common.dto.AuthorizationTicketUpdateRequest; @@ -36,6 +38,8 @@ import com.authlete.common.dto.BackchannelAuthenticationIssueResponse; import com.authlete.common.dto.BackchannelAuthenticationRequest; import com.authlete.common.dto.BackchannelAuthenticationResponse; +import com.authlete.common.dto.BackchannelLogoutTokenRequest; +import com.authlete.common.dto.BackchannelLogoutTokenResponse; import com.authlete.common.dto.Client; import com.authlete.common.dto.ClientAuthorizationGetListRequest; import com.authlete.common.dto.ClientAuthorizationUpdateRequest; @@ -58,6 +62,8 @@ import com.authlete.common.dto.CredentialIssuerMetadataResponse; import com.authlete.common.dto.CredentialJwtIssuerMetadataRequest; import com.authlete.common.dto.CredentialJwtIssuerMetadataResponse; +import com.authlete.common.dto.CredentialNonceRequest; +import com.authlete.common.dto.CredentialNonceResponse; import com.authlete.common.dto.CredentialOfferCreateRequest; import com.authlete.common.dto.CredentialOfferCreateResponse; import com.authlete.common.dto.CredentialOfferInfoRequest; @@ -4567,6 +4573,45 @@ CredentialDeferredIssueResponse credentialDeferredIssue( CredentialDeferredIssueRequest request, Options options) throws AuthleteApiException; + /** + * Call Authlete's {@code /vci/nonce} API. + * + * @param request + * Request parameters passed to the API. + * + * @return + * Response from the API. + * + * @since 4.27 + * @since Authlete 3.0.22 + */ + default CredentialNonceResponse credentialNonce( + CredentialNonceRequest request) throws AuthleteApiException + { + return credentialNonce(request, null); + } + + + /** + * Call Authlete's {@code /vci/nonce} API. + * + * @param request + * Request parameters passed to the API. + * + * @param options + * Request options. + * + * @return + * Response from the API. + * + * @since 4.27 + * @since Authlete 3.0.22 + */ + CredentialNonceResponse credentialNonce( + CredentialNonceRequest request, Options options) throws AuthleteApiException; + + + /** * Call Authlete's {@code /idtoken/reissue} API. * @@ -4801,4 +4846,80 @@ NativeSsoResponse nativeSso( */ NativeSsoLogoutResponse nativeSsoLogout( NativeSsoLogoutRequest request, Options options) throws AuthleteApiException; + + + /** + * Call Authlete's {@code /attestation/challenge} API. + * + * @param request + * Request parameters passed to the API. + * + * @return + * Response from the API. + * + * @since 4.39 + * @since Authlete 3.0.28 + */ + default AttestationChallengeResponse attestationChallenge( + AttestationChallengeRequest request) throws AuthleteApiException + { + return attestationChallenge(request, null); + } + + + /** + * Call Authlete's {@code /attestation/challenge} API. + * + * @param request + * Request parameters passed to the API. + * + * @param options + * Request options. + * + * @return + * Response from the API. + * + * @since 4.39 + * @since Authlete 3.0.28 + */ + AttestationChallengeResponse attestationChallenge( + AttestationChallengeRequest request, Options options) throws AuthleteApiException; + + + /** + * Call Authlete's {@code /backchannel/logout/token} API. + * + * @param request + * Request parameters passed to the API. + * + * @return + * Response from the API. + * + * @since 4.44 + * @since Authlete 3.0.32 + */ + default BackchannelLogoutTokenResponse backchannelLogoutToken( + BackchannelLogoutTokenRequest request) throws AuthleteApiException + { + return backchannelLogoutToken(request, null); + } + + + /** + * Call Authlete's {@code /backchannel/logout/token} API. + * + * @param request + * Request parameters passed to the API. + * + * @param options + * Request options. + * + * @return + * Response from the API. + * + * @since 4.44 + * @since Authlete 3.0.32 + */ + BackchannelLogoutTokenResponse backchannelLogoutToken( + BackchannelLogoutTokenRequest request, Options options) throws AuthleteApiException; } diff --git a/src/main/java/com/authlete/common/api/AuthleteApiImpl.java b/src/main/java/com/authlete/common/api/AuthleteApiImpl.java index 518d357f65..71c60a8784 100644 --- a/src/main/java/com/authlete/common/api/AuthleteApiImpl.java +++ b/src/main/java/com/authlete/common/api/AuthleteApiImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017-2025 Authlete, Inc. + * Copyright (C) 2017-2026 Authlete, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,6 +22,8 @@ import com.authlete.common.conf.AuthleteApiVersion; import com.authlete.common.conf.AuthleteConfiguration; import com.authlete.common.dto.ApiResponse; +import com.authlete.common.dto.AttestationChallengeRequest; +import com.authlete.common.dto.AttestationChallengeResponse; import com.authlete.common.dto.AuthorizationFailRequest; import com.authlete.common.dto.AuthorizationFailResponse; import com.authlete.common.dto.AuthorizationIssueRequest; @@ -41,6 +43,8 @@ import com.authlete.common.dto.BackchannelAuthenticationIssueResponse; import com.authlete.common.dto.BackchannelAuthenticationRequest; import com.authlete.common.dto.BackchannelAuthenticationResponse; +import com.authlete.common.dto.BackchannelLogoutTokenRequest; +import com.authlete.common.dto.BackchannelLogoutTokenResponse; import com.authlete.common.dto.Client; import com.authlete.common.dto.ClientAuthorizationDeleteRequest; import com.authlete.common.dto.ClientAuthorizationGetListRequest; @@ -66,6 +70,8 @@ import com.authlete.common.dto.CredentialIssuerMetadataResponse; import com.authlete.common.dto.CredentialJwtIssuerMetadataRequest; import com.authlete.common.dto.CredentialJwtIssuerMetadataResponse; +import com.authlete.common.dto.CredentialNonceRequest; +import com.authlete.common.dto.CredentialNonceResponse; import com.authlete.common.dto.CredentialOfferCreateRequest; import com.authlete.common.dto.CredentialOfferCreateResponse; import com.authlete.common.dto.CredentialOfferInfoRequest; @@ -141,6 +147,7 @@ */ class AuthleteApiImpl extends AuthleteApiBasicImpl { + private static final String ATTESTATION_CHALLENGE_API_PATH = "/api/attestation/challenge"; private static final String AUTH_AUTHORIZATION_API_PATH = "/api/auth/authorization"; private static final String AUTH_AUTHORIZATION_FAIL_API_PATH = "/api/auth/authorization/fail"; private static final String AUTH_AUTHORIZATION_ISSUE_API_PATH = "/api/auth/authorization/issue"; @@ -190,6 +197,7 @@ class AuthleteApiImpl extends AuthleteApiBasicImpl private static final String BC_AUTHENTICATION_COMPLETE_API_PATH = "/api/backchannel/authentication/complete"; private static final String BC_AUTHENTICATION_FAIL_API_PATH = "/api/backchannel/authentication/fail"; private static final String BC_AUTHENTICATION_ISSUE_API_PATH = "/api/backchannel/authentication/issue"; + private static final String BC_LOGOUT_TOKEN_API_PATH = "/api/backchannel/logout/token"; private static final String DEVICE_AUTHORIZATION_API_PATH = "/api/device/authorization"; private static final String DEVICE_COMPLETE_API_PATH = "/api/device/complete"; private static final String DEVICE_VERIFICATION_API_PATH = "/api/device/verification"; @@ -204,6 +212,7 @@ class AuthleteApiImpl extends AuthleteApiBasicImpl private static final String FEDERATION_CONFIGURATION_API_PATH = "/api/federation/configuration"; private static final String FEDERATION_REGISTRATION_API_PATH = "/api/federation/registration"; private static final String VCI_METADATA_API_PATH = "/api/vci/metadata"; + private static final String VCI_NONCE_API_PATH = "/api/vci/nonce"; private static final String VCI_JWT_ISSUER_API_PATH = "/api/vci/jwtissuer"; private static final String VCI_JWKS_API_PATH = "/api/vci/jwks"; private static final String VCI_OFFER_CREATE_API_PATH = "/api/vci/offer/create"; @@ -221,7 +230,7 @@ class AuthleteApiImpl extends AuthleteApiBasicImpl private final String mServiceOwnerAuth; private final String mServiceAuth; - + /** * The constructor with an instance of {@link AuthleteConfiguration}. * @@ -1704,6 +1713,21 @@ public CredentialDeferredIssueResponse credentialDeferredIssue( } + @Override + public CredentialNonceResponse credentialNonce( + CredentialNonceRequest request, Options options) throws AuthleteApiException + { + // Note that the /vci/nonce API is not available in Authlete 2.x, + // so the executeApiCall below will throw an exception. + + return executeApiCall( + new ServicePostApiCaller( + CredentialNonceResponse.class, request, + VCI_NONCE_API_PATH) + .setOptions(options)); + } + + @Override public IDTokenReissueResponse idTokenReissue( IDTokenReissueRequest request, Options options) throws AuthleteApiException @@ -1792,4 +1816,34 @@ public NativeSsoLogoutResponse nativeSsoLogout( NATIVE_SSO_LOGOUT_API_PATH) .setOptions(options)); } + + + @Override + public AttestationChallengeResponse attestationChallenge( + AttestationChallengeRequest request, Options options) throws AuthleteApiException + { + // Note that the /attestation/challenge API is not available in Authlete 2.x, + // so the executeApiCall below will throw an exception. + + return executeApiCall( + new ServicePostApiCaller( + AttestationChallengeResponse.class, request, + ATTESTATION_CHALLENGE_API_PATH) + .setOptions(options)); + } + + + @Override + public BackchannelLogoutTokenResponse backchannelLogoutToken( + BackchannelLogoutTokenRequest request, Options options) throws AuthleteApiException + { + // Note that the /backchannel/logout/token API is not available in + // Authlete 2.x, so the executeApiCall below will throw an exception. + + return executeApiCall( + new ServicePostApiCaller( + BackchannelLogoutTokenResponse.class, request, + BC_LOGOUT_TOKEN_API_PATH) + .setOptions(options)); + } } diff --git a/src/main/java/com/authlete/common/api/AuthleteApiImplV3.java b/src/main/java/com/authlete/common/api/AuthleteApiImplV3.java index cdbaa66b8c..fbf9857b61 100644 --- a/src/main/java/com/authlete/common/api/AuthleteApiImplV3.java +++ b/src/main/java/com/authlete/common/api/AuthleteApiImplV3.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2025 Authlete, Inc. + * Copyright (C) 2026 Authlete, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,6 +23,8 @@ import com.authlete.common.conf.AuthleteApiVersion; import com.authlete.common.conf.AuthleteConfiguration; import com.authlete.common.dto.ApiResponse; +import com.authlete.common.dto.AttestationChallengeRequest; +import com.authlete.common.dto.AttestationChallengeResponse; import com.authlete.common.dto.AuthorizationFailRequest; import com.authlete.common.dto.AuthorizationFailResponse; import com.authlete.common.dto.AuthorizationIssueRequest; @@ -42,6 +44,8 @@ import com.authlete.common.dto.BackchannelAuthenticationIssueResponse; import com.authlete.common.dto.BackchannelAuthenticationRequest; import com.authlete.common.dto.BackchannelAuthenticationResponse; +import com.authlete.common.dto.BackchannelLogoutTokenRequest; +import com.authlete.common.dto.BackchannelLogoutTokenResponse; import com.authlete.common.dto.Client; import com.authlete.common.dto.ClientAuthorizationDeleteRequest; import com.authlete.common.dto.ClientAuthorizationGetListRequest; @@ -67,6 +71,8 @@ import com.authlete.common.dto.CredentialIssuerMetadataResponse; import com.authlete.common.dto.CredentialJwtIssuerMetadataRequest; import com.authlete.common.dto.CredentialJwtIssuerMetadataResponse; +import com.authlete.common.dto.CredentialNonceRequest; +import com.authlete.common.dto.CredentialNonceResponse; import com.authlete.common.dto.CredentialOfferCreateRequest; import com.authlete.common.dto.CredentialOfferCreateResponse; import com.authlete.common.dto.CredentialOfferInfoRequest; @@ -142,6 +148,7 @@ */ public class AuthleteApiImplV3 extends AuthleteApiBasicImpl { + private static final String ATTESTATION_CHALLENGE_API_PATH = "/api/%d/attestation/challenge"; private static final String AUTH_AUTHORIZATION_API_PATH = "/api/%d/auth/authorization"; private static final String AUTH_AUTHORIZATION_FAIL_API_PATH = "/api/%d/auth/authorization/fail"; private static final String AUTH_AUTHORIZATION_ISSUE_API_PATH = "/api/%d/auth/authorization/issue"; @@ -191,6 +198,7 @@ public class AuthleteApiImplV3 extends AuthleteApiBasicImpl private static final String BACKCHANNEL_AUTHENTICATION_COMPLETE_API_PATH = "/api/%d/backchannel/authentication/complete"; private static final String BACKCHANNEL_AUTHENTICATION_FAIL_API_PATH = "/api/%d/backchannel/authentication/fail"; private static final String BACKCHANNEL_AUTHENTICATION_ISSUE_API_PATH = "/api/%d/backchannel/authentication/issue"; + private static final String BACKCHANNEL_LOGOUT_TOKEN_API_PATH = "/api/%d/backchannel/logout/token"; private static final String DEVICE_AUTHORIZATION_API_PATH = "/api/%d/device/authorization"; private static final String DEVICE_COMPLETE_API_PATH = "/api/%d/device/complete"; private static final String DEVICE_VERIFICATION_API_PATH = "/api/%d/device/verification"; @@ -207,6 +215,7 @@ public class AuthleteApiImplV3 extends AuthleteApiBasicImpl private static final String VCI_JWKS_API_PATH = "/api/%d/vci/jwks"; private static final String VCI_JWT_ISSUER_API_PATH = "/api/%d/vci/jwtissuer"; private static final String VCI_METADATA_API_PATH = "/api/%d/vci/metadata"; + private static final String VCI_NONCE_API_PATH = "/api/%d/vci/nonce"; private static final String VCI_OFFER_CREATE_API_PATH = "/api/%d/vci/offer/create"; private static final String VCI_OFFER_INFO_API_PATH = "/api/%d/vci/offer/info"; private static final String VCI_SINGLE_PARSE_API_PATH = "/api/%d/vci/single/parse"; @@ -1569,6 +1578,18 @@ public CredentialDeferredIssueResponse credentialDeferredIssue( } + @Override + public CredentialNonceResponse credentialNonce( + CredentialNonceRequest request, Options options) throws AuthleteApiException + { + return executeApiCall( + new PostApiCaller( + CredentialNonceResponse.class, request, + VCI_NONCE_API_PATH, mServiceId) + .setOptions(options)); + } + + @Override public IDTokenReissueResponse idTokenReissue( IDTokenReissueRequest request, Options options) throws AuthleteApiException @@ -1652,4 +1673,28 @@ public NativeSsoLogoutResponse nativeSsoLogout( NATIVE_SSO_LOGOUT_API_PATH, mServiceId) .setOptions(options)); } + + + @Override + public AttestationChallengeResponse attestationChallenge( + AttestationChallengeRequest request, Options options) throws AuthleteApiException + { + return executeApiCall( + new PostApiCaller( + AttestationChallengeResponse.class, request, + ATTESTATION_CHALLENGE_API_PATH, mServiceId) + .setOptions(options)); + } + + + @Override + public BackchannelLogoutTokenResponse backchannelLogoutToken( + BackchannelLogoutTokenRequest request, Options options) throws AuthleteApiException + { + return executeApiCall( + new PostApiCaller( + BackchannelLogoutTokenResponse.class, request, + BACKCHANNEL_LOGOUT_TOKEN_API_PATH, mServiceId) + .setOptions(options)); + } } diff --git a/src/main/java/com/authlete/common/conf/AuthletePropertiesConfiguration.java b/src/main/java/com/authlete/common/conf/AuthletePropertiesConfiguration.java index e5f5cc1d93..2ecc26b2ec 100644 --- a/src/main/java/com/authlete/common/conf/AuthletePropertiesConfiguration.java +++ b/src/main/java/com/authlete/common/conf/AuthletePropertiesConfiguration.java @@ -19,7 +19,7 @@ import java.util.logging.Logger; import com.authlete.common.util.PropertiesLoader; import com.authlete.common.util.TypedProperties; -import com.neovisionaries.security.AESCipher; +import com.authlete.common.util.security.AESCipher; /** diff --git a/src/main/java/com/authlete/common/dto/ApiResponse.java b/src/main/java/com/authlete/common/dto/ApiResponse.java index 4349368559..238bc2c9bb 100644 --- a/src/main/java/com/authlete/common/dto/ApiResponse.java +++ b/src/main/java/com/authlete/common/dto/ApiResponse.java @@ -17,6 +17,8 @@ import java.io.Serializable; +import java.util.List; +import java.util.Map; /** @@ -32,6 +34,7 @@ public class ApiResponse implements Serializable private String resultCode; private String resultMessage; + private Map> responseHeaders; /** @@ -81,4 +84,33 @@ public void setResultMessage(String message) { this.resultMessage = message; } + + + /** + * Get the HTTP response headers returned from an Authlete API call. + * + * @return + * A map of HTTP response headers. May be {@code null} or empty. + * + * @since 4.23 + */ + public Map> getResponseHeaders() + { + return responseHeaders; + } + + + /** + * Set the HTTP response headers returned from an Authlete API call. + * + * @param responseHeaders + * A map of HTTP response headers where each key is a header name + * and the corresponding value is a list of header values. + * + * @since 4.23 + */ + public void setResponseHeaders(Map> responseHeaders) + { + this.responseHeaders = responseHeaders; + } } diff --git a/src/main/java/com/authlete/common/dto/AttestationChallengeRequest.java b/src/main/java/com/authlete/common/dto/AttestationChallengeRequest.java new file mode 100644 index 0000000000..6c89203fd6 --- /dev/null +++ b/src/main/java/com/authlete/common/dto/AttestationChallengeRequest.java @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2026 Authlete, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.authlete.common.dto; + + +import java.io.Serializable; + + +/** + * Request to Authlete's {@code /attestation/challenge} API. + * + *

+ * The Authlete API is supposed to be used from within the implementation of + * the challenge endpoint of the authorization server. + *

+ * + * @since 4.39 + * @since Authlete 3.0.28 + * + * @see + * OAuth 2.0 Attestation-Based Client Authentication + */ +public class AttestationChallengeRequest implements Serializable +{ + private static final long serialVersionUID = 1L; + + + private boolean pretty; + + + + /** + * Get the flag indicating whether the challenge response is written in the + * pretty format or not. + * + * @return + * {@code true} if the challenge response is written in the pretty + * format. + */ + public boolean isPretty() + { + return pretty; + } + + + /** + * Set the flag indicating whether the challenge response is written in the + * pretty format or not. + * + * @param pretty + * {@code true} to write the challenge response in the pretty format. + * + * @return + * {@code this} object. + */ + public AttestationChallengeRequest setPretty(boolean pretty) + { + this.pretty = pretty; + + return this; + } +} diff --git a/src/main/java/com/authlete/common/dto/AttestationChallengeResponse.java b/src/main/java/com/authlete/common/dto/AttestationChallengeResponse.java new file mode 100644 index 0000000000..b3ad5a306e --- /dev/null +++ b/src/main/java/com/authlete/common/dto/AttestationChallengeResponse.java @@ -0,0 +1,249 @@ +/* + * Copyright (C) 2026 Authlete, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.authlete.common.dto; + + +/** + * Response from Authlete's {@code /attestation/challenge} API. + * + *

+ * The Authlete API is supposed to be used from within the implementation + * the challenge endpoint of the authorization server. + *

+ * + *

+ * Authlete's {@code /attestation/challenge} API returns JSON which can be + * mapped to this class. The authorization server implementation should + * retrieve the value of the {@link #getAction() action} + * response parameter from the API response and take the following steps + * according to the value. + *

+ * + *

OK

+ * + *

+ * When the value of the {@link #getAction() action} response + * parameter is {@link Action#OK OK}, it means that Authlete + * could prepare a challenge response successfully. + *

+ * + *

+ * In this case, the implementation of the challenge endpoint of the + * authorization server should return an HTTP response with the HTTP status + * code "{@code 200 OK}" and the content type "{@code application/json}". + * The message body of the response has been prepared by Authlete's + * {@code /attestation/challenge} API and it is available as the + * {@link #getResponseContent() responseContent} response + * parameter. + *

+ * + *

+ * The implementation of the challenge endpoint can construct an HTTP response + * by doing like below. + *

+ * + *
+ * 200 OK
+ * Content-Type: application/json
+ * Cache-Control: no-store
+ * (Other HTTP headers)
+ *
+ * (the value of the {@link #getResponseContent() responseContent} response parameter)
+ * + *

+ * Note that the OAuth 2.0 Attestation-Based Client Authentication specification + * requires the response to include the {@code Cache-Control} header including + * the value {@code no-store}. + *

+ * + *

INTERNAL_SERVER_ERROR

+ * + *

+ * When the value of the {@link #getAction() action} response + * parameter is {@link Action#INTERNAL_SERVER_ERROR INTERNAL_SERVER_ERROR}, + * it means that an unexpected error has occurred on Authlete side or the + * service has not been set up properly yet. + *

+ * + *

+ * In this case, a simple implementation of the challenge endpoint would return + * an HTTP response with the HTTP status code "{@code 500 Internal Server Error}" + * and the content type "{@code application/json}". The message body (= error + * information in the JSON format) of the response has been prepared by Authlete's + * {@code /attestation/challenge} API and it is available as the + * {@link #getResponseContent() responseContent} response parameter. + *

+ * + *

+ * Such simple implementation of the challenge endpoint can construct an HTTP + * response by doing like below. + *

+ * + *
+ * 500 Internal Server Error
+ * Content-Type: application/json
+ * (Other HTTP headers)
+ *
+ * (the value of the {@link #getResponseContent() responseContent} response parameter)
+ * + *

+ * However, in real commercial deployments, it is rare for an authorization + * server to return "{@code 500 Internal Server Error}" when it encounters an + * unexpected internal error. It's up to implementations of authorization + * servers what they actually return in the case of internal server error. + *

+ * + * @since 4.39 + * @since Authlete 3.0.28 + * + * @see + * OAuth 2.0 Attestation-Based Client Authentication + */ +public class AttestationChallengeResponse extends ApiResponse +{ + private static final long serialVersionUID = 1L; + + + /** + * The next action that the implementation of the challenge endpoint + * should take after getting a response from Authlete's + * {@code /attestation/challenge} API. + * + * @since 4.39 + * @since Authlete 3.0.28 + */ + public enum Action + { + /** + * A challenge response has been prepared successfully. The + * implementation of the challenge endpoint should return an HTTP + * response with the HTTP status code "{@code 200 OK}" and the content + * type "{@code application/json}". + */ + OK, + + /** + * An unexpected error occurred on Authlete side or the service has + * not been set up properly yet. A simple implementation of the + * challenge endpoint would return an HTTP response with the HTTP + * status code "{@code 500 Internal Server Error}" and the content + * type "{@code application/json}". + */ + INTERNAL_SERVER_ERROR, + } + + + private Action action; + private String responseContent; + private String attestationChallenge; + + + /** + * Get the next action that the implementation of the challenge endpoint + * should take after getting a response from Authlete's + * {@code /attestation/challenge} API. + * + * @return + * The next action. + */ + public Action getAction() + { + return action; + } + + + /** + * Set the next action that the implementation of the challenge endpoint + * should take after getting a response from Authlete's + * {@code /attestation/challenge} API. + * + * @param action + * The next action. + * + * @return + * {@code this} object. + */ + public AttestationChallengeResponse setAction(Action action) + { + this.action = action; + + return this; + } + + + /** + * Get the content that the implementation of the challenge endpoint should + * use when it constructs a response. + * + * @return + * The response content in the JSON format. + */ + public String getResponseContent() + { + return responseContent; + } + + + /** + * Set the content that the implementation of the challenge endpoint should + * use when it constructs a response. + * + * @param content + * The response content in the JSON format. + * + * @return + * {@code this} object. + */ + public AttestationChallengeResponse setResponseContent(String content) + { + this.responseContent = content; + + return this; + } + + + /** + * Get the {@code attestation_challenge} value generated by the + * {@code /attestation/challenge} API. + * + * @return + * The generated {@code attestation_challenge} value. + */ + public String getAttestationChallenge() + { + return attestationChallenge; + } + + + /** + * Set the {@code attestation_challenge} value generated by the + * {@code /attestation/challenge} API. + * + * @param challenge + * The generated {@code attestation_challenge} value. + * + * @return + * {@code this} object. + */ + public AttestationChallengeResponse setAttestationChallenge(String challenge) + { + this.attestationChallenge = challenge; + + return this; + } +} diff --git a/src/main/java/com/authlete/common/dto/AuthorizationFailRequest.java b/src/main/java/com/authlete/common/dto/AuthorizationFailRequest.java index 4a690ba4a0..47b7ae2028 100644 --- a/src/main/java/com/authlete/common/dto/AuthorizationFailRequest.java +++ b/src/main/java/com/authlete/common/dto/AuthorizationFailRequest.java @@ -261,6 +261,40 @@ public enum Reason * @since 2.62 */ INVALID_TARGET, + + /** + * The requested scope is invalid, unknown, or malformed. + * + *

+ * Using this reason will result in {@code error=invalid_scope}. + *

+ * + * @since 4.46 + */ + INVALID_SCOPE, + + /** + * The client is not authorized to request an authorization code using this method. + * + *

+ * Using this reason will result in {@code error=unauthorized_client}. + *

+ * @since 4.46 + */ + UNAUTHORIZED_CLIENT, + + /** + * The authorization server is currently unable to handle the request due to a temporary overloading or maintenance + * of the server. (This error code is needed because a 503 Service Unavailable HTTP status code cannot be returned + * to the client via an HTTP redirect.) + * + *

+ * Using this reason will result in {@code error=temporarily_unavailable}. + *

+ * + * @since 4.46 + */ + TEMPORARILY_UNAVAILABLE, } diff --git a/src/main/java/com/authlete/common/dto/AuthorizationIssueRequest.java b/src/main/java/com/authlete/common/dto/AuthorizationIssueRequest.java index 2a298dfc95..24ec702ad6 100644 --- a/src/main/java/com/authlete/common/dto/AuthorizationIssueRequest.java +++ b/src/main/java/com/authlete/common/dto/AuthorizationIssueRequest.java @@ -1,11 +1,11 @@ /* - * Copyright (C) 2014-2025 Authlete, Inc. + * Copyright (C) 2014-2026 Authlete, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -45,9 +45,9 @@ * is required unless the authorization request has come with * {@code response_type=none} (which means the client application * did not request any token to be returned). See "4. None Response Type" in OAuth 2.0 Multiple Response Type Encoding Practices for * details about {@code response_type=none}. *

@@ -73,7 +73,7 @@ *

* The claims of the end-user (= pieces of information about the * end-user) in JSON format. See - * OpenID Connect Core 1.0, 5.1. Standard Claims for details * about the format. *

@@ -122,7 +122,7 @@ * If you add "offline_access" scope although it is not * included in the original request, keep in mind that the specification * requires explicit consent from the user for the scope (OpenID Connect Core 1.0, 11. Offline Access). When * "offline_access" is included in the original request, the * current implementation of Authlete's {@code /auth/authorization} API @@ -234,7 +234,7 @@ * * @see AuthorizationResponse * - * @see OpenID Connect Core 1.0, 5.1. Standard Claims * * @author Takahiko Kawasaki @@ -659,7 +659,7 @@ public String getClaims() *

* *

- * See OpenID Connect Core 1.0, 5.1. Standard Claims for further details * about the format. *

@@ -670,7 +670,7 @@ public String getClaims() * @return * {@code this} object. * - * @see OpenID Connect Core 1.0, 5.1. Standard Claims */ public AuthorizationIssueRequest setClaims(String claims) @@ -750,7 +750,7 @@ public Property[] getProperties() * Keys listed below should not be used and they would be ignored on * the server side even if they were used. It's because they are reserved * in RFC 6749 and - * OpenID Connect Core 1.0. *

* @@ -835,7 +835,7 @@ public String[] getScopes() * If you add "offline_access" scope although it is not * included in the original request, keep in mind that the specification * requires explicit consent from the user for the scope (OpenID Connect Core 1.0, 11. Offline Access). When * "offline_access" is included in the original request, the * current implementation of Authlete's /api/auth/authorization API checks @@ -1657,7 +1657,7 @@ public AuthorizationIssueRequest setAccessTokenDuration(long duration) * claim. * *

- * This parameter needs to be provided only if you want to support the + * This parameter needs to be provided if you want to support the * "OpenID Connect Native SSO for Mobile Apps 1.0" specification * (a.k.a. "Native SSO"). To enable support for the Native SSO specification, @@ -1669,10 +1669,28 @@ public AuthorizationIssueRequest setAccessTokenDuration(long duration) * NOTE: When the response from the {@code /auth/authorization} API * contains the {@code nativeSsoRequested} property with a value of * {@code true} (see {@link AuthorizationResponse#isNativeSsoRequested()}), - * the {@code sessionId} request parameter must be provided to the + * this {@code sessionId} request parameter must be provided to the * {@code /auth/authorization/issue} API. *

* + *

+ * Also, if your server's + * backchannel_logout_session_supported metadata + * parameter is set to {@code true} (see + * {@link Service#isBackchannelLogoutSessionSupported()}), this + * {@code sessionId} request parameter should be provided. See the OpenID + * Connect Back-Channel Logout 1.0 specification for the metadata + * parameter. + *

+ * + *

+ * In the current Authlete implementation, this {@code sessionId} request + * parameter is recognized only when Native SSO is requested or the + * backchannel_logout_session_supported server + * metadata parameter is set to {@code true}. + *

+ * * @return * The session ID of the user's authentication session. * @@ -1683,6 +1701,10 @@ public AuthorizationIssueRequest setAccessTokenDuration(long duration) * >OpenID Connect Native SSO for Mobile Apps 1.0 * @see AuthorizationResponse#isNativeSsoRequested() * @see Service#isNativeSsoSupported() + * + * @see OpenID Connect Back-Channel Logout 1.0 + * @see Service#isBackchannelLogoutSessionSupported() */ public String getSessionId() { @@ -1696,7 +1718,7 @@ public String getSessionId() * claim. * *

- * This parameter needs to be provided only if you want to support the + * This parameter needs to be provided if you want to support the * "OpenID Connect Native SSO for Mobile Apps 1.0" specification * (a.k.a. "Native SSO"). To enable support for the Native SSO specification, @@ -1708,10 +1730,28 @@ public String getSessionId() * NOTE: When the response from the {@code /auth/authorization} API * contains the {@code nativeSsoRequested} property with a value of * {@code true} (see {@link AuthorizationResponse#isNativeSsoRequested()}), - * the {@code sessionId} request parameter must be provided to the + * this {@code sessionId} request parameter must be provided to the * {@code /auth/authorization/issue} API. *

* + *

+ * Also, if your server's + * backchannel_logout_session_supported metadata + * parameter is set to {@code true} (see + * {@link Service#isBackchannelLogoutSessionSupported()}), this + * {@code sessionId} request parameter should be provided. See the OpenID + * Connect Back-Channel Logout 1.0 specification for the metadata + * parameter. + *

+ * + *

+ * In the current Authlete implementation, this {@code sessionId} request + * parameter is recognized only when Native SSO is requested or the + * backchannel_logout_session_supported server + * metadata parameter is set to {@code true}. + *

+ * * @param sessionId * The session ID of the user's authentication session. * @@ -1725,6 +1765,10 @@ public String getSessionId() * >OpenID Connect Native SSO for Mobile Apps 1.0 * @see AuthorizationResponse#isNativeSsoRequested() * @see Service#isNativeSsoSupported() + * + * @see OpenID Connect Back-Channel Logout 1.0 + * @see Service#isBackchannelLogoutSessionSupported() */ public AuthorizationIssueRequest setSessionId(String sessionId) { diff --git a/src/main/java/com/authlete/common/dto/AuthorizationRequest.java b/src/main/java/com/authlete/common/dto/AuthorizationRequest.java index 5c2aeabd00..4a0d2f696e 100644 --- a/src/main/java/com/authlete/common/dto/AuthorizationRequest.java +++ b/src/main/java/com/authlete/common/dto/AuthorizationRequest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014-2023 Authlete, Inc. + * Copyright (C) 2014-2025 Authlete, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -50,7 +50,7 @@ */ public class AuthorizationRequest implements Serializable { - private static final long serialVersionUID = 3L; + private static final long serialVersionUID = 4L; /** @@ -69,6 +69,15 @@ public class AuthorizationRequest implements Serializable private String context; + /** + * Options for CIMD processing. + * + * @since 4.30 + * @since Authlete 3.0.22 + */ + private CimdOptions cimdOptions; + + /** * Get the value of {@code parameters} which are the request * parameters that the OAuth 2.0 authorization endpoint of the @@ -184,4 +193,49 @@ public AuthorizationRequest setContext(String context) return this; } + + + /** + * Get options for CIMD processing. + * + * @return + * Options for CIMD processing. + * + * @since 4.30 + * @since Authlete 3.0.22 + * + * @see + * OAuth Client ID Metadata Document + */ + public CimdOptions getCimdOptions() + { + return cimdOptions; + } + + + /** + * Set options for CIMD processing. + * + * @param options + * Options for CIMD processing. + * + * @return + * {@code this} object. + * + * @since 4.30 + * @since Authlete 3.0.22 + * + * @see + * OAuth Client ID Metadata Document + */ + public AuthorizationRequest setCimdOptions(CimdOptions options) + { + this.cimdOptions = options; + + return this; + } } diff --git a/src/main/java/com/authlete/common/dto/AuthorizationResponse.java b/src/main/java/com/authlete/common/dto/AuthorizationResponse.java index 2b949d692c..e2117a4ee8 100644 --- a/src/main/java/com/authlete/common/dto/AuthorizationResponse.java +++ b/src/main/java/com/authlete/common/dto/AuthorizationResponse.java @@ -1021,7 +1021,7 @@ */ public class AuthorizationResponse extends ApiResponse { - private static final long serialVersionUID = 21L; + private static final long serialVersionUID = 22L; /** @@ -1161,6 +1161,11 @@ public enum Action */ private boolean clientEntityIdUsed; + /** + * @since Authlete 3.0.22 + */ + private boolean metadataDocumentUsed; + /** * @since Authlete 1.1 */ @@ -1756,6 +1761,75 @@ public void setClientEntityIdUsed(boolean used) } + /** + * Get the flag which indicates whether the value of the {@code client_id} + * request parameter included in the authorization request is the location + * of the client's metadata document. + * + *

+ * This can happen when the service supports OAuth Client ID Metadata Document. + * (cf. {@link Service#isClientIdMetadataDocumentSupported()}) + *

+ * + *

+ * When this flag is {@code true}, {@code client.}{@link + * Client#getMetadataDocumentLocation() getMetadataDocumentLocation()} + * returns the location of the client's metadata document. + *

+ * + * @return + * {@code true} if the value of the {@code client_id} request + * parameter is the location of the client's metadata document. + * + * @since 4.29 + * @since Authlete 3.0.22 + * + * @see + * OAuth Client ID Metadata Document + */ + public boolean isMetadataDocumentUsed() + { + return metadataDocumentUsed; + } + + + /** + * Set the flag which indicates whether the value of the {@code client_id} + * request parameter included in the authorization request is the location + * of the client's metadata document. + * + *

+ * This can happen when the service supports OAuth Client ID Metadata Document. + * (cf. {@link Service#isClientIdMetadataDocumentSupported()}) + *

+ * + *

+ * When this flag is {@code true}, {@code client.}{@link + * Client#getMetadataDocumentLocation() getMetadataDocumentLocation()} + * returns the location of the client's metadata document. + *

+ * + * @param used + * {@code true} to indicate that the value of the {@code client_id} + * request parameter is the location of the client's metadata + * document. + * + * @since 4.29 + * @since Authlete 3.0.22 + * + * @see + * OAuth Client ID Metadata Document + */ + public void setMetadataDocumentUsed(boolean used) + { + metadataDocumentUsed = used; + } + + /** * Get the list of ACRs (Authentication Context Class References) * requested by the client application. The value come from (1) diff --git a/src/main/java/com/authlete/common/dto/AuthzDetailsElement.java b/src/main/java/com/authlete/common/dto/AuthzDetailsElement.java index be8b29d4d9..f69bf8ab8b 100644 --- a/src/main/java/com/authlete/common/dto/AuthzDetailsElement.java +++ b/src/main/java/com/authlete/common/dto/AuthzDetailsElement.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2019-2021 Authlete, Inc. + * Copyright (C) 2019-2026 Authlete, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -30,7 +30,7 @@ */ public class AuthzDetailsElement implements Serializable { - private static final long serialVersionUID = 4L; + private static final long serialVersionUID = 5L; private String type; @@ -42,6 +42,43 @@ public class AuthzDetailsElement implements Serializable private String otherFields; + /** + * The default constructor. + */ + public AuthzDetailsElement() + { + } + + + /** + * The copy constructor. + * + * @param element + * The source instance. + * + * @since 4.34 + */ + public AuthzDetailsElement(AuthzDetailsElement element) + { + if (element != null) + { + this.type = element.type; + this.locations = copy(element.locations); + this.actions = copy(element.actions); + this.dataTypes = copy(element.dataTypes); + this.identifier = element.identifier; + this.privileges = copy(element.privileges); + this.otherFields = element.otherFields; + } + } + + + private static String[] copy(String[] source) + { + return (source != null) ? source.clone() : null; + } + + /** * Get the type of this element. * diff --git a/src/main/java/com/authlete/common/dto/BackchannelAuthenticationCompleteResponse.java b/src/main/java/com/authlete/common/dto/BackchannelAuthenticationCompleteResponse.java index 67326120b9..c4bb60e36a 100644 --- a/src/main/java/com/authlete/common/dto/BackchannelAuthenticationCompleteResponse.java +++ b/src/main/java/com/authlete/common/dto/BackchannelAuthenticationCompleteResponse.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2018-2023 Authlete, Inc. + * Copyright (C) 2018-2025 Authlete, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -139,7 +139,7 @@ */ public class BackchannelAuthenticationCompleteResponse extends ApiResponse { - private static final long serialVersionUID = 8L; + private static final long serialVersionUID = 9L; /** @@ -218,6 +218,22 @@ public enum Action */ private boolean clientEntityIdUsed; + /** + * The location of the client's metadata document. + * + * @since 4.29 + * @since Authlete 3.0.22 + */ + private URI metadataDocumentLocation; + + /** + * Whether the client's metadata document was used. + * + * @since 4.29 + * @since Authlete 3.0.22 + */ + private boolean metadataDocumentUsed; + /** * @since Authlete 2.0.0 */ @@ -581,18 +597,129 @@ public BackchannelAuthenticationCompleteResponse setClientEntityIdUsed(boolean u /** - * Get the client identifier used in the backchannel authentication - * request. + * Get the location of the client's metadata document. This property + * holds a non-null value only when {@link #isMetadataDocumentUsed()} + * returns {@code true}. + * + * @return + * The location of the client's metadata document. + * + * @since 4.29 + * @since Authlete 3.0.22 + * + * @see + * OAuth Client ID Metadata Document + */ + public URI getMetadataDocumentLocation() + { + return metadataDocumentLocation; + } + + + /** + * Set the location of the client's metadata document. This property + * should hold a non-null value only when {@link #isMetadataDocumentUsed()} + * returns {@code true}. + * + * @param location + * The location of the client's metadata document. + * + * @return + * {@code this} object. + * + * @since 4.29 + * @since Authlete 3.0.22 + * + * @see + * OAuth Client ID Metadata Document + */ + public BackchannelAuthenticationCompleteResponse setMetadataDocumentLocation(URI location) + { + this.metadataDocumentLocation = location; + + return this; + } + + + /** + * Get the flag which indicates whether the location of the client's + * metadata document was used as a client ID. + * + *

+ * This can happen when the service supports OAuth Client ID Metadata Document. + * (cf. {@link Service#isClientIdMetadataDocumentSupported()}) + *

+ * + * @return + * {@code true} if the location of client's metadata document + * was used as a client ID. + * + * @since 4.29 + * @since Authlete 3.0.22 + * + * @see + * OAuth Client ID Metadata Document + */ + public boolean isMetadataDocumentUsed() + { + return metadataDocumentUsed; + } + + + /** + * Set the flag which indicates whether the location of the client's + * metadata document was used as a client ID. * *

- * When {@link #isClientIdAliasUsed()} returns {@code true}, this method - * returns the same value as {@link #getClientIdAlias()} does. Otherwise, - * if {@link #isClientEntityIdUsed()} returns {@code true}, this method - * returns the same value as {@link #getClientEntityId()}{@code .toString()} - * does. In other cases, this method returns the string representation of - * the value returned from {@link #getClientId()}. + * This can happen when the service supports OAuth Client ID Metadata Document. + * (cf. {@link Service#isClientIdMetadataDocumentSupported()}) *

* + * @param used + * {@code true} to indicate that the location of the client's + * metadata document was used as a client ID. + * + * @return + * {@code this} object. + * + * @since 4.29 + * @since Authlete 3.0.22 + * + * @see + * OAuth Client ID Metadata Document + */ + public BackchannelAuthenticationCompleteResponse setMetadataDocumentUsed(boolean used) + { + this.metadataDocumentUsed = used; + + return this; + } + + + /** + * Get the client identifier used in the backchannel authentication + * request. + * + *
    + *
  1. When {@link #isClientEntityIdUsed()} returns {@code true}, this + * method returns the same value as {@link #getClientIdAlias()} does. + * + *
  2. Otherwise, if {@link #isClientEntityIdUsed()} returns {@code true}, + * this method returns the same value as + * {@link #getClientEntityId()}{@code .toString()} does. + * + *
  3. Otherwise, if {@link #isMetadataDocumentUsed()} returns {@code true}, + * this method returns the same value as + * {@link #getMetadataDocumentLocation()}{@code .toString()} does. + * + *
  4. In other cases, this method returns the string representation of + * the value returned from {@link #getClientId()}. + *
+ * * @return * The client identifier used in the backchannel authentication * request. @@ -607,6 +734,10 @@ else if (clientEntityIdUsed) { return clientEntityId.toString(); } + else if (metadataDocumentUsed) + { + return metadataDocumentLocation.toString(); + } else { return String.valueOf(clientId); diff --git a/src/main/java/com/authlete/common/dto/BackchannelAuthenticationRequest.java b/src/main/java/com/authlete/common/dto/BackchannelAuthenticationRequest.java index 68f0151cfb..6aa3157c7a 100644 --- a/src/main/java/com/authlete/common/dto/BackchannelAuthenticationRequest.java +++ b/src/main/java/com/authlete/common/dto/BackchannelAuthenticationRequest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2018-2024 Authlete, Inc. + * Copyright (C) 2018-2025 Authlete, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -124,7 +124,7 @@ */ public class BackchannelAuthenticationRequest implements Serializable { - private static final long serialVersionUID = 2L; + private static final long serialVersionUID = 3L; /** @@ -181,6 +181,15 @@ public class BackchannelAuthenticationRequest implements Serializable private String oauthClientAttestationPop; + /** + * Options for CIMD processing. + * + * @since 4.30 + * @since Authlete 3.0.22 + */ + private CimdOptions cimdOptions; + + /** * Get the value of {@code parameters} which are the request parameters * that the backchannel authentication endpoint of the OpenID provider @@ -446,4 +455,49 @@ public BackchannelAuthenticationRequest setOauthClientAttestationPop(String jwt) return this; } + + + /** + * Get options for CIMD processing. + * + * @return + * Options for CIMD processing. + * + * @since 4.30 + * @since Authlete 3.0.22 + * + * @see + * OAuth Client ID Metadata Document + */ + public CimdOptions getCimdOptions() + { + return cimdOptions; + } + + + /** + * Set options for CIMD processing. + * + * @param options + * Options for CIMD processing. + * + * @return + * {@code this} object. + * + * @since 4.30 + * @since Authlete 3.0.22 + * + * @see + * OAuth Client ID Metadata Document + */ + public BackchannelAuthenticationRequest setCimdOptions(CimdOptions options) + { + this.cimdOptions = options; + + return this; + } } diff --git a/src/main/java/com/authlete/common/dto/BackchannelAuthenticationResponse.java b/src/main/java/com/authlete/common/dto/BackchannelAuthenticationResponse.java index 73d6afb732..0828ba2016 100644 --- a/src/main/java/com/authlete/common/dto/BackchannelAuthenticationResponse.java +++ b/src/main/java/com/authlete/common/dto/BackchannelAuthenticationResponse.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2018-2023 Authlete, Inc. + * Copyright (C) 2018-2026 Authlete, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of @@ -594,11 +594,39 @@ * * * + *
+ *
+ *

Attestation Challenge (Authlete 3.0.28 onwards)

+ * + *

+ * Since version 3.0.28, Authlete recognizes the {@code challenge} claim in + * Client Attestation PoP JWTs. If the {@code challenge} claim is required + * (= if the service's challenge endpoint is configured), the Authlete API + * checks whether the {@code challenge} claim in the presented Client + * Attestation PoP JWT is identical to the expected value. + *

+ * + *

+ * If the {@code attestationChallenge} response parameter from the API is not + * null, its value is the expected attestation challenge value for Client + * Attestation PoP JWT. The expected value needs to be conveyed to the client + * application as the value of the {@code OAuth-Client-Attestation-Challenge} + * HTTP header. + *

+ * + *
OAuth-Client-Attestation-Challenge: (The value returned from {@link #getAttestationChallenge()})
+ * + *

+ * See OAuth 2.0 Attestation-Based Client Authentication for details. + *

+ * * @since 2.32 */ public class BackchannelAuthenticationResponse extends ApiResponse { - private static final long serialVersionUID = 11L; + private static final long serialVersionUID = 13L; /** @@ -682,6 +710,22 @@ public enum Action */ private boolean clientEntityIdUsed; + /** + * The location of the client's metadata document. + * + * @since 4.29 + * @since Authlete 3.0.22 + */ + private URI metadataDocumentLocation; + + /** + * Whether the client's metadata document was used. + * + * @since 4.29 + * @since Authlete 3.0.22 + */ + private boolean metadataDocumentUsed; + /** * @since Authlete 2.0.0 */ @@ -812,6 +856,16 @@ public enum Action */ private String ticket; + /** + * The expected attestation challenge value for Client Attestation PoP JWT, + * which should be used as the value of the + * {@code OAuth-Client-Attestation-Challenge} HTTP header. + * + * @since 4.39 + * @since Authlete 3.0.28 + */ + private String attestationChallenge; + /** * Get the next action that the implementation of the backchannel @@ -1087,18 +1141,129 @@ public BackchannelAuthenticationResponse setClientEntityIdUsed(boolean used) /** - * Get the client identifier used in the backchannel authentication - * request. + * Get the location of the client's metadata document. This property + * holds a non-null value only when {@link #isMetadataDocumentUsed()} + * returns {@code true}. + * + * @return + * The location of the client's metadata document. + * + * @since 4.29 + * @since Authlete 3.0.22 + * + * @see + * OAuth Client ID Metadata Document + */ + public URI getMetadataDocumentLocation() + { + return metadataDocumentLocation; + } + + + /** + * Set the location of the client's metadata document. This property + * should hold a non-null value only when {@link #isMetadataDocumentUsed()} + * returns {@code true}. + * + * @param location + * The location of the client's metadata document. + * + * @return + * {@code this} object. + * + * @since 4.29 + * @since Authlete 3.0.22 + * + * @see + * OAuth Client ID Metadata Document + */ + public BackchannelAuthenticationResponse setMetadataDocumentLocation(URI location) + { + this.metadataDocumentLocation = location; + + return this; + } + + + /** + * Get the flag which indicates whether the location of the client's + * metadata document was used as a client ID. + * + *

+ * This can happen when the service supports OAuth Client ID Metadata Document. + * (cf. {@link Service#isClientIdMetadataDocumentSupported()}) + *

+ * + * @return + * {@code true} if the location of client's metadata document + * was used as a client ID. + * + * @since 4.29 + * @since Authlete 3.0.22 + * + * @see + * OAuth Client ID Metadata Document + */ + public boolean isMetadataDocumentUsed() + { + return metadataDocumentUsed; + } + + + /** + * Set the flag which indicates whether the location of the client's + * metadata document was used as a client ID. * *

- * When {@link #isClientIdAliasUsed()} returns {@code true}, this method - * returns the same value as {@link #getClientIdAlias()} does. Otherwise, - * if {@link #isClientEntityIdUsed()} returns {@code true}, this method - * returns the same value as {@link #getClientEntityId()}{@code .toString()} - * does. In other cases, this method returns the string representation of - * the value returned from {@link #getClientId()}. + * This can happen when the service supports OAuth Client ID Metadata Document. + * (cf. {@link Service#isClientIdMetadataDocumentSupported()}) *

* + * @param used + * {@code true} to indicate that the location of the client's + * metadata document was used as a client ID. + * + * @return + * {@code this} object. + * + * @since 4.29 + * @since Authlete 3.0.22 + * + * @see + * OAuth Client ID Metadata Document + */ + public BackchannelAuthenticationResponse setMetadataDocumentUsed(boolean used) + { + this.metadataDocumentUsed = used; + + return this; + } + + + /** + * Get the client identifier used in the backchannel authentication + * request. + * + *
    + *
  1. When {@link #isClientEntityIdUsed()} returns {@code true}, this + * method returns the same value as {@link #getClientIdAlias()} does. + * + *
  2. Otherwise, if {@link #isClientEntityIdUsed()} returns {@code true}, + * this method returns the same value as + * {@link #getClientEntityId()}{@code .toString()} does. + * + *
  3. Otherwise, if {@link #isMetadataDocumentUsed()} returns {@code true}, + * this method returns the same value as + * {@link #getMetadataDocumentLocation()}{@code .toString()} does. + * + *
  4. In other cases, this method returns the string representation of + * the value returned from {@link #getClientId()}. + *
+ * * @return * The client identifier used in the backchannel authentication * request. @@ -1113,6 +1278,10 @@ else if (clientEntityIdUsed) { return clientEntityId.toString(); } + else if (metadataDocumentUsed) + { + return metadataDocumentLocation.toString(); + } else { return String.valueOf(clientId); @@ -2218,4 +2387,75 @@ public BackchannelAuthenticationResponse setTicket(String ticket) return this; } + + + /** + * Get the expected attestation challenge value for Client Attestation PoP + * JWT, which should be used as the value of the + * {@code OAuth-Client-Attestation-Challenge} HTTP header. + * + *

+ * When this response parameter is not null, the implementation of the + * backchannel authentication endpoint should add the + * {@code OAuth-Client-Attestation-Challenge} HTTP header in the response + * from the endpoint to the client application, using the value of this + * response parameter as the value of the HTTP header. + *

+ * + *
+     * OAuth-Client-Attestation-Challenge: (The value of this {@code attestationChallenge} response parameter)
+     * 
+ * + * @return + * The expected attestation challenge value for Client Attestation + * PoP JWT. + * + * @since 4.39 + * @since Authlete 3.0.28 + * + * @see + * OAuth 2.0 Attestation-Based Client Authentication + */ + public String getAttestationChallenge() + { + return attestationChallenge; + } + + + /** + * Set the expected attestation challenge value for Client Attestation PoP + * JWT, which should be used as the value of the + * {@code OAuth-Client-Attestation-Challenge} HTTP header. + * + *

+ * When this response parameter is not null, the implementation of the + * backchannel authentication endpoint should add the + * {@code OAuth-Client-Attestation-Challenge} HTTP header in the response + * from the endpoint to the client application, using the value of this + * response parameter as the value of the HTTP header. + *

+ * + *
+     * OAuth-Client-Attestation-Challenge: (The value of this {@code attestationChallenge} response parameter)
+     * 
+ * + * @param challenge + * The expected attestation challenge value for Client Attestation + * PoP JWT. + * + * @return + * {@code this} object. + * + * @since 4.39 + * @since Authlete 3.0.28 + * + * @see + * OAuth 2.0 Attestation-Based Client Authentication + */ + public BackchannelAuthenticationResponse setAttestationChallenge(String challenge) + { + this.attestationChallenge = challenge; + + return this; + } } diff --git a/src/main/java/com/authlete/common/dto/BackchannelLogoutTokenRequest.java b/src/main/java/com/authlete/common/dto/BackchannelLogoutTokenRequest.java new file mode 100644 index 0000000000..13f9caa876 --- /dev/null +++ b/src/main/java/com/authlete/common/dto/BackchannelLogoutTokenRequest.java @@ -0,0 +1,193 @@ +/* + * Copyright (C) 2026 Authlete, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.authlete.common.dto; + + +import java.io.Serializable; + + +/** + * Request to Authlete's + * /api/{serivce-id}/backchannel/logout/token + * API. + * + *

+ * The Authlete API generates a Logout Token that complies with the OpenID Connect + * Back-Channel Logout 1.0 specification. + *

+ * + * @see + * OpenID Connect Back-Channel Logout 1.0 + * + * @since 4.43 + * @since Authlete 3.0.32 + */ +public class BackchannelLogoutTokenRequest implements Serializable +{ + private static final long serialVersionUID = 1L; + + + /** + * The client identifier. + */ + private String clientIdentifier; + + + /** + * The subject of the end-user. + */ + private String subject; + + + /** + * The session ID. + */ + private String sessionId; + + + /** + * Get the client identifier. + * This request parameter is mandatory. + * + * @return + * The client identifier. + */ + public String getClientIdentifier() + { + return clientIdentifier; + } + + + /** + * Set the client identifier. + * This request parameter is mandatory. + * + * @param identifier + * The client identifier. + * + * @return + * {@code this} object. + */ + public BackchannelLogoutTokenRequest setClientIdentifier(String identifier) + { + this.clientIdentifier = identifier; + + return this; + } + + + /** + * Get the subject of the end-user. At least one of this {@code subject} + * request parameter or the {@code sessionId} request parameter is + * required. + * + *

+ * This value is used as the value of the {@code sub} claim in the + * Logout Token. + *

+ * + * @return + * The subject of the end-user. + */ + public String getSubject() + { + return subject; + } + + + /** + * Set the subject of the end-user. At least one of this {@code subject} + * request parameter or the {@code sessionId} request parameter is + * required. + * + *

+ * This value is used as the value of the {@code sub} claim in the + * Logout Token. + *

+ * + * @param subject + * The subject of the end-user. + * + * @return + * {@code this} object. + */ + public BackchannelLogoutTokenRequest setSubject(String subject) + { + this.subject = subject; + + return this; + } + + + /** + * Get the session ID. At least one of this {@code sessionId} request + * parameter or the {@code subject} request parameter is required. + * + *

+ * This value is used as the value of the {@code sid} claim in the + * Logout Token. + *

+ * + *

+ * If the backchannel_logout_session_supported + * server metadata parameter of your server is {@code true} (= if the + * backchannelLogoutSessionSupported property + * of your {@link Service} is {@code true}), this request parameter should + * be included. + *

+ * + * @return + * The session ID. + */ + public String getSessionId() + { + return sessionId; + } + + + /** + * Set the session ID. At least one of this {@code sessionId} request + * parameter or the {@code subject} request parameter is required. + * + *

+ * This value is used as the value of the {@code sid} claim in the + * Logout Token. + *

+ * + *

+ * If the backchannel_logout_session_supported + * server metadata parameter of your server is {@code true} (= if the + * backchannelLogoutSessionSupported property + * of your {@link Service} is {@code true}), this request parameter should + * be included. + *

+ * + * @param sessionId + * The session ID. + * + * @return + * {@code this} object. + */ + public BackchannelLogoutTokenRequest setSessionId(String sessionId) + { + this.sessionId = sessionId; + + return this; + } +} diff --git a/src/main/java/com/authlete/common/dto/BackchannelLogoutTokenResponse.java b/src/main/java/com/authlete/common/dto/BackchannelLogoutTokenResponse.java new file mode 100644 index 0000000000..ae95324920 --- /dev/null +++ b/src/main/java/com/authlete/common/dto/BackchannelLogoutTokenResponse.java @@ -0,0 +1,230 @@ +/* + * Copyright (C) 2026 Authlete, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.authlete.common.dto; + + +import java.net.URI; + + +/** + * Response from Authlete's + * /api/{serivce-id}/backchannel/logout/token + * API. + * + *

+ * The Authlete API generates a Logout Token that complies with the OpenID Connect + * Back-Channel Logout 1.0 specification. + *

+ * + *

+ * If the value of the {@code action} parameter in the response from the + * Authlete API is {@link Action#OK OK}, it indicates that a Logout Token has + * been generated successfully. In this case, your server is expected to send + * a Back-Channel Logout Request containing the generated Logout Token to + * the client's Backchannel Logout URI. + *

+ * + *

+ * The {@code logoutToken} parameter in the API response contains the generated + * Logout Token, and the backchannelLogoutUri parameter + * contains the client's Back-Channel Logout URI, which corresponds to the + * backchannel_logout_uri client metadata parameter. + * Using these values, the Back-Channel Logout Request can be constructed as + * follows. + *

+ * + *
+ * POST {@link #getBackchannelLogoutUri()} HTTP/1.1
+ * Content-Type: application/x-www-form-urlencoded
+ * Content-Length: ...
+ *
+ * logout_token={@link #getLogoutToken()}
+ *
+ * + *

+ * Note that the Authlete API does not verify whether the client's + * backchannel_logout_uri metadata parameter is + * configured properly. Therefore, the value of the + * backchannelLogoutUri response parameter may not be + * a valid HTTP-accessible URL. It may even be {@code null}. + *

+ * + *

+ * Also note that the API call does not revoke any refresh tokens. If necessary, + * the client itself should trigger refresh token revocation (e.g., by calling + * the revocation endpoint). + *

+ * + * @see + * OpenID Connect Back-Channel Logout 1.0 + * + * @since 4.43 + * @since Authlete 3.0.32 + */ +public class BackchannelLogoutTokenResponse extends ApiResponse +{ + private static final long serialVersionUID = 1L; + + + /** + * The next action that the API caller should take. + */ + public enum Action + { + /** + * A Logout Token has been generated successfully. The API caller is + * expected to send a Back-Channel Logout Request to the client. + */ + OK, + + + /** + * There is an error on Authlete's side, such as a database access error. + */ + SERVER_ERROR, + + + /** + * The API call is invalid. For example, the mandatory request parameter + * {@code clientIdentifier} is missing. + */ + CALLER_ERROR, + } + + + /** + * The next action that the API caller should take. + */ + private Action action; + + + /** + * The generated Logout Token. + */ + private String logoutToken; + + + /** + * The client's Back-Channel Logout URI. + */ + private URI backchannelLogoutUri; + + + /** + * Get the next action that the API caller should take. In practice, this + * value represents the result of the API call. + * + * @return + * The next action that the API caller should take. + */ + public Action getAction() + { + return action; + } + + + /** + * Set the next action that API caller should take. In practice, this + * value represents the result of the API call. + * + * @param action + * The next action that the API caller should take. + * + * @return + * {@code this} object. + */ + public BackchannelLogoutTokenResponse setAction(Action action) + { + this.action = action; + + return this; + } + + + /** + * Get the issued Logout Token. + * + * @return + * The issued Logout Token. + */ + public String getLogoutToken() + { + return logoutToken; + } + + + /** + * Set the issued Logout Token. + * + * @param logoutToken + * The issued Logout Token. + * + * @return + * {@code this} object. + */ + public BackchannelLogoutTokenResponse setLogoutToken(String logoutToken) + { + this.logoutToken = logoutToken; + + return this; + } + + + /** + * Get the client's Back-Channel Logout URI, which corresponds to the + * backchannel_logout_uri client metadata + * parameter defined in the OpenID + * Connect Back-Channel Logout 1.0 specification. + * + * @return + * The client's Back-Channel Logout URI. + */ + public URI getBackchannelLogoutUri() + { + return backchannelLogoutUri; + } + + + /** + * Set the client's Back-Channel Logout URI, which corresponds to the + * backchannel_logout_uri client metadata + * parameter defined in the OpenID + * Connect Back-Channel Logout 1.0 specification. + * + * @param uri + * The client's Back-Channel Logout URI. + * + * @return + * {@code this} object. + */ + public BackchannelLogoutTokenResponse setBackchannelLogoutUri(URI uri) + { + this.backchannelLogoutUri = uri; + + return this; + } +} diff --git a/src/main/java/com/authlete/common/dto/CimdOptions.java b/src/main/java/com/authlete/common/dto/CimdOptions.java new file mode 100644 index 0000000000..a02389ed3e --- /dev/null +++ b/src/main/java/com/authlete/common/dto/CimdOptions.java @@ -0,0 +1,300 @@ +/* + * Copyright (C) 2025 Authlete, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.authlete.common.dto; + + +import java.io.Serializable; + + +/** + * Options for OAuth Client ID Metadata Document (CIMD). + * + * @since 4.30 + * @since Authlete 3.0.22 + * + * @see + * OAuth Client ID Metadata Document + */ +public class CimdOptions implements Serializable +{ + private static final long serialVersionUID = 1L; + + + /** + * Whether to always retrieve client metadata in the CIMD context + * regardless of the cache's validity. + */ + private boolean alwaysRetrieved; + + + /** + * Whether to allow the {@code http} scheme in client IDs in the CIMD + * context. + */ + private boolean httpPermitted; + + + /** + * Whether to allow a query component in client IDs in the CIMD context. + */ + private boolean queryPermitted; + + + /** + * Get the flag that indicates whether Authlete should always fetch the + * client metadata from the location specified by the client ID, regardless + * of whether a cached copy already exists and has not yet expired. + * + *

+ * Under normal circumstances, client metadata retrieved from the location + * referenced by the client ID is stored in the database with an expiration + * time calculated using HTTP caching mechanisms (see RFC 9111 HTTP Caching). + * Until that expiration time is reached, Authlete does not attempt to + * retrieve the client metadata again. + *

+ * + *

+ * When this flag is set to {@code true}, Authlete retrieves the client + * metadata regardless of the cache's validity. + *

+ * + *

+ * If this flag is included in an Authlete API call and its value is + * {@code true}, it takes precedence over the corresponding service + * configuration (see {@link Service#isCimdAlwaysRetrieved()}). + *

+ * + *

+ * This flag is effective only when the service supports CIMD (see {@link + * Service#isClientIdMetadataDocumentSupported()}) and CIMD is actually + * used to resolve client metadata. For example, if the client ID in a + * request does not appear to be a valid URI, CIMD will not be used even + * if the service is configured to support it. In such cases, this flag + * has no effect. + *

+ * + *

+ * Client metadata retrieval is performed only in the initiating request + * of an authorization flow, and not in any subsequent requests. For + * example, in the authorization code flow, metadata may be retrieved + * during the authorization request, but not during the subsequent token + * request. In contrast, in the client credentials flow, metadata retrieval + * may occur because the token request itself is the initiating request + * in the flow. + *

+ * + * @return + * {@code true} if Authlete attempts to retrieve client metadata + * regardless of the cache's validity. + */ + public boolean isAlwaysRetrieved() + { + return alwaysRetrieved; + } + + + /** + * Set the flag that indicates whether Authlete should always fetch the + * client metadata from the location specified by the client ID, regardless + * of whether a cached copy already exists and has not yet expired. + * + *

+ * Under normal circumstances, client metadata retrieved from the location + * referenced by the client ID is stored in the database with an expiration + * time calculated using HTTP caching mechanisms (see RFC 9111 HTTP Caching). + * Until that expiration time is reached, Authlete does not attempt to + * retrieve the client metadata again. + *

+ * + *

+ * When this flag is set to {@code true}, Authlete retrieves the client + * metadata regardless of the cache's validity. + *

+ * + *

+ * If this flag is included in an Authlete API call and its value is + * {@code true}, it takes precedence over the corresponding service + * configuration (see {@link Service#isCimdAlwaysRetrieved()}). + *

+ * + *

+ * This flag is effective only when the service supports CIMD (see {@link + * Service#isClientIdMetadataDocumentSupported()}) and CIMD is actually + * used to resolve client metadata. For example, if the client ID in a + * request does not appear to be a valid URI, CIMD will not be used even + * if the service is configured to support it. In such cases, this flag + * has no effect. + *

+ * + *

+ * Client metadata retrieval is performed only in the initiating request + * of an authorization flow, and not in any subsequent requests. For + * example, in the authorization code flow, metadata may be retrieved + * during the authorization request, but not during the subsequent token + * request. In contrast, in the client credentials flow, metadata retrieval + * may occur because the token request itself is the initiating request + * in the flow. + *

+ * + * @param always + * {@code true} to instruct Authlete to retrieve client metadata + * regardless of the cache's validity. + * + * @return + * {@code this} object. + */ + public CimdOptions setAlwaysRetrieved(boolean always) + { + this.alwaysRetrieved = always; + + return this; + } + + + /** + * Get the flag that indicates whether the {@code http} scheme in the client + * ID is permitted. + * + *

+ * The specification requires the {@code https} scheme, but if this flag is + * set to {@code true}, Authlete also allows the {@code http} scheme. The + * main purpose of this option is to make development easier for developers + * who run CIMD-enabled servers and a web server publishing client metadata + * on their local machines without TLS. + *

+ * + *

+ * Given this purpose, it is not recommended to enable this option in + * production environments unless an allowlist is used (see {@link + * Service#isCimdAllowlistEnabled()}). + *

+ * + *

+ * If this flag is included in an Authlete API call and its value is + * {@code true}, it takes precedence over the corresponding service + * configuration (see {@link Service#isCimdHttpPermitted()}). + *

+ * + * @return + * {@code true} if the {@code http} scheme in the client ID is + * permitted. + */ + public boolean isHttpPermitted() + { + return httpPermitted; + } + + + /** + * Set the flag that indicates whether the {@code http} scheme in the client + * ID is permitted. + * + *

+ * The specification requires the {@code https} scheme, but if this flag is + * set to {@code true}, Authlete also allows the {@code http} scheme. The + * main purpose of this option is to make development easier for developers + * who run CIMD-enabled servers and a web server publishing client metadata + * on their local machines without TLS. + *

+ * + *

+ * Given this purpose, it is not recommended to enable this option in + * production environments unless an allowlist is used (see {@link + * Service#isCimdAllowlistEnabled()}). + *

+ * + *

+ * If this flag is included in an Authlete API call and its value is + * {@code true}, it takes precedence over the corresponding service + * configuration (see {@link Service#isCimdHttpPermitted()}). + *

+ * + * @param permitted + * {@code true} to permit the {@code http} scheme in the client ID. + * + * @return + * {@code this} object. + */ + public CimdOptions setHttpPermitted(boolean permitted) + { + this.httpPermitted = permitted; + + return this; + } + + + /** + * Get the flag that indicates whether a query component in the client ID + * is permitted. + * + *

+ * Although the specification states that a client ID "SHOULD NOT + * include a query string component," it does technically allow it. + * However, query components are prone to misuse. Therefore, Authlete does + * not allow them by default. Setting this flag to {@code true} relaxes + * that restriction. + *

+ * + *

+ * If this flag is included in an Authlete API call and its value is + * {@code true}, it takes precedence over the corresponding service + * configuration (see {@link Service#isCimdQueryPermitted()}). + *

+ * + * @return + * {@code true} if a query component in the client ID is permitted. + */ + public boolean isQueryPermitted() + { + return queryPermitted; + } + + + /** + * Set the flag that indicates whether a query component in the client ID + * is permitted. + * + *

+ * Although the specification states that a client ID "SHOULD NOT + * include a query string component," it does technically allow it. + * However, query components are prone to misuse. Therefore, Authlete does + * not allow them by default. Setting this flag to {@code true} relaxes + * that restriction. + *

+ * + *

+ * If this flag is included in an Authlete API call and its value is + * {@code true}, it takes precedence over the corresponding service + * configuration (see {@link Service#isCimdQueryPermitted()}). + *

+ * + * @param permitted + * {@code true} to permit a query component in the client ID. + * + * @return + * {@code this} object. + */ + public CimdOptions setQueryPermitted(boolean permitted) + { + this.queryPermitted = permitted; + + return this; + } +} diff --git a/src/main/java/com/authlete/common/dto/Client.java b/src/main/java/com/authlete/common/dto/Client.java index 63ca8992f7..7209308ea8 100644 --- a/src/main/java/com/authlete/common/dto/Client.java +++ b/src/main/java/com/authlete/common/dto/Client.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014-2024 Authlete, Inc. + * Copyright (C) 2014-2026 Authlete, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,6 +27,7 @@ import com.authlete.common.types.ApplicationType; import com.authlete.common.types.ClientAuthMethod; import com.authlete.common.types.ClientRegistrationType; +import com.authlete.common.types.ClientSource; import com.authlete.common.types.ClientType; import com.authlete.common.types.DeliveryMode; import com.authlete.common.types.FapiMode; @@ -80,10 +81,16 @@ * * @see OpenID for Verifiable Credential Issuance + * + * @see OAuth Client ID Metadata Document + * + * @see OpenID Connect Back-Channel Logout 1.0 */ public class Client implements Serializable { - private static final long serialVersionUID = 38L; + private static final long serialVersionUID = 42L; /* @@ -583,6 +590,13 @@ public class Client implements Serializable */ private boolean mtlsEndpointAliasesUsed; + /** + * Indicates if the client is in scope for token migration. + * + * @since Authlete 4.23 + */ + private boolean inScopeForTokenMigration; + /* * For OpenID Federation 1.0. @@ -638,6 +652,7 @@ public class Client implements Serializable */ private boolean explicitlyRegistered; + /* * For Verifiable Credentials */ @@ -653,6 +668,98 @@ public class Client implements Serializable private boolean credentialResponseEncryptionRequired; + /* + * For OAuth Client ID Metadata Document + */ + + /** + * The location of the client's metadata document. + * + * @since 4.29 + * @since Authlete 3.0.22 + */ + private URI metadataDocumentLocation; + + /** + * The time at which the metadata document will expire. + * + * @since 4.29 + * @since Authlete 3.0.22 + */ + private long metadataDocumentExpiresAt; + + /** + * The time at which the metadata document was updated. + * + * @since 4.29 + * @since Authlete 3.0.22 + */ + private long metadataDocumentUpdatedAt; + + /** + * Whether the client was discovered by Client ID Metadata Document. + * + * @since 4.29 + * @since Authlete 3.0.22 + */ + private boolean discoveredByMetadataDocument; + + + /** + * The source of this client. + * + * @since 4.29 + * @since Authlete 3.0.22 + */ + private ClientSource clientSource; + + + /** + * The SPIFFE ID. + * + * @since 4.36 + * @since Authlete 3.0.28 + */ + private URI spiffeId; + + + /** + * The SPIFFE Bundle Endpoint. + * + * @since 4.36 + * @since Authlete 3.0.28 + */ + private URI spiffeBundleEndpoint; + + + /** + * The HAIP version to comply with. + * + * @since 4.41 + * @since Authlete 3.0.29 + */ + private String haipVersion; + + + /** + * The Back-Channel Logout URI. + * + * @since 4.44 + * @since Authlete 3.0.32 + */ + private URI backchannelLogoutUri; + + + /** + * The flag indicating whether this client requires Logout Tokens to + * include the {@code sid} claim. + * + * @since 4.44 + * @since Authlete 3.0.32 + */ + private boolean backchannelLogoutSessionRequired; + + /** * Get the client number. * @@ -5209,6 +5316,710 @@ public Client setMtlsEndpointAliasesUsed(boolean mtlsEndpointAliasesUsed) } + /** + * Get the value that indicates whether this Client is inscope for token migration. + * + * @return + * The value that indicates whether this Client is in scope for token migration. + * + * @since Authlete 4.23 + */ + public boolean isInScopeForTokenMigration() + { + return inScopeForTokenMigration; + } + + + /** + * Sets the flag that indicates that this Client is in scope for token migration. + * + * @param inScopeForTokenMigration + * The new value for the flag to indicates whether this Client is in scope for token migration. + * + * @return + * The {@link Client} after setting the provided property. + * + * @since Authlete 4.23 + */ + public Client setInScopeForTokenMigration(boolean inScopeForTokenMigration) + { + this.inScopeForTokenMigration = inScopeForTokenMigration; + + return this; + } + + + /** + * Get the location of the client's metadata document. + * + *

+ * This property is available only when the client was discovered using + * the mechanism defined in OAuth Client ID Metadata Document. The mechanism is used only + * when the service supports it. + * (cf. {@link Service#isClientIdMetadataDocumentSupported()}) + *

+ * + * @return + * The location of the client's metadata document. + * + * @since 4.29 + * @since Authlete 3.0.22 + * + * @see + * OAuth Client ID Metadata Document + */ + public URI getMetadataDocumentLocation() + { + return metadataDocumentLocation; + } + + + /** + * Set the location of the client's metadata document. + * + *

+ * This property is available only when the client was discovered using + * the mechanism defined in OAuth Client ID Metadata Document. The mechanism is used only + * when the service supports it. + * (cf. {@link Service#isClientIdMetadataDocumentSupported()}) + *

+ * + * @param location + * The location of the client's metadata document. + * + * @return + * {@code this} object. + * + * @since 4.29 + * @since Authlete 3.0.22 + * + * @see + * OAuth Client ID Metadata Document + */ + public Client setMetadataDocumentLocation(URI location) + { + this.metadataDocumentLocation = location; + + return this; + } + + + /** + * Get the expiration time of the client's metadata document. The value is + * represented as milliseconds elapsed since the Unix epoch (1970-01-01). + * + *

+ * This property is meaningful only when the client was discovered using + * the mechanism defined in OAuth Client ID Metadata Document. The mechanism is used only + * when the service supports it. + * (cf. {@link Service#isClientIdMetadataDocumentSupported()}) + *

+ * + * @return + * The expiration time of the client's metadata document. + * + * @since 4.29 + * @since Authlete 3.0.22 + * + * @see + * OAuth Client ID Metadata Document + */ + public long getMetadataDocumentExpiresAt() + { + return metadataDocumentExpiresAt; + } + + + /** + * Set the expiration time of the client's metadata document. The value is + * represented as milliseconds elapsed since the Unix epoch (1970-01-01). + * + *

+ * This property is meaningful only when the client was discovered using + * the mechanism defined in OAuth Client ID Metadata Document. The mechanism is used only + * when the service supports it. + * (cf. {@link Service#isClientIdMetadataDocumentSupported()}) + *

+ * + * @param expiresAt + * The expiration time of the client's metadata document. + * + * @return + * {@code this} object. + * + * @since 4.29 + * @since Authlete 3.0.22 + * + * @see + * OAuth Client ID Metadata Document + */ + public Client setMetadataDocumentExpiresAt(long expiresAt) + { + this.metadataDocumentExpiresAt = expiresAt; + + return this; + } + + + /** + * Get the time at which the client was last updated with its metadata + * document. + * + *

+ * This property is meaningful only when the client was discovered using + * the mechanism defined in OAuth Client ID Metadata Document. The mechanism is used only + * when the service supports it. + * (cf. {@link Service#isClientIdMetadataDocumentSupported()}) + *

+ * + * @return + * The time at which the client was last updated with its metadata + * document. + * + * @since 4.29 + * @since Authlete 3.0.22 + * + * @see + * OAuth Client ID Metadata Document + */ + public long getMetadataDocumentUpdatedAt() + { + return metadataDocumentUpdatedAt; + } + + + /** + * Set the time at which the client was last updated with its metadata + * document. + * + *

+ * This property is meaningful only when the client was discovered using + * the mechanism defined in OAuth Client ID Metadata Document. The mechanism is used only + * when the service supports it. + * (cf. {@link Service#isClientIdMetadataDocumentSupported()}) + *

+ * + * @param updatedAt + * The time at which the client was last updated with its metadata + * document. + * + * @return + * {@code this} object. + * + * @since 4.29 + * @since Authlete 3.0.22 + * + * @see + * OAuth Client ID Metadata Document + */ + public Client setMetadataDocumentUpdatedAt(long updatedAt) + { + this.metadataDocumentUpdatedAt = updatedAt; + + return this; + } + + + /** + * Get the flag indicating whether this client was discovered and + * registered by the mechanism defined in OAuth Client ID Metadata Document. + * + * @return + * {@code true} if this client was discovered and registered by + * the mechanism defined in OAuth Client ID Metadata Document. + * + * @since 4.29 + * @since Authlete 3.0.22 + * + * @see + * OAuth Client ID Metadata Document + */ + public boolean isDiscoveredByMetadataDocument() + { + return discoveredByMetadataDocument; + } + + + /** + * Set the flag indicating whether this client was discovered and + * registered by the mechanism defined in OAuth Client ID Metadata Document. + * + * @param discovered + * {@code true} to indicate that this client was discovered and + * registered by the mechanism defined in OAuth Client ID Metadata + * Document. + * + * @return + * {@code this} object. + * + * @since 4.29 + * @since Authlete 3.0.22 + * + * @see + * OAuth Client ID Metadata Document + */ + public Client setDiscoveredByMetadataDocument(boolean discovered) + { + this.discoveredByMetadataDocument = discovered; + + return this; + } + + + /** + * Get the source of this client. + * + * @return + * The source of this client. + * + * @since 4.29 + * @since Authlete 3.0.22 + * + * @see #updateClientSource() + */ + public ClientSource getClientSource() + { + return clientSource; + } + + + /** + * Set the source of this client. + * + * @param source + * The source of this client. + * + * @return + * {@code this} object. + * + * @since 4.29 + * @since Authlete 3.0.22 + * + * @see #updateClientSource() + */ + public Client setClientSource(ClientSource source) + { + this.clientSource = source; + + return this; + } + + + /** + * Update the {@code clientSource} property according to the current + * values of registration-related fields. + * + *
    + *
  1. {@link ClientSource#DYNAMIC_REGISTRATION DYNAMIC_REGISTRATION} + * if {@link #isDynamicallyRegistered()} returns {@code true}. + * + *
  2. {@link ClientSource#AUTOMATIC_REGISTRATION AUTOMATIC_REGISTRATION} + * if {@link #isAutomaticallyRegistered()} returns {@code true}. + * + *
  3. {@link ClientSource#EXPLICIT_REGISTRATION EXPLICIT_REGISTRATION} + * if {@link #isExplicitlyRegistered()} returns {@code true}. + * + *
  4. {@link ClientSource#METADATA_DOCUMENT METADATA_DOCUMENT} + * if {@link #isDiscoveredByMetadataDocument()} returns {@code true}. + * + *
  5. {@link ClientSource#STATIC_REGISTRATION STATIC_REGISTRATION} + * in other cases. + *
+ * + * @return + * {@code this} object. + * + * @since 4.29 + */ + public Client updateClientSource() + { + return updateClientSource(this); + } + + + private static Client updateClientSource(Client client) + { + if (client == null) + { + return null; + } + + // Derive the client source from fields. + ClientSource cs = deriveClientSource(client); + + return client.setClientSource(cs); + } + + + private static ClientSource deriveClientSource(Client client) + { + if (client == null) + { + return null; + } + + if (client.isDynamicallyRegistered()) + { + // RFC 7591: OAuth 2.0 Dynamic Client Registration Protocol, or + // OpenID Connect Dynamic Client Registration 1.0. + return ClientSource.DYNAMIC_REGISTRATION; + } + else if (client.isAutomaticallyRegistered()) + { + // Automatic registration defined in OpenID Federation 1.0. + return ClientSource.AUTOMATIC_REGISTRATION; + } + else if (client.isExplicitlyRegistered()) + { + // Explicit registration defined in OpenID Federation 1.0. + return ClientSource.EXPLICIT_REGISTRATION; + } + else if (client.isDiscoveredByMetadataDocument()) + { + // Discovery using OAuth Client ID Metadata Document. + return ClientSource.METADATA_DOCUMENT; + } + else + { + // Static registration. + return ClientSource.STATIC_REGISTRATION; + } + } + + + /** + * Get the SPIFFE ID. This property corresponds to the {@code spiffe_id} + * client metadata. + * + *

+ * The value of this property is compared against the SPIFFE ID contained + * in the SVID presented by the client during SPIFFE Client Authentication. + *

+ * + *

+ * According to the SPIFFE + * ID specification, the characters allowed in the path component of a + * SPIFFE ID are limited to {@code [a-zA-Z0-9.-_]}. However, in the value + * of this property (i.e., in the value of the {@code spiffe_id} client + * metadata), the final path segment may be specified using a wildcard + * {@code *} (e.g., spiffe://example.com/workload/*). + * When comparing it with the SPIFFE ID in the SVID, the wildcard is taken + * into account. + *

+ * + * @return + * The SPIFFE ID. + * + * @since 4.36 + * @since Authlete 3.0.28 + * + * @see SPIFFE + * @see + * OAuth SPIFFE Client Authentication + */ + public URI getSpiffeId() + { + return spiffeId; + } + + + /** + * Set the SPIFFE ID. This property corresponds to the {@code spiffe_id} + * client metadata. + * + *

+ * The value of this property is compared against the SPIFFE ID contained + * in the SVID presented by the client during SPIFFE Client Authentication. + *

+ * + *

+ * According to the SPIFFE + * ID specification, the characters allowed in the path component of a + * SPIFFE ID are limited to {@code [a-zA-Z0-9.-_]}. However, in the value + * of this property (i.e., in the value of the {@code spiffe_id} client + * metadata), the final path segment may be specified using a wildcard + * {@code *} (e.g., spiffe://example.com/workload/*). + * When comparing it with the SPIFFE ID in the SVID, the wildcard is taken + * into account. + *

+ * + * @param spiffeId + * The SPIFFE ID. + * + * @since 4.36 + * @since Authlete 3.0.28 + * + * @see SPIFFE + * @see + * OAuth SPIFFE Client Authentication + */ + public Client setSpiffeId(URI spiffeId) + { + this.spiffeId = spiffeId; + + return this; + } + + + /** + * Get the SPIFFE Bundle Endpoint. This property corresponds to the + * {@code spiffe_bundle_endpoint} client metadata. + * + *

+ * The authorization server retrieves the SPIFFE Bundle from the location + * indicated by this property (i.e., by the {@code spiffe_bundle_endpoint} + * client metadata) in order to verify the SVID presented by the client + * during SPIFFE Client Authentication. The SPIFFE Bundle is, in essence, + * a JWK Set. + *

+ * + * @return + * The SPIFFE Bundle Endpoint. + * + * @since 4.36 + * @since Authlete 3.0.28 + * + * @see SPIFFE + * @see + * OAuth SPIFFE Client Authentication + */ + public URI getSpiffeBundleEndpoint() + { + return spiffeBundleEndpoint; + } + + + /** + * Set the SPIFFE Bundle Endpoint. This property corresponds to the + * {@code spiffe_bundle_endpoint} client metadata. + * + *

+ * The authorization server retrieves the SPIFFE Bundle from the location + * indicated by this property (i.e., by the {@code spiffe_bundle_endpoint} + * client metadata) in order to verify the SVID presented by the client + * during SPIFFE Client Authentication. The SPIFFE Bundle is, in essence, + * a JWK Set. + *

+ * + * @param endpoint + * The SPIFFE Bundle Endpoint. + * + * @since 4.36 + * @since Authlete 3.0.28 + * + * @see SPIFFE + * @see + * OAuth SPIFFE Client Authentication + */ + public Client setSpiffeBundleEndpoint(URI endpoint) + { + this.spiffeBundleEndpoint = endpoint; + + return this; + } + + + /** + * Get the HAIP version to comply with. Valid values for this property are + * as follows: + * + *
    + *
  • {@code "1.0"} + *
+ * + *

+ * If this property is set, requests from this client undergo additional + * validation based on the specified version of the HAIP specification. + *

+ * + *

+ * Note that even if this property is not set, additional validation based + * on the HAIP specification is performed if the {@link Service}'s + * {@code haipVersion} is set or if a scope with the {@code haip} attribute + * is included in the request. + *

+ * + * @return + * The HAIP version to comply with. + * + * @since 4.41 + * @since Authlete 3.0.29 + * + * @see + * OpenID4VC High Assurance Interoperability Profile 1.0 + */ + public String getHaipVersion() + { + return haipVersion; + } + + + /** + * Set the HAIP version to comply with. Valid values for this property are + * as follows: + * + *
    + *
  • {@code "1.0"} + *
+ * + *

+ * If this property is set, requests from this client undergo additional + * validation based on the specified version of the HAIP specification. + *

+ * + *

+ * Note that even if this property is not set, additional validation based + * on the HAIP specification is performed if the {@link Service}'s + * {@code haipVersion} is set or if a scope with the {@code haip} attribute + * is included in the request. + *

+ * + * @param version + * The HAIP version to comply with. + * + * @return + * {@code this} object. + * + * @since 4.41 + * @since Authlete 3.0.29 + * + * @see + * OpenID4VC High Assurance Interoperability Profile 1.0 + */ + public Client setHaipVersion(String version) + { + this.haipVersion = version; + + return this; + } + + + /** + * Get the Back-Channel Logout URI. This property corresponds to the + * backchannel_logout_uri client metadata parameter + * defined in the OpenID + * Connect Back-Channel Logout 1.0 specification. + * + * @return + * The Back-Channel Logout URI. + * + * @since 4.44 + * @since Authlete 3.0.32 + * + * @see + * OpenID Connect Back-Channel Logout 1.0 + */ + public URI getBackchannelLogoutUri() + { + return backchannelLogoutUri; + } + + + /** + * Set the Back-Channel Logout URI. This property corresponds to the + * backchannel_logout_uri client metadata parameter + * defined in the OpenID + * Connect Back-Channel Logout 1.0 specification. + * + * @param uri + * The Back-Channel Logout URI. + * + * @return + * {@code this} object. + * + * @since 4.44 + * @since Authlete 3.0.32 + * + * @see + * OpenID Connect Back-Channel Logout 1.0 + */ + public Client setBackchannelLogoutUri(URI uri) + { + this.backchannelLogoutUri = uri; + + return this; + } + + + /** + * Get the flag indicating whether this client requires Logout Tokens to include the {@code sid} claim. This property + * corresponds to the backchannel_logout_sesssion_required + * client metadata parameter defined in the OpenID + * Connect Back-Channel Logout 1.0 specification. + * + * @return + * The flag indicating whether this client requires Logout Tokens + * to include the {@code sid} claim. + * + * @since 4.44 + * @since Authlete 3.0.32 + * + * @see + * OpenID Connect Back-Channel Logout 1.0 + */ + public boolean isBackchannelLogoutSessionRequired() + { + return backchannelLogoutSessionRequired; + } + + + /** + * Set the flag indicating whether this client requires Logout Tokens to include the {@code sid} claim. This property + * corresponds to the backchannel_logout_sesssion_required + * client metadata parameter defined in the OpenID + * Connect Back-Channel Logout 1.0 specification. + * + * @param required + * {@code true} to require Logout Tokens to include the {@code sid} + * claim. + * + * @return + * {@code this} object. + * + * @since 4.44 + * @since Authlete 3.0.32 + * + * @see + * OpenID Connect Back-Channel Logout 1.0 + */ + public Client setBackchannelLogoutSessionRequired(boolean required) + { + this.backchannelLogoutSessionRequired = required; + + return this; + } + + /** * Get a {@code Map} instance that represents a set of standard client * metadata. @@ -5502,6 +6313,26 @@ public Map toStandardMetadata(ClientMetadataControl control) // response_modes put(metadata, "response_modes", getResponseModes(), nullIncluded); + //---------------------------------------------------------------------- + // OAuth SPIFFE Client Authentication + //---------------------------------------------------------------------- + + // spiffe_id + put(metadata, "spiffe_id", getSpiffeId(), nullIncluded); + + // spiffe_bundle_endpoint + put(metadata, "spiffe_bundle_endpoint", getSpiffeBundleEndpoint(), nullIncluded); + + //---------------------------------------------------------------------- + // OpenID Connect Back-Channel Logout 1.0 + //---------------------------------------------------------------------- + + // backchannel_logout_uri + put(metadata, "backchannel_logout_uri", getBackchannelLogoutUri(), nullIncluded); + + // backchannel_logout_session_required + put(metadata, "backchannel_logout_session_required", isBackchannelLogoutSessionRequired(), falseIncluded); + //---------------------------------------------------------------------- // Custom Metadata //---------------------------------------------------------------------- @@ -5528,6 +6359,12 @@ else if (control.isEntityIdPreferred() && getEntityId() != null) // Use the entity ID as the value of "client_id". value = getEntityId().toString(); } + // If "metadata document location" is preferred and available. + else if (control.isMetadataDocumentLocationPreferred() && getMetadataDocumentLocation() != null) + { + // Use the metadata document location as the value of "client_id". + value = getMetadataDocumentLocation().toString(); + } else { // Use the original numeric client ID as the value of "client_id". diff --git a/src/main/java/com/authlete/common/dto/CredentialDeferredIssueRequest.java b/src/main/java/com/authlete/common/dto/CredentialDeferredIssueRequest.java index 586316c0d6..a8f419fa39 100644 --- a/src/main/java/com/authlete/common/dto/CredentialDeferredIssueRequest.java +++ b/src/main/java/com/authlete/common/dto/CredentialDeferredIssueRequest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Authlete, Inc. + * Copyright (C) 2023-2026 Authlete, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of @@ -26,7 +26,7 @@ * The Authlete API is supposed to be called by the implementation of the * deferred credential endpoint. The endpoint is defined in the "OpenID for Verifiable Credential Issuance" (OID4VCI) specification. + * >OpenID for Verifiable Credential Issuance 1.0" (OID4VCI) specification. *

* *

@@ -41,14 +41,59 @@ * * *

- * The role of the {@code /vci/deferred/issue} API is to issue a credential. + * The {@code /vci/deferred/issue} API is used for one of the following purposes: *

* + *
    + *
  1. To issue a credential + *
  2. To deny the deferred credential request + *
  3. To notify that the requested credential is not ready yet + *
+ * + *

Issuing a credential

+ * *

- * If the credential for the transaction ID is not ready, the implementation - * of the deferred credential endpoint should prepare an error response with - * {@code "error":"issuance_pending"} manually and return it to the request - * sender, without calling the {@code /vci/deferred/issue} API. + * To issue a credential, the {@code order} request parameter must be set up + * properly, and both the {@code denied} and {@code order.issuanceDeferred} + * request parameters must be set to {@code false}. + *

+ * + *

Denying a deferred credential request

+ * + *

+ * To deny the deferred credential request, the {@code denied} request parameter + * in the API call must be set to {@code true}. In this case, Authlete prepares + * a response containing {@code "error":"credential_request_denied"} and returns + * "action":"{@link CredentialDeferredIssueResponse.Action#BAD_REQUEST BAD_REQUEST}". + *

+ * + *

+ * Note that the {@code credential_request_denied} error code does not exist in + * OID4VCI 1.0 ID1. Therefore, you should not call the API with {@code denied} + * set to {@code true} when the target specification version is "1.0-ID1". + *

+ * + *

Notifying that the credential is not ready yet

+ * + *

+ * To notify that the requested credential is not ready yet, the + * {@code order.issuanceDeferred} request parameter in the API call must be + * set to {@code true}. In this case, Authlete will do one of the following: + *

+ * + *
    + *
  1. + * Prepare an error response with the {@code issuance_pending} error code for + * OID4VCI 1.0 ID1. + *
  2. + * Prepare a successful response containing the {@code transaction_id} and + * {@code interval} parameters for OID4VCI 1.0 Final or later. + *
+ * + *

+ * Note that if the Authlete Server version is older than 3.0.25 and the target + * specification version is "1.0-ID1", you need to manually construct an error + * response as shown below, without using the {@code /vci/deferred/issue} API. *

* *
@@ -61,15 +106,38 @@
  * }
  * 
* + *

Interval handling

+ * + *

+ * If the {@code interval} request parameter in the API call is present and its + * value is positive, it is used as the value of the {@code interval} response + * parameter in the deferred credential response. If the {@code interval} + * request parameter is missing, zero, or negative, Authlete uses the default + * value. + *

+ * + *

+ * Note that the {@code interval} response parameter does not exist in OID4VCI + * 1.0 ID1. Therefore, you don't have to care about the {@code interval} request + * parameter if the target specification version is "1.0-ID1". + *

+ * + *

Important note

+ * + *

+ * In all cases, the {@code order.requestIdentifier} request parameter must be + * set properly. + *

+ * * @since 3.70 * @since Authlete 3.0 * - * @see OpenID for Verifiable Credential Issuance + * @see + * OpenID for Verifiable Credential Issuance 1.0 */ public class CredentialDeferredIssueRequest implements Serializable { - private static final long serialVersionUID = 1L; + private static final long serialVersionUID = 2L; /** @@ -78,6 +146,26 @@ public class CredentialDeferredIssueRequest implements Serializable private CredentialIssuanceOrder order; + /** + * The flag that indicates whether to deny the deferred credential request. + * + * @since 4.35 + * @since Authlete 3.0.25 + */ + private boolean denied; + + + /** + * The minimum amount of time in seconds that the Wallet SHOULD wait after + * receiving the response before sending a new request to the Deferred + * Credential Endpoint. + * + * @since 4.35 + * @since Authlete 3.0.25 + */ + private int interval; + + /** * Get the credential order that provides an instruction for issuing a * credential. @@ -107,4 +195,160 @@ public CredentialDeferredIssueRequest setOrder(CredentialIssuanceOrder order) return this; } + + + /** + * Get the flag that indicates whether to deny the deferred credential + * request. + * + *

+ * If this parameter is set to {@code true}, the response content prepared + * by Authlete will contain {@code "error":"credential_request_denied"} + * like below. + *

+ * + *
+     * {
+     *     "error": "credential_request_denied"
+     * }
+     * 
+ * + *

+ * Note that the {@code credential_request_denied} error code does not + * exist in OID4VCI 1.0 ID1. + *

+ * + * @return + * {@code true} for generating a response content containing + * {@code "error":"credential_request_denied"}. + * + * @since 4.35 + * @since Authlete 3.0.25 + */ + public boolean isDenied() + { + return denied; + } + + + /** + * Set the flag that indicates whether to deny the deferred credential + * request. + * + *

+ * If this parameter is set to {@code true}, the response content prepared + * by Authlete will contain {@code "error":"credential_request_denied"} + * like below. + *

+ * + *
+     * {
+     *     "error": "credential_request_denied"
+     * }
+     * 
+ * + *

+ * Note that the {@code credential_request_denied} error code does not + * exist in OID4VCI 1.0 ID1. + *

+ * + * @param denied + * {@code true} for generating a response content containing + * {@code "error":"credential_request_denied"}. + * + * @return + * {@code this} object. + * + * @since 4.35 + * @since Authlete 3.0.25 + */ + public CredentialDeferredIssueRequest setDenied(boolean denied) + { + this.denied = denied; + + return this; + } + + + /** + * Get the minimum amount of time in seconds that the Wallet SHOULD wait + * after receiving the response before sending a new request to the + * Deferred Credential Endpoint. + * + *

+ * The value of this parameter is used as the value of the {@code interval} + * parameter in the deferred credential response. However, the + * {@code interval} parameter is included in the deferred credential + * response only when the following conditions are met: + *

+ * + *
    + *
  • + * The value of the {@code order.issuanceDeferred} request parameter is + * {@code true}. + *
  • + * + *
  • + * The version of the OID4VCI specification is 1.0-Final or later (that is, + * the {@code oid4vciVersion} property of {@link Service} is set and its + * value is not {@code "1.0-ID1"}). + *
  • + *
+ * + * @return + * The minimum amount of time in seconds that the Wallet SHOULD + * wait after receiving the response before sending a new request + * to the Deferred Credential Endpoint. + * + * @since 4.35 + * @since Authlete 3.0.25 + */ + public int getInterval() + { + return interval; + } + + + /** + * Set the minimum amount of time in seconds that the Wallet SHOULD wait + * after receiving the response before sending a new request to the + * Deferred Credential Endpoint. + * + *

+ * The value of this parameter is used as the value of the {@code interval} + * parameter in the deferred credential response. However, the + * {@code interval} parameter is included in the deferred credential + * response only when the following conditions are met: + *

+ * + *
    + *
  • + * The value of the {@code order.issuanceDeferred} request parameter is + * {@code true}. + *
  • + * + *
  • + * The version of the OID4VCI specification is 1.0-Final or later (that is, + * the {@code oid4vciVersion} property of {@link Service} is set and its + * value is not {@code "1.0-ID1"}). + *
  • + *
+ * + * @param interval + * The minimum amount of time in seconds that the Wallet SHOULD + * wait after receiving the response before sending a new request + * to the Deferred Credential Endpoint. + * + * @return + * {@code this} object. + * + * @since 4.35 + * @since Authlete 3.0.25 + */ + public CredentialDeferredIssueRequest setInterval(int interval) + { + this.interval = interval; + + return this; + } } diff --git a/src/main/java/com/authlete/common/dto/CredentialDeferredIssueResponse.java b/src/main/java/com/authlete/common/dto/CredentialDeferredIssueResponse.java index e22a1efb37..3121285169 100644 --- a/src/main/java/com/authlete/common/dto/CredentialDeferredIssueResponse.java +++ b/src/main/java/com/authlete/common/dto/CredentialDeferredIssueResponse.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Authlete, Inc. + * Copyright (C) 2023-2026 Authlete, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of @@ -71,6 +71,51 @@ * * *
+ *

{@code action} = {@link Action#ACCEPTED ACCEPTED}

+ * + *

+ * The {@code action} value {@link Action#ACCEPTED ACCEPTED} means that + * the requested credential is not ready yet. In this case, the + * implementation of the deferred credential endpoint should return a + * successful response to the request sender. The HTTP status code and the + * content type of the response should be 202 and {@code application/json}, + * respectively. The value of the {@code responseContent} parameter can + * be used as the message body of the response. It contains the + * "{@code transaction_id}" parameter that conforms to the specification + * of "Deferred Credential Response". + *

+ * + *
+ * HTTP/1.1 202 Accepted
+ * Content-Type: application/json
+ * Cache-Control: no-store
+ *
+ * (Put the value of the "responseContent" parameter here.)
+ * 
+ * + *
+ *

{@code action} = {@link Action#ACCEPTED_JWT ACCEPTED_JWT}

+ * + *

+ * The {@code action} value {@link Action#ACCEPTED_JWT ACCEPTED_JWT} means + * that the requested credential is not ready yet and the deferred credential + * response should be encrypted. In this case, the implementation of the + * deferred credential endpoint should return a successful response to the + * request sender. The HTTP status code and the content type of the response + * should be 202 and {@code application/jwt}, respectively. The value of the + * {@code responseContent} parameter is an encrypted JWT and can be used + * as the message body of the response. + *

+ * + *
+ * HTTP/1.1 202 Accepted
+ * Content-Type: application/jwt
+ * Cache-Control: no-store
+ *
+ * (Put the value of the "responseContent" parameter here.)
+ * 
+ * + *
*

{@code action} = {@link Action#BAD_REQUEST BAD_REQUEST}

* *

@@ -169,7 +214,7 @@ */ public class CredentialDeferredIssueResponse extends ApiResponse { - private static final long serialVersionUID = 2L; + private static final long serialVersionUID = 3L; /** @@ -197,12 +242,42 @@ public enum Action */ OK_JWT, + /** + * The requested credential is not ready yet. The implementation of + * the deferred credential endpoint should return a successful response + * with the HTTP status code "202 Accepted" and the content type + * {@code application/json}. + * + * @since 4.35 + * @since Authlete 3.0.25 + */ + ACCEPTED, + + /** + * The requested credential is not ready yet and the deferred + * credential response should be encrypted. The implementation of the + * deferred credential endpoint should return a successful response + * with the HTTP status code "202 Accepted" and the content type + * {@code application/jwt}. + * + * @since 4.35 + * @since Authlete 3.0.25 + */ + ACCEPTED_JWT, + /** * The original deferred credential request is wrong. This can happen, * for example, when the process for encrypting the deferred credential * response with the encryption parameters specified in the deferred * credential request failed. * + *

+ * Since Authlete 3.0.25, this action is returned also when the + * credential issuer has decided not to issue a credential for the + * presented transaction ID (that is, when the {@code /vci/deferred/issue} + * API is called with {@code denied} set to {@code true}). + *

+ * * @since 3.86 */ BAD_REQUEST, diff --git a/src/main/java/com/authlete/common/dto/CredentialDeferredParseResponse.java b/src/main/java/com/authlete/common/dto/CredentialDeferredParseResponse.java index 778bfda188..3891482730 100644 --- a/src/main/java/com/authlete/common/dto/CredentialDeferredParseResponse.java +++ b/src/main/java/com/authlete/common/dto/CredentialDeferredParseResponse.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Authlete, Inc. + * Copyright (C) 2023-2026 Authlete, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of @@ -33,14 +33,15 @@ * The {@code action} value {@link Action#OK OK} means that the deferred * credential request is valid. In this case, the implementation of the * deferred credential endpoint should call the {@code /vci/deferred/issue} - * API in order to issue a verifiable credential, or return the - * {@code issuance_pending} error if the verifiable credential is not ready - * yet. + * API in order to issue a verifiable credential, deny the deferred credential + * request, or notify that the requested credential is not ready yet. *

* *

- * The following is an example error response telling the request sender - * that the verifiable credential is not ready yet. + * Note that if the Authlete Server version is older than 3.0.25 and the + * target specification version is "1.0-ID1", you need to manually construct + * an error response containing {@code "error":"issuance_pending"}, as shown + * below, without using the {@code /vci/deferred/issue} API. *

* *
diff --git a/src/main/java/com/authlete/common/dto/CredentialIssuerMetadata.java b/src/main/java/com/authlete/common/dto/CredentialIssuerMetadata.java
index 0a002df1b8..7b0fb32090 100644
--- a/src/main/java/com/authlete/common/dto/CredentialIssuerMetadata.java
+++ b/src/main/java/com/authlete/common/dto/CredentialIssuerMetadata.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2023-2024 Authlete, Inc.
+ * Copyright (C) 2023-2025 Authlete, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -18,26 +18,103 @@
 
 import static com.authlete.common.util.MapUtils.put;
 import static com.authlete.common.util.MapUtils.putJsonObject;
+import static com.authlete.common.util.MapUtils.putJsonArray;
 import java.io.Serializable;
 import java.net.URI;
 import java.util.LinkedHashMap;
 import java.util.Map;
 import com.authlete.common.types.JWEAlg;
 import com.authlete.common.types.JWEEnc;
+import com.authlete.common.types.JWEZip;
+import com.nimbusds.jose.jwk.JWKSet;
 
 
 /**
  * A class that represents the set of credential issuer metadata.
- * The set consists of the following.
+ * The set consists of the following:
+ *
+ * 
+ * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
ParameterSpec Version
1.0-ID11.0-Final
{@code credential_issuer}
{@code authorization_servers}
{@code credential_endpoint}
{@code batch_credential_endpoint}deprecated
{@code deferred_credential_endpoint}
{@code notification_endpoint}
{@code nonce_endpoint}
{@code credential_request_encryption}
{@code credential_response_encryption}
{@code batch_credential_issuance}
{@code display}
{@code credential_configurations_supported}
+ *
+ * + *

+ * The following parameters, which existed in the 1.0-ID1 version but have + * removed from the 1.0-Final version, are not supported by Authlete + * from the beginning: + *

* *
    - *
  • {@code credential_issuer} - *
  • {@code authorization_servers} - *
  • {@code credential_endpoint} - *
  • {@code batch_credential_endpoint} - *
  • {@code deferred_credential_endpoint} - *
  • {@code credential_response_encryption} - *
  • {@code credential_configurations_supported} + *
  • {@code credential_identifiers_supported}
  • + *
  • {@code signed_metadata}
  • *
* *

@@ -76,14 +153,14 @@ * @since Authlete 3.0 * * @see OpenID for Verifiable Credential Issuance + * >OpenID for Verifiable Credential Issuance 1.0 * - * @see OpenID for Verifiable Credential Issuance, Working Draft + * @see OpenID for Verifiable Credential Issuance 1.0, Implementer's Draft 1 */ public class CredentialIssuerMetadata implements Serializable { - private static final long serialVersionUID = 4L; + private static final long serialVersionUID = 5L; /** @@ -119,6 +196,22 @@ public class CredentialIssuerMetadata implements Serializable private URI deferredCredentialEndpoint; + /** + * The URL of the notification endpoint. + * + * @since 4.26 + */ + private URI notificationEndpoint; + + + /** + * The URL of the nonce endpoint. + * + * @since 4.26 + */ + private URI nonceEndpoint; + + /** * The supported JWE alg algorithms for credential response encryption. * @@ -127,6 +220,40 @@ public class CredentialIssuerMetadata implements Serializable private JWEAlg[] credentialResponseEncryptionAlgValuesSupported; + /** + * A JSON Web Key Set containing private keys for credential request + * encryption. + * + * @since 4.26 + */ + private String credentialRequestEncryptionJwks; + + + /** + * The supported JWE enc algorithms for credential request encryption. + * + * @since 4.26 + */ + private JWEEnc[] credentialRequestEncryptionEncValuesSupported; + + + /** + * The supported JWE zip algorithms for credential request encryption. + * + * @since 4.26 + */ + private JWEZip[] credentialRequestEncryptionZipValuesSupported; + + + /** + * The boolean flag indicating whether credential request encryption is + * required. + * + * @since 4.26 + */ + private boolean requireCredentialRequestEncryption; + + /** * The supported JWE enc algorithms for credential response encryption. * @@ -135,6 +262,14 @@ public class CredentialIssuerMetadata implements Serializable private JWEEnc[] credentialResponseEncryptionEncValuesSupported; + /** + * The supported JWE zip algorithms for credential response encryption. + * + * @since 4.26 + */ + private JWEZip[] credentialResponseEncryptionZipValuesSupported; + + /** * The boolean flag indicating whether credential response encryption is * required. @@ -144,6 +279,23 @@ public class CredentialIssuerMetadata implements Serializable private boolean requireCredentialResponseEncryption; + /** + * The maximum array size for the {@code proofs} parameter in a credential + * request. + * + * @since 4.26 + */ + private int batchSize; + + + /** + * The credential issuer's display properties in JSON array format. + * + * @since 4.26 + */ + private String display; + + /** * A JSON object describing supported credential configurations. */ @@ -176,9 +328,18 @@ public CredentialIssuerMetadata(CredentialIssuerMetadata metadata) credentialEndpoint = metadata.getCredentialEndpoint(); batchCredentialEndpoint = metadata.getBatchCredentialEndpoint(); deferredCredentialEndpoint = metadata.getDeferredCredentialEndpoint(); + notificationEndpoint = metadata.getNotificationEndpoint(); + nonceEndpoint = metadata.getNonceEndpoint(); + credentialRequestEncryptionJwks = metadata.getCredentialRequestEncryptionJwks(); + credentialRequestEncryptionEncValuesSupported = metadata.getCredentialRequestEncryptionEncValuesSupported(); + credentialRequestEncryptionZipValuesSupported = metadata.getCredentialRequestEncryptionZipValuesSupported(); + requireCredentialRequestEncryption = metadata.isRequireCredentialRequestEncryption(); credentialResponseEncryptionAlgValuesSupported = metadata.getCredentialResponseEncryptionAlgValuesSupported(); credentialResponseEncryptionEncValuesSupported = metadata.getCredentialResponseEncryptionEncValuesSupported(); + credentialResponseEncryptionZipValuesSupported = metadata.getCredentialResponseEncryptionZipValuesSupported(); requireCredentialResponseEncryption = metadata.isRequireCredentialResponseEncryption(); + batchSize = metadata.getBatchSize(); + display = metadata.getDisplay(); credentialsSupported = metadata.getCredentialsSupported(); } @@ -323,6 +484,11 @@ public CredentialIssuerMetadata setCredentialEndpoint(URI endpoint) * this property should be omitted. *

* + *

+ * NOTE: This metadata parameter was deprecated and is not available in the + * 1.0-Final version of the OID4VCI specification. + *

+ * * @return * The URL of the batch credential endpoint. */ @@ -341,10 +507,16 @@ public URI getBatchCredentialEndpoint() * this property should be omitted. *

* + *

+ * NOTE: This metadata parameter was deprecated and is not available in the + * 1.0-Final version of the OID4VCI specification. + *

+ * * @param endpoint * The URL of the batch credential endpoint. * * @return + * {@code this} object. */ public CredentialIssuerMetadata setBatchCredentialEndpoint(URI endpoint) { @@ -399,6 +571,284 @@ public CredentialIssuerMetadata setDeferredCredentialEndpoint(URI endpoint) } + /** + * Get the URL of the notification endpoint. This property corresponds to + * the {@code notification_endpoint} metadata. + * + * @return + * The URL of the notification endpoint. + * + * @since 4.26 + */ + public URI getNotificationEndpoint() + { + return notificationEndpoint; + } + + + /** + * Set the URL of the notification endpoint. This property corresponds to + * the {@code notification_endpoint} metadata. + * + * @param endpoint + * The URL of the notification endpoint. + * + * @return + * {@code this} object. + * + * @since 4.26 + */ + public CredentialIssuerMetadata setNotificationEndpoint(URI endpoint) + { + this.notificationEndpoint = endpoint; + + return this; + } + + + /** + * Get the URL of the nonce endpoint. This property corresponds to the + * {@code nonce_endpoint} metadata. + * + *

+ * NOTE: This metadata parameter exists in the 1.0-Final version of the + * OID4VCI specification but is not available in the 1.0-ID1 version. + *

+ * + * @return + * The URL of the nonce endpoint. + * + * @since 4.26 + */ + public URI getNonceEndpoint() + { + return nonceEndpoint; + } + + + /** + * Set the URL of the nonce endpoint. This property corresponds to the + * {@code nonce_endpoint} metadata. + * + *

+ * NOTE: This metadata parameter exists in the 1.0-Final version of the + * OID4VCI specification but is not available in the 1.0-ID1 version. + *

+ * + * @param endpoint + * The URL of the nonce endpoint. + * + * @return + * {@code this} object. + * + * @since 4.26 + */ + public CredentialIssuerMetadata setNonceEndpoint(URI endpoint) + { + this.nonceEndpoint = endpoint; + + return this; + } + + + /** + * Get the JWK Set for credential request encryption. + * + *

+ * The public part of this JWK Set is used as the value of the + * {@code credential_request_encryption.jwks} metadata. + *

+ * + *

+ * NOTE: This metadata parameter exists in the 1.0-Final version of the + * OID4VCI specification but is not available in the 1.0-ID1 version. + *

+ * + * @return + * The JWK Set for credential request encryption. + * + * @since 4.26 + */ + public String getCredentialRequestEncryptionJwks() + { + return credentialRequestEncryptionJwks; + } + + + /** + * Set the JWK Set for credential request encryption. + * + *

+ * The public part of this JWK Set is used as the value of the + * {@code credential_request_encryption.jwks} metadata. + *

+ * + *

+ * NOTE: This metadata parameter exists in the 1.0-Final version of the + * OID4VCI specification but is not available in the 1.0-ID1 version. + *

+ * + * @param jwks + * The JWK Set for credential request encryption. + * + * @return + * {@code this} object. + * + * @since 4.26 + */ + public CredentialIssuerMetadata setCredentialRequestEncryptionJwks(String jwks) + { + this.credentialRequestEncryptionJwks = jwks; + + return this; + } + + + /** + * Get the supported JWE {@code enc} algorithms for credential request + * encryption. This property corresponds to the + * {@code credential_request_encryption.enc_values_supported} metadata. + * + *

+ * NOTE: This metadata parameter exists in the 1.0-Final version of the + * OID4VCI specification but is not available in the 1.0-ID1 version. + *

+ * + * @return + * The supported JWE {@code enc} algorithms for credential request + * encryption. + * + * @since 4.26 + */ + public JWEEnc[] getCredentialRequestEncryptionEncValuesSupported() + { + return credentialRequestEncryptionEncValuesSupported; + } + + + /** + * Set the supported JWE {@code enc} algorithms for credential request + * encryption. This property corresponds to the + * {@code credential_request_encryption.enc_values_supported} metadata. + * + *

+ * NOTE: This metadata parameter exists in the 1.0-Final version of the + * OID4VCI specification but is not available in the 1.0-ID1 version. + *

+ * + * @param encs + * The supported JWE {@code enc} algorithms for credential request + * encryption. + * + * @return + * {@code this} object. + * + * @since 4.26 + */ + public CredentialIssuerMetadata setCredentialRequestEncryptionEncValuesSupported(JWEEnc[] encs) + { + this.credentialRequestEncryptionEncValuesSupported = encs; + + return this; + } + + + /** + * Get the supported JWE {@code zip} algorithms for credential request + * encryption. This property corresponds to the + * {@code credential_request_encryption.zip_values_supported} metadata. + * + *

+ * NOTE: This metadata parameter exists in the 1.0-Final version of the + * OID4VCI specification but is not available in the 1.0-ID1 version. + *

+ * + * @return + * The supported JWE {@code zip} algorithms for credential request + * encryption. + * + * @since 4.26 + */ + public JWEZip[] getCredentialRequestEncryptionZipValuesSupported() + { + return credentialRequestEncryptionZipValuesSupported; + } + + + /** + * Set the supported JWE {@code zip} algorithms for credential request + * encryption. This property corresponds to the + * {@code credential_request_encryption.zip_values_supported} metadata. + * + *

+ * NOTE: This metadata parameter exists in the 1.0-Final version of the + * OID4VCI specification but is not available in the 1.0-ID1 version. + *

+ * + * @param zips + * The supported JWE {@code zip} algorithms for credential request + * encryption. + * + * @return + * {@code this} object. + * + * @since 4.26 + */ + public CredentialIssuerMetadata setCredentialRequestEncryptionZipValuesSupported(JWEZip[] zips) + { + this.credentialRequestEncryptionZipValuesSupported = zips; + + return this; + } + + + /** + * Get the boolean flag indicating whether credential request encryption + * is required. This property corresponds to the + * {@code credential_request_encryption.encryption_required} metadata. + * + *

+ * If this flag is {@code true}, every credential request to the credential + * issuer must be encrypted. + *

+ * + * @return + * {@code true} if credential request encryption is required. + * + * @since 4.26 + */ + public boolean isRequireCredentialRequestEncryption() + { + return requireCredentialRequestEncryption; + } + + + /** + * Set the boolean flag indicating whether credential request encryption + * is required. This property corresponds to the + * {@code credential_request_encryption.encryption_required} metadata. + * + *

+ * If this flag is {@code true}, every credential request to the credential + * issuer must be encrypted + *

+ * + * @param required + * {@code true} to require credential request encryption. + * + * @return + * {@code this} object. + * + * @since 4.26 + */ + public CredentialIssuerMetadata setRequireCredentialRequestEncryption(boolean required) + { + this.requireCredentialRequestEncryption = required; + + return this; + } + + /** * Get the supported JWE {@code alg} algorithms for credential response * encryption. This property corresponds to the @@ -478,6 +928,55 @@ public CredentialIssuerMetadata setCredentialResponseEncryptionEncValuesSupporte } + /** + * Get the supported JWE {@code zip} algorithms for credential response + * encryption. This property corresponds to the + * {@code credential_response_encryption.zip_values_supported} metadata. + * + *

+ * NOTE: This metadata parameter exists in the 1.0-Final version of the + * OID4VCI specification but is not available in the 1.0-ID1 version. + *

+ * + * @return + * The supported JWE {@code zip} algorithms for credential response + * encryption. + * + * @since 4.26 + */ + public JWEZip[] getCredentialResponseEncryptionZipValuesSupported() + { + return credentialResponseEncryptionZipValuesSupported; + } + + + /** + * Set the supported JWE {@code zip} algorithms for credential response + * encryption. This property corresponds to the + * {@code credential_response_encryption.zip_values_supported} metadata. + * + *

+ * NOTE: This metadata parameter exists in the 1.0-Final version of the + * OID4VCI specification but is not available in the 1.0-ID1 version. + *

+ * + * @param zips + * The supported JWE {@code zip} algorithms for credential response + * encryption. + * + * @return + * {@code this} object. + * + * @since 4.26 + */ + public CredentialIssuerMetadata setCredentialResponseEncryptionZipValuesSupported(JWEZip[] zips) + { + this.credentialResponseEncryptionZipValuesSupported = zips; + + return this; + } + + /** * Get the boolean flag indicating whether credential response encryption * is required. This property corresponds to the @@ -525,6 +1024,152 @@ public CredentialIssuerMetadata setRequireCredentialResponseEncryption(boolean r } + /** + * Get the maximum array size for the {@code proofs} parameter in a + * credential request. This property corresponds to the + * {@code batch_credential_issuance.batch_size} metadata. + * + *

+ * If the value of this property is 2 or greater, the + * {@code batch_credential_issuance} parameter will appear in the response + * from the credential metadata endpoint. + *

+ * + *

+ * NOTE: This metadata parameter exists in the 1.0-Final version of the + * OID4VCI specification but is not available in the 1.0-ID1 version. + *

+ * + * @return + * The maximum array size for the {@code proofs} parameter in a + * credential request. + * + * @since 4.26 + */ + public int getBatchSize() + { + return batchSize; + } + + + /** + * Set the maximum array size for the {@code proofs} parameter in a + * credential request. This property corresponds to the + * {@code batch_credential_issuance.batch_size} metadata. + * + *

+ * If the value of this property is 2 or greater, the + * {@code batch_credential_issuance} parameter will appear in the response + * from the credential metadata endpoint. + *

+ * + *

+ * NOTE: This metadata parameter exists in the 1.0-Final version of the + * OID4VCI specification but is not available in the 1.0-ID1 version. + *

+ * + * @param batchSize + * The maximum array size for the {@code proofs} parameter in a + * credential request. + * + * @return + * {@code this} object. + * + * @since 4.26 + */ + public CredentialIssuerMetadata setBatchSize(int batchSize) + { + this.batchSize = batchSize; + + return this; + } + + + /** + * Get the display properties of this credential issuer in JSON array format. + * + *

+ * The following is an example of {@code display} value excerpted from Appendix I.1. Credential IssuerMetadata of the OID4VCI specification. + *

+ * + *
+     * [
+     *   {
+     *     "name": "Example University",
+     *     "locale": "en-US",
+     *     "logo": {
+     *       "uri": "https://university.example.edu/public/logo.png",
+     *       "alt_text":"a square logo of a university"
+     *     }
+     *   },
+     *   {
+     *     "name": "Example Université",
+     *     "locale": "fr-FR",
+     *     "logo": {
+     *       "uri": "https://university.example.edu/public/logo.png",
+     *       "alt_text":"Un logo universitaire carré"
+     *     }
+     *   }
+     * ]
+ * + * @return + * The display properties. + * + * @since 4.26 + */ + public String getDisplay() + { + return display; + } + + + /** + * Set the display properties of this credential issuer in JSON array format. + * + *

+ * The following is an example of {@code display} value excerpted from Appendix I.1. Credential IssuerMetadata of the OID4VCI specification. + *

+ * + *
+     * [
+     *   {
+     *     "name": "Example University",
+     *     "locale": "en-US",
+     *     "logo": {
+     *       "uri": "https://university.example.edu/public/logo.png",
+     *       "alt_text":"a square logo of a university"
+     *     }
+     *   },
+     *   {
+     *     "name": "Example Université",
+     *     "locale": "fr-FR",
+     *     "logo": {
+     *       "uri": "https://university.example.edu/public/logo.png",
+     *       "alt_text":"Un logo universitaire carré"
+     *     }
+     *   }
+     * ]
+ * + * @param display + * The display properties. + * + * @return + * {@code this} object. + * + * @since 4.26 + */ + public CredentialIssuerMetadata setDisplay(String display) + { + this.display = display; + + return this; + } + + /** * Get the information about supported credential configurations in the * JSON format. This property corresponds to the @@ -609,8 +1254,15 @@ public boolean isEmpty() (credentialEndpoint == null) && (batchCredentialEndpoint == null) && (deferredCredentialEndpoint == null) && + (notificationEndpoint == null) && + (nonceEndpoint == null) && + (credentialRequestEncryptionJwks == null) && + (credentialRequestEncryptionEncValuesSupported == null) && + (credentialRequestEncryptionZipValuesSupported == null) && (credentialResponseEncryptionAlgValuesSupported == null) && (credentialResponseEncryptionEncValuesSupported == null) && + (credentialResponseEncryptionZipValuesSupported == null) && + (display == null) && (credentialsSupported == null); } @@ -696,20 +1348,60 @@ public Map toMap() put(map, "credential_endpoint", credentialEndpoint, false); put(map, "batch_credential_endpoint", batchCredentialEndpoint, false); put(map, "deferred_credential_endpoint", deferredCredentialEndpoint, false); + put(map, "notification_endpoint", notificationEndpoint, false); + put(map, "nonce_endpoint", nonceEndpoint, false); + put(map, "credential_request_encryption", credentialRequestEncryption(), false); put(map, "credential_response_encryption", credentialResponseEncryption(), false); + put(map, "batch_credential_issuance", batchCredentialIssuance(), false); + putDisplay(map); // display + putCredentialConfigurationsSupported(map); // credential_configurations_supported + + return map; + } + + + private Map credentialRequestEncryption() + { + if (credentialRequestEncryptionJwks == null || + credentialRequestEncryptionEncValuesSupported == null || + credentialRequestEncryptionEncValuesSupported.length == 0) + { + return null; + } + + Map map = new LinkedHashMap<>(); + + put(map, "jwks", credentialRequestEncryptionPublicJwks(), true); + put(map, "enc_values_supported", credentialRequestEncryptionEncValuesSupported, true); + put(map, "zip_values_supported", credentialRequestEncryptionZipValuesSupported, false); + put(map, "encryption_required", requireCredentialRequestEncryption, true); + + return map; + } + + + private Map credentialRequestEncryptionPublicJwks() + { + if (credentialRequestEncryptionJwks == null) + { + return null; + } + + JWKSet jwks; try { - // Parse the value of 'credentialsSupported' as a JSON object. - putJsonObject(map, "credential_configurations_supported", credentialsSupported, false); + // Parse the string as a JWK Set. + jwks = JWKSet.parse(credentialRequestEncryptionJwks); } catch (Exception cause) { throw new IllegalStateException( - "The value of the 'credentialsSupported' property failed to be parsed as a JSON object.", cause); + "The value of the 'credentialRequestEncryptionJwks' property failed to be parsed as a JWK Set.", cause); } - return map; + // Public keys only. + return jwks.toJSONObject(true); } @@ -727,8 +1419,74 @@ private Map credentialResponseEncryption() put(map, "alg_values_supported", credentialResponseEncryptionAlgValuesSupported, true); put(map, "enc_values_supported", credentialResponseEncryptionEncValuesSupported, true); + put(map, "zip_values_supported", credentialResponseEncryptionZipValuesSupported, false); put(map, "encryption_required", requireCredentialResponseEncryption, true); return map; } + + + private Map batchCredentialIssuance() + { + // OpenID for Verifiable Credential Issuance 1.0 + // 12.2.4. Credential Issuer Metadata Parameters + // + // batch_credential_issuance: + // + // OPTIONAL. Object containing information about the Credential + // Issuer's support for issuance of multiple Credentials in a batch + // in the Credential Endpoint. The presence of this parameter means + // that the issuer supports more than one key proof in the proofs + // parameter in the Credential Request so can issue more than one + // Verifiable Credential for the same Credential Dataset in a + // single request/response. + // + // batch_size: + // REQUIRED. Integer value specifying the maximum array size + // for the proofs parameter in a Credential Request. It MUST + // be 2 or greater. + // + if (batchSize < 2) + { + return null; + } + + // { + // "batch_size": batchSize + // } + Map value = new LinkedHashMap<>(); + value.put("batch_size", batchSize); + + return value; + } + + + private void putDisplay(Map map) + { + try + { + // Parse the value of 'display as a JSON array. + putJsonArray(map, "display", display, false); + } + catch (Exception cause) + { + throw new IllegalStateException( + "The value of the 'display' property failed to be parsed as a JSON array.", cause); + } + } + + + private void putCredentialConfigurationsSupported(Map map) + { + try + { + // Parse the value of 'credentialsSupported' as a JSON object. + putJsonObject(map, "credential_configurations_supported", credentialsSupported, false); + } + catch (Exception cause) + { + throw new IllegalStateException( + "The value of the 'credentialsSupported' property failed to be parsed as a JSON object.", cause); + } + } } diff --git a/src/main/java/com/authlete/common/dto/CredentialNonceRequest.java b/src/main/java/com/authlete/common/dto/CredentialNonceRequest.java new file mode 100644 index 0000000000..1e8687355c --- /dev/null +++ b/src/main/java/com/authlete/common/dto/CredentialNonceRequest.java @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2025 Authlete, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.authlete.common.dto; + + +import java.io.Serializable; + + +/** + * Request to Authlete's {@code /vci/nonce} API. + * + *

+ * The Authlete API is supposed to be used from within the implementation of + * the nonce endpoint of the credential issuer. + *

+ * + * @since 4.27 + * @since Authlete 3.0.22 + * + * @see + * OpenID for Verifiable Credential Issuance 1.0, + * Section 7. Nonce Endpoint + */ +public class CredentialNonceRequest implements Serializable +{ + private static final long serialVersionUID = 1L; + + + private boolean pretty; + + + + /** + * Get the flag indicating whether the nonce response is written in the pretty + * format or not. + * + * @return + * {@code true} if the nonce response is written in the pretty format. + */ + public boolean isPretty() + { + return pretty; + } + + + /** + * Set the flag indicating whether the nonce response is written in the pretty + * format or not. + * + * @param pretty + * {@code true} to write the nonce response in the pretty format. + * + * @return + * {@code this} object. + */ + public CredentialNonceRequest setPretty(boolean pretty) + { + this.pretty = pretty; + + return this; + } +} diff --git a/src/main/java/com/authlete/common/dto/CredentialNonceResponse.java b/src/main/java/com/authlete/common/dto/CredentialNonceResponse.java new file mode 100644 index 0000000000..4bd7c91070 --- /dev/null +++ b/src/main/java/com/authlete/common/dto/CredentialNonceResponse.java @@ -0,0 +1,283 @@ +/* + * Copyright (C) 2025 Authlete, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.authlete.common.dto; + + +/** + * Response from Authlete's {@code /vci/nonce} API. + * + *

+ * The Authlete API is supposed to be used from within the implementation + * the nonce endpoint of the credential issuer. + *

+ * + *

+ * Authlete's {@code /vci/nonce} API returns JSON which can be mapped to + * this class. The credential issuer implementation should retrieve the value + * of the {@link #getAction() action} response parameter from + * the API response and take the following steps according to the value. + *

+ * + *

OK

+ * + *

+ * When the value of the {@link #getAction() action} response + * parameter is {@link Action#OK OK}, it means that Authlete + * could prepare a nonce response successfully. + *

+ * + *

+ * In this case, the implementation of the nonce endpoint of the credential + * issuer should return an HTTP response with the HTTP status code + * "{@code 200 OK}" and the content type "{@code application/json}". The + * message body of the response has been prepared by Authlete's {@code /vci/nonce} + * API and it is available as the + * {@link #getResponseContent() responseContent} response parameter. + *

+ * + *

+ * The implementation of the nonce endpoint can construct an HTTP response by + * doing like below. + *

+ * + *
+ * 200 OK
+ * Content-Type: application/json
+ * Cache-Control: no-store
+ * (Other HTTP headers)
+ *
+ * (the value of the {@link #getResponseContent() responseContent} response parameter)
+ * + *

+ * Note that the OpenID for Verifiable Credential Issuance 1.0 specification requires + * the response to include the {@code Cache-Control} header including the + * value {@code no-store}. + *

+ * + *

NOT_FOUND

+ * + *

+ * When the value of the {@link #getAction() action} response + * parameter is {@link Action#NOT_FOUND NOT_FOUND}, it means that + * the service configuration has not enabled the feature of Verifiable + * Credentials and so the nonce endpoint should not be accessed. + *

+ * + *

+ * In this case, the implementation of the nonce endpoint of the credential + * issuer should return an HTTP response with the HTTP status code + * "{@code 404 Not Found}" and the content type "{@code application/json}". + * The message body (= error information in the JSON format) of the response + * has been prepared by Authlete's {@code /vci/nonce} API and it is available + * as the {@link #getResponseContent() responseContent} response + * parameter. + *

+ * + *

+ * The implementation of the nonce endpoint can construct an HTTP response by + * doing like below. + *

+ * + *
+ * 404 Not Found
+ * Content-Type: application/json
+ * (Other HTTP headers)
+ *
+ * (the value of the {@link #getResponseContent() responseContent} response parameter)
+ * + *

INTERNAL_SERVER_ERROR

+ * + *

+ * When the value of the {@link #getAction() action} response + * parameter is {@link Action#INTERNAL_SERVER_ERROR INTERNAL_SERVER_ERROR}, + * it means that an unexpected error has occurred on Authlete side or the + * service has not been set up properly yet. + *

+ * + *

+ * In this case, a simple implementation of the nonce endpoint would return + * an HTTP response with the HTTP status code "{@code 500 Internal Server Error}" + * and the content type "{@code application/json}". The message body (= error + * information in the JSON format) of the response has been prepared by Authlete's + * {@code /vci/nonce} API and it is available as the {@link #getResponseContent() + * responseContent} response parameter. + *

+ * + *

+ * Such simple implementation of the nonce endpoint can construct an HTTP response + * by doing like below. + *

+ * + *
+ * 500 Internal Server Error
+ * Content-Type: application/json
+ * (Other HTTP headers)
+ *
+ * (the value of the {@link #getResponseContent() responseContent} response parameter)
+ * + *

+ * However, in real commercial deployments, it is rare for a credential issuer + * to return "{@code 500 Internal Server Error}" when it encounters an + * unexpected internal error. It's up to implementations of credential issuers + * what they actually return in the case of internal server error. + *

+ * + * @since 4.27 + * @since Authlete 3.0.22 + * + * @see + * OpenID for Verifiable Credential Issuance 1.0 + */ +public class CredentialNonceResponse extends ApiResponse +{ + private static final long serialVersionUID = 1L; + + + /** + * The next action that the implementation of the nonce endpoint + * should take after getting a response from Authlete's + * {@code /vci/nonce} API. + * + * @since 4.27 + * @since Authlete 3.0.22 + */ + public enum Action + { + /** + * A nonce response has been prepared successfully. The implementation + * of the nonce endpoint should return an HTTP response with the HTTP + * status code "{@code 200 OK}" and the content type + * "{@code application/json}". + */ + OK, + + /** + * The feature of Verifiable Credentials is not enabled. The + * implementation of the nonce endpoint should return an HTTP response + * with the HTTP status code "{@code 404 Not Found}" and the content + * type "{@code application/json}" to indicate that the endpoint should + * not be accessed. + */ + NOT_FOUND, + + /** + * An unexpected error occurred on Authlete side or the service has + * not been set up properly yet. A simple implementation of the nonce + * endpoint would return an HTTP response with the HTTP status code + * "{@code 500 Internal Server Error}" and the content type + * "{@code application/json}". + */ + INTERNAL_SERVER_ERROR, + } + + + private Action action; + private String responseContent; + private String cnonce; + + + /** + * Get the next action that the implementation of the nonce endpoint should + * take after getting a response from Authlete's {@code /vci/nonce} API. + * + * @return + * The next action. + */ + public Action getAction() + { + return action; + } + + + /** + * Set the next action that the implementation of the nonce endpoint should + * take after getting a response from Authlete's {@code /vci/nonce} API. + * + * @param action + * The next action. + * + * @return + * {@code this} object. + */ + public CredentialNonceResponse setAction(Action action) + { + this.action = action; + + return this; + } + + + /** + * Get the content that the implementation of the nonce endpoint should use + * when it constructs a response. + * + * @return + * The response content in the JSON format. + */ + public String getResponseContent() + { + return responseContent; + } + + + /** + * Set the content that the implementation of the nonce endpoint should use + * when it constructs a response. + * + * @param content + * The response content in the JSON format. + * + * @return + * {@code this} object. + */ + public CredentialNonceResponse setResponseContent(String content) + { + this.responseContent = content; + + return this; + } + + + /** + * Get the {@code c_nonce} value generated by the {@code /vci/nonce} API. + * + * @return + * The generated {@code c_nonce} value. + */ + public String getCnonce() + { + return cnonce; + } + + + /** + * Set the {@code c_nonce} value generated by the {@code /vci/nonce} API. + * + * @param cnonce + * The generated {@code c_nonce} value. + * + * @return + * {@code this} object. + */ + public CredentialNonceResponse setCnonce(String cnonce) + { + this.cnonce = cnonce; + + return this; + } +} diff --git a/src/main/java/com/authlete/common/dto/CredentialRequestInfo.java b/src/main/java/com/authlete/common/dto/CredentialRequestInfo.java index 97644f91bd..7f0a6d020e 100644 --- a/src/main/java/com/authlete/common/dto/CredentialRequestInfo.java +++ b/src/main/java/com/authlete/common/dto/CredentialRequestInfo.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023-2024 Authlete, Inc. + * Copyright (C) 2023-2026 Authlete, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of @@ -32,12 +32,12 @@ * @since 3.66 * @since Authlete 3.0 * - * @see OpenID for Verifiable Credential Issuance + * @see + * OpenID for Verifiable Credential Issuance 1.0 */ public class CredentialRequestInfo implements Serializable { - private static final long serialVersionUID = 2L; + private static final long serialVersionUID = 3L; /** @@ -72,6 +72,25 @@ public class CredentialRequestInfo implements Serializable private String details; + /** + * The {@code credential_configuration_id} parameter in the credential + * request. + * + * @since 4.34 + * @since Authlete 3.0.25 + */ + private String credentialConfigurationId; + + + /** + * The {@code credential_identifier} parameter in the credential request. + * + * @since 4.34; + * @since Authlete 3.0.25 + */ + private String credentialIdentifier; + + /** * Get the identifier of the credential request. * @@ -118,9 +137,18 @@ public CredentialRequestInfo setIdentifier(String identifier) /** * Get the value of the {@code format} parameter in the credential request. * + *

+ * This {@code format} property is available only when the supported OID4VCI + * specification version is {@code "1.0-ID1"} (i.e., when the Service's + * {@code oid4vciVersion} is unset or set to {@code "1.0-ID1"}) and the + * credential request contains the {@code format} parameter. Note that in + * the final version of the specification, the {@code format} parameter is + * deprecated. + *

+ * * @return * The value of the {@code format} parameter in the credential request. - * Such as {@code "vc+sd-jwt"}. + * Such as {@code "dc+sd-jwt"}. */ public String getFormat() { @@ -131,9 +159,18 @@ public String getFormat() /** * Set the value of the {@code format} parameter in the credential request. * + *

+ * This {@code format} property is available only when the supported OID4VCI + * specification version is {@code "1.0-ID1"} (i.e., when the Service's + * {@code oid4vciVersion} is unset or set to {@code "1.0-ID1"}) and the + * credential request contains the {@code format} parameter. Note that in + * the final version of the specification, the {@code format} parameter is + * deprecated. + *

+ * * @param format * The value of the {@code format} parameter in the credential request. - * Such as {@code "jwt_vc_json"} and {@code "vc+sd-jwt"}. + * Such as {@code "jwt_vc_json"} and {@code "dc+sd-jwt"}. * * @return * {@code this} object. @@ -233,11 +270,18 @@ public CredentialRequestInfo setBindingKeys(String[] bindingKeys) * *

* The value is almost the same as the credential request except that it - * does not contain the {@code "format"} parameter, the {@code "proof"} - * parameter, the {@code "proofs"} parameter, and the - * {@code credential_response_encryption} parameter. + * does not contain the following parameters: *

* + *
    + *
  • {@code credential_configuration_id} + *
  • {@code credential_identifier} + *
  • {@code credential_response_encryption} + *
  • {@code format} + *
  • {@code proof} + *
  • {@code proofs} + *
+ * *

* For example, when the original credential request holds the following: *

@@ -299,4 +343,114 @@ public CredentialRequestInfo setDetails(String details) return this; } + + + /** + * Get the value of the {@code credential_configuration_id} parameter in + * the credential request. + * + *

+ * This property is not available when the supported OID4VCI specification + * version is {@code "1.0-ID1"} (i.e., when the Service's + * {@code oid4vciVersion} is unset or set to {@code "1.0-ID1"}), because + * the {@code credential_configuration_id} parameter does not exist in + * OID4VCI 1.0 ID1. + *

+ * + * @return + * The value of the {@code credential_configuration_id} parameter + * in the credential request. + * + * @since 4.34 + * @since Authlete 3.0.25 + */ + public String getCredentialConfigurationId() + { + return credentialConfigurationId; + } + + + /** + * Set the value of the {@code credential_configuration_id} parameter in + * the credential request. + * + *

+ * This property is not available when the supported OID4VCI specification + * version is {@code "1.0-ID1"} (i.e., when the Service's + * {@code oid4vciVersion} is unset or set to {@code "1.0-ID1"}), because + * the {@code credential_configuration_id} parameter does not exist in + * OID4VCI 1.0 ID1. + *

+ * + * @param id + * The value of the {@code credential_configuration_id} parameter + * in the credential request. + * + * @return + * {@code this} object. + * + * @since 4.34 + * @since Authlete 3.0.25 + */ + public CredentialRequestInfo setCredentialConfigurationId(String id) + { + this.credentialConfigurationId = id; + + return this; + } + + + /** + * Get the value of the {@code credential_identifier} parameter in the + * credential request. + * + *

+ * This property is not available when the supported OID4VCI specification + * version is {@code "1.0-ID1"} (i.e., when the Service's + * {@code oid4vciVersion} is unset or set to {@code "1.0-ID1"}), because + * Authlete does not support the {@code credential_identifier} parameter + * for OID4VCI 1.0 ID1. + *

+ * + * @return + * The value of the {@code credential_identifier} parameter in the + * credential request. + * + * @since 4.34 + * @since Authlete 3.0.25 + */ + public String getCredentialIdentifier() + { + return credentialIdentifier; + } + + + /** + * Set the value of the {@code credential_identifier} parameter in the + * credential request. + * + *

+ * This property is not available when the supported OID4VCI specification + * version is {@code "1.0-ID1"} (i.e., when the Service's + * {@code oid4vciVersion} is unset or set to {@code "1.0-ID1"}), because + * Authlete does not support the {@code credential_identifier} parameter + * for OID4VCI 1.0 ID1. + *

+ * + * @param identifier + * The value of the {@code credential_identifier} parameter in the + * credential request. + * + * @return + * {@code this} object. + * + * @since 4.34 + * @since Authlete 3.0.25 + */ + public CredentialRequestInfo setCredentialIdentifier(String identifier) + { + this.credentialIdentifier = identifier; + + return this; + } } diff --git a/src/main/java/com/authlete/common/dto/CredentialSingleIssueRequest.java b/src/main/java/com/authlete/common/dto/CredentialSingleIssueRequest.java index a1a36d61af..e116ca52c3 100644 --- a/src/main/java/com/authlete/common/dto/CredentialSingleIssueRequest.java +++ b/src/main/java/com/authlete/common/dto/CredentialSingleIssueRequest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Authlete, Inc. + * Copyright (C) 2023-2026 Authlete, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of @@ -26,7 +26,7 @@ * The Authlete API is supposed to be called by the implementation of the * credential endpoint. The endpoint is defined in the "OpenID for Verifiable Credential Issuance" (OID4VCI) specification. + * >OpenID for Verifiable Credential Issuance 1.0" (OID4VCI) specification. *

* *

@@ -49,12 +49,12 @@ * @since 3.67 * @since Authlete 3.0 * - * @see OpenID for Verifiable Credential Issuance + * @see + * OpenID for Verifiable Credential Issuance 1.0 */ public class CredentialSingleIssueRequest implements Serializable { - private static final long serialVersionUID = 1L; + private static final long serialVersionUID = 2L; /** @@ -69,6 +69,26 @@ public class CredentialSingleIssueRequest implements Serializable private CredentialIssuanceOrder order; + /** + * The flag that indicates whether to deny the credential request. + * + * @since 4.35 + * @since Authlete 3.0.25 + */ + private boolean denied; + + + /** + * The minimum amount of time in seconds that the Wallet SHOULD wait after + * receiving the response before sending a new request to the Deferred + * Credential Endpoint. + * + * @since 4.35 + * @since Authlete 3.0.25 + */ + private int interval; + + /** * Get the access token that was presented at the credential endpoint. * @@ -127,4 +147,158 @@ public CredentialSingleIssueRequest setOrder(CredentialIssuanceOrder order) return this; } + + + /** + * Get the flag that indicates whether to deny the credential request. + * + *

+ * If this parameter is set to {@code true}, the response content prepared + * by Authlete will contain {@code "error":"credential_request_denied"} + * like below. + *

+ * + *
+     * {
+     *     "error": "credential_request_denied"
+     * }
+     * 
+ * + *

+ * Note that the {@code credential_request_denied} error code does not + * exist in OID4VCI 1.0 ID1. + *

+ * + * @return + * {@code true} for generating a response content containing + * {@code "error":"credential_request_denied"}. + * + * @since 4.35 + * @since Authlete 3.0.25 + */ + public boolean isDenied() + { + return denied; + } + + + /** + * Set the flag that indicates whether to deny the credential request. + * + *

+ * If this parameter is set to {@code true}, the response content prepared + * by Authlete will contain {@code "error":"credential_request_denied"} + * like below. + *

+ * + *
+     * {
+     *     "error": "credential_request_denied"
+     * }
+     * 
+ * + *

+ * Note that the {@code credential_request_denied} error code does not + * exist in OID4VCI 1.0 ID1. + *

+ * + * @param denied + * {@code true} for generating a response content containing + * {@code "error":"credential_request_denied"}. + * + * @return + * {@code this} object. + * + * @since 4.35 + * @since Authlete 3.0.25 + */ + public CredentialSingleIssueRequest setDenied(boolean denied) + { + this.denied = denied; + + return this; + } + + + /** + * Get the minimum amount of time in seconds that the Wallet SHOULD wait + * after receiving the response before sending a new request to the + * Deferred Credential Endpoint. + * + *

+ * The value of this parameter is used as the value of the {@code interval} + * parameter in the credential response. However, the {@code interval} + * parameter is included in the credential response only when the following + * conditions are met: + *

+ * + *
    + *
  • + * The value of the {@code order.issuanceDeferred} request parameter is + * {@code true}. + *
  • + * + *
  • + * The version of the OID4VCI specification is 1.0-Final or later (that is, + * the {@code oid4vciVersion} property of {@link Service} is set and its + * value is not {@code "1.0-ID1"}). + *
  • + *
+ * + * @return + * The minimum amount of time in seconds that the Wallet SHOULD + * wait after receiving the response before sending a new request + * to the Deferred Credential Endpoint. + * + * @since 4.35 + * @since Authlete 3.0.25 + */ + public int getInterval() + { + return interval; + } + + + /** + * Set the minimum amount of time in seconds that the Wallet SHOULD wait + * after receiving the response before sending a new request to the + * Deferred Credential Endpoint. + * + *

+ * The value of this parameter is used as the value of the {@code interval} + * parameter in the credential response. However, the {@code interval} + * parameter is included in the credential response only when the following + * conditions are met: + *

+ * + *
    + *
  • + * The value of the {@code order.issuanceDeferred} request parameter is + * {@code true}. + *
  • + * + *
  • + * The version of the OID4VCI specification is 1.0-Final or later (that is, + * the {@code oid4vciVersion} property of {@link Service} is set and its + * value is not {@code "1.0-ID1"}). + *
  • + *
+ * + * @param interval + * The minimum amount of time in seconds that the Wallet SHOULD + * wait after receiving the response before sending a new request + * to the Deferred Credential Endpoint. + * + * @return + * {@code this} object. + * + * @since 4.35 + * @since Authlete 3.0.25 + */ + public CredentialSingleIssueRequest setInterval(int interval) + { + this.interval = interval; + + return this; + } } diff --git a/src/main/java/com/authlete/common/dto/CredentialSingleIssueResponse.java b/src/main/java/com/authlete/common/dto/CredentialSingleIssueResponse.java index c91f0f67d3..64940b10c6 100644 --- a/src/main/java/com/authlete/common/dto/CredentialSingleIssueResponse.java +++ b/src/main/java/com/authlete/common/dto/CredentialSingleIssueResponse.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Authlete, Inc. + * Copyright (C) 2023-2026 Authlete, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of @@ -292,6 +292,13 @@ public enum Action * with the encryption parameters specified in the credential request * failed. * + *

+ * Since Authlete 3.0.25, this action is returned also when the + * credential issuer has decided not to issue a credential for the + * presented transaction ID (that is, when the {@code /vci/single/issue} + * API is called with {@code denied} set to {@code true}). + *

+ * * @since 3.86 */ BAD_REQUEST, diff --git a/src/main/java/com/authlete/common/dto/CursorResponse.java b/src/main/java/com/authlete/common/dto/CursorResponse.java new file mode 100644 index 0000000000..6683c58c42 --- /dev/null +++ b/src/main/java/com/authlete/common/dto/CursorResponse.java @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2014 Authlete, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.authlete.common.dto; + + +import java.io.Serializable; + + +/** + * Response from Authlete's cursor based APIs. + * + * @author Kyle Gonzalez + * @since Authlete 3.0.34 + */ +public class CursorResponse implements Serializable +{ + private static final long serialVersionUID = 1L; + + /** + * The total number of entities, in situations where this is expensive to calculate it can be omitted and set to {@code null}. + */ + private Integer totalCount = null; + + /** + * The requested max limit of entities to return in this payload. + */ + private int limit; + + /** + * The array of entities that have been returned. + */ + private T[] entities; + + /** + * The opaque cursor token which is required for the next list request. If there are no further entities to retrieve this property will be {@code null}. + */ + private String nextCursor; + + + public CursorResponse() + { + + } + + + public CursorResponse(T[] entities, int limit, String nextCursor) + { + this.entities = entities; + this.limit = limit; + this.nextCursor = nextCursor; + } + + + public CursorResponse(T[] entities, int limit, String nextCursor, Integer totalCount) + { + this(entities, limit, nextCursor); + this.totalCount = totalCount; + } + + + /** + * Get the list of entities that match the list conditions. + * + * @return Array of entities that match, otherwise an empty array + */ + public T[] getEntities() + { + return entities; + } + + + /** + * Get the interpreted limit of entities that will be return from the single call. + * + * @return the maximum amount of entities that could be returned from this single call + */ + public int getLimit() + { + return limit; + } + + + /** + * Get the total count of entities available from this endpoint given the provided conditions. + * + * @return the maximum available amount of entities that can be retrieved at this point in time + */ + public Integer getTotalCount() + { + return totalCount; + } + + + /** + * The next {@code cursor} that should be provided to this endpoint to retrieve the next portion of entities. + * If {@code null} it means all entities have been iterated over already and there are no further entities to retrieve. + * + * @return the next cursor value to be used if more entities can be retrieved, otherwise {@code null} + */ + public String getNextCursor() + { + return nextCursor; + } +} diff --git a/src/main/java/com/authlete/common/dto/DeviceAuthorizationRequest.java b/src/main/java/com/authlete/common/dto/DeviceAuthorizationRequest.java index 236b44d120..1c1c8ebd68 100644 --- a/src/main/java/com/authlete/common/dto/DeviceAuthorizationRequest.java +++ b/src/main/java/com/authlete/common/dto/DeviceAuthorizationRequest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2019-2024 Authlete, Inc. + * Copyright (C) 2019-2025 Authlete, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -124,7 +124,7 @@ */ public class DeviceAuthorizationRequest implements Serializable { - private static final long serialVersionUID = 2L; + private static final long serialVersionUID = 3L; /** @@ -181,6 +181,15 @@ public class DeviceAuthorizationRequest implements Serializable private String oauthClientAttestationPop; + /** + * Options for CIMD processing. + * + * @since 4.30 + * @since Authlete 3.0.22 + */ + private CimdOptions cimdOptions; + + /** * Get the value of {@code parameters} which are the request parameters * that the device authorization endpoint of the authorization server @@ -446,4 +455,49 @@ public DeviceAuthorizationRequest setOauthClientAttestationPop(String jwt) return this; } + + + /** + * Get options for CIMD processing. + * + * @return + * Options for CIMD processing. + * + * @since 4.30 + * @since Authlete 3.0.22 + * + * @see + * OAuth Client ID Metadata Document + */ + public CimdOptions getCimdOptions() + { + return cimdOptions; + } + + + /** + * Set options for CIMD processing. + * + * @param options + * Options for CIMD processing. + * + * @return + * {@code this} object. + * + * @since 4.30 + * @since Authlete 3.0.22 + * + * @see + * OAuth Client ID Metadata Document + */ + public DeviceAuthorizationRequest setCimdOptions(CimdOptions options) + { + this.cimdOptions = options; + + return this; + } } diff --git a/src/main/java/com/authlete/common/dto/DeviceAuthorizationResponse.java b/src/main/java/com/authlete/common/dto/DeviceAuthorizationResponse.java index 347db34018..18bd97bcf7 100644 --- a/src/main/java/com/authlete/common/dto/DeviceAuthorizationResponse.java +++ b/src/main/java/com/authlete/common/dto/DeviceAuthorizationResponse.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2019-2023 Authlete, Inc. + * Copyright (C) 2019-2026 Authlete, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of @@ -166,11 +166,39 @@ * * * + *
+ *
+ *

Attestation Challenge (Authlete 3.0.28 onwards)

+ * + *

+ * Since version 3.0.28, Authlete recognizes the {@code challenge} claim in + * Client Attestation PoP JWTs. If the {@code challenge} claim is required + * (= if the service's challenge endpoint is configured), the Authlete API + * checks whether the {@code challenge} claim in the presented Client + * Attestation PoP JWT is identical to the expected value. + *

+ * + *

+ * If the {@code attestationChallenge} response parameter from the API is not + * null, its value is the expected attestation challenge value for Client + * Attestation PoP JWT. The expected value needs to be conveyed to the client + * application as the value of the {@code OAuth-Client-Attestation-Challenge} + * HTTP header. + *

+ * + *
OAuth-Client-Attestation-Challenge: (The value returned from {@link #getAttestationChallenge()})
+ * + *

+ * See OAuth 2.0 Attestation-Based Client Authentication for details. + *

+ * * @since 2.42 */ public class DeviceAuthorizationResponse extends ApiResponse { - private static final long serialVersionUID = 9L; + private static final long serialVersionUID = 11L; /** @@ -249,6 +277,22 @@ public enum Action */ private boolean clientEntityIdUsed; + /** + * The location of the client's metadata document. + * + * @since 4.29 + * @since Authlete 3.0.22 + */ + private URI metadataDocumentLocation; + + /** + * Whether the client's metadata document was used. + * + * @since 4.29 + * @since Authlete 3.0.22 + */ + private boolean metadataDocumentUsed; + /** * @since Authlete 2.0.0 */ @@ -354,6 +398,16 @@ public enum Action */ private String[] warnings; + /** + * The expected attestation challenge value for Client Attestation PoP JWT, + * which should be used as the value of the + * {@code OAuth-Client-Attestation-Challenge} HTTP header. + * + * @since 4.39 + * @since Authlete 3.0.28 + */ + private String attestationChallenge; + /** * Get the next action that the implementation of the device authorization @@ -622,17 +676,128 @@ public DeviceAuthorizationResponse setClientEntityIdUsed(boolean used) /** - * Get the client identifier used in the device authorization request. + * Get the location of the client's metadata document. This property + * holds a non-null value only when {@link #isMetadataDocumentUsed()} + * returns {@code true}. + * + * @return + * The location of the client's metadata document. + * + * @since 4.29 + * @since Authlete 3.0.22 + * + * @see + * OAuth Client ID Metadata Document + */ + public URI getMetadataDocumentLocation() + { + return metadataDocumentLocation; + } + + + /** + * Set the location of the client's metadata document. This property + * should hold a non-null value only when {@link #isMetadataDocumentUsed()} + * returns {@code true}. + * + * @param location + * The location of the client's metadata document. + * + * @return + * {@code this} object. + * + * @since 4.29 + * @since Authlete 3.0.22 + * + * @see + * OAuth Client ID Metadata Document + */ + public DeviceAuthorizationResponse setMetadataDocumentLocation(URI location) + { + this.metadataDocumentLocation = location; + + return this; + } + + + /** + * Get the flag which indicates whether the location of the client's + * metadata document was used as a client ID. + * + *

+ * This can happen when the service supports OAuth Client ID Metadata Document. + * (cf. {@link Service#isClientIdMetadataDocumentSupported()}) + *

+ * + * @return + * {@code true} if the location of client's metadata document + * was used as a client ID. + * + * @since 4.29 + * @since Authlete 3.0.22 + * + * @see + * OAuth Client ID Metadata Document + */ + public boolean isMetadataDocumentUsed() + { + return metadataDocumentUsed; + } + + + /** + * Set the flag which indicates whether the location of the client's + * metadata document was used as a client ID. * *

- * When {@link #isClientIdAliasUsed()} returns {@code true}, this method - * returns the same value as {@link #getClientIdAlias()} does. Otherwise, - * if {@link #isClientEntityIdUsed()} returns {@code true}, this method - * returns the same value as {@link #getClientEntityId()}{@code .toString()} - * does. In other cases, this method returns the string representation of - * the value returned from {@link #getClientId()}. + * This can happen when the service supports OAuth Client ID Metadata Document. + * (cf. {@link Service#isClientIdMetadataDocumentSupported()}) *

* + * @param used + * {@code true} to indicate that the location of the client's + * metadata document was used as a client ID. + * + * @return + * {@code this} object. + * + * @since 4.29 + * @since Authlete 3.0.22 + * + * @see + * OAuth Client ID Metadata Document + */ + public DeviceAuthorizationResponse setMetadataDocumentUsed(boolean used) + { + this.metadataDocumentUsed = used; + + return this; + } + + + /** + * Get the client identifier used in the device authorization request. + * + *
    + *
  1. When {@link #isClientEntityIdUsed()} returns {@code true}, this + * method returns the same value as {@link #getClientIdAlias()} does. + * + *
  2. Otherwise, if {@link #isClientEntityIdUsed()} returns {@code true}, + * this method returns the same value as + * {@link #getClientEntityId()}{@code .toString()} does. + * + *
  3. Otherwise, if {@link #isMetadataDocumentUsed()} returns {@code true}, + * this method returns the same value as + * {@link #getMetadataDocumentLocation()}{@code .toString()} does. + * + *
  4. In other cases, this method returns the string representation of + * the value returned from {@link #getClientId()}. + *
+ * * @return * The client identifier used in the device authorization request. */ @@ -646,6 +811,10 @@ else if (clientEntityIdUsed) { return clientEntityId.toString(); } + else if (metadataDocumentUsed) + { + return metadataDocumentLocation.toString(); + } else { return String.valueOf(clientId); @@ -1536,4 +1705,75 @@ public DeviceAuthorizationResponse setClientAttributes(Pair[] attributes) return this; } + + + /** + * Get the expected attestation challenge value for Client Attestation PoP + * JWT, which should be used as the value of the + * {@code OAuth-Client-Attestation-Challenge} HTTP header. + * + *

+ * When this response parameter is not null, the implementation of the + * device authorization endpoint should add the + * {@code OAuth-Client-Attestation-Challenge} HTTP header in the response + * from the endpoint to the client application, using the value of this + * response parameter as the value of the HTTP header. + *

+ * + *
+     * OAuth-Client-Attestation-Challenge: (The value of this {@code attestationChallenge} response parameter)
+     * 
+ * + * @return + * The expected attestation challenge value for Client Attestation + * PoP JWT. + * + * @since 4.39 + * @since Authlete 3.0.28 + * + * @see + * OAuth 2.0 Attestation-Based Client Authentication + */ + public String getAttestationChallenge() + { + return attestationChallenge; + } + + + /** + * Set the expected attestation challenge value for Client Attestation PoP + * JWT, which should be used as the value of the + * {@code OAuth-Client-Attestation-Challenge} HTTP header. + * + *

+ * When this response parameter is not null, the implementation of the + * device authorization endpoint should add the + * {@code OAuth-Client-Attestation-Challenge} HTTP header in the response + * from the endpoint to the client application, using the value of this + * response parameter as the value of the HTTP header. + *

+ * + *
+     * OAuth-Client-Attestation-Challenge: (The value of this {@code attestationChallenge} response parameter)
+     * 
+ * + * @param challenge + * The expected attestation challenge value for Client Attestation + * PoP JWT. + * + * @return + * {@code this} object. + * + * @since 4.39 + * @since Authlete 3.0.28 + * + * @see + * OAuth 2.0 Attestation-Based Client Authentication + */ + public DeviceAuthorizationResponse setAttestationChallenge(String challenge) + { + this.attestationChallenge = challenge; + + return this; + } } diff --git a/src/main/java/com/authlete/common/dto/DeviceVerificationResponse.java b/src/main/java/com/authlete/common/dto/DeviceVerificationResponse.java index 5487dda579..c8023dbaa2 100644 --- a/src/main/java/com/authlete/common/dto/DeviceVerificationResponse.java +++ b/src/main/java/com/authlete/common/dto/DeviceVerificationResponse.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2019-2023 Authlete, Inc. + * Copyright (C) 2019-2025 Authlete, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of @@ -76,7 +76,7 @@ */ public class DeviceVerificationResponse extends ApiResponse { - private static final long serialVersionUID = 9L; + private static final long serialVersionUID = 10L; /** @@ -148,6 +148,22 @@ public enum Action */ private boolean clientEntityIdUsed; + /** + * The location of the client's metadata document. + * + * @since 4.29 + * @since Authlete 3.0.22 + */ + private URI metadataDocumentLocation; + + /** + * Whether the client's metadata document was used. + * + * @since 4.29 + * @since Authlete 3.0.22 + */ + private boolean metadataDocumentUsed; + /** * @since Authlete 2.0.0 */ @@ -453,18 +469,129 @@ public DeviceVerificationResponse setClientEntityIdUsed(boolean used) /** - * Get the client identifier used in the device authorization request for - * the user code. + * Get the location of the client's metadata document. This property + * holds a non-null value only when {@link #isMetadataDocumentUsed()} + * returns {@code true}. + * + * @return + * The location of the client's metadata document. + * + * @since 4.29 + * @since Authlete 3.0.22 + * + * @see + * OAuth Client ID Metadata Document + */ + public URI getMetadataDocumentLocation() + { + return metadataDocumentLocation; + } + + + /** + * Set the location of the client's metadata document. This property + * should hold a non-null value only when {@link #isMetadataDocumentUsed()} + * returns {@code true}. + * + * @param location + * The location of the client's metadata document. + * + * @return + * {@code this} object. + * + * @since 4.29 + * @since Authlete 3.0.22 + * + * @see + * OAuth Client ID Metadata Document + */ + public DeviceVerificationResponse setMetadataDocumentLocation(URI location) + { + this.metadataDocumentLocation = location; + + return this; + } + + + /** + * Get the flag which indicates whether the location of the client's + * metadata document was used as a client ID. + * + *

+ * This can happen when the service supports OAuth Client ID Metadata Document. + * (cf. {@link Service#isClientIdMetadataDocumentSupported()}) + *

+ * + * @return + * {@code true} if the location of client's metadata document + * was used as a client ID. + * + * @since 4.29 + * @since Authlete 3.0.22 + * + * @see + * OAuth Client ID Metadata Document + */ + public boolean isMetadataDocumentUsed() + { + return metadataDocumentUsed; + } + + + /** + * Set the flag which indicates whether the location of the client's + * metadata document was used as a client ID. * *

- * When {@link #isClientIdAliasUsed()} returns {@code true}, this method - * returns the same value as {@link #getClientIdAlias()} does. Otherwise, - * if {@link #isClientEntityIdUsed()} returns {@code true}, this method - * returns the same value as {@link #getClientEntityId()}{@code .toString()} - * does. In other cases, this method returns the string representation of - * the value returned from {@link #getClientId()}. + * This can happen when the service supports OAuth Client ID Metadata Document. + * (cf. {@link Service#isClientIdMetadataDocumentSupported()}) *

* + * @param used + * {@code true} to indicate that the location of the client's + * metadata document was used as a client ID. + * + * @return + * {@code this} object. + * + * @since 4.29 + * @since Authlete 3.0.22 + * + * @see + * OAuth Client ID Metadata Document + */ + public DeviceVerificationResponse setMetadataDocumentUsed(boolean used) + { + this.metadataDocumentUsed = used; + + return this; + } + + + /** + * Get the client identifier used in the device authorization request for + * the user code. + * + *
    + *
  1. When {@link #isClientEntityIdUsed()} returns {@code true}, this + * method returns the same value as {@link #getClientIdAlias()} does. + * + *
  2. Otherwise, if {@link #isClientEntityIdUsed()} returns {@code true}, + * this method returns the same value as + * {@link #getClientEntityId()}{@code .toString()} does. + * + *
  3. Otherwise, if {@link #isMetadataDocumentUsed()} returns {@code true}, + * this method returns the same value as + * {@link #getMetadataDocumentLocation()}{@code .toString()} does. + * + *
  4. In other cases, this method returns the string representation of + * the value returned from {@link #getClientId()}. + *
+ * * @return * The client identifier used in the device authorization request * for the user code. @@ -479,6 +606,10 @@ else if (clientEntityIdUsed) { return clientEntityId.toString(); } + else if (metadataDocumentUsed) + { + return metadataDocumentLocation.toString(); + } else { return String.valueOf(clientId); diff --git a/src/main/java/com/authlete/common/dto/IntrospectionResponse.java b/src/main/java/com/authlete/common/dto/IntrospectionResponse.java index e199151417..fa19e6f426 100644 --- a/src/main/java/com/authlete/common/dto/IntrospectionResponse.java +++ b/src/main/java/com/authlete/common/dto/IntrospectionResponse.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014-2024 Authlete, Inc. + * Copyright (C) 2014-2025 Authlete, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -268,7 +268,7 @@ */ public class IntrospectionResponse extends ApiResponse { - private static final long serialVersionUID = 23L; + private static final long serialVersionUID = 24L; /** @@ -442,6 +442,25 @@ public enum Action private boolean clientEntityIdUsed; + /** + * The location of the client's metadata document. + * + * @since 4.29 + * @since Authlete 3.0.22 + */ + private URI metadataDocumentLocation; + + + /** + * Flag which indicates whether the location of the client's metadata + * document was used when the request for the access token was made. + * + * @since 4.29 + * @since Authlete 3.0.22 + */ + private boolean metadataDocumentUsed; + + /** * Confirmation hash for MTLS-bound access tokens. Currently only the S256 * type is supported and is assumed. @@ -1091,6 +1110,100 @@ public void setClientEntityIdUsed(boolean used) } + /** + * Get the location of the client's metadata document. This property + * holds a non-null value only when {@link #isMetadataDocumentUsed()} + * returns {@code true}. + * + * @return + * The location of the client's metadata document. + * + * @since 4.29 + * @since Authlete 3.0.22 + * + * @see + * OAuth Client ID Metadata Document + */ + public URI getMetadataDocumentLocation() + { + return metadataDocumentLocation; + } + + + /** + * Set the location of the client's metadata document. This property + * should hold a non-null value only when {@link #isMetadataDocumentUsed()} + * returns {@code true}. + * + * @param location + * The location of the client's metadata document. + * + * @since 4.29 + * @since Authlete 3.0.22 + * + * @see + * OAuth Client ID Metadata Document + */ + public void setMetadataDocumentLocation(URI location) + { + this.metadataDocumentLocation = location; + } + + + /** + * Get the flag which indicates whether the location of the client's + * metadata document was used as a client ID. + * + *

+ * This can happen when the service supports OAuth Client ID Metadata Document. + * (cf. {@link Service#isClientIdMetadataDocumentSupported()}) + *

+ * + * @return + * {@code true} if the location of client's metadata document + * was used as a client ID. + * + * @since 4.29 + * @since Authlete 3.0.22 + * + * @see + * OAuth Client ID Metadata Document + */ + public boolean isMetadataDocumentUsed() + { + return metadataDocumentUsed; + } + + + /** + * Set the flag which indicates whether the location of the client's + * metadata document was used as a client ID. + * + *

+ * This can happen when the service supports OAuth Client ID Metadata Document. + * (cf. {@link Service#isClientIdMetadataDocumentSupported()}) + *

+ * + * @param used + * {@code true} to indicate that the location of the client's + * metadata document was used as a client ID. + * + * @since 4.29 + * @since Authlete 3.0.22 + * + * @see + * OAuth Client ID Metadata Document + */ + public void setMetadataDocumentUsed(boolean used) + { + this.metadataDocumentUsed = used; + } + + /** * Get the client certificate thumbprint used to validate the access token. * diff --git a/src/main/java/com/authlete/common/dto/PushedAuthReqResponse.java b/src/main/java/com/authlete/common/dto/PushedAuthReqResponse.java index 467f1e7382..33b6997561 100644 --- a/src/main/java/com/authlete/common/dto/PushedAuthReqResponse.java +++ b/src/main/java/com/authlete/common/dto/PushedAuthReqResponse.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2019-2023 Authlete, Inc. + * Copyright (C) 2019-2026 Authlete, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -262,11 +262,39 @@ * 2.0 Demonstrating Proof of Possession (DPoP) for details. *

* + *
+ *
+ *

Attestation Challenge (Authlete 3.0.28 onwards)

+ * + *

+ * Since version 3.0.28, Authlete recognizes the {@code challenge} claim in + * Client Attestation PoP JWTs. If the {@code challenge} claim is required + * (= if the service's challenge endpoint is configured), the Authlete API + * checks whether the {@code challenge} claim in the presented Client + * Attestation PoP JWT is identical to the expected value. + *

+ * + *

+ * If the {@code attestationChallenge} response parameter from the API is not + * null, its value is the expected attestation challenge value for Client + * Attestation PoP JWT. The expected value needs to be conveyed to the client + * application as the value of the {@code OAuth-Client-Attestation-Challenge} + * HTTP header. + *

+ * + *
OAuth-Client-Attestation-Challenge: (The value returned from {@link #getAttestationChallenge()})
+ * + *

+ * See OAuth 2.0 Attestation-Based Client Authentication for details. + *

+ * * @since 2.51 */ public class PushedAuthReqResponse extends ApiResponse { - private static final long serialVersionUID = 2L; + private static final long serialVersionUID = 3L; /** @@ -353,6 +381,16 @@ public static enum Action */ private String dpopNonce; + /** + * The expected attestation challenge value for Client Attestation PoP JWT, + * which should be used as the value of the + * {@code OAuth-Client-Attestation-Challenge} HTTP header. + * + * @since 4.39 + * @since Authlete 3.0.28 + */ + private String attestationChallenge; + /** * Get the next action that the authorization server should take. @@ -549,6 +587,77 @@ public PushedAuthReqResponse setDpopNonce(String dpopNonce) } + /** + * Get the expected attestation challenge value for Client Attestation PoP + * JWT, which should be used as the value of the + * {@code OAuth-Client-Attestation-Challenge} HTTP header. + * + *

+ * When this response parameter is not null, the implementation of the + * pushed authorization request endpoint should add the + * {@code OAuth-Client-Attestation-Challenge} HTTP header in the response + * from the endpoint to the client application, using the value of this + * response parameter as the value of the HTTP header. + *

+ * + *
+     * OAuth-Client-Attestation-Challenge: (The value of this {@code attestationChallenge} response parameter)
+     * 
+ * + * @return + * The expected attestation challenge value for Client Attestation + * PoP JWT. + * + * @since 4.39 + * @since Authlete 3.0.28 + * + * @see + * OAuth 2.0 Attestation-Based Client Authentication + */ + public String getAttestationChallenge() + { + return attestationChallenge; + } + + + /** + * Set the expected attestation challenge value for Client Attestation PoP + * JWT, which should be used as the value of the + * {@code OAuth-Client-Attestation-Challenge} HTTP header. + * + *

+ * When this response parameter is not null, the implementation of the + * pushed authorization request endpoint should add the + * {@code OAuth-Client-Attestation-Challenge} HTTP header in the response + * from the endpoint to the client application, using the value of this + * response parameter as the value of the HTTP header. + *

+ * + *
+     * OAuth-Client-Attestation-Challenge: (The value of this {@code attestationChallenge} response parameter)
+     * 
+ * + * @param challenge + * The expected attestation challenge value for Client Attestation + * PoP JWT. + * + * @return + * {@code this} object. + * + * @since 4.39 + * @since Authlete 3.0.28 + * + * @see + * OAuth 2.0 Attestation-Based Client Authentication + */ + public PushedAuthReqResponse setAttestationChallenge(String challenge) + { + this.attestationChallenge = challenge; + + return this; + } + + /** * Get the summary of this instance. */ diff --git a/src/main/java/com/authlete/common/dto/RevocationResponse.java b/src/main/java/com/authlete/common/dto/RevocationResponse.java index 9d1e963050..b97867cf38 100644 --- a/src/main/java/com/authlete/common/dto/RevocationResponse.java +++ b/src/main/java/com/authlete/common/dto/RevocationResponse.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015 Authlete, Inc. + * Copyright (C) 2015-2026 Authlete, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -199,6 +199,34 @@ * * * + *
+ *
+ *

Attestation Challenge (Authlete 3.0.28 onwards)

+ * + *

+ * Since version 3.0.28, Authlete recognizes the {@code challenge} claim in + * Client Attestation PoP JWTs. If the {@code challenge} claim is required + * (= if the service's challenge endpoint is configured), the Authlete API + * checks whether the {@code challenge} claim in the presented Client + * Attestation PoP JWT is identical to the expected value. + *

+ * + *

+ * If the {@code attestationChallenge} response parameter from the API is not + * null, its value is the expected attestation challenge value for Client + * Attestation PoP JWT. The expected value needs to be conveyed to the client + * application as the value of the {@code OAuth-Client-Attestation-Challenge} + * HTTP header. + *

+ * + *
OAuth-Client-Attestation-Challenge: (The value returned from {@link #getAttestationChallenge()})
+ * + *

+ * See OAuth 2.0 Attestation-Based Client Authentication for details. + *

+ * * @see RFC 7009, OAuth 2.0 Token Revocation * * @author Takahiko Kawasaki @@ -208,7 +236,7 @@ */ public class RevocationResponse extends ApiResponse { - private static final long serialVersionUID = 1L; + private static final long serialVersionUID = 2L; /** @@ -250,6 +278,16 @@ public enum Action private Action action; private String responseContent; + /** + * The expected attestation challenge value for Client Attestation PoP JWT, + * which should be used as the value of the + * {@code OAuth-Client-Attestation-Challenge} HTTP header. + * + * @since 4.39 + * @since Authlete 3.0.28 + */ + private String attestationChallenge; + /** * Get the next action that the service implementation should take. @@ -289,6 +327,70 @@ public void setResponseContent(String responseContent) } + /** + * Get the expected attestation challenge value for Client Attestation PoP + * JWT, which should be used as the value of the + * {@code OAuth-Client-Attestation-Challenge} HTTP header. + * + *

+ * When this response parameter is not null, the implementation of the + * revocation endpoint should add the {@code OAuth-Client-Attestation-Challenge} + * HTTP header in the response from the endpoint to the client application, + * using the value of this response parameter as the value of the HTTP header. + *

+ * + *
+     * OAuth-Client-Attestation-Challenge: (The value of this {@code attestationChallenge} response parameter)
+     * 
+ * + * @return + * The expected attestation challenge value for Client Attestation + * PoP JWT. + * + * @since 4.39 + * @since Authlete 3.0.28 + * + * @see + * OAuth 2.0 Attestation-Based Client Authentication + */ + public String getAttestationChallenge() + { + return attestationChallenge; + } + + + /** + * Set the expected attestation challenge value for Client Attestation PoP + * JWT, which should be used as the value of the + * {@code OAuth-Client-Attestation-Challenge} HTTP header. + * + *

+ * When this response parameter is not null, the implementation of the + * revocation endpoint should add the {@code OAuth-Client-Attestation-Challenge} + * HTTP header in the response from the endpoint to the client application, + * using the value of this response parameter as the value of the HTTP header. + *

+ * + *
+     * OAuth-Client-Attestation-Challenge: (The value of this {@code attestationChallenge} response parameter)
+     * 
+ * + * @param challenge + * The expected attestation challenge value for Client Attestation + * PoP JWT. + * + * @since 4.39 + * @since Authlete 3.0.28 + * + * @see + * OAuth 2.0 Attestation-Based Client Authentication + */ + public void setAttestationChallenge(String challenge) + { + this.attestationChallenge = challenge; + } + + /** * Get the summary of this instance. */ diff --git a/src/main/java/com/authlete/common/dto/Service.java b/src/main/java/com/authlete/common/dto/Service.java index 3ffdf39496..5837ec016a 100644 --- a/src/main/java/com/authlete/common/dto/Service.java +++ b/src/main/java/com/authlete/common/dto/Service.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014-2025 Authlete, Inc. + * Copyright (C) 2014-2026 Authlete, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -330,7 +330,7 @@ */ public class Service implements Serializable { - private static final long serialVersionUID = 83L; + private static final long serialVersionUID = 93L; /* @@ -738,6 +738,25 @@ public class Service implements Serializable private long idTokenDuration; + /** + * Duration of authorization code in seconds. + * + *

+ * A value of {@code 0} means that this property is not set and that the + * implementation falls back to the default configuration on the server. + *

+ * + *

+ * For FAPI 2.0, the lifetime of authorization codes is capped to 60 + * seconds. See OpenID FAPI 2.0 Security Profile for details. + *

+ * + * @since 4.40 + * @since Authlete 3.0.29 + */ + private long authorizationCodeDuration; + + /** * Duration of authorization response JWTs. * @@ -1883,6 +1902,207 @@ public class Service implements Serializable private boolean nativeSsoSupported; + /** + * The version of the OpenID for Verifiable Credential Issuance specification + * to support. + * + * @since 4.25 + * @since Authlete 3.0.22 + */ + private String oid4vciVersion; + + + /** + * Whether to support OAuth Client ID Metadata Document. + * + * @since 4.29 + * @since Authlete 3.0.22 + */ + private boolean clientIdMetadataDocumentSupported; + + + /** + * Whether to enable the allowlist for client IDs in the CIMD context. + * + * @since 4.31 + * @since Authlete 3.0.22 + */ + private boolean cimdAllowlistEnabled; + + + /** + * The allowlist for client IDs in the CIMD context. + * + * @since 4.31 + * @since Authlete 3.0.22 + */ + private String[] cimdAllowlist; + + + /** + * Whether to always retrieve client metadata in the CIMD context + * regardless of the cache's validity. + * + * @since 4.30 + * @since Authlete 3.0.22 + */ + private boolean cimdAlwaysRetrieved; + + + /** + * Whether to allow the {@code http} scheme in client IDs in the CIMD + * context. + * + * @since 4.30 + * @since Authlete 3.0.22 + */ + private boolean cimdHttpPermitted; + + + /** + * Whether to allow a query component in client IDs in the CIMD context. + * + * @since 4.30 + * @since Authlete 3.0.22 + */ + private boolean cimdQueryPermitted; + + + /** + * Whether to apply the metadata policy to client metadata that is obtained + * through the CIMD mechanism. + * + * @since 4.33 + * @since Authlete 3.0.23 + */ + private boolean cimdMetadataPolicyEnabled; + + + /** + * The metadata policy applied to client metadata that is obtained through + * the CIMD mechanism. + * + * @since 4.33 + * @since Authlete 3.0.23 + */ + private String cimdMetadataPolicy; + + + /** + * Whether to prohibit client ID aliases that start with {@code https://} + * or {@code http://}. + * + * @since 4.32 + * @since Authlete 3.0.22 + */ + private boolean httpAliasProhibited; + + + /** + * The challenge endpoint defined in + * OAuth 2.0 Attestation-Based Client Authentication. + * + * @since 4.39 + * @since Authlete 3.0.28 + */ + private URI challengeEndpoint; + + + /** + * The time window of attestation challenges in seconds. + * + * @since 4.39 + * @since Authlete 3.0.28 + */ + private long attestationChallengeTimeWindow; + + + /** + * The HAIP version to comply with. + * + * @since 4.41 + * @since Authlete 3.0.29 + */ + private String haipVersion; + + + /** + * The trusted root certificates for Client Attestation JWT. + * + * @since 4.42 + * @since Authlete 3.0.31 + */ + private String[] clientAttesterRoots; + + + /** + * Whether to enable the trusted root certificates for Client Attestation JWT. + * + * @since 4.42 + * @since Authlete 3.0.31 + */ + private boolean clientAttesterRootsEnabled; + + + /** + * Whether to refer to only the trusted root certificates for Client + * Attestation JWT, without referring to system-installed ones. + * + * @since 4.42 + * @since Authlete 3.0.31 + */ + private boolean clientAttesterRootsOnly; + + + /** + * The trusted root certificates for Key Attestation JWT. + * + * @since 4.42 + * @since Authlete 3.0.31 + */ + private String[] keyAttesterRoots; + + + /** + * Whether to enable the trusted root certificates for Key Attestation JWT. + * + * @since 4.42 + * @since Authlete 3.0.31 + */ + private boolean keyAttesterRootsEnabled; + + + /** + * Whether to refer to only the trusted root certificates for Key + * Attestation JWT, without referring to system-installed ones. + * + * @since 4.42 + * @since Authlete 3.0.31 + */ + private boolean keyAttesterRootsOnly; + + + /** + * Whether to support the OpenID Connect Back-Channel Logout 1.0 + * specification. + * + * @since 4.44 + * @since Authlete 3.0.32 + */ + private boolean backchannelLogoutSupported; + + + /** + * Whether to support the {@code sid} claim in Logout Tokens. + * + * @since 4.44 + * @since Authlete 3.0.32 + */ + private boolean backchannelLogoutSessionSupported; + + /** * Get the service number. * @@ -2971,6 +3191,39 @@ public Service setIdTokenDuration(long duration) } + /** + * Get the duration of authorization code in seconds. + * + * @return + * The duration of authorization code in seconds. + * + * @since 3.0.29 + */ + public long getAuthorizationCodeDuration() + { + return authorizationCodeDuration; + } + + + /** + * Set the duration of authorization code in seconds. + * + * @param duration + * The duration of authorization code in seconds. + * + * @return + * {@code this} object. + * + * @since 3.0.29 + */ + public Service setAuthorizationCodeDuration(long duration) + { + this.authorizationCodeDuration = duration; + + return this; + } + + /** * Get the duration of authorization response JWTs. * @@ -4234,6 +4487,8 @@ public Service setErrorUriOmitted(boolean omitted) * {@code false} if the feature is disabled. * * @since 2.2 + * + * @see #isHttpAliasProhibited() */ public boolean isClientIdAliasEnabled() { @@ -4260,6 +4515,8 @@ public boolean isClientIdAliasEnabled() * {@code this} object. * * @since 2.2 + * + * @see #isHttpAliasProhibited() */ public Service setClientIdAliasEnabled(boolean enabled) { @@ -11092,13 +11349,37 @@ public Service setPreAuthorizedGrantAnonymousAccessSupported(boolean supported) /** - * Get the duration of {@code c_nonce} in seconds. + * Get the duration (in 1.0-ID1) or validity period (time window) + * (in 1.0-Final) of {@code c_nonce} in seconds. + * + *

+ * The method of generating {@code c_nonce} differs between OID4VCI 1.0-ID1 + * and OID4VCI 1.0-Final. + *

* *

- * {@code c_nonce} is issued from the token endpoint of an authorization - * server in the pre-authorized code flow, and from the credential endpoint - * and the batch credential endpoint of a credential issuer. This property - * is used as the lifetime of the {@code c_nonce}. + * In 1.0-ID1, the {@code c_nonce} is issued from the token endpoint or the + * credential endpoint. In Authlete's implementation, the {@code c_nonce} + * for 1.0-ID1 is associated with an access token. + *

+ * + *

+ * For {@code c_nonce} in 1.0-ID1, this {@code cnonceDuration} property is + * used as the lifetime of {@code c_nonce}. + *

+ * + *

+ * In contrast, in 1.0-Final, the {@code c_nonce} is issued from the nonce + * endpoint. Requests to this endpoint do not include an access token, so + * the server cannot determine who the requester is. As a result, the + * {@code c_nonce} cannot be generated per access token or per client + * application. Consequently, the same {@code c_nonce} value is returned to + * anyone who accesses the endpoint within the same time window. + *

+ * + *

+ * For {@code c_nonce} in 1.0-Final, this {@code cnonceDuration} property + * specifies the validity period (time window) in seconds. *

* *

@@ -11114,12 +11395,14 @@ public Service setPreAuthorizedGrantAnonymousAccessSupported(boolean supported) *

* *

+ * NOTE: * The {@code getCNonceDuration()} method added by the version 3.63 has * been renamed to {@code getCnonceDuration()} by the version 3.90. *

* * @return - * The duration of {@code c_nonce} in seconds. + * The duration of {@code c_nonce} in seconds in OID4VCI 1.0-ID1, or + * the validity period (time window) in seconds in OID4VCI 1.0-Final. * * @since 3.90 * @since Authlete 3.0 @@ -11134,13 +11417,37 @@ public long getCnonceDuration() /** - * Set the duration of {@code c_nonce} in seconds. + * Set the duration (in 1.0-ID1) or validity period (time window) + * (in 1.0-Final) of {@code c_nonce} in seconds. + * + *

+ * The method of generating {@code c_nonce} differs between OID4VCI 1.0-ID1 + * and OID4VCI 1.0-Final. + *

+ * + *

+ * In 1.0-ID1, the {@code c_nonce} is issued from the token endpoint or the + * credential endpoint. In Authlete's implementation, the {@code c_nonce} + * for 1.0-ID1 is associated with an access token. + *

+ * + *

+ * For {@code c_nonce} in 1.0-ID1, this {@code cnonceDuration} property is + * used as the lifetime of {@code c_nonce}. + *

+ * + *

+ * In contrast, in 1.0-Final, the {@code c_nonce} is issued from the nonce + * endpoint. Requests to this endpoint do not include an access token, so + * the server cannot determine who the requester is. As a result, the + * {@code c_nonce} cannot be generated per access token or per client + * application. Consequently, the same {@code c_nonce} value is returned to + * anyone who accesses the endpoint within the same time window. + *

* *

- * {@code c_nonce} is issued from the token endpoint of an authorization - * server in the pre-authorized code flow, and from the credential endpoint - * and the batch credential endpoint of a credential issuer. This property - * is used as the lifetime of the {@code c_nonce}. + * For {@code c_nonce} in 1.0-Final, this {@code cnonceDuration} property + * specifies the validity period (time window) in seconds. *

* *

@@ -11156,12 +11463,14 @@ public long getCnonceDuration() *

* *

+ * NOTE: * The {@code setCNonceDuration(long)} method added by the version 3.63 has * been renamed to {@code setCnonceDuration(long)} by the version 3.90. *

* * @param duration - * The duration of {@code c_nonce} in seconds. + * The duration of {@code c_nonce} in seconds in OID4VCI 1.0-ID1, or + * the validity period (time window) in seconds in OID4VCI 1.0-Final. * * @return * {@code this} object. @@ -11962,4 +12271,1698 @@ public Service setNativeSsoSupported(boolean supported) return this; } + + + /** + * Get the version of the OpenID for Verifiable Credential Issuance + * specification to support. + * + *

+ * Valid values are as follows: + *

+ * + *
+ * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
ValueSpecification
null + * + * OpenID for Verifiable Credential Issuance 1.0, Implementer's Draft 1 + *
{@code "1.0-ID1"}
{@code "1.0"} + * + * OpenID for Verifiable Credential Issuance 1.0, Final + *
{@code "1.0-Final"}
+ *
+ * + * @return + * The version of the OpenID for Verifiable Credential Issuance + * specification to support. + * + * @since 4.25 + * @since Authlete 3.0.22 + * + * @see + * OpenID for Verifiable Credential Issuance + */ + public String getOid4vciVersion() + { + return oid4vciVersion; + } + + + /** + * Set the version of the OpenID for Verifiable Credential Issuance + * specification to support. + * + *

+ * Valid values are as follows: + *

+ * + *
+ * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
ValueSpecification
null + * + * OpenID for Verifiable Credential Issuance 1.0, Implementer's Draft 1 + *
{@code "1.0-ID1"}
{@code "1.0"} + * + * OpenID for Verifiable Credential Issuance 1.0, Final + *
{@code "1.0-Final"}
+ *
+ * + * @param version + * The version of the OpenID for Verifiable Credential Issuance + * specification to support. + * + * @return + * {@code this} object. + * + * @since 4.25 + * @since Authlete 3.0.22 + * + * @see + * OpenID for Verifiable Credential Issuance + */ + public Service setOid4vciVersion(String version) + { + this.oid4vciVersion = version; + + return this; + } + + + /** + * Get the flag to determine to support OAuth Client ID Metadata Document. + * + *

+ * This flag corresponds to the {@code client_id_metadata_document_supported} + * metadata parameter. + *

+ * + * @return + * {@code true} if OAuth Client ID Metadata Document is supported. + * + * @since 4.29 + * @since Authlete 3.0.22 + * + * @see + * OAuth Client ID Metadata Document + */ + public boolean isClientIdMetadataDocumentSupported() + { + return clientIdMetadataDocumentSupported; + } + + + /** + * Set the flag to determine to support OAuth Client ID Metadata Document. + * + *

+ * This flag corresponds to the {@code client_id_metadata_document_supported} + * metadata parameter. + *

+ * + * @param supported + * {@code true} to support OAuth Client ID Metadata Document. + * + * @return + * {@code this} object. + * + * @since 4.29 + * @since Authlete 3.0.22 + * + * @see + * OAuth Client ID Metadata Document + */ + public Service setClientIdMetadataDocumentSupported(boolean supported) + { + this.clientIdMetadataDocumentSupported = supported; + + return this; + } + + + /** + * Get the flag that indicates whether the allowlist for client IDs + * in the CIMD context is enabled or not. + * + *

+ * If the allowlist is enabled, the client ID in a request must match at + * least one entry in the allowlist to be considered a valid client ID + * in the CIMD context. + *

+ * + * @return + * {@code true} if the allowlist for client IDs in the CIMD + * context is enabled. + * + * @since 4.31 + * @since Authlete 3.0.22 + * + * @see + * OAuth Client ID Metadata Document + */ + public boolean isCimdAllowlistEnabled() + { + return cimdAllowlistEnabled; + } + + + /** + * Set the flag that indicates whether the allowlist for client IDs + * in the CIMD context is enabled or not. + * + *

+ * If the allowlist is enabled, the client ID in a request must match at + * least one entry in the allowlist to be considered a valid client ID + * in the CIMD context. + *

+ * + * @param enabled + * {@code true} to enable the allowlist for client IDs in the + * CIMD context. + * + * @return + * {@code this} object. + * + * @since 4.31 + * @since Authlete 3.0.22 + * + * @see + * OAuth Client ID Metadata Document + */ + public Service setCimdAllowlistEnabled(boolean enabled) + { + this.cimdAllowlistEnabled = enabled; + + return this; + } + + + /** + * Get the allowlist for client IDs in the CIMD context. + * + *

+ * When the allowlist feature is enabled (see {@link #isCimdAllowlistEnabled()}), + * the allowlist specified by this property is consulted to determine + * whether the client ID in a request is valid in the CIMD context. + * In this case, the client ID must match at least one entry in the + * allowlist. + *

+ * + *

+ * Each entry in the allowlist must be a valid URI. The comparison between + * an allowlist entry and a client ID is performed as follows: + *

+ * + *
    + *
  1. [scheme] Simple string comparison. + *
  2. [authority] Simple string comparison. + *
  3. [path] The client ID's path must contain all path segments of + * the allowlist entry in the same order. + *
  4. [query] Simple string comparison, but only if the allowlist + * entry has a query component. + *
+ * + *

+ * For example, if the allowlist contains "{@code https://example.com/a/b}", + * then "{@code https://example.com/a/b/c}" is considered valid, but + * "{@code https://example.com/a}" is not. + *

+ * + * @return + * The allowlist for client IDs in the CIMD context. + * + * @since 4.31 + * @since Authlete 3.0.22 + * + * @see + * OAuth Client ID Metadata Document + */ + public String[] getCimdAllowlist() + { + return cimdAllowlist; + } + + + /** + * Set the allowlist for client IDs in the CIMD context. + * + *

+ * When the allowlist feature is enabled (see {@link #isCimdAllowlistEnabled()}), + * the allowlist specified by this property is consulted to determine + * whether the client ID in a request is valid in the CIMD context. + * In this case, the client ID must match at least one entry in the + * allowlist. + *

+ * + *

+ * Each entry in the allowlist must be a valid URI. The comparison between + * an allowlist entry and a client ID is performed as follows: + *

+ * + *
    + *
  1. [scheme] Simple string comparison. + *
  2. [authority] Simple string comparison. + *
  3. [path] The client ID's path must contain all path segments of + * the allowlist entry in the same order. + *
  4. [query] Simple string comparison, but only if the allowlist + * entry has a query component. + *
+ * + *

+ * For example, if the allowlist contains "{@code https://example.com/a/b}", + * then "{@code https://example.com/a/b/c}" is considered valid, but + * "{@code https://example.com/a}" is not. + *

+ * + * @param allowlist + * The allowlist for client IDs in the CIMD context. + * + * @return + * {@code this} object. + * + * @since 4.31 + * @since Authlete 3.0.22 + * + * @see + * OAuth Client ID Metadata Document + */ + public Service setCimdAllowlist(String[] allowlist) + { + this.cimdAllowlist = allowlist; + + return this; + } + + + /** + * Get the flag that indicates whether Authlete should always fetch the + * client metadata from the location specified by the client ID (when + * CIMD applies), regardless of whether a cached copy already exists + * and has not yet expired. + * + *

+ * Under normal circumstances, client metadata retrieved from the location + * referenced by the client ID is stored in the database with an expiration + * time calculated using HTTP caching mechanisms (see RFC 9111 HTTP Caching). + * Until that expiration time is reached, Authlete does not attempt to + * retrieve the client metadata again. + *

+ * + *

+ * When this flag is set to {@code true}, Authlete retrieves the client + * metadata regardless of the cache's validity. + *

+ * + *

+ * Some Authlete APIs accept a {@code cimdOptions.alwaysRetrieved} request + * parameter (see {@link CimdOptions}). If the parameter is provided and + * its value is {@code true}, it takes precedence over this service + * configuration. + *

+ * + *

+ * This flag is effective only when the service supports CIMD (see {@link + * Service#isClientIdMetadataDocumentSupported()}) and CIMD is actually + * used to resolve client metadata. For example, if the client ID in a + * request does not appear to be a valid URI, CIMD will not be used even + * if the service is configured to support it. In such cases, this flag + * has no effect. + *

+ * + *

+ * Client metadata retrieval is performed only in the initiating request + * of an authorization flow, and not in any subsequent requests. For + * example, in the authorization code flow, metadata may be retrieved + * during the authorization request, but not during the subsequent token + * request. In contrast, in the client credentials flow, metadata retrieval + * may occur because the token request itself is the initiating request + * in the flow. + *

+ * + * @return + * {@code true} if Authlete attempts to retrieve client metadata + * regardless of the cache's validity. + * + * @since 4.30 + * @since Authlete 3.0.22 + * + * @see + * OAuth Client ID Metadata Document + */ + public boolean isCimdAlwaysRetrieved() + { + return cimdAlwaysRetrieved; + } + + + /** + * Set the flag that indicates whether Authlete should always fetch the + * client metadata from the location specified by the client ID (when + * CIMD applies), regardless of whether a cached copy already exists + * and has not yet expired. + * + *

+ * Under normal circumstances, client metadata retrieved from the location + * referenced by the client ID is stored in the database with an expiration + * time calculated using HTTP caching mechanisms (see RFC 9111 HTTP Caching). + * Until that expiration time is reached, Authlete does not attempt to + * retrieve the client metadata again. + *

+ * + *

+ * When this flag is set to {@code true}, Authlete retrieves the client + * metadata regardless of the cache's validity. + *

+ * + *

+ * Some Authlete APIs accept a {@code cimdOptions.alwaysRetrieved} request + * parameter (see {@link CimdOptions}). If the parameter is provided and + * its value is {@code true}, it takes precedence over this service + * configuration. + *

+ * + *

+ * This flag is effective only when the service supports CIMD (see {@link + * Service#isClientIdMetadataDocumentSupported()}) and CIMD is actually + * used to resolve client metadata. For example, if the client ID in a + * request does not appear to be a valid URI, CIMD will not be used even + * if the service is configured to support it. In such cases, this flag + * has no effect. + *

+ * + *

+ * Client metadata retrieval is performed only in the initiating request + * of an authorization flow, and not in any subsequent requests. For + * example, in the authorization code flow, metadata may be retrieved + * during the authorization request, but not during the subsequent token + * request. In contrast, in the client credentials flow, metadata retrieval + * may occur because the token request itself is the initiating request + * in the flow. + *

+ * + * @param always + * {@code true} to instruct Authlete to retrieve client metadata + * regardless of the cache's validity. + * + * @return + * {@code this} object. + * + * @since 4.30 + * @since Authlete 3.0.22 + * + * @see + * OAuth Client ID Metadata Document + */ + public Service setCimdAlwaysRetrieved(boolean always) + { + this.cimdAlwaysRetrieved = always; + + return this; + } + + + /** + * Get the flag that indicates whether the {@code http} scheme in the client + * ID is permitted (when CIMD applies). + * + *

+ * The specification requires the {@code https} scheme, but if this flag is + * set to {@code true}, Authlete also allows the {@code http} scheme. The + * main purpose of this option is to make development easier for developers + * who run CIMD-enabled servers and a web server publishing client metadata + * on their local machines without TLS. + *

+ * + *

+ * Given this purpose, it is not recommended to enable this option in + * production environments unless an allowlist is used (see {@link + * Service#isCimdAllowlistEnabled()}). + *

+ * + *

+ * Some Authlete APIs accept a {@code cimdOptions.httpPermitted} request + * parameter (see {@link CimdOptions}). If the parameter is provided and + * its value is {@code true}, it takes precedence over this service + * configuration. + *

+ * + * @return + * {@code true} if the {@code http} scheme in the client ID is + * permitted. + * + * @since 4.30 + * @since Authlete 3.0.22 + * + * @see + * OAuth Client ID Metadata Document + */ + public boolean isCimdHttpPermitted() + { + return cimdHttpPermitted; + } + + + /** + * Set the flag that indicates whether the {@code http} scheme in the client + * ID is permitted (when CIMD applies). + * + *

+ * The specification requires the {@code https} scheme, but if this flag is + * set to {@code true}, Authlete also allows the {@code http} scheme. The + * main purpose of this option is to make development easier for developers + * who run CIMD-enabled servers and a web server publishing client metadata + * on their local machines without TLS. + *

+ * + *

+ * Given this purpose, it is not recommended to enable this option in + * production environments unless an allowlist is used (see {@link + * Service#isCimdAllowlistEnabled()}). + *

+ * + *

+ * Some Authlete APIs accept a {@code cimdOptions.httpPermitted} request + * parameter (see {@link CimdOptions}). If the parameter is provided and + * its value is {@code true}, it takes precedence over this service + * configuration. + *

+ * + * @param permitted + * {@code true} to permit the {@code http} scheme in the client ID. + * + * @return + * {@code this} object. + * + * @since 4.30 + * @since Authlete 3.0.22 + * + * @see + * OAuth Client ID Metadata Document + */ + public Service setCimdHttpPermitted(boolean permitted) + { + this.cimdHttpPermitted = permitted; + + return this; + } + + + /** + * Get the flag that indicates whether a query component in the client ID + * is permitted (when CIMD applies). + * + *

+ * Although the specification states that a client ID "SHOULD NOT + * include a query string component," it does technically allow it. + * However, query components are prone to misuse. Therefore, Authlete does + * not allow them by default. Setting this flag to {@code true} relaxes + * that restriction. + *

+ * + *

+ * Some Authlete APIs accept a {@code cimdOptions.queryPermitted} request + * parameter (see {@link CimdOptions}). If the parameter is provided and + * its value is {@code true}, it takes precedence over this service + * configuration. + *

+ * + * @return + * {@code true} if a query component in the client ID is permitted. + * + * @since 4.30 + * @since Authlete 3.0.22 + * + * @see + * OAuth Client ID Metadata Document + */ + public boolean isCimdQueryPermitted() + { + return cimdQueryPermitted; + } + + + /** + * Set the flag that indicates whether a query component in the client ID + * is permitted (when CIMD applies). + * + *

+ * Although the specification states that a client ID "SHOULD NOT + * include a query string component," it does technically allow it. + * However, query components are prone to misuse. Therefore, Authlete does + * not allow them by default. Setting this flag to {@code true} relaxes + * that restriction. + *

+ * + *

+ * Some Authlete APIs accept a {@code cimdOptions.queryPermitted} request + * parameter (see {@link CimdOptions}). If the parameter is provided and + * its value is {@code true}, it takes precedence over this service + * configuration. + *

+ * + * @param permitted + * {@code true} to permit a query component in the client ID. + * + * @return + * {@code this} object. + * + * @since 4.30 + * @since Authlete 3.0.22 + * + * @see + * OAuth Client ID Metadata Document + */ + public Service setCimdQueryPermitted(boolean permitted) + { + this.cimdQueryPermitted = permitted; + + return this; + } + + + /** + * Get the flag that indicates whether to apply the metadata policy to + * client metadata that is obtained through the CIMD mechanism. + * + *

+ * If this flag is set to {@code true}, the metadata policy specified by + * the {@code cimdMetadataPolicy} property, if available, is applied to + * the client metadata that is obtained through the CIMD mechanism. + *

+ * + * @return + * {@code true} if the metadata policy is applied to client + * metadata that is obtained through the CIMD mechanism. + * + * @since 4.33 + * @since Authlete 3.0.23 + * + * @see + * OAuth Client ID Metadata Document + */ + public boolean isCimdMetadataPolicyEnabled() + { + return cimdMetadataPolicyEnabled; + } + + + /** + * Set the flag that indicates whether to apply the metadata policy to + * client metadata that is obtained through the CIMD mechanism. + * + *

+ * If this flag is set to {@code true}, the metadata policy specified by + * the {@code cimdMetadataPolicy} property, if available, is applied to + * the client metadata that is obtained through the CIMD mechanism. + *

+ * + * @param enabled + * {@code true} to apply the metadata policy to client metadata + * that is obtained through the CIMD mechanism. + * + * @return + * {@code this} object. + * + * @since 4.33 + * @since Authlete 3.0.23 + * + * @see + * OAuth Client ID Metadata Document + */ + public Service setCimdMetadataPolicyEnabled(boolean enabled) + { + this.cimdMetadataPolicyEnabled = enabled; + + return this; + } + + + /** + * Get the metadata policy applied to client metadata that is obtained + * through the CIMD mechanism. + * + *

+ * If the {@code cimdMetadataPolicyEnabled} property is set to {@code true}, + * the metadata policy specified by this {@code cimdMetadataPolicy} property + * is applied to client metadata that is obtained through the CIMD mechanism. + *

+ * + *

+ * The metadata policy must comply with the grammar defined in 6.1. Metadata Policy of the OpenID Federation + * 1.0 specification. Below is an example of metadata policy: + *

+ * + *
+     * {
+     *   "grant_types": {
+     *     "default": [
+     *       "authorization_code"
+     *     ],
+     *     "subset_of": [
+     *       "authorization_code",
+     *       "refresh_token"
+     *     ],
+     *     "superset_of": [
+     *       "authorization_code"
+     *     ]
+     *   },
+     *   "token_endpoint_auth_method": {
+     *     "one_of": [
+     *       "private_key_jwt",
+     *       "self_signed_tls_client_auth"
+     *     ],
+     *     "essential": true
+     *   },
+     *   "token_endpoint_auth_signing_alg" : {
+     *     "one_of": [
+     *       "PS256",
+     *       "ES256"
+     *     ]
+     *   }
+     * }
+ * + * @return + * The metadata policy applied to client metadata that is obtained + * through the CIMD mechanism. + * + * @since 4.33 + * @since Authlete 3.0.23 + * + * @see + * OAuth Client ID Metadata Document + * @see + * OpenID Federation 1.0, Section 6.1. Metadata Policy + */ + public String getCimdMetadataPolicy() + { + return cimdMetadataPolicy; + } + + + /** + * Set the metadata policy applied to client metadata that is obtained + * through the CIMD mechanism. + * + *

+ * If the {@code cimdMetadataPolicyEnabled} property is set to {@code true}, + * the metadata policy specified by this {@code cimdMetadataPolicy} property + * is applied to client metadata that is obtained through the CIMD mechanism. + *

+ * + *

+ * The metadata policy must comply with the grammar defined in 6.1. Metadata Policy of the OpenID Federation + * 1.0 specification. Below is an example of metadata policy: + *

+ * + *
+     * {
+     *   "grant_types": {
+     *     "default": [
+     *       "authorization_code"
+     *     ],
+     *     "subset_of": [
+     *       "authorization_code",
+     *       "refresh_token"
+     *     ],
+     *     "superset_of": [
+     *       "authorization_code"
+     *     ]
+     *   },
+     *   "token_endpoint_auth_method": {
+     *     "one_of": [
+     *       "private_key_jwt",
+     *       "self_signed_tls_client_auth"
+     *     ],
+     *     "essential": true
+     *   },
+     *   "token_endpoint_auth_signing_alg" : {
+     *     "one_of": [
+     *       "PS256",
+     *       "ES256"
+     *     ]
+     *   }
+     * }
+ * + * @param policy + * The metadata policy applied to client metadata that is obtained + * through the CIMD mechanism. + * + * @return + * {@code this} object. + * + * @since 4.33 + * @since Authlete 3.0.23 + * + * @see + * OAuth Client ID Metadata Document + * @see + * OpenID Federation 1.0, Section 6.1. Metadata Policy + */ + public Service setCimdMetadataPolicy(String policy) + { + this.cimdMetadataPolicy = policy; + + return this; + } + + + /** + * Get the flag that indicates whether to prohibit client ID aliases that + * start with {@code https://} or {@code http://}. + * + *

+ * The primary purpose of this flag is to prevent the use of client ID + * aliases that may conflict with entity IDs in OpenID Federation + * 1.0 or metadata document locations in CIMD. + *

+ * + *

+ * For backward compatibility, the default value of this flag is set to + * {@code false}, but it is recommended to set it to {@code true} whenever + * possible. + *

+ * + * @return + * {@code true} if client ID aliases that start with {@code https://} + * or {@code http://} are prohibited. + * + * @since 4.32 + * @since Authlete 3.0.22 + * + * @see + * OpenID Federation 1.0 + * @see + * OAuth Client ID Metadata Document + */ + public boolean isHttpAliasProhibited() + { + return httpAliasProhibited; + } + + + /** + * Set the flag that indicates whether to prohibit client ID aliases that + * start with {@code https://} or {@code http://}. + * + *

+ * The primary purpose of this flag is to prevent the use of client ID + * aliases that may conflict with entity IDs in OpenID Federation + * 1.0 or metadata document locations in CIMD. + *

+ * + *

+ * For backward compatibility, the default value of this flag is set to + * {@code false}, but it is recommended to set it to {@code true} whenever + * possible. + *

+ * + * @param prohibited + * {@code true} to prohibit client ID aliases that start with + * {@code https://} or {@code http://}. + * + * @return + * {@code this} object. + * + * @since 4.32 + * @since Authlete 3.0.22 + * + * @see + * OpenID Federation 1.0 + * @see + * OAuth Client ID Metadata Document + */ + public Service setHttpAliasProhibited(boolean prohibited) + { + this.httpAliasProhibited = prohibited; + + return this; + } + + + /** + * Get the URI of the challenge endpoint that is defined in + * OAuth 2.0 Attestation-Based Client Authentication. + * + * @return + * The URI of the challenge endpoint. + * + * @since 4.39 + * @since Authlete 3.0.28 + * + * @see + * OAuth 2.0 Attestation-Based Client Authentication + */ + public URI getChallengeEndpoint() + { + return challengeEndpoint; + } + + + /** + * Set the URI of the challenge endpoint that is defined in + * OAuth 2.0 Attestation-Based Client Authentication. + * + * @param endpoint + * The URI of the challenge endpoint. + * + * @return + * {@code this} object. + * + * @since 4.39 + * @since Authlete 3.0.28 + * + * @see + * OAuth 2.0 Attestation-Based Client Authentication + */ + public Service setChallengeEndpoint(URI endpoint) + { + this.challengeEndpoint = endpoint; + + return this; + } + + + /** + * Get the time window of attestation challenges in seconds. + * + *

+ * The value of this property is used as the time window of attestation + * challenges issued from the challenge endpoint defined in the + * OAuth 2.0 Attestation-Based Client Authentication. + *

+ * + * @return + * The time window of attestation challenges in seconds. + * + * @since 4.39 + * @since Authlete 3.0.28 + * + * @see + * OAuth 2.0 Attestation-Based Client Authentication + */ + public long getAttestationChallengeTimeWindow() + { + return attestationChallengeTimeWindow; + } + + + /** + * Set the time window of attestation challenges in seconds. + * + *

+ * The value of this property is used as the time window of attestation + * challenges issued from the challenge endpoint defined in the + * OAuth 2.0 Attestation-Based Client Authentication. + *

+ * + * @param timeWindow + * The time window of attestation challenges in seconds. + * + * @return + * {@code this} object. + * + * @since 4.39 + * @since Authlete 3.0.28 + * + * @see + * OAuth 2.0 Attestation-Based Client Authentication + */ + public Service setAttestationChallengeTimeWindow(long timeWindow) + { + this.attestationChallengeTimeWindow = timeWindow; + + return this; + } + + + /** + * Get the HAIP version to comply with. Valid values for this property are + * as follows: + * + *
    + *
  • {@code "1.0"} + *
+ * + *

+ * If this property is set, requests to this service undergo additional + * validation based on the specified version of the HAIP specification. + *

+ * + *

+ * Note that even if this property is not set, additional validation based + * on the HAIP specification is performed if the {@link Client}'s + * {@code haipVersion} is set or if a scope with the {@code haip} attribute + * is included in the request. + *

+ * + * @return + * The HAIP version to comply with. + * + * @since 4.41 + * @since 3.0.29 + * + * @see + * OpenID4VC High Assurance Interoperability Profile 1.0 + */ + public String getHaipVersion() + { + return haipVersion; + } + + + /** + * Set the HAIP version to comply with. Valid values for this property are + * as follows: + * + *
    + *
  • {@code "1.0"} + *
+ * + *

+ * If this property is set, requests to this service undergo additional + * validation based on the specified version of the HAIP specification. + *

+ * + *

+ * Note that even if this property is not set, additional validation based + * on the HAIP specification is performed if the {@link Client}'s + * {@code haipVersion} is set or if a scope with the {@code haip} attribute + * is included in the request. + *

+ * + * @param version + * The HAIP version to comply with. + * + * @return + * {@code this} object. + * + * @since 4.41 + * @since 3.0.29 + * + * @see + * OpenID4VC High Assurance Interoperability Profile 1.0 + */ + public Service setHaipVersion(String version) + { + this.haipVersion = version; + + return this; + } + + + /** + * Get the trusted root certificates used to validate the certificate chain + * specified in the {@code x5c} header parameter of the Client Attestation + * JWT. + * + *

+ * If the Client Attestation JWT includes the {@code x5c} header parameter, + * Authlete validates the specified certificate chain. During validation, + * Authlete uses the X.509 certificates specified by this property as + * trusted root certificates when the {@code clientAttesterRootsEnabled} + * property is set to {@code true}. System-installed root certificates are + * also used unless the {@code clientAttesterRootsOnly} property is set to + * {@code true}. + *

+ * + *

+ * Each element in this property represents an X.509 certificate. The value + * is a base64-encoded string of the certificate in DER format, with or + * without PEM markers. The base64-encoded string may contain newlines and + * other whitespace characters. + *

+ * + *

+ * When Authlete returns this certificate list from its APIs, the certificates + * are provided in PEM format with PEM markers. + *

+ * + *

+ * Note that the OpenID4VC High Assurance Interoperability Profile 1.0 specification + * requires the Client Attestation JWT to include the {@code x5c} header + * parameter. + *

+ * + * @return + * Trusted root certificates used to validate the certificate chain + * of the Client Attestation JWT. + * + * @see + * OAuth 2.0 Attestation-Based Client Authentication + * @see + * OpenID4VC High Assurance Interoperability Profile 1.0 + * @see #isClientAttesterRootsEnabled() + * @see #isClientAttesterRootsOnly() + * + * @since 4.42 + * @since Authlete 3.0.31 + */ + public String[] getClientAttesterRoots() + { + return clientAttesterRoots; + } + + + /** + * Set the trusted root certificates used to validate the certificate chain + * specified in the {@code x5c} header parameter of the Client Attestation + * JWT. + * + *

+ * If the Client Attestation JWT includes the {@code x5c} header parameter, + * Authlete validates the specified certificate chain. During validation, + * Authlete uses the X.509 certificates specified by this property as + * trusted root certificates when the {@code clientAttesterRootsEnabled} + * property is set to {@code true}. System-installed root certificates are + * also used unless the {@code clientAttesterRootsOnly} property is set to + * {@code true}. + *

+ * + *

+ * Each element in this property represents an X.509 certificate. The value + * is a base64-encoded string of the certificate in DER format, with or + * without PEM markers. The base64-encoded string may contain newlines and + * other whitespace characters. + *

+ * + *

+ * When Authlete returns this certificate list from its APIs, the certificates + * are provided in PEM format with PEM markers. + *

+ * + *

+ * Note that the OpenID4VC High Assurance Interoperability Profile 1.0 specification + * requires the Client Attestation JWT to include the {@code x5c} header + * parameter. + *

+ * + * @param certificates + * Trusted root certificates used to validate the certificate chain + * of the Client Attestation JWT. + * + * @return + * {@code this} object. + * + * @see + * OAuth 2.0 Attestation-Based Client Authentication + * @see + * OpenID4VC High Assurance Interoperability Profile 1.0 + * @see #setClientAttesterRootsEnabled(boolean) + * @see #setClientAttesterRootsOnly(boolean) + * + * @since 4.42 + * @since Authlete 3.0.31 + */ + public Service setClientAttesterRoots(String[] certificates) + { + this.clientAttesterRoots = certificates; + + return this; + } + + + /** + * Get the flag indicating whether to use the trusted root certificates + * specified by the {@code clientAttesterRoots} property to validate the + * certificate chain specified in the {@code x5c} header parameter of the + * Client Attestation JWT. + * + * @return + * {@code true} if the trusted root certificates specified by the + * {@code clientAttesterRoots} property are used to validate the + * certificate chain of the Client Attestation JWT. + * + * @see + * OAuth 2.0 Attestation-Based Client Authentication + * @see + * OpenID4VC High Assurance Interoperability Profile 1.0 + * @see #getClientAttesterRoots() + * @see #isClientAttesterRootsOnly() + * + * @since 4.42 + * @since Authlete 3.0.31 + */ + public boolean isClientAttesterRootsEnabled() + { + return clientAttesterRootsEnabled; + } + + + /** + * Set the flag indicating whether to use the trusted root certificates + * specified by the {@code clientAttesterRoots} property to validate the + * certificate chain specified in the {@code x5c} header parameter of the + * Client Attestation JWT. + * + * @param enabled + * {@code true} to use the trusted root certificates specified by + * the {@code clientAttesterRoots} property. + * + * @return + * {@code this} object. + * + * @see + * OAuth 2.0 Attestation-Based Client Authentication + * @see + * OpenID4VC High Assurance Interoperability Profile 1.0 + * @see #setClientAttesterRoots(String[]) + * @see #setClientAttesterRootsOnly(boolean) + * + * @since 4.42 + * @since Authlete 3.0.31 + */ + public Service setClientAttesterRootsEnabled(boolean enabled) + { + this.clientAttesterRootsEnabled = enabled; + + return this; + } + + + /** + * Get the flag indicating whether to use only the trusted root certificates + * specified by the {@code clientAttesterRoots} property to validate the + * certificate chain specified in the {@code x5c} header parameter of the + * Client Attestation JWT, without using system-installed root certificates. + * + * @return + * {@code true} if only the trusted root certificates specified by + * the {@code clientAttesterRoots} property are used. + * + * @see + * OAuth 2.0 Attestation-Based Client Authentication + * @see + * OpenID4VC High Assurance Interoperability Profile 1.0 + * @see #getClientAttesterRoots() + * @see #isClientAttesterRootsEnabled() + * + * @since 4.42 + * @since Authlete 3.0.31 + */ + public boolean isClientAttesterRootsOnly() + { + return clientAttesterRootsOnly; + } + + + /** + * Set the flag indicating whether to use only the trusted root certificates + * specified by the {@code clientAttesterRoots} property to validate the + * certificate chain specified in the {@code x5c} header parameter of the + * Client Attestation JWT, without using system-installed root certificates. + * + * @param only + * {@code true} to use only the trusted root certificates specified + * by the {@code clientAttesterRoots} property. + * + * @return + * {@code this} object. + * + * @see + * OAuth 2.0 Attestation-Based Client Authentication + * @see + * OpenID4VC High Assurance Interoperability Profile 1.0 + * @see #setClientAttesterRoots(String[]) + * @see #isClientAttesterRootsEnabled() + * + * @since 4.42 + * @since Authlete 3.0.31 + */ + public Service setClientAttesterRootsOnly(boolean only) + { + this.clientAttesterRootsOnly = only; + + return this; + } + + + /** + * Get the trusted root certificates used to validate the certificate chain + * specified in the {@code x5c} header parameter of the Key Attestation JWT. + * + *

+ * If the Key Attestation JWT includes the {@code x5c} header parameter, + * Authlete validates the specified certificate chain. During validation, + * Authlete uses the X.509 certificates specified by this property as + * trusted root certificates when the {@code keyAttesterRootsEnabled} + * property is set to {@code true}. System-installed root certificates are + * also used unless the {@code keyAttesterRootsOnly} property is set to + * {@code true}. + *

+ * + *

+ * Each element in this property represents an X.509 certificate. The value + * is a base64-encoded string of the certificate in DER format, with or + * without PEM markers. The base64-encoded string may contain newlines and + * other whitespace characters. + *

+ * + *

+ * When Authlete returns this certificate list from its APIs, the certificates + * are provided in PEM format with PEM markers. + *

+ * + *

+ * Note that the OpenID4VC High Assurance Interoperability Profile 1.0 specification + * requires the Key Attestation JWT to include the {@code x5c} header + * parameter. + *

+ * + * @return + * Trusted root certificates used to validate the certificate chain + * of the Key Attestation JWT. + * + * @see + * OpenID for Verifiable Credential Issuance 1.0 + * @see + * OpenID4VC High Assurance Interoperability Profile 1.0 + * @see #isKeyAttesterRootsEnabled() + * @see #isKeyAttesterRootsOnly() + * + * @since 4.42 + * @since Authlete 3.0.31 + */ + public String[] getKeyAttesterRoots() + { + return keyAttesterRoots; + } + + + /** + * Set the trusted root certificates used to validate the certificate chain + * specified in the {@code x5c} header parameter of the Key Attestation JWT. + * + *

+ * If the Key Attestation JWT includes the {@code x5c} header parameter, + * Authlete validates the specified certificate chain. During validation, + * Authlete uses the X.509 certificates specified by this property as + * trusted root certificates when the {@code keyAttesterRootsEnabled} + * property is set to {@code true}. System-installed root certificates are + * also used unless the {@code keyAttesterRootsOnly} property is set to + * {@code true}. + *

+ * + *

+ * Each element in this property represents an X.509 certificate. The value + * is a base64-encoded string of the certificate in DER format, with or + * without PEM markers. The base64-encoded string may contain newlines and + * other whitespace characters. + *

+ * + *

+ * When Authlete returns this certificate list from its APIs, the certificates + * are provided in PEM format with PEM markers. + *

+ * + *

+ * Note that the OpenID4VC High Assurance Interoperability Profile 1.0 specification + * requires the Key Attestation JWT to include the {@code x5c} header + * parameter. + *

+ * + * @param certificates + * Trusted root certificates used to validate the certificate chain + * of the Key Attestation JWT. + * + * @return + * {@code this} object. + * + * @see + * OpenID for Verifiable Credential Issuance 1.0 + * @see + * OpenID4VC High Assurance Interoperability Profile 1.0 + * @see #setKeyAttesterRootsEnabled(boolean) + * @see #setKeyAttesterRootsOnly(boolean) + * + * @since 4.42 + * @since Authlete 3.0.31 + */ + public Service setKeyAttesterRoots(String[] certificates) + { + this.keyAttesterRoots = certificates; + + return this; + } + + + /** + * Get the flag indicating whether to use the trusted root certificates + * specified by the {@code keyAttesterRoots} property to validate the + * certificate chain specified in the {@code x5c} header parameter of the + * Key Attestation JWT. + * + * @return + * {@code true} if the trusted root certificates specified by the + * {@code keyAttesterRoots} property are used to validate the + * certificate chain of the Key Attestation JWT. + * + * @see + * OpenID for Verifiable Credential Issuance 1.0 + * @see + * OpenID4VC High Assurance Interoperability Profile 1.0 + * @see #getKeyAttesterRoots() + * @see #isKeyAttesterRootsOnly() + * + * @since 4.42 + * @since Authlete 3.0.31 + */ + public boolean isKeyAttesterRootsEnabled() + { + return keyAttesterRootsEnabled; + } + + + /** + * Set the flag indicating whether to use the trusted root certificates + * specified by the {@code keyAttesterRoots} property to validate the + * certificate chain specified in the {@code x5c} header parameter of the + * Key Attestation JWT. + * + * @param enabled + * {@code true} to use the trusted root certificates specified by + * the {@code keyAttesterRoots} property. + * + * @return + * {@code this} object. + * + * @see + * OpenID for Verifiable Credential Issuance 1.0 + * @see + * OpenID4VC High Assurance Interoperability Profile 1.0 + * @see #setKeyAttesterRoots(String[]) + * @see #setKeyAttesterRootsOnly(boolean) + * + * @since 4.42 + * @since Authlete 3.0.31 + */ + public Service setKeyAttesterRootsEnabled(boolean enabled) + { + this.keyAttesterRootsEnabled = enabled; + + return this; + } + + + /** + * Get the flag indicating whether to use only the trusted root certificates + * specified by the {@code keyAttesterRoots} property to validate the + * certificate chain specified in the {@code x5c} header parameter of the + * Key Attestation JWT, without using system-installed root certificates. + * + * @return + * {@code true} if only the trusted root certificates specified by + * the {@code keyAttesterRoots} property are used. + * + * @see + * OpenID for Verifiable Credential Issuance 1.0 + * @see + * OpenID4VC High Assurance Interoperability Profile 1.0 + * @see #getKeyAttesterRoots() + * @see #isKeyAttesterRootsEnabled() + * + * @since 4.42 + * @since Authlete 3.0.31 + */ + public boolean isKeyAttesterRootsOnly() + { + return keyAttesterRootsOnly; + } + + + /** + * Set the flag indicating whether to use only the trusted root certificates + * specified by the {@code keyAttesterRoots} property to validate the + * certificate chain specified in the {@code x5c} header parameter of the + * Key Attestation JWT, without using system-installed root certificates. + * + * @param only + * {@code true} to use only the trusted root certificates specified + * by the {@code keyAttesterRoots} property. + * + * @return + * {@code this} object. + * + * @see + * OpenID for Verifiable Credential Issuance 1.0 + * @see + * OpenID4VC High Assurance Interoperability Profile 1.0 + * @see #setKeyAttesterRoots(String[]) + * @see #isKeyAttesterRootsEnabled() + * + * @since 4.42 + * @since Authlete 3.0.31 + */ + public Service setKeyAttesterRootsOnly(boolean only) + { + this.keyAttesterRootsOnly = only; + + return this; + } + + + /** + * Get the flag indicating whether to support the OpenID + * Connect Back-Channel Logout 1.0 specification. This property + * corresponds to the backchannel_logout_supported + * server metadata parameter defined in the specification. + * + *

+ * The /api/{service-id}/backchannel/logout/token + * API can be used to generate a Logout Token. See {@link BackchannelLogoutTokenRequest} and + * {@link BackchannelLogoutTokenResponse} for details. + *

+ * + * @return + * The flag indicating whether to support the OpenID Connect Back-Channel Logout 1.0 specification. + * + * @since 4.44 + * @since Authlete 3.0.32 + * + * @see + * OpenID Connect Back-Channel Logout 1.0 + * @see BackchannelLogoutTokenRequest + * @see BackchannelLogoutTokenResponse + */ + public boolean isBackchannelLogoutSupported() + { + return backchannelLogoutSupported; + } + + + /** + * Set the flag indicating whether to support the OpenID + * Connect Back-Channel Logout 1.0 specification. This property + * corresponds to the backchannel_logout_supported + * server metadata parameter defined in the specification. + * + *

+ * The /api/{service-id}/backchannel/logout/token + * API can be used to generate a Logout Token. See {@link BackchannelLogoutTokenRequest} and + * {@link BackchannelLogoutTokenResponse} for details. + *

+ * + * @param supported + * {@code true} to indicate that this service supports the OpenID Connect Back-Channel Logout 1.0 specification. + * + * @return + * {@code this} object. + * + * @since 4.44 + * @since Authlete 3.0.32 + * + * @see + * OpenID Connect Back-Channel Logout 1.0 + * @see BackchannelLogoutTokenRequest + * @see BackchannelLogoutTokenResponse + */ + public Service setBackchannelLogoutSupported(boolean supported) + { + this.backchannelLogoutSupported = supported; + + return this; + } + + + /** + * Get the flag indicating whether to support the {@code sid} claim of + * Logout Tokens. This property corresponds to the + * backchannel_logout_session_supported server + * metadata parameter defined in the OpenID + * Connect Back-Channel Logout 1.0 specification. + * + *

+ * If this property is set to {@code true}, the {@code sessionId} request + * parameter for the /auth/authorization/issue API + * (see {@link AuthorizationIssueRequest#getSessionId()}) is recognized, + * and the specified value is embedded in ID Tokens as the value of the + * {@code sid} claim. + *

+ * + *

+ * If this property is set to {@code true}, API calls to the + * /api/{service-id}/backchannel/logout/token + * API should include the {@code sessionId} request parameter (see + * {@link BackchannelLogoutTokenRequest#getSessionId()}). + *

+ * + * @return + * The flag indicating whether to support the {@code sid} claim + * of Logout Tokens. + * + * @since 4.44 + * @since Authlete 3.0.32 + * + * @see + * OpenID Connect Back-Channel Logout 1.0 + */ + public boolean isBackchannelLogoutSessionSupported() + { + return backchannelLogoutSessionSupported; + } + + + /** + * Set the flag indicating whether to support the {@code sid} claim of + * Logout Tokens. This property corresponds to the + * backchannel_logout_session_supported server + * metadata parameter defined in the OpenID + * Connect Back-Channel Logout 1.0 specification. + * + *

+ * If this property is set to {@code true}, the {@code sessionId} request + * parameter for the /auth/authorization/issue API + * (see {@link AuthorizationIssueRequest#getSessionId()}) is recognized, + * and the specified value is embedded in ID Tokens as the value of the + * {@code sid} claim. + *

+ * + *

+ * If this property is set to {@code true}, API calls to the + * /api/{service-id}/backchannel/logout/token + * API should include the {@code sessionId} request parameter (see + * {@link BackchannelLogoutTokenRequest#getSessionId()}). + *

+ * + * @param supported + * {@code true} to indicate that the {@code sid} claim of Logout + * Tokens is supported. + * + * @return + * {@code this} object. + * + * @since 4.44 + * @since Authlete 3.0.32 + * + * @see + * OpenID Connect Back-Channel Logout 1.0 + */ + public Service setBackchannelLogoutSessionSupported(boolean supported) + { + this.backchannelLogoutSessionSupported = supported; + + return this; + } } diff --git a/src/main/java/com/authlete/common/dto/TokenCreateRequest.java b/src/main/java/com/authlete/common/dto/TokenCreateRequest.java index 8006269a39..d1afdca893 100644 --- a/src/main/java/com/authlete/common/dto/TokenCreateRequest.java +++ b/src/main/java/com/authlete/common/dto/TokenCreateRequest.java @@ -275,7 +275,7 @@ */ public class TokenCreateRequest implements Serializable { - private static final long serialVersionUID = 15L; + private static final long serialVersionUID = 16L; private GrantType grantType; @@ -287,6 +287,7 @@ public class TokenCreateRequest implements Serializable private Property[] properties; private boolean clientIdAliasUsed; private boolean clientEntityIdUsed; + private boolean metadataDocumentUsed; private String accessToken; private String refreshToken; private boolean accessTokenPersistent; @@ -595,8 +596,8 @@ public TokenCreateRequest setProperties(Property[] properties) *

* *

- * Note that {@code clientIdAliasUsed} and {@code clientEntityIdUsed} are - * mutually exclusive. + * Note that {@code clientIdAliasUsed}, {@code clientEntityIdUsed}, and + * {@code metadataDocumentUsed} are mutually exclusive. *

* * @return @@ -633,8 +634,8 @@ public boolean isClientIdAliasUsed() *

* *

- * Note that {@code clientIdAliasUsed} and {@code clientEntityIdUsed} are - * mutually exclusive. + * Note that {@code clientIdAliasUsed}, {@code clientEntityIdUsed}, and + * {@code metadataDocumentUsed} are mutually exclusive. *

* * @param used @@ -679,8 +680,8 @@ public TokenCreateRequest setClientIdAliasUsed(boolean used) *

* *

- * Note that {@code clientIdAliasUsed} and {@code clientEntityIdUsed} are - * mutually exclusive. + * Note that {@code clientIdAliasUsed}, {@code clientEntityIdUsed}, and + * {@code metadataDocumentUsed} are mutually exclusive. *

* * @return @@ -721,8 +722,8 @@ public boolean isClientEntityIdUsed() *

* *

- * Note that {@code clientIdAliasUsed} and {@code clientEntityIdUsed} are - * mutually exclusive. + * Note that {@code clientIdAliasUsed}, {@code clientEntityIdUsed}, and + * {@code metadataDocumentUsed} are mutually exclusive. *

* * @param used @@ -743,6 +744,77 @@ public TokenCreateRequest setClientEntityIdUsed(boolean used) } + /** + * Get the flag which indicates whether to emulate that the location of + * the client's metadata document is used as a client ID. + * + *

+ * If the service supports OAuth Client ID Metadata Document + * (cf. {@link Service#isClientIdMetadataDocumentSupported()}), the + * location of the client's metadata document can be used as a client ID. + *

+ * + *

+ * Note that {@code clientIdAliasUsed}, {@code clientEntityIdUsed}, and + * {@code metadataDocumentUsed} are mutually exclusive. + *

+ * + * @return + * {@code true} to emulate that the location of the client's + * metadata document is used as a client ID. + * + * @since 4.29 + * @since Authlete 3.0.22 + * + * @see + * OAuth Client ID Metadata Document + */ + public boolean isMetadataDocumentUsed() + { + return metadataDocumentUsed; + } + + + /** + * Set the flag which indicates whether to emulate that the location of + * the client's metadata document is used as a client ID. + * + *

+ * If the service supports OAuth Client ID Metadata Document + * (cf. {@link Service#isClientIdMetadataDocumentSupported()}), the + * location of the client's metadata document can be used as a client ID. + *

+ * + *

+ * Note that {@code clientIdAliasUsed}, {@code clientEntityIdUsed}, and + * {@code metadataDocumentUsed} are mutually exclusive. + *

+ * + * @param used + * {@code true} to emulate that the location of the client's + * metadata document is used as a client ID. + * + * @return + * {@code this} object. + * + * @since 4.29 + * @since Authlete 3.0.22 + * + * @see + * OAuth Client ID Metadata Document + */ + public TokenCreateRequest setMetadataDocumentUsed(boolean used) + { + this.metadataDocumentUsed = used; + + return this; + } + + /** * Get the access token. * diff --git a/src/main/java/com/authlete/common/dto/TokenFailRequest.java b/src/main/java/com/authlete/common/dto/TokenFailRequest.java index a834f51987..80ffb34a3d 100644 --- a/src/main/java/com/authlete/common/dto/TokenFailRequest.java +++ b/src/main/java/com/authlete/common/dto/TokenFailRequest.java @@ -63,37 +63,68 @@ public enum Reason * Unknown reason. * *

- * Using this reason will result in - * {@code "error":"server_error"}. + * Using this reason will result in {@code "error":"server_error"}. *

*/ UNKNOWN, /** - * The resource owner's credentials ({@code username} and - * {@code password}) contained in the token request whose - * flow is - * "Resource Owner Password Credentials") are invalid. + * The resource owner's credentials ({@code username} and {@code password}) contained in the token request whose flow is "Resource + * Owner Password Credentials") are invalid. * *

- * Using this reason will result in - * {@code "error":"invalid_request"}. + * Using this reason will result in {@code "error":"invalid_request"}. *

*/ INVALID_RESOURCE_OWNER_CREDENTIALS, /** - * The requested resource is invalid, missing, unknown, or malformed. - * See "Resource Indicators for OAuth 2.0" for details. + * The requested resource is invalid, missing, unknown, or malformed. See "Resource Indicators for OAuth 2.0" for details. * *

- * Using this reason will result in - * {@code "error":"invalid_target"}. + * Using this reason will result in {@code "error":"invalid_target"}. *

* * @since 2.62 */ INVALID_TARGET, + + /** + * Client authentication failed (e.g., unknown client, no client authentication included, or unsupported + * authentication method). The authorization server MAY return an HTTP 401 (Unauthorized) status code to indicate + * which HTTP authentication schemes are supported. If the client attempted to authenticate via the "Authorization" + * request header field, the authorization server MUST respond with an HTTP 401 (Unauthorized) status code and + * include the "WWW-Authenticate" response header field matching the authentication scheme used by the client. + * + *

+ * Using this reason will result in {@code "error":"invalid_client"}. + *

+ * + * @since 4.46 + */ + INVALID_CLIENT, + + /** + * The authenticated client is not authorized to use this authorization grant type. + * + *

+ * Using this reason will result in {@code "error":"unauthorized_client"}. + *

+ * + * @since 4.46 + */ + UNAUTHORIZED_CLIENT, + + /** + * The requested scope is invalid, unknown, malformed, or exceeds the scope granted by the resource owner. + * + *

+ * Using this reason will result in {@code "error":"invalid_scope"}. + *

+ * + * @since 4.46 + */ + INVALID_SCOPE, } diff --git a/src/main/java/com/authlete/common/dto/TokenInfo.java b/src/main/java/com/authlete/common/dto/TokenInfo.java index 20d0621f42..c087f8034a 100644 --- a/src/main/java/com/authlete/common/dto/TokenInfo.java +++ b/src/main/java/com/authlete/common/dto/TokenInfo.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2022-2023 Authlete, Inc. + * Copyright (C) 2022-2025 Authlete, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -42,7 +42,7 @@ */ public class TokenInfo implements Serializable { - private static final long serialVersionUID = 2L; + private static final long serialVersionUID = 3L; /** @@ -106,6 +106,25 @@ public class TokenInfo implements Serializable private boolean clientEntityIdUsed; + /** + * The location of the client's metadata document. + * + * @since 4.29 + * @since Authlete 3.0.22 + */ + private URI metadataDocumentLocation; + + + /** + * Flag which indicates whether the location of the client's metadata + * document was used when the request for the token was made. + * + * @since 4.29 + * @since Authlete 3.0.22 + */ + private boolean metadataDocumentUsed; + + /** * The target resources of the token. */ @@ -459,6 +478,110 @@ public TokenInfo setClientEntityIdUsed(boolean used) } + /** + * Get the location of the client's metadata document. This property + * holds a non-null value only when {@link #isMetadataDocumentUsed()} + * returns {@code true}. + * + * @return + * The location of the client's metadata document. + * + * @since 4.29 + * @since Authlete 3.0.22 + * + * @see + * OAuth Client ID Metadata Document + */ + public URI getMetadataDocumentLocation() + { + return metadataDocumentLocation; + } + + + /** + * Set the location of the client's metadata document. This property + * should hold a non-null value only when {@link #isMetadataDocumentUsed()} + * returns {@code true}. + * + * @param location + * The location of the client's metadata document. + * + * @return + * {@code this} object. + * + * @since 4.29 + * @since Authlete 3.0.22 + * + * @see + * OAuth Client ID Metadata Document + */ + public TokenInfo setMetadataDocumentLocation(URI location) + { + this.metadataDocumentLocation = location; + + return this; + } + + + /** + * Get the flag which indicates whether the location of the client's + * metadata document was used as a client ID. + * + *

+ * This can happen when the service supports OAuth Client ID Metadata Document. + * (cf. {@link Service#isClientIdMetadataDocumentSupported()}) + *

+ * + * @return + * {@code true} if the location of client's metadata document + * was used as a client ID. + * + * @since 4.29 + * @since Authlete 3.0.22 + * + * @see + * OAuth Client ID Metadata Document + */ + public boolean isMetadataDocumentUsed() + { + return metadataDocumentUsed; + } + + + /** + * Set the flag which indicates whether the location of the client's + * metadata document was used as a client ID. + * + *

+ * This can happen when the service supports OAuth Client ID Metadata Document. + * (cf. {@link Service#isClientIdMetadataDocumentSupported()}) + *

+ * + * @param used + * {@code true} to indicate that the location of the client's + * metadata document was used as a client ID. + * + * @return + * {@code this} object. + * + * @since 4.29 + * @since Authlete 3.0.22 + * + * @see + * OAuth Client ID Metadata Document + */ + public TokenInfo setMetadataDocumentUsed(boolean used) + { + this.metadataDocumentUsed = used; + + return this; + } + + /** * Get the resources associated with the token. * diff --git a/src/main/java/com/authlete/common/dto/TokenIssueResponse.java b/src/main/java/com/authlete/common/dto/TokenIssueResponse.java index 823f6376ff..8a7423548e 100644 --- a/src/main/java/com/authlete/common/dto/TokenIssueResponse.java +++ b/src/main/java/com/authlete/common/dto/TokenIssueResponse.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014-2023 Authlete, Inc. + * Copyright (C) 2014-2025 Authlete, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -103,7 +103,7 @@ */ public class TokenIssueResponse extends ApiResponse { - private static final long serialVersionUID = 10L; + private static final long serialVersionUID = 11L; /** @@ -202,6 +202,22 @@ public enum Action */ private boolean clientEntityIdUsed; + /** + * The location of the client's metadata document. + * + * @since 4.29 + * @since Authlete 3.0.22 + */ + private URI metadataDocumentLocation; + + /** + * Whether the client's metadata document was used. + * + * @since 4.29 + * @since Authlete 3.0.22 + */ + private boolean metadataDocumentUsed; + /** * @since Authlete 1.1 */ @@ -685,6 +701,100 @@ public void setClientEntityIdUsed(boolean used) } + /** + * Get the location of the client's metadata document. This property + * holds a non-null value only when {@link #isMetadataDocumentUsed()} + * returns {@code true}. + * + * @return + * The location of the client's metadata document. + * + * @since 4.29 + * @since Authlete 3.0.22 + * + * @see + * OAuth Client ID Metadata Document + */ + public URI getMetadataDocumentLocation() + { + return metadataDocumentLocation; + } + + + /** + * Set the location of the client's metadata document. This property + * should hold a non-null value only when {@link #isMetadataDocumentUsed()} + * returns {@code true}. + * + * @param location + * The location of the client's metadata document. + * + * @since 4.29 + * @since Authlete 3.0.22 + * + * @see + * OAuth Client ID Metadata Document + */ + public void setMetadataDocumentLocation(URI location) + { + this.metadataDocumentLocation = location; + } + + + /** + * Get the flag which indicates whether the location of the client's + * metadata document was used as a client ID. + * + *

+ * This can happen when the service supports OAuth Client ID Metadata Document. + * (cf. {@link Service#isClientIdMetadataDocumentSupported()}) + *

+ * + * @return + * {@code true} if the location of client's metadata document + * was used as a client ID. + * + * @since 4.29 + * @since Authlete 3.0.22 + * + * @see + * OAuth Client ID Metadata Document + */ + public boolean isMetadataDocumentUsed() + { + return metadataDocumentUsed; + } + + + /** + * Set the flag which indicates whether the location of the client's + * metadata document was used as a client ID. + * + *

+ * This can happen when the service supports OAuth Client ID Metadata Document. + * (cf. {@link Service#isClientIdMetadataDocumentSupported()}) + *

+ * + * @param used + * {@code true} to indicate that the location of the client's + * metadata document was used as a client ID. + * + * @since 4.29 + * @since Authlete 3.0.22 + * + * @see + * OAuth Client ID Metadata Document + */ + public void setMetadataDocumentUsed(boolean used) + { + this.metadataDocumentUsed = used; + } + + /** * Get the subject (= resource owner's ID) of the access token. * diff --git a/src/main/java/com/authlete/common/dto/TokenRequest.java b/src/main/java/com/authlete/common/dto/TokenRequest.java index f0be40df27..46a68f24ca 100644 --- a/src/main/java/com/authlete/common/dto/TokenRequest.java +++ b/src/main/java/com/authlete/common/dto/TokenRequest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014-2024 Authlete, Inc. + * Copyright (C) 2014-2025 Authlete, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -212,7 +212,7 @@ */ public class TokenRequest implements Serializable { - private static final long serialVersionUID = 12L; + private static final long serialVersionUID = 13L; /** @@ -346,6 +346,15 @@ public class TokenRequest implements Serializable private String oauthClientAttestationPop; + /** + * Options for CIMD processing. + * + * @since 4.30 + * @since Authlete 3.0.22 + */ + private CimdOptions cimdOptions; + + /** * Get the value of {@code parameters} which are the request * parameters that the OAuth 2.0 token endpoint of the service @@ -1247,4 +1256,49 @@ public TokenRequest setOauthClientAttestationPop(String jwt) return this; } + + + /** + * Get options for CIMD processing. + * + * @return + * Options for CIMD processing. + * + * @since 4.30 + * @since Authlete 3.0.22 + * + * @see + * OAuth Client ID Metadata Document + */ + public CimdOptions getCimdOptions() + { + return cimdOptions; + } + + + /** + * Set options for CIMD processing. + * + * @param options + * Options for CIMD processing. + * + * @return + * {@code this} object. + * + * @since 4.30 + * @since Authlete 3.0.22 + * + * @see + * OAuth Client ID Metadata Document + */ + public TokenRequest setCimdOptions(CimdOptions options) + { + this.cimdOptions = options; + + return this; + } } diff --git a/src/main/java/com/authlete/common/dto/TokenResponse.java b/src/main/java/com/authlete/common/dto/TokenResponse.java index 7a15841744..16fc38d69b 100644 --- a/src/main/java/com/authlete/common/dto/TokenResponse.java +++ b/src/main/java/com/authlete/common/dto/TokenResponse.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014-2025 Authlete, Inc. + * Copyright (C) 2014-2026 Authlete, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -1165,6 +1165,33 @@ * 2.0 Demonstrating Proof of Possession (DPoP) for details. *

* + *
+ *

Attestation Challenge (Authlete 3.0.28 onwards)

+ * + *

+ * Since version 3.0.28, Authlete recognizes the {@code challenge} claim in + * Client Attestation PoP JWTs. If the {@code challenge} claim is required + * (= if the service's challenge endpoint is configured), the Authlete API + * checks whether the {@code challenge} claim in the presented Client + * Attestation PoP JWT is identical to the expected value. + *

+ * + *

+ * If the {@code attestationChallenge} response parameter from the API is not + * null, its value is the expected attestation challenge value for Client + * Attestation PoP JWT. The expected value needs to be conveyed to the client + * application as the value of the {@code OAuth-Client-Attestation-Challenge} + * HTTP header. + *

+ * + *
OAuth-Client-Attestation-Challenge: (The value returned from {@link #getAttestationChallenge()})
+ * + *

+ * See OAuth 2.0 Attestation-Based Client Authentication for details. + *

+ * * @see RFC 6749 The OAuth 2.0 Authorization Framework * @@ -1184,10 +1211,13 @@ * * @see OpenID Connect Native SSO for Mobile Apps 1.0 + * + * @see + * OAuth 2.0 Attestation-Based Client Authentication */ public class TokenResponse extends ApiResponse { - private static final long serialVersionUID = 21L; + private static final long serialVersionUID = 23L; /** @@ -1414,6 +1444,22 @@ public enum Action */ private boolean clientEntityIdUsed; + /** + * The location of the client's metadata document. + * + * @since 4.29 + * @since Authlete 3.0.22 + */ + private URI metadataDocumentLocation; + + /** + * Whether the client's metadata document was used. + * + * @since 4.29 + * @since Authlete 3.0.22 + */ + private boolean metadataDocumentUsed; + /** * @since Authlete 1.1.9 */ @@ -1598,6 +1644,12 @@ public enum Action */ private String sessionId; + /** + * Additional claims that need to be added to the ID token. + * This field contains a JSON string with claims like nonce and s_hash. + */ + private String additionalClaims; + /** * The device secret. * @@ -1614,6 +1666,16 @@ public enum Action */ private String deviceSecretHash; + /** + * The expected attestation challenge value for Client Attestation PoP JWT, + * which should be used as the value of the + * {@code OAuth-Client-Attestation-Challenge} HTTP header. + * + * @since 4.39 + * @since Authlete 3.0.28 + */ + private String attestationChallenge; + /** * Get the next action that the service implementation should take. @@ -2206,6 +2268,100 @@ public void setClientEntityIdUsed(boolean used) } + /** + * Get the location of the client's metadata document. This property + * holds a non-null value only when {@link #isMetadataDocumentUsed()} + * returns {@code true}. + * + * @return + * The location of the client's metadata document. + * + * @since 4.29 + * @since Authlete 3.0.22 + * + * @see + * OAuth Client ID Metadata Document + */ + public URI getMetadataDocumentLocation() + { + return metadataDocumentLocation; + } + + + /** + * Set the location of the client's metadata document. This property + * should hold a non-null value only when {@link #isMetadataDocumentUsed()} + * returns {@code true}. + * + * @param location + * The location of the client's metadata document. + * + * @since 4.29 + * @since Authlete 3.0.22 + * + * @see + * OAuth Client ID Metadata Document + */ + public void setMetadataDocumentLocation(URI location) + { + this.metadataDocumentLocation = location; + } + + + /** + * Get the flag which indicates whether the location of the client's + * metadata document was used as a client ID. + * + *

+ * This can happen when the service supports OAuth Client ID Metadata Document. + * (cf. {@link Service#isClientIdMetadataDocumentSupported()}) + *

+ * + * @return + * {@code true} if the location of client's metadata document + * was used as a client ID. + * + * @since 4.29 + * @since Authlete 3.0.22 + * + * @see + * OAuth Client ID Metadata Document + */ + public boolean isMetadataDocumentUsed() + { + return metadataDocumentUsed; + } + + + /** + * Set the flag which indicates whether the location of the client's + * metadata document was used as a client ID. + * + *

+ * This can happen when the service supports OAuth Client ID Metadata Document. + * (cf. {@link Service#isClientIdMetadataDocumentSupported()}) + *

+ * + * @param used + * {@code true} to indicate that the location of the client's + * metadata document was used as a client ID. + * + * @since 4.29 + * @since Authlete 3.0.22 + * + * @see + * OAuth Client ID Metadata Document + */ + public void setMetadataDocumentUsed(boolean used) + { + this.metadataDocumentUsed = used; + } + + /** * Get the subject (= resource owner's ID) of the access token. * @@ -3556,6 +3712,44 @@ public void setSessionId(String sessionId) } + /** + * Get the additional claims that need to be added to the ID token. + * + *

+ * This field contains a JSON string with claims like nonce and s_hash + * that need to be included in the ID token when the action is NATIVE_SSO. + *

+ * + * @return + * The additional claims as a JSON string. + */ + public String getAdditionalClaims() + { + return additionalClaims; + } + + + /** + * Set the additional claims that need to be added to the ID token. + * + *

+ * This field should contain a JSON string with claims like nonce and s_hash + * that need to be included in the ID token when the action is NATIVE_SSO. + *

+ * + * @param additionalClaims + * The additional claims as a JSON string. + * + * @return + * {@code this} object. + */ + public TokenResponse setAdditionalClaims(String additionalClaims) + { + this.additionalClaims = additionalClaims; + return this; + } + + /** * Get the device secret presented in the token request. This is available * only when the authorization server supports the + * When this response parameter is not null, the implementation of the + * token endpoint should add the {@code OAuth-Client-Attestation-Challenge} + * HTTP header in the response from the endpoint to the client application, + * using the value of this response parameter as the value of the HTTP header. + *

+ * + *
+     * OAuth-Client-Attestation-Challenge: (The value of this {@code attestationChallenge} response parameter)
+     * 
+ * + * @return + * The expected attestation challenge value for Client Attestation + * PoP JWT. + * + * @since 4.39 + * @since Authlete 3.0.28 + * + * @see
+ * OAuth 2.0 Attestation-Based Client Authentication + */ + public String getAttestationChallenge() + { + return attestationChallenge; + } + + + /** + * Set the expected attestation challenge value for Client Attestation PoP + * JWT, which should be used as the value of the + * {@code OAuth-Client-Attestation-Challenge} HTTP header. + * + *

+ * When this response parameter is not null, the implementation of the + * token endpoint should add the {@code OAuth-Client-Attestation-Challenge} + * HTTP header in the response from the endpoint to the client application, + * using the value of this response parameter as the value of the HTTP header. + *

+ * + *
+     * OAuth-Client-Attestation-Challenge: (The value of this {@code attestationChallenge} response parameter)
+     * 
+ * + * @param challenge + * The expected attestation challenge value for Client Attestation + * PoP JWT. + * + * @since 4.39 + * @since Authlete 3.0.28 + * + * @see + * OAuth 2.0 Attestation-Based Client Authentication + */ + public void setAttestationChallenge(String challenge) + { + this.attestationChallenge = challenge; + } } diff --git a/src/main/java/com/authlete/common/dto/TokenRevokeRequest.java b/src/main/java/com/authlete/common/dto/TokenRevokeRequest.java index a5ed98fdea..90303484bc 100644 --- a/src/main/java/com/authlete/common/dto/TokenRevokeRequest.java +++ b/src/main/java/com/authlete/common/dto/TokenRevokeRequest.java @@ -79,6 +79,14 @@ * the API returns {@code 400 Bad Request}. *

* + * + *

+ * Bulk revocation with {@code clientIdentifier} only, {@code clientIdentifier} + {@code subject}, + * or {@code subject} only deletes at most 20 tokens per request + * (the default of {@code token.revoke.count.max} in {@code ServerConfiguration.java}). If the + * target has more than 20 tokens, the response {@code count} will be 20 and the remainder + * is left untouched. To fully wipe them, call the endpoint repeatedly until {@code count} returns 0. + *

* * @since 3.26 * @since Authlete 2.2.29 diff --git a/src/main/java/com/authlete/common/dto/TokenRevokeResponse.java b/src/main/java/com/authlete/common/dto/TokenRevokeResponse.java index 3286520b27..2b16591fba 100644 --- a/src/main/java/com/authlete/common/dto/TokenRevokeResponse.java +++ b/src/main/java/com/authlete/common/dto/TokenRevokeResponse.java @@ -26,12 +26,19 @@ public class TokenRevokeResponse extends ApiResponse { private static final long serialVersionUID = 1L; - + /** + * The maximum amount of tokens that can be revoked per request is 20. + * If 20 is returned then additional token revocation requests should be made until this value + * is 0 to ensure that all tokens are revoked. + */ private int count; /** * Get the number of revoked tokens. + * The maximum amount of tokens that can be revoked per request is 20. + * If 20 is returned then additional token revocation requests should be made until this value + * is 0 to ensure that all tokens are revoked. * * @return * The number of revoked tokens. @@ -44,6 +51,9 @@ public int getCount() /** * Set the number of revoked tokens. + * The maximum amount of tokens that can be revoked per request is 20. + * If 20 is returned then additional token revocation requests should be made until this value + * is 0 to ensure that all tokens are revoked. * * @param count * The number of revoked tokens. diff --git a/src/main/java/com/authlete/common/dto/TokenUpdateRequest.java b/src/main/java/com/authlete/common/dto/TokenUpdateRequest.java index 1435639af2..a9afe91398 100644 --- a/src/main/java/com/authlete/common/dto/TokenUpdateRequest.java +++ b/src/main/java/com/authlete/common/dto/TokenUpdateRequest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016-2023 Authlete, Inc. + * Copyright (C) 2016-2026 Authlete, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -90,6 +90,28 @@ *

* * + *
refreshToken
+ *
+ *

+ * The refresh token value associated with the access token to update. + *

+ *

+ * The value of the {@code accessToken} parameter and the {@code accessTokenHash} + * parameter take precedence. + *

+ *
+ * + *
refreshTokenHash
+ *
+ *

+ * The hash of the refresh token value associated with the access token to update. + *

+ *

+ * The value of the {@code accessToken} parameter, the {@code accessTokenHash} parameter + * and the {@code refreshToken} parameter take precedence. + *

+ *
+ * *
accessTokenValueUpdated
*
*

@@ -152,7 +174,7 @@ */ public class TokenUpdateRequest implements Serializable { - private static final long serialVersionUID = 8L; + private static final long serialVersionUID = 9L; private String accessToken; @@ -170,6 +192,8 @@ public class TokenUpdateRequest implements Serializable private AuthzDetails authorizationDetails; private boolean forExternalAttachment; private String tokenId; + private String refreshToken; + private String refreshTokenHash; /** @@ -928,4 +952,93 @@ public TokenUpdateRequest setTokenId(String tokenId) return this; } + + + /** + * Get the refresh token associated with the access token to update. + * + *

+ * The value of the {@code accessToken} parameter and the {@code accessTokenHash} + * parameter take precedence. + *

+ * + * @return + * The refresh token associated with the access token to update. + * + * @since 4.38 + */ + public String getRefreshToken() + { + return refreshToken; + } + + + /** + * Set the refresh token associated with the access token to update. + * + *

+ * The value of the {@code accessToken} parameter and the {@code accessTokenHash} + * parameter take precedence. + *

+ * + * @param refreshToken + * The refresh token associated with the access token to update. + * + * @return + * {@code this} object. + * + * @since 4.38 + */ + public TokenUpdateRequest setRefreshToken(String refreshToken) + { + this.refreshToken = refreshToken; + + return this; + } + + + /** + * Get the hash of the refresh token value associated with the access token to update. + * + *

+ * The value of the {@code accessToken} parameter, the {@code accessTokenHash} + * parameter and the {@code refreshToken} parameter take precedence. + *

+ * + * @return + * The hash of the refresh token value associated with the access token to + * update. + * + * @since 4.38 + */ + public String getRefreshTokenHash() + { + return refreshTokenHash; + } + + + /** + * Set the hash of the refresh token value associated with the access + * token to update. + * + *

+ * The value of the {@code accessToken} parameter, the {@code accessTokenHash} + * parameter and the {@code refreshToken} parameter take precedence. + *

+ * + * @param refreshTokenHash + * The hash of the refresh token value associated with the + * access token to update. + * + * @return + * {@code this} object. + * + * @since 4.38 + */ + public TokenUpdateRequest setRefreshTokenHash(String refreshTokenHash) + { + this.refreshTokenHash = refreshTokenHash; + + return this; + } } diff --git a/src/main/java/com/authlete/common/dto/UserInfoResponse.java b/src/main/java/com/authlete/common/dto/UserInfoResponse.java index dc15d2efb9..6820f70e16 100644 --- a/src/main/java/com/authlete/common/dto/UserInfoResponse.java +++ b/src/main/java/com/authlete/common/dto/UserInfoResponse.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015-2023 Authlete, Inc. + * Copyright (C) 2015-2025 Authlete, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -247,7 +247,7 @@ */ public class UserInfoResponse extends ApiResponse { - private static final long serialVersionUID = 7L; + private static final long serialVersionUID = 8L; /** @@ -392,6 +392,25 @@ public enum Action private boolean clientEntityIdUsed; + /** + * The location of the client's metadata document. + * + * @since 4.29 + * @since Authlete 3.0.22 + */ + private URI metadataDocumentLocation; + + + /** + * Flag which indicates whether the location of the client's metadata + * document was used when the request for the access token was made. + * + * @since 4.29 + * @since Authlete 3.0.22 + */ + private boolean metadataDocumentUsed; + + /** * "userinfo" in "claims" of an authorization request. * @since Authlete 2.2.1 @@ -801,6 +820,100 @@ public void setClientEntityIdUsed(boolean used) } + /** + * Get the location of the client's metadata document. This property + * holds a non-null value only when {@link #isMetadataDocumentUsed()} + * returns {@code true}. + * + * @return + * The location of the client's metadata document. + * + * @since 4.29 + * @since Authlete 3.0.22 + * + * @see + * OAuth Client ID Metadata Document + */ + public URI getMetadataDocumentLocation() + { + return metadataDocumentLocation; + } + + + /** + * Set the location of the client's metadata document. This property + * should hold a non-null value only when {@link #isMetadataDocumentUsed()} + * returns {@code true}. + * + * @param location + * The location of the client's metadata document. + * + * @since 4.29 + * @since Authlete 3.0.22 + * + * @see + * OAuth Client ID Metadata Document + */ + public void setMetadataDocumentLocation(URI location) + { + this.metadataDocumentLocation = location; + } + + + /** + * Get the flag which indicates whether the location of the client's + * metadata document was used as a client ID. + * + *

+ * This can happen when the service supports OAuth Client ID Metadata Document. + * (cf. {@link Service#isClientIdMetadataDocumentSupported()}) + *

+ * + * @return + * {@code true} if the location of client's metadata document + * was used as a client ID. + * + * @since 4.29 + * @since Authlete 3.0.22 + * + * @see + * OAuth Client ID Metadata Document + */ + public boolean isMetadataDocumentUsed() + { + return metadataDocumentUsed; + } + + + /** + * Set the flag which indicates whether the location of the client's + * metadata document was used as a client ID. + * + *

+ * This can happen when the service supports OAuth Client ID Metadata Document. + * (cf. {@link Service#isClientIdMetadataDocumentSupported()}) + *

+ * + * @param used + * {@code true} to indicate that the location of the client's + * metadata document was used as a client ID. + * + * @since 4.29 + * @since Authlete 3.0.22 + * + * @see + * OAuth Client ID Metadata Document + */ + public void setMetadataDocumentUsed(boolean used) + { + this.metadataDocumentUsed = used; + } + + /** * Get the value of the {@code "userinfo"} property in the {@code "claims"} * request parameter or in the {@code "claims"} property in an authorization diff --git a/src/main/java/com/authlete/common/package-info.java b/src/main/java/com/authlete/common/package-info.java index b50466ba76..ca6e225b94 100644 --- a/src/main/java/com/authlete/common/package-info.java +++ b/src/main/java/com/authlete/common/package-info.java @@ -17,11 +17,11 @@ * <dependency> * <groupId>com.authlete</groupId> * <artifactId>authlete-java-common</artifactId> - * <version>4.21</version> + * <version>4.23</version> * </dependency>
* - * @version 4.21 + * @version 4.23 */ package com.authlete.common; diff --git a/src/main/java/com/authlete/common/types/ClientAssertionType.java b/src/main/java/com/authlete/common/types/ClientAssertionType.java index ce3f877cac..450777d9b5 100644 --- a/src/main/java/com/authlete/common/types/ClientAssertionType.java +++ b/src/main/java/com/authlete/common/types/ClientAssertionType.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023-2024 Authlete, Inc. + * Copyright (C) 2023-2026 Authlete, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of @@ -64,6 +64,22 @@ public enum ClientAssertionType * @deprecated */ JWT_CLIENT_ATTESTATION((short)2, "urn:ietf:params:oauth:client-assertion-type:jwt-client-attestation"), + + + /** + * {@code "urn:ietf:params:oauth:client-assertion-type:jwt-spiffe"}. + * + *

+ * The client assertion type used by the OAuth SPIFFE Client Authentication. + *

+ * + * @see + * OAuth SPIFFE Client Authentication + * + * @since 4.37 + * @since Authlete 3.0.28 + */ + JWT_SPIFFE((short)3, "urn:ietf:params:oauth:client-assertion-type:jwt-spiffe"), ; diff --git a/src/main/java/com/authlete/common/types/ClientAuthMethod.java b/src/main/java/com/authlete/common/types/ClientAuthMethod.java index 56645e282c..7a192dc9ef 100644 --- a/src/main/java/com/authlete/common/types/ClientAuthMethod.java +++ b/src/main/java/com/authlete/common/types/ClientAuthMethod.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014-2024 Authlete, Inc. + * Copyright (C) 2014-2026 Authlete, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -157,6 +157,22 @@ public enum ClientAuthMethod * >OAuth 2.0 Attestation-Based Client Authentication */ ATTEST_JWT_CLIENT_AUTH((short)7, "attest_jwt_client_auth", 0x2), + + + /** + * {@code "spiffe_jwt"} (8). + * + *

+ * OAuth SPIFFE Client Authentication using JWT-SVID. + *

+ * + * @since 4.36 + * @since Authlete 3.0.28 + * + * @see + * OAuth SPIFFE Client Authentication + */ + SPIFFE_JWT((short)8, "spiffe_jwt", 0x2), ; diff --git a/src/main/java/com/authlete/common/types/ClientSource.java b/src/main/java/com/authlete/common/types/ClientSource.java new file mode 100644 index 0000000000..0db40b51a0 --- /dev/null +++ b/src/main/java/com/authlete/common/types/ClientSource.java @@ -0,0 +1,128 @@ +/* + * Copyright (C) 2025 Authlete, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.authlete.common.types; + + +/** + * The source of a client. + * + *
+ * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
enum valuedescription
{@link #STATIC_REGISTRATION}Static registration.
{@link #DYNAMIC_REGISTRATION} + * Dynamic registration defined in + * RFC 7591: + * OAuth 2.0 Dynamic Client Registration Protocol or + * + * OpenID Connect Dynamic Client Registration 1.0. + *
{@link #AUTOMATIC_REGISTRATION} + * Automatic registration defined in + * OpenID + * Federation 1.0. + *
{@link #EXPLICIT_REGISTRATION} + * Explicit registration defined in + * OpenID + * Federation 1.0. + *
{@link #METADATA_DOCUMENT} + * Discovery using + * + * OAuth Client ID Metadata Document. + *
+ *
+ * + * @since 4.29 + * @since Authlete 3.0.22 + */ +public enum ClientSource +{ + /** + * Static registration. + */ + STATIC_REGISTRATION, + + + /** + * Dynamic registration defined in + * RFC 7591: OAuth + * 2.0 Dynamic Client Registration Protocol or + * OpenID Connect Dynamic Client Registration 1.0. + * + * @see + * RFC 7591: OAuth 2.0 Dynamic Client Registration Protocol + * + * @see + * OpenID Connect Dynamic Client Registration 1.0 + */ + DYNAMIC_REGISTRATION, + + + /** + * Automatic registration defined in + * OpenID Federation 1.0. + * + * @see + * OpenID Federation 1.0 + */ + AUTOMATIC_REGISTRATION, + + + /** + * Explicit registration defined in + * OpenID Federation 1.0. + * + * @see + * OpenID Federation 1.0 + */ + EXPLICIT_REGISTRATION, + + + /** + * Discovery using + * OAuth Client ID Metadata Document. + * + * @see + * OAuth Client ID Metadata Document + */ + METADATA_DOCUMENT, + ; +} diff --git a/src/main/java/com/authlete/common/types/ErrorCode.java b/src/main/java/com/authlete/common/types/ErrorCode.java index 0cb8f13450..76cc10bca8 100644 --- a/src/main/java/com/authlete/common/types/ErrorCode.java +++ b/src/main/java/com/authlete/common/types/ErrorCode.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014-2023 Authlete, Inc. + * Copyright (C) 2014-2026 Authlete, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -44,7 +44,11 @@ * @see RFC 7591, 3.2.2. Client Registration Error Response * - * @author Takahiko Kawasaki + * @see OpenID for Verifiable Credential Issuance 1.0, 8.3.1.2. Credential Request Errors + * + * @see OpenID for Verifiable Credential Issuance 1.0, 11.3. Notification Error Response */ public enum ErrorCode { @@ -620,5 +624,124 @@ public enum ErrorCode * @since Authlete 3.0 */ use_dpop_nonce, + + + /** + * Requested Credential Configuration is unknown. + * + * @see + * OpenID for Verifiable Credential Issuance 1.0, 8.3.1.2. Credential Request Errors + * + * @since 4.28 + * @since Authlete 3.0.22 + */ + unknown_credential_configuration, + + + /** + * Requested Credential identifier is unknown. + * + * @see + * OpenID for Verifiable Credential Issuance 1.0, 8.3.1.2. Credential Request Errors + * + * @since 4.28 + * @since Authlete 3.0.22 + */ + unknown_credential_identifier, + + + /** + * The {@code proofs} parameter in the Credential Request uses an invalid + * nonce: at least one of the key proofs contains an invalid {@code c_nonce} + * value. The wallet should retrieve a new {@code c_nonce value} (refer to + * Section 7). + * + * @see + * OpenID for Verifiable Credential Issuance 1.0, 8.3.1.2. Credential Request Errors + * + * @since 4.28 + * @since Authlete 3.0.22 + */ + invalid_nonce, + + + /** + * The Credential Request has not been accepted by the Credential Issuer. + * The Wallet SHOULD treat this error as unrecoverable, meaning if + * received from a Credential Issuer the Credential cannot be issued. + * + * @see + * OpenID for Verifiable Credential Issuance 1.0, 8.3.1.2. Credential Request Errors + * + * @since 4.28 + * @since Authlete 3.0.22 + */ + credential_request_denied, + + + /** + * The {@code notification_id} in the Notification Request was invalid. + * + * @see + * OpenID for Verifiable Credential Issuance 1.0, 11.3. Notification Error Response + * + * @since 4.28 + * @since Authlete 3.0.22 + */ + invalid_notification_id, + + + /** + * The Notification Request is missing a required parameter, includes an + * unsupported parameter or parameter value, repeats the same parameter, + * or is otherwise malformed. + * + * @see + * OpenID for Verifiable Credential Issuance 1.0, 11.3. Notification Error Response + * + * @since 4.28 + * @since Authlete 3.0.22 + */ + invalid_notification_request, + + + /** + * The Client Attestation PoP JWT is not using an expected server-provided + * challenge. + * + * @see + * OAuth 2.0 Attestation-Based Client Authentication + * + * @since 4.39 + * @since Authlete 3.0.28 + */ + use_attestation_challenge, + + + /** + * The Client Attestation JWT is deemed to be not fresh enough to be + * accepted by the server. + * + * @see + * OAuth 2.0 Attestation-Based Client Authentication + * + * @since 4.39 + * @since Authlete 3.0.28 + */ + use_fresh_attestation, + + + /** + * The attestation or its proof of possession could not be successfully + * verified. + * + * @see + * OAuth 2.0 Attestation-Based Client Authentication + * + * @since 4.39 + * @since Authlete 3.0.28 + */ + invalid_client_attestation, ; } diff --git a/src/main/java/com/authlete/common/types/JWEZip.java b/src/main/java/com/authlete/common/types/JWEZip.java new file mode 100644 index 0000000000..512e75407f --- /dev/null +++ b/src/main/java/com/authlete/common/types/JWEZip.java @@ -0,0 +1,189 @@ +/* + * Copyright (C) 2025 Authlete, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.authlete.common.types; + + +import java.util.EnumSet; + + +/** + * {@code "zip"} (Compression Algorithm) Header Parameter Values for JWE. + * + * @see + * RFC 7516: JSON Web Encryption (JWE), + * Section 4.1.3. "zip" (Compression Algorithm) Header Parameter + * + * @see + * RFC 7518: JSON Web Algorithms (JWA), + * Section 7.3. JSON Web Encryption Compression Algorithms Registry + * + * @see + * IANA: JSON Object Signing and Encryption (JOSE), + * JSON Web Encryption Compression Algorithms + * + * @since 4.26 + */ +public enum JWEZip +{ + /** + * {@code "DEF"} (1); DEFLATE. + */ + DEF((short)1, "DEF"), + ; + + + private static final JWEZip[] sValues = values(); + private static final Helper sHelper = new Helper(sValues); + private final short mValue; + private final String mString; + + + private JWEZip(short value, String string) + { + mValue = value; + mString = string; + } + + + /** + * Get the name of this algorithm. + * + * One of the values listed in the table in + * JSON Web Encryption Compression Algorithms. + * + * @return + * The name of this algorithm. + */ + public String getName() + { + return mString; + } + + + /** + * Get the integer representation of this enum instance. + */ + public short getValue() + { + return mValue; + } + + + @Override + public String toString() + { + return mString; + } + + + /** + * Find an instance of this enum by a value. + * + * @param value + * The integer representation of the instance to find. + * + * @return + * An instance of this enum, or {@code null} if not found. + */ + public static JWEZip getByValue(short value) + { + if (value < 1 || sValues.length < value) + { + // Not found. + return null; + } + + return sValues[value - 1]; + } + + + /** + * Convert {@code String} to {@code JWEZip}. + * + * @param alg + * Algorithm name. For example, {@code "DEF"}. + * + * @return + * {@code JWEZip} instance, or {@code null}. + */ + public static JWEZip parse(String alg) + { + if (alg == null) + { + return null; + } + + for (JWEZip entry : sValues) + { + if (entry.mString.equals(alg)) + { + // Found. + return entry; + } + } + + // Not found. + return null; + } + + + public static int toBits(EnumSet set) + { + return sHelper.toBits(set); + } + + + public static JWEZip[] toArray(int bits) + { + return sHelper.toArray(bits); + } + + + public static EnumSet toSet(int bits) + { + return sHelper.toSet(bits); + } + + + public static EnumSet toSet(JWEZip[] array) + { + return sHelper.toSet(array); + } + + + private static class Helper extends EnumHelper + { + public Helper(JWEZip[] values) + { + super(JWEZip.class, values); + } + + + @Override + protected short getValue(JWEZip entry) + { + return entry.getValue(); + } + + + @Override + protected JWEZip[] newArray(int size) + { + return new JWEZip[size]; + } + } +} diff --git a/src/main/java/com/authlete/common/util/ClientMetadataControl.java b/src/main/java/com/authlete/common/util/ClientMetadataControl.java index 5d62a51efd..0a76924dcf 100644 --- a/src/main/java/com/authlete/common/util/ClientMetadataControl.java +++ b/src/main/java/com/authlete/common/util/ClientMetadataControl.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2022 Authlete, Inc. + * Copyright (C) 2022-2025 Authlete, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,6 +27,7 @@ public class ClientMetadataControl extends MapControl private boolean customIncluded; private boolean aliasPreferred; private boolean entityIdPreferred; + private boolean metadataDocumentLocationPreferred; @Override @@ -179,6 +180,9 @@ public ClientMetadataControl setAliasPreferred(boolean preferred) * @return * {@code true} if the entity ID is used as the value of the * {@code client_id} property when available. + * + * @see + * OpenID Federation 1.0 */ public boolean isEntityIdPreferred() { @@ -196,6 +200,9 @@ public boolean isEntityIdPreferred() * * @return * {@code this} object. + * + * @see + * OpenID Federation 1.0 */ public ClientMetadataControl setEntityIdPreferred(boolean preferred) { @@ -203,4 +210,53 @@ public ClientMetadataControl setEntityIdPreferred(boolean preferred) return this; } + + + /** + * Get the flag whether to use the location of the client's metadata + * document as the value of the {@code client_id} property when + * available. + * + * @return + * {@code true} if the location of the client's metadata + * document is used as the value of the {@code client_id} + * property when available. + * + * @since 4.29 + * @since Authlete 3.0.22 + * + * @see + * OAuth Client ID Metadata Document + */ + public boolean isMetadataDocumentLocationPreferred() + { + return metadataDocumentLocationPreferred; + } + + + /** + * Set the flag whether to use the location of the client's metadata + * document as the value of the {@code client_id} property when + * available. + * + * @param preferred + * {@code true} to use the location of the client's metadata + * document as the value of the {@code client_id} property + * when available. + * + * @return + * {@code this} object. + * + * @since 4.29 + * @since Authlete 3.0.22 + * + * @see + * OAuth Client ID Metadata Document + */ + public ClientMetadataControl setMetadataDocumentLocationPreferred(boolean preferred) + { + this.metadataDocumentLocationPreferred = preferred; + + return this; + } } diff --git a/src/main/java/com/authlete/common/util/digest/Digest.java b/src/main/java/com/authlete/common/util/digest/Digest.java new file mode 100644 index 0000000000..e9c2de26da --- /dev/null +++ b/src/main/java/com/authlete/common/util/digest/Digest.java @@ -0,0 +1,2444 @@ +package com.authlete.common.util.digest; + + +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.nio.ByteBuffer; +import java.security.DigestException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.security.Provider; +import java.util.HashMap; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; +import org.apache.commons.codec.BinaryEncoder; +import org.apache.commons.codec.EncoderException; + + +/** + * A wrapper class over MessageDigest with many {@code update} + * methods in a fluent style, meaning {@code update} methods + * can be chained. + * + *

+ * {@code update} methods are provided for all the primitive types + * and {@code String}, and their array types. In addition, + * {@link #updateJson(String)} has been available since the version + * 1.2 which updates the digest with the content of the given JSON. + * Note that {@link #update(String)} and {@link #updateJson(String)} + * are different. + *

+ * + *

+ * getInstanceXXX methods (where XXX + * is a pre-defined algorithm name with hyphens removed) such as + * {@link #getInstanceSHA1()} are provided. They won't throw + * {@code NoSuchAlgorithmException}. + *

+ * + *
+ * 
+ * // Compute SHA-1 of "Hello, world.".
+ * // 'digest' will have "2ae01472317d1935a84797ec1983ae243fc6aa28".
+ * String digest = Digest.{@link #getInstanceSHA1()}
+ *                 .{@link #update(String) update}("Hello, world.")
+ *                 .{@link #digestAsString()};
+ *
+ * // Compute SHA-1 of "Hello, world." and get the result as Base64.
+ * // 'digest' will have "KuAUcjF9GTWoR5fsGYOuJD/Gqig=".
+ * String digest = Digest.{@link #getInstanceSHA1()}
+ *                 .{@link #update(String) update}("Hello, world.")
+ *                 .{@link #digestAsString(BinaryEncoder) digestAsString}(new {@link
+ *                 org.apache.commons.codec.binary.Base64#Base64() Base64()});
+ *
+ * // Compute SHA-1 of two JSONs.
+ * // 'result1' and 'result2' will have the same value.
+ * String json1 = "{ \"key1\":\"value1\", \"key2\":\"value2\" }";
+ * String json2 = "{ \"key2\":\"value2\", \"key1\":\"value1\" }";
+ * String result1 = Digest.{@link #getInstanceSHA1()}.{@link #updateJson(String)
+ * updateJson}(json1).{@link #digestAsString()};
+ * String result2 = Digest.{@link #getInstanceSHA1()}.{@link #updateJson(String)
+ * updateJson}(json2).{@link #digestAsString()};
+ * 
+ * + * @author Takahiko Kawasaki + * @since 4.23 + */ +public class Digest implements Cloneable +{ + /** + * Features to control behaviors. + * + */ + public static enum Feature + { + /** + * Ignore JSON key-value entries whose value is {@code null}. + * In other words, JSON key-value entries whose value is + * {@code null} are treated as if they did not exist. + * + *

+ * If this feature is enabled, two JSONs below generate + * the same digest value. + *

+ * + *
+         * { "key1":"value1", "key2":null }
+         * { "key1":"value1" }
+         * 
+ * + *

+ * The default value is 'disabled'. + *

+ */ + IGNORE_JSON_OBJECT_ENTRY_WITH_VALUE_NULL, + + + /** + * Ignore JSON key-value entries whose value is {@code false}. + * In other words, JSON key-value entries whose value is + * {@code false} are treated as if they did not exist. + * + *

+ * If this feature is enabled, two JSONs below generate + * the same digest value. + *

+ * + *
+         * { "key1":"value1", "key2":false }
+         * { "key1":"value1" }
+         * 
+ * + *

+ * The default value is 'disabled'. + *

+ */ + IGNORE_JSON_OBJECT_ENTRY_WITH_VALUE_FALSE, + + + /** + * Ignore JSON key-value entries whose value is zero. + * In other words, JSON key-value entries whose value is + * zero are treated as if they did not exist. + * + *

+ * If this feature is enabled, two JSONs below generate + * the same digest value. + *

+ * + *
+         * { "key1":"value1", "key2":0, "key3":0.0 }
+         * { "key1":"value1" }
+         * 
+ * + *

+ * The default value is 'disabled'. + *

+ */ + IGNORE_JSON_OBJECT_ENTRY_WITH_VALUE_ZERO, + + + /** + * Ignore JSON key-value entries whose value is an empty string. + * In other words, JSON key-value entries whose value is an empty + * string are treated as if they did not exist. + * + *

+ * If this feature is enabled, two JSONs below generate + * the same digest value. + *

+ * + *
+         * { "key1":"value1", "key2":"" }
+         * { "key1":"value1" }
+         * 
+ * + *

+ * The default value is 'disabled'. + *

+ */ + IGNORE_JSON_OBJECT_ENTRY_WITH_VALUE_EMPTY_STRING, + + + /** + * Ignore JSON key-value entries whose value is an empty array. + * In other words, JSON key-value entries whose value is an empty + * array are treated as if they did not exist. + * + *

+ * If this feature is enabled, two JSONs below generate + * the same digest value. + *

+ * + *
+         * { "key1":"value1", "key2":[] }
+         * { "key1":"value1" }
+         * 
+ * + *

+ * The default value is 'disabled'. + *

+ */ + IGNORE_JSON_OBJECT_ENTRY_WITH_VALUE_EMPTY_ARRAY, + + + /** + * Ignore JSON key-value entries whose value is an empty object. + * In other words, JSON key-value entries whose value is an empty + * object are treated as if they did not exist. + * + *

+ * If this feature is enabled, two JSONs below generate + * the same digest value. + *

+ * + *
+         * { "key1":"value1", "key2":{} }
+         * { "key1":"value1" }
+         * 
+ * + *

+ * The default value is 'disabled'. + *

+ */ + IGNORE_JSON_OBJECT_ENTRY_WITH_VALUE_EMPTY_OBJECT, + + + /** + * Sort keys of JSON key-value entries before updating. + * + *

+ * If this feature is enabled, two JSONs below generate + * the same digest value. + *

+ * + *
+         * { "key1":"value1", "key2":"value2" }
+         * { "key2":"value2", "key1":"value1" }
+         * 
+ * + *

+ * The default value is 'enabled'. + *

+ */ + SORT_JSON_OBJECT_ENTRY_KEYS + } + + + /** + * Characters used to generate a hex string. + */ + private static final char[] mHexArray = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; + + + /** + * The wrapped messaged digest object. + */ + private MessageDigest mMessageDigest; + + + /** + * Features (configuration). + */ + private HashMap mFeatures; + + + /** + * Constructor with a {@link MessageDigest} instance. + * + * @param messageDigest + * A {@link MessageDigest} instance wrapped in this + * {@code Digest} instance. + * + * @throws IllegalArgumentException + * The given argument is null. + */ + public Digest(MessageDigest messageDigest) + { + if (messageDigest == null) + { + throw new IllegalArgumentException("messageDigest is null"); + } + + mMessageDigest = messageDigest; + mFeatures = createFeatureMap(); + } + + + /** + * Create a feature map with default values. + */ + private HashMap createFeatureMap() + { + HashMap map = new HashMap(); + + map.put(Feature.IGNORE_JSON_OBJECT_ENTRY_WITH_VALUE_NULL, Boolean.FALSE); + map.put(Feature.IGNORE_JSON_OBJECT_ENTRY_WITH_VALUE_FALSE, Boolean.FALSE); + map.put(Feature.IGNORE_JSON_OBJECT_ENTRY_WITH_VALUE_ZERO, Boolean.FALSE); + map.put(Feature.IGNORE_JSON_OBJECT_ENTRY_WITH_VALUE_EMPTY_STRING, Boolean.FALSE); + map.put(Feature.IGNORE_JSON_OBJECT_ENTRY_WITH_VALUE_EMPTY_ARRAY, Boolean.FALSE); + map.put(Feature.IGNORE_JSON_OBJECT_ENTRY_WITH_VALUE_EMPTY_OBJECT, Boolean.FALSE); + map.put(Feature.SORT_JSON_OBJECT_ENTRY_KEYS, Boolean.TRUE); + + return map; + } + + + /** + * Constructor with an algorithm name. + * + *

+ * This constructor is equivalent to {@link #Digest(MessageDigest) this}{@code + * (}{@link MessageDigest#getInstance(String) MessageDigest.getInstance}{@code + * (algorithm))}. + *

+ * + * @param algorithm + * Algorithm name such as "MD5" and "SHA-1". + * + * @throws NoSuchAlgorithmException + * No provider supports the specified algorithm. + */ + public Digest(String algorithm) throws NoSuchAlgorithmException + { + this(MessageDigest.getInstance(algorithm)); + } + + + /** + * Constructor with an algorithm name and a provider. + * + *

+ * This constructor is equivalent to {@link #Digest(MessageDigest) this}{@code + * (}{@link MessageDigest#getInstance(String,String) MessageDigest.getInstance}{@code + * (algorithm, provider))}. + *

+ * + * @param algorithm + * Algorithm name such as "MD5" and "SHA-1". + * + * @param provider + * Provider name. + * + * @throws NoSuchAlgorithmException + * The provider does not support the specified algorithm. + * + * @throws NoSuchProviderException + * The specified provider is not registered in the security provider list. + */ + public Digest(String algorithm, String provider) throws NoSuchAlgorithmException, NoSuchProviderException + { + this(MessageDigest.getInstance(algorithm, provider)); + } + + + /** + * Constructor with an algorithm name and a provider. + * + *

+ * This constructor is equivalent to {@link #Digest(MessageDigest) this}{@code + * (}{@link MessageDigest#getInstance(String,Provider) MessageDigest.getInstance}{@code + * (algorithm, provider))}. + *

+ * + * @param algorithm + * Algorithm name such as "MD5" and "SHA-1". + * + * @param provider + * Provider. + * + * @throws NoSuchAlgorithmException + * The provider does not support the specified algorithm. + */ + public Digest(String algorithm, Provider provider) throws NoSuchAlgorithmException + { + this(MessageDigest.getInstance(algorithm, provider)); + } + + + /** + * Create a {@code Digest} instance with the specified algorithm. + * + *

+ * This method creates a {@link MessageDigest} instance by + * {@link MessageDigest#getInstance(String)} and wraps it + * in a {@code Digest} instance. + *

+ * + * @param algorithm + * Algorithm name such as "MD5" and "SHA-1". + * + * @return + * A {@code Digest} instance that implements the specified algorithm. + * + * @throws NoSuchAlgorithmException + * No provider supports the specified algorithm. + */ + public static Digest getInstance(String algorithm) throws NoSuchAlgorithmException + { + return new Digest(algorithm); + } + + + /** + * Create a {@code Digest} instance with the specified algorithm. + * + *

+ * This method creates a {@link MessageDigest} instance by + * {@link MessageDigest#getInstance(String, String)} and wraps it + * in a {@code Digest} instance. + *

+ * + * @param algorithm + * Algorithm name such as "MD5" and "SHA-1". + * + * @param provider + * Provider name. + * + * @return + * A {@code Digest} instance that implements the specified algorithm. + * + * @throws NoSuchAlgorithmException + * The provider does not support the specified algorithm. + * + * @throws NoSuchProviderException + * The specified provider is not registered in the security provider list. + */ + public static Digest getInstance(String algorithm, String provider) + throws NoSuchAlgorithmException, NoSuchProviderException + { + return new Digest(algorithm, provider); + } + + + /** + * Create a {@code Digest} instance with the specified algorithm. + * + *

+ * This method creates a {@link MessageDigest} instance by + * {@link MessageDigest#getInstance(String, Provider)} and wraps it + * in a {@code Digest} instance. + *

+ * + * @param algorithm + * Algorithm name such as "MD5" and "SHA-1". + * + * @param provider + * Provider. + * + * @return + * A {@code Digest} instance that implements the specified algorithm. + * + * @throws NoSuchAlgorithmException + * The provider does not support the specified algorithm. + */ + public static Digest getInstance(String algorithm, Provider provider) throws NoSuchAlgorithmException + { + return new Digest(algorithm, provider); + } + + + /** + * Create a {@code Digest} instance with the specified algorithm. + * + *

+ * This method exists just to ignore {@code NoSuchAlgorithmException}. + *

+ * + * @param algorithm + * Algorithm name such as "MD5" and "SHA-1". + * + * @return + * A {@code Digest} instance that implements the specified algorithm. + */ + private static Digest getInstancePredefined(String algorithm) + { + try + { + return getInstance(algorithm); + } + catch (NoSuchAlgorithmException e) + { + // This won't happen. + return null; + } + } + + + /** + * Create a {@code Digest} instance that implements MD2. + * + * @return + * A {@code Digest} instance that implements MD2. + */ + public static Digest getInstanceMD2() + { + return getInstancePredefined("MD2"); + } + + + /** + * Create a {@code Digest} instance that implements MD5. + * + * @return + * A {@code Digest} instance that implements MD5. + */ + public static Digest getInstanceMD5() + { + return getInstancePredefined("MD5"); + } + + + /** + * Create a {@code Digest} instance that implements SHA-1. + * + * @return + * A {@code Digest} instance that implements SHA-1. + */ + public static Digest getInstanceSHA1() + { + return getInstancePredefined("SHA-1"); + } + + + /** + * Create a {@code Digest} instance that implements SHA-256. + * + * @return + * A {@code Digest} instance that implements SHA-256. + */ + public static Digest getInstanceSHA256() + { + return getInstancePredefined("SHA-256"); + } + + + /** + * Create a {@code Digest} instance that implements SHA-384. + * + * @return + * A {@code Digest} instance that implements SHA-384. + */ + public static Digest getInstanceSHA384() + { + return getInstancePredefined("SHA-384"); + } + + + /** + * Create a {@code Digest} instance that implements SHA-512. + * + * @return + * A {@code Digest} instance that implements SHA-512. + */ + public static Digest getInstanceSHA512() + { + return getInstancePredefined("SHA-512"); + } + + + /** + * Get the algorithm name. + * + *

+ * This method just calls {@link MessageDigest#getAlgorithm() + * getAlgorithm()} of the wrapped {@code MessageDigest} instance. + *

+ * + * @return + * Algorithm name. + */ + public String getAlgorithm() + { + return mMessageDigest.getAlgorithm(); + } + + + /** + * Get the length of the digest in bytes. + * + *

+ * This method just calls {@link MessageDigest#getDigestLength() + * getDigestLength()} of the wrapped {@code MessageDigest} instance. + *

+ * + * @return + * Length of the digest in bytes. + */ + public int getDigestLength() + { + return mMessageDigest.getDigestLength(); + } + + + /** + * Get the provider. + * + *

+ * This method just calls {@link MessageDigest#getProvider() + * getProvider()} of the wrapped {@code MessageDigest} instance. + *

+ * + * @return + * Provider. + */ + public Provider getProvider() + { + return mMessageDigest.getProvider(); + } + + + /** + * Get the wrapped {@code MessageDigest} instance. + * + * @return + * The {@code MessageDigest} instance that has been + * given to the constructor. + */ + public MessageDigest getWrappedMessageDigest() + { + return mMessageDigest; + } + + + /** + * Get a clone of this {@code Digest} instance. + * + * @return + * A cloned object. + * + * @throws CloneNotSupportedException + * The implementation does not support {@code clone} operation. + */ + @SuppressWarnings("unchecked") + @Override + public Object clone() throws CloneNotSupportedException + { + Digest cloned = (Digest)super.clone(); + + cloned.mMessageDigest = (MessageDigest)mMessageDigest.clone(); + cloned.mFeatures = (HashMap)mFeatures.clone(); + + return cloned; + } + + + /** + * Complete the hash computation. The digest is reset after + * this call is made. + * + *

+ * This method just calls {@link MessageDigest#digest() digest()} + * method of the wrapped {@code MessageDigest} instance. + *

+ * + * @return + * The resulting hash value. + */ + public byte[] digest() + { + return mMessageDigest.digest(); + } + + + /** + * Perform the final update with the given byte array, and then + * complete the hash computation. The digest is reset after + * this call is made. + * + *

+ * This method just calls {@link MessageDigest#digest(byte[]) + * digest(byte[])} method of the wrapped {@code MessageDigest} + * instance. + *

+ * + * @param input + * Byte array used for the last update. + * + * @return + * The resulting hash value. + */ + public byte[] digest(byte[] input) + { + return mMessageDigest.digest(input); + } + + + /** + * Complete the hash computation. The digest is reset after + * this call is made. + * + *

+ * This method just calls {@link MessageDigest#digest(byte[],int,int) + * digest(byte[], int, int)} method of the wrapped {@code MessageDigest} + * instance. + *

+ * + * @param output + * Output buffer for the computed digest. + * + * @param offset + * Offset into the output buffer to begin storing the digest. + * + * @param length + * Number of bytes within the output buffer allotted for the digest. + * + * @return + * The resulting hash value. + * + * @throws DigestException + * The {@code digest} method of the {@link MessageDigest} class + * failed. + */ + public int digest(byte[] output, int offset, int length) throws DigestException + { + return mMessageDigest.digest(output, offset, length); + } + + + /** + * Complete the hash computation and get the resulting hash value + * as a hex string. The digest is reset after this call is made. + * + *

+ * This method calls {@link #digest()} method and converts the result + * to a String object. + *

+ * + * @return + * The result hash value represented in a hex String. + */ + public String digestAsString() + { + return bytesToHex(digest()); + } + + + /** + * Perform the final update with the given byte array, and then + * complete the hash computation and get the resulting hash value + * as a hex string. The digest is reset after this call is made. + * + *

+ * This method calls {@link #digest(byte[])} method and converts + * the result to a String object. + *

+ * + * @param input + * Byte array used for the last update. + * + * @return + * The result hash value represented in a hex String. + */ + public String digestAsString(byte[] input) + { + return bytesToHex(digest(input)); + } + + + /** + * Complete the hash computation and get the resulting hash value + * as a string. The given encoder is used to convert the digest + * value to a string. + * + *

+ * This method is an alias of {@link #digestAsString(byte[], + * BinaryEncoder) digestAsString((byte[])null, encoder)}. + *

+ * + * @param encoder + * Encoder to convert a digest value to a byte array + * whose elements are printable characters. For example, + * {@link org.apache.commons.codec.binary.Base64}. + * + * @return + * The result hash value encoded by the encoder. + * + * @throws RuntimeException + * If the encoder throws {@link EncoderException}, + * a {@code RuntimeException} wrapping the + * {@code EncoderException} is thrown. + */ + public String digestAsString(BinaryEncoder encoder) + { + return digestAsString((byte[])null, encoder); + } + + + /** + * Perform the final update with the given byte array, and then + * complete the hash computation and get the resulting hash value + * as a string. The given encoder is used to convert the digest + * value to a string. + * + * @param input + * Byte array used for the last update. If {@code null} + * is given, it is just ignored. + * + * @param encoder + * Encoder to convert a digest value to a byte array + * whose elements are printable characters. For example, + * {@link org.apache.commons.codec.binary.Base64}. + * + * @return + * The result hash value encoded by the encoder. + * + * @throws IllegalArgumentException + * {@code encoder} is {@code null}. + * + * @throws RuntimeException + * If the encoder throws {@link EncoderException}, + * a {@code RuntimeException} wrapping the + * {@code EncoderException} is thrown. + */ + public String digestAsString(byte[] input, BinaryEncoder encoder) + { + if (encoder == null) + { + throw new IllegalArgumentException("encoder is null."); + } + + // Compute the digest value. + byte[] digest = (input != null) ? digest(input) : digest(); + + // Encoded value. + byte[] encoded = null; + + try + { + // Encode the digest value. + encoded = encoder.encode(digest); + } + catch (EncoderException e) + { + // Failed to encode the digest value. + throw new RuntimeException("Failed to encode the digest value.", e); + } + + try + { + // Convert the byte array into a string. + return new String(encoded, "UTF-8"); + } + catch (UnsupportedEncodingException e) + { + // This won't happen. + return null; + } + } + + + /** + * Reset the wrapped {@code MessageDigest} instance. + * + * @return + * {@code this} object. + */ + public Digest reset() + { + mMessageDigest.reset(); + + return this; + } + + + /** + * Update the wrapped {@code MessageDigest} object with the + * given input data. + * + *

+ * This method calls {@link MessageDigest#update(byte) update(byte)} + * method of the wrapped {@code MessageDigest} instance. + *

+ * + * @param input + * Input data. + * + * @return + * {@code this} object. + */ + public Digest update(byte input) + { + mMessageDigest.update(input); + + return this; + } + + + /** + * Update the wrapped {@code MessageDigest} object with the + * given input data. + * + *

+ * This method calls {@link MessageDigest#update(byte[]) update(byte[])} + * method of the wrapped {@code MessageDigest} instance. + *

+ * + * @param input + * Input data. + * + * @return + * {@code this} object. + */ + public Digest update(byte[] input) + { + mMessageDigest.update(input); + + return this; + } + + + /** + * Update the wrapped {@code MessageDigest} object with the + * given input data. + * + *

+ * This method calls {@link MessageDigest#update(byte[],int,int) + * update(byte[], int, int)} method of the wrapped + * {@code MessageDigest} instance. + *

+ * + * @param input + * Input data. + * + * @param offset + * The offset to start from in the array. + * + * @param length + * The number of elements to use, starting at offset. + * + * @return + * {@code this} object. + */ + public Digest update(byte[] input, int offset, int length) + { + mMessageDigest.update(input, offset, length); + + return this; + } + + + /** + * Update the wrapped {@code MessageDigest} object with the + * given input data. + * + *

+ * This method calls {@link MessageDigest#update(ByteBuffer) + * update(ByteBuffer)} method of the wrapped {@code MessageDigest} + * instance. + *

+ * + * @param input + * Input data. + * + * @return + * {@code this} object. + */ + public Digest update(ByteBuffer input) + { + mMessageDigest.update(input); + + return this; + } + + + /** + * Check the validity of the combination of the given parameters. + * + * @param size + * Size of an array. + * + * @param offset + * Offset in the array. + * + * @param length + * Length of data to use. + * + * @throws IllegalArgumentException + *
    + *
  • {@code offset < 0} + *
  • {@code size <= offset} + *
  • {@code length < 0} + *
  • {@code size < (offset + length)} + *
+ */ + private void checkRange(int size, int offset, int length) + { + String message = null; + + if (offset < 0) + { + message = "The offset is less than 0."; + } + else if (size <= offset) + { + message = "The offset is equal to or greater than the size."; + } + else if (length < 0) + { + message = "The length is less than 0."; + } + else if (size < (offset + length)) + { + message = "The sum of the offset and the length is greater than the size."; + } + + if (message != null) + { + throw new IllegalArgumentException(message); + } + } + + + /** + * Update the wrapped {@code MessageDigest} object with the + * given input data. + * + *

+ * This method is an alias of {@link #update(boolean) + * update}{@code (input.booleanValue())}. + *

+ * + * @param input + * Input data. + * + * @return + * {@code this} object. + */ + public Digest update(Boolean input) + { + if (input == null) + { + return this; + } + + return update(input.booleanValue()); + } + + + /** + * Update the wrapped {@code MessageDigest} object with the + * given input data. + * + *

+ * This method is an alias of {@link #update(Boolean[], int, int) + * update}{@code (input, 0, input.length)}. + *

+ * + * @param input + * Input data. If null is given, update is not performed. + * + * @return + * {@code this} object. + */ + public Digest update(Boolean[] input) + { + if (input == null) + { + return this; + } + + return update(input, 0, input.length); + } + + + /** + * Update the wrapped {@code MessageDigest} object with the + * given input data. + * + *

+ * This method calls {@link #update(Boolean)} for each + * array element which is in the specified range. + *

+ * + * @param input + * Input data. + * + * @param offset + * The offset to start from in the array. + * + * @param length + * The number of elements to use, starting at offset. + * + * @return + * {@code this} object. + * + * @throws IllegalArgumentException + * The range specified by the parameters is invalid. + */ + public Digest update(Boolean[] input, int offset, int length) + { + if (input == null) + { + return this; + } + + checkRange(input.length, offset, length); + + for (int i = 0; i < length; ++i) + { + update(input[i + offset]); + } + + return this; + } + + + /** + * Update the wrapped {@code MessageDigest} object with the + * given input data. + * + * @param input + * Input data. {@code true} results in {@link #update(byte) + * update}{@code ((byte)1)} and {@code false} results in + * {@link #update(byte) update}{@code ((byte)0)}. + * + * @return + * {@code this} object. + */ + public Digest update(boolean input) + { + return update((byte)(input ? 1 : 0)); + } + + + /** + * Update the wrapped {@code MessageDigest} object with the + * given input data. + * + *

+ * This method is an alias of {@link #update(boolean[], int, int) + * update}{@code (input, 0, input.length)}. + *

+ * + * @param input + * Input data. If null is given, update is not performed. + * + * @return + * {@code this} object. + */ + public Digest update(boolean[] input) + { + if (input == null) + { + return this; + } + + return update(input, 0, input.length); + } + + + /** + * Update the wrapped {@code MessageDigest} object with the + * given input data. + * + *

+ * This method calls {@link #update(boolean)} for each + * array element which is in the specified range. + *

+ * + * @param input + * Input data. + * + * @param offset + * The offset to start from in the array. + * + * @param length + * The number of elements to use, starting at offset. + * + * @return + * {@code this} object. + * + * @throws IllegalArgumentException + * The range specified by the parameters is invalid. + */ + public Digest update(boolean[] input, int offset, int length) + { + if (input == null) + { + return this; + } + + checkRange(input.length, offset, length); + + for (int i = 0; i < length; ++i) + { + update(input[i + offset]); + } + + return this; + } + + + /** + * Update the wrapped {@code MessageDigest} object with the + * given input data. + * + *

+ * This method is an alias of {@link #update(char) + * update}{@code (input.charValue())}. + *

+ * + * @param input + * Input data. + * + * @return + * {@code this} object. + */ + public Digest update(Character input) + { + if (input == null) + { + return this; + } + + return update(input.charValue()); + } + + + /** + * Update the wrapped {@code MessageDigest} object with the + * given input data. + * + *

+ * This method is an alias of {@link #update(Character[], int, int) + * update}{@code (input, 0, input.length)}. + *

+ * + * @param input + * Input data. If null is given, update is not performed. + * + * @return + * {@code this} object. + */ + public Digest update(Character[] input) + { + if (input == null) + { + return this; + } + + return update(input, 0, input.length); + } + + + /** + * Update the wrapped {@code MessageDigest} object with the + * given input data. + * + *

+ * This method calls {@link #update(Character)} for each + * array element which is in the specified range. + *

+ * + * @param input + * Input data. + * + * @param offset + * The offset to start from in the array. + * + * @param length + * The number of elements to use, starting at offset. + * + * @return + * {@code this} object. + * + * @throws IllegalArgumentException + * The range specified by the parameters is invalid. + */ + public Digest update(Character[] input, int offset, int length) + { + if (input == null) + { + return this; + } + + checkRange(input.length, offset, length); + + for (int i = 0; i < length; ++i) + { + update(input[i + offset]); + } + + return this; + } + + + /** + * Update the wrapped {@code MessageDigest} object with the + * given input data. + * + *

+ * This method calls {@link #update(byte) update}{@code + * ((byte)((input >> 8) & 0xff))} and {@link #update(byte) + * update}({@code ((byte)(input >> 0) & 0xff)}. + *

+ * + * @param input + * Input data. + * + * @return + * {@code this} object. + */ + public Digest update(char input) + { + update((byte)((input >> 8) & 0xff)); + update((byte)((input >> 0) & 0xff)); + + return this; + } + + + /** + * Update the wrapped {@code MessageDigest} object with the + * given input data. + * + *

+ * This method is an alias of {@link #update(char[], int, int) + * update}{@code (input, 0, input.length)}. + *

+ * + * @param input + * Input data. If null is given, update is not performed. + * + * @return + * {@code this} object. + */ + public Digest update(char[] input) + { + if (input == null) + { + return this; + } + + return update(input, 0, input.length); + } + + + /** + * Update the wrapped {@code MessageDigest} object with the + * given input data. + * + *

+ * This method calls {@link #update(char)} for each + * array element which is in the specified range. + *

+ * + * @param input + * Input data. + * + * @param offset + * The offset to start from in the array. + * + * @param length + * The number of elements to use, starting at offset. + * + * @return + * {@code this} object. + * + * @throws IllegalArgumentException + * The range specified by the parameters is invalid. + */ + public Digest update(char[] input, int offset, int length) + { + if (input == null) + { + return this; + } + + checkRange(input.length, offset, length); + + for (int i = 0; i < length; ++i) + { + update(input[i + offset]); + } + + return this; + } + + + /** + * Update the wrapped {@code MessageDigest} object with the + * given input data. + * + *

+ * This method calls {@link #update(byte) update}{@code + * ((byte)((input >> 8) & 0xff))} and {@link #update(byte) + * update}({@code ((byte)(input >> 0) & 0xff)}. + *

+ * + * @param input + * Input data. + * + * @return + * {@code this} object. + */ + public Digest update(short input) + { + update((byte)((input >> 8) & 0xff)); + update((byte)((input >> 0) & 0xff)); + + return this; + } + + + /** + * Update the wrapped {@code MessageDigest} object with the + * given input data. + * + *

+ * This method is an alias of {@link #update(short[], int, int) + * update}{@code (input, 0, input.length)}. + *

+ * + * @param input + * Input data. If null is given, update is not performed. + * + * @return + * {@code this} object. + */ + public Digest update(short[] input) + { + if (input == null) + { + return this; + } + + return update(input, 0, input.length); + } + + + /** + * Update the wrapped {@code MessageDigest} object with the + * given input data. + * + *

+ * This method calls {@link #update(short)} for each + * array element which is in the specified range. + *

+ * + * @param input + * Input data. + * + * @param offset + * The offset to start from in the array. + * + * @param length + * The number of elements to use, starting at offset. + * + * @return + * {@code this} object. + * + * @throws IllegalArgumentException + * The range specified by the parameters is invalid. + */ + public Digest update(short[] input, int offset, int length) + { + if (input == null) + { + return this; + } + + checkRange(input.length, offset, length); + + for (int i = 0; i < length; ++i) + { + update(input[i + offset]); + } + + return this; + } + + + /** + * Update the wrapped {@code MessageDigest} object with the + * given input data. + * + *

+ * This method calls {@link #update(byte)} for each byte of the + * 4 bytes from MSB to LSB (from {@code ((input >> 24) & 0xff)} + * to {@code ((input >> 0) & 0xff)}). + *

+ * + * @param input + * Input data. + * + * @return + * {@code this} object. + */ + public Digest update(int input) + { + update((byte)((input >> 24) & 0xff)); + update((byte)((input >> 16) & 0xff)); + update((byte)((input >> 8) & 0xff)); + update((byte)((input >> 0) & 0xff)); + + return this; + } + + + /** + * Update the wrapped {@code MessageDigest} object with the + * given input data. + * + *

+ * This method is an alias of {@link #update(int[], int, int) + * update}{@code (input, 0, input.length)}. + *

+ * + * @param input + * Input data. If null is given, update is not performed. + * + * @return + * {@code this} object. + */ + public Digest update(int[] input) + { + if (input == null) + { + return this; + } + + return update(input, 0, input.length); + } + + + /** + * Update the wrapped {@code MessageDigest} object with the + * given input data. + * + *

+ * This method calls {@link #update(int)} for each + * array element which is in the specified range. + *

+ * + * @param input + * Input data. + * + * @param offset + * The offset to start from in the array. + * + * @param length + * The number of elements to use, starting at offset. + * + * @return + * {@code this} object. + * + * @throws IllegalArgumentException + * The range specified by the parameters is invalid. + */ + public Digest update(int[] input, int offset, int length) + { + if (input == null) + { + return this; + } + + checkRange(input.length, offset, length); + + for (int i = 0; i < length; ++i) + { + update(input[i + offset]); + } + + return this; + } + + + /** + * Update the wrapped {@code MessageDigest} object with the + * given input data. + * + *

+ * This method calls {@link #update(byte)} for each byte of the + * 8 bytes from MSB to LSB (from {@code ((input >> 54) & 0xff)} + * to {@code ((input >> 0) & 0xff)}). + *

+ * + * @param input + * Input data. + * + * @return + * {@code this} object. + */ + public Digest update(long input) + { + update((byte)((input >> 54) & 0xff)); + update((byte)((input >> 48) & 0xff)); + update((byte)((input >> 40) & 0xff)); + update((byte)((input >> 32) & 0xff)); + update((byte)((input >> 24) & 0xff)); + update((byte)((input >> 16) & 0xff)); + update((byte)((input >> 8) & 0xff)); + update((byte)((input >> 0) & 0xff)); + + return this; + } + + + /** + * Update the wrapped {@code MessageDigest} object with the + * given input data. + * + *

+ * This method is an alias of {@link #update(long[], int, int) + * update}{@code (input, 0, input.length)}. + *

+ * + * @param input + * Input data. If null is given, update is not performed. + * + * @return + * {@code this} object. + */ + public Digest update(long[] input) + { + if (input == null) + { + return this; + } + + return update(input, 0, input.length); + } + + + /** + * Update the wrapped {@code MessageDigest} object with the + * given input data. + * + *

+ * This method calls {@link #update(long)} for each + * array element which is in the specified range. + *

+ * + * @param input + * Input data. + * + * @param offset + * The offset to start from in the array. + * + * @param length + * The number of elements to use, starting at offset. + * + * @return + * {@code this} object. + * + * @throws IllegalArgumentException + * The range specified by the parameters is invalid. + */ + public Digest update(long[] input, int offset, int length) + { + if (input == null) + { + return this; + } + + checkRange(input.length, offset, length); + + for (int i = 0; i < length; ++i) + { + update(input[i + offset]); + } + + return this; + } + + + /** + * Update the wrapped {@code MessageDigest} object with the + * given input data. + * + *

+ * This method converts the given {@code float} value to a + * {@code int} by {@link Float#floatToRawIntBits(float)} + * and then passes it to {@link #update(int)}. + *

+ * + * @param input + * Input data. + * + * @return + * {@code this} object. + */ + public Digest update(float input) + { + return update(Float.floatToRawIntBits(input)); + } + + + /** + * Update the wrapped {@code MessageDigest} object with the + * given input data. + * + *

+ * This method is an alias of {@link #update(float[], int, int) + * update}{@code (input, 0, input.length)}. + *

+ * + * @param input + * Input data. If null is given, update is not performed. + * + * @return + * {@code this} object. + */ + public Digest update(float[] input) + { + if (input == null) + { + return this; + } + + return update(input, 0, input.length); + } + + + /** + * Update the wrapped {@code MessageDigest} object with the + * given input data. + * + *

+ * This method calls {@link #update(float)} for each + * array element which is in the specified range. + *

+ * + * @param input + * Input data. + * + * @param offset + * The offset to start from in the array. + * + * @param length + * The number of elements to use, starting at offset. + * + * @return + * {@code this} object. + * + * @throws IllegalArgumentException + * The range specified by the parameters is invalid. + */ + public Digest update(float[] input, int offset, int length) + { + if (input == null) + { + return this; + } + + checkRange(input.length, offset, length); + + for (int i = 0; i < length; ++i) + { + update(input[i + offset]); + } + + return this; + } + + + /** + * Update the wrapped {@code MessageDigest} object with the + * given input data. + * + *

+ * This method converts the given {@code double} value to a + * {@code long} by {@link Double#doubleToRawLongBits(double)} + * and then passes it to {@link #update(long)}. + *

+ * + * @param input + * Input data. + * + * @return + * {@code this} object. + */ + public Digest update(double input) + { + return update(Double.doubleToRawLongBits(input)); + } + + + /** + * Update the wrapped {@code MessageDigest} object with the + * given input data. + * + *

+ * This method is an alias of {@link #update(double[], int, int) + * update}{@code (input, 0, input.length)}. + *

+ * + * @param input + * Input data. If null is given, update is not performed. + * + * @return + * {@code this} object. + */ + public Digest update(double[] input) + { + if (input == null) + { + return this; + } + + return update(input, 0, input.length); + } + + + /** + * Update the wrapped {@code MessageDigest} object with the + * given input data. + * + *

+ * This method calls {@link #update(double)} for each + * array element which is in the specified range. + *

+ * + * @param input + * Input data. + * + * @param offset + * The offset to start from in the array. + * + * @param length + * The number of elements to use, starting at offset. + * + * @return + * {@code this} object. + * + * @throws IllegalArgumentException + * The range specified by the parameters is invalid. + */ + public Digest update(double[] input, int offset, int length) + { + if (input == null) + { + return this; + } + + checkRange(input.length, offset, length); + + for (int i = 0; i < length; ++i) + { + update(input[i + offset]); + } + + return this; + } + + + /** + * Update the wrapped {@code MessageDigest} object with the + * given input data. + * + *

+ * This method checks the class of the given instance and + * calls a corresponding {@code update} method. + *

+ * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
ClassExecuted code
{@code Byte}{@link #update(byte) update}{@code (((Byte)number).byteValue())}
{@code Short}{@link #update(short) update}{@code (((Short)number).shortValue())}
{@code Integer}{@link #update(int) update}{@code (((Integer)number).intValue())}
{@code Long}{@link #update(long) update}{@code (((Long)number).longValue())}
{@code Float}{@link #update(float) update}{@code (((Float)number).floatValue())}
{@code Double}{@link #update(double) update}{@code (((Double)number).doubleValue())}
{@code BigInteger}{@link #update(byte[]) update}{@code (((BigInteger)number).toByteArray())}
{@code BigDecimal}{@link #update(String) update}{@code (((BigDecimal)number).toString())}
{@code AtomicInteger}{@link #update(int) update}{@code (((AtomicInteger)number).intValue())}
{@code AtomicLong}{@link #update(long) update}{@code (((AtomicLong)number).longValue())}
OthersIgnored.
+ * + * @param number + * Input data. If null or none of the above, update is not performed. + * + * @return + * {@code this} object. + */ + public Digest update(Number number) + { + if (number == null) + { + return this; + } + + // Byte + if (number instanceof Byte) + { + update(((Byte)number).byteValue()); + } + // Short + else if (number instanceof Short) + { + update(((Short)number).shortValue()); + } + // Integer + else if (number instanceof Integer) + { + update(((Integer)number).intValue()); + } + // Long + else if (number instanceof Long) + { + update(((Long)number).longValue()); + } + // Float + else if (number instanceof Float) + { + update(((Float)number).floatValue()); + } + // Double + else if (number instanceof Double) + { + update(((Double)number).doubleValue()); + } + // BigInteger + else if (number instanceof BigInteger) + { + update(((BigInteger)number).toByteArray()); + } + // BigDecimal + else if (number instanceof BigDecimal) + { + update(((BigDecimal)number).toString()); + } + // AtomicInteger + else if (number instanceof AtomicInteger) + { + update(((AtomicInteger)number).intValue()); + } + // AtomicLong + else if (number instanceof AtomicLong) + { + update(((AtomicLong)number).longValue()); + } + + return this; + } + + + /** + * Update the wrapped {@code MessageDigest} object with the + * given input data. + * + *

+ * This method is an alias of {@link #update(Number[], int, int) + * update}{@code (input, 0, input.length)}. + *

+ * + * @param + * The subclass of the {@link Number} class. + * + * @param input + * Input data. If null is given, update is not performed. + * + * @return + * {@code this} object. + */ + public Digest update(TNumber[] input) + { + if (input == null) + { + return this; + } + + return update(input, 0, input.length); + } + + + /** + * Update the wrapped {@code MessageDigest} object with the + * given input data. + * + *

+ * This method calls {@link #update(Number)} for each + * array element which is in the specified range. + *

+ * + * @param + * The subclass of the {@link Number} class. + * + * @param input + * Input data. + * + * @param offset + * The offset to start from in the array. + * + * @param length + * The number of elements to use, starting at offset. + * + * @return + * {@code this} object. + * + * @throws IllegalArgumentException + * The range specified by the parameters is invalid. + */ + public Digest update(TNumber[] input, int offset, int length) + { + if (input == null) + { + return this; + } + + checkRange(input.length, offset, length); + + for (int i = 0; i < length; ++i) + { + update(input[i + offset]); + } + + return this; + } + + + /** + * Update the wrapped {@code MessageDigest} object with the + * given input data. + * + *

+ * This method converts the given string into bytes with the + * character set of UTF-8 and then passes the byte array to + * {@link #update(byte[])}. + *

+ * + * @param input + * Input data. If null is given, update is not performed. + * + * @return + * {@code this} object. + */ + public Digest update(String input) + { + if (input == null) + { + return this; + } + + try + { + return update(input.getBytes("UTF-8")); + } + catch (UnsupportedEncodingException e) + { + // This won't happen. + return this; + } + } + + + /** + * Update the wrapped {@code MessageDigest} object with the + * given input data. + * + *

+ * This method is an alias of {@link #update(String[], int, int) + * update}{@code (input, 0, input.length)}. + *

+ * + * @param input + * Input data. If null is given, update is not performed. + * + * @return + * {@code this} object. + */ + public Digest update(String[] input) + { + if (input == null) + { + return this; + } + + return update(input, 0, input.length); + } + + + /** + * Update the wrapped {@code MessageDigest} object with the + * given input data. + * + *

+ * This method calls {@link #update(String)} for each + * array element which is in the specified range. + *

+ * + * @param input + * Input data. + * + * @param offset + * The offset to start from in the array. + * + * @param length + * The number of elements to use, starting at offset. + * + * @return + * {@code this} object. + * + * @throws IllegalArgumentException + * The range specified by the parameters is invalid. + */ + public Digest update(String[] input, int offset, int length) + { + if (input == null) + { + return this; + } + + checkRange(input.length, offset, length); + + for (int i = 0; i < length; ++i) + { + update(input[i + offset]); + } + + return this; + } + + + /** + * Update the wrapped {@code MessageDigest} object with the + * given input data. + * + *

+ * This method calls {@link #update(Object)} for each element. + *

+ * + * @param input + * Input data. If {@code null} is given, update is not performed. + * {@code null} elements are ignored. Elements of unsupported + * classes are ignored, too. + * + * @return + * {@code this} object. + */ + public Digest update(Iterable input) + { + if (input == null) + { + return this; + } + + for (Object element : input) + { + if (element == null) + { + continue; + } + + update(element); + } + + return this; + } + + + /** + * Update the wrapped {@code MessageDigest} object with the + * given input data. + * + *

+ * This method calls {@link #update(Object)} for each element. + *

+ * + * @param input + * Input data. If {@code null} is given, update is not performed. + * {@code null} elements are ignored. Elements of unsupported + * classes are ignored, too. + * + * @return + * {@code this} object. + */ + public Digest update(Object... input) + { + if (input == null) + { + return this; + } + + for (Object element : input) + { + if (element == null) + { + continue; + } + + update(element); + } + + return this; + } + + + /** + * Update the wrapped {@code MessageDigest} object with the + * given input data. + * + *

+ * This method checks the class of the given object and calls + * a corresponding {@code update} method. + *

+ * + * @param input + * Input data. If {@code null} is given, update is not performed. + * + * @return + * {@code this} object. + */ + public Digest update(Object input) + { + if (input == null) + { + return this; + } + + if (input instanceof String) + { + update((String)input); + } + else if (input instanceof String[]) + { + update((String[])input); + } + else if (input instanceof Character) + { + update((Character)input); + } + else if (input instanceof Character[]) + { + update((Character[])input); + } + else if (input instanceof Boolean) + { + update((Boolean)input); + } + else if (input instanceof boolean[]) + { + update((boolean[])input); + } + else if (input instanceof Boolean[]) + { + update((Boolean[])input); + } + else if (input instanceof Number) + { + update((Number)input); + } + else if (input instanceof byte[]) + { + update((byte[])input); + } + else if (input instanceof Byte[]) + { + update((Byte[])input); + } + else if (input instanceof ByteBuffer) + { + update((ByteBuffer)input); + } + else if (input instanceof char[]) + { + update((char[])input); + } + else if (input instanceof double[]) + { + update((double[])input); + } + else if (input instanceof Double[]) + { + update((Double[])input); + } + else if (input instanceof float[]) + { + update((float[])input); + } + else if (input instanceof Float[]) + { + update((Float[])input); + } + else if (input instanceof int[]) + { + update((int[])input); + } + else if (input instanceof Integer[]) + { + update((Integer[])input); + } + else if (input instanceof long[]) + { + update((long[])input); + } + else if (input instanceof Long[]) + { + update((Long[])input); + } + else if (input instanceof short[]) + { + update((short[])input); + } + else if (input instanceof Short[]) + { + update((Short[])input); + } + else if (input instanceof Iterable) + { + update((Iterable)input); + } + + return this; + } + + + /** + * Update the wrapped {@code MessageDigest} object with the + * given JSON. This method updates the digest based on the + * content of the given JSON, and in the respect, this method + * is different from {@link #update(String)}. + * + *

+ * JSONs with the same content, for example, two JSONs below, + * generate the same digest. + *

+ * + *
+     * { "key1":"value1", "key2":"value2" }
+     * { "key1" : "value1" , "key2" : "value2" }
+     * 
+ * + *

+ * If {@link Feature#IGNORE_JSON_OBJECT_ENTRY_WITH_VALUE_NULL} + * is enabled (it is disabled by default), key-value entries + * with value 'null' are treated as if they did not exist. + * Therefore, two JSONs below generate the same digest value. + *

+ * + *
+     * { "key1":"value1", "key2":null }
+     * { "key1":"value1" }
+     * 
+ * + *

+ * If {@link Feature#SORT_JSON_OBJECT_ENTRY_KEYS} is enabled + * (it is enabled by default), orders of JSON object keys do + * not matter. Therefore, two JSONs below generate the same + * digest value. + *

+ * + *
+     * { "key1":"value1", "key2":"value2" }
+     * { "key2":"value2", "key1":"value1" }
+     * 
+ * + * @param json + * JSON. + * + * @return + * {@code this} object. + * + * @throws IOException + * Failed to parse the given JSON. + */ + public Digest updateJson(String json) throws IOException + { + return new JsonDigestUpdater().update(this, json); + } + + + /** + * Check if the specified feature is enabled. + * + * @param feature + * Feature to check. + * + * @return + * {@code true} if the feature is enabled. Otherwise, {@code false}. + */ + public boolean isEnabled(Feature feature) + { + return mFeatures.get(feature).booleanValue(); + } + + + /** + * Enable or disable the specified feature. + * + * @param feature + * {@link Feature} to enable or disable. + * + * @param enabled + * {@code true} to enable the feature. + * {@code false} to disable the feature. + * + * @return + * {@code this} object. + */ + public Digest setEnabled(Feature feature, boolean enabled) + { + mFeatures.put(feature, Boolean.valueOf(enabled)); + + return this; + } + + + /** + * Convert the given byte array to a hex string. + * + * @param bytes + * A byte array to convert. + * + * @return + * A hex string with 0-9 and a-f. + */ + public static String bytesToHex(byte[] bytes) + { + // http://stackoverflow.com/a/9855338/1174054 + char[] hexChars = new char[bytes.length * 2]; + int v; + + for (int j = 0; j < bytes.length; ++j) + { + v = bytes[j] & 0xFF; + hexChars[j * 2] = mHexArray[v >>> 4]; + hexChars[j * 2 + 1] = mHexArray[v & 0x0F]; + } + + return new String(hexChars); + } +} diff --git a/src/main/java/com/authlete/common/util/digest/JsonDigestUpdater.java b/src/main/java/com/authlete/common/util/digest/JsonDigestUpdater.java new file mode 100644 index 0000000000..5026a2e54e --- /dev/null +++ b/src/main/java/com/authlete/common/util/digest/JsonDigestUpdater.java @@ -0,0 +1,398 @@ +package com.authlete.common.util.digest; + + +import static com.authlete.common.util.digest.Digest.Feature.IGNORE_JSON_OBJECT_ENTRY_WITH_VALUE_EMPTY_ARRAY; +import static com.authlete.common.util.digest.Digest.Feature.IGNORE_JSON_OBJECT_ENTRY_WITH_VALUE_EMPTY_OBJECT; +import static com.authlete.common.util.digest.Digest.Feature.IGNORE_JSON_OBJECT_ENTRY_WITH_VALUE_EMPTY_STRING; +import static com.authlete.common.util.digest.Digest.Feature.IGNORE_JSON_OBJECT_ENTRY_WITH_VALUE_FALSE; +import static com.authlete.common.util.digest.Digest.Feature.IGNORE_JSON_OBJECT_ENTRY_WITH_VALUE_NULL; +import static com.authlete.common.util.digest.Digest.Feature.IGNORE_JSON_OBJECT_ENTRY_WITH_VALUE_ZERO; +import static com.authlete.common.util.digest.Digest.Feature.SORT_JSON_OBJECT_ENTRY_KEYS; +import java.io.IOException; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; + + +/** + * Implementation of {@link Digest#updateJson(String)}. + * + * @author Takahiko Kawasaki + * + * @since 4.23 + */ +class JsonDigestUpdater +{ + private Digest digest; + private StringBuilder path; + private boolean ignoreNull; + private boolean ignoreFalse; + private boolean ignoreZero; + private boolean ignoreEmptyString; + private boolean ignoreEmptyArray; + private boolean ignoreEmptyObject; + private boolean sortKeys; + + + public Digest update(Digest digest, String json) throws IOException + { + // Set the digest to update. + this.digest = digest; + + // Initialize the path. + this.path = new StringBuilder(); + + // Copy configuration. + this.ignoreNull = digest.isEnabled(IGNORE_JSON_OBJECT_ENTRY_WITH_VALUE_NULL); + this.ignoreFalse = digest.isEnabled(IGNORE_JSON_OBJECT_ENTRY_WITH_VALUE_FALSE); + this.ignoreZero = digest.isEnabled(IGNORE_JSON_OBJECT_ENTRY_WITH_VALUE_ZERO); + this.ignoreEmptyString = digest.isEnabled(IGNORE_JSON_OBJECT_ENTRY_WITH_VALUE_EMPTY_STRING); + this.ignoreEmptyArray = digest.isEnabled(IGNORE_JSON_OBJECT_ENTRY_WITH_VALUE_EMPTY_ARRAY); + this.ignoreEmptyObject = digest.isEnabled(IGNORE_JSON_OBJECT_ENTRY_WITH_VALUE_EMPTY_OBJECT); + this.sortKeys = digest.isEnabled(SORT_JSON_OBJECT_ENTRY_KEYS); + + // Convert JSON to a node tree. + JsonNode root = createTree(json); + + // Path start + mark("P"); + + // Traverse the node tree. + update(root); + + // Path end + mark("p"); + + // Finally, update the digest with 'path' in order to prevent + // different JSONs from generating the same digest accidentally. + digest.update(path.toString()); + + return digest; + } + + + private JsonNode createTree(String json) throws IOException + { + return createObjectMapper().readTree(json); + } + + + private ObjectMapper createObjectMapper() + { + ObjectMapper mapper = new ObjectMapper(); + + mapper.configure(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS, true); + + return mapper; + } + + + private void mark(String mark) + { + path.append(mark); + } + + + private void update(JsonNode node) + { + if (node.isArray()) + { + updateArray(node); + } + else if (node.isObject()) + { + updateObject(node); + } + else if (node.isBoolean()) + { + updateBoolean(node); + } + else if (node.isNumber()) + { + updateNumber(node); + } + else if (node.isTextual()) + { + updateText(node); + } + else if (node.isNull()) + { + updateNull(node); + } + else if (node.isBinary()) + { + // Probably, this won't happen. + updateBinary(node); + } + } + + + private void updateArray(JsonNode node) + { + // Array start. + mark("A"); + + // For each element. + for (JsonNode element : node) + { + // Array element start. + mark("E"); + + // Update the digest with the element. + update(element); + + // Array element end. + mark("e"); + } + + // Array end. + mark("a"); + } + + + private void updateObject(JsonNode node) + { + // Object start. + mark("O"); + + // Field names. + List names = getFieldNames(node); + + // For each field name. + for (String name : names) + { + // Get the value of the field. + JsonNode value = node.findValue(name); + + // Update the digest with the pair of name and value. + updateObjectEntry(name, value); + } + + // Object end. + mark("o"); + } + + + private void updateObjectEntry(String name, JsonNode value) + { + if (shouldIgnore(value)) + { + // Ignore this entry. + return; + } + + // Key start. + mark("K"); + + // Update the digest with the name. + digest.update(name); + + // Key end and Value start. + mark("kV"); + + // Update the digest with the value. + update(value); + + // Value end. + mark("v"); + } + + + private boolean shouldIgnore(JsonNode value) + { + if (ignoreNull && value.isNull()) + { + // The value of the field is null. Ignore this entry. + return true; + } + + if (ignoreFalse && value.isBoolean()) + { + // Ignore this entry if its value is false. + return value.asBoolean() == false; + } + + if (ignoreZero && value.isNumber()) + { + // Ignore this entry if its value is zero. + return isZero(value); + } + + if (ignoreEmptyString && value.isTextual()) + { + // Ignore this entry if its value is an empty string. + return value.textValue().length() == 0; + } + + if (ignoreEmptyArray && value.isArray()) + { + // Ignore this entry if its value is an empty array. + return value.size() == 0; + } + + if (ignoreEmptyObject && value.isObject()) + { + // Ignore this entry if its value is an empty object. + return value.size() == 0; + } + + // Should not ignore. + return false; + } + + + private boolean isZero(JsonNode value) + { + // int + if (value.isInt() && value.intValue() == 0) + { + return true; + } + + // long + if (value.isLong() && value.longValue() == 0) + { + return true; + } + + // short + if (value.isShort() && value.shortValue() == 0) + { + return true; + } + + // float + if (value.isFloat() && value.floatValue() == 0.0F) + { + return true; + } + + // double + if (value.isDouble() && value.doubleValue() == 0.0) + { + return true; + } + + // BigInteger + if (value.isBigInteger() && value.bigIntegerValue().equals(BigInteger.ZERO)) + { + return true; + } + + // BigDecimal + if (value.isBigDecimal() && value.decimalValue().equals(BigDecimal.ZERO)) + { + return true; + } + + return false; + } + + + private List getFieldNames(JsonNode node) + { + // Generate a list of field names. + List list = iteratorToList(node.fieldNames()); + + if (sortKeys) + { + // Sort on field names. + Collections.sort(list); + } + + return list; + } + + + private List iteratorToList(Iterator iterator) + { + List list = new ArrayList(); + + while (iterator.hasNext()) + { + list.add(iterator.next()); + } + + return list; + } + + + private void updateBoolean(JsonNode node) + { + // Boolean start. + mark("B"); + + digest.update(node.asBoolean()); + + // "Boolean end. + mark("b"); + } + + + private void updateNumber(JsonNode node) + { + // Number start. + mark("N"); + + // Numbers contribute to the digest as String to make it + // easy to implement JsonDigestUpdater equivalent for other + // non-Java platforms. + // + // Note that BigDecimal.toString() is used for floating point + // numbers because USE_BIG_DECIMAL_FOR_FLOATS is enabled. + digest.update(node.toString()); + + // Number end. + mark("n"); + } + + + private void updateText(JsonNode node) + { + // Text start. + mark("T"); + + digest.update(node.asText()); + + // Text end. + mark("t"); + } + + + private void updateNull(JsonNode node) + { + // Null start. + mark("<"); + + digest.update("NULL"); + + // Null end. + mark(">"); + } + + + private void updateBinary(JsonNode node) + { + // Binary start. + mark("["); + + try + { + digest.update(node.binaryValue()); + } + catch (IOException e) + { + // This won't happen because updateBinary(JsonNode) + // is called only when node.isBinary() returns true. + } + + // Binary end. + mark("]"); + } +} \ No newline at end of file diff --git a/src/main/java/com/authlete/common/util/security/AESCipher.java b/src/main/java/com/authlete/common/util/security/AESCipher.java new file mode 100644 index 0000000000..9e770a70a2 --- /dev/null +++ b/src/main/java/com/authlete/common/util/security/AESCipher.java @@ -0,0 +1,554 @@ +package com.authlete.common.util.security; + + +import static com.authlete.common.util.security.StandardCipherTransformations.AES_CBC_PKCS5PADDING; +import javax.crypto.SecretKey; +import javax.crypto.spec.IvParameterSpec; +import org.apache.commons.codec.BinaryDecoder; +import org.apache.commons.codec.BinaryEncoder; +import org.apache.commons.codec.binary.Base64; + + +/** + * Cipher using {@code "AES/CBC/PKCS5Padding"}. + * + *
+ * // Create a cipher with a secret key.
+ * AESCipher cipher = new {@link #AESCipher()}.{@link #setKey(String, String) setKey}("secret key", "initial vector");
+ *
+ * // Encryption & decryption.
+ * // 'plaintext' and 'decrypted' have the same value.
+ * String plaintext = "plain text";
+ * String encrypted = cipher.{@link #encrypt(String) encrypt(plaintext)};
+ * String decrypted = cipher.{@link #decrypt(String) decrypt(encrypted)};
+ *
+ * // In the above example, 'encrypted' is encoded by Base64 (default).
+ * // If you want to change the format, use {@code setCoder} method.
+ * // For example, to change the format to hexadecimal:
+ * Hex hex = new org.apache.commons.codec.binary.Hex();
+ * cipher.setCoder(hex);
+ *
+ * // Binary representation (only "0"s and "1"s) also can be used.
+ * BinaryCodec binary = new org.apache.commons.codec.BinaryCodec();
+ * cipher.setCoder(binary);
+ *
+ * // Coder can be specified as a constructor parameter.
+ * cipher = new AESCipher(hex);
+ *
+ * // If you want, an encoder and a decoder can be set separately.
+ * cipher.{@link #setEncoder(BinaryEncoder) setEncoder(hex)};
+ * cipher.{@link #setDecoder(BinaryDecoder) setDecoder(hex)};
+ * 
+ * + *
+ * // Another example which performs encryption without initial vector.
+ * String secretkey = "secret key";
+ * String plaintext = "plain text";
+ *
+ * // Create and set up without initial vector.
+ * AESCipher cipher = new AESCipher().setKey(secretkey);
+ *
+ * // Encrypt.
+ * String encrypted = cipher.encrypt(plaintext);
+ *
+ * // Get the auto-generated initial vector.
+ * byte[] iv = cipher.getCipher().getIV();
+ *
+ * // Decryption requires initial vector.
+ * cipher.setKey(secretkey, iv);
+ *
+ * // Decrypt.
+ * String decrypted = cipher.decrypt(encrypted);
+ * 
+ * + * @author Takahiko Kawasaki + * @since 4.23 + */ +public class AESCipher extends CodecCipher +{ + /** + * The default key size. The value is 16, meaning 128 bits. + */ + public static final int DEFAULT_KEY_SIZE = 16; + + + /** + * The default transformation. The value is "AES/CBC/PKCS5Padding". + */ + public static final String DEFAULT_TRANSFORMATION = AES_CBC_PKCS5PADDING; + + + /** + * The initial vector. The size of initial vectors for AES is always 16. + */ + private static final int INITIAL_VECTOR_SIZE = 16; + + + /** + * Constructor. + * + *

+ * This constructor just performs {@link CodecCipher#CodecCipher(String) + * super("AES/CBC/PKCS5Padding")}. + *

+ */ + public AESCipher() + { + super(DEFAULT_TRANSFORMATION); + } + + + /** + * Constructor. + * + *

+ * This constructor just performs {@link CodecCipher#CodecCipher(String) + * super(transformation)}. + *

+ */ + public AESCipher(String transformation) + { + super(transformation); + } + + + /** + * Constructor with an encoder and a decoder. + * + *

+ * This constructor just performs {@link CodecCipher#CodecCipher(String, + * BinaryEncoder, BinaryDecoder) super("AES/CBC/PKCS5Padding", encoder, decoder)}. + *

+ * + * @param encoder + * An encoder used in {@link #encrypt(String) encrypt(String)} and + * {@link #encrypt(byte[]) encrypt(byte[])} to encode an encrypted byte array. + * If {@code null} is given, {@link Base64} is used as the default + * encoder. + * + * @param decoder + * A decoder used in {@link #decrypt(String) decrypt(String)} and + * {@link #decrypt(byte[]) decrypt(byte[])} to decode an encoded input byte array. + * If {@code null} is given, {@link Base64} is used as the default + * decoder. + */ + public AESCipher(BinaryEncoder encoder, BinaryDecoder decoder) + { + super(DEFAULT_TRANSFORMATION, encoder, decoder); + } + + + /** + * Constructor with a transformation, an encoder and a decoder. + * + *

+ * This constructor just performs {@link CodecCipher#CodecCipher(String, + * BinaryEncoder, BinaryDecoder) super(transformation, encoder, decoder)}. + *

+ * + * @param encoder + * An encoder used in {@link #encrypt(String) encrypt(String)} and + * {@link #encrypt(byte[]) encrypt(byte[])} to encode an encrypted byte array. + * If {@code null} is given, {@link Base64} is used as the default + * encoder. + * + * @param decoder + * A decoder used in {@link #decrypt(String) decrypt(String)} and + * {@link #decrypt(byte[]) decrypt(byte[])} to decode an encoded input byte array. + * If {@code null} is given, {@link Base64} is used as the default + * decoder. + */ + public AESCipher(String transformation, BinaryEncoder encoder, BinaryDecoder decoder) + { + super(transformation, encoder, decoder); + } + + + /** + * Constructor with a coder. + * + *

+ * This constructor just performs {@code super("AES/CBC/PKCS5Padding", coder)}. + *

+ * + * @param coder + * A coder which works as both an encoder and a decoder. + * If {@code null} is given, {@link Base64} is used as the + * default coder. + */ + public AESCipher(TCoder coder) + { + super(DEFAULT_TRANSFORMATION, coder); + } + + + /** + * Constructor with a transformation and a coder. + * + *

+ * This constructor just performs {@code super(transformation, coder)}. + *

+ * + * @param coder + * A coder which works as both an encoder and a decoder. + * If {@code null} is given, {@link Base64} is used as the + * default coder. + */ + public AESCipher(String transformation, TCoder coder) + { + super(transformation, coder); + } + + + /** + * Set cipher initialization parameters. + * + *

+ * This method is an alias of {@link #setInit(java.security.Key, + * java.security.spec.AlgorithmParameterSpec) setInit(key, iv)}. + *

+ * + * @param key + * Secret key. + * + * @param iv + * Initial vector. + * + * @return + * {@code this} object. + * + * @throws IllegalArgumentException + * {@code key} is {@code null}. + */ + public AESCipher setKey(SecretKey key, IvParameterSpec iv) + { + return (AESCipher)setInit(key, iv); + } + + + /** + * Set cipher initialization parameters. + * + *

+ * This method is an alias of {@link #setKey(byte[], byte[], int) + * setKey}(key, iv, {@link #DEFAULT_KEY_SIZE}). + *

+ * + * @param key + * Secret key. + * + * @param iv + * Initial vector. + * + * @return + * {@code this} object. + */ + public AESCipher setKey(byte[] key, byte[] iv) + { + return setKey(key, iv, DEFAULT_KEY_SIZE); + } + + + /** + * Set cipher initialization parameters. Other {@code setKey} + * method variants except {@link #setKey(SecretKey, IvParameterSpec)} + * eventually call this method. + * + *

+ * This method constructs a {@link SecretKey} instance and an + * {@link IvParameterSpec} instance from the arguments, + * and then calls {@link #setKey(SecretKey, IvParameterSpec)}. + *

+ * + * @param key + * Secret key. If {@code null} is given, {@code new byte[16]} + * is used. If not {@code null} and the length is less than 16, + * a byte array of size 16 is allocated and the content of + * {@code key} is copied to the newly allocated byte array, + * and the resultant byte array is used. + * + * @param iv + * Initial vector. If {@code null} is given, {@code null} + * is used, meaning that {@code IvParameterSepc} argument + * passed to {@link #setKey(SecretKey, IvParameterSpec)} is + * {@code null}. In that case, you will want to obtain the + * auto-generated initial vector by calling {@link #getCipher() + * getCipher()}{@code .}{@link javax.crypto.Cipher#getIV() + * getIV()} in order to decrypt the encrypted data. + * + *

+ * If {@code iv} is not {@code null} and the length is less + * than 16, a byte array of size 16 is allocated and the content + * of {@code iv} is copied to the newly allocated byte array, + * and the resultant byte array is used. Even if the length is + * greater than 16, only the first 16 bytes are used to construct + * an {@code IvParameterSpec} instance. + *

+ * + * @param keySize + * The size of the secret key in bytes. + * + * @return + * {@code this} object. + */ + public AESCipher setKey(byte[] key, byte[] iv, int keySize) + { + SecretKey secretKey = Utils.createSecretKeySpec(key, getAlgorithm(), keySize); + IvParameterSpec spec = null; + + if (iv != null) + { + spec = Utils.createIvParameterSpec(iv, INITIAL_VECTOR_SIZE); + } + + return setKey(secretKey, spec); + } + + + /** + * Set cipher initialization parameters. + * + *

+ * This method is an alias of {@link #setKey(String, byte[], int) + * setKey}(key, iv, {@link #DEFAULT_KEY_SIZE}). + *

+ * + * @param key + * Secret key. + * + * @param iv + * Initial vector. + * + * @return + * {@code this} object. + */ + public AESCipher setKey(String key, byte[] iv) + { + return setKey(key, iv, DEFAULT_KEY_SIZE); + } + + + /** + * Set cipher initialization parameters. + * + *

+ * This method is an alias of {@link #setKey(byte[], byte[], int)}. + *

+ * + * @param key + * Secret key. The value is converted to a byte array + * by {@code key.getBytes("UTF-8")} and used as the + * first argument of {@link #setKey(byte[], byte[], int)}. + * + * @param iv + * Initial vector. + * + * @param keySize + * The size of the secret key in bytes. + * + * @return + * {@code this} object. + */ + public AESCipher setKey(String key, byte[] iv, int keySize) + { + byte[] key2 = Utils.getBytesUTF8(key); + + return setKey(key2, iv, keySize); + } + + + /** + * Set cipher initialization parameters. + * + *

+ * This method is an alias of {@link #setKey(String, String, int) + * setKey}(key, iv, {@link #DEFAULT_KEY_SIZE}). + *

+ * + * @param key + * Secret key. + * + * @param iv + * Initial vector. + * + * @return + * {@code this} object. + */ + public AESCipher setKey(String key, String iv) + { + return setKey(key, iv, DEFAULT_KEY_SIZE); + } + + + /** + * Set cipher initialization parameters. + * + *

+ * This method is an alias of {@link #setKey(byte[], byte[], int)}. + *

+ * + * @param key + * Secret key. The value is converted to a byte array + * by {@code key.getBytes("UTF-8")} and used as the + * first argument of {@link #setKey(byte[], byte[], int)}. + * + * @param iv + * Initial vector. The value is converted to a byte array + * by {@code iv.getBytes("UTF-8")} and used as the + * second argument of {@link #setKey(byte[], byte[], int)}. + * + * @param keySize + * The size of the secret key in bytes. + * + * @return + * {@code this} object. + */ + public AESCipher setKey(String key, String iv, int keySize) + { + byte[] key2 = Utils.getBytesUTF8(key); + byte[] iv2 = Utils.getBytesUTF8(iv); + + return setKey(key2, iv2, keySize); + } + + + /** + * Set cipher initialization parameters. + * + *

+ * This method is an alias of {@link #setKey(String, int) + * setKey}(key, {@link #DEFAULT_KEY_SIZE}) + *

+ * + * @param key + * Secret key. + * + * @return + * {@code this} object. + */ + public AESCipher setKey(String key) + { + return setKey(key, DEFAULT_KEY_SIZE); + } + + + /** + * Set cipher initialization parameters. + * + *

+ * This method is an alias of {@link #setKey(byte[], byte[], int)}. + *

+ * + * @param key + * Secret key. The value is converted to a byte array + * by {@code key.getBytes("UTF-8")} and used as the + * first argument of {@link #setKey(byte[], byte[], int)}. + * + * @param keySize + * The size of the secret key in bytes. + * + * @return + * {@code this} object. + */ + public AESCipher setKey(String key, int keySize) + { + byte[] key2 = Utils.getBytesUTF8(key); + + return setKey(key2, (byte[])null, keySize); + } + + + /** + * Set cipher initialization parameters. + * + *

+ * This method is an alias of {@link #setKey(byte[], String, int) + * setKey}(key, iv, {@link #DEFAULT_KEY_SIZE}). + *

+ * + * @param key + * Secret key. + * + * @param iv + * Initial vector. + * + * @return + * {@code this} object. + */ + public AESCipher setKey(byte[] key, String iv) + { + return setKey(key, iv, DEFAULT_KEY_SIZE); + } + + + /** + * Set cipher initialization parameters. + * + *

+ * This method is an alias of {@link #setKey(byte[], byte[], int)}. + *

+ * + * @param key + * Secret key. + * + * @param iv + * Initial vector. The value is converted to a byte array + * by {@code iv.getBytes("UTF-8")} and used as the + * second argument of {@link #setKey(byte[], byte[], int)}. + * + * @param keySize + * The size of the secret key in bytes. + * + * @return + * {@code this} object. + */ + public AESCipher setKey(byte[] key, String iv, int keySize) + { + byte[] iv2 = Utils.getBytesUTF8(iv); + + return setKey(key, iv2, keySize); + } + + + /** + * Set cipher initialization parameters. + * + *

+ * This method is an alias of {@link #setKey(byte[], int) + * setKey}(key, {@link #DEFAULT_KEY_SIZE}). + *

+ * + * @param key + * Secret key. + * + * @return + * {@code this} object. + */ + public AESCipher setKey(byte[] key) + { + return setKey(key, DEFAULT_KEY_SIZE); + } + + + /** + * Set cipher initialization parameters. + * + *

+ * This method is an alias of {@link #setKey(byte[], byte[], int) + * setKey}(key, (byte[])null, keySize). + *

+ * + * @param key + * Secret key. + * + * @param keySize + * The size of the secret key in bytes. + * + * @return + * {@code this} object. + */ + public AESCipher setKey(byte[] key, int keySize) + { + return setKey(key, (byte[])null, keySize); + } +} \ No newline at end of file diff --git a/src/main/java/com/authlete/common/util/security/CodecCipher.java b/src/main/java/com/authlete/common/util/security/CodecCipher.java new file mode 100644 index 0000000000..06739d7582 --- /dev/null +++ b/src/main/java/com/authlete/common/util/security/CodecCipher.java @@ -0,0 +1,1209 @@ +package com.authlete.common.util.security; + + +import java.security.AlgorithmParameters; +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.Key; +import java.security.Provider; +import java.security.SecureRandom; +import java.security.cert.Certificate; +import java.security.spec.AlgorithmParameterSpec; +import javax.crypto.BadPaddingException; +import javax.crypto.Cipher; +import javax.crypto.IllegalBlockSizeException; +import org.apache.commons.codec.BinaryDecoder; +import org.apache.commons.codec.BinaryEncoder; +import org.apache.commons.codec.DecoderException; +import org.apache.commons.codec.EncoderException; +import org.apache.commons.codec.binary.Base64; + + +/** + * Cipher with encoder and decoder. + * + *
+ * // This example shows encryption/decryption using "AES/CBC/PKCS5Padding",
+ * // but {@link AESCipher AESCipher} is much easier to use for "AES/CBC/PKCS5Padding".
+ *
+ * // Prepare cipher initialization parameters.
+ * Key key
+ *     = new SecretKeySpec("1234567890123456".getBytes("UTF-8"), "AES");
+ * AlgorithmParameterSpec spec
+ *     = new IvParameterSpec("abcdefghijklmnop".getBytes("UTF-8"));
+ *
+ * // Create a cipher using "AES/CBC/PKCS5Padding".
+ * String transformation = {@link StandardCipherTransformations}.{@link StandardCipherTransformations#AES_CBC_PKCS5PADDING AES_CBC_PKCS5PADDING};
+ * CodecCipher cipher = new {@link #CodecCipher(String) CodecCipher(transformation)};
+ *
+ * // Set initialization parameters.
+ * cipher.{@link #setInit(Key, AlgorithmParameterSpec) setInit(key, spec)};
+ *
+ * // Encryption & decryption.
+ * // 'plaintext' and 'decrypted' have the same value.
+ * String plaintext = "plain text";
+ * String encrypted = cipher.{@link #encrypt(String) encrypt(plaintext)};
+ * String decrypted = cipher.{@link #decrypt(String) decrypt(encrypted)};
+ *
+ * // In the above example, 'encrypted' is encoded by Base64 (default).
+ * // If you want to change the format, use {@code setCoder} method.
+ * // For example, to change the format to hexadecimal:
+ * Hex hex = new org.apache.commons.codec.binary.Hex();
+ * cipher.setCoder(hex);
+ *
+ * // Binary representation (only "0"s and "1"s) also can be used.
+ * BinaryCodec binary = new org.apache.commons.codec.BinaryCodec();
+ * cipher.setCoder(binary);
+ *
+ * // Coder can be specified as a parameter of some constructors.
+ * cipher = new CodecCipher("AES/CBC/PKCS5Padding", hex);
+ *
+ * // If you want, an encoder and a decoder can be set separately.
+ * cipher.{@link #setEncoder(BinaryEncoder) setEncoder(hex)};
+ * cipher.{@link #setDecoder(BinaryDecoder) setDecoder(hex)};
+ * 
+ * + * @see javax.crypto.Cipher + * + * @author Takahiko Kawasaki + * @since 4.23 + */ +public class CodecCipher +{ + private static final Base64 DEFAULT_CODER = new Base64(); + + private Cipher cipher; + private BinaryEncoder encoder; + private BinaryDecoder decoder; + private Key key; + private AlgorithmParameters params; + private AlgorithmParameterSpec spec; + private Certificate certificate; + private SecureRandom random; + + + /** + * The default constructor. + */ + public CodecCipher() + { + } + + + /** + * Constructor with a cipher, an encoder and a decoder. + * + * @param cipher + * A cipher. If {@code null} is given, {@link #setCipher(Cipher)} + * must be called later with a valid cipher. + * + * @param encoder + * An encoder used in {@link #encrypt(String)} and + * {@link #encrypt(byte[])} to encode an encrypted byte array. + * If {@code null} is given, {@link Base64} is used as the default + * encoder. + * + * @param decoder + * A decoder used in {@link #decrypt(String)} and + * {@link #decrypt(byte[])} to decode an encoded input byte array. + * If {@code null} is given, {@link Base64} is used as the default + * decoder. + */ + public CodecCipher(Cipher cipher, BinaryEncoder encoder, BinaryDecoder decoder) + { + this.cipher = cipher; + this.encoder = encoder; + this.decoder = decoder; + } + + + /** + * Constructor with a cipher and a coder. + * + *

+ * This constructor is an alias of {@link #CodecCipher(Cipher, BinaryEncoder, BinaryDecoder) + * CodecCipher(cipher, coder, coder)}. + *

+ * + * @param cipher + * A cipher. If {@code null} is given, {@link #setCipher(Cipher)} + * must be called later with a valid cipher. + * + * @param coder + * A coder which works as both an encoder and a decoder. + * If {@code null} is given, {@link Base64} is used as the + * default coder. + */ + public CodecCipher(Cipher cipher, TCoder coder) + { + this(cipher, coder, coder); + } + + + /** + * Constructor with a cipher. + * + *

+ * This constructor is an alias of {@link #CodecCipher(Cipher, BinaryEncoder, BinaryDecoder) + * CodecCipher(cipher, (BinaryEncoder)null, (BinaryDecoder)null)}. + *

+ * + * @param cipher + * A cipher. If {@code null} is given, {@link #setCipher(Cipher)} + * must be called later with a valid cipher. + */ + public CodecCipher(Cipher cipher) + { + this(cipher, (BinaryEncoder)null, (BinaryDecoder)null); + } + + + /** + * Constructor with a transformation, an encoder and a decoder. + * + * @param transformation + * A transformation in the form of either "algorithm/mode/padding" + * or "algorithm". For example, "AES/CBC/PKCS5Padding". + * + * @param encoder + * An encoder used in {@link #encrypt(String)} and + * {@link #encrypt(byte[])} to encode an encrypted byte array. + * If {@code null} is given, {@link Base64} is used as the default + * encoder. + * + * @param decoder + * A decoder used in {@link #decrypt(String)} and + * {@link #decrypt(byte[])} to decode an encoded input byte array. + * If {@code null} is given, {@link Base64} is used as the default + * decoder. + * + * @throws IllegalArgumentException + * The given transformation is not supported. This exception wraps + * the original exception (such as {@link java.security.NoSuchAlgorithmException + * NoSuchAlgorithmException} and {@link javax.crypto.NoSuchPaddingException + * NoSuchPaddingException}) as the cause. + * + * @see Cipher (Encryption) Algorithms + * + * @see StandardCipherTransformations + */ + public CodecCipher(String transformation, BinaryEncoder encoder, BinaryDecoder decoder) throws IllegalArgumentException + { + this(getCipherInstance(transformation), encoder, decoder); + } + + + /** + * Constructor with a transformation and a coder. + * + *

+ * This constructor is an alias of {@link #CodecCipher(String, BinaryEncoder, BinaryDecoder) + * CodecCipher(transformation, coder, coder)}. + *

+ * + * @param transformation + * A transformation in the form of either "algorithm/mode/padding" + * or "algorithm". For example, "AES/CBC/PKCS5Padding". + * + * @param coder + * A coder which works as both an encoder and a decoder. + * If {@code null} is given, {@link Base64} is used as the + * default coder. + * + * @throws IllegalArgumentException + * The given transformation is not supported. This exception wraps + * the original exception (such as {@link java.security.NoSuchAlgorithmException + * NoSuchAlgorithmException} and {@link javax.crypto.NoSuchPaddingException + * NoSuchPaddingException}) as the cause. + * + * @see Cipher (Encryption) Algorithms + * + * @see StandardCipherTransformations + */ + public CodecCipher(String transformation, TCoder coder) throws IllegalArgumentException + { + this(transformation, coder, coder); + } + + + /** + * Constructor with a transformation. + * + *

+ * This constructor is an alias of {@link #CodecCipher(String, BinaryEncoder, BinaryDecoder) + * CodecCipher(transformation, (BinaryEncoder)null, (BinaryDecoder)null)}. + *

+ * + * @param transformation + * A transformation in the form of either "algorithm/mode/padding" + * or "algorithm". For example, "AES/CBC/PKCS5Padding". + * + * @throws IllegalArgumentException + * The given transformation is not supported. This exception wraps + * the original exception (such as {@link java.security.NoSuchAlgorithmException + * NoSuchAlgorithmException} and {@link javax.crypto.NoSuchPaddingException + * NoSuchPaddingException}) as the cause. + * + * @see Cipher (Encryption) Algorithms + * + * @see StandardCipherTransformations + */ + public CodecCipher(String transformation) throws IllegalArgumentException + { + this(transformation, (BinaryEncoder)null, (BinaryDecoder)null); + } + + + /** + * Constructor with a transformation, a provider, an encoder and a decoder. + * + * @param transformation + * A transformation in the form of either "algorithm/mode/padding" + * or "algorithm". For example, "AES/CBC/PKCS5Padding". + * + * @param provider + * A name of provider which provides the implementation of the + * transformation. + * + * @param encoder + * An encoder used in {@link #encrypt(String)} and + * {@link #encrypt(byte[])} to encode an encrypted byte array. + * If {@code null} is given, {@link Base64} is used as the default + * encoder. + * + * @param decoder + * A decoder used in {@link #decrypt(String)} and + * {@link #decrypt(byte[])} to decode an encoded input byte array. + * If {@code null} is given, {@link Base64} is used as the default + * decoder. + * + * @throws IllegalArgumentException + * The given transformation is not supported. This exception wraps + * the original exception (such as {@link java.security.NoSuchAlgorithmException + * NoSuchAlgorithmException}, {@link javax.crypto.NoSuchPaddingException + * NoSuchPaddingException} and {@link java.security.NoSuchProviderException + * NoSuchProviderException}) as the cause. + * + * @see Cipher (Encryption) Algorithms + * + * @see StandardCipherTransformations + */ + public CodecCipher(String transformation, String provider, BinaryEncoder encoder, BinaryDecoder decoder) throws IllegalArgumentException + { + this(getCipherInstance(transformation, provider), encoder, decoder); + } + + + /** + * Constructor with a transformation, a provider and a coder. + * + *

+ * This constructor is an alias of {@link #CodecCipher(String, String, BinaryEncoder, BinaryDecoder) + * CodecCipher(transformation, provider, coder, coder)}. + *

+ * + * @param transformation + * A transformation in the form of either "algorithm/mode/padding" + * or "algorithm". For example, "AES/CBC/PKCS5Padding". + * + * @param provider + * A name of provider which provides the implementation of the + * transformation. + * + * @param coder + * A coder which works as both an encoder and a decoder. + * If {@code null} is given, {@link Base64} is used as the + * default coder. + * + * @throws IllegalArgumentException + * The given transformation is not supported. This exception wraps + * the original exception (such as {@link java.security.NoSuchAlgorithmException + * NoSuchAlgorithmException}, {@link javax.crypto.NoSuchPaddingException + * NoSuchPaddingException} and {@link java.security.NoSuchProviderException + * NoSuchProviderException}) as the cause. + * + * @see Cipher (Encryption) Algorithms + * + * @see StandardCipherTransformations + */ + public CodecCipher(String transformation, String provider, TCoder coder) throws IllegalArgumentException + { + this(transformation, provider, coder, coder); + } + + + /** + * Constructor with a transformation and a provider. + * + *

+ * This constructor is an alias of {@link #CodecCipher(String, String, BinaryEncoder, BinaryDecoder) + * CodecCipher(transformation, provider, (BinaryEncoder)null, (BinaryDecoder)null)}. + *

+ * + * @param transformation + * A transformation in the form of either "algorithm/mode/padding" + * or "algorithm". For example, "AES/CBC/PKCS5Padding". + * + * @param provider + * A name of provider which provides the implementation of the + * transformation. + * + * @throws IllegalArgumentException + * The given transformation is not supported. This exception wraps + * the original exception (such as {@link java.security.NoSuchAlgorithmException + * NoSuchAlgorithmException}, {@link javax.crypto.NoSuchPaddingException + * NoSuchPaddingException} and {@link java.security.NoSuchProviderException + * NoSuchProviderException}) as the cause. + * + * @see Cipher (Encryption) Algorithms + * + * @see StandardCipherTransformations + */ + public CodecCipher(String transformation, String provider) throws IllegalArgumentException + { + this(transformation, provider, (BinaryEncoder)null, (BinaryDecoder)null); + } + + + /** + * Constructor with a transformation, a provider, an encoder and a decoder. + * + * @param transformation + * A transformation in the form of either "algorithm/mode/padding" + * or "algorithm". For example, "AES/CBC/PKCS5Padding". + * + * @param provider + * A provider which provides the implementation of the transformation. + * + * @param encoder + * An encoder used in {@link #encrypt(String)} and + * {@link #encrypt(byte[])} to encode an encrypted byte array. + * If {@code null} is given, {@link Base64} is used as the default + * encoder. + * + * @param decoder + * A decoder used in {@link #decrypt(String)} and + * {@link #decrypt(byte[])} to decode an encoded input byte array. + * If {@code null} is given, {@link Base64} is used as the default + * decoder. + * + * @throws IllegalArgumentException + * The given transformation is not supported. This exception wraps + * the original exception (such as {@link java.security.NoSuchAlgorithmException + * NoSuchAlgorithmException} and {@link javax.crypto.NoSuchPaddingException + * NoSuchPaddingException}) as the cause. + * + * @see Cipher (Encryption) Algorithms + * + * @see StandardCipherTransformations + */ + public CodecCipher(String transformation, Provider provider, BinaryEncoder encoder, BinaryDecoder decoder) throws IllegalArgumentException + { + this(getCipherInstance(transformation, provider), encoder, decoder); + } + + + /** + * Constructor with a transformation, a provider and a coder. + * + *

+ * This constructor is an alias of {@link #CodecCipher(String, Provider, BinaryEncoder, BinaryDecoder) + * CodecCipher(transformation, provider, coder, coder)}. + *

+ * + * @param transformation + * A transformation in the form of either "algorithm/mode/padding" + * or "algorithm". For example, "AES/CBC/PKCS5Padding". + * + * @param provider + * A provider which provides the implementation of the transformation. + * + * @param coder + * A coder which works as both an encoder and a decoder. + * If {@code null} is given, {@link Base64} is used as the + * default coder. + * + * @throws IllegalArgumentException + * The given transformation is not supported. This exception wraps + * the original exception (such as {@link java.security.NoSuchAlgorithmException + * NoSuchAlgorithmException} and {@link javax.crypto.NoSuchPaddingException + * NoSuchPaddingException}) as the cause. + * + * @see Cipher (Encryption) Algorithms + * + * @see StandardCipherTransformations + */ + public CodecCipher(String transformation, Provider provider, TCoder coder) throws IllegalArgumentException + { + this(transformation, provider, coder, coder); + } + + + /** + * Constructor with a transformation and a provider. + * + *

+ * This constructor is an alias of {@link #CodecCipher(String, Provider, BinaryEncoder, BinaryDecoder) + * CodecCipher(transformation, provider, (BinaryEncoder)null, (BinaryDecoder)null)}. + *

+ * + * @param transformation + * A transformation in the form of either "algorithm/mode/padding" + * or "algorithm". For example, "AES/CBC/PKCS5Padding". + * + * @param provider + * A provider which provides the implementation of the transformation. + * + * @throws IllegalArgumentException + * The given transformation is not supported. This exception wraps + * the original exception (such as {@link java.security.NoSuchAlgorithmException + * NoSuchAlgorithmException} and {@link javax.crypto.NoSuchPaddingException + * NoSuchPaddingException}) as the cause. + * + * @see Cipher (Encryption) Algorithms + * + * @see StandardCipherTransformations + */ + public CodecCipher(String transformation, Provider provider) throws IllegalArgumentException + { + this(transformation, provider, (BinaryEncoder)null, (BinaryDecoder)null); + } + + + /** + * Create a {@link Cipher} instance by {@link Cipher#getInstance(String) + * Cipher.getInstance(transformation)}. + */ + private static Cipher getCipherInstance(String transformation) throws IllegalArgumentException + { + try + { + return Cipher.getInstance(transformation); + } + catch (Exception e) + { + throw new IllegalArgumentException(e); + } + } + + + /** + * Create a {@link Cipher} instance by {@link Cipher#getInstance(String, String) + * Cipher.getInstance(transformation, provider)}. + */ + private static Cipher getCipherInstance(String transformation, String provider) + { + try + { + return Cipher.getInstance(transformation, provider); + } + catch (Exception e) + { + throw new IllegalArgumentException(e); + } + } + + + /** + * Create a {@link Cipher} instance by {@link Cipher#getInstance(String, Provider) + * Cipher.getInstance(transformation, provider)}. + */ + private static Cipher getCipherInstance(String transformation, Provider provider) + { + try + { + return Cipher.getInstance(transformation, provider); + } + catch (Exception e) + { + throw new IllegalArgumentException(e); + } + } + + + /** + * Get the cipher which this instance internally holds. + * + * @return + * The {@link Cipher} instance. + */ + public Cipher getCipher() + { + return cipher; + } + + + /** + * Set a cipher instance. + * + * @param cipher + * A cipher instance. + * + * @return + * {@code this} object. + */ + public CodecCipher setCipher(Cipher cipher) + { + if (cipher == null) + { + throw new IllegalArgumentException("cipher is null."); + } + + this.cipher = cipher; + + return this; + } + + + /** + * Get the cipher algorithm name. + * + *

+ * If the internal cipher is {@code null}, {@code null} is returned. + * Otherwise, the algorithm part of the cipher's transformation is + * returned. + *

+ * + * @return + * The cipher algorithm name. + */ + public String getAlgorithm() + { + if (cipher == null) + { + return null; + } + + String transformation = cipher.getAlgorithm(); + + if (transformation == null) + { + return null; + } + + // Separator position. + int pos = transformation.indexOf('/'); + + if (pos < 0) + { + return transformation; + } + + return transformation.substring(0, pos); + } + + + /** + * Get the encoder which this instance internally holds. + * + * @return + * The internal encoder. This may be {@code null}, and + * in such a case, {@link #encrypt(String)} and + * {@link #encrypt(byte[])} use {@link Base64}. + */ + public BinaryEncoder getEncoder() + { + return encoder; + } + + + /** + * Set an encoder. + * + * @param encoder + * An encoder used by {@link #encrypt(String)} and + * {@link #encrypt(byte[])}. + * + * @return + * {@code this} object. + */ + public CodecCipher setEncoder(BinaryEncoder encoder) + { + this.encoder = encoder; + + return this; + } + + + /** + * Get the decoder which this instance internally holds. + * + * @return + * The internal decoder. This may be {@code null}, and + * in such a case, {@link #decrypt(String)} and + * {@link #decrypt(byte[])} use {@link Base64}. + */ + public BinaryDecoder getDecoder() + { + return decoder; + } + + + /** + * Set a decoder. + * + * @param decoder + * A decoder used by {@link #decrypt(String)} and + * {@link #decrypt(byte[])}. + * + * @return + * {@code this} object. + */ + public CodecCipher setDecoder(BinaryDecoder decoder) + { + this.decoder = decoder; + + return this; + } + + + /** + * Set a coder. + * + * @param coder + * A coder which works as both an encoder and a decoder. + * If {@code null} is given, {@link Base64} is used as the + * default coder. + * + * @return + * {@code this} object. + */ + public CodecCipher setCoder(TCoder coder) + { + this.encoder = coder; + this.decoder = coder; + + return this; + } + + + /** + * Set cipher initialization parameters. + * + *

+ * If this method is used to set initialization parameters, + * {@link Cipher#init(int, Key) Cipher.init(mode, (Key)key)} + * is called later from within {@code encrypt}/{@code decrypt} methods. + *

+ * + * @param key + * + * @return + * {@code this} object. + * + * @throws IllegalArgumentException + * {@code key} is {@code null}. + */ + public CodecCipher setInit(Key key) throws IllegalArgumentException + { + return setInit(key, null, null, null); + } + + + /** + * Set cipher initialization parameters. + * + *

+ * If this method is used to set initialization parameters, + * {@link Cipher#init(int, Key, SecureRandom) + * Cipher.init(mode, (Key)key, (SecureRandom)random)} + * is called later from within {@code encrypt}/{@code decrypt} methods. + *

+ * + * @param key + * + * @param random + * + * @return + * {@code this} object. + * + * @throws IllegalArgumentException + * {@code key} is {@code null}. + */ + public CodecCipher setInit(Key key, SecureRandom random) throws IllegalArgumentException + { + return setInit(key, null, null, random); + } + + + /** + * Set cipher initialization parameters. + * + *

+ * If this method is used to set initialization parameters, + * {@link Cipher#init(int, Key, AlgorithmParameters) + * Cipher.init(mode, (Key)key, (AlgorithmParameters)params)} + * is called later from within {@code encrypt}/{@code decrypt} methods. + *

+ * + * @param key + * + * @param params + * + * @return + * {@code this} object. + * + * @throws IllegalArgumentException + * {@code key} is {@code null}. + */ + public CodecCipher setInit(Key key, AlgorithmParameters params) throws IllegalArgumentException + { + return setInit(key, params, null, null); + } + + + /** + * Set cipher initialization parameters. + * + *

+ * If this method is used to set initialization parameters, + * {@link Cipher#init(int, Key, AlgorithmParameters, SecureRandom) + * Cipher.init(mode, (Key)key, (AlgorithmParameters)params, (SecureRandom)random)} + * is called later from within {@code encrypt}/{@code decrypt} methods. + *

+ * + * @param key + * + * @param params + * + * @param random + * + * @return + * {@code this} object. + * + * @throws IllegalArgumentException + * {@code key} is {@code null}. + */ + public CodecCipher setInit(Key key, AlgorithmParameters params, SecureRandom random) throws IllegalArgumentException + { + return setInit(key, params, null, random); + } + + + /** + * Set cipher initialization parameters. + * + *

+ * If this method is used to set initialization parameters, + * {@link Cipher#init(int, Key, AlgorithmParameterSpec) + * Cipher.init(mode, (Key)key, (AlgorithmParameterSpec)spec)} + * is called later from within {@code encrypt}/{@code decrypt} methods. + *

+ * + * @param key + * + * @param spec + * + * @return + * {@code this} object. + * + * @throws IllegalArgumentException + * {@code key} is {@code null}. + */ + public CodecCipher setInit(Key key, AlgorithmParameterSpec spec) throws IllegalArgumentException + { + return setInit(key, null, spec, null); + } + + + /** + * Set cipher initialization parameters. + * + *

+ * If this method is used to set initialization parameters, + * {@link Cipher#init(int, Key, AlgorithmParameterSpec, SecureRandom) + * Cipher.init(mode, (Key)key, (AlgorithmParameterSpec)spec, (SecureRandom)random)} + * is called later from within {@code encrypt}/{@code decrypt} methods. + *

+ * + * @param key + * + * @param spec + * + * @param random + * + * @return + * {@code this} object. + * + * @throws IllegalArgumentException + * {@code key} is {@code null}. + */ + public CodecCipher setInit(Key key, AlgorithmParameterSpec spec, SecureRandom random) throws IllegalArgumentException + { + return setInit(key, null, spec, random); + } + + + /** + * Set cipher initialization parameters. + * + *

+ * If this method is used to set initialization parameters, + * {@link Cipher#init(int, Certificate) + * Cipher.init(mode, (Certificate)certificate)} + * is called later from within {@code encrypt}/{@code decrypt} methods. + *

+ * + * @param certificate + * + * @return + * {@code this} object. + * + * @throws IllegalArgumentException + * {@code certificate} is {@code null}. + */ + public CodecCipher setInit(Certificate certificate) throws IllegalArgumentException + { + return setInit(certificate, null); + } + + + /** + * Set cipher initialization parameters. + * + *

+ * If this method is used to set initialization parameters, + * {@link Cipher#init(int, Certificate, SecureRandom) + * Cipher.init(mode, (Certificate)certificate, (SecureRandom)random)} + * is called later from within {@code encrypt}/{@code decrypt} methods. + *

+ * + * @param certificate + * + * @param random + * + * @return + * {@code this} object. + * + * @throws IllegalArgumentException + * {@code certificate} is {@code null}. + */ + public CodecCipher setInit(Certificate certificate, SecureRandom random) throws IllegalArgumentException + { + if (certificate == null) + { + throw new IllegalArgumentException("certificate is null."); + } + + return setInit(null, null, null, certificate, random); + } + + + /** + * Set cipher initialization parameters. + */ + private CodecCipher setInit(Key key, AlgorithmParameters params, AlgorithmParameterSpec spec, SecureRandom random) + { + if (key == null) + { + throw new IllegalArgumentException("key is null."); + } + + return setInit(key, params, spec, null, random); + } + + + /** + * Set cipher initialization parameters. + */ + private CodecCipher setInit(Key key, AlgorithmParameters params, AlgorithmParameterSpec spec, Certificate certificate, SecureRandom random) + { + this.key = key; + this.params = params; + this.spec = spec; + this.certificate = certificate; + this.random = random; + + return this; + } + + + /** + * Encrypt the given string. + * + * @param input + * Plain text before encryption. + * + * @return + * Encrypted string encoded by the encoder. + * If {@code input} is {@code null}, {@code null} is returned. + * + * @throws IllegalStateException + * Failed to encrypt the input. The {@code IllegalStateException} + * wraps the original exception (such as {@link InvalidKeyException} + * and {@link EncoderException}) as the cause, if any. + *
    + *
  • A cipher instance has not been set. + *
  • {@code setInit} method has not been called. + *
  • The cipher failed to encrypt the input (probably initialization + * parameters set by {@code setInit} method are wrong). + *
  • The encoder failed to encode the encrypted byte array. + *
+ */ + public String encrypt(String input) throws IllegalStateException + { + return cipher(input, Cipher.ENCRYPT_MODE); + } + + + /** + * Decrypt the given string. + * + * @param input + * Encrypted string encoded by an encoder. + * + * @return + * Decrypted plain text. + * If {@code input} is {@code null}, {@code null} is returned. + * + * @throws IllegalStateException + * Failed to decrypt the input. The {@code IllegalStateException} + * wraps the original exception (such as {@link InvalidKeyException} + * and {@link DecoderException}) as the cause, if any. + *
    + *
  • A cipher instance has not been set. + *
  • {@code setInit} method has not been called. + *
  • The decoder failed to decode the input. + *
  • The cipher failed to decrypt the byte array (probably initialization + * parameters set by {@code setInit} method are wrong). + *
+ */ + public String decrypt(String input) throws IllegalStateException + { + return cipher(input, Cipher.DECRYPT_MODE); + } + + + /** + * Encrypt the given byte array. + * + * @param input + * Plain byte array before encryption. + * + * @return + * Encrypted byte array encoded by the encoder. + * If {@code input} is {@code null}, {@code null} is returned. + * + * @throws IllegalStateException + * Failed to encrypt the input. The {@code IllegalStateException} + * wraps the original exception (such as {@link InvalidKeyException} + * and {@link EncoderException}) as the cause, if any. + *
    + *
  • A cipher instance has not been set. + *
  • {@code setInit} method has not been called. + *
  • The cipher failed to encrypt the input (probably initialization + * parameters set by {@code setInit} method are wrong). + *
  • The encoder failed to encode the encrypted byte array. + *
+ */ + public byte[] encrypt(byte[] input) throws IllegalStateException + { + return cipher(input, Cipher.ENCRYPT_MODE); + } + + + /** + * Decrypt the given byte array. + * + * @param input + * Encrypted byte array encoded by an encoder. + * + * @return + * Decrypted plain byte array. + * If {@code input} is {@code null}, {@code null} is returned. + * + * @throws IllegalStateException + * Failed to decrypt the input. The {@code IllegalStateException} + * wraps the original exception (such as {@link InvalidKeyException} + * and {@link DecoderException}) as the cause, if any. + *
    + *
  • A cipher instance has not been set. + *
  • {@code setInit} method has not been called. + *
  • The decoder failed to decode the input. + *
  • The cipher failed to decrypt the byte array (probably initialization + * parameters set by {@code setInit} method are wrong). + *
+ */ + public byte[] decrypt(byte[] input) throws IllegalStateException + { + return cipher(input, Cipher.DECRYPT_MODE); + } + + + /** + * Encrypt or decrypt. + */ + private String cipher(String input, int mode) + { + if (input == null) + { + return null; + } + + // Convert the input string into a byte array. + byte[] inputBytes = Utils.getBytesUTF8(input); + + // Encrypt or decrypt. + byte[] outputBytes = cipher(inputBytes, mode); + + // Build a string from the byte array. + return Utils.toStringUTF8(outputBytes); + } + + + /** + * Encrypt or decrypt. + */ + private byte[] cipher(byte[] input, int mode) + { + try + { + return doCipher(input, mode); + } + catch (Exception e) + { + throw new IllegalStateException(e); + } + } + + + /** + * Encrypt or decrypt. + */ + private byte[] doCipher(byte[] input, int mode) throws + DecoderException, InvalidKeyException, InvalidAlgorithmParameterException, + IllegalBlockSizeException, BadPaddingException, EncoderException + { + if (input == null) + { + return null; + } + + if (cipher == null) + { + throw new IllegalStateException("setCipher() has not been called."); + } + + if (key == null && certificate == null) + { + throw new IllegalStateException("setInit() has not been called."); + } + + if (mode == Cipher.DECRYPT_MODE) + { + input = decode(input); + } + + initCipher(mode); + + byte[] output = cipher.doFinal(input); + + if (mode == Cipher.ENCRYPT_MODE) + { + output = encode(output); + } + + return output; + } + + + /** + * Initialize {@link cipher} by calling one of {@code Cipher.init} methods + * using {@code mode} and one or some of {@link #key}, {@link #params}, + * {@link #spec}, {@link #certificate} and {@link #random}. + */ + private void initCipher(int mode) throws InvalidKeyException, InvalidAlgorithmParameterException + { + if (key != null) + { + if (params != null) + { + if (random != null) + { + cipher.init(mode, key, params, random); + } + else + { + cipher.init(mode, key, params); + } + } + else if (spec != null) + { + if (random != null) + { + cipher.init(mode, key, spec, random); + } + else + { + cipher.init(mode, key, spec); + } + } + else + { + if (random != null) + { + cipher.init(mode, key, random); + } + else + { + cipher.init(mode, key); + } + } + } + else + { + if (random != null) + { + cipher.init(mode, certificate, random); + } + else + { + cipher.init(mode, certificate); + } + } + } + + + /** + * Encode the byte array using {@link #encoder}, or using + * {@link #DEFAULT_CODER} if {@code encoder} is {@code null}. + */ + private byte[] encode(byte[] input) throws EncoderException + { + if (encoder != null) + { + return encoder.encode(input); + } + else + { + return DEFAULT_CODER.encode(input); + } + } + + + /** + * Decode the byte array using {@link #decoder}, or using + * {@link #DEFAULT_CODER} if {@code decoder} is {@code null}. + */ + private byte[] decode(byte[] input) throws DecoderException + { + if (decoder != null) + { + return decoder.decode(input); + } + else + { + return DEFAULT_CODER.decode(input); + } + } +} diff --git a/src/main/java/com/authlete/common/util/security/StandardCipherTransformations.java b/src/main/java/com/authlete/common/util/security/StandardCipherTransformations.java new file mode 100644 index 0000000000..1489f5ae09 --- /dev/null +++ b/src/main/java/com/authlete/common/util/security/StandardCipherTransformations.java @@ -0,0 +1,114 @@ +package com.authlete.common.util.security; + + +/** + * List of standard cipher transformations which are valid as + * arguments for {@link javax.crypto.Cipher#getInstance(String)}. + * + *

+ * The list here is a copy from Java SE 7 JavaDoc. Therefore, + * they may not be supported in older Java SE environments. + *

+ * + * @see javax.crypto.Cipher + * + * @author Takahiko Kawasaki + * @since 4.23 + */ +public class StandardCipherTransformations +{ + /** + * AES/CBC/NoPadding (128) + */ + public static final String AES_CBC_NOPADDING = "AES/CBC/NoPadding"; + + + /** + * AES/CBC/PKCS5Padding (128) + */ + public static final String AES_CBC_PKCS5PADDING = "AES/CBC/PKCS5Padding"; + + + /** + * AES/ECB/NoPadding (128) + */ + public static final String AES_ECB_NOPADDING = "AES/ECB/NoPadding"; + + + /** + * AES/ECB/PKCS5Padding (128) + */ + public static final String AES_ECB_PKCS5PADDING = "AES/ECB/PKCS5Padding"; + + + /** + * DES/CBC/NoPadding (56) + */ + public static final String DES_CBC_NOPADDING = "DES/CBC/NoPadding"; + + + /** + * DES/CBC/PKCS5Padding (56) + */ + public static final String DES_CBC_PKCS5PADDING = "DES/CBC/PKCS5Padding"; + + + /** + * DES/ECB/NoPadding (56) + */ + public static final String DES_ECB_NOPADDING = "DES/ECB/NoPadding"; + + + /** + * DES/ECB/PKCS5Padding (56) + */ + public static final String DES_ECB_PKCS5PADDING = "DES/ECB/PKCS5Padding"; + + + /** + * DESede/CBC/NoPadding (168) + */ + public static final String DESEDE_CBC_NOPADDING = "DESede/CBC/NoPadding"; + + + /** + * DESede/CBC/PKCS5Padding (168) + */ + public static final String DESEDE_CBC_PKCS5PADDING = "DESede/CBC/PKCS5Padding"; + + + /** + * DESede/ECB/NoPadding (168) + */ + public static final String DESEDE_ECB_NOPADDING = "DESede/ECB/NoPadding"; + + + /** + * DESede/ECB/PKCS5Padding (168) + */ + public static final String DESEDE_ECB_PKCS5PADDING = "DESede/ECB/PKCS5Padding"; + + + /** + * RSA/ECB/PKCS1Padding (1024, 2048) + */ + public static final String RSA_ECB_PKCS1PADDING = "RSA/ECB/PKCS1Padding"; + + + /** + * RSA/ECB/OAEPWithSHA-1AndMGF1Padding (1024, 2048) + */ + public static final String RSA_ECB_OAEPWITHSHA1ANDMGF1PADDING = "RSA/ECB/OAEPWithSHA-1AndMGF1Padding"; + + + /** + * RSA/ECB/OAEPWithSHA-256AndMGF1Padding (1024, 2048) + */ + public static final String RSA_ECB_OAEPWITHSHA256ANDMGF1PADDING = "RSA/ECB/OAEPWithSHA-256AndMGF1Padding"; + + + private StandardCipherTransformations() + { + } +} diff --git a/src/main/java/com/authlete/common/util/security/Utils.java b/src/main/java/com/authlete/common/util/security/Utils.java new file mode 100644 index 0000000000..9977d45a92 --- /dev/null +++ b/src/main/java/com/authlete/common/util/security/Utils.java @@ -0,0 +1,113 @@ +package com.authlete.common.util.security; + + +import java.io.UnsupportedEncodingException; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.SecretKeySpec; + + +/** + * Utility methods for internal implementation. + * + * @author Takahiko Kawasaki + * @since 4.23 + */ +class Utils +{ + private Utils() + { + } + + + /** + * Get bytes by {@code input.getBytes("UTF-8")}. + */ + public static byte[] getBytesUTF8(String string) + { + if (string == null) + { + return null; + } + + try + { + // Convert the string to a byte array encoded in UTF-8. + return string.getBytes("UTF-8"); + } + catch (UnsupportedEncodingException e) + { + // This won't happen. + return null; + } + } + + + /** + * Build a {@code String} instance by {@code new String(input, "UTF-8")}. + */ + public static String toStringUTF8(byte[] input) + { + if (input == null) + { + return null; + } + + try + { + return new String(input, "UTF-8"); + } + catch (UnsupportedEncodingException e) + { + // This won't happen. + return null; + } + } + + + public static byte[] ensureSize(byte[] data, int size) + { + if (data == null) + { + return new byte[size]; + } + + if (size <= data.length) + { + return data; + } + + byte[] data2 = new byte[size]; + + System.arraycopy(data, 0, data2, 0, data.length); + + return data2; + } + + + public static SecretKeySpec createSecretKeySpec(String key, String algorithm, int size) + { + return createSecretKeySpec(getBytesUTF8(key), algorithm, size); + } + + + public static SecretKeySpec createSecretKeySpec(byte[] key, String algorithm, int size) + { + key = ensureSize(key, size); + + return new SecretKeySpec(key, 0, size, algorithm); + } + + + public static IvParameterSpec createIvParameterSpec(String iv, int size) + { + return createIvParameterSpec(getBytesUTF8(iv), size); + } + + + public static IvParameterSpec createIvParameterSpec(byte[] iv, int size) + { + iv = ensureSize(iv, size); + + return new IvParameterSpec(iv, 0, size); + } +} diff --git a/src/test/java/com/authlete/common/api/AccessRightTest.java b/src/test/java/com/authlete/common/api/AccessRightTest.java new file mode 100644 index 0000000000..5ef8a6161a --- /dev/null +++ b/src/test/java/com/authlete/common/api/AccessRightTest.java @@ -0,0 +1,166 @@ +/* + * Copyright (C) 2026 Authlete, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.authlete.common.api; + + +import static com.authlete.common.api.AccessRight.*; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import org.junit.Test; + + +public class AccessRightTest +{ + private static final long SERVICE_ID = 1L; + private static final long CLIENT_ID = 2L; + private static final long NONE = 0L; + + + // --- canDo: identity --- + + @Test + public void testCanDoSelf() + { + assertTrue(USE_INTROSPECTION.canDo(NONE, SERVICE_ID, USE_INTROSPECTION)); + assertTrue(USE_SERVICE.canDo(NONE, SERVICE_ID, USE_SERVICE)); + assertTrue(MODIFY_SERVICE.canDo(NONE, SERVICE_ID, MODIFY_SERVICE)); + assertTrue(CREATE_CLIENT.canDo(NONE, SERVICE_ID, CREATE_CLIENT)); + assertTrue(ADMIN.canDo(NONE, NONE, ADMIN)); + } + + + // --- canDo: USE_INTROSPECTION subsumption --- + + @Test + public void testUseServiceIncludesUseIntrospection() + { + assertTrue(USE_SERVICE.canDo(NONE, SERVICE_ID, USE_INTROSPECTION)); + } + + @Test + public void testModifyServiceIncludesUseIntrospection() + { + assertTrue(MODIFY_SERVICE.canDo(NONE, SERVICE_ID, USE_INTROSPECTION)); + } + + @Test + public void testCreateClientIncludesUseIntrospection() + { + assertTrue(CREATE_CLIENT.canDo(NONE, SERVICE_ID, USE_INTROSPECTION)); + } + + @Test + public void testAdminIncludesUseIntrospection() + { + assertTrue(ADMIN.canDo(NONE, SERVICE_ID, USE_INTROSPECTION)); + } + + + // --- canDo: VIEW_SERVICE subsumption --- + + @Test + public void testUseServiceIncludesViewService() + { + assertTrue(USE_SERVICE.canDo(NONE, SERVICE_ID, VIEW_SERVICE)); + } + + @Test + public void testModifyServiceIncludesViewService() + { + assertTrue(MODIFY_SERVICE.canDo(NONE, SERVICE_ID, VIEW_SERVICE)); + } + + @Test + public void testCreateClientIncludesViewService() + { + assertTrue(CREATE_CLIENT.canDo(NONE, SERVICE_ID, VIEW_SERVICE)); + } + + + // --- canDo: missing service/client IDs --- + + @Test + public void testServiceSpecificRequiresServiceId() + { + assertFalse(USE_INTROSPECTION.canDo(NONE, NONE, USE_INTROSPECTION)); + assertFalse(USE_SERVICE.canDo(NONE, NONE, USE_SERVICE)); + assertFalse(MODIFY_SERVICE.canDo(NONE, NONE, MODIFY_SERVICE)); + } + + @Test + public void testClientSpecificRequiresBoth() + { + assertFalse(VIEW_CLIENT.canDo(NONE, SERVICE_ID, VIEW_CLIENT)); + assertFalse(VIEW_CLIENT.canDo(CLIENT_ID, NONE, VIEW_CLIENT)); + assertTrue(VIEW_CLIENT.canDo(CLIENT_ID, SERVICE_ID, VIEW_CLIENT)); + } + + + // --- canDo: no upward escalation --- + + @Test + public void testUseIntrospectionDoesNotIncludeUseService() + { + assertFalse(USE_INTROSPECTION.canDo(NONE, SERVICE_ID, USE_SERVICE)); + } + + @Test + public void testViewServiceDoesNotIncludeModifyService() + { + assertFalse(VIEW_SERVICE.canDo(NONE, SERVICE_ID, MODIFY_SERVICE)); + } + + @Test + public void testUseServiceDoesNotIncludeModifyService() + { + assertFalse(USE_SERVICE.canDo(NONE, SERVICE_ID, MODIFY_SERVICE)); + } + + + // --- canDoAny: USE_INTROSPECTION --- + + @Test + public void testCanDoAnyUseServiceIncludesUseIntrospection() + { + assertTrue(USE_SERVICE.canDoAny(SERVICE_ID, USE_INTROSPECTION)); + } + + @Test + public void testCanDoAnyModifyServiceIncludesUseIntrospection() + { + assertTrue(MODIFY_SERVICE.canDoAny(SERVICE_ID, USE_INTROSPECTION)); + } + + @Test + public void testCanDoAnyCreateClientIncludesUseIntrospection() + { + assertTrue(CREATE_CLIENT.canDoAny(SERVICE_ID, USE_INTROSPECTION)); + } + + @Test + public void testCanDoAnyAdminIncludesUseIntrospection() + { + assertTrue(ADMIN.canDoAny(SERVICE_ID, USE_INTROSPECTION)); + } + + @Test + public void testCanDoAnyClientSpecificRequiresServiceId() + { + // VIEW_CLIENT is clientSpecific — canDoAny requires a serviceId in that case + assertFalse(VIEW_CLIENT.canDoAny(NONE, VIEW_CLIENT)); + assertTrue(VIEW_CLIENT.canDoAny(SERVICE_ID, VIEW_CLIENT)); + } +} diff --git a/src/test/java/com/authlete/common/assurance/VerifiedClaimsTest.java b/src/test/java/com/authlete/common/assurance/VerifiedClaimsTest.java index 6cdd7dedbb..1d5defac96 100644 --- a/src/test/java/com/authlete/common/assurance/VerifiedClaimsTest.java +++ b/src/test/java/com/authlete/common/assurance/VerifiedClaimsTest.java @@ -21,9 +21,9 @@ import java.io.IOException; import java.util.LinkedHashMap; import java.util.Map; +import com.authlete.common.util.digest.Digest; import org.junit.Test; import com.google.gson.Gson; -import com.neovisionaries.security.Digest; public class VerifiedClaimsTest diff --git a/src/test/java/com/authlete/common/assurance/constraint/VerifiedClaimsContainerConstraintTest.java b/src/test/java/com/authlete/common/assurance/constraint/VerifiedClaimsContainerConstraintTest.java index a4f1263f7c..bd271178e6 100644 --- a/src/test/java/com/authlete/common/assurance/constraint/VerifiedClaimsContainerConstraintTest.java +++ b/src/test/java/com/authlete/common/assurance/constraint/VerifiedClaimsContainerConstraintTest.java @@ -19,19 +19,8 @@ import static org.junit.Assert.*; import java.io.IOException; +import com.authlete.common.util.digest.Digest; import org.junit.Test; -import com.authlete.common.assurance.constraint.ClaimsConstraint; -import com.authlete.common.assurance.constraint.EvidenceArrayConstraint; -import com.authlete.common.assurance.constraint.EvidenceConstraint; -import com.authlete.common.assurance.constraint.IDDocumentConstraint; -import com.authlete.common.assurance.constraint.LeafConstraint; -import com.authlete.common.assurance.constraint.QESConstraint; -import com.authlete.common.assurance.constraint.TimeConstraint; -import com.authlete.common.assurance.constraint.UtilityBillConstraint; -import com.authlete.common.assurance.constraint.VerificationConstraint; -import com.authlete.common.assurance.constraint.VerifiedClaimsConstraint; -import com.authlete.common.assurance.constraint.VerifiedClaimsContainerConstraint; -import com.neovisionaries.security.Digest; public class VerifiedClaimsContainerConstraintTest diff --git a/src/test/java/com/authlete/common/dto/AuthzDetailsElementTest.java b/src/test/java/com/authlete/common/dto/AuthzDetailsElementTest.java index 3210ed2af9..0db0d215a9 100644 --- a/src/test/java/com/authlete/common/dto/AuthzDetailsElementTest.java +++ b/src/test/java/com/authlete/common/dto/AuthzDetailsElementTest.java @@ -16,10 +16,10 @@ package com.authlete.common.dto; +import com.authlete.common.util.digest.Digest; import org.junit.Test; import com.google.gson.Gson; import com.google.gson.JsonParseException; -import com.neovisionaries.security.Digest; import static org.junit.Assert.*; import java.io.IOException; import java.util.ArrayList; diff --git a/src/test/java/com/authlete/common/dto/CredentialIssuerMetadataTest.java b/src/test/java/com/authlete/common/dto/CredentialIssuerMetadataTest.java index 39b755295a..fc5eaf1f20 100644 --- a/src/test/java/com/authlete/common/dto/CredentialIssuerMetadataTest.java +++ b/src/test/java/com/authlete/common/dto/CredentialIssuerMetadataTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2024 Authlete, Inc. + * Copyright (C) 2024-2025 Authlete, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,8 +19,11 @@ import org.junit.Test; import com.authlete.common.types.JWEAlg; import com.authlete.common.types.JWEEnc; +import com.authlete.common.types.JWEZip; +import com.nimbusds.jose.jwk.JWK; +import com.nimbusds.jose.jwk.JWKSet; import static org.junit.Assert.*; -import java.net.URI; +import java.text.ParseException; import java.util.List; import java.util.Map; @@ -29,31 +32,87 @@ public class CredentialIssuerMetadataTest { @SuppressWarnings("unchecked") @Test - public void test_toMap() + public void test_toMap_request_encryption() throws ParseException { - URI issuer = URI.create("https://example.com"); - JWEAlg[] algs = new JWEAlg[] { JWEAlg.ECDH_ES }; - JWEEnc[] encs = new JWEEnc[] { JWEEnc.A128CBC_HS256 }; + String jwks = + "{\n" + + " \"keys\": [\n" + + " {\n" + + " \"kty\": \"EC\",\n" + + " \"crv\": \"P-256\",\n" + + " \"x\": \"8fpkqvB9-NjASgqR4KnM9s1gjyaDZpYYp9E65c7glns\",\n" + + " \"y\": \"SdbSFS314C2N415VQ7IaXkxHRH34aA32csKOs_JipSg\",\n" + + " \"d\": \"gTVIUWvmYAW2WT_lOMiQGBK2BbRHOhew-sO5JBTCkp4\",\n" + + " \"kid\": \"TxNdpxeiLUxuPXh_INadmBVkOqk77pmYLL7BthTpZsA\"\n" + + " }\n" + + " ]\n" + + "}\n" + + ""; + JWEEnc[] encs = new JWEEnc[] { JWEEnc.A128CBC_HS256 }; + JWEZip[] zips = new JWEZip[] { JWEZip.DEF }; + + CredentialIssuerMetadata metadata = new CredentialIssuerMetadata() + .setCredentialRequestEncryptionJwks(jwks) + .setCredentialRequestEncryptionEncValuesSupported(encs) + .setCredentialRequestEncryptionZipValuesSupported(zips) + .setRequireCredentialRequestEncryption(true) + ; + + Map map = metadata.toMap(); + + // credential_request_encryption + Map encryption = (Map)map.get("credential_request_encryption"); + assertNotNull(encryption); + + // credential_request_encryption.jwks + Map publicJwks = (Map)encryption.get("jwks"); + + List publicKeys = JWKSet.parse(publicJwks).getKeys(); + assertEquals(1, publicKeys.size()); + + for (JWK key : publicKeys) + { + assertFalse("Keys in 'jwks' must not be private keys.", key.isPrivate()); + } + + // credential_request_encryption.enc_values_supported + List encValues = (List)encryption.get("enc_values_supported"); + assertNotNull(encValues); + assertEquals(1, encValues.size()); + assertEquals("A128CBC-HS256", encValues.get(0)); + + // credential_request_encryption.zip_values_supported + List zipValues = (List)encryption.get("zip_values_supported"); + assertNotNull(zipValues); + assertEquals(1, zipValues.size()); + assertEquals("DEF", zipValues.get(0)); + + // credential_request_encryption.encrypition_required + assertEquals(Boolean.TRUE, encryption.get("encryption_required")); + } + + + @SuppressWarnings("unchecked") + @Test + public void test_toMap_response_encryption() + { + JWEAlg[] algs = new JWEAlg[] { JWEAlg.ECDH_ES }; + JWEEnc[] encs = new JWEEnc[] { JWEEnc.A128CBC_HS256 }; + JWEZip[] zips = new JWEZip[] { JWEZip.DEF }; CredentialIssuerMetadata metadata = new CredentialIssuerMetadata() - .setRequireCredentialResponseEncryption(true) - .setCredentialIssuer(issuer) .setCredentialResponseEncryptionAlgValuesSupported(algs) .setCredentialResponseEncryptionEncValuesSupported(encs) + .setCredentialResponseEncryptionZipValuesSupported(zips) + .setRequireCredentialResponseEncryption(true) ; Map map = metadata.toMap(); - // credential_issuer - assertEquals("https://example.com", map.get("credential_issuer")); - // credential_response_encryption Map encryption = (Map)map.get("credential_response_encryption"); assertNotNull(encryption); - // credential_response_encryption.encrypition_required - assertEquals(Boolean.TRUE, encryption.get("encryption_required")); - // credential_response_encryption.alg_values_supported List algValues = (List)encryption.get("alg_values_supported"); assertNotNull(algValues); @@ -65,5 +124,92 @@ public void test_toMap() assertNotNull(encValues); assertEquals(1, encValues.size()); assertEquals("A128CBC-HS256", encValues.get(0)); + + // credential_response_encryption.zip_values_supported + List zipValues = (List)encryption.get("zip_values_supported"); + assertNotNull(zipValues); + assertEquals(1, zipValues.size()); + assertEquals("DEF", zipValues.get(0)); + + // credential_response_encryption.encrypition_required + assertEquals(Boolean.TRUE, encryption.get("encryption_required")); + } + + + @SuppressWarnings("unchecked") + @Test + public void test_toMap_batch_size_1() + { + CredentialIssuerMetadata metadata = new CredentialIssuerMetadata() + .setBatchSize(1) + ; + + Map map = metadata.toMap(); + + // batch_credential_issuance + Map bci = (Map)map.get("batch_credential_issuance"); + assertNull(bci); + } + + + @SuppressWarnings("unchecked") + @Test + public void test_toMap_batch_size_2() + { + CredentialIssuerMetadata metadata = new CredentialIssuerMetadata() + .setBatchSize(2) + ; + + Map map = metadata.toMap(); + + // batch_credential_issuance + Map bci = (Map)map.get("batch_credential_issuance"); + assertNotNull(bci); + + // batch_credential_issuance.batch_size + Number bs = (Number)bci.get("batch_size"); + assertNotNull(bs); + assertEquals(2, bs.intValue()); + } + + + @SuppressWarnings("unchecked") + @Test + public void test_toMap_display() + { + String display = + "[\n" + + " {\n" + + " \"name\": \"Example University\",\n" + + " \"locale\": \"en-US\",\n" + + " \"logo\": {\n" + + " \"uri\": \"https://university.example.edu/public/logo.png\",\n" + + " \"alt_text\":\"a square logo of a university\"\n" + + " }\n" + + " },\n" + + " {\n" + + " \"name\": \"Example Université\",\n" + + " \"locale\": \"fr-FR\",\n" + + " \"logo\": {\n" + + " \"uri\": \"https://university.example.edu/public/logo.png\",\n" + + " \"alt_text\":\"Un logo universitaire carré\"\n" + + " }\n" + + " }\n" + + "]"; + + CredentialIssuerMetadata metadata = new CredentialIssuerMetadata() + .setDisplay(display) + ; + + Map map = metadata.toMap(); + + // display + Object displayObject = map.get("display"); + assertNotNull(displayObject); + + assertTrue("The value of 'display' should be a list", displayObject instanceof List); + List displayList = (List)displayObject; + + assertEquals(2, displayList.size()); } } diff --git a/src/test/java/com/authlete/common/ida/DatasetExtractorTest.java b/src/test/java/com/authlete/common/ida/DatasetExtractorTest.java index 6416a46d84..0a33c00de1 100644 --- a/src/test/java/com/authlete/common/ida/DatasetExtractorTest.java +++ b/src/test/java/com/authlete/common/ida/DatasetExtractorTest.java @@ -16,11 +16,11 @@ package com.authlete.common.ida; +import com.authlete.common.util.digest.Digest; import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.gson.Gson; -import com.neovisionaries.security.Digest; import static org.junit.Assert.*; import java.io.IOException; import java.io.InputStreamReader;