diff --git a/CHANGELOG.md b/CHANGELOG.md index 31e9571b..561b0ba5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,34 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). -## [3.1-?] - TBD +## [3.2] - TBD + +### Changes + +- Added legacy signing algorithms (`SHA256_WITH_RSA_ENCRYPTION`, `SHA384_WITH_RSA_ENCRYPTION`, `SHA512_WITH_RSA_ENCRYPTION`) for RSASSA-PKCS#1 v1.5. + - Compatible with DigiDoc4j library which does not support RSASSA-PSS. + - Use `SigningSignatureAlgorithm` enum and `withSignatureAlgorithm()` on signature session builders. + - Use `SigningSignatureAlgorithm.getHashAlgorithmForLegacy()` when creating `SignableData` for legacy algorithms. + - Legacy algorithms do not use `signatureAlgorithmParameters` in requests or responses. +- Split `SignatureAlgorithm` into `AuthenticationSignatureAlgorithm` (authentication) and `SigningSignatureAlgorithm` (signing). + - Only allowed `AuthenticationSignatureAlgorithm` is `RSASSA_PSS`; default `SigningSignatureAlgorithm` is `RSASSA_PSS`. +- Added `SignatureFactory` interface for creating `java.security.Signature` instance for verifying signature and added its implementations: + - `RsaSsaPssSignatureFactory` + - `RsaSsaPkcs1SignatureFactory` +- Changed `SignatureValueValidator.validate` last parameter from `RsaSsaPssParameters` to `SignatureFactory`: +- The following classes are moved from `ee.sk.smartid` to `ee.sk.smartid.signature`: + - `AuthenticationSignatureAlgorithm` + - `DigestInput` + - `MaskGenAlgorithm` + - `RsaSsaPssParameters` + - `SignableData` + - `SignableHash` + - `SignatureValueValidator` + - `SignatureValueValidatorImpl` + - `SigningSignatureAlgorithm` + - `TrailerField` + +## [3.1] - 2025-10-15 ### Structural changes diff --git a/MIGRATION_GUIDE.md b/MIGRATION_GUIDE.md index d10b351d..2de88b2e 100644 --- a/MIGRATION_GUIDE.md +++ b/MIGRATION_GUIDE.md @@ -4,6 +4,43 @@ Library v3.1 supports only Smart-ID v3 API. All the previous v2 related code has been removed and all the code necessary for Smart-ID API v3 is under package smartid. Some classes could also be used in v3 and for those classes the package did not change. +# Migrating from library v3.1 to v3.2 + +For signing flows are restored legacy RSASSA-PKCS#1 v1.5 algorithms (`SHA256_WITH_RSA_ENCRYPTION`, `SHA384_WITH_RSA_ENCRYPTION`, `SHA512_WITH_RSA_ENCRYPTION`) which are compatible with DigiDoc4j's signing support. +For that reason: +- `SignatureAlgorithm` class is split into `AuthenticationSignatureAlgorithm` and `SigningSignatureAlgorithm`. +- `SignatureValueValidator.validate` last parameter changed from `RsaSsaPssParameters` to `SignatureFactory` + +Changes needed in authentication flows: +- change `SignatureAlgorithm` to `AuthenticationSignatureAlgorithm` +- change `SignatureValueValidator.validate` last parameter from `RsaSsaPssParameters` to `new RsaSsaPssSignatureFactory(RsaSsaPssParameters)` + +Changes needed in signing flows: +- change `SignatureAlgorithm` to `SigningSignatureAlgorithm` +- suggestion for `SignatureValueValidator.validate` last parameter changes: + - when using only signature algorithm RSASSA_PSS then use `new RsaSsaPssSignatureFactory(RsaSsaPssParameters)` + - when using only legacy signature algorithms (`SHA256_WITH_RSA_ENCRYPTION`, `SHA384_WITH_RSA_ENCRYPTION`, `SHA512_WITH_RSA_ENCRYPTION`) then use `new RsaSsaPkcs1SignatureFactory(SigningSignatureAlgorithm)` + - when both RSASSA_PSS and legacy RSA algorithms are used then possible solution is: + ```java + SignatureFactory signatureFactory = signatureResponse.getSignatureAlgorithm().isLegacyRsa() + ? new RsaSsaPkcs1SignatureFactory(signatureResponse.getSignatureAlgorithm()) + : new RsaSsaPssSignatureFactory(signatureResponse.getRsaSsaPssParameters()); + ``` + +The following classes are moved from `ee.sk.smartid` to `ee.sk.smartid.signature` so when used then imports need to be adjusted: +- `AuthenticationSignatureAlgorithm` +- `DigestInput` +- `MaskGenAlgorithm` +- `RsaSsaPssParameters` +- `SignableData` +- `SignableHash` +- `SignatureValueValidator` +- `SignatureValueValidatorImpl` +- `SigningSignatureAlgorithm` +- `TrailerField` + +For legacy RSA with DigiDoc4j there is new chapter in README.md: [Legacy algorithms for signing with DigiDoc4j](./README.md#legacy-algorithms-for-signing-with-digidoc4j) + # Migrating from Smart-ID v2 to Smart-ID v3 API ## Migrating authentication diff --git a/README.md b/README.md index 9fadd696..9e900ada 100644 --- a/README.md +++ b/README.md @@ -56,6 +56,7 @@ This library supports Smart-ID API v3.1. * [Error handling for session status](#error-handling-for-session-status) * [Certificate by document number](#certificate-by-document-number) * [Example of querying certificate by document number](#example-of-querying-certificate-by-document-number) + * [Legacy algorithms for signing with DigiDoc4j](#legacy-algorithms-for-signing-with-digidoc4j) * [Linked signature session flow](#linked-signature-flow) * [Device link certificate choice session](#device-link-certificate-choice-session) * [Examples of initiating a device-link certificate choice session](#example-of-initiating-a-device-link-certificate-choice-session) @@ -203,9 +204,9 @@ More info available here https://sk-eid.github.io/smart-id-documentation/rp-api/ * `signatureProtocol`: Required. Signature protocol to use. Currently, the only allowed value is ACSP_V2. * `signatureProtocolParameters`: Required. Parameters for the ACSP_V2 signature protocol. * `rpChallenge`: Required. Base64-encoded value, length between 44 and 88 characters. - * `signatureAlgorithm`: Required. Signature algorithm name. Supported value only `rsassa-pss`. + * `signatureAlgorithm`: Required. Use `AuthenticationSignatureAlgorithm` enum. Supported value: `RSASSA_PSS`. * `signatureAlgorithmParameters`: Required. Parameters for the signature algorithm. - * `hashAlgorithm`: Required. Hash algorithm name. Supported values are `SHA-256`, `SHA-384`, `SHA-512`, `SHA3-256`, `SHA3-384`, `SHA3-512`. + * `hashAlgorithm`: Required. Use `HashAlgorithm` enum. Supported values are `SHA-256`, `SHA-384`, `SHA-512`, `SHA3-256`, `SHA3-384`, `SHA3-512`. * `interactions`: Required. Base64-encoded JSON string of an array of interaction objects. * Each interaction object includes: * `type`: Required. Type of interaction. Allowed types are `displayTextAndPIN`, `confirmationMessage`. @@ -240,6 +241,8 @@ DeviceLinkAuthenticationSessionRequestBuilder builder = smartIdClient .createDeviceLinkAuthentication() // to use anonymous authentication, do not set semantics identifier or document number .withRpChallenge(rpChallenge.toBase64EncodedValue()) + .withSignatureAlgorithm(AuthenticationSignatureAlgorithm.RSASSA_PSS) + .withHashAlgorithm(HashAlgorithm.SHA3_512) .withInteractions(Collections.singletonList( DeviceLinkInteraction.displayTextAndPin("Logging into ") // Display text should be concise and specific. )); @@ -289,6 +292,8 @@ DeviceLinkAuthenticationSessionRequestBuilder builder = smartIdClient .createDeviceLinkAuthentication() .withSemanticsIdentifier(semanticsIdentifier) .withRpChallenge(rpChallenge.toBase64EncodedValue()) + .withSignatureAlgorithm(AuthenticationSignatureAlgorithm.RSASSA_PSS) + .withHashAlgorithm(HashAlgorithm.SHA3_512) .withInteractions(Collections.singletonList( DeviceLinkInteraction.displayTextAndPin("Logging into ") // Display text should be concise and specific. )); @@ -331,6 +336,8 @@ DeviceLinkAuthenticationSessionRequestBuilder builder = smartIdClient .createDeviceLinkAuthentication() .withDocumentNumber(documentNumber) .withRpChallenge(rpChallenge.toBase64EncodedValue()) + .withSignatureAlgorithm(AuthenticationSignatureAlgorithm.RSASSA_PSS) + .withHashAlgorithm(HashAlgorithm.SHA3_512) .withInteractions(Collections.singletonList( DeviceLinkInteraction.displayTextAndPin("Logging into ") // Display text should be concise and specific. )); @@ -377,6 +384,8 @@ DeviceLinkAuthenticationSessionRequestBuilder builder = smartIdClient .createDeviceLinkAuthentication() .withDocumentNumber(documentNumber) .withRpChallenge(rpChallenge.toBase64EncodedValue()) + .withSignatureAlgorithm(AuthenticationSignatureAlgorithm.RSASSA_PSS) + .withHashAlgorithm(HashAlgorithm.SHA3_512) .withInteractions(Collections.singletonList( DeviceLinkInteraction.displayTextAndPin("Logging into ") // Display text should be concise and specific. )) @@ -419,9 +428,9 @@ The request parameters for the device-link signature session are as follows: * `signatureProtocol`: Required. Signature protocol to use. Currently, the only allowed value is RAW_DIGEST_SIGNATURE. * `signatureProtocolParameters`: Required. Parameters for the RAW_DIGEST_SIGNATURE signature protocol. * `digest`: Required. Base64 encoded digest to be signed. - * `signatureAlgorithm`: Required. Signature algorithm name. Only supported value is `rsassa-pss` - * `signatureAlgorithmParameters`: Required. Parameters for the signature algorithm. - * `hashAlgorithm`: Required. Hash algorithm name. Supported values are `SHA-256`, `SHA-384`, `SHA-512`, `SHA3-256`, `SHA3-384`, `SHA3-512`. + * `signatureAlgorithm`: Required. Use `SigningSignatureAlgorithm` enum. Supported: `RSASSA_PSS` (default), `SHA256_WITH_RSA_ENCRYPTION`, `SHA384_WITH_RSA_ENCRYPTION`, `SHA512_WITH_RSA_ENCRYPTION`. Legacy algorithms are compatible with DigiDoc4j. + * `signatureAlgorithmParameters`: Required for RSASSA_PSS; omit for legacy algorithms. Parameters for the signature algorithm. + * `hashAlgorithm`: Required. Use `HashAlgorithm` enum. Supported values are `SHA-256`, `SHA-384`, `SHA-512`, `SHA3-256`, `SHA3-384`, `SHA3-512`. * `interactions`: Required. Base64-encoded JSON string of an array of interaction objects. * Each interaction object includes: * `type`: Required. Type of interaction. Allowed types are `displayTextAndPIN`, `confirmationMessage`. @@ -461,6 +470,7 @@ DeviceLinkSessionResponse signatureResponse = client.createDeviceLinkSignature() .withCertificateLevel(CertificateLevel.QSCD) .withSignableData(signableData) .withSemanticsIdentifier(semanticsIdentifier) + .withSignatureAlgorithm(SigningSignatureAlgorithm.RSASSA_PSS) .withHashAlgorithm(HashAlgorithm.SHA_512) .withInteractions(List.of(DeviceLinkInteraction.displayTextAndPin("Please sign the "))) // Display text should be concise and specific. .withInitialCallbackUrl("https://example.com/callback") // Only needed for same-device flows(Web2App, App2App) @@ -494,6 +504,7 @@ DeviceLinkSessionResponse signatureResponse = smartIdClient.createDeviceLinkSign .withCertificateLevel(CertificateLevel.QSCD) .withSignableData(signableData) .withDocumentNumber(documentNumber) + .withSignatureAlgorithm(SigningSignatureAlgorithm.RSASSA_PSS) .withHashAlgorithm(HashAlgorithm.SHA_512) .withInteractions(List.of(DeviceLinkInteraction.displayTextAndPin("Please sign the "))) // Display text should be concise and specific. .initSignatureSession(); @@ -926,13 +937,17 @@ try { // Initialize the signature response validator with CertificateValidator SignatureResponseValidator signatureResponseValidator = new SignatureResponseValidator(certificateValidator); // Validate and map the session status. If the sessions end result is other than OK, then an exception will be thrown. - SignatureResponse signatureResponse = signatureResponseValidator.validate(signatureSessionStatus, CertificateLevel.QUALIFIED.name()); - // Validate signature value. This step can be skipped if other means of validating the signature value can be used. + SignatureResponse signatureResponse = signatureResponseValidator.validate(signatureSessionStatus, CertificateLevel.QUALIFIED); + // Validate signature value using suitable SignatureFactory, for RSASSA_PSS use RsaSsaPssSignatureFactory and for legacy RSA RsaSsaPkcs1SignatureFactory SignatureValueValidator signatureValueValidator = new SignatureValueValidatorImpl(); - signatureValueValidator.validate(signatureResponse.getSignatureValue(), - signableData.calculateHash(), + SignatureFactory signatureFactory = signatureResponse.getSignatureAlgorithm().isLegacyRsa() + ? new RsaSsaPkcs1SignatureFactory(signatureResponse.getSignatureAlgorithm()) + : new RsaSsaPssSignatureFactory(signatureResponse.getRsaSsaPssParameters()); + signatureValueValidator.validate( + signatureResponse.getSignatureValue(), + signableData.dataToSign(), certResponse.certificate(), - signatureResponse.getRsaSsaPssParameters()); + signatureFactory); // Process the response (e.g., save to database or pass to another system) handleSignatureResponse(signatureResponse); @@ -1011,6 +1026,86 @@ certificateValidator.validateCertificate(certResponse.certificate()); ``` Checkout out other ways to set up TrustedCaCertStore with CertificateValidator in [Set up CertificateValidator](#set-up-certificatevalidator). +## Legacy algorithms for signing with DigiDoc4j + +DigiDoc4j library does not support RSASSA-PSS. To sign with DigiDoc4j, use legacy algorithms: `SHA256_WITH_RSA_ENCRYPTION`, `SHA384_WITH_RSA_ENCRYPTION`, or `SHA512_WITH_RSA_ENCRYPTION` from `SigningSignatureAlgorithm`. + +When in signing using legacy RSA and DigiDoc4J then conversion from `ee.sk.smartid.signature.SigningSignatureAlgorithm` to DigiDoc4J `org.digidoc4j.DigestAlgorithm` is: +```java +switch (signatureAlgorithm) { + case SHA256_WITH_RSA_ENCRYPTION -> DigestAlgorithm.SHA256; + case SHA384_WITH_RSA_ENCRYPTION -> DigestAlgorithm.SHA384; + case SHA512_WITH_RSA_ENCRYPTION -> DigestAlgorithm.SHA512; +} +``` + +### Create `SignableData` with DigiDoc4j's `SignatureBuilder` using the queried certificate + +Sample code snippet (Prerequisite: `SmartIdClient smartIdClient` configured and available to use): + +```java +var signatureCertificateLevel = CertificateLevel.QUALIFIED; +var documentNumber = "PNOEE-40504040001-DEM0-Q"; +var dataBytes = "Signable data".getBytes(); +var dataFileName = "test.txt"; +var dataFileMimeType = "text/plain"; +var signatureAlgorithm = SigningSignatureAlgorithm.SHA512_WITH_RSA_ENCRYPTION; +org.digidoc4j.DigestAlgorithm digestAlgorithm = DigestAlgorithm.SHA512; + +var certificateByDocumentNumberResult = smartIdClient + .createCertificateByDocumentNumber() + .withDocumentNumber(documentNumber) + .withCertificateLevel(signatureCertificateLevel) + .getCertificateByDocumentNumber(); +var certificate = certificateByDocumentNumberResult.certificate(); + +org.digidoc4j.Configuration configuration = new Configuration(Configuration.Mode.PROD); +org.digidoc4j.DataFile dataFile = new DataFile(dataBytes, dataFileName, dataFileMimeType); +org.digidoc4j.Container container = ContainerBuilder.aContainer() + .withConfiguration(configuration) + .withDataFile(dataFile) + .build(); +org.digidoc4j.DataToSign dataToSign = SignatureBuilder.aSignature(container) + .withSigningCertificate(certificate) + .withSignatureDigestAlgorithm(digestAlgorithm) + .withSignatureProfile(SignatureProfile.LT) + .buildDataToSign(); +byte[] dataToSignBytes = dataToSign.getDataToSign(); +SignableData signableData = new SignableData(dataToSignBytes, signatureAlgorithm.getHashAlgorithmForLegacy()); +``` + +### Validating signature using DigiDoc4j + +Prerequisite: +- `container` and `dataToSign` are the same as in previous code fragment +- `SignatureResponse signatureResponse` is read from RP API with successful response + +Sample code snippet: +```java +byte[] signatureValue = signatureResponse.getSignatureValue(); + +SignatureValueValidator validator = new SignatureValueValidatorImpl(); +SigningSignatureAlgorithm signatureAlgorithm = signatureResponse.getSignatureAlgorithm(); +SignatureFactory signatureFactory = signatureResponse.getSignatureAlgorithm().isLegacyRsa() + ? new RsaSsaPkcs1SignatureFactory(signatureResponse.getSignatureAlgorithm()) + : new RsaSsaPssSignatureFactory(signatureResponse.getRsaSsaPssParameters()); +validator.validate( + signatureValue, + dataToSign.getDataToSign(), + signatureResponse.getCertificate(), + signatureFactory); + +org.digidoc4j.Signature digiDoc4jSignature = dataToSign.finalize(signatureValue); +container.addSignature(digiDoc4jSignature); + +org.digidoc4j.ValidationResult validationResult = digiDoc4jSignature.validateSignature(); + +// possible data to use and DigiDoc4J container to save +boolean signatureValid = validationResult.isValid(); +Date timeStampCreationTime = digiDoc4jSignature.getTimeStampCreationTime(); +container.saveAsFile("targetPath"); +``` + ## Linked signature flow In API v3.1 a new flow was introduced to link signature session to a previously completed certificate choice session. @@ -1070,9 +1165,9 @@ Second part of the linked signature flow. Will be used to start the signature se * `signatureProtocol`: Required. Signature protocol to use. Currently, the only allowed value is RAW_DIGEST_SIGNATURE. * `signatureProtocolParameters`: Required. Parameters for the RAW_DIGEST_SIGNATURE signature protocol. * `digest`: Required. Base64 encoded digest to be signed. - * `signatureAlgorithm`: Required. Signature algorithm name. Only supported value is `rsassa-pss` - * `signatureAlgorithmParameters`: Required. Parameters for the signature algorithm. - * `hashAlgorithm`: Required. Hash algorithm name. Supported values are `SHA-256`, `SHA-384`, `SHA-512`, `SHA3-256`, `SHA3-384`, `SHA3-512`. + * `signatureAlgorithm`: Required. Use `SigningSignatureAlgorithm` enum. Supported: `RSASSA_PSS` (default), legacy algorithms for DigiDoc4j compatibility. + * `signatureAlgorithmParameters`: Required for RSASSA_PSS; omit for legacy algorithms. + * `hashAlgorithm`: Required. Use `HashAlgorithm` enum. Supported values are `SHA-256`, `SHA-384`, `SHA-512`, `SHA3-256`, `SHA3-384`, `SHA3-512`. * `linkedSessionID`: Required. Session ID of the previously completed certificate choice session. * `interactions`: Required. Base64-encoded JSON string of an array of interaction objects. * Each interaction object includes: @@ -1099,6 +1194,7 @@ LinkedSignatureSessionResponse signatureSessionResponse = smartIdClient.createLi .withDocumentNumber(certificateChoiceResponse.getDocumentNumber()) .withLinkedSessionID(certificateChoiceSessionResponse.sessionID()) .withSignableData(new SignableData("dataToSign".getBytes(), HashAlgorithm.SHA_256)) + .withSignatureAlgorithm(SigningSignatureAlgorithm.RSASSA_PSS) .withInteractions(List.of(DeviceLinkInteraction.displayTextAndPin("Please sign the "))) // Display text should be concise and specific. .initSignatureSession(); @@ -1135,9 +1231,9 @@ Jump to [Query session status](#example-of-using-session-status-poller-to-query- * `signatureProtocol`: Required. Signature protocol to use. Currently, the only allowed value is ACSP_V2. * `signatureProtocolParameters`: Required. Parameters for the ACSP_V2 signature protocol. * `rpChallenge`: Required. Random value with size in range of 32-64 bytes. Must be Base64 encoded. - * `signatureAlgorithm`: Required. Signature algorithm name. Supported values is 'rsassa-pss' + * `signatureAlgorithm`: Required. Use `AuthenticationSignatureAlgorithm` enum. Supported value: `RSASSA_PSS`. * `signatureAlgorithmParameters`: Required. Parameters for the signature algorithm. - * `hashAlgorithm`: Required. Hash algorithm name. Supported values are `SHA-256`, `SHA-384`, `SHA-512`, `SHA3-256`, `SHA3-384`, `SHA3-512`. + * `hashAlgorithm`: Required. Use `HashAlgorithm` enum. Supported values are `SHA-256`, `SHA-384`, `SHA-512`, `SHA3-256`, `SHA3-384`, `SHA3-512`. * `interactions`: Required. An array of interaction objects defining the interactions in order of preference. * Each interaction object includes: * `type`: Required. Type of interaction. Allowed types are `displayTextAndPIN`, `confirmationMessage`, `confirmationMessageAndVerificationCodeChoice`. @@ -1170,6 +1266,8 @@ NotificationAuthenticationSessionResponse authenticationSessionResponse = client .withDocumentNumber(documentNumber) .withRpChallenge(rpChallenge.toBase64EncodedValue()) .withCertificateLevel(AuthenticationCertificateLevel.QUALIFIED) + .withSignatureAlgorithm(AuthenticationSignatureAlgorithm.RSASSA_PSS) + .withHashAlgorithm(HashAlgorithm.SHA3_512) .withInteractions(Collections.singletonList( NotificationInteraction.displayTextAndPin("Logging into ") // Display text should be concise and specific. )) @@ -1202,6 +1300,8 @@ NotificationAuthenticationSessionResponse authenticationSessionResponse = client .withSemanticsIdentifier(semanticsIdentifier) .withRpChallenge(rpChallenge.toBase64EncodedValue()) .withCertificateLevel(AuthenticationCertificateLevel.QUALIFIED) + .withSignatureAlgorithm(AuthenticationSignatureAlgorithm.RSASSA_PSS) + .withHashAlgorithm(HashAlgorithm.SHA3_512) .withInteractions(Collections.singletonList( NotificationInteraction.displayTextAndPin("Logging into "))) // Display text should be concise and specific. .initAuthenticationSession(); @@ -1263,9 +1363,9 @@ The request parameters for the notification-based signature session are as follo * `signatureProtocol`: Required. Signature protocol to use. Currently, the only allowed value is RAW_DIGEST_SIGNATURE. * `signatureProtocolParameters`: Required. Parameters for the RAW_DIGEST_SIGNATURE signature protocol. * `digest`: Required. Base64 encoded digest to be signed. - * `signatureAlgorithm`: Required. Signature algorithm name. Only `rsassa-pss` is currently supported. - * `signatureAlgorithmParameters`: Required. Parameters for the signature algorithm. - * `hashAlgorithm`: Required. Hash algorithm used for digest. Supported values are `SHA-256`, `SHA-384`, `SHA-512`, `SHA3-256`, `SHA3-384`, `SHA3-512`. + * `signatureAlgorithm`: Required. Use `SigningSignatureAlgorithm` enum. Supported: `RSASSA_PSS` (default), legacy algorithms for DigiDoc4j compatibility. + * `signatureAlgorithmParameters`: Required for RSASSA_PSS; omit for legacy algorithms. + * `hashAlgorithm`: Required. Use `HashAlgorithm` enum. Supported values are `SHA-256`, `SHA-384`, `SHA-512`, `SHA3-256`, `SHA3-384`, `SHA3-512`. * `interactions`: Required. Base64-encoded string of interactions to be used for a session. The interactions are defined in order of preference. * Each interaction object includes: * `type`: Required. Type of interaction. Allowed types are `displayTextAndPIN`, `confirmationMessage`, `confirmationMessageAndVerificationCodeChoice`. @@ -1301,6 +1401,7 @@ NotificationSignatureSessionResponse signatureSessionResponse = smartIdClient.cr .withCertificateLevel(CertificateLevel.QSCD) .withSignableData(signableData) .withSemanticsIdentifier(semanticsIdentifier) + .withSignatureAlgorithm(SigningSignatureAlgorithm.RSASSA_PSS) .withInteractions(List.of( NotificationInteraction.confirmationMessage("Please sign the ")) // Display text should be concise and specific. ) @@ -1310,7 +1411,7 @@ NotificationSignatureSessionResponse signatureSessionResponse = smartIdClient.cr String sessionID = signatureSessionResponse.sessionID(); // Display verification code to the user -String verificationCode = signatureSessionResponse.vc().getValue(); +String verificationCode = signatureSessionResponse.vc().value(); ``` Jump to [Query session status](#example-of-using-session-status-poller-to-query-final-sessions-status) for an example of session querying. @@ -1330,7 +1431,8 @@ NotificationSignatureSessionResponse signatureResponse = client.createNotificati .withCertificateLevel(CertificateLevel.QUALIFIED) .withSignableData(signableData) .withDocumentNumber(documentNumber) - .withAllowedInteractionsOrder(List.of( + .withSignatureAlgorithm(SigningSignatureAlgorithm.RSASSA_PSS) + .withInteractions(List.of( NotificationInteraction.confirmationMessage("Please sign the "))) // Display text should be concise and specific. .initSignatureSession(); diff --git a/pom.xml b/pom.xml index 7e13153b..a18b77ee 100644 --- a/pom.xml +++ b/pom.xml @@ -7,7 +7,7 @@ ee.sk.smartid smart-id-java-client jar - 3.0-SNAPSHOT + 3.2-SNAPSHOT Smart-ID Java client Smart-ID Java client is a Java library that can be used for easy integration of the Smart-ID solution to information systems or e-services diff --git a/src/main/java/ee/sk/smartid/AuthenticationResponse.java b/src/main/java/ee/sk/smartid/AuthenticationResponse.java index 25bdabba..103f7dde 100644 --- a/src/main/java/ee/sk/smartid/AuthenticationResponse.java +++ b/src/main/java/ee/sk/smartid/AuthenticationResponse.java @@ -4,7 +4,7 @@ * #%L * Smart ID sample Java client * %% - * Copyright (C) 2018 - 2025 SK ID Solutions AS + * Copyright (C) 2018 - 2026 SK ID Solutions AS * %% * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -31,6 +31,7 @@ import java.util.Base64; import ee.sk.smartid.exception.UnprocessableSmartIdResponseException; +import ee.sk.smartid.signature.RsaSsaPssParameters; /** * The authentication response after a successful authentication session status response was received. diff --git a/src/main/java/ee/sk/smartid/AuthenticationResponseMapperImpl.java b/src/main/java/ee/sk/smartid/AuthenticationResponseMapperImpl.java index b036e61e..2077f8e9 100644 --- a/src/main/java/ee/sk/smartid/AuthenticationResponseMapperImpl.java +++ b/src/main/java/ee/sk/smartid/AuthenticationResponseMapperImpl.java @@ -39,6 +39,10 @@ import ee.sk.smartid.rest.dao.SessionResult; import ee.sk.smartid.rest.dao.SessionSignature; import ee.sk.smartid.rest.dao.SessionStatus; +import ee.sk.smartid.signature.AuthenticationSignatureAlgorithm; +import ee.sk.smartid.signature.MaskGenAlgorithm; +import ee.sk.smartid.signature.RsaSsaPssParameters; +import ee.sk.smartid.signature.TrailerField; import ee.sk.smartid.util.StringUtil; /** @@ -177,7 +181,7 @@ private static void validateSignature(SessionSignature sessionSignature) { if (StringUtil.isEmpty(sessionSignature.getSignatureAlgorithm())) { throw new UnprocessableSmartIdResponseException("Authentication session status field 'signature.signatureAlgorithm' is empty"); } - if (!SignatureAlgorithm.isSupported(sessionSignature.getSignatureAlgorithm())) { + if (!AuthenticationSignatureAlgorithm.isSupported(sessionSignature.getSignatureAlgorithm())) { logger.error("Authentication session status field 'signature.signatureAlgorithm' has invalid value: {}", sessionSignature.getSignatureAlgorithm()); throw new UnprocessableSmartIdResponseException("Authentication session status field 'signature.signatureAlgorithm' has unsupported value"); } diff --git a/src/main/java/ee/sk/smartid/DeviceLinkAuthenticationResponseValidator.java b/src/main/java/ee/sk/smartid/DeviceLinkAuthenticationResponseValidator.java index 55496621..f0e67ed5 100644 --- a/src/main/java/ee/sk/smartid/DeviceLinkAuthenticationResponseValidator.java +++ b/src/main/java/ee/sk/smartid/DeviceLinkAuthenticationResponseValidator.java @@ -4,7 +4,7 @@ * #%L * Smart ID sample Java client * %% - * Copyright (C) 2018 - 2025 SK ID Solutions AS + * Copyright (C) 2018 - 2026 SK ID Solutions AS * %% * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -37,6 +37,9 @@ import ee.sk.smartid.exception.useraccount.CertificateLevelMismatchException; import ee.sk.smartid.rest.dao.DeviceLinkAuthenticationSessionRequest; import ee.sk.smartid.rest.dao.SessionStatus; +import ee.sk.smartid.signature.RsaSsaPssSignatureFactory; +import ee.sk.smartid.signature.SignatureValueValidator; +import ee.sk.smartid.signature.SignatureValueValidatorImpl; import ee.sk.smartid.util.InteractionUtil; import ee.sk.smartid.util.StringUtil; @@ -159,7 +162,7 @@ private void validateSignature(AuthenticationResponse authenticationResponse, signatureValueValidator.validate(authenticationResponse.getSignatureValue(), payload, authenticationResponse.getCertificate(), - authenticationResponse.getRsaSsaPssSignatureParameters()); + new RsaSsaPssSignatureFactory(authenticationResponse.getRsaSsaPssSignatureParameters())); } private byte[] constructPayload(AuthenticationResponse authenticationResponse, diff --git a/src/main/java/ee/sk/smartid/DeviceLinkAuthenticationSessionRequestBuilder.java b/src/main/java/ee/sk/smartid/DeviceLinkAuthenticationSessionRequestBuilder.java index a7451f7e..89ee9253 100644 --- a/src/main/java/ee/sk/smartid/DeviceLinkAuthenticationSessionRequestBuilder.java +++ b/src/main/java/ee/sk/smartid/DeviceLinkAuthenticationSessionRequestBuilder.java @@ -4,7 +4,7 @@ * #%L * Smart ID sample Java client * %% - * Copyright (C) 2018 - 2025 SK ID Solutions AS + * Copyright (C) 2018 - 2026 SK ID Solutions AS * %% * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -42,6 +42,7 @@ import ee.sk.smartid.rest.dao.RequestProperties; import ee.sk.smartid.rest.dao.SemanticsIdentifier; import ee.sk.smartid.rest.dao.SignatureAlgorithmParameters; +import ee.sk.smartid.signature.AuthenticationSignatureAlgorithm; import ee.sk.smartid.util.InteractionUtil; import ee.sk.smartid.util.SetUtil; import ee.sk.smartid.util.StringUtil; @@ -59,7 +60,7 @@ public class DeviceLinkAuthenticationSessionRequestBuilder { private String relyingPartyName; private AuthenticationCertificateLevel certificateLevel = AuthenticationCertificateLevel.QUALIFIED; private String rpChallenge; - private SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.RSASSA_PSS; + private AuthenticationSignatureAlgorithm signatureAlgorithm = AuthenticationSignatureAlgorithm.RSASSA_PSS; private HashAlgorithm hashAlgorithm = HashAlgorithm.SHA3_512; private List interactions; private Boolean shareMdClientIpAddress; @@ -136,7 +137,7 @@ public DeviceLinkAuthenticationSessionRequestBuilder withRpChallenge(String rpCh * @param signatureAlgorithm the signature algorithm * @return this builder */ - public DeviceLinkAuthenticationSessionRequestBuilder withSignatureAlgorithm(SignatureAlgorithm signatureAlgorithm) { + public DeviceLinkAuthenticationSessionRequestBuilder withSignatureAlgorithm(AuthenticationSignatureAlgorithm signatureAlgorithm) { this.signatureAlgorithm = signatureAlgorithm; return this; } diff --git a/src/main/java/ee/sk/smartid/DeviceLinkSignatureSessionRequestBuilder.java b/src/main/java/ee/sk/smartid/DeviceLinkSignatureSessionRequestBuilder.java index 56687739..b6a99ea7 100644 --- a/src/main/java/ee/sk/smartid/DeviceLinkSignatureSessionRequestBuilder.java +++ b/src/main/java/ee/sk/smartid/DeviceLinkSignatureSessionRequestBuilder.java @@ -4,7 +4,7 @@ * #%L * Smart ID sample Java client * %% - * Copyright (C) 2018 - 2025 SK ID Solutions AS + * Copyright (C) 2018 - 2026 SK ID Solutions AS * %% * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -41,6 +41,10 @@ import ee.sk.smartid.rest.dao.SemanticsIdentifier; import ee.sk.smartid.rest.dao.SignatureAlgorithmParameters; import ee.sk.smartid.rest.dao.DeviceLinkSignatureSessionRequest; +import ee.sk.smartid.signature.DigestInput; +import ee.sk.smartid.signature.SignableData; +import ee.sk.smartid.signature.SignableHash; +import ee.sk.smartid.signature.SigningSignatureAlgorithm; import ee.sk.smartid.util.InteractionUtil; import ee.sk.smartid.util.SetUtil; import ee.sk.smartid.util.StringUtil; @@ -63,7 +67,7 @@ public class DeviceLinkSignatureSessionRequestBuilder { private Set capabilities; private List interactions; private Boolean shareMdClientIpAddress; - private SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.RSASSA_PSS; + private SigningSignatureAlgorithm signatureAlgorithm = SigningSignatureAlgorithm.RSASSA_PSS; private String initialCallbackUrl; private DigestInput digestInput; @@ -183,7 +187,7 @@ public DeviceLinkSignatureSessionRequestBuilder withShareMdClientIpAddress(boole * @param signatureAlgorithm the signature algorithm * @return this builder */ - public DeviceLinkSignatureSessionRequestBuilder withSignatureAlgorithm(SignatureAlgorithm signatureAlgorithm) { + public DeviceLinkSignatureSessionRequestBuilder withSignatureAlgorithm(SigningSignatureAlgorithm signatureAlgorithm) { this.signatureAlgorithm = signatureAlgorithm; return this; } @@ -287,9 +291,12 @@ private DeviceLinkSessionResponse initSignatureSession(DeviceLinkSignatureSessio } private DeviceLinkSignatureSessionRequest createSignatureSessionRequest() { + SignatureAlgorithmParameters algorithmParams = signatureAlgorithm.isLegacyRsa() + ? null + : new SignatureAlgorithmParameters(digestInput.hashAlgorithm().getAlgorithmName()); var signatureProtocolParameters = new RawDigestSignatureProtocolParameters(digestInput.getDigestInBase64(), signatureAlgorithm.getAlgorithmName(), - new SignatureAlgorithmParameters(digestInput.hashAlgorithm().getAlgorithmName())); + algorithmParams); return new DeviceLinkSignatureSessionRequest(relyingPartyUUID, relyingPartyName, certificateLevel != null ? certificateLevel.name() : null, diff --git a/src/main/java/ee/sk/smartid/LinkedNotificationSignatureSessionRequestBuilder.java b/src/main/java/ee/sk/smartid/LinkedNotificationSignatureSessionRequestBuilder.java index 67785957..c260e306 100644 --- a/src/main/java/ee/sk/smartid/LinkedNotificationSignatureSessionRequestBuilder.java +++ b/src/main/java/ee/sk/smartid/LinkedNotificationSignatureSessionRequestBuilder.java @@ -4,7 +4,7 @@ * #%L * Smart ID sample Java client * %% - * Copyright (C) 2018 - 2025 SK ID Solutions AS + * Copyright (C) 2018 - 2026 SK ID Solutions AS * %% * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -39,6 +39,10 @@ import ee.sk.smartid.rest.dao.RawDigestSignatureProtocolParameters; import ee.sk.smartid.rest.dao.RequestProperties; import ee.sk.smartid.rest.dao.SignatureAlgorithmParameters; +import ee.sk.smartid.signature.DigestInput; +import ee.sk.smartid.signature.SignableData; +import ee.sk.smartid.signature.SignableHash; +import ee.sk.smartid.signature.SigningSignatureAlgorithm; import ee.sk.smartid.util.SetUtil; import ee.sk.smartid.util.InteractionUtil; import ee.sk.smartid.util.StringUtil; @@ -54,7 +58,7 @@ public class LinkedNotificationSignatureSessionRequestBuilder { private String relyingPartyName; private String documentNumber; private DigestInput digestInput; - private SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.RSASSA_PSS; + private SigningSignatureAlgorithm signatureAlgorithm = SigningSignatureAlgorithm.RSASSA_PSS; private String linkedSessionID; private List interactions; private CertificateLevel certificateLevel; @@ -151,7 +155,7 @@ public LinkedNotificationSignatureSessionRequestBuilder withSignableHash(Signabl * @param signatureAlgorithm The signature algorithm * @return this builder */ - public LinkedNotificationSignatureSessionRequestBuilder withSignatureAlgorithm(SignatureAlgorithm signatureAlgorithm) { + public LinkedNotificationSignatureSessionRequestBuilder withSignatureAlgorithm(SigningSignatureAlgorithm signatureAlgorithm) { this.signatureAlgorithm = signatureAlgorithm; return this; } @@ -257,9 +261,12 @@ private void validateRequestParameters() { } private LinkedSignatureSessionRequest createSessionRequest() { + SignatureAlgorithmParameters algorithmParams = signatureAlgorithm.isLegacyRsa() + ? null + : new SignatureAlgorithmParameters(digestInput.hashAlgorithm().getAlgorithmName()); var rawDigestParams = new RawDigestSignatureProtocolParameters(digestInput.getDigestInBase64(), signatureAlgorithm.getAlgorithmName(), - new SignatureAlgorithmParameters(digestInput.hashAlgorithm().getAlgorithmName())); + algorithmParams); return new LinkedSignatureSessionRequest(relyingPartyUUID, relyingPartyName, certificateLevel != null ? certificateLevel.name() : null, diff --git a/src/main/java/ee/sk/smartid/NotificationAuthenticationResponseValidator.java b/src/main/java/ee/sk/smartid/NotificationAuthenticationResponseValidator.java index 0efb494e..ef58b7e2 100644 --- a/src/main/java/ee/sk/smartid/NotificationAuthenticationResponseValidator.java +++ b/src/main/java/ee/sk/smartid/NotificationAuthenticationResponseValidator.java @@ -4,7 +4,7 @@ * #%L * Smart ID sample Java client * %% - * Copyright (C) 2018 - 2025 SK ID Solutions AS + * Copyright (C) 2018 - 2026 SK ID Solutions AS * %% * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -36,6 +36,9 @@ import ee.sk.smartid.exception.useraccount.CertificateLevelMismatchException; import ee.sk.smartid.rest.dao.NotificationAuthenticationSessionRequest; import ee.sk.smartid.rest.dao.SessionStatus; +import ee.sk.smartid.signature.RsaSsaPssSignatureFactory; +import ee.sk.smartid.signature.SignatureValueValidator; +import ee.sk.smartid.signature.SignatureValueValidatorImpl; import ee.sk.smartid.util.InteractionUtil; import ee.sk.smartid.util.StringUtil; @@ -152,7 +155,7 @@ private void validateSignature(AuthenticationResponse authenticationResponse, signatureValueValidator.validate(authenticationResponse.getSignatureValue(), payload, authenticationResponse.getCertificate(), - authenticationResponse.getRsaSsaPssSignatureParameters()); + new RsaSsaPssSignatureFactory(authenticationResponse.getRsaSsaPssSignatureParameters())); } private byte[] constructPayload(AuthenticationResponse authenticationResponse, diff --git a/src/main/java/ee/sk/smartid/NotificationAuthenticationSessionRequestBuilder.java b/src/main/java/ee/sk/smartid/NotificationAuthenticationSessionRequestBuilder.java index b694ffb5..64b49972 100644 --- a/src/main/java/ee/sk/smartid/NotificationAuthenticationSessionRequestBuilder.java +++ b/src/main/java/ee/sk/smartid/NotificationAuthenticationSessionRequestBuilder.java @@ -4,7 +4,7 @@ * #%L * Smart ID sample Java client * %% - * Copyright (C) 2018 - 2024 SK ID Solutions AS + * Copyright (C) 2018 - 2026 SK ID Solutions AS * %% * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -42,6 +42,7 @@ import ee.sk.smartid.rest.dao.RequestProperties; import ee.sk.smartid.rest.dao.SemanticsIdentifier; import ee.sk.smartid.rest.dao.SignatureAlgorithmParameters; +import ee.sk.smartid.signature.AuthenticationSignatureAlgorithm; import ee.sk.smartid.util.InteractionUtil; import ee.sk.smartid.util.SetUtil; import ee.sk.smartid.util.StringUtil; @@ -57,7 +58,7 @@ public class NotificationAuthenticationSessionRequestBuilder { private String relyingPartyName; private AuthenticationCertificateLevel certificateLevel; private String rpChallenge; - private SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.RSASSA_PSS; + private AuthenticationSignatureAlgorithm signatureAlgorithm = AuthenticationSignatureAlgorithm.RSASSA_PSS; private HashAlgorithm hashAlgorithm = HashAlgorithm.SHA3_512; private List interactions; private Boolean shareMdClientIpAddress; @@ -130,7 +131,7 @@ public NotificationAuthenticationSessionRequestBuilder withRpChallenge(String rp * @param signatureAlgorithm the signature algorithm * @return this builder */ - public NotificationAuthenticationSessionRequestBuilder withSignatureAlgorithm(SignatureAlgorithm signatureAlgorithm) { + public NotificationAuthenticationSessionRequestBuilder withSignatureAlgorithm(AuthenticationSignatureAlgorithm signatureAlgorithm) { this.signatureAlgorithm = signatureAlgorithm; return this; } diff --git a/src/main/java/ee/sk/smartid/NotificationSignatureSessionRequestBuilder.java b/src/main/java/ee/sk/smartid/NotificationSignatureSessionRequestBuilder.java index ead4a53b..35d85831 100644 --- a/src/main/java/ee/sk/smartid/NotificationSignatureSessionRequestBuilder.java +++ b/src/main/java/ee/sk/smartid/NotificationSignatureSessionRequestBuilder.java @@ -4,7 +4,7 @@ * #%L * Smart ID sample Java client * %% - * Copyright (C) 2018 - 2025 SK ID Solutions AS + * Copyright (C) 2018 - 2026 SK ID Solutions AS * %% * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -30,6 +30,10 @@ import java.util.Set; import java.util.regex.Pattern; +import ee.sk.smartid.signature.DigestInput; +import ee.sk.smartid.signature.SignableData; +import ee.sk.smartid.signature.SignableHash; +import ee.sk.smartid.signature.SigningSignatureAlgorithm; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -69,7 +73,7 @@ public class NotificationSignatureSessionRequestBuilder { private Set capabilities; private List interactions; private Boolean shareMdClientIpAddress; - private SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.RSASSA_PSS; + private SigningSignatureAlgorithm signatureAlgorithm = SigningSignatureAlgorithm.RSASSA_PSS; private DigestInput digestInput; /** @@ -186,7 +190,7 @@ public NotificationSignatureSessionRequestBuilder withShareMdClientIpAddress(boo * @param signatureAlgorithm the signature algorithm * @return this builder */ - public NotificationSignatureSessionRequestBuilder withSignatureAlgorithm(SignatureAlgorithm signatureAlgorithm) { + public NotificationSignatureSessionRequestBuilder withSignatureAlgorithm(SigningSignatureAlgorithm signatureAlgorithm) { this.signatureAlgorithm = signatureAlgorithm; return this; } @@ -265,9 +269,12 @@ private NotificationSignatureSessionResponse initSignatureSession(NotificationSi } private NotificationSignatureSessionRequest createSignatureSessionRequest() { + SignatureAlgorithmParameters algorithmParams = signatureAlgorithm.isLegacyRsa() + ? null + : new SignatureAlgorithmParameters(digestInput.hashAlgorithm().getAlgorithmName()); var signatureProtocolParameters = new RawDigestSignatureProtocolParameters(digestInput.getDigestInBase64(), signatureAlgorithm.getAlgorithmName(), - new SignatureAlgorithmParameters(digestInput.hashAlgorithm().getAlgorithmName())); + algorithmParams); return new NotificationSignatureSessionRequest(relyingPartyUUID, relyingPartyName, diff --git a/src/main/java/ee/sk/smartid/SignatureResponse.java b/src/main/java/ee/sk/smartid/SignatureResponse.java index 31e933d5..f125e974 100644 --- a/src/main/java/ee/sk/smartid/SignatureResponse.java +++ b/src/main/java/ee/sk/smartid/SignatureResponse.java @@ -4,7 +4,7 @@ * #%L * Smart ID sample Java client * %% - * Copyright (C) 2018 - 2025 SK ID Solutions AS + * Copyright (C) 2018 - 2026 SK ID Solutions AS * %% * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -31,6 +31,8 @@ import java.util.Base64; import ee.sk.smartid.exception.UnprocessableSmartIdResponseException; +import ee.sk.smartid.signature.RsaSsaPssParameters; +import ee.sk.smartid.signature.SigningSignatureAlgorithm; /** * Response of a completed and validated signature session. @@ -40,7 +42,7 @@ public class SignatureResponse implements Serializable { private String endResult; private String signatureValueInBase64; private String algorithmName; - private SignatureAlgorithm signatureAlgorithm; + private SigningSignatureAlgorithm signatureAlgorithm; private FlowType flowType; private X509Certificate certificate; private CertificateLevel requestedCertificateLevel; @@ -124,7 +126,7 @@ public void setAlgorithmName(String algorithmName) { * * @return the signature algorithm */ - public SignatureAlgorithm getSignatureAlgorithm() { + public SigningSignatureAlgorithm getSignatureAlgorithm() { return signatureAlgorithm; } @@ -133,7 +135,7 @@ public SignatureAlgorithm getSignatureAlgorithm() { * * @param signatureAlgorithm the signature algorithm */ - public void setSignatureAlgorithm(SignatureAlgorithm signatureAlgorithm) { + public void setSignatureAlgorithm(SigningSignatureAlgorithm signatureAlgorithm) { this.signatureAlgorithm = signatureAlgorithm; } diff --git a/src/main/java/ee/sk/smartid/SignatureResponseValidator.java b/src/main/java/ee/sk/smartid/SignatureResponseValidator.java index 63b85de4..c6bc5658 100644 --- a/src/main/java/ee/sk/smartid/SignatureResponseValidator.java +++ b/src/main/java/ee/sk/smartid/SignatureResponseValidator.java @@ -4,7 +4,7 @@ * #%L * Smart ID sample Java client * %% - * Copyright (C) 2018 - 2025 SK ID Solutions AS + * Copyright (C) 2018 - 2026 SK ID Solutions AS * %% * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -49,6 +49,10 @@ import ee.sk.smartid.rest.dao.SessionSignature; import ee.sk.smartid.rest.dao.SessionSignatureAlgorithmParameters; import ee.sk.smartid.rest.dao.SessionStatus; +import ee.sk.smartid.signature.MaskGenAlgorithm; +import ee.sk.smartid.signature.RsaSsaPssParameters; +import ee.sk.smartid.signature.SigningSignatureAlgorithm; +import ee.sk.smartid.signature.TrailerField; import ee.sk.smartid.util.StringUtil; /** @@ -110,15 +114,18 @@ public SignatureResponse validate(SessionStatus sessionStatus, signatureResponse.setEndResult(sessionResult.getEndResult()); signatureResponse.setSignatureValueInBase64(sessionSignature.getValue()); signatureResponse.setAlgorithmName(sessionSignature.getSignatureAlgorithm()); + signatureResponse.setSignatureAlgorithm(SigningSignatureAlgorithm.fromString(sessionSignature.getSignatureAlgorithm())); - SessionSignatureAlgorithmParameters signatureAlgorithmParameters = sessionSignature.getSignatureAlgorithmParameters(); - var rsaSsaPssParams = new RsaSsaPssParameters(); - rsaSsaPssParams.setDigestHashAlgorithm(HashAlgorithm.fromString(signatureAlgorithmParameters.getHashAlgorithm()).orElse(null)); - rsaSsaPssParams.setMaskGenAlgorithm(MaskGenAlgorithm.ID_MGF1); - rsaSsaPssParams.setMaskHashAlgorithm(HashAlgorithm.fromString(signatureAlgorithmParameters.getMaskGenAlgorithm().getParameters().getHashAlgorithm()).orElse(null)); - rsaSsaPssParams.setSaltLength(signatureAlgorithmParameters.getSaltLength()); - rsaSsaPssParams.setTrailerField(TrailerField.BC); - signatureResponse.setRsaSsaPssParameters(rsaSsaPssParams); + if (!SigningSignatureAlgorithm.isLegacyRsa(sessionSignature.getSignatureAlgorithm())) { + SessionSignatureAlgorithmParameters signatureAlgorithmParameters = sessionSignature.getSignatureAlgorithmParameters(); + var rsaSsaPssParams = new RsaSsaPssParameters(); + rsaSsaPssParams.setDigestHashAlgorithm(HashAlgorithm.fromString(signatureAlgorithmParameters.getHashAlgorithm()).orElse(null)); + rsaSsaPssParams.setMaskGenAlgorithm(MaskGenAlgorithm.ID_MGF1); + rsaSsaPssParams.setMaskHashAlgorithm(HashAlgorithm.fromString(signatureAlgorithmParameters.getMaskGenAlgorithm().getParameters().getHashAlgorithm()).orElse(null)); + rsaSsaPssParams.setSaltLength(signatureAlgorithmParameters.getSaltLength()); + rsaSsaPssParams.setTrailerField(TrailerField.BC); + signatureResponse.setRsaSsaPssParameters(rsaSsaPssParams); + } signatureResponse.setFlowType(FlowType.fromString(sessionSignature.getFlowType())); signatureResponse.setCertificate(CertificateParser.parseX509Certificate(certificate.getValue())); @@ -234,7 +241,9 @@ private static void validateRawDigestSignature(SessionStatus sessionStatus) { validateSignatureValue(signature.getValue()); validateSignatureAlgorithmName(signature.getSignatureAlgorithm()); validateFlowType(signature.getFlowType()); - validateSignatureAlgorithmParameters(signature.getSignatureAlgorithmParameters()); + if (!SigningSignatureAlgorithm.isLegacyRsa(signature.getSignatureAlgorithm())) { + validateSignatureAlgorithmParameters(signature.getSignatureAlgorithmParameters()); + } } private static void validateSignatureValue(String value) { @@ -251,8 +260,8 @@ private static void validateSignatureAlgorithmName(String signatureAlgorithm) { throw new UnprocessableSmartIdResponseException("Signature session status field 'signature.signatureAlgorithm' is missing"); } - if (!SignatureAlgorithm.isSupported(signatureAlgorithm)) { - List possibleValues = Arrays.stream(SignatureAlgorithm.values()).map(SignatureAlgorithm::getAlgorithmName).toList(); + if (!SigningSignatureAlgorithm.isSupported(signatureAlgorithm)) { + List possibleValues = Arrays.stream(SigningSignatureAlgorithm.values()).map(SigningSignatureAlgorithm::getAlgorithmName).toList(); logger.error("Signature session status field 'signature.signatureAlgorithm' has unsupported value: {}. Possible values: {}", signatureAlgorithm, possibleValues); throw new UnprocessableSmartIdResponseException("Signature session status field 'signature.signatureAlgorithm' has unsupported value"); } diff --git a/src/main/java/ee/sk/smartid/rest/dao/RawDigestSignatureProtocolParameters.java b/src/main/java/ee/sk/smartid/rest/dao/RawDigestSignatureProtocolParameters.java index 16e9fe9b..57fb78d1 100644 --- a/src/main/java/ee/sk/smartid/rest/dao/RawDigestSignatureProtocolParameters.java +++ b/src/main/java/ee/sk/smartid/rest/dao/RawDigestSignatureProtocolParameters.java @@ -4,7 +4,7 @@ * #%L * Smart ID sample Java client * %% - * Copyright (C) 2018 - 2025 SK ID Solutions AS + * Copyright (C) 2018 - 2026 SK ID Solutions AS * %% * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -28,14 +28,16 @@ import java.io.Serializable; +import com.fasterxml.jackson.annotation.JsonInclude; + /** * Parameters for protocol RAW_DIGEST_SIGNATURE * * @param digest Required. The digest to be signed, Base64 encoded. - * @param signatureAlgorithm Required. The signature algorithm. Supported value is RSASSA-PSS. - * @param signatureAlgorithmParameters Required. The parameters for signature algorithm. + * @param signatureAlgorithm Required. The signature algorithm (e.g. rsassa-pss, sha512WithRSAEncryption). + * @param signatureAlgorithmParameters Required for RSASSA-PSS. Omitted for RSASSA-PKCS#1 v1.5 algorithms. */ public record RawDigestSignatureProtocolParameters(String digest, String signatureAlgorithm, - SignatureAlgorithmParameters signatureAlgorithmParameters) implements Serializable { + @JsonInclude(JsonInclude.Include.NON_NULL) SignatureAlgorithmParameters signatureAlgorithmParameters) implements Serializable { } \ No newline at end of file diff --git a/src/main/java/ee/sk/smartid/SignatureAlgorithm.java b/src/main/java/ee/sk/smartid/signature/AuthenticationSignatureAlgorithm.java similarity index 75% rename from src/main/java/ee/sk/smartid/SignatureAlgorithm.java rename to src/main/java/ee/sk/smartid/signature/AuthenticationSignatureAlgorithm.java index f6a33872..390e7700 100644 --- a/src/main/java/ee/sk/smartid/SignatureAlgorithm.java +++ b/src/main/java/ee/sk/smartid/signature/AuthenticationSignatureAlgorithm.java @@ -1,10 +1,10 @@ -package ee.sk.smartid; +package ee.sk.smartid.signature; /*- * #%L * Smart ID sample Java client * %% - * Copyright (C) 2018 - 2025 SK ID Solutions AS + * Copyright (C) 2018 - 2026 SK ID Solutions AS * %% * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -29,19 +29,20 @@ import java.util.Arrays; /** - * Signature algorithms supported by Smart-ID API. + * Signature algorithms supported for authentication sessions. + *

+ * Currently only RSASSA-PSS is allowed by the Smart-ID API. */ -public enum SignatureAlgorithm { +public enum AuthenticationSignatureAlgorithm { /** * RSASSA-PSS (RSA Probabilistic Signature Scheme) as defined in PKCS #1 v2.1. - * This algorithm provides probabilistic signature generation for enhanced security. */ RSASSA_PSS("rsassa-pss"); private final String algorithmName; - SignatureAlgorithm(String algorithmName) { + AuthenticationSignatureAlgorithm(String algorithmName) { this.algorithmName = algorithmName; } @@ -55,13 +56,13 @@ public String getAlgorithmName() { } /** - * Checks if the provided signature algorithm is supported. + * Checks if the provided signature algorithm is supported for authentication. * * @param signatureAlgorithm the signature algorithm name to check * @return true if the signature algorithm is supported, false otherwise */ public static boolean isSupported(String signatureAlgorithm) { - return Arrays.stream(SignatureAlgorithm.values()) + return Arrays.stream(AuthenticationSignatureAlgorithm.values()) .anyMatch(s -> s.getAlgorithmName().equals(signatureAlgorithm)); } @@ -69,14 +70,15 @@ public static boolean isSupported(String signatureAlgorithm) { * Converts a string representation of a signature algorithm to its corresponding enum value. * * @param signatureAlgorithm the signature algorithm name - * @return the corresponding SignatureAlgorithm enum value + * @return the corresponding AuthenticationSignatureAlgorithm enum value * @throws IllegalArgumentException if the provided signature algorithm is not supported */ - public static SignatureAlgorithm fromString(String signatureAlgorithm) { + public static AuthenticationSignatureAlgorithm fromString(String signatureAlgorithm) { return Arrays - .stream(SignatureAlgorithm.values()) + .stream(AuthenticationSignatureAlgorithm.values()) .filter(s -> s.getAlgorithmName().equals(signatureAlgorithm)) .findFirst() - .orElseThrow(() -> new IllegalArgumentException("Invalid signatureAlgorithm value: " + signatureAlgorithm)); + .orElseThrow(() -> new IllegalArgumentException("Invalid authentication signatureAlgorithm value: " + signatureAlgorithm)); } } + diff --git a/src/main/java/ee/sk/smartid/DigestInput.java b/src/main/java/ee/sk/smartid/signature/DigestInput.java similarity index 94% rename from src/main/java/ee/sk/smartid/DigestInput.java rename to src/main/java/ee/sk/smartid/signature/DigestInput.java index bf74c745..411c199b 100644 --- a/src/main/java/ee/sk/smartid/DigestInput.java +++ b/src/main/java/ee/sk/smartid/signature/DigestInput.java @@ -1,10 +1,10 @@ -package ee.sk.smartid; +package ee.sk.smartid.signature; /*- * #%L * Smart ID sample Java client * %% - * Copyright (C) 2018 - 2025 SK ID Solutions AS + * Copyright (C) 2018 - 2026 SK ID Solutions AS * %% * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -26,6 +26,8 @@ * #L% */ +import ee.sk.smartid.HashAlgorithm; + /** * Represents data to be signed. *

diff --git a/src/main/java/ee/sk/smartid/MaskGenAlgorithm.java b/src/main/java/ee/sk/smartid/signature/MaskGenAlgorithm.java similarity index 97% rename from src/main/java/ee/sk/smartid/MaskGenAlgorithm.java rename to src/main/java/ee/sk/smartid/signature/MaskGenAlgorithm.java index dad59496..bf24b457 100644 --- a/src/main/java/ee/sk/smartid/MaskGenAlgorithm.java +++ b/src/main/java/ee/sk/smartid/signature/MaskGenAlgorithm.java @@ -1,10 +1,10 @@ -package ee.sk.smartid; +package ee.sk.smartid.signature; /*- * #%L * Smart ID sample Java client * %% - * Copyright (C) 2018 - 2025 SK ID Solutions AS + * Copyright (C) 2018 - 2026 SK ID Solutions AS * %% * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/src/main/java/ee/sk/smartid/signature/RsaSsaPkcs1SignatureFactory.java b/src/main/java/ee/sk/smartid/signature/RsaSsaPkcs1SignatureFactory.java new file mode 100644 index 00000000..e7926c3e --- /dev/null +++ b/src/main/java/ee/sk/smartid/signature/RsaSsaPkcs1SignatureFactory.java @@ -0,0 +1,68 @@ +package ee.sk.smartid.signature; + +/*- + * #%L + * Smart ID sample Java client + * %% + * Copyright (C) 2018 - 2026 SK ID Solutions AS + * %% + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * #L% + */ + +import java.security.NoSuchAlgorithmException; +import java.security.Signature; + +import ee.sk.smartid.exception.UnprocessableSmartIdResponseException; +import ee.sk.smartid.exception.permanent.SmartIdClientException; + +/** + * {@link SignatureFactory} implementation for legacy RSASSA-PKCS#1 v1.5 algorithms (signing only). + */ +public final class RsaSsaPkcs1SignatureFactory implements SignatureFactory { + + private final SigningSignatureAlgorithm signingSignatureAlgorithm; + + /** + * Creates a factory for legacy RSA (RSASSA-PKCS#1 v1.5) signature verification. + * + * @param signingSignatureAlgorithm the signature algorithm; must not be null and must be a legacy RSA algorithm + * @throws SmartIdClientException if {@code signingSignatureAlgorithm} is null + * @throws UnprocessableSmartIdResponseException if the algorithm is not a legacy RSA algorithm + */ + public RsaSsaPkcs1SignatureFactory(SigningSignatureAlgorithm signingSignatureAlgorithm) { + if (signingSignatureAlgorithm == null) { + throw new SmartIdClientException("Parameter 'signatureAlgorithmName' is not provided"); + } + if (!signingSignatureAlgorithm.isLegacyRsa()) { + throw new UnprocessableSmartIdResponseException("Signature algorithm '" + signingSignatureAlgorithm + + "' is not a legacy RSA (RSASSA-PKCS#1 v1.5) algorithm; use validate(..., RsaSsaPssParameters) for RSASSA-PSS"); + } + this.signingSignatureAlgorithm = signingSignatureAlgorithm; + } + + @Override + public Signature getSignature() { + try { + return Signature.getInstance(signingSignatureAlgorithm.getJceAlgorithmName()); + } catch (NoSuchAlgorithmException ex) { + throw new UnprocessableSmartIdResponseException("Signature value validation failed", ex); + } + } +} diff --git a/src/main/java/ee/sk/smartid/RsaSsaPssParameters.java b/src/main/java/ee/sk/smartid/signature/RsaSsaPssParameters.java similarity index 91% rename from src/main/java/ee/sk/smartid/RsaSsaPssParameters.java rename to src/main/java/ee/sk/smartid/signature/RsaSsaPssParameters.java index 81bc797f..4da4a6bf 100644 --- a/src/main/java/ee/sk/smartid/RsaSsaPssParameters.java +++ b/src/main/java/ee/sk/smartid/signature/RsaSsaPssParameters.java @@ -1,10 +1,10 @@ -package ee.sk.smartid; +package ee.sk.smartid.signature; /*- * #%L * Smart ID sample Java client * %% - * Copyright (C) 2018 - 2025 SK ID Solutions AS + * Copyright (C) 2018 - 2026 SK ID Solutions AS * %% * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -26,12 +26,14 @@ * #L% */ +import ee.sk.smartid.HashAlgorithm; + /** * Encapsulates multiple parameters of RSASSA-PSS */ public class RsaSsaPssParameters { - private final SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.RSASSA_PSS; + private final String signatureAlgorithmName = "rsassa-pss"; private HashAlgorithm digestHashAlgorithm; private MaskGenAlgorithm maskGenAlgorithm; @@ -85,12 +87,12 @@ public void setTrailerField(TrailerField trailerField) { } /** - * Gets the signature algorithm + * Gets the signature algorithm name * - * @return the signature algorithm; see {@link SignatureAlgorithm} + * @return the signature algorithm name */ - public SignatureAlgorithm getSignatureAlgorithm() { - return signatureAlgorithm; + public String getSignatureAlgorithmName() { + return signatureAlgorithmName; } /** diff --git a/src/main/java/ee/sk/smartid/SignatureValueValidatorImpl.java b/src/main/java/ee/sk/smartid/signature/RsaSsaPssSignatureFactory.java similarity index 53% rename from src/main/java/ee/sk/smartid/SignatureValueValidatorImpl.java rename to src/main/java/ee/sk/smartid/signature/RsaSsaPssSignatureFactory.java index 678f5f9d..6dcc8511 100644 --- a/src/main/java/ee/sk/smartid/SignatureValueValidatorImpl.java +++ b/src/main/java/ee/sk/smartid/signature/RsaSsaPssSignatureFactory.java @@ -1,10 +1,10 @@ -package ee.sk.smartid; +package ee.sk.smartid.signature; /*- * #%L * Smart ID sample Java client * %% - * Copyright (C) 2018 - 2025 SK ID Solutions AS + * Copyright (C) 2018 - 2026 SK ID Solutions AS * %% * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -26,11 +26,9 @@ * #L% */ -import java.security.GeneralSecurityException; import java.security.InvalidAlgorithmParameterException; import java.security.NoSuchAlgorithmException; import java.security.Signature; -import java.security.cert.X509Certificate; import java.security.spec.MGF1ParameterSpec; import java.security.spec.PSSParameterSpec; @@ -41,64 +39,43 @@ import ee.sk.smartid.exception.permanent.SmartIdClientException; /** - * Implementation of {@link SignatureValueValidator} that uses RSASSA-PSS signature algorithm - * to validate the signature value in the authentication and signature session status response. + * {@link SignatureFactory} implementation for RSASSA-PSS (authentication and signing). */ -public final class SignatureValueValidatorImpl implements SignatureValueValidator { +public final class RsaSsaPssSignatureFactory implements SignatureFactory { - private final Logger logger = LoggerFactory.getLogger(SignatureValueValidatorImpl.class); + private static final Logger logger = LoggerFactory.getLogger(RsaSsaPssSignatureFactory.class); - @Override - public void validate(byte[] signatureValue, - byte[] payload, - X509Certificate certificate, - RsaSsaPssParameters rsaSsaPssParameters) { - validateInputs(signatureValue, payload, certificate, rsaSsaPssParameters); - try { - Signature result = getSignature(rsaSsaPssParameters); - result.initVerify(certificate.getPublicKey()); - result.update(payload); - if (!result.verify(signatureValue)) { - throw new UnprocessableSmartIdResponseException("Provided signature value does not match the calculated signature value"); - } - } catch (GeneralSecurityException ex) { - throw new UnprocessableSmartIdResponseException("Signature value validation failed", ex); + private final RsaSsaPssParameters rsaSsaPssParameters; + + /** + * Creates a factory for RSASSA-PSS signature verification. + * + * @param rsaSsaPssParameters signature parameters; must not be null + * @throws SmartIdClientException if {@code rsaSsaPssParameters} is null + */ + public RsaSsaPssSignatureFactory(RsaSsaPssParameters rsaSsaPssParameters) { + if (rsaSsaPssParameters == null) { + throw new SmartIdClientException("Parameter 'rsaSsaPssParameters' is not provided"); } + this.rsaSsaPssParameters = rsaSsaPssParameters; } - private Signature getSignature(RsaSsaPssParameters rsaSsaPssParameters) { + @Override + public Signature getSignature() { try { var params = new PSSParameterSpec(rsaSsaPssParameters.getDigestHashAlgorithm().getAlgorithmName(), rsaSsaPssParameters.getMaskGenAlgorithm().getMgfName(), new MGF1ParameterSpec(rsaSsaPssParameters.getMaskHashAlgorithm().getAlgorithmName()), rsaSsaPssParameters.getSaltLength(), rsaSsaPssParameters.getTrailerField().getPssSpecValue()); - var signature = Signature.getInstance(rsaSsaPssParameters.getSignatureAlgorithm().getAlgorithmName()); + var signature = Signature.getInstance(rsaSsaPssParameters.getSignatureAlgorithmName()); signature.setParameter(params); return signature; } catch (NoSuchAlgorithmException ex) { - logger.error("Invalid signature algorithm was provided: {}", rsaSsaPssParameters.getSignatureAlgorithm()); + logger.error("Invalid signature algorithm name was provided: {}", rsaSsaPssParameters.getSignatureAlgorithmName()); throw new UnprocessableSmartIdResponseException("Invalid signature algorithm was provided", ex); } catch (InvalidAlgorithmParameterException ex) { throw new UnprocessableSmartIdResponseException("Invalid signature algorithm parameters were provided", ex); } } - - private static void validateInputs(byte[] signatureValue, - byte[] payload, - X509Certificate certificate, - RsaSsaPssParameters rsaSsaPssParameters) { - if (signatureValue == null) { - throw new SmartIdClientException("Parameter 'signatureValue' is not provided"); - } - if (payload == null) { - throw new SmartIdClientException("Parameter 'payload' is not provided"); - } - if (certificate == null) { - throw new SmartIdClientException("Parameter 'certificate' is not provided"); - } - if (rsaSsaPssParameters == null) { - throw new SmartIdClientException("Parameter 'rsaSsaPssParameters' is not provided"); - } - } } diff --git a/src/main/java/ee/sk/smartid/SignableData.java b/src/main/java/ee/sk/smartid/signature/SignableData.java similarity index 95% rename from src/main/java/ee/sk/smartid/SignableData.java rename to src/main/java/ee/sk/smartid/signature/SignableData.java index 29a7495a..cec6ad5a 100644 --- a/src/main/java/ee/sk/smartid/SignableData.java +++ b/src/main/java/ee/sk/smartid/signature/SignableData.java @@ -1,10 +1,10 @@ -package ee.sk.smartid; +package ee.sk.smartid.signature; /*- * #%L * Smart ID sample Java client * %% - * Copyright (C) 2018 - 2025 SK ID Solutions AS + * Copyright (C) 2018 - 2026 SK ID Solutions AS * %% * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -29,6 +29,8 @@ import java.io.Serializable; import java.util.Base64; +import ee.sk.smartid.DigestCalculator; +import ee.sk.smartid.HashAlgorithm; import ee.sk.smartid.exception.permanent.SmartIdRequestSetupException; /** diff --git a/src/main/java/ee/sk/smartid/SignableHash.java b/src/main/java/ee/sk/smartid/signature/SignableHash.java similarity index 96% rename from src/main/java/ee/sk/smartid/SignableHash.java rename to src/main/java/ee/sk/smartid/signature/SignableHash.java index 9d18117a..9cbbd02d 100644 --- a/src/main/java/ee/sk/smartid/SignableHash.java +++ b/src/main/java/ee/sk/smartid/signature/SignableHash.java @@ -1,10 +1,10 @@ -package ee.sk.smartid; +package ee.sk.smartid.signature; /*- * #%L * Smart ID sample Java client * %% - * Copyright (C) 2018 - 2025 SK ID Solutions AS + * Copyright (C) 2018 - 2026 SK ID Solutions AS * %% * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -29,6 +29,7 @@ import java.io.Serializable; import java.util.Base64; +import ee.sk.smartid.HashAlgorithm; import ee.sk.smartid.exception.permanent.SmartIdRequestSetupException; /** diff --git a/src/main/java/ee/sk/smartid/signature/SignatureFactory.java b/src/main/java/ee/sk/smartid/signature/SignatureFactory.java new file mode 100644 index 00000000..ec849563 --- /dev/null +++ b/src/main/java/ee/sk/smartid/signature/SignatureFactory.java @@ -0,0 +1,50 @@ +package ee.sk.smartid.signature; + +/*- + * #%L + * Smart ID sample Java client + * %% + * Copyright (C) 2018 - 2026 SK ID Solutions AS + * %% + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * #L% + */ + +import java.security.Signature; + +/** + * Factory for creating preconfigured {@link Signature} instances used when + * validating Smart-ID signatures. + *

+ * Implementations encapsulate the details of the concrete signature algorithm + * (for example RSASSA-PSS or legacy RSASSA-PKCS#1 v1.5) so that callers can + * obtain a correctly initialised {@link Signature} object without dealing with + * low-level JCA configuration. + */ +public interface SignatureFactory { + + /** + * Creates a new {@link Signature} instance configured for the underlying + * Smart-ID signature algorithm. + * + * @return a configured {@link Signature} implementation ready for public key + * initialization + */ + Signature getSignature(); +} diff --git a/src/main/java/ee/sk/smartid/SignatureValueValidator.java b/src/main/java/ee/sk/smartid/signature/SignatureValueValidator.java similarity index 61% rename from src/main/java/ee/sk/smartid/SignatureValueValidator.java rename to src/main/java/ee/sk/smartid/signature/SignatureValueValidator.java index 5df475af..1f0099f8 100644 --- a/src/main/java/ee/sk/smartid/SignatureValueValidator.java +++ b/src/main/java/ee/sk/smartid/signature/SignatureValueValidator.java @@ -1,10 +1,10 @@ -package ee.sk.smartid; +package ee.sk.smartid.signature; /*- * #%L * Smart ID sample Java client * %% - * Copyright (C) 2018 - 2025 SK ID Solutions AS + * Copyright (C) 2018 - 2026 SK ID Solutions AS * %% * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -32,20 +32,25 @@ /** * Interface for signature value validator. + *

+ * Use a concrete {@link SignatureFactory} implementation to specify the signature algorithm and parameters: + * {@link RsaSsaPssSignatureFactory} for RSASSA-PSS (authentication and signing), or + * {@link RsaSsaPkcs1SignatureFactory} for legacy RSASSA-PKCS#1 v1.5 algorithms (signing only). + * The factory encapsulates algorithm choice and parameter validation. */ public interface SignatureValueValidator { /** - * Validates the signature value against the calculated signature value. + * Validates the signature value using the provided signature factory. * - * @param signatureValue the signature value to validate - * @param payload the original data that was signed - * @param certificate X509 certificate used for signature validation - * @param rsaSsaPssParameters signature parameters used for creating signature value - * @throws UnprocessableSmartIdResponseException when there are any issue with validating the signature value + * @param signatureValue the signature value to validate + * @param payload the original data that was signed (typically the hash that was sent to Smart-ID) + * @param certificate X.509 certificate used for signature validation + * @param signatureFactory factory that creates the {@link java.security.Signature} instance for verification + * @throws UnprocessableSmartIdResponseException when there is any issue with validating the signature value */ void validate(byte[] signatureValue, byte[] payload, X509Certificate certificate, - RsaSsaPssParameters rsaSsaPssParameters); + SignatureFactory signatureFactory); } diff --git a/src/main/java/ee/sk/smartid/signature/SignatureValueValidatorImpl.java b/src/main/java/ee/sk/smartid/signature/SignatureValueValidatorImpl.java new file mode 100644 index 00000000..eb34af7c --- /dev/null +++ b/src/main/java/ee/sk/smartid/signature/SignatureValueValidatorImpl.java @@ -0,0 +1,70 @@ +package ee.sk.smartid.signature; + +/*- + * #%L + * Smart ID sample Java client + * %% + * Copyright (C) 2018 - 2026 SK ID Solutions AS + * %% + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * #L% + */ + +import java.security.GeneralSecurityException; +import java.security.Signature; +import java.security.cert.X509Certificate; + +import ee.sk.smartid.exception.UnprocessableSmartIdResponseException; +import ee.sk.smartid.exception.permanent.SmartIdClientException; + +/** + * Implementation of {@link SignatureValueValidator} that validates signature values + * for both RSASSA-PSS (authentication and signing) and RSASSA-PKCS#1 v1.5 (signing only). + */ +public final class SignatureValueValidatorImpl implements SignatureValueValidator { + + @Override + public void validate(byte[] signatureValue, + byte[] payload, + X509Certificate certificate, + SignatureFactory signatureFactory) { + if (signatureValue == null) { + throw new SmartIdClientException("Parameter 'signatureValue' is not provided"); + } + if (payload == null) { + throw new SmartIdClientException("Parameter 'payload' is not provided"); + } + if (certificate == null) { + throw new SmartIdClientException("Parameter 'certificate' is not provided"); + } + if (signatureFactory == null) { + throw new SmartIdClientException("Parameter 'signatureFactory' is not provided"); + } + try { + Signature signature = signatureFactory.getSignature(); + signature.initVerify(certificate.getPublicKey()); + signature.update(payload); + if (!signature.verify(signatureValue)) { + throw new UnprocessableSmartIdResponseException("Provided signature value does not match the calculated signature value"); + } + } catch (GeneralSecurityException ex) { + throw new UnprocessableSmartIdResponseException("Signature value validation failed", ex); + } + } +} diff --git a/src/main/java/ee/sk/smartid/signature/SigningSignatureAlgorithm.java b/src/main/java/ee/sk/smartid/signature/SigningSignatureAlgorithm.java new file mode 100644 index 00000000..fd684fe2 --- /dev/null +++ b/src/main/java/ee/sk/smartid/signature/SigningSignatureAlgorithm.java @@ -0,0 +1,152 @@ +package ee.sk.smartid.signature; + +/*- + * #%L + * Smart ID sample Java client + * %% + * Copyright (C) 2018 - 2026 SK ID Solutions AS + * %% + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * #L% + */ + +import ee.sk.smartid.HashAlgorithm; + +import java.util.Arrays; + +/** + * Signature algorithms supported for signing sessions. + *

+ * Includes RSASSA-PSS and RSASSA-PKCS#1 v1.5 algorithms. The latter are marked as legacy RSA. + */ +public enum SigningSignatureAlgorithm { + + /** + * RSASSA-PSS (RSA Probabilistic Signature Scheme) as defined in PKCS #1 v2.1. + */ + RSASSA_PSS("rsassa-pss", false, "rsassa-pss", null), + + /** + * RSASSA-PKCS#1 v1.5 with SHA-256. Signing only; no signatureAlgorithmParameters. + */ + SHA256_WITH_RSA_ENCRYPTION("sha256WithRSAEncryption", true, "SHA256withRSA", HashAlgorithm.SHA_256), + + /** + * RSASSA-PKCS#1 v1.5 with SHA-384. Signing only; no signatureAlgorithmParameters. + */ + SHA384_WITH_RSA_ENCRYPTION("sha384WithRSAEncryption", true, "SHA384withRSA", HashAlgorithm.SHA_384), + + /** + * RSASSA-PKCS#1 v1.5 with SHA-512. Signing only; no signatureAlgorithmParameters. + */ + SHA512_WITH_RSA_ENCRYPTION("sha512WithRSAEncryption", true, "SHA512withRSA", HashAlgorithm.SHA_512); + + private final String algorithmName; + private final boolean legacyRsa; + private final String jceAlgorithmName; + private final HashAlgorithm hashAlgorithmForLegacy; + + SigningSignatureAlgorithm(String algorithmName, + boolean legacyRsa, + String jceAlgorithmName, + HashAlgorithm hashAlgorithmForLegacy) { + this.algorithmName = algorithmName; + this.legacyRsa = legacyRsa; + this.jceAlgorithmName = jceAlgorithmName; + this.hashAlgorithmForLegacy = hashAlgorithmForLegacy; + } + + /** + * Provides the signature algorithm name as used in the Smart-ID API. + * + * @return the signature algorithm name + */ + public String getAlgorithmName() { + return algorithmName; + } + + /** + * Returns whether this algorithm is RSASSA-PKCS#1 v1.5 (legacy RSA). + * Such algorithms do not use or require {@code signatureAlgorithmParameters} in requests or responses. + * + * @return true for SHA256/SHA384/SHA512 with RSA encryption + */ + public boolean isLegacyRsa() { + return legacyRsa; + } + + /** + * Returns the JCE standard algorithm name for {@link java.security.Signature#getInstance(String)}. + * For legacy RSA algorithms this is the name used to verify the signature (e.g. SHA256withRSA). + * + * @return the JCE algorithm name + */ + public String getJceAlgorithmName() { + return jceAlgorithmName; + } + + /** + * Returns the hash algorithm for legacy RSA algorithms. Used when creating {@link SignableData} + * so that {@link SignableData#calculateHash()} uses the correct hash for the signature algorithm. + * + * @return the hash algorithm for legacy algorithms, or null for RSASSA_PSS + */ + public HashAlgorithm getHashAlgorithmForLegacy() { + return hashAlgorithmForLegacy; + } + + /** + * Checks if the provided signature algorithm is supported for signing. + * + * @param signatureAlgorithm the signature algorithm name to check + * @return true if the signature algorithm is supported, false otherwise + */ + public static boolean isSupported(String signatureAlgorithm) { + return Arrays.stream(SigningSignatureAlgorithm.values()) + .anyMatch(s -> s.getAlgorithmName().equals(signatureAlgorithm)); + } + + /** + * Returns whether the given algorithm name is a legacy RSA (RSASSA-PKCS#1 v1.5) algorithm. + * + * @param signatureAlgorithm the signature algorithm name + * @return true if legacy RSA, false otherwise + */ + public static boolean isLegacyRsa(String signatureAlgorithm) { + return Arrays.stream(SigningSignatureAlgorithm.values()) + .filter(s -> s.getAlgorithmName().equals(signatureAlgorithm)) + .anyMatch(SigningSignatureAlgorithm::isLegacyRsa); + } + + /** + * Converts a string representation of a signature algorithm to its corresponding enum value. + * + * @param signatureAlgorithm the signature algorithm name + * @return the corresponding SigningSignatureAlgorithm enum value + * @throws IllegalArgumentException if the provided signature algorithm is not supported + */ + public static SigningSignatureAlgorithm fromString(String signatureAlgorithm) { + return Arrays + .stream(SigningSignatureAlgorithm.values()) + .filter(s -> s.getAlgorithmName().equals(signatureAlgorithm)) + .findFirst() + .orElseThrow(() -> new IllegalArgumentException("Invalid signatureAlgorithm value: " + signatureAlgorithm)); + } +} + diff --git a/src/main/java/ee/sk/smartid/TrailerField.java b/src/main/java/ee/sk/smartid/signature/TrailerField.java similarity index 97% rename from src/main/java/ee/sk/smartid/TrailerField.java rename to src/main/java/ee/sk/smartid/signature/TrailerField.java index b8482bb8..55b6f22b 100644 --- a/src/main/java/ee/sk/smartid/TrailerField.java +++ b/src/main/java/ee/sk/smartid/signature/TrailerField.java @@ -1,10 +1,10 @@ -package ee.sk.smartid; +package ee.sk.smartid.signature; /*- * #%L * Smart ID sample Java client * %% - * Copyright (C) 2018 - 2025 SK ID Solutions AS + * Copyright (C) 2018 - 2026 SK ID Solutions AS * %% * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/src/test/java/ee/sk/smartid/DeviceLinkAuthenticationResponseValidatorTest.java b/src/test/java/ee/sk/smartid/DeviceLinkAuthenticationResponseValidatorTest.java index 1f1ba798..2766c53b 100644 --- a/src/test/java/ee/sk/smartid/DeviceLinkAuthenticationResponseValidatorTest.java +++ b/src/test/java/ee/sk/smartid/DeviceLinkAuthenticationResponseValidatorTest.java @@ -4,7 +4,7 @@ * #%L * Smart ID sample Java client * %% - * Copyright (C) 2018 - 2025 SK ID Solutions AS + * Copyright (C) 2018 - 2026 SK ID Solutions AS * %% * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -57,6 +57,9 @@ import ee.sk.smartid.rest.dao.SessionSignatureAlgorithmParameters; import ee.sk.smartid.rest.dao.SessionStatus; import ee.sk.smartid.rest.dao.SignatureAlgorithmParameters; +import ee.sk.smartid.signature.AuthenticationSignatureAlgorithm; +import ee.sk.smartid.signature.MaskGenAlgorithm; +import ee.sk.smartid.signature.TrailerField; import ee.sk.smartid.util.InteractionUtil; class DeviceLinkAuthenticationResponseValidatorTest { @@ -231,7 +234,7 @@ private static SessionStatus toSessionStatus(String certificateValue, signature.setUserChallenge(userChallengeVerifier); signature.setValue(toBase64("signatureValue")); signature.setFlowType(flowType.getDescription()); - signature.setSignatureAlgorithm(SignatureAlgorithm.RSASSA_PSS.getAlgorithmName()); + signature.setSignatureAlgorithm(AuthenticationSignatureAlgorithm.RSASSA_PSS.getAlgorithmName()); signature.setSignatureAlgorithmParameters(sessionSignatureAlgorithmParameters); var cert = new SessionCertificate(); @@ -254,7 +257,7 @@ private static DeviceLinkAuthenticationSessionRequest toAuthenticationSessionReq "DEMO", certificateLevel, SignatureProtocol.ACSP_V2, - new AcspV2SignatureProtocolParameters("rpChallenge", SignatureAlgorithm.RSASSA_PSS.getAlgorithmName(), new SignatureAlgorithmParameters(HashAlgorithm.SHA3_512.getAlgorithmName())), + new AcspV2SignatureProtocolParameters("rpChallenge", AuthenticationSignatureAlgorithm.RSASSA_PSS.getAlgorithmName(), new SignatureAlgorithmParameters(HashAlgorithm.SHA3_512.getAlgorithmName())), InteractionUtil.encodeToBase64(List.of(new Interaction(DeviceLinkInteractionType.DISPLAY_TEXT_AND_PIN.getCode(), "Log in?", null))), null, null, diff --git a/src/test/java/ee/sk/smartid/DeviceLinkAuthenticationSessionRequestBuilderTest.java b/src/test/java/ee/sk/smartid/DeviceLinkAuthenticationSessionRequestBuilderTest.java index e5e007a2..e3354398 100644 --- a/src/test/java/ee/sk/smartid/DeviceLinkAuthenticationSessionRequestBuilderTest.java +++ b/src/test/java/ee/sk/smartid/DeviceLinkAuthenticationSessionRequestBuilderTest.java @@ -4,7 +4,7 @@ * #%L * Smart ID sample Java client * %% - * Copyright (C) 2018 - 2025 SK ID Solutions AS + * Copyright (C) 2018 - 2026 SK ID Solutions AS * %% * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -70,6 +70,7 @@ import ee.sk.smartid.rest.dao.DeviceLinkSessionResponse; import ee.sk.smartid.rest.dao.Interaction; import ee.sk.smartid.rest.dao.SemanticsIdentifier; +import ee.sk.smartid.signature.AuthenticationSignatureAlgorithm; class DeviceLinkAuthenticationSessionRequestBuilderTest { @@ -145,8 +146,8 @@ void initAuthenticationSession_certificateLevel_ok(AuthenticationCertificateLeve } @ParameterizedTest - @EnumSource - void initAuthenticationSession_signatureAlgorithm_ok(SignatureAlgorithm signatureAlgorithm) { + @EnumSource(value = AuthenticationSignatureAlgorithm.class, names = {"RSASSA_PSS"}) + void initAuthenticationSession_signatureAlgorithm_ok(AuthenticationSignatureAlgorithm signatureAlgorithm) { when(connector.initAnonymousDeviceLinkAuthentication(any(DeviceLinkAuthenticationSessionRequest.class))) .thenReturn(toDeviceLinkAuthenticationResponse()); diff --git a/src/test/java/ee/sk/smartid/DeviceLinkSignatureSessionRequestBuilderTest.java b/src/test/java/ee/sk/smartid/DeviceLinkSignatureSessionRequestBuilderTest.java index 4d1cb1e2..dae450df 100644 --- a/src/test/java/ee/sk/smartid/DeviceLinkSignatureSessionRequestBuilderTest.java +++ b/src/test/java/ee/sk/smartid/DeviceLinkSignatureSessionRequestBuilderTest.java @@ -4,7 +4,7 @@ * #%L * Smart ID sample Java client * %% - * Copyright (C) 2018 - 2025 SK ID Solutions AS + * Copyright (C) 2018 - 2026 SK ID Solutions AS * %% * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -28,6 +28,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.ArgumentMatchers.any; @@ -65,6 +66,9 @@ import ee.sk.smartid.rest.dao.DeviceLinkSessionResponse; import ee.sk.smartid.rest.dao.SemanticsIdentifier; import ee.sk.smartid.rest.dao.DeviceLinkSignatureSessionRequest; +import ee.sk.smartid.signature.SignableData; +import ee.sk.smartid.signature.SignableHash; +import ee.sk.smartid.signature.SigningSignatureAlgorithm; class DeviceLinkSignatureSessionRequestBuilderTest { @@ -162,7 +166,7 @@ void initSignatureSession_withRequestProperties() { @Test void initSignatureSession_withSignatureAlgorithm_setsCorrectAlgorithm() { when(connector.initDeviceLinkSignature(any(DeviceLinkSignatureSessionRequest.class), any(SemanticsIdentifier.class))).thenReturn(mockSignatureSessionResponse()); - var deviceLinkSessionRequestBuilder = toDeviceLinkSignatureSessionRequestBuilder(b -> b.withSignatureAlgorithm(SignatureAlgorithm.RSASSA_PSS)); + var deviceLinkSessionRequestBuilder = toDeviceLinkSignatureSessionRequestBuilder(b -> b.withSignatureAlgorithm(SigningSignatureAlgorithm.RSASSA_PSS)); DeviceLinkSessionResponse signatureSessionResponse = deviceLinkSessionRequestBuilder.initSignatureSession(); @@ -172,7 +176,22 @@ void initSignatureSession_withSignatureAlgorithm_setsCorrectAlgorithm() { verify(connector).initDeviceLinkSignature(requestCaptor.capture(), any(SemanticsIdentifier.class)); DeviceLinkSignatureSessionRequest capturedRequest = requestCaptor.getValue(); - assertEquals(SignatureAlgorithm.RSASSA_PSS.getAlgorithmName(), capturedRequest.signatureProtocolParameters().signatureAlgorithm()); + assertEquals(SigningSignatureAlgorithm.RSASSA_PSS.getAlgorithmName(), capturedRequest.signatureProtocolParameters().signatureAlgorithm()); + } + + @ParameterizedTest + @EnumSource(value = SigningSignatureAlgorithm.class, names = {"SHA256_WITH_RSA_ENCRYPTION", "SHA384_WITH_RSA_ENCRYPTION", "SHA512_WITH_RSA_ENCRYPTION"}) + void initSignatureSession_withLegacyRsaAlgorithm_omitsSignatureAlgorithmParameters(SigningSignatureAlgorithm signatureAlgorithm) { + when(connector.initDeviceLinkSignature(any(DeviceLinkSignatureSessionRequest.class), any(SemanticsIdentifier.class))).thenReturn(mockSignatureSessionResponse()); + var deviceLinkSessionRequestBuilder = toDeviceLinkSignatureSessionRequestBuilder(b -> b.withSignatureAlgorithm(signatureAlgorithm)); + + deviceLinkSessionRequestBuilder.initSignatureSession(); + + ArgumentCaptor requestCaptor = ArgumentCaptor.forClass(DeviceLinkSignatureSessionRequest.class); + verify(connector).initDeviceLinkSignature(requestCaptor.capture(), any(SemanticsIdentifier.class)); + DeviceLinkSignatureSessionRequest capturedRequest = requestCaptor.getValue(); + assertEquals(signatureAlgorithm.getAlgorithmName(), capturedRequest.signatureProtocolParameters().signatureAlgorithm()); + assertNull(capturedRequest.signatureProtocolParameters().signatureAlgorithmParameters()); } @ParameterizedTest @@ -258,7 +277,28 @@ void initSignatureSession_withDefaultAlgorithmWhenNoSignatureAlgorithmSet() { verify(connector).initDeviceLinkSignature(requestCaptor.capture(), any(SemanticsIdentifier.class)); DeviceLinkSignatureSessionRequest capturedRequest = requestCaptor.getValue(); - assertEquals(SignatureAlgorithm.RSASSA_PSS.getAlgorithmName(), capturedRequest.signatureProtocolParameters().signatureAlgorithm()); + assertEquals(SigningSignatureAlgorithm.RSASSA_PSS.getAlgorithmName(), capturedRequest.signatureProtocolParameters().signatureAlgorithm()); + } + + @ParameterizedTest + @EnumSource(SigningSignatureAlgorithm.class) + void initSignatureSession_withSignatureAlgorithm_ok(SigningSignatureAlgorithm signatureAlgorithm) { + when(connector.initDeviceLinkSignature(any(DeviceLinkSignatureSessionRequest.class), any(SemanticsIdentifier.class))).thenReturn(mockSignatureSessionResponse()); + + toDeviceLinkSignatureSessionRequestBuilder(b -> b.withSignatureAlgorithm(signatureAlgorithm)) + .initSignatureSession(); + + ArgumentCaptor requestCaptor = ArgumentCaptor.forClass(DeviceLinkSignatureSessionRequest.class); + verify(connector).initDeviceLinkSignature(requestCaptor.capture(), any(SemanticsIdentifier.class)); + DeviceLinkSignatureSessionRequest capturedRequest = requestCaptor.getValue(); + + assertEquals(signatureAlgorithm.getAlgorithmName(), capturedRequest.signatureProtocolParameters().signatureAlgorithm()); + if (signatureAlgorithm.isLegacyRsa()) { + assertNull(capturedRequest.signatureProtocolParameters().signatureAlgorithmParameters()); + } else { + assertNotNull(capturedRequest.signatureProtocolParameters().signatureAlgorithmParameters()); + assertEquals(HashAlgorithm.SHA_512.getAlgorithmName(), capturedRequest.signatureProtocolParameters().signatureAlgorithmParameters().hashAlgorithm()); + } } @Test diff --git a/src/test/java/ee/sk/smartid/LinkedNotificationSignatureSessionRequestBuilderTest.java b/src/test/java/ee/sk/smartid/LinkedNotificationSignatureSessionRequestBuilderTest.java index 2e817836..8dbefe83 100644 --- a/src/test/java/ee/sk/smartid/LinkedNotificationSignatureSessionRequestBuilderTest.java +++ b/src/test/java/ee/sk/smartid/LinkedNotificationSignatureSessionRequestBuilderTest.java @@ -4,7 +4,7 @@ * #%L * Smart ID sample Java client * %% - * Copyright (C) 2018 - 2025 SK ID Solutions AS + * Copyright (C) 2018 - 2026 SK ID Solutions AS * %% * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -27,6 +27,8 @@ */ import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; @@ -54,6 +56,9 @@ import ee.sk.smartid.rest.SmartIdConnector; import ee.sk.smartid.rest.dao.LinkedSignatureSessionRequest; import ee.sk.smartid.rest.dao.LinkedSignatureSessionResponse; +import ee.sk.smartid.signature.SignableData; +import ee.sk.smartid.signature.SignableHash; +import ee.sk.smartid.signature.SigningSignatureAlgorithm; class LinkedNotificationSignatureSessionRequestBuilderTest { @@ -126,6 +131,30 @@ void initSignatureSession_withCapabilities_ok(String[] capabilities, Set assertEquals(expectedRequestCapabilities, request.capabilities()); } + + @ParameterizedTest + @EnumSource(SigningSignatureAlgorithm.class) + void initSignatureSession_withSignatureAlgorithm_ok(SigningSignatureAlgorithm signatureAlgorithm) { + LinkedNotificationSignatureSessionRequestBuilder builder = toLinkedNotificationSignatureSessionRequestBuilder(b -> b.withSignatureAlgorithm(signatureAlgorithm)); + when(connector.initLinkedNotificationSignature(any(LinkedSignatureSessionRequest.class), eq(DOCUMENT_NUMBER))) + .thenReturn(new LinkedSignatureSessionResponse("20000000-0000-0000-0000-000000000000")); + + LinkedSignatureSessionResponse response = builder.initSignatureSession(); + + assertEquals("20000000-0000-0000-0000-000000000000", response.sessionID()); + + ArgumentCaptor requestCaptor = ArgumentCaptor.forClass(LinkedSignatureSessionRequest.class); + verify(connector).initLinkedNotificationSignature(requestCaptor.capture(), eq(DOCUMENT_NUMBER)); + LinkedSignatureSessionRequest request = requestCaptor.getValue(); + assertEquals(signatureAlgorithm.getAlgorithmName(), request.signatureProtocolParameters().signatureAlgorithm()); + if (signatureAlgorithm.isLegacyRsa()) { + assertNull(request.signatureProtocolParameters().signatureAlgorithmParameters()); + } else { + assertNotNull(request.signatureProtocolParameters().signatureAlgorithmParameters()); + assertEquals(HashAlgorithm.SHA_512.getAlgorithmName(), request.signatureProtocolParameters().signatureAlgorithmParameters().hashAlgorithm()); + } + } + @Nested class ValidateRequestParameters { diff --git a/src/test/java/ee/sk/smartid/NotificationAuthenticationResponseValidatorTest.java b/src/test/java/ee/sk/smartid/NotificationAuthenticationResponseValidatorTest.java index 8ae7b76f..b57f0dca 100644 --- a/src/test/java/ee/sk/smartid/NotificationAuthenticationResponseValidatorTest.java +++ b/src/test/java/ee/sk/smartid/NotificationAuthenticationResponseValidatorTest.java @@ -4,7 +4,7 @@ * #%L * Smart ID sample Java client * %% - * Copyright (C) 2018 - 2025 SK ID Solutions AS + * Copyright (C) 2018 - 2026 SK ID Solutions AS * %% * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -51,6 +51,9 @@ import ee.sk.smartid.rest.dao.SessionSignatureAlgorithmParameters; import ee.sk.smartid.rest.dao.SessionStatus; import ee.sk.smartid.rest.dao.SignatureAlgorithmParameters; +import ee.sk.smartid.signature.AuthenticationSignatureAlgorithm; +import ee.sk.smartid.signature.MaskGenAlgorithm; +import ee.sk.smartid.signature.TrailerField; class NotificationAuthenticationResponseValidatorTest { @@ -149,7 +152,7 @@ private static NotificationAuthenticationSessionRequest toAuthenticationSessionR certificateLevel, SignatureProtocol.ACSP_V2.name(), new AcspV2SignatureProtocolParameters("3mhDkd0ulDR/WVZx678FcrNw4pUhrZxcQsmejf8jQ1HtSp3GAxCH/Fi9EEiuULp44G/KNKONPXZELqCSZw4AoA==", - SignatureAlgorithm.RSASSA_PSS.getAlgorithmName(), + AuthenticationSignatureAlgorithm.RSASSA_PSS.getAlgorithmName(), new SignatureAlgorithmParameters(HashAlgorithm.SHA3_512.getAlgorithmName())), "W3sidHlwZSI6ImRpc3BsYXlUZXh0QW5kUElOIiwiZGlzcGxheVRleHQ2MCI6IkxvZyBpbiB3aXRoIFNtYXJ0LUlEIGRlbW8/In1d", null, @@ -180,7 +183,7 @@ private static SessionStatus toSessionsStatus(String certificateValue, String ce signature.setUserChallenge("RvrVNS1GJYCsuEnEqPCdHHn5vl65F3XiBjmxB4zSosw"); signature.setValue(signatureValue); signature.setFlowType(FlowType.NOTIFICATION.getDescription()); - signature.setSignatureAlgorithm(SignatureAlgorithm.RSASSA_PSS.getAlgorithmName()); + signature.setSignatureAlgorithm(AuthenticationSignatureAlgorithm.RSASSA_PSS.getAlgorithmName()); signature.setSignatureAlgorithmParameters(sessionSignatureAlgorithmParameters); var cert = new SessionCertificate(); diff --git a/src/test/java/ee/sk/smartid/NotificationAuthenticationSessionRequestBuilderTest.java b/src/test/java/ee/sk/smartid/NotificationAuthenticationSessionRequestBuilderTest.java index dab26384..23262885 100644 --- a/src/test/java/ee/sk/smartid/NotificationAuthenticationSessionRequestBuilderTest.java +++ b/src/test/java/ee/sk/smartid/NotificationAuthenticationSessionRequestBuilderTest.java @@ -4,7 +4,7 @@ * #%L * Smart ID sample Java client * %% - * Copyright (C) 2018 - 2025 SK ID Solutions AS + * Copyright (C) 2018 - 2026 SK ID Solutions AS * %% * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -62,6 +62,7 @@ import ee.sk.smartid.rest.dao.NotificationAuthenticationSessionRequest; import ee.sk.smartid.rest.dao.NotificationAuthenticationSessionResponse; import ee.sk.smartid.rest.dao.SemanticsIdentifier; +import ee.sk.smartid.signature.AuthenticationSignatureAlgorithm; class NotificationAuthenticationSessionRequestBuilderTest { @@ -133,8 +134,8 @@ void initAuthenticationSession_certificateLevel_ok(AuthenticationCertificateLeve } @ParameterizedTest - @EnumSource - void initAuthenticationSession_signatureAlgorithm_ok(SignatureAlgorithm signatureAlgorithm) { + @EnumSource(value = AuthenticationSignatureAlgorithm.class, names = {"RSASSA_PSS"}) + void initAuthenticationSession_signatureAlgorithm_ok(AuthenticationSignatureAlgorithm signatureAlgorithm) { when(connector.initNotificationAuthentication(any(NotificationAuthenticationSessionRequest.class), any(String.class))).thenReturn(toNotificationAuthenticationResponse()); NotificationAuthenticationSessionRequestBuilder builder = toNotificationAuthenticationSessionRequestBuilder(b -> b.withSignatureAlgorithm(signatureAlgorithm)); diff --git a/src/test/java/ee/sk/smartid/NotificationSignatureSessionRequestBuilderTest.java b/src/test/java/ee/sk/smartid/NotificationSignatureSessionRequestBuilderTest.java index 55bbf052..6983240d 100644 --- a/src/test/java/ee/sk/smartid/NotificationSignatureSessionRequestBuilderTest.java +++ b/src/test/java/ee/sk/smartid/NotificationSignatureSessionRequestBuilderTest.java @@ -4,7 +4,7 @@ * #%L * Smart ID sample Java client * %% - * Copyright (C) 2018 - 2025 SK ID Solutions AS + * Copyright (C) 2018 - 2026 SK ID Solutions AS * %% * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -28,6 +28,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; @@ -65,6 +66,9 @@ import ee.sk.smartid.rest.dao.NotificationSignatureSessionResponse; import ee.sk.smartid.rest.dao.SemanticsIdentifier; import ee.sk.smartid.rest.dao.VerificationCode; +import ee.sk.smartid.signature.SignableData; +import ee.sk.smartid.signature.SignableHash; +import ee.sk.smartid.signature.SigningSignatureAlgorithm; class NotificationSignatureSessionRequestBuilderTest { @@ -174,7 +178,7 @@ void initSignatureSession_useDefaultHashAlgorithmForSignableHash_ok() { NotificationSignatureSessionRequest capturedRequest = requestCaptor.getValue(); assertEquals(HashAlgorithm.SHA_512.getAlgorithmName(), capturedRequest.signatureProtocolParameters().signatureAlgorithmParameters().hashAlgorithm()); - assertEquals(SignatureAlgorithm.RSASSA_PSS.getAlgorithmName(), capturedRequest.signatureProtocolParameters().signatureAlgorithm()); + assertEquals(SigningSignatureAlgorithm.RSASSA_PSS.getAlgorithmName(), capturedRequest.signatureProtocolParameters().signatureAlgorithm()); } @ParameterizedTest @@ -192,7 +196,7 @@ void initSignatureSession_overrideDefaultHashAlgorithmForSignableHash_ok(HashAlg verify(connector).initNotificationSignature(requestCaptor.capture(), any(SemanticsIdentifier.class)); NotificationSignatureSessionRequest capturedRequest = requestCaptor.getValue(); - assertEquals(SignatureAlgorithm.RSASSA_PSS.getAlgorithmName(), capturedRequest.signatureProtocolParameters().signatureAlgorithm()); + assertEquals(SigningSignatureAlgorithm.RSASSA_PSS.getAlgorithmName(), capturedRequest.signatureProtocolParameters().signatureAlgorithm()); assertEquals(Base64.getEncoder().encodeToString("Test hash".getBytes()), capturedRequest.signatureProtocolParameters().digest()); assertEquals(hashAlgorithm.getAlgorithmName(), capturedRequest.signatureProtocolParameters().signatureAlgorithmParameters().hashAlgorithm()); assertEquals(SignatureProtocol.RAW_DIGEST_SIGNATURE.name(), capturedRequest.signatureProtocol()); @@ -211,7 +215,7 @@ void initSignatureSession_useDefaultHashAlgorithmForSignableData_ok() { NotificationSignatureSessionRequest capturedRequest = requestCaptor.getValue(); assertEquals(HashAlgorithm.SHA_512.getAlgorithmName(), capturedRequest.signatureProtocolParameters().signatureAlgorithmParameters().hashAlgorithm()); - assertEquals(SignatureAlgorithm.RSASSA_PSS.getAlgorithmName(), capturedRequest.signatureProtocolParameters().signatureAlgorithm()); + assertEquals(SigningSignatureAlgorithm.RSASSA_PSS.getAlgorithmName(), capturedRequest.signatureProtocolParameters().signatureAlgorithm()); } @ParameterizedTest @@ -227,8 +231,8 @@ void initSignatureSession_overrideDefaultHashAlgorithmForSignableData_ok(HashAlg verify(connector).initNotificationSignature(requestCaptor.capture(), any(SemanticsIdentifier.class)); NotificationSignatureSessionRequest capturedRequest = requestCaptor.getValue(); - assertEquals(SignatureAlgorithm.RSASSA_PSS.getAlgorithmName(), capturedRequest.signatureProtocolParameters().signatureAlgorithm()); - assertEquals(Base64.getEncoder().encodeToString(signableData.calculateHash()), capturedRequest.signatureProtocolParameters().digest()); + assertEquals(SigningSignatureAlgorithm.RSASSA_PSS.getAlgorithmName(), capturedRequest.signatureProtocolParameters().signatureAlgorithm()); + assertEquals(signableData.getDigestInBase64(), capturedRequest.signatureProtocolParameters().digest()); assertEquals(hashAlgorithm.getAlgorithmName(), capturedRequest.signatureProtocolParameters().signatureAlgorithmParameters().hashAlgorithm()); assertEquals(SignatureProtocol.RAW_DIGEST_SIGNATURE.name(), capturedRequest.signatureProtocol()); } @@ -249,6 +253,27 @@ void initSignatureSession_withCapabilities_ok(String[] capabilities, Set assertEquals(SignatureProtocol.RAW_DIGEST_SIGNATURE.name(), capturedRequest.signatureProtocol()); } + @ParameterizedTest + @EnumSource(SigningSignatureAlgorithm.class) + void initSignatureSession_withSignatureAlgorithm_ok(SigningSignatureAlgorithm signatureAlgorithm) { + when(connector.initNotificationSignature(any(NotificationSignatureSessionRequest.class), any(SemanticsIdentifier.class))).thenReturn(mockNotificationSignatureSessionResponse()); + + toNotificationSignatureSessionRequestBuilder(b -> b.withSignatureAlgorithm(signatureAlgorithm)) + .initSignatureSession(); + + ArgumentCaptor requestCaptor = ArgumentCaptor.forClass(NotificationSignatureSessionRequest.class); + verify(connector).initNotificationSignature(requestCaptor.capture(), any(SemanticsIdentifier.class)); + NotificationSignatureSessionRequest capturedRequest = requestCaptor.getValue(); + + assertEquals(signatureAlgorithm.getAlgorithmName(), capturedRequest.signatureProtocolParameters().signatureAlgorithm()); + if (signatureAlgorithm.isLegacyRsa()) { + assertNull(capturedRequest.signatureProtocolParameters().signatureAlgorithmParameters()); + } else { + assertNotNull(capturedRequest.signatureProtocolParameters().signatureAlgorithmParameters()); + assertEquals(HashAlgorithm.SHA_512.getAlgorithmName(), capturedRequest.signatureProtocolParameters().signatureAlgorithmParameters().hashAlgorithm()); + } + } + @Nested class ErrorCases { diff --git a/src/test/java/ee/sk/smartid/SignatureResponseValidatorTest.java b/src/test/java/ee/sk/smartid/SignatureResponseValidatorTest.java index 5f7d52d2..3adc92e3 100644 --- a/src/test/java/ee/sk/smartid/SignatureResponseValidatorTest.java +++ b/src/test/java/ee/sk/smartid/SignatureResponseValidatorTest.java @@ -4,7 +4,7 @@ * #%L * Smart ID sample Java client * %% - * Copyright (C) 2018 - 2025 SK ID Solutions AS + * Copyright (C) 2018 - 2026 SK ID Solutions AS * %% * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -27,6 +27,7 @@ */ import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -50,6 +51,7 @@ import ee.sk.smartid.rest.dao.SessionSignature; import ee.sk.smartid.rest.dao.SessionSignatureAlgorithmParameters; import ee.sk.smartid.rest.dao.SessionStatus; +import ee.sk.smartid.signature.SigningSignatureAlgorithm; class SignatureResponseValidatorTest { @@ -109,6 +111,21 @@ void validate_nqSigning_ok() { assertEquals("OK", response.getEndResult()); } + @ParameterizedTest + @EnumSource(value = SigningSignatureAlgorithm.class, names = {"SHA256_WITH_RSA_ENCRYPTION", "SHA384_WITH_RSA_ENCRYPTION", "SHA512_WITH_RSA_ENCRYPTION"}) + void validate_legacyRsaSignature_ok_withoutSignatureAlgorithmParameters(SigningSignatureAlgorithm signatureAlgorithm) { + SessionStatus sessionStatus = toQualifiedSignatureSessionStatus("RAW_DIGEST_SIGNATURE", signatureAlgorithm.getAlgorithmName()); + sessionStatus.setSignatureProtocol("RAW_DIGEST_SIGNATURE"); + sessionStatus.getSignature().setSignatureAlgorithmParameters(null); + + SignatureResponse response = signatureResponseValidator.validate(sessionStatus, CertificateLevel.QUALIFIED); + + assertEquals("OK", response.getEndResult()); + assertEquals(signatureAlgorithm.getAlgorithmName(), response.getAlgorithmName()); + assertEquals(signatureAlgorithm, response.getSignatureAlgorithm()); + assertNull(response.getRsaSsaPssParameters()); + } + @Test void validate_stateParameterMissing() { SessionStatus sessionStatus = toQualifiedSignatureSessionStatus("RAW_DIGEST_SIGNATURE", "rsassa-pss"); @@ -542,7 +559,7 @@ private static SessionStatus toNqignatureSessionStatus() { sessionCertificate.setValue(CertificateUtil.getEncodedCertificateData(NQ_SIGNING_CERTIFICATE)); var params = toSessionSignatureAlgorithmParams(); - var sessionSignature = toSessionSignature(NQ_SIGNATURE_VALUE, SignatureAlgorithm.RSASSA_PSS.getAlgorithmName(), params, FlowType.QR); + var sessionSignature = toSessionSignature(NQ_SIGNATURE_VALUE, SigningSignatureAlgorithm.RSASSA_PSS.getAlgorithmName(), params, FlowType.QR); var sessionStatus = new SessionStatus(); sessionStatus.setState("COMPLETE"); diff --git a/src/test/java/ee/sk/smartid/SignatureValueValidatorImplTest.java b/src/test/java/ee/sk/smartid/SignatureValueValidatorImplTest.java deleted file mode 100644 index 4ab8d7ac..00000000 --- a/src/test/java/ee/sk/smartid/SignatureValueValidatorImplTest.java +++ /dev/null @@ -1,121 +0,0 @@ -package ee.sk.smartid; - -/*- - * #%L - * Smart ID sample Java client - * %% - * Copyright (C) 2018 - 2025 SK ID Solutions AS - * %% - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * #L% - */ - -import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; - -import java.nio.charset.StandardCharsets; -import java.security.cert.CertificateException; -import java.security.cert.X509Certificate; -import java.util.Base64; -import java.util.stream.Stream; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtensionContext; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.Arguments; -import org.junit.jupiter.params.provider.ArgumentsProvider; -import org.junit.jupiter.params.provider.ArgumentsSource; - -import ee.sk.smartid.exception.UnprocessableSmartIdResponseException; -import ee.sk.smartid.exception.permanent.SmartIdClientException; - -class SignatureValueValidatorImplTest { - - // TODO - 22.08.25: replace these values when test accounts are available - private static final String CERT = "MIIHSjCCBtCgAwIBAgIQBQHi3vqqZg+tDaGzQeB2GzAKBggqhkjOPQQDAzBxMSwwKgYDVQQDDCNURVNUIG9mIFNLIElEIFNvbHV0aW9ucyBFSUQtUSAyMDI0RTEXMBUGA1UEYQwOTlRSRUUtMTA3NDcwMTMxGzAZBgNVBAoMElNLIElEIFNvbHV0aW9ucyBBUzELMAkGA1UEBhMCRUUwHhcNMjUwNzI5MDgxMTAzWhcNMjgwNzI4MDgxMTAyWjBfMQswCQYDVQQGEwJFRTEUMBIGA1UEAwwLTVVTRVIsVVJNQVMxDjAMBgNVBAQMBU1VU0VSMQ4wDAYDVQQqDAVVUk1BUzEaMBgGA1UEBRMRUE5PRUUtMzkwMDMwMTI3OTgwggMiMA0GCSqGSIb3DQEBAQUAA4IDDwAwggMKAoIDAQCf8qQkO51SM/Gdw63LObpk4kwutMSqW345PU4HC+HqQ2H03fTludjY7iBCgEWmXQjoTt6vQgDGPfBlydjZiu2GUSCL/f2DTv76BuWzR/Jw6q4+R86GRhlMJFqfqE2gqCIddVbUx+qYZ37qCddqgIoRYejdrUeWopp2xzya5gt41FM9By95e3pS/1tug7aAlPoT3Tg18+13qqru1SDGxYW+0NVojesYX3Pzz8Exz2dWcFWwMqoU3SMlAULHDC9OPMtuZBSZA2tvyuD+CHHsU13LI46iDRU2j9BVr9EBuO/uvL3U5eIkX0gpy5bdo/TWmXDijTb5udXO9cz+GMaCQTx4yuBTnC31pHw/qrEp00FRZy7yiG0expv7w4c0YiziMFK8GfhnPmNAVEyjTWImmckK9SiIZH0F/oU1VZvtX3aXsmoTzEwpzAy3KPiKxJ0ZSSsVHV+G1nZvx/1mRxKcT+rOzNcx7iY9uAzin9ajPLYTukWsGVOTgQ2GxpYrEhuf8PvQlZ62BVIvfS5swhlwXzMU8oEAsHCpUVDNCLtckkKBgoy9pYZyKbXUtUP1TTEL3ZC9/4h3Udmao6JNWp5niyHDWVpF6r56O/ORZGx1GlT1P+G9rK6bBteptvNWillGPMA5E1fdwSci7/eH8amSED0CAy0rlq+0CdMdnpasqyG5oDmYJncWhhEozQ2fI7SkvNgSiMxDnJXhi8/Zvh4j+29eC7fqG5ZsLxQ1YqaK8XsIsNJ2Lxj0BhrEgU7Zz5lILUdOILEfU1S2Wi4Ow1P23dAP/O+o6u4SDSKSM2+C5s9daq/5zJ2w2s/B8JB8Mat5MPJuzKrvSnYMIUzQjtlsuMBRIRbHmHtCjDXufF11BOCLfPUYU5GDvk6MY51+p/hZrAowQHWZYI+271UxJR9I1dCTNvo1HsiNEnLSgdOikWvmykqiDVWPe6SiRpVKBQ7MkhgvF/CrHGG0S4GBuG6E2OHEMKl73CWuqU8MrPSOQvaXY7f99ZGK9RL1OG8oxRJpJNECAwEAAaOCAo8wggKLMAkGA1UdEwQCMAAwHwYDVR0jBBgwFoAUsCQXGYjjZvjNKFhle00U2JJmT2swcAYIKwYBBQUHAQEEZDBiMDMGCCsGAQUFBzAChidodHRwOi8vYy5zay5lZS9URVNUX0VJRC1RXzIwMjRFLmRlci5jcnQwKwYIKwYBBQUHMAGGH2h0dHA6Ly9haWEuZGVtby5zay5lZS9laWRxMjAyNGUwMAYDVR0RBCkwJ6QlMCMxITAfBgNVBAMMGFBOT0VFLTM5MDAzMDEyNzk4LUZGTDgtUTB5BgNVHSAEcjBwMGMGCSsGAQQBzh8RAjBWMFQGCCsGAQUFBwIBFkhodHRwczovL3d3dy5za2lkc29sdXRpb25zLmV1L3Jlc291cmNlcy9jZXJ0aWZpY2F0aW9uLXByYWN0aWNlLXN0YXRlbWVudC8wCQYHBACL7EABAjAoBgNVHQkEITAfMB0GCCsGAQUFBwkBMREYDzE5OTAwMzAxMTIwMDAwWjCBrgYIKwYBBQUHAQMEgaEwgZ4wFQYIKwYBBQUHCwIwCQYHBACL7EkBATAIBgYEAI5GAQEwCAYGBACORgEEMBMGBgQAjkYBBjAJBgcEAI5GAQYBMFwGBgQAjkYBBTBSMFAWSmh0dHBzOi8vd3d3LnNraWRzb2x1dGlvbnMuZXUvcmVzb3VyY2VzL2NvbmRpdGlvbnMtZm9yLXVzZS1vZi1jZXJ0aWZpY2F0ZXMvEwJlbjA0BgNVHR8ELTArMCmgJ6AlhiNodHRwOi8vYy5zay5lZS90ZXN0X2VpZC1xXzIwMjRlLmNybDAdBgNVHQ4EFgQUq5xLZIjeh1p1kreds8ie7OgpfmwwDgYDVR0PAQH/BAQDAgZAMAoGCCqGSM49BAMDA2gAMGUCMQCdrnNqlxbO/N6FELvGd4MHeNjTIpdDSj+6Htu6W7KRFleQGe8zhK9yA2l/zSerZvwCMGgbT0nvtgyoXBhSsUhY3RWTMiee4nKn7aBKqcmrDuHC9I9o67WpttfSE4srvL+qWQ=="; - private static final byte[] PAYLOAD = Base64.getDecoder().decode(("PGRzOlNpZ25lZEluZm8geG1sbnM6ZHM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvMDkveG1sZHNpZyMiPjxkczpDYW5vbmljYWxpemF0aW9uTWV0aG9kIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS8xMC94bWwtZXhjLWMxNG4jIj48L2RzOkNhbm9uaWNhbGl6YXRpb25NZXRob2Q" + "+PGRzOlNpZ25hdHVyZU1ldGhvZCBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvMDQveG1sZHNpZy1tb3JlI3JzYS1zaGE1MTIiPjwvZHM6U2lnbmF0dXJlTWV0aG9kPjxkczpSZWZlcmVuY2UgSWQ9InItaWQtNzcwMDA4OTNlNWU1YmVjOGMwY2IyOThjNmFkMGY0YTQtMSIgVVJJPSJkdW1teS5wZGYiPjxkczpEaWdlc3RNZXRob2QgQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzA0L3htbGVuYyNzaGEyNTYiPjwvZHM6RGlnZXN0TWV0aG9kPjxkczpEaWdlc3RWYWx1ZT5QZmVkTkt1OHFaTUk1NXk1UkdIQmlUV0NZRTFvTXBwQi9VdnNHSVhtcmJRPTwvZHM6RGlnZXN0VmFsdWU+PC9kczpSZWZlcmVuY2U+PGRzOlJlZmVyZW5jZSBUeXBlPSJodHRwOi8vdXJpLmV0c2kub3JnLzAxOTAzI1NpZ25lZFByb3BlcnRpZXMiIFVSST0iI3hhZGVzLWlkLTc3MDAwODkzZTVlNWJlYzhjMGNiMjk4YzZhZDBmNGE0Ij48ZHM6VHJhbnNmb3Jtcz48ZHM6VHJhbnNmb3JtIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS8xMC94bWwtZXhjLWMxNG4jIj48L2RzOlRyYW5zZm9ybT48L2RzOlRyYW5zZm9ybXM+PGRzOkRpZ2VzdE1ldGhvZCBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvMDQveG1sZW5jI3NoYTI1NiI+PC9kczpEaWdlc3RNZXRob2Q+PGRzOkRpZ2VzdFZhbHVlPjFZd014blRUYmwwZXB5S0g0OEZ0WXFDb3pNbzAxem03NWpwV1pWNDJJNlk9PC9kczpEaWdlc3RWYWx1ZT48L2RzOlJlZmVyZW5jZT48L2RzOlNpZ25lZEluZm8+").getBytes(StandardCharsets.UTF_8)); - private static final byte[] SIGNATURE_VALUE = Base64.getDecoder().decode("UEVKOrz3Mr+qAXyOjGEt3Nnb8andzicBcEdbb4T2qVyGUslHdeJfgkXccPqBnjmEbL7xoU7eHVkO02K+XNseVY5UBHnXDlMBj1TyCnfelfiZFpAppgWmHKBXC11yE1OhtQ5/siaokPGBX1nZM2rzGlHYWxXYZrOGHCrm7gQEbClL342N6bEzeVVVPnxnxDEkzpNMFvY8UIL3C55WPPNKLBzFwSfduNcLaBiHc4ghaIiJebQc1h+Kad5OAYeu35v70k4HVePbDDp1Cb7RXfMRyUx7GNFSTZiKrG16XO8krp+d9T10SGRbZNoTzxvXBjtb8SjXM6Zvx0tiNdVnsWBrEylGzjS2DVU2+MDbek9QxlxqUU5E5H/WrelywgGTEzfZekowjofQjkYXaEAvNTK8x8Me1wIJThZwfrOy6H8MyPAdgAwl7fDwsZG6QhRCeG+9VY4CtmcII6YMZccCFCy9X3SJvXga4OcSrPi+Nwh3tfvJ5pkYvLliVKSCDpslTZk7JQYcQsJ1DVefMW6BfA+V3iX35mG/VHPo789BpzlZL6Ebs/dxNSEnyyWTDECFl2k2/B38w9jO4OuFLLg/U0AvM6ZLNlLWUjsKKg1s4U+SGlLc7r3hxaWCCwx4/NP2h8f3MTquxOCt+7WrjvCNOQ33bKcFGjYyCWpfGAfVgfMenp4oa40A1+Or+Px4Sd5yD3ZTnPSMYh2UzFZOiejGAS/koBYhn60P5PKRpEkC0nq+WQJD58soelH1EKifLoBtYNzhNOAuOfGRI5nEsW94TZ8hbC/mIEBmMnhKr9Lq/+glxbqskwOavWIF5xeWTKeSt2ErvgtNxX3hTlGxdNavwPi+/qtLikrNoirE26t1WFyPMaeH6hm0rIW42h5c0IvsXrQ4258uJzpZPe/RLbjdy62wi1S35PmowFUFImlHDKSIj4plEVXkrApZDV+/bL0VR6PNr7bsIZqgamL9OyLm6vTunP+A7Q+zpaZxuun17SC1QsthiGGBk03uf4CpNVVUpsO3".getBytes(StandardCharsets.UTF_8)); - - private SignatureValueValidator signatureValueValidator; - - @BeforeEach - void setUp() { - signatureValueValidator = new SignatureValueValidatorImpl(); - } - - @Test - void validate() throws CertificateException { - X509Certificate certificate = CertificateUtil.toX509CertificateFromEncodedString(CERT); - RsaSsaPssParameters rsaSsaPssParameters = toRsaSsaPssParameters(); - - assertDoesNotThrow(() -> signatureValueValidator.validate(SIGNATURE_VALUE, PAYLOAD, certificate, rsaSsaPssParameters)); - } - - @ParameterizedTest - @ArgumentsSource(EmptyInputArgumentProvider.class) - void validate_InputParametersNotProvided_throwException(byte[] signatureValue, byte[] payload, X509Certificate certificate, RsaSsaPssParameters rsaSsaPssParameters) { - assertThrows(SmartIdClientException.class, () -> signatureValueValidator.validate(signatureValue, payload, certificate, rsaSsaPssParameters)); - } - - @Test - void validateSignatureValue_IsInvalid_throwException() { - var ex = assertThrows(UnprocessableSmartIdResponseException.class, - () -> signatureValueValidator.validate( - "invalidValue".getBytes(StandardCharsets.UTF_8), - PAYLOAD, - CertificateUtil.toX509CertificateFromEncodedString(CERT), - toRsaSsaPssParameters())); - assertEquals("Signature value validation failed", ex.getMessage()); - } - - @Test - void validateSignatureValue_constructedPayloadDoesNotMatchTheSignature_throwException() { - var ex = assertThrows(UnprocessableSmartIdResponseException.class, - () -> signatureValueValidator.validate( - SIGNATURE_VALUE, - "payloadThatDoesNotMatch".getBytes(StandardCharsets.UTF_8), - CertificateUtil.toX509CertificateFromEncodedString(CERT), - toRsaSsaPssParameters())); - assertEquals("Provided signature value does not match the calculated signature value", ex.getMessage()); - } - - private static RsaSsaPssParameters toRsaSsaPssParameters() { - RsaSsaPssParameters rsaSsaPssParameters = new RsaSsaPssParameters(); - rsaSsaPssParameters.setDigestHashAlgorithm(HashAlgorithm.SHA_512); - rsaSsaPssParameters.setMaskGenAlgorithm(MaskGenAlgorithm.ID_MGF1); - rsaSsaPssParameters.setMaskHashAlgorithm(HashAlgorithm.SHA_512); - rsaSsaPssParameters.setSaltLength(HashAlgorithm.SHA_512.getOctetLength()); - rsaSsaPssParameters.setTrailerField(TrailerField.BC); - return rsaSsaPssParameters; - } - - private static class EmptyInputArgumentProvider implements ArgumentsProvider { - @Override - public Stream provideArguments(ExtensionContext context) throws CertificateException { - return Stream.of( - Arguments.of(null, null, null, null), - Arguments.of(new byte[0], null, null, null), - Arguments.of(new byte[0], new byte[0], null, null), - Arguments.of(new byte[0], new byte[0], CertificateUtil.toX509CertificateFromEncodedString(CERT), null) - ); - } - } -} diff --git a/src/test/java/ee/sk/smartid/SmartIdClientTest.java b/src/test/java/ee/sk/smartid/SmartIdClientTest.java index a44def42..277c671f 100644 --- a/src/test/java/ee/sk/smartid/SmartIdClientTest.java +++ b/src/test/java/ee/sk/smartid/SmartIdClientTest.java @@ -4,7 +4,7 @@ * #%L * Smart ID sample Java client * %% - * Copyright (C) 2018 - 2025 SK ID Solutions AS + * Copyright (C) 2018 - 2026 SK ID Solutions AS * %% * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -58,6 +58,10 @@ import ee.sk.smartid.rest.dao.SemanticsIdentifier; import ee.sk.smartid.rest.dao.SessionStatus; import ee.sk.smartid.rest.dao.VerificationCode; +import ee.sk.smartid.signature.AuthenticationSignatureAlgorithm; +import ee.sk.smartid.signature.SignableData; +import ee.sk.smartid.signature.SignableHash; +import ee.sk.smartid.signature.SigningSignatureAlgorithm; class SmartIdClientTest { @@ -455,7 +459,7 @@ void createLinkedNotificationSignature_onlyRequiredFields_ok() { LinkedSignatureSessionResponse response = smartIdClient.createLinkedNotificationSignature() .withDocumentNumber(DOCUMENT_NUMBER) .withSignableData(new SignableData("Test data".getBytes())) - .withSignatureAlgorithm(SignatureAlgorithm.RSASSA_PSS) + .withSignatureAlgorithm(SigningSignatureAlgorithm.RSASSA_PSS) .withLinkedSessionID("10000000-0000-000-000-000000000000") .withInteractions(List.of(DeviceLinkInteraction.displayTextAndPin("Sign?"))) .initSignatureSession(); @@ -473,7 +477,7 @@ void createLinkedNotificationSignature_allFields_ok() { .withDocumentNumber(DOCUMENT_NUMBER) .withCertificateLevel(CertificateLevel.QUALIFIED) .withSignableData(new SignableData("Test data".getBytes())) - .withSignatureAlgorithm(SignatureAlgorithm.RSASSA_PSS) + .withSignatureAlgorithm(SigningSignatureAlgorithm.RSASSA_PSS) .withLinkedSessionID("10000000-0000-000-000-000000000000") .withNonce("cmFuZG9tTm9uY2U=") .withInteractions(List.of(DeviceLinkInteraction.displayTextAndPin("Sign?"))) @@ -522,7 +526,7 @@ void createDynamicContent_authenticationForSameDeviceFlows(DeviceLinkType device DeviceLinkAuthenticationSessionRequestBuilder builder = smartIdClient.createDeviceLinkAuthentication() .withRpChallenge(Base64.toBase64String("a".repeat(32).getBytes())) - .withSignatureAlgorithm(SignatureAlgorithm.RSASSA_PSS) + .withSignatureAlgorithm(AuthenticationSignatureAlgorithm.RSASSA_PSS) .withInteractions(List.of(DeviceLinkInteraction.displayTextAndPin("Log in?"))) .withHashAlgorithm(HashAlgorithm.SHA3_512) .withInitialCallbackUrl(INITIAL_CALLBACK_URL); @@ -552,7 +556,7 @@ void createDynamicContent_authenticationWithQRCode() { DeviceLinkAuthenticationSessionRequestBuilder builder = smartIdClient.createDeviceLinkAuthentication() .withRpChallenge(Base64.toBase64String("a".repeat(32).getBytes())) - .withSignatureAlgorithm(SignatureAlgorithm.RSASSA_PSS) + .withSignatureAlgorithm(AuthenticationSignatureAlgorithm.RSASSA_PSS) .withInteractions(List.of(DeviceLinkInteraction.displayTextAndPin("Log in?"))) .withHashAlgorithm(HashAlgorithm.SHA3_512); DeviceLinkSessionResponse response = builder.initAuthenticationSession(); @@ -583,7 +587,7 @@ void createDynamicContent_authenticationWithQRCodeImage() { DeviceLinkAuthenticationSessionRequestBuilder builder = smartIdClient.createDeviceLinkAuthentication() .withRpChallenge(Base64.toBase64String("a".repeat(32).getBytes())) - .withSignatureAlgorithm(SignatureAlgorithm.RSASSA_PSS) + .withSignatureAlgorithm(AuthenticationSignatureAlgorithm.RSASSA_PSS) .withInteractions(List.of(DeviceLinkInteraction.displayTextAndPin("Log in?"))) .withHashAlgorithm(HashAlgorithm.SHA3_512); DeviceLinkSessionResponse response = builder.initAuthenticationSession(); diff --git a/src/test/java/ee/sk/smartid/integration/DeviceLinkMockRequest.java b/src/test/java/ee/sk/smartid/integration/DeviceLinkMockRequest.java new file mode 100644 index 00000000..6bc91961 --- /dev/null +++ b/src/test/java/ee/sk/smartid/integration/DeviceLinkMockRequest.java @@ -0,0 +1,45 @@ +package ee.sk.smartid.integration; + +/*- + * #%L + * Smart ID sample Java client + * %% + * Copyright (C) 2018 - 2026 SK ID Solutions AS + * %% + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * #L% + */ + +import com.fasterxml.jackson.annotation.JsonInclude; + +/** + * Object to send to Smart-ID Mock service to simulate user-action in the Device Link flow. + * + * @param documentNumber Required. The document number of the user. + * @param deviceLink Required. The device link URL generated for device link flow + * @param flowType Required. Supported values QR, Web2App and App2App + * @param browserCookie Required for Web2App and App2App flows. The browser cookie value for the session. + * @param initialCallbackUrl Required for Web2App and App2App flows. The initial callback URL to which the user will be redirected. + */ +public record DeviceLinkMockRequest(String documentNumber, + String deviceLink, + String flowType, + @JsonInclude(JsonInclude.Include.NON_EMPTY) String browserCookie, + @JsonInclude(JsonInclude.Include.NON_EMPTY) String initialCallbackUrl) { +} diff --git a/src/test/java/ee/sk/smartid/integration/ReadmeIntegrationTest.java b/src/test/java/ee/sk/smartid/integration/ReadmeIntegrationTest.java index b3619a6f..4043f1e6 100644 --- a/src/test/java/ee/sk/smartid/integration/ReadmeIntegrationTest.java +++ b/src/test/java/ee/sk/smartid/integration/ReadmeIntegrationTest.java @@ -4,7 +4,7 @@ * #%L * Smart ID sample Java client * %% - * Copyright (C) 2018 - 2025 SK ID Solutions AS + * Copyright (C) 2018 - 2026 SK ID Solutions AS * %% * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -33,6 +33,9 @@ import java.io.IOException; import java.io.InputStream; import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; import java.nio.charset.StandardCharsets; import java.security.KeyStore; import java.security.KeyStoreException; @@ -40,16 +43,18 @@ import java.security.cert.CertificateException; import java.time.Duration; import java.time.Instant; -import java.util.Base64; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.regex.Pattern; +import com.fasterxml.jackson.databind.ObjectMapper; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; import ee.sk.smartid.AuthenticationCertificateLevel; import ee.sk.smartid.AuthenticationIdentity; @@ -71,14 +76,11 @@ import ee.sk.smartid.RpChallenge; import ee.sk.smartid.RpChallengeGenerator; import ee.sk.smartid.SessionType; -import ee.sk.smartid.SignableData; import ee.sk.smartid.SignatureCertificatePurposeValidator; import ee.sk.smartid.SignatureCertificatePurposeValidatorFactory; import ee.sk.smartid.SignatureCertificatePurposeValidatorFactoryImpl; import ee.sk.smartid.SignatureResponse; import ee.sk.smartid.SignatureResponseValidator; -import ee.sk.smartid.SignatureValueValidator; -import ee.sk.smartid.SignatureValueValidatorImpl; import ee.sk.smartid.SmartIdClient; import ee.sk.smartid.SmartIdDemoIntegrationTest; import ee.sk.smartid.TrustedCACertStore; @@ -97,11 +99,22 @@ import ee.sk.smartid.rest.dao.NotificationSignatureSessionResponse; import ee.sk.smartid.rest.dao.SemanticsIdentifier; import ee.sk.smartid.rest.dao.SessionStatus; +import ee.sk.smartid.signature.RsaSsaPkcs1SignatureFactory; +import ee.sk.smartid.signature.RsaSsaPssSignatureFactory; +import ee.sk.smartid.signature.SignableData; +import ee.sk.smartid.signature.SignatureFactory; +import ee.sk.smartid.signature.SigningSignatureAlgorithm; +import ee.sk.smartid.signature.SignatureValueValidator; +import ee.sk.smartid.signature.SignatureValueValidatorImpl; import ee.sk.smartid.util.CallbackUrlUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; @SmartIdDemoIntegrationTest public class ReadmeIntegrationTest { + private static final Logger logger = LoggerFactory.getLogger(ReadmeIntegrationTest.class); + private static final Pattern NUMERIC_PATTERN = Pattern.compile("^[0-9]{4}$"); private SmartIdClient smartIdClient; @@ -117,15 +130,16 @@ void setUp() { smartIdClient.setTrustStore(keyStore); } - @Disabled("Testing with device-link demo accounts is not possible at the moment") @Nested class DeviceLinkBasedExamples { @Nested class Authentication { - @Test - void anonymousAuthentication_withApp2App() { + @Disabled("Testing with App2App and Web2App is not possible at the moment") + @ParameterizedTest + @EnumSource(value = DeviceLinkType.class, names = {"APP_2_APP", "WEB_2_APP"}) + void anonymousAuthentication_with_App2App_or_Web2App(DeviceLinkType deviceLinkType) throws IOException, InterruptedException { // For security reasons a new hash value must be created for each new authentication request String rpChallenge = RpChallengeGenerator.generate().toBase64EncodedValue(); // Store generated rpChallenge only on backend side. Do not expose it to the client side. @@ -161,15 +175,12 @@ void anonymousAuthentication_withApp2App() { // Will be used to calculate elapsed time being used in device link and in authCode Instant responseReceivedAt = authenticationSessionResponse.receivedAt(); - // Next steps: - // - Generate QR-code or device link to be displayed to the user using sessionToken, sessionSecret and receivedAt provided in the authenticationResponse - // - Start querying sessions status - // Build the device link URI (without the authCode parameter) - // This base URI will be used for QR code or App2App flows + // This base URI will be used for App2App or Web2App flows URI deviceLink = smartIdClient.createDynamicContent() + .withSchemeName("smart-id-demo") .withDeviceLinkBase(deviceLinkBase.toString()) - .withDeviceLinkType(DeviceLinkType.APP_2_APP) + .withDeviceLinkType(deviceLinkType) .withSessionType(SessionType.AUTHENTICATION) .withSessionToken(sessionToken) .withDigest(rpChallenge) @@ -178,6 +189,18 @@ void anonymousAuthentication_withApp2App() { .withInteractions(authenticationSessionRequest.interactions()) .buildDeviceLink(sessionSecret); + // In real application user is routed to Smart-ID app and enters PIN 1 there + // In this test submit device link to the Mock Service so it simulates the user completing the flow (see device_link_test_endpoint in Smart-ID documentation) + submitDeviceLinkToMockService( + new DeviceLinkMockRequest( + "PNOEE-40404040009-MOCK-Q", + deviceLink.toString(), + deviceLinkType.getValue(), + "a=b;c=d", + callbackUrl.initialCallbackUri().toString() + ) + ); + // Use the sessionId from the authentication session response to poll for session status updates SessionStatusPoller poller = smartIdClient.getSessionStatusPoller(); SessionStatus sessionStatus = poller.fetchFinalSessionStatus(sessionId); @@ -206,20 +229,20 @@ void anonymousAuthentication_withApp2App() { queryParameters.get("userChallengeVerifier"), "smart-id-demo"); - assertEquals("40504040001", authenticationIdentity.getIdentityCode()); + assertEquals("40404040009", authenticationIdentity.getIdentityCode()); assertEquals("OK", authenticationIdentity.getGivenName()); - assertEquals("TESTNUMBER", authenticationIdentity.getSurname()); - assertEquals("LT", authenticationIdentity.getCountry()); + assertEquals("TEST", authenticationIdentity.getSurname()); + assertEquals("EE", authenticationIdentity.getCountry()); } @Test - void authentication_withSemanticIdentifierAndQrCode() { + void authentication_withSemanticIdentifierAndQrCode() throws IOException, InterruptedException { var semanticsIdentifier = new SemanticsIdentifier( // 3 character identity type // (PAS-passport, IDC-national identity card or PNO - (national) personal number) SemanticsIdentifier.IdentityType.PNO, SemanticsIdentifier.CountryCode.EE, // 2 character ISO 3166-1 alpha-2 country code - "40504040001"); // identifier (according to country and identity type reference) + "40404040009"); // identifier (according to country and identity type reference) // For security reasons a new rpChallenge must be created for each new authentication request String rpChallenge = RpChallengeGenerator.generate().toBase64EncodedValue(); @@ -250,15 +273,12 @@ void authentication_withSemanticIdentifierAndQrCode() { // Will be used to calculate elapsed time being used in device link Instant responseReceivedAt = authenticationSessionResponse.receivedAt(); - // Next steps: - // - Generate QR-code or device link to be displayed to the user using sessionToken, sessionSecret and receivedAt provided in the authenticationResponse - // - Start querying sessions status - // Calculate elapsed seconds from response received time long elapsedSeconds = Duration.between(responseReceivedAt, Instant.now()).getSeconds(); // Build the device link URI (without the authCode parameter) // This base URI will be used for QR code or App2App flows URI deviceLink = smartIdClient.createDynamicContent() + .withSchemeName("smart-id-demo") .withDeviceLinkBase(deviceLinkBase.toString()) .withDeviceLinkType(DeviceLinkType.QR_CODE) .withSessionType(SessionType.AUTHENTICATION) @@ -268,16 +288,25 @@ void authentication_withSemanticIdentifierAndQrCode() { .withInteractions(authenticationSessionRequest.interactions()) .withLang("est") .buildDeviceLink(sessionSecret); - // Return URI to be used with QR-code generation library on the frontend side + + // In real application return URI to be used with QR-code generation library on the frontend side // or create QR-code data-URI from device link and return that to the client side String dataUri = QrCodeGenerator.generateDataUri(deviceLink.toString()); + // In this test submit device link to the Mock Service so it simulates the user scanning the QR and completing the flow + submitDeviceLinkToMockService(new DeviceLinkMockRequest( + "PNOEE-40404040009-MOCK-Q", + deviceLink.toString(), + DeviceLinkType.QR_CODE.getValue(), + "", + "")); + // Use sessionId to poll for session status updates SessionStatusPoller poller = smartIdClient.getSessionStatusPoller(); SessionStatus sessionStatus = poller.fetchFinalSessionStatus(sessionId); // The session can have states such as RUNNING or COMPLETE. Check that the session has completed successfully. - assertEquals("COMPLETED", sessionStatus.getState()); + assertEquals("COMPLETE", sessionStatus.getState()); // Validate the response and return user's identity TrustedCACertStore trustedCaCertStore = new FileTrustedCAStoreBuilder().build(); @@ -285,15 +314,15 @@ void authentication_withSemanticIdentifierAndQrCode() { AuthenticationIdentity authenticationIdentity = DeviceLinkAuthenticationResponseValidator.defaultSetupWithCertificateValidator(certificateValidator) .validate(sessionStatus, authenticationSessionRequest, null, "smart-id-demo"); - assertEquals("40504040001", authenticationIdentity.getIdentityCode()); + assertEquals("40404040009", authenticationIdentity.getIdentityCode()); assertEquals("OK", authenticationIdentity.getGivenName()); - assertEquals("TESTNUMBER", authenticationIdentity.getSurname()); + assertEquals("TEST", authenticationIdentity.getSurname()); assertEquals("EE", authenticationIdentity.getCountry()); } @Test - void authentication_withDocumentNumberAndQrCode() { - String documentNumber = "PNOLT-40504040001-MOCK-Q"; + void authentication_withDocumentNumberAndQrCode() throws IOException, InterruptedException { + String documentNumber = "PNOEE-40404040009-MOCK-Q"; // For security reasons a new rpChallenge must be created for each new authentication request String rpChallenge = RpChallengeGenerator.generate().toBase64EncodedValue(); @@ -325,20 +354,29 @@ void authentication_withDocumentNumberAndQrCode() { // Generate the base (unprotected) device link URI, which does not yet include the authCode long elapsedSeconds = Duration.between(responseReceivedAt, Instant.now()).getSeconds(); URI deviceLink = smartIdClient.createDynamicContent() + .withSchemeName("smart-id-demo") .withDeviceLinkBase(deviceLinkBase.toString()) .withDeviceLinkType(DeviceLinkType.QR_CODE) .withSessionType(SessionType.AUTHENTICATION) .withSessionToken(sessionToken) .withDigest(rpChallenge) - .withRelyingPartyName(Base64.getEncoder().encodeToString(smartIdClient.getRelyingPartyName().getBytes(StandardCharsets.UTF_8))) .withElapsedSeconds(elapsedSeconds) .withInteractions(authenticationSessionRequest.interactions()) .withLang("est") .buildDeviceLink(sessionSecret); - // Return URI to be used with QR-code generation library on the frontend side + + // In real application return URI to be used with QR-code generation library on the frontend side // or create QR-code data-URI from device link and return that to the client side String dataUri = QrCodeGenerator.generateDataUri(deviceLink.toString()); + // In this test submit device link to the Mock Service so it simulates the user scanning the QR and completing the flow + submitDeviceLinkToMockService(new DeviceLinkMockRequest( + documentNumber, + deviceLink.toString(), + DeviceLinkType.QR_CODE.getValue(), + "", + "")); + // Use sessionId to poll for session status updates SessionStatusPoller poller = smartIdClient.getSessionStatusPoller(); SessionStatus sessionStatus = poller.fetchFinalSessionStatus(sessionId); @@ -352,9 +390,9 @@ void authentication_withDocumentNumberAndQrCode() { AuthenticationIdentity authenticationIdentity = DeviceLinkAuthenticationResponseValidator.defaultSetupWithCertificateValidator(certificateValidator) .validate(sessionStatus, authenticationSessionRequest, null, "smart-id-demo"); - assertEquals("40504040001", authenticationIdentity.getIdentityCode()); + assertEquals("40404040009", authenticationIdentity.getIdentityCode()); assertEquals("OK", authenticationIdentity.getGivenName()); - assertEquals("TESTNUMBER", authenticationIdentity.getSurname()); + assertEquals("TEST", authenticationIdentity.getSurname()); assertEquals("EE", authenticationIdentity.getCountry()); } } @@ -362,9 +400,10 @@ void authentication_withDocumentNumberAndQrCode() { @Nested class Signature { - @Test - void signature_withDocumentNumberAndQRCode() { - String documentNumber = "PNOLT-40504040001-MOCK-Q"; + @ParameterizedTest + @EnumSource(SigningSignatureAlgorithm.class) + void signature_withDocumentNumberAndQRCode(SigningSignatureAlgorithm signatureAlgorithm) throws IOException, InterruptedException { + String documentNumber = "PNOEE-40404040009-MOCK-Q"; CertificateByDocumentNumberResult certResponse = smartIdClient .createCertificateByDocumentNumber() @@ -375,7 +414,8 @@ void signature_withDocumentNumberAndQRCode() { // DataToSign dataToSign = toDataToSign(container,certResponse.certificate()); // Create the signable data from DataToSign - var signableData = new SignableData("dataToSign".getBytes(), HashAlgorithm.SHA_256); + HashAlgorithm hashAlgorithm = signatureAlgorithm.isLegacyRsa() ? signatureAlgorithm.getHashAlgorithmForLegacy() : HashAlgorithm.SHA_512; + var signableData = new SignableData("dataToSign".getBytes(), hashAlgorithm); // Build the device link signature request List signatureInteractions = List.of(DeviceLinkInteraction.displayTextAndPin("Please sign the document")); @@ -383,7 +423,8 @@ void signature_withDocumentNumberAndQRCode() { .withCertificateLevel(CertificateLevel.QSCD) .withSignableData(signableData) .withDocumentNumber(documentNumber) - .withInteractions(signatureInteractions); + .withInteractions(signatureInteractions) + .withSignatureAlgorithm(signatureAlgorithm); DeviceLinkSessionResponse signatureSessionResponse = deviceLinkSignatureSessionRequestBuilder.initSignatureSession(); // Get SignatureSessionRequest after the request is made and store for validations DeviceLinkSignatureSessionRequest deviceLinkSignatureSessionRequest = deviceLinkSignatureSessionRequestBuilder.getSignatureSessionRequest(); @@ -396,9 +437,6 @@ void signature_withDocumentNumberAndQRCode() { Instant receivedAt = signatureSessionResponse.receivedAt(); URI deviceLinkBase = signatureSessionResponse.deviceLinkBase(); - // Generate QR-code or device link to be displayed to the user using sessionToken, sessionSecret and receivedAt provided in the signatureSessionResponse - // Start querying sessions status - // Calculate elapsed seconds from response received time long elapsedSeconds = Duration.between(receivedAt, Instant.now()).getSeconds(); // Generate auth code @@ -408,21 +446,29 @@ void signature_withDocumentNumberAndQRCode() { .withDeviceLinkType(DeviceLinkType.QR_CODE) .withSessionType(SessionType.SIGNATURE) .withSessionToken(sessionToken) - .withRelyingPartyName(Base64.getEncoder().encodeToString(smartIdClient.getRelyingPartyName().getBytes(StandardCharsets.UTF_8))) .withElapsedSeconds(elapsedSeconds) .withLang("est") .withInteractions(deviceLinkSignatureSessionRequest.interactions()) + .withDigest(deviceLinkSignatureSessionRequest.signatureProtocolParameters().digest()) .buildDeviceLink(sessionSecret); - // Return URI to be used with QR-code generation library on the frontend side + // In real application return URI to be used with QR-code generation library on the frontend side // or create QR-code data-URI from device link and return that to the client side String dataUri = QrCodeGenerator.generateDataUri(deviceLink.toString()); + // In this test submit device link to the Mock Service so it simulates the user scanning the QR and completing the flow + submitDeviceLinkToMockService(new DeviceLinkMockRequest( + documentNumber, + deviceLink.toString(), + DeviceLinkType.QR_CODE.getValue(), + "", + "")); + // Get the session status poller SessionStatusPoller poller = smartIdClient.getSessionStatusPoller(); // Get signatureSessionId from current session response and poll for session status SessionStatus signatureSessionStatus = poller.fetchFinalSessionStatus(signatureSessionId); - // Session can have two states RUNNING or COMPLETED, check sessionStatus.getResult().getEndResult() for OK or error responses (f.e USER_REFUSED, TIMEOUT) + // Session can have two states RUNNING or COMPLETE, check sessionStatus.getResult().getEndResult() for OK or error responses (f.e USER_REFUSED, TIMEOUT) assertEquals("COMPLETE", signatureSessionStatus.getState()); TrustedCACertStore trustedCaCertStore = new FileTrustedCAStoreBuilder().build(); @@ -432,24 +478,32 @@ void signature_withDocumentNumberAndQRCode() { SignatureResponse signatureResponse = signatureResponseValidator.validate(signatureSessionStatus, CertificateLevel.QUALIFIED); // Validate signature value SignatureValueValidator signatureValueValidator = new SignatureValueValidatorImpl(); - signatureValueValidator.validate(signatureResponse.getSignatureValue(), signableData.calculateHash(), certResponse.certificate(), signatureResponse.getRsaSsaPssParameters()); + SignatureFactory signatureFactory = signatureResponse.getSignatureAlgorithm().isLegacyRsa() + ? new RsaSsaPkcs1SignatureFactory(signatureResponse.getSignatureAlgorithm()) + : new RsaSsaPssSignatureFactory(signatureResponse.getRsaSsaPssParameters()); + signatureValueValidator.validate( + signatureResponse.getSignatureValue(), + signableData.dataToSign(), + certResponse.certificate(), + signatureFactory); assertEquals("OK", signatureResponse.getEndResult()); - assertEquals("PNOLT-40504040001-MOCK-Q", signatureResponse.getDocumentNumber()); + assertEquals(documentNumber, signatureResponse.getDocumentNumber()); assertEquals(CertificateLevel.QUALIFIED, signatureResponse.getCertificateLevel()); assertEquals(CertificateLevel.QUALIFIED, signatureResponse.getRequestedCertificateLevel()); assertEquals("displayTextAndPIN", signatureResponse.getInteractionFlowUsed()); assertNotNull(signatureResponse.getCertificate()); } - @Test - void signature_withSemanticIdentifier() { + @ParameterizedTest + @EnumSource(SigningSignatureAlgorithm.class) + void signature_withSemanticIdentifier(SigningSignatureAlgorithm signatureAlgorithm) throws IOException, InterruptedException { var semanticIdentifier = new SemanticsIdentifier( // 3 character identity type // (PAS-passport, IDC-national identity card or PNO - (national) personal number) SemanticsIdentifier.IdentityType.PNO, SemanticsIdentifier.CountryCode.EE, // 2 character ISO 3166-1 alpha-2 country code - "40504040001"); // identifier (according to country and identity type reference) + "40404040009"); // identifier (according to country and identity type reference) NotificationCertificateChoiceSessionResponse certificateChoiceSessionResponse = smartIdClient .createNotificationCertificateChoice() @@ -464,7 +518,7 @@ void signature_withSemanticIdentifier() { SessionStatusPoller poller = smartIdClient.getSessionStatusPoller(); // Querying the sessions status - SessionStatus certificateSessionStatus = poller.getSessionStatus(certificateChoiceSessionId); + SessionStatus certificateSessionStatus = poller.fetchFinalSessionStatus(certificateChoiceSessionId); TrustedCACertStore trustedCACertStore = new FileTrustedCAStoreBuilder().build(); CertificateValidator certificateValidator = new CertificateValidatorImpl(trustedCACertStore); CertificateChoiceResponseValidator certificateChoiceResponseValidator = new CertificateChoiceResponseValidator(certificateValidator); @@ -474,22 +528,17 @@ void signature_withSemanticIdentifier() { // DataToSign dataToSign = toDataToSign(container,certResponse.certificate()); // Create the signable data - var signableData = new SignableData("dataToSign".getBytes(), HashAlgorithm.SHA_512); - - var semanticsIdentifier = new SemanticsIdentifier( - // 3 character identity type - // (PAS-passport, IDC-national identity card or PNO - (national) personal number) - SemanticsIdentifier.IdentityType.PNO, - SemanticsIdentifier.CountryCode.EE, // 2 character ISO 3166-1 alpha-2 country code - "40504040001"); // identifier (according to country and identity type reference) + HashAlgorithm hashAlgorithm = signatureAlgorithm.isLegacyRsa() ? signatureAlgorithm.getHashAlgorithmForLegacy() : HashAlgorithm.SHA3_512; + var signableData = new SignableData("dataToSign".getBytes(), hashAlgorithm); // Build the device link signature request List signatureInteractions = List.of(DeviceLinkInteraction.displayTextAndPin("Please sign the document")); DeviceLinkSignatureSessionRequestBuilder deviceLinkSignatureSessionRequestBuilder = smartIdClient.createDeviceLinkSignature() .withCertificateLevel(CertificateLevel.QUALIFIED) .withSignableData(signableData) - .withSemanticsIdentifier(semanticsIdentifier) - .withInteractions(signatureInteractions); + .withSemanticsIdentifier(semanticIdentifier) + .withInteractions(signatureInteractions) + .withSignatureAlgorithm(signatureAlgorithm); // Init signature session DeviceLinkSessionResponse signatureSessionResponse = deviceLinkSignatureSessionRequestBuilder.initSignatureSession(); @@ -504,29 +553,38 @@ void signature_withSemanticIdentifier() { String sessionSecret = signatureSessionResponse.sessionSecret(); Instant receivedAt = signatureSessionResponse.receivedAt(); - // Generate QR-code or device link to be displayed to the user using sessionToken, sessionSecret and receivedAt provided in the signatureSessionResponse - // Start querying sessions status - // Calculate elapsed seconds from response received time long elapsedSeconds = Duration.between(receivedAt, Instant.now()).getSeconds(); // Generate auth code URI deviceLink = smartIdClient.createDynamicContent() - .withDeviceLinkBase("smartid://") + .withSchemeName("smart-id-demo") + .withDeviceLinkBase(signatureSessionResponse.deviceLinkBase().toString()) .withDeviceLinkType(DeviceLinkType.QR_CODE) .withSessionType(SessionType.SIGNATURE) .withSessionToken(sessionToken) - .withRelyingPartyName(Base64.getEncoder().encodeToString(smartIdClient.getRelyingPartyName().getBytes(StandardCharsets.UTF_8))) .withElapsedSeconds(elapsedSeconds) .withLang("est") .withInteractions(deviceLinkSignatureSessionRequest.interactions()) // interactions string must be the same as in the signature session request + .withDigest(deviceLinkSignatureSessionRequest.signatureProtocolParameters().digest()) .buildDeviceLink(sessionSecret); - // Display QR-code to the user + + // In real application return URI to be used with QR-code generation library on the frontend side + // or create QR-code data-URI from device link and return that to the client side + String dataUri = QrCodeGenerator.generateDataUri(deviceLink.toString()); + + // In this test submit device link to the Mock Service so it simulates the user scanning the QR and completing the flow + submitDeviceLinkToMockService(new DeviceLinkMockRequest( + certificateChoiceResponse.getDocumentNumber(), + deviceLink.toString(), + DeviceLinkType.QR_CODE.getValue(), + "", + "")); // Get the session status poller poller = smartIdClient.getSessionStatusPoller(); // Get signatureSessionId from current session response and poll for session status SessionStatus signatureSessionStatus = poller.fetchFinalSessionStatus(signatureSessionId); - // Session can have two states RUNNING or COMPLETED, check sessionStatus.getResult().getEndResult() for OK or error responses (f.e USER_REFUSED, TIMEOUT) + // Session can have two states RUNNING or COMPLETE, check sessionStatus.getResult().getEndResult() for OK or error responses (f.e USER_REFUSED, TIMEOUT) assertEquals("COMPLETE", signatureSessionStatus.getState()); // Validate signature response @@ -534,13 +592,17 @@ void signature_withSemanticIdentifier() { SignatureResponse signatureResponse = signatureResponseValidator.validate(signatureSessionStatus, CertificateLevel.QUALIFIED); // Validate signature value SignatureValueValidator signatureValueValidator = new SignatureValueValidatorImpl(); - signatureValueValidator.validate(signatureResponse.getSignatureValue(), - signableData.calculateHash(), + SignatureFactory signatureFactory = signatureResponse.getSignatureAlgorithm().isLegacyRsa() + ? new RsaSsaPkcs1SignatureFactory(signatureResponse.getSignatureAlgorithm()) + : new RsaSsaPssSignatureFactory(signatureResponse.getRsaSsaPssParameters()); + signatureValueValidator.validate( + signatureResponse.getSignatureValue(), + signableData.dataToSign(), certificateChoiceResponse.getCertificate(), - signatureResponse.getRsaSsaPssParameters()); + signatureFactory); assertEquals("OK", signatureResponse.getEndResult()); - assertEquals("PNOLT-40504040001-MOCK-Q", signatureResponse.getDocumentNumber()); + assertEquals(certificateChoiceResponse.getDocumentNumber(), signatureResponse.getDocumentNumber()); assertEquals(CertificateLevel.QUALIFIED, signatureResponse.getCertificateLevel()); assertEquals(CertificateLevel.QUALIFIED, signatureResponse.getRequestedCertificateLevel()); assertEquals("displayTextAndPIN", signatureResponse.getInteractionFlowUsed()); @@ -693,8 +755,9 @@ void certificateChoice_withSemanticIdentifier() { assertEquals(CertificateLevel.QUALIFIED, response.getCertificateLevel()); } - @Test - void signature_withSemanticsIdentifier() { + @ParameterizedTest + @EnumSource(SigningSignatureAlgorithm.class) + void signature_withSemanticsIdentifier(SigningSignatureAlgorithm signatureAlgorithm) { var semanticIdentifier = new SemanticsIdentifier( // 3 character identity type // (PAS-passport, IDC-national identity card or PNO - (national) personal number) @@ -716,7 +779,7 @@ void signature_withSemanticsIdentifier() { SessionStatusPoller poller = smartIdClient.getSessionStatusPoller(); // Querying the sessions status - SessionStatus certificateSessionStatus = poller.getSessionStatus(certificateChoiceSessionId); + SessionStatus certificateSessionStatus = poller.fetchFinalSessionStatus(certificateChoiceSessionId); TrustedCACertStore trustedCACertStore = new FileTrustedCAStoreBuilder().build(); CertificateValidator certificateValidator = new CertificateValidatorImpl(trustedCACertStore); @@ -724,8 +787,9 @@ void signature_withSemanticsIdentifier() { CertificateChoiceResponse response = certificateChoiceResponseValidator.validate(certificateSessionStatus, certificateLevel); // For example use digidoc4j use SignatureBuilder to create DataToSign using certificateChoiceResponse.getCertificate(); - // Create the signable data - var signableData = new SignableData("dataToSign".getBytes(), HashAlgorithm.SHA_512); + // Create the signable data so calculateHash() uses the correct hash for the signature algorithm + HashAlgorithm hashForDigest = signatureAlgorithm.isLegacyRsa() ? signatureAlgorithm.getHashAlgorithmForLegacy() : HashAlgorithm.SHA_512; + var signableData = new SignableData("dataToSign".getBytes(), hashForDigest); // Create the Semantics Identifier var semanticsIdentifier = new SemanticsIdentifier( @@ -736,6 +800,7 @@ void signature_withSemanticsIdentifier() { NotificationSignatureSessionResponse signatureSessionResponse = smartIdClient.createNotificationSignature() .withCertificateLevel(certificateLevel) + .withSignatureAlgorithm(signatureAlgorithm) .withSignableData(signableData) .withSemanticsIdentifier(semanticsIdentifier) .withInteractions(List.of( @@ -752,23 +817,34 @@ void signature_withSemanticsIdentifier() { // Get sessionID from current session response and poll for session status SessionStatus signatureSessionStatus = poller.fetchFinalSessionStatus(sessionID); - // Session can have two states RUNNING or COMPLETED, check sessionStatus.getResult().getEndResult() for OK or error responses (f.e USER_REFUSED, TIMEOUT) + // Session can have two states RUNNING or COMPLETE, check sessionStatus.getResult().getEndResult() for OK or error responses (f.e USER_REFUSED, TIMEOUT) assertEquals("COMPLETE", signatureSessionStatus.getState()); SignatureResponseValidator validator = new SignatureResponseValidator(certificateValidator); SignatureResponse signatureResponse = validator.validate(signatureSessionStatus, certificateLevel); + SignatureValueValidator signatureValueValidator = new SignatureValueValidatorImpl(); + SignatureFactory signatureFactory = signatureAlgorithm.isLegacyRsa() + ? new RsaSsaPkcs1SignatureFactory(signatureAlgorithm) + : new RsaSsaPssSignatureFactory(signatureResponse.getRsaSsaPssParameters()); + signatureValueValidator.validate( + signatureResponse.getSignatureValue(), + signableData.dataToSign(), + signatureResponse.getCertificate(), + signatureFactory); + assertEquals("OK", signatureResponse.getEndResult()); - assertEquals("PNOEE-40504040001-DEMO-Q", signatureResponse.getDocumentNumber()); + assertEquals("PNOEE-40504040001-DEM0-Q", signatureResponse.getDocumentNumber()); assertEquals(CertificateLevel.QUALIFIED, signatureResponse.getCertificateLevel()); assertEquals(CertificateLevel.QSCD, signatureResponse.getRequestedCertificateLevel()); assertEquals("confirmationMessage", signatureResponse.getInteractionFlowUsed()); assertNotNull(signatureResponse.getCertificate()); } - @Test - void signature_withDocumentNumber() { - String documentNumber = "PNOEE-40504040001-DEMO-Q"; + @ParameterizedTest + @EnumSource(SigningSignatureAlgorithm.class) + void signature_withDocumentNumber(SigningSignatureAlgorithm signatureAlgorithm) { + String documentNumber = "PNOEE-50001029996-DEMO-Q"; CertificateLevel certificateLevel = CertificateLevel.QSCD; // Query the certificate by document number to be used for creating the DataToSign @@ -791,11 +867,13 @@ void signature_withDocumentNumber() { // For example use digidoc4j with SignatureBuilder to create DataToSign using `certificateByDocumentNumber.certificate()` - // Create the signable data - var signableData = new SignableData("dataToSign".getBytes(), HashAlgorithm.SHA_512); + // Create the signable data so calculateHash() uses the correct hash for the signature algorithm + HashAlgorithm hashForDigest = signatureAlgorithm.isLegacyRsa() ? signatureAlgorithm.getHashAlgorithmForLegacy() : HashAlgorithm.SHA_512; + var signableData = new SignableData("dataToSign".getBytes(), hashForDigest); NotificationSignatureSessionResponse signatureSessionResponse = smartIdClient.createNotificationSignature() .withCertificateLevel(certificateLevel) + .withSignatureAlgorithm(signatureAlgorithm) .withSignableData(signableData) .withDocumentNumber(documentNumber) .withInteractions(List.of( @@ -815,12 +893,22 @@ void signature_withDocumentNumber() { // Get sessionID from current session response and poll for session status SessionStatus signatureSessionStatus = poller.fetchFinalSessionStatus(signatureSessionId); - // Session can have two states RUNNING or COMPLETED, check sessionStatus.getResult().getEndResult() for OK or error responses (f.e USER_REFUSED, TIMEOUT) + // Session can have two states RUNNING or COMPLETE, check sessionStatus.getResult().getEndResult() for OK or error responses (f.e USER_REFUSED, TIMEOUT) assertEquals("COMPLETE", signatureSessionStatus.getState()); SignatureResponseValidator validator = new SignatureResponseValidator(certificateValidator); SignatureResponse signatureResponse = validator.validate(signatureSessionStatus, certificateLevel); + SignatureValueValidator signatureValueValidator = new SignatureValueValidatorImpl(); + SignatureFactory signatureFactory = signatureAlgorithm.isLegacyRsa() + ? new RsaSsaPkcs1SignatureFactory(signatureAlgorithm) + : new RsaSsaPssSignatureFactory(signatureResponse.getRsaSsaPssParameters()); + signatureValueValidator.validate( + signatureResponse.getSignatureValue(), + signableData.dataToSign(), + signatureResponse.getCertificate(), + signatureFactory); + assertEquals("OK", signatureResponse.getEndResult()); assertEquals(documentNumber, signatureResponse.getDocumentNumber()); assertEquals(CertificateLevel.QUALIFIED, signatureResponse.getCertificateLevel()); @@ -887,6 +975,7 @@ void signing_withQrCode() { // Build the device link URI // This base URI will be used for QR code or App2App flows URI deviceLink = smartIdClient.createDynamicContent() + .withSchemeName("smart-id-demo") .withDeviceLinkBase(deviceLinkBase.toString()) .withDeviceLinkType(DeviceLinkType.QR_CODE) .withSessionType(SessionType.CERTIFICATE_CHOICE) @@ -904,7 +993,7 @@ void signing_withQrCode() { SessionStatus certificateSessionStatus = poller.fetchFinalSessionStatus(certificateChoiceSessionId); // The session can have states such as RUNNING or COMPLETE. Check that the session has completed successfully. - assertEquals("COMPLETED", certificateSessionStatus.getState()); + assertEquals("COMPLETE", certificateSessionStatus.getState()); // Validate the certificate choice response CertificateValidatorImpl certificateValidator = new CertificateValidatorImpl(new FileTrustedCAStoreBuilder().build()); @@ -927,7 +1016,7 @@ void signing_withQrCode() { // Use sessionId to poll for signature session status updates SessionStatus signatureSessionStatus = poller.fetchFinalSessionStatus(signatureSessionResponse.sessionID()); - assertEquals("COMPLETED", signatureSessionStatus.getState()); + assertEquals("COMPLETE", signatureSessionStatus.getState()); // Validate signature response SignatureResponseValidator signatureResponseValidator = new SignatureResponseValidator(certificateValidator); @@ -937,6 +1026,40 @@ void signing_withQrCode() { } } + private static void submitDeviceLinkToMockService(DeviceLinkMockRequest deviceLinkMockRequest) throws IOException, InterruptedException { + var mapper = new ObjectMapper(); + String body = mapper.writeValueAsString(deviceLinkMockRequest); + + String url = "https://sid.demo.sk.ee/mock/device-link"; + + if (logger.isDebugEnabled()) { + logger.debug("POST {}", url); + } + if (logger.isTraceEnabled()) { + logger.trace("Request headers: {}", Collections.singletonMap("Content-Type", "application/json")); + logger.trace("Message body: {}", body); + } + + HttpClient client = HttpClient.newBuilder().build(); + HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create(url)) + .header("Content-Type", "application/json") + .POST(HttpRequest.BodyPublishers.ofString(body)) + .build(); + HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8)); + + if (logger.isDebugEnabled()) { + logger.debug("Response status: {}", response.statusCode()); + } + if (logger.isTraceEnabled()) { + logger.trace("Response body: {}", response.body()); + } + + if (response.statusCode() != 200) { + throw new RuntimeException("Mock device-link submission failed: " + response.statusCode() + " " + response.body()); + } + } + private static KeyStore getKeystore() { try (InputStream is = ReadmeIntegrationTest.class.getResourceAsStream("/demo_server_trusted_ssl_certs.jks")) { KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); diff --git a/src/test/java/ee/sk/smartid/integration/SmartIdRestIntegrationTest.java b/src/test/java/ee/sk/smartid/integration/SmartIdRestIntegrationTest.java index 69ba46f4..432ed38a 100644 --- a/src/test/java/ee/sk/smartid/integration/SmartIdRestIntegrationTest.java +++ b/src/test/java/ee/sk/smartid/integration/SmartIdRestIntegrationTest.java @@ -4,7 +4,7 @@ * #%L * Smart ID sample Java client * %% - * Copyright (C) 2018 - 2025 SK ID Solutions AS + * Copyright (C) 2018 - 2026 SK ID Solutions AS * %% * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -35,14 +35,14 @@ import org.bouncycastle.util.encoders.Base64; import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; import ee.sk.smartid.DigestCalculator; import ee.sk.smartid.HashAlgorithm; import ee.sk.smartid.RpChallengeGenerator; -import ee.sk.smartid.SignatureAlgorithm; import ee.sk.smartid.SignatureProtocol; import ee.sk.smartid.SmartIdDemoIntegrationTest; import ee.sk.smartid.VerificationCodeType; @@ -65,6 +65,8 @@ import ee.sk.smartid.rest.dao.RequestProperties; import ee.sk.smartid.rest.dao.SemanticsIdentifier; import ee.sk.smartid.rest.dao.SignatureAlgorithmParameters; +import ee.sk.smartid.signature.AuthenticationSignatureAlgorithm; +import ee.sk.smartid.signature.SigningSignatureAlgorithm; import ee.sk.smartid.util.InteractionUtil; @SmartIdDemoIntegrationTest @@ -85,7 +87,6 @@ void setUp() { smartIdConnector = new SmartIdRestConnector("https://sid.demo.sk.ee/smart-id-rp/v3/"); } - @Disabled("Testing device-link flows with demo accounts is not yet possible") @Nested class DeviceLink { @@ -108,7 +109,7 @@ void initAnonymousDeviceLinkAuthentication() { void initDeviceLinkAuthentication_withDocumentNumber() { DeviceLinkAuthenticationSessionRequest request = toDeviceLinkAuthenticationSessionRequest(); - DeviceLinkSessionResponse sessionResponse = smartIdConnector.initDeviceLinkAuthentication(request, "PNOEE-40504040001-MOCK-Q"); + DeviceLinkSessionResponse sessionResponse = smartIdConnector.initDeviceLinkAuthentication(request, "PNOEE-40404040009-MOCK-Q"); assertTrue(UUID_PATTERN.matcher(sessionResponse.sessionID()).matches()); assertTrue(SESSION_TOKEN_PATTERN.matcher(sessionResponse.sessionToken()).matches()); @@ -131,7 +132,7 @@ void initDeviceLinkAuthentication_withSemanticsIdentifier() { private static DeviceLinkAuthenticationSessionRequest toDeviceLinkAuthenticationSessionRequest() { var signatureParameters = new AcspV2SignatureProtocolParameters( RpChallengeGenerator.generate().toBase64EncodedValue(), - SignatureAlgorithm.RSASSA_PSS.getAlgorithmName(), + AuthenticationSignatureAlgorithm.RSASSA_PSS.getAlgorithmName(), new SignatureAlgorithmParameters(HashAlgorithm.SHA3_512.getAlgorithmName())); return new DeviceLinkAuthenticationSessionRequest(RELYING_PARTY_UUID, @@ -177,7 +178,7 @@ class Signature { @Test void initDeviceLinkSignature_withSemanticIdentifier() { var signatureProtocolParameters = new RawDigestSignatureProtocolParameters(Base64.toBase64String(DigestCalculator.calculateDigest("test".getBytes(), HashAlgorithm.SHA3_512)), - SignatureAlgorithm.RSASSA_PSS.getAlgorithmName(), + SigningSignatureAlgorithm.RSASSA_PSS.getAlgorithmName(), new SignatureAlgorithmParameters(HashAlgorithm.SHA3_512.getAlgorithmName())); var request = new DeviceLinkSignatureSessionRequest(RELYING_PARTY_UUID, RELYING_PARTY_NAME, @@ -203,7 +204,7 @@ void initDeviceLinkSignature_withSemanticIdentifier() { void initDeviceLinkSignature_withDocumentNumber() { var signatureProtocolParameters = new RawDigestSignatureProtocolParameters( Base64.toBase64String(DigestCalculator.calculateDigest("test".getBytes(), HashAlgorithm.SHA_512)), - SignatureAlgorithm.RSASSA_PSS.getAlgorithmName(), + SigningSignatureAlgorithm.RSASSA_PSS.getAlgorithmName(), new SignatureAlgorithmParameters(HashAlgorithm.SHA3_512.getAlgorithmName())); var request = new DeviceLinkSignatureSessionRequest(RELYING_PARTY_UUID, RELYING_PARTY_NAME, @@ -217,7 +218,7 @@ void initDeviceLinkSignature_withDocumentNumber() { null ); - DeviceLinkSessionResponse sessionResponse = smartIdConnector.initDeviceLinkSignature(request, "PNOEE-40504040001-MOCK-Q"); + DeviceLinkSessionResponse sessionResponse = smartIdConnector.initDeviceLinkSignature(request, "PNOEE-40404040009-MOCK-Q"); assertTrue(UUID_PATTERN.matcher(sessionResponse.sessionID()).matches()); assertTrue(SESSION_TOKEN_PATTERN.matcher(sessionResponse.sessionToken()).matches()); @@ -231,7 +232,7 @@ void initDeviceLinkSignature_withDocumentNumber() { class NotificationBasedRequests { private static final SemanticsIdentifier SEMANTICS_IDENTIFIER = new SemanticsIdentifier("PNOEE-40504040001"); - private static final String DOCUMENT_NUMBER = "PNOEE-40504040001-DEMO-Q"; + private static final String DOCUMENT_NUMBER = "PNOEE-50001029996-DEMO-Q"; @Nested class Authentication { @@ -257,8 +258,8 @@ void initNotificationAuthentication_withDocumentNumber() { private static NotificationAuthenticationSessionRequest toAuthenticationRequest() { var signatureParameters = new AcspV2SignatureProtocolParameters( RpChallengeGenerator.generate().toBase64EncodedValue(), - SignatureAlgorithm.RSASSA_PSS.getAlgorithmName(), - new SignatureAlgorithmParameters(HashAlgorithm.SHA3_512.getAlgorithmName())); + AuthenticationSignatureAlgorithm.RSASSA_PSS.getAlgorithmName(), + new SignatureAlgorithmParameters(HashAlgorithm.SHA_512.getAlgorithmName())); return new NotificationAuthenticationSessionRequest(RELYING_PARTY_UUID, RELYING_PARTY_NAME, @@ -311,10 +312,52 @@ void initNotificationCertificateChoice_withDocumentNumber() { assertEquals(VerificationCodeType.NUMERIC4.getValue(), sessionResponse.vc().type()); } + @ParameterizedTest + @EnumSource(value = SigningSignatureAlgorithm.class, names = {"SHA256_WITH_RSA_ENCRYPTION", "SHA384_WITH_RSA_ENCRYPTION", "SHA512_WITH_RSA_ENCRYPTION"}) + void initNotificationSignature_withRsaSsaPkcs1Algorithm_andSemanticIdentifier(SigningSignatureAlgorithm signatureAlgorithm) { + var request = toSignatureSessionRequestWithRsaSsaPkcs1(signatureAlgorithm); + + NotificationSignatureSessionResponse sessionResponse = smartIdConnector.initNotificationSignature(request, SEMANTICS_IDENTIFIER); + + assertTrue(UUID_PATTERN.matcher(sessionResponse.sessionID()).matches()); + assertTrue(VERIFICATION_CODE_PATTERN.matcher(sessionResponse.vc().value()).matches()); + assertEquals(VerificationCodeType.NUMERIC4.getValue(), sessionResponse.vc().type()); + } + + @ParameterizedTest + @EnumSource(value = SigningSignatureAlgorithm.class, names = {"SHA256_WITH_RSA_ENCRYPTION", "SHA384_WITH_RSA_ENCRYPTION", "SHA512_WITH_RSA_ENCRYPTION"}) + void initNotificationSignature_withRsaSsaPkcs1Algorithm_andDocumentNumber(SigningSignatureAlgorithm signatureAlgorithm) { + var request = toSignatureSessionRequestWithRsaSsaPkcs1(signatureAlgorithm); + + NotificationSignatureSessionResponse sessionResponse = smartIdConnector.initNotificationSignature(request, DOCUMENT_NUMBER); + + assertTrue(UUID_PATTERN.matcher(sessionResponse.sessionID()).matches()); + assertTrue(VERIFICATION_CODE_PATTERN.matcher(sessionResponse.vc().value()).matches()); + assertEquals(VerificationCodeType.NUMERIC4.getValue(), sessionResponse.vc().type()); + } + + private static NotificationSignatureSessionRequest toSignatureSessionRequestWithRsaSsaPkcs1(SigningSignatureAlgorithm signatureAlgorithm) { + byte[] digest = DigestCalculator.calculateDigest("test".getBytes(), signatureAlgorithm.getHashAlgorithmForLegacy()); + var signatureProtocolParameters = new RawDigestSignatureProtocolParameters( + Base64.toBase64String(digest), + signatureAlgorithm.getAlgorithmName(), + null); + return new NotificationSignatureSessionRequest(RELYING_PARTY_UUID, + RELYING_PARTY_NAME, + "QUALIFIED", + SignatureProtocol.RAW_DIGEST_SIGNATURE.name(), + signatureProtocolParameters, + null, + null, + InteractionUtil.encodeToBase64(List.of(new Interaction(DeviceLinkInteractionType.DISPLAY_TEXT_AND_PIN.getCode(), "Sign it!", null))), + null + ); + } + private static NotificationSignatureSessionRequest toSignatureSessionRequest() { var signatureProtocolParameters = new RawDigestSignatureProtocolParameters( Base64.toBase64String(DigestCalculator.calculateDigest("test".getBytes(), HashAlgorithm.SHA_512)), - SignatureAlgorithm.RSASSA_PSS.getAlgorithmName(), + SigningSignatureAlgorithm.RSASSA_PSS.getAlgorithmName(), new SignatureAlgorithmParameters(HashAlgorithm.SHA3_512.getAlgorithmName())); return new NotificationSignatureSessionRequest(RELYING_PARTY_UUID, RELYING_PARTY_NAME, diff --git a/src/test/java/ee/sk/smartid/signature/RsaSsaPkcs1SignatureFactoryTest.java b/src/test/java/ee/sk/smartid/signature/RsaSsaPkcs1SignatureFactoryTest.java new file mode 100644 index 00000000..bd7eca19 --- /dev/null +++ b/src/test/java/ee/sk/smartid/signature/RsaSsaPkcs1SignatureFactoryTest.java @@ -0,0 +1,63 @@ +package ee.sk.smartid.signature; + +/*- + * #%L + * Smart ID sample Java client + * %% + * Copyright (C) 2018 - 2026 SK ID Solutions AS + * %% + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * #L% + */ + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; + +import ee.sk.smartid.exception.UnprocessableSmartIdResponseException; +import ee.sk.smartid.exception.permanent.SmartIdClientException; + +class RsaSsaPkcs1SignatureFactoryTest { + + @Test + void constructor_nullAlgorithm_throwException() { + var ex = assertThrows(SmartIdClientException.class, () -> new RsaSsaPkcs1SignatureFactory(null)); + assertEquals("Parameter 'signatureAlgorithmName' is not provided", ex.getMessage()); + } + + @Test + void constructor_nonLegacyAlgorithm_throwException() { + var ex = assertThrows(UnprocessableSmartIdResponseException.class, + () -> new RsaSsaPkcs1SignatureFactory(SigningSignatureAlgorithm.RSASSA_PSS)); + assertTrue(ex.getMessage().contains("not a legacy RSA")); + } + + @Test + void getSignature_legacyAlgorithm_returnsSignatureInstance() { + var factory = new RsaSsaPkcs1SignatureFactory(SigningSignatureAlgorithm.SHA256_WITH_RSA_ENCRYPTION); + + var signature = factory.getSignature(); + + assertNotNull(signature); + assertEquals(SigningSignatureAlgorithm.SHA256_WITH_RSA_ENCRYPTION.getJceAlgorithmName(), signature.getAlgorithm()); + } +} diff --git a/src/test/java/ee/sk/smartid/signature/RsaSsaPssSignatureFactoryTest.java b/src/test/java/ee/sk/smartid/signature/RsaSsaPssSignatureFactoryTest.java new file mode 100644 index 00000000..6bccc89d --- /dev/null +++ b/src/test/java/ee/sk/smartid/signature/RsaSsaPssSignatureFactoryTest.java @@ -0,0 +1,64 @@ +package ee.sk.smartid.signature; + +/*- + * #%L + * Smart ID sample Java client + * %% + * Copyright (C) 2018 - 2026 SK ID Solutions AS + * %% + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * #L% + */ + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.security.Signature; + +import org.junit.jupiter.api.Test; + +import ee.sk.smartid.HashAlgorithm; +import ee.sk.smartid.exception.permanent.SmartIdClientException; + +class RsaSsaPssSignatureFactoryTest { + + @Test + void constructor_nullParameters_throwException() { + var ex = assertThrows(SmartIdClientException.class, () -> new RsaSsaPssSignatureFactory(null)); + assertEquals("Parameter 'rsaSsaPssParameters' is not provided", ex.getMessage()); + } + + @Test + void getSignature_validParameters_returnsConfiguredSignature() { + var rsaSsaPssParameters = new RsaSsaPssParameters(); + rsaSsaPssParameters.setDigestHashAlgorithm(HashAlgorithm.SHA_256); + rsaSsaPssParameters.setMaskGenAlgorithm(MaskGenAlgorithm.ID_MGF1); + rsaSsaPssParameters.setMaskHashAlgorithm(HashAlgorithm.SHA_256); + rsaSsaPssParameters.setSaltLength(32); + rsaSsaPssParameters.setTrailerField(TrailerField.BC); + + SignatureFactory factory = new RsaSsaPssSignatureFactory(rsaSsaPssParameters); + + Signature signature = factory.getSignature(); + + assertNotNull(signature); + assertEquals(rsaSsaPssParameters.getSignatureAlgorithmName(), signature.getAlgorithm()); + } +} diff --git a/src/test/java/ee/sk/smartid/SignableDataTest.java b/src/test/java/ee/sk/smartid/signature/SignableDataTest.java similarity index 94% rename from src/test/java/ee/sk/smartid/SignableDataTest.java rename to src/test/java/ee/sk/smartid/signature/SignableDataTest.java index 82d57a2d..116288fd 100644 --- a/src/test/java/ee/sk/smartid/SignableDataTest.java +++ b/src/test/java/ee/sk/smartid/signature/SignableDataTest.java @@ -1,10 +1,10 @@ -package ee.sk.smartid; +package ee.sk.smartid.signature; /*- * #%L * Smart ID sample Java client * %% - * Copyright (C) 2018 - 2025 SK ID Solutions AS + * Copyright (C) 2018 - 2026 SK ID Solutions AS * %% * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -34,6 +34,8 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.NullAndEmptySource; +import ee.sk.smartid.DigestCalculator; +import ee.sk.smartid.HashAlgorithm; import ee.sk.smartid.exception.permanent.SmartIdRequestSetupException; class SignableDataTest { diff --git a/src/test/java/ee/sk/smartid/SignableHashTest.java b/src/test/java/ee/sk/smartid/signature/SignableHashTest.java similarity index 94% rename from src/test/java/ee/sk/smartid/SignableHashTest.java rename to src/test/java/ee/sk/smartid/signature/SignableHashTest.java index 7d6bdf7c..880343da 100644 --- a/src/test/java/ee/sk/smartid/SignableHashTest.java +++ b/src/test/java/ee/sk/smartid/signature/SignableHashTest.java @@ -1,10 +1,10 @@ -package ee.sk.smartid; +package ee.sk.smartid.signature; /*- * #%L * Smart ID sample Java client * %% - * Copyright (C) 2018 - 2025 SK ID Solutions AS + * Copyright (C) 2018 - 2026 SK ID Solutions AS * %% * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -35,6 +35,8 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.NullAndEmptySource; +import ee.sk.smartid.DigestCalculator; +import ee.sk.smartid.HashAlgorithm; import ee.sk.smartid.exception.permanent.SmartIdRequestSetupException; class SignableHashTest { diff --git a/src/test/java/ee/sk/smartid/signature/SignatureValueValidatorImplTest.java b/src/test/java/ee/sk/smartid/signature/SignatureValueValidatorImplTest.java new file mode 100644 index 00000000..5cf2a5c0 --- /dev/null +++ b/src/test/java/ee/sk/smartid/signature/SignatureValueValidatorImplTest.java @@ -0,0 +1,186 @@ +package ee.sk.smartid.signature; + +/*- + * #%L + * Smart ID sample Java client + * %% + * Copyright (C) 2018 - 2026 SK ID Solutions AS + * %% + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * #L% + */ + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.nio.charset.StandardCharsets; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; +import java.util.Base64; +import java.util.Map; +import java.util.stream.Stream; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.ArgumentsProvider; +import org.junit.jupiter.params.provider.ArgumentsSource; +import org.junit.jupiter.params.provider.EnumSource; + +import ee.sk.smartid.CertificateUtil; +import ee.sk.smartid.HashAlgorithm; +import ee.sk.smartid.exception.UnprocessableSmartIdResponseException; +import ee.sk.smartid.exception.permanent.SmartIdClientException; + +class SignatureValueValidatorImplTest { + + // person PNOEE-40504040001 certificate from Smart-ID mock + private static final String CERT = "MIIHQjCCBsigAwIBAgIQZg+OZ2My84me/diBMmXJsDAKBggqhkjOPQQDAzBxMSwwKgYDVQQDDCNURVNUIG9mIFNLIElEIFNvbHV0aW9ucyBFSUQtUSAyMDI0RTEXMBUGA1UEYQwOTlRSRUUtMTA3NDcwMTMxGzAZBgNVBAoMElNLIElEIFNvbHV0aW9ucyBBUzELMAkGA1UEBhMCRUUwHhcNMjYwMTA2MTQyNTAxWhcNMjkwMTA1MTQyNTAwWjBXMQswCQYDVQQGEwJFRTEQMA4GA1UEAwwHVEVTVCxPSzENMAsGA1UEBAwEVEVTVDELMAkGA1UEKgwCT0sxGjAYBgNVBAUTEVBOT0VFLTQwNTA0MDQwMDAxMIIDIjANBgkqhkiG9w0BAQEFAAOCAw8AMIIDCgKCAwEAlkcfflcUuP/wZRqsPlJTzSOffEo6BkOsYZEEfUKT8M6EBTLoF6DsG6kbBYzAytlp7HeY6gO7PU6BuvzsotvTRf9bqKZQUVxyQssTOKE3eraS/HWpVyC0PVstieNds3jUI/b1MHSd4jWt02yl7nxOVDOGD7er0Eq/o0uBsLGJCvD7dytiyKUUbyPuPxa3FyogHm9F6+8N2lCGPn5ST1cBCE2QQZh+Wjjaf1OJPc3JhxPPPnJD3yMdAUC+FWE7i/fk/8k62EcJ4zWrPUGw4frCUf20tPsQ1Dd0telOWk9CwC1b9oXfSTDVjQlTBfjo11qaSBbqk/GK9UoHCkiq7PUaXw3vkUrPOt9tq/wDNlhi+YTumN6bjxc6HTmW/aM2gzxvswS0yX7IenKD8DPEgcpJyWWYUzE64FychCfdniZ+QZeiD+g1gdWbDn3p9fQ8S8eVu5TApIZQolWZal4q15ZgNWxb6f1c05AAmquH5K4+7I4IDhlYLBfTEGVoSDM5gdYWORiFvGQb0Vv9awy0C8m18wN0z4l0qQsKCj/pR9WjoBCvAr2KGfY/S2p63tMpVFPOWFwOOmiWQQkpZ0TRCaZSuOYBiFhKUnbSG83hfqeN9B9WNXou5HfJ+e/GAmYOxxXXTFTWtBHc85yT5rnmq6Pjcz4aFnU7ZS7CaZtnCPkP4uctXicjJBNp+IDdB3hp4/Pqpaj+UeqbZadkcgEN7bWBogq3GblxZq6dBecX+XHWQx/3hBBIpmOnXZZHxlZCnhznGw6DBcFJ05gYf4p6nGyyFED7HPhd43cbl2OZuP26u/np8EdnkJ0l4lbL2wtG+Xf9ySkKM1Lm7ZLdA510ADsdZJzqzE35adlcNcWyTeK9Aku9XuFPa4N1go46xkBnrK1LwE3XCDOr3ncbm3PCsic11U7AoljKI1EL4jvEa3P5y2UQlVBy+V5nL6QSmo88XeB30wKVcSLZfopjUUmIxrTxdMHAqhC117qQfr2KEJPJnTnJjkuWpiW2gRuBSdVE20oNAgMBAAGjggKPMIICizAJBgNVHRMEAjAAMB8GA1UdIwQYMBaAFLAkFxmI42b4zShYZXtNFNiSZk9rMHAGCCsGAQUFBwEBBGQwYjAzBggrBgEFBQcwAoYnaHR0cDovL2Muc2suZWUvVEVTVF9FSUQtUV8yMDI0RS5kZXIuY3J0MCsGCCsGAQUFBzABhh9odHRwOi8vYWlhLmRlbW8uc2suZWUvZWlkcTIwMjRlMDAGA1UdEQQpMCekJTAjMSEwHwYDVQQDDBhQTk9FRS00MDUwNDA0MDAwMS1ERU0wLVEweQYDVR0gBHIwcDBjBgkrBgEEAc4fEQIwVjBUBggrBgEFBQcCARZIaHR0cHM6Ly93d3cuc2tpZHNvbHV0aW9ucy5ldS9yZXNvdXJjZXMvY2VydGlmaWNhdGlvbi1wcmFjdGljZS1zdGF0ZW1lbnQvMAkGBwQAi+xAAQIwKAYDVR0JBCEwHzAdBggrBgEFBQcJATERGA8xOTA1MDQwNDEyMDAwMFowga4GCCsGAQUFBwEDBIGhMIGeMBUGCCsGAQUFBwsCMAkGBwQAi+xJAQEwCAYGBACORgEBMAgGBgQAjkYBBDATBgYEAI5GAQYwCQYHBACORgEGATBcBgYEAI5GAQUwUjBQFkpodHRwczovL3d3dy5za2lkc29sdXRpb25zLmV1L3Jlc291cmNlcy9jb25kaXRpb25zLWZvci11c2Utb2YtY2VydGlmaWNhdGVzLxMCZW4wNAYDVR0fBC0wKzApoCegJYYjaHR0cDovL2Muc2suZWUvdGVzdF9laWQtcV8yMDI0ZS5jcmwwHQYDVR0OBBYEFJGVYDWDS/+f3wOUdFXmjEAIKsSiMA4GA1UdDwEB/wQEAwIGQDAKBggqhkjOPQQDAwNoADBlAjA/30qqYa6kKYDAzWeph4bYJfY2Sve/spq3znrNMdU3mD4VPhHpeLt8q0M/cWHNG0QCMQCoBStc4F10hjXQSpTCva8SqkYWVm6pclSLy5+K40CaYWM4yrZhcM7FgcUM2aOqfEs="; + // original data as string is "dataToSign" + private static final byte[] PAYLOAD = Base64.getDecoder().decode("ZGF0YVRvU2lnbg=="); + // signatures from Smart-ID mock + private static final Map SIGNATURE_VALUE_MAP = Map.of( + SigningSignatureAlgorithm.RSASSA_PSS, Base64.getDecoder().decode("iBgRsurf7Hc0uEUYAnRR3IfQ8JXNvYM/Ft9TvWtQvnatwzLLDDukhpPCRPPP5CtNrPvKVLSLSJ9GRMz1w6lHqrlooWJm74sQaD0o6QAl5UrdaJe5ez9zn/jBPh0p+E5Z1TC9bYFTibyRHGX1W6LFTP+/wDS0eGLURnc82Q0CxQjjqzlpOQXBFqPYLmCFn8ps2AAzzKPIKLimebkuOYzYWGFT25qaiWN8ZHif8RysEYex07eTmsaLl/ZeO5DURZtJaaIFFQURcD7XIgg1iy4jns3Q3T+E5lfdPMwocn3CZgZNyx0gsoiJF0A3LRjN9lb5MS3SCN697i8EFZxsOXg/IJRWjwp2afeRgJunwFK/STkeVVDwdOAgSJ0DY4/BjasFWRuBSdrkTPPjzAFvJa00DRofS1GMaayjcVgw4H7QAvAXoden/QTLUMIy8nJSfVTfAV42dbfBLrlkenL2fWfyR6gr1s8Lkh9beFM+5AYjfwNlsuy1iY6riqVtcFDl8glpE0bxo706W143RPRxfo2VxJkk+hZtcSaunMkFCjFxxzHUOgh2Jmtz7fmWmWtknnTcc6uBhB2w00y4lLrQ80c80TMO8K6uivZU2WQ7PDxvJ0uOJXDXJItd36Aa7wwIV/XPefkDrCBRyUdpKKbgjSO3S0cbRhnvVzSDy8t95wFheXKg8uJ8bbU/f2ZpFpCN+dk+HTgSNffh9dpCV4FgjDhMYE1Oz7Ennf/gLWW5FePg7K9Xd1d9T5DfBb/YGVvxOOoleQr0DTbarCwGaUnyTSatgcSMrtSrQlq0nh1p3RKrJF8qluqNqdDvhFLMEEUHt66Zkg/JEiT/k3+PYMk43E2TsIaqTOrs+AiTvZRuJlwhXKAUSApG6U76UdsWF3Ui7pGBRlwHDbJtq4qsAhUiEqSqfFnsGeIMxvUT9pv7aasStv13G7RzehGtxoOqkae8ajy7prmFgRl0Om8xwcHWXRGlMFTppeagsjG5ilKYkj7RpWo2NOXCwsOx5KXQxu3xcv1Y"), + SigningSignatureAlgorithm.SHA256_WITH_RSA_ENCRYPTION, Base64.getDecoder().decode("cRZanVdkMHJAuBYKpmmwT4TowxqibpwpFR//XJo3Cx71XN55XlMeTrrhjrNs/IYr2y5plRFD9n8OkrvkQIK/dSzAhnnwVirMFmJv/NjxXkb1HisKCS1XMEcNtpW17k5N6PU8yOD6jLZqcMmGCIjO/PQQZfIwKXJCOC1YC0oAmaBDvVMpT+j4jaxoGhdzpjvf5vaueam94K9DmMRkJaoCxo93WGa04dZgYOz62GnFp991bOQv95TasH48fUkVCju5zSuzD2Cf8jaxd3Zc8V8JfsUEmAiumsyTv2wNyjrTZ3TVh3W7oOYXLI7GLb6XQFVQG7TBwnPnD4wf5SvLuRgCUtXVUjYLHt9tQz20AO7tEAxnX3FcmS4r759aiqFjX/xreCmIQ96zTnsF3pMsHgPhnnPCiXHRRWQHPU1vil19LYTciYrjRouuO2IKV9Etx/5Ire3YUXQM81yDfeWfPFEj4s8DRtywYNW9Ditv2MECUJTP07h4DbKpNpJFHfh6etI43YM755949DkFwmN6ZyIvZ32jgclV0ZpPsAvB0VquH8Odc8OpHYOXMBvHXr/Kd2TywTNA88HFgesBhWSbKcKt756cvFbUX/+Eji/DxjJ6DTmS8jl9q+dPvcIAzNcDEcmxTGcwS+qjD6HzoMv0I3+pZ4v3ZzwwDeahfhAqxMr3sqceCqzqS1fG+KTpeXJw+vADnVcCh+oOKPfLf9iQStOheX2c+cBqjEGWuoCVrMY+3+MaQ0NHEjG5UA7M+m9Oj80Jr6ODSSFLi4KM6NZ1yT/vOh4b0klIET0LSVm3HXACvmVc+l7IC2toVNoAsaZ83zQ2O5R2dtwNc/6p8TCOrlfvxnx3MM8R8HDET1csyqnnOAiZi0JToMJkr3F/0N8gFgAb/BJs7onETvCLuv+8SCaTTJD5XdcUno6BrjRho9vcbOnjpKWE6KbsOZu/rYwWkeqeMEzgHryD0MkYLUhqfNHuIKw2zJgP5T0apsKASoJjSLzTd0TFFNkT34MNx7YMEOHF"), + SigningSignatureAlgorithm.SHA384_WITH_RSA_ENCRYPTION, Base64.getDecoder().decode("XHDnLzHGds19lU4zdwWILXGOoFEE9h1OCZ3McSJE0osEWkR7v05FCsLTylSx/UdExxgqdychsBP6vlRyrkx5aZM64xletCgMbd+AQn3+nGxkCFSWzo/szv/8fB0OpJ5o1Ks5BpgvONrfRVhjvVCYxH9d1tjNvJYNMsxVYvpkz4Njox0IefOHwJWj9VoL13S/g5h0MBUNPjKlec3evCbZ4n0eesQySY6dQSWL8WhMdnXp9UIU0pEROEhO2Y+iHnjXa1T7FKcdzh/uhHaAipJnufBJ2Q0xpjlV3GPpYMhvk/qL2Rdh71bJKlPDaKCdZEgOglK/++qHmuxMOfMRimGT57QGVcVMcQXf/iBzoSIkucZu4rwPhQdVJpVSpBydO8rVcz1orSi3WCYHcQIMmdn3MSSK3nIxxpx/DLBChVNoVyGrCKLEDktb5ZCRS71RZZBrIW2reMi9l/jQGdZ5YAdQ1hx29LhANtxFPL+8imbBHqbOCkGHGLf+5d9ibmLWPBfnwdoBuN9pb5UH3gdf6/YWp3XObOoY2919ZwtWqBYqY0rum5a6cGuVia+52y8z+GJql/IE49sLispI9B0gm1tWY1mH7YP+6mzxweNBY+O4L49X23YHdNq9uTgYkkmUFu4CQqFDpuzqMh8KabgnZxop4IwXyZSKlAFFiM7GQgLKI9MRM69e/xjMMBjBjzILgusDdnStRs3VcRWW5zpbeUwqnEiV+stZweDSuLJsy4BfAEeYOzVVSu1O94zIKZJJRl+sW9W0lbfdQd0jjKzOXebTLegud+RzU4Agoh4zLa0mGS4pMiNJfye4Lt8po7JCLsqR4RU46UFnyt/R7eTU8Zk01nN8F1O+qyo2FIyU2drOxybhE2YsOdgBiAox04IlR1XmHFenaytFn+6Gn951gZPah+1OrCWiO+z63jzdAfNMkWk5vhaqr81jYKYAYc8d5/TTEd4kkNtK5aaLoiwh7ZAG5nu4Y7dciGt2YJurINdG9la3Z9cJA8DJAkjyrNK9Uz9p"), + SigningSignatureAlgorithm.SHA512_WITH_RSA_ENCRYPTION, Base64.getDecoder().decode("IgbB+cIDO2jTFuzWWowjN2PZlGm1PFLLSDv2K6eEfnSt1DPHDeKl8nQMituwuNOqh6HpG6TZhmHvOxebxx9FCSNyradw4rYNX/tSDEuIc4dBZQGLHJqDbI+1Yz1mHOEZE1GpJb8LbHF34/1z98s05isTQ8kwiFkzipSL1Qm3G/o6O018yEsCD4YxsDL8YUBdgUzF1T+NhMOppDy5rVge+gblGY2WfFV+Q0i3dPuqllqpa7k31QEBzAzeOPzUQqC78J9Rax9AaR1vKrYS5RR9U9hxCCQUROK6Ead/1igYnDfwd8xXe+RMSel46KQkBrH9tWUw5DpQuzDbyuCw/2HvK68/jZwJjAeAPoiRgE2DLuQJkzTGMDE6JREe/nJ+LpOSEJxX+eyKp4Ia1+J317j/cK+CApdbAK1molV1Cb4N264tT+KZmG24ERDfMDfXKzLVJUF+AIfJbQdbxxUZ51D+NQX03gj6yjVoIQ0rAdFbRarqtW1AAPAqNfcx9hvYTfD52fzPk3IHd2WMcronmTLU21RCnu0V072DXsKBOnQyBmL3UE4udVQBZxOsip3u2y/qGN8fVFzL5J0+xHbDIixHWo7b5QCR+ZZSUZmoVA/SMqumRGVMcUTaRwxPOih32+eXOElZRoGJo80Km8EHJPpo1Fvsw13I9NyHKRudIFDVJz+iJTZRHrVaWAxUdf5qJZI1FknQXX3jUmrcCMFdjAlGpVz2LxCPz7rHJh6oBg4+eWdP4TdnHuG6Hzh4QaAKEvHnmWHNHSKNYYTCzkOpRMVgQd2w9ymkVHgu8K0oBwKcMu43Pb9u/lxWRUgwt3qf6wa336vrE70A17xJZa2fWCncnCIl9ilXKH1FxHrWvhK7rH1YgfLgYQHXcO5PGDEemMiPmzKnGJDhP11BaNEhnI9ogWCr/nahwDL7nBpgYP9SMN3EY6pxZzP9II6fGWA9VNtGJg4fif6pPVUutLV5El+cf04rWChBMis+5cSgKyKNAPBHJFHiASDOEaPG4fpgaiCT") + ); + + private SignatureValueValidator signatureValueValidator; + + @BeforeEach + void setUp() { + signatureValueValidator = new SignatureValueValidatorImpl(); + } + + @Test + void validate_withRsaSsaPssSignatureFactory_ok() throws CertificateException { + X509Certificate certificate = CertificateUtil.toX509CertificateFromEncodedString(CERT); + SignatureFactory factory = new RsaSsaPssSignatureFactory(toRsaSsaPssParameters()); + + assertDoesNotThrow(() -> signatureValueValidator.validate( + SIGNATURE_VALUE_MAP.get(SigningSignatureAlgorithm.RSASSA_PSS), + PAYLOAD, + certificate, + factory)); + } + + @ParameterizedTest + @EnumSource(value = SigningSignatureAlgorithm.class, names = {"SHA256_WITH_RSA_ENCRYPTION", "SHA384_WITH_RSA_ENCRYPTION", "SHA512_WITH_RSA_ENCRYPTION"}) + void validate_withRsaSsaPkcs1SignatureFactory_ok(SigningSignatureAlgorithm signatureAlgorithm) throws CertificateException { + X509Certificate certificate = CertificateUtil.toX509CertificateFromEncodedString(CERT); + SignatureFactory factory = new RsaSsaPkcs1SignatureFactory(signatureAlgorithm); + + assertDoesNotThrow(() -> signatureValueValidator.validate( + SIGNATURE_VALUE_MAP.get(signatureAlgorithm), + PAYLOAD, + certificate, + factory)); + } + + @ParameterizedTest + @ArgumentsSource(EmptyInputArgumentProvider.class) + void validate_inputNotProvided_throwException(byte[] signatureValue, byte[] payload, X509Certificate certificate, SignatureFactory signatureFactory, String errorParameter) { + var ex = assertThrows(SmartIdClientException.class, () -> signatureValueValidator.validate(signatureValue, payload, certificate, signatureFactory)); + assertEquals("Parameter '" + errorParameter + "' is not provided", ex.getMessage()); + } + + @Test + void validate_withRsaSsaPssSignatureFactory_invalidSignature_throwException() throws CertificateException { + X509Certificate certificate = CertificateUtil.toX509CertificateFromEncodedString(CERT); + SignatureFactory factory = new RsaSsaPssSignatureFactory(toRsaSsaPssParameters()); + + var ex = assertThrows(UnprocessableSmartIdResponseException.class, + () -> signatureValueValidator.validate( + "invalidValue".getBytes(StandardCharsets.UTF_8), + PAYLOAD, + certificate, + factory)); + assertEquals("Signature value validation failed", ex.getMessage()); + } + + @ParameterizedTest + @EnumSource(value = SigningSignatureAlgorithm.class, names = {"SHA256_WITH_RSA_ENCRYPTION", "SHA384_WITH_RSA_ENCRYPTION", "SHA512_WITH_RSA_ENCRYPTION"}) + void validate_withRsaSsaPkcs1SignatureFactory_invalidSignature_throwException(SigningSignatureAlgorithm algorithm) throws CertificateException { + X509Certificate certificate = CertificateUtil.toX509CertificateFromEncodedString(CERT); + SignatureFactory factory = new RsaSsaPkcs1SignatureFactory(algorithm); + + var ex = assertThrows(UnprocessableSmartIdResponseException.class, + () -> signatureValueValidator.validate( + "invalidSignature".getBytes(StandardCharsets.UTF_8), + PAYLOAD, + certificate, + factory)); + assertEquals("Signature value validation failed", ex.getMessage()); + } + + @Test + void validate_withRsaSsaPssSignatureFactory_payloadDoesNotMatch_throwException() throws CertificateException { + X509Certificate certificate = CertificateUtil.toX509CertificateFromEncodedString(CERT); + SignatureFactory factory = new RsaSsaPssSignatureFactory(toRsaSsaPssParameters()); + + var ex = assertThrows(UnprocessableSmartIdResponseException.class, + () -> signatureValueValidator.validate( + SIGNATURE_VALUE_MAP.get(SigningSignatureAlgorithm.RSASSA_PSS), + "payloadThatDoesNotMatch".getBytes(StandardCharsets.UTF_8), + certificate, + factory)); + assertEquals("Provided signature value does not match the calculated signature value", ex.getMessage()); + } + + @ParameterizedTest + @EnumSource(value = SigningSignatureAlgorithm.class, names = {"SHA256_WITH_RSA_ENCRYPTION", "SHA384_WITH_RSA_ENCRYPTION", "SHA512_WITH_RSA_ENCRYPTION"}) + void validate_withRsaSsaPkcs1SignatureFactory_payloadDoesNotMatch_throwException(SigningSignatureAlgorithm signatureAlgorithm) throws CertificateException { + X509Certificate certificate = CertificateUtil.toX509CertificateFromEncodedString(CERT); + SignatureFactory factory = new RsaSsaPkcs1SignatureFactory(signatureAlgorithm); + + var ex = assertThrows(UnprocessableSmartIdResponseException.class, + () -> signatureValueValidator.validate( + SIGNATURE_VALUE_MAP.get(signatureAlgorithm), + "payloadThatDoesNotMatch".getBytes(StandardCharsets.UTF_8), + certificate, + factory)); + assertEquals("Provided signature value does not match the calculated signature value", ex.getMessage()); + } + + private static RsaSsaPssParameters toRsaSsaPssParameters() { + RsaSsaPssParameters rsaSsaPssParameters = new RsaSsaPssParameters(); + rsaSsaPssParameters.setDigestHashAlgorithm(HashAlgorithm.SHA_512); + rsaSsaPssParameters.setMaskGenAlgorithm(MaskGenAlgorithm.ID_MGF1); + rsaSsaPssParameters.setMaskHashAlgorithm(HashAlgorithm.SHA_512); + rsaSsaPssParameters.setSaltLength(HashAlgorithm.SHA_512.getOctetLength()); + rsaSsaPssParameters.setTrailerField(TrailerField.BC); + return rsaSsaPssParameters; + } + + private static class EmptyInputArgumentProvider implements ArgumentsProvider { + @Override + public Stream provideArguments(ExtensionContext context) throws CertificateException { + return Stream.of( + Arguments.of(null, null, null, null, "signatureValue"), + Arguments.of(new byte[0], null, null, null, "payload"), + Arguments.of(new byte[0], new byte[0], null, null, "certificate"), + Arguments.of(new byte[0], new byte[0], CertificateUtil.toX509CertificateFromEncodedString(CERT), null, "signatureFactory") + ); + } + } +} diff --git a/src/test/resources/logback.xml b/src/test/resources/logback.xml index c05b1e63..a96c34e8 100644 --- a/src/test/resources/logback.xml +++ b/src/test/resources/logback.xml @@ -8,6 +8,7 @@ +