Skip to content

Commit 29079d4

Browse files
authored
Update notification based signature to use v3.1 endpoints (#141)
* SLIB-126 - add util to create callbackUrl with url-token * SLIB-126 - move UrlSafeTokenGenerator and CallbackUrl to common package * SLIB-116 - update notification-based signature path and request object * SLIB-116 - update notification-based signature session request builder * SLIB-116 - add pattern validation for verification code value * SLIB-116 - convert NotificationCertificateChoiceSessionResponse into record * SLIB-116 - convert NotificationSignatureSessionResponse into record * SLIB-116 - convert VerificationCode into record * SLIB-116 - fix notification-based signature session readme integration tests * SLIB-116 - improve tests for notification-based signature flow * SLIB-116 - fix typo in package name * SLIB-116 - code style improvements * SLIB-116 - improve documentation for idempotent behaviour in Readme
1 parent ae75afa commit 29079d4

37 files changed

Lines changed: 1088 additions & 782 deletions

File tree

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
44

55
Changes mentioned under 3.1.x version have not been published yet. Will be released when v3.1 is stable.
66

7+
## [3.1.17] - 2025-10-07
8+
- Updated SmartIdRestConnector to use v3.1 notification-based signature endpoint
9+
710
## [3.1.16] - 2025-10-04
811
- Updated SmartIdRestConnector to use v3.1 notification-based certificate choice endpoint
912
- Added AccountUnusableException to handle ACCOUNT_UNUSABLE endResult from session status response

README.md

Lines changed: 28 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -990,7 +990,7 @@ the Smart-ID API will stay waiting for the RP to start the [linked notification-
990990
* `relyingPartyUUID`: Required. UUID of the Relying Party.
991991
* `relyingPartyName`: Required. Friendly name of the Relying Party, limited to 32 bytes in UTF-8 encoding.
992992
* `certificateLevel`: Level of certificate requested. ADVANCED/QUALIFIED/QSCD, defaults to QUALIFIED.
993-
* `nonce`: Random string, up to 30 characters. If present, must have at least 1 character. Used for overriding idempotency.
993+
* `nonce`: Random string, up to 30 characters. If present, must have at least 1 character. Used for overriding idempotent behaviour.
994994
* `capabilities`: Used only when agreed with Smart-ID provider. When omitted, request capabilities are derived from certificateLevel.
995995
* `requestProperties`: A request properties object as a set of name/value pairs. For example, requesting the IP address of the user's device.
996996
* `initialCallbackUrl` : Optional. Must match regex `^https:\/\/([^\\|]+)$`. If it contains the vertical bar `|`, it must be percent-encoded. Should be used for same-device flow.
@@ -1218,9 +1218,6 @@ Jump to [Query session status](#example-of-using-session-status-poller-to-query-
12181218

12191219
### Notification-based signature session
12201220

1221-
> [!CAUTION]
1222-
> The notification-based signature has not yet been updated to be used with Smart-ID API v3.1
1223-
12241221
#### Request Parameters
12251222
The request parameters for the notification-based signature session are as follows:
12261223

@@ -1230,21 +1227,23 @@ The request parameters for the notification-based signature session are as follo
12301227
* `signatureProtocol`: Required. Signature protocol to use. Currently, the only allowed value is RAW_DIGEST_SIGNATURE.
12311228
* `signatureProtocolParameters`: Required. Parameters for the RAW_DIGEST_SIGNATURE signature protocol.
12321229
* `digest`: Required. Base64 encoded digest to be signed.
1233-
* `signatureAlgorithm`: Required. Signature algorithm name. Supported values are `sha256WithRSAEncryption`, `sha384WithRSAEncryption`, `sha512WithRSAEncryption`.
1234-
* `allowedInteractionsOrder`: Required. An array of interaction objects defining the allowed interactions in order of preference.
1230+
* `signatureAlgorithm`: Required. Signature algorithm name. Only `rsassa-pss` is currently supported.
1231+
* `signatureAlgorithmParameters`: Required. Parameters for the signature algorithm.
1232+
* `hashAlgorithm`: Required. Hash algorithm used for digest. Supported values are `SHA-256`, `SHA-384`, `SHA-512`, `SHA3-256`, `SHA3-384`, `SHA3-512`.
1233+
* `interactions`: Required. Base64-encoded string of interactions to be used for a session. The interactions are defined in order of preference.
12351234
* Each interaction object includes:
1236-
* `type`: Required. Type of interaction. Allowed types are `verificationCodeChoice`, `confirmationMessageAndVerificationCodeChoice`.
1235+
* `type`: Required. Type of interaction. Allowed types are `displayTextAndPIN`, `confirmationMessage`, `confirmationMessageAndVerificationCodeChoice`.
12371236
* `displayText60` or `displayText200`: Required based on type. Text to display to the user. `displayText60` is limited to 60 characters, and `displayText200` is limited to 200 characters.
1238-
* `nonce`: Optional. Random string, up to 30 characters. If present, must have at least 1 character.
1237+
* `nonce`: Optional. Random string, up to 30 characters. If present, must have at least 1 character. To be used for overriding idempotency.
12391238
* `requestProperties`: requestProperties:
12401239
* `shareMdClientIpAddress`: Optional. Boolean indicating whether to request the IP address of the user's device.
12411240
* `capabilities`: Optional. Array of strings specifying capabilities. Used only when agreed with the Smart-ID provider.
12421241

12431242
#### Response Parameters
12441243
* `sessionID`: Required. String used to request the operation result.
1245-
* `verificationCode`: Required. Object describing the Verification Code to be displayed.
1246-
* `type`: Required. Type of the VC code. Currently, the only allowed type is `alphaNumeric4`.
1247-
* `value`: Required. Value of the VC code.
1244+
* `vc`: Required. Object describing the verification code details.
1245+
* `type`: Required. Type of the verification code. Currently, the only allowed type is `numeric4`.
1246+
* `value`: Required. Value of the verification code to be displayed to the user.
12481247

12491248
#### Examples of initiating a notification-based signature session
12501249

@@ -1262,21 +1261,20 @@ SemanticsIdentifier semanticsIdentifier = new SemanticsIdentifier(
12621261
);
12631262

12641263
// Build the notification signature request
1265-
NotificationSignatureSessionResponse signatureSessionResponse = client.createNotificationSignature()
1266-
.withRelyingPartyUUID(client.getRelyingPartyUUID())
1267-
.withRelyingPartyName(client.getRelyingPartyName())
1268-
.withCertificateLevel(CertificateLevel.QUALIFIED)
1269-
.withSignableData(signableData)
1270-
.withSemanticsIdentifier(semanticsIdentifier)
1271-
.withAllowedInteractionsOrder(List.of(
1272-
NotificationInteraction.confirmationMessage("Please sign the <document-name>"))) // Display text should be concise and specific.
1273-
.initSignatureSession();
1264+
NotificationSignatureSessionResponse signatureSessionResponse = smartIdClient.createNotificationSignature()
1265+
.withCertificateLevel(CertificateLevel.QSCD)
1266+
.withSignableData(signableData)
1267+
.withSemanticsIdentifier(semanticsIdentifier)
1268+
.withInteractions(List.of(
1269+
NotificationInteraction.confirmationMessage("Please sign the <document-name>")) // Display text should be concise and specific.
1270+
)
1271+
.initSignatureSession();
12741272

1275-
// Process the querying sessions status response
1273+
// Get the session ID and continue to querying session status
12761274
String sessionID = signatureSessionResponse.sessionID();
12771275

12781276
// Display verification code to the user
1279-
String verificationCode = signatureSessionResponse.getVc().getValue();
1277+
String verificationCode = signatureSessionResponse.vc().getValue();
12801278
```
12811279
Jump to [Query session status](#example-of-using-session-status-poller-to-query-final-sessions-status) for an example of session querying.
12821280

@@ -1300,11 +1298,11 @@ NotificationSignatureSessionResponse signatureResponse = client.createNotificati
13001298
NotificationInteraction.confirmationMessage("Please sign the <document-name>"))) // Display text should be concise and specific.
13011299
.initSignatureSession();
13021300

1303-
// Process the signature response
1301+
// Get the session ID and continue to querying session status
13041302
String sessionID = signatureResponse.sessionID();
13051303

13061304
// Display verification code to the user
1307-
String verificationCode = signatureResponse.getVc().getValue();
1305+
String verificationCode = signatureResponse.vc().getValue();
13081306
```
13091307
Jump to [Query session status](#example-of-using-session-status-poller-to-query-final-sessions-status) for an example of session querying.
13101308

@@ -1339,7 +1337,12 @@ try {
13391337

13401338
#### Using nonce to override idempotent behaviour
13411339

1342-
Authentication is used as an example, nonce can also be used with certificate choice and signature sessions requests by using method `withNonce("randomValue")`.
1340+
Idempotent behaviour means that if the session request with same values is made multiple times within a 15-second window,
1341+
the same response with identical values will be returned. If there is a need to override this behaviour, a nonce can be used.
1342+
Nonce value must be a random string with a minimum length of 1 and a maximum length of 30 characters.
1343+
1344+
Notification-based signature request is used as an example. Nonce can also be used with other signing session request
1345+
(device-link signature and certificate choice; notification-based certificate choice) by using method `withNonce("randomValue")`.
13431346

13441347
```java
13451348
NotificationSignatureSessionResponse signatureSessionResponse = smartIdClient.createNotificationSignature()

src/main/java/ee/sk/smartid/DeviceLinkSignatureSessionRequestBuilder.java

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@
4040
import ee.sk.smartid.rest.dao.RequestProperties;
4141
import ee.sk.smartid.rest.dao.SemanticsIdentifier;
4242
import ee.sk.smartid.rest.dao.SignatureAlgorithmParameters;
43-
import ee.sk.smartid.rest.dao.SignatureSessionRequest;
43+
import ee.sk.smartid.rest.dao.DeviceLinkSignatureSessionRequest;
4444
import ee.sk.smartid.util.InteractionUtil;
4545
import ee.sk.smartid.util.SetUtil;
4646
import ee.sk.smartid.util.StringUtil;
@@ -67,7 +67,7 @@ public class DeviceLinkSignatureSessionRequestBuilder {
6767
private String initialCallbackUrl;
6868
private DigestInput digestInput;
6969

70-
private SignatureSessionRequest signatureSessionRequest;
70+
private DeviceLinkSignatureSessionRequest deviceLinkSignatureSessionRequest;
7171

7272
/**
7373
* Constructs a new Smart-ID signature request builder with the given connector.
@@ -251,29 +251,32 @@ public DeviceLinkSignatureSessionRequestBuilder withInitialCallbackUrl(String in
251251
*/
252252
public DeviceLinkSessionResponse initSignatureSession() {
253253
validateRequestParameters();
254-
SignatureSessionRequest signatureSessionRequest = createSignatureSessionRequest();
255-
DeviceLinkSessionResponse deviceLinkSignatureSessionResponse = initSignatureSession(signatureSessionRequest);
254+
DeviceLinkSignatureSessionRequest deviceLinkSignatureSessionRequest = createSignatureSessionRequest();
255+
DeviceLinkSessionResponse deviceLinkSignatureSessionResponse = initSignatureSession(deviceLinkSignatureSessionRequest);
256256
validateResponseParameters(deviceLinkSignatureSessionResponse);
257-
this.signatureSessionRequest = signatureSessionRequest;
257+
this.deviceLinkSignatureSessionRequest = deviceLinkSignatureSessionRequest;
258258
return deviceLinkSignatureSessionResponse;
259259
}
260260

261261
/**
262-
* Gets the SignatureSessionRequest that was used to initiate the signature session.
262+
* Gets the DeviceLinkSignatureSessionRequest that was used to initiate the signature session.
263263
* <p>
264264
* This method can only be called after {@link #initSignatureSession()} has been invoked.
265265
*
266266
* @return the signature request that was used to initiate the session
267267
* @throws SmartIdClientException if called before initSignatureSession()
268268
*/
269-
public SignatureSessionRequest getSignatureSessionRequest() {
270-
if (signatureSessionRequest == null) {
269+
public DeviceLinkSignatureSessionRequest getSignatureSessionRequest() {
270+
if (deviceLinkSignatureSessionRequest == null) {
271271
throw new SmartIdClientException("Signature session has not been initiated yet");
272272
}
273-
return signatureSessionRequest;
273+
return deviceLinkSignatureSessionRequest;
274274
}
275275

276-
private DeviceLinkSessionResponse initSignatureSession(SignatureSessionRequest request) {
276+
private DeviceLinkSessionResponse initSignatureSession(DeviceLinkSignatureSessionRequest request) {
277+
if (semanticsIdentifier != null && documentNumber != null) {
278+
throw new SmartIdRequestSetupException("Only one of 'semanticsIdentifier' or 'documentNumber' may be set");
279+
}
277280
if (!StringUtil.isEmpty(documentNumber)) {
278281
return connector.initDeviceLinkSignature(request, documentNumber);
279282
} else if (semanticsIdentifier != null) {
@@ -283,11 +286,11 @@ private DeviceLinkSessionResponse initSignatureSession(SignatureSessionRequest r
283286
}
284287
}
285288

286-
private SignatureSessionRequest createSignatureSessionRequest() {
289+
private DeviceLinkSignatureSessionRequest createSignatureSessionRequest() {
287290
var signatureProtocolParameters = new RawDigestSignatureProtocolParameters(digestInput.getDigestInBase64(),
288291
signatureAlgorithm.getAlgorithmName(),
289292
new SignatureAlgorithmParameters(digestInput.hashAlgorithm().getAlgorithmName()));
290-
return new SignatureSessionRequest(relyingPartyUUID,
293+
return new DeviceLinkSignatureSessionRequest(relyingPartyUUID,
291294
relyingPartyName,
292295
certificateLevel != null ? certificateLevel.name() : null,
293296
SignatureProtocol.RAW_DIGEST_SIGNATURE.name(),

src/main/java/ee/sk/smartid/ErrorResultHandler.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ public class ErrorResultHandler {
5858
* @throws SmartIdClientException when input parameter sessionResult is null
5959
* @throws UserActionException sub-exceptions based on end result
6060
* @throws UserAccountException sub-exceptions based on end result
61-
* @throws ProtocolFailureException when there was an error in the process (e.g. shcema name is incorrect)
61+
* @throws ProtocolFailureException when there was an error in the process (e.g. schema name is incorrect)
6262
* @throws ExpectedLinkedSessionException when different session type was started than expected
6363
* @throws SmartIdServerException when technical error occurred on server side
6464
* @throws UnprocessableSmartIdResponseException when unexpected end result was received

src/main/java/ee/sk/smartid/NonQualifiedSignatureCertificatePurposeValidator.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828

2929
import java.security.cert.X509Certificate;
3030

31-
import ee.sk.smartid.common.certifiate.NonQualifiedSmartIdCertificateValidator;
31+
import ee.sk.smartid.common.certificate.NonQualifiedSmartIdCertificateValidator;
3232
import ee.sk.smartid.exception.UnprocessableSmartIdResponseException;
3333
import ee.sk.smartid.util.CertificateAttributeUtil;
3434

src/main/java/ee/sk/smartid/NotificationCertificateChoiceSessionRequestBuilder.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,7 @@ private NotificationCertificateChoiceSessionRequest createCertificateChoiceReque
187187
}
188188

189189
private void validateResponseParameters(NotificationCertificateChoiceSessionResponse notificationCertificateChoiceSessionResponse) {
190-
if (StringUtil.isEmpty(notificationCertificateChoiceSessionResponse.getSessionID())) {
190+
if (StringUtil.isEmpty(notificationCertificateChoiceSessionResponse.sessionID())) {
191191
throw new UnprocessableSmartIdResponseException("Notification-based certificate choice response field 'sessionID' is missing or empty");
192192
}
193193
}

0 commit comments

Comments
 (0)