Server-Side Signing

Note

Available only in the Enterprise version.

Starting from version 2.3.5, the ecos-integrations microservice includes functionality for server-side signing of documents with an electronic signature without using the CryptoPro EDS Browser plug-in.

For this purpose, an additional service ecos-crypto-sign was developed (repository https://gitlab.citeck.ru/citeck-projects/ecos-crypto-sign)

General Operation Scheme:

../../_images/server_signing_1png.png

Description

  1. The API client authenticates with KeyCloak. KeyCloak issues a token.

  2. The API client sends its request to ecos-proxy, specifying the token in the header.

  3. The ecos-proxy component checks the token with KeyCloak. If the token is valid, it forwards the request. Otherwise, an error occurs.

  4. The ecos-proxy component routes the request to the target ecos-gateway microservice. The header with the token is transmitted.

  5. This microservice routes the request to one of the available ecos-integrations microservices. The header with the token is transmitted.

  6. The integrations microservice sends a request to the signing microservice. The header with the token is transmitted.

  7. The signing microservice checks the token with KeyCloak. If the token is valid, signing occurs. Otherwise, an error occurs. Return response to the API client

The signing service is a standard Spring Boot application.

Currently, 2 REST requests are available:

Content Signing

{host:port}/ecos/crypto/sign

headers:

Authorization: Bearer access_token //keycloak bearer access token

body:

{
    "documentContent":"QQMTIzNDU2Nzg5",
    "keyStoreInfo":{
        "providerType":"JCSP",
        "keyStoreType":"FAT12_D"
    },
    "certificateInfo":{
        "pin":"1",
        "alias":"null",
        "serialNumber":"5927a700f7ac223d9548e8628fe9c2c302",
        "thumbprint":"A14BFD6503EFD7116AFCE95A6FAA2097E0883DEAD811",
        "signatureAlgorithm":""
    }
}
  • documentContent - Content to be signed (Base64 encoded byte[])

  • providerType - Provider type (By default, we use JCSP)

  • keyStoreType - Certificate store type (e.g., HDD on drive D, as in the current example, or registry, etc.)

  • pin - Container password for retrieving the private key

  • alias - Certificate alias for finding the correct certificate for signing

  • serialNumber - Certificate serial number? used if alias is unknown

  • thumbprint - Certificate thumbprint, used if alias and serialNumber are unknown

  • signatureAlgorithm - Signature algorithm (currently, the passed algorithm is not used; it is taken from the found certificate)

In the current implementation, the certificate is searched for:

  1. First by alias (if it is not empty; if empty, skip this step)

  2. If not found, then by serial number (if it is not empty; if empty, skip this step)

  3. If still not found, then by thumbprint (if empty and the certificate is not found, return a signing error)

Expected response:

{
    "success": true,
    "error": "",
    "signatureContent": "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"
}
  • success - Result of the REST request execution (successful/unsuccessful)

  • error - Error string (in case of a signing error)

  • signatureContent - Content of the obtained signature (Base64 encoded byte[])

During signing, a signature in CMS format is created. A Base64-encoded signature string is returned (similar to signing via the plugin).

Signature Validation

{host:port}/ecos/crypto/signVerify

headers:

Authorization: Bearer access_token //keycloak bearer access token

body:

{
    "documentContent":"QQMTIzNDU2Nzg5",
    "signatureContent":"MIIMoQYJKoZIh",
    "keyStoreInfo":{
        "providerType":"JCSP",
        "keyStoreType":"FAT12_D"
    },
    "certificateInfo":{
        "pin":"1",
        "alias":null,
        "serialNumber":"5927a700f713a4c2d9548e8628fe559c2c302",
        "thumbprint":"A14BFD6503E123FD76AFCE95A6FAA2097E0883DEAD811"
        "signatureAlgorithm":"GOST3411_2012_256withGOST3410DH_2012_256"
    }
}
  • documentContent - Content that was signed (Base64 encoded byte[])

  • signatureContent - The signature itself (Base64 encoded byte[])

  • providerType - Provider type (By default, we use JCSP)

  • keyStoreType - Certificate store type (e.g., HDD on drive D, as in the current example, or registry, etc.)

  • pin - Container password for retrieving the private key

  • alias - Certificate alias for finding the correct certificate for signing

  • serialNumber - Certificate serial number if alias is unknown

  • thumbprint - Certificate thumbprint, used if alias and serialNumber are unknown

  • signatureAlgorithm - Signature algorithm (currently, the passed algorithm is not used; it is taken from the found certificate)

In the current implementation, the certificate is searched for:

  1. First by alias (if it is not empty; if empty, skip this step)

  2. If not found, then by serial number (if it is not empty; if empty, skip this step)

  3. If still not found, then by thumbprint (if empty and the certificate is not found, return a signing error)

Expected response:

{
    "success": true,
    "error": "",
    "result": true
}
  • success - Result of the REST request execution (successful/unsuccessful)

  • error - Error string (in case of a validation error)

  • result - Result of the signature validation

Token verification can be disabled by setting the appropriate properties:

In the integrations microservice: ecos-integrations.server-sign.keycloak.isTokenCheckEnabled

In the signing service: keycloak.enabled

Full Keycloak configuration for the signing service:

keycloak:
    enabled: true
    auth-server-url: http://localhost:8484/auth
    realm: "master"
    resource: "service_client"
    bearer-only: true
    security-constraints:
      - authRoles:
          - uma_authorization
      securityCollections:
          - patterns:
              - /ecos/*
  • enabled - Enable/disable token verification before signing/validation

  • auth-server-url - Auth endpoint URL for Keycloak

  • realm - Realm for which the token is verified.

  • resource - Client ID requiring access (Not relevant in this service; any valid client in the specified realm can be added)

  • bearer-only - If set to true, the application can only verify tokens, and login within the application will not be possible

  • security-constraints - For describing the role-based policy

    • authRoles - List of Keycloak roles (uma_authorization by default, as it is issued to all clients)

    • securityCollections

      • patterns - URL patterns for REST API methods that need to be secured with appropriate roles (in this case, two methods /ecos/’*’)

Configuration of additional settings for the integrations microservice:

ecos-integrations:
server-sign:
    root-uri: http://localhost:8083
    keycloak:
        host: http://localhost
        port: 8484
        client-id: service_client
        client-secret: 5307e923-570b-4fed-ad18-cc6320056bf9
        realm: master
        isTokenCheckEnabled: false
        isTrustAllEnabled: false
  • root-uri - Address of the signing service

  • keycloak.host - Keycloak host

  • keycloak.port - Keycloak port

  • keycloak.client-id - Keycloak service client ID

  • keycloak.client-secret - Keycloak service client secret

  • keycloak.realm - Realm for which the token is verified.

  • keycloak.isTokenCheckEnabled - When the flag is enabled, the service client’s access token is passed to the signing service

  • keycloak.isTrustAllEnabled - Enable/disable TrustAll policy when requesting a token from Keycloak (set to true if an access error occurs, but note that this introduces a vulnerability)

Creating a Keycloak service client

The service client must be created in the corresponding Keycloak realm. It is necessary for obtaining an access token.

  1. Log in to the admin console directly on the Keycloak server

  2. Switch to the required realm

  3. Go to Clients → Add new (Client name - any) → Create

  4. Set Access Type = confidential

  5. Set flag Service Accounts Enabled = true

  6. Valid Redirect URIs - Add any redirect URI to the main server, although in this case it’s not strictly necessary, as we only need the token from the client, not resources

  7. Save

After this, on the Credentials tab of this client, you can find its secret; the ID is the name we specified.

Request from ECOS

In order to be able to request signature validation and signing of any content in the integrations microservice, corresponding RecordsDao were implemented

Signing - ServerSignRecords

ID - “server-sign“

Dto - SignRequest

public class SignRequest {
    private String documentContent;
    private KeyStoreInfo keyStoreInfo;
    private CertificateInfo certificateInfo;
}

CertificateInfo:

public class CertificateInfo {
    private String pin;
    private String alias;
    private String serialNumber;
    private String thumbprint;
    private String signatureAlgorithm;
}

KeyStoreInfo:

public class KeyStoreInfo {
    private String providerType;
    private String keyStoreType;
}

Example request from browser console:

Records.query(
    {
        sourceId: "integrations/server-sign",
        query: {
            "documentContent":"MTIzNDU2Nzg5",
            "keyStoreInfo":{
                "providerType":"JCSP",
                "keyStoreType":"FAT12_D"
            },
            "certificateInfo":{
                "pin":"1",
                "alias":null,
                "serialNumber":"",
                "thumbprint":"A1424BFD6503EFD7236AFCE95A1236FAA2097E0883DEAD8123",
                "signatureAlgorithm":""
            }
        }
    },{
    signatureContent: "signatureContent",
    error: "error"
    }
).then(res => console.log(res));

Validation - ServerSignVerificationRecords

ID - “server-sign-verify“

Dto - SignVerifyRequest

public class SignVerifyRequest {
    private String documentContent;
    private String signatureContent;
    private KeyStoreInfo keyStoreInfo;
    private CertificateInfo certificateInfo;
}

CertificateInfo:

public class CertificateInfo {
    private String pin;
    private String alias;
    private String serialNumber;
    private String thumbprint;
    private String signatureAlgorithm;
}

KeyStoreInfo:

public class KeyStoreInfo {
    private String providerType;
    private String keyStoreType;
}

Example request from browser console:

Records.query(
    {
        sourceId: "integrations/server-sign-verify",
        query: {
            "documentContent":"MTIzNDU2Nzg5",
            "signatureContent":"asdasdfbvhadsfbdhasbfks",
            "keyStoreInfo":{
                "providerType":"JCSP",
                "keyStoreType":"FAT12_D"
            },
            "certificateInfo":{
                "pin":"1",
                "alias":null,
                "serialNumber":"",
                "thumbprint":"A14BFD6503EFD1276AFCE952A6FAA205197E0883DEAD813",
                "signatureAlgorithm":""
            }
        }
    },{
    result: "result",
    error: "error"
    }
).then(res => console.log(res));

To run the functionality locally, you need to clone the ecos-crypto-sign repository and run the CryptoSignApp application

Missing libraries (in case of compilation errors and NoClassDefFoundException) can be taken from the official CryptoPro website

It is necessary to install JCP + JCSP functionality into the required JRE