Electronic Signature System (eSign)

Role

FaceKom’s electronic signature subsystem. Two services work in tandem: esign_css (customer-facing frontend) and esign_oss (backend with business logic, PDF signing, and admin UI). Enables legally binding document signing with 2FA verification.

Architecture

graph TB
    Customer[Customer Browser] -->|HTTPS :30180| esign_css
    Operator[Operator Browser] -->|HTTPS :20180| esign_oss

    subgraph esign_css ["esign_css (Frontend)"]
        WebCSS[Express :10183]
        SocketCSS[Socket.IO :10184]
    end

    subgraph esign_oss ["esign_oss (Backend)"]
        WebOSS[Admin UI :10181]
        ExtAPI[External API :10182]
        SocketOSS[Socket.IO :10180]
    end

    esign_css <-->|RabbitMQ RPC<br/>8 queues| esign_oss
    esign_oss -->|RabbitMQ RPC| pdfservice[["[[pdfservice]]<br/>PDF Signing"]]
    esign_oss --> PostgreSQL[(PostgreSQL)]
    esign_oss --> Redis[(Redis)]
    esign_oss -->|SMTP| Email[Email Service]
    esign_oss -->|SmsApi.com| SMS[SMS Service]
    esign_oss -->|FCM| Push[Push Notifications]

Service Overview

Propertyesign_cssesign_oss
RoleCustomer-facing signing UIBackend, admin, business logic
Version1.3.01.3.0
Node.js>= 20.8.1>= 22.13.0
Ports:10183 (web), :10184 (socket):10181 (admin), :10182 (ext API), :10180 (socket)
DatabaseNone (all via RPC)PostgreSQL (27 models)
BuildBrowserify, Stylus, Dust.js + TwigBrowserify, Stylus, Dust.js + Twig

esign_css — Frontend

Key Files

FileRole
server.jsEntry point: queue, services, web (:10183), socket (:10184)
config.jsConfig loader (getconfig + Spring Cloud Config)
server/web/routes.jsHTTP routes: customer APIs, signature flow, offer signing UI
server/web/web-server.jsExpress setup with Redis sessions, Helmet, CSP
server/service/SessionService.jsJWT-based session management
server/service/SignatureService.jsSignature operations proxied to esign_oss via RPC
server/service/CustomerService.jsCustomer auth/login proxied to esign_oss via RPC
server/socket/socket-server.jsSocket.IO real-time updates
client/features/signature/App.jsClient-side signature flow (2FA auth, refuse)
client/features/signature/BrowserSignature.jsBrowser-based signature implementation

RPC Clients (talks to esign_oss)

ClientPurpose
AppApp registration/management
JwtAuthJWT token validation
CustomerCustomer auth, login, 2FA
TimestampTimestamp operations
DocumentDocument CRUD
ContractContract management
OfferOffer operations
SignatureSignature creation/verification
SignatureCheckSignature check service

RPC Servers (receives from esign_oss)

  • Ping — Health check
  • SystemOss — System configuration push

Queue Servers

  • ServiceBus — General service bus messages
  • Customer — Customer updates
  • Contract — Contract updates

esign_oss — Backend

Key Files

FileRole
server.jsEntry point: DB, queue, web (:10181), external API (:10182), socket (:10180)
server/db/models.jsSequelize models: 27 entities with relationships
server/service/SignatureService.jsPDF signing via Java binary, signature verification
server/service/CryptoService.jsAES-256-GCM encryption for cert passwords
server/gcm.jsFull AES-256-GCM implementation (encrypt/decrypt/streams)
server/service/TrustedTimestampService.jsRFC 3161 trusted timestamps via Hungarian e-Szigno
server/external/routes.jsExternal webhook API
server/web/routes.jsAdmin UI routes with ACL-based access control
cron.jsCron jobs (offer timeout, document deletion, URL expiry)

Three Concurrent Servers

PortServicePurpose
:10181Admin UIOperator web interface with ACL protection
:10182External APIWebhook endpoints for external system integration
:10180Socket.IOReal-time updates to operator UI

RPC Servers (handles from esign_css)

QueuePurpose
esign:rpc-appApp registration/management
esign:rpc-customerCustomer auth, login, 2FA
esign:rpc-timestampTimestamp operations
esign:rpc-documentDocument CRUD
esign:rpc-contractContract management
esign:rpc-offerOffer operations
esign:rpc-signatureSignature creation/verification
esign:rpc-signature-checkSignature check service

RPC Clients (calls esign_css)

  • esign:css-ping — Health check
  • esign:cron-ping — Cron health check
  • esign:rpc-system-oss — System configuration push

Database Schema (27 Models)

erDiagram
    Customer ||--o{ Contract : has
    Customer ||--o{ Offer : has
    Customer ||--o{ Activity : has
    Customer ||--o{ CustomerData : has
    Customer ||--o{ VerificationToken : has
    Customer ||--o{ App : has
    Contract ||--o{ Document : has
    Offer ||--o{ Signature : has
    Signature ||--o{ SignatureData : has
    Signature ||--o| SignatureValidation : has
    App ||--o| Device : has
    App ||--o| FcmInfo : has

Supporting models: User, AuditLog, EmailLog, SmsLog, Setting, Shortener, ClientErrorLog, Cert, BackgroundProcess, BackgroundProcessLog, TimestampToken, Attachment, Session.

External Webhook API (port 10182)

Each endpoint can be independently enabled/disabled and protected with HTTP Basic Auth.

EndpointPurpose
POST /offer/uploadUpload new offer
POST /offer/revokeRevoke offer
POST /contract/uploadUpload signed contract
POST /signature/verifyVerify signature
POST /customer/bootstrapBootstrap customer
POST /customer/send-association-tokenSend device association token
POST /customer/approveApprove customer
POST /app/notificationSend push notification

PDF Signing Flow

The SignatureService.signPdf() method:

  1. Takes a customer-signed PDF, certificate (.p12), and password
  2. Creates temp files for the PDF and certificate
  3. Writes a JSON config file with signature parameters and TSA credentials
  4. Executes either:
    • Pre-4.0: java -jar /opt/facekom/pdf/FaceKomPDFSigner.jar <config>
    • 4.0+: /opt/facekom/pdf/bin/facekom_pdf_signer <config>
  5. TSA credentials passed via environment variables
  6. Reads the signed PDF from disk, cleans up temp files

Also supports PAdES hash-based signing via signPdfPadesHash() using FaceKomPDFClientSigner.jar.

Crypto Implementation (AES-256-GCM)

PropertyValue
AlgorithmAES-256-GCM
Key derivationPBKDF2 (5000 iterations, SHA-256)
Key encodingBase62 (32 bytes)
IV12 bytes random
MAC16 bytes
OperationsBuffer, streaming, and string encrypt/decrypt

Weak PBKDF2

5000 PBKDF2 iterations is well below the modern recommendation of 600,000+ for SHA-256.

Cron Jobs

JobSchedulePurpose
OfferTimeoutCronJobWeekly (Monday 8am)Revokes expired offers
DeleteDocumentsCronJobDaily (6am)Deletes old documents
DeleteExpiredURLCronJobDaily (3am)Cleans up expired short URLs

End-to-End Signing Flow

sequenceDiagram
    participant Op as Operator
    participant EOSS as esign_oss
    participant ECSS as esign_css
    participant Cust as Customer
    participant PDF as pdfservice

    Op->>EOSS: Upload offer (admin UI or webhook)
    EOSS->>Cust: Notification (SMS/email/push)
    Cust->>ECSS: Open esign_css, authenticate
    ECSS->>EOSS: RPC: fetch documents
    EOSS-->>ECSS: Return documents
    ECSS-->>Cust: Display documents for review
    Cust->>ECSS: Initiate signing (M1: send public key)
    ECSS->>EOSS: RPC: create Signature + send 2FA SMS
    EOSS-->>Cust: SMS with verification token
    Cust->>ECSS: Provide username + password + SMS token
    ECSS->>EOSS: RPC: 2FA auth + M2 (signed data)
    EOSS->>EOSS: Validate signature cryptographically
    EOSS->>PDF: RPC: sign PDF with bank certificate
    PDF->>PDF: Add PAdES signature + trusted timestamp
    PDF-->>EOSS: Return signed PDF
    EOSS->>EOSS: Store signed contract
    EOSS-->>Op: Notify operator (Socket.IO)
    Op->>EOSS: Verify signature (optional)
    EOSS->>PDF: RPC: validate signature

Customization System

  • customization/customizations.js — Hook-based system using event emitter
  • customization/branding-options.json — Visual branding
  • customization/translations/offer.trans.js — Translation overrides

Dependencies

PackageVersionPurpose
@techteamer/mq^7.0.1RabbitMQ client
Express5.2.1HTTP framework
Socket.IO4.6.1Real-time communication
node-jose-JWE/JWS crypto
csurf-CSRF protection
connect-redis-Session storage
jQuery4.0Client-side (esign_css)

TODOs

LocationIssue
esign_css/server/service/AppService.js:5”oss sends notification” — not implemented
esign_css/server/web/web-server.js:112”expires false?” on cookie config
esign_css/server/web/routes.js:69”provide estimate location” (always ‘N/A’)
esign_css/server/web/api/customer/login.js:11Handle duplicate login sessions
esign_css/server/web/api/pre-check.js:3”implement user-agent check login” (always returns compatible)

Mobile App REST API (esign_oss)

App Lifecycle

EndpointMethodAuthPurpose
/api/app/registerPOSTNoneRegister device (IMEI, OS, FCM token) → returns sessionToken
/api/app/associatePOSTBearerAssociate device with SMS token
/api/app/remove-accountPOSTBearerRemove account from device
/api/app/update-refresh-tokenPOSTBearerUpdate FCM push token

Customer

EndpointMethodPurpose
/api/customer/check-usernamePOSTValidate username (too_short/too_long/unavailable)
/api/customer/check-passwordPOSTValidate password (too_short/too_long/pattern_mismatch)
/api/customer/createPOSTCreate customer account
/api/customer/loginPOSTLogin with username+password
/api/customer/2f-authPOST2FA auth (signatureId + username + password + smsToken)

Signature

EndpointMethodPurpose
/api/signature/m1POSTM1 message: publicKey + signedData + ts1 + offerId
/api/signature/m2POSTM2 message: ts3Data + signedTs3 + ts3

Documents & Contracts

EndpointMethodPurpose
/api/document/{id}GETDownload document (octet-stream)
/api/document/{id}/mark-as-readPOSTMark document as read
/api/contract/{id}GETGet contract with documents list
/api/contract/{id}/acknowledgeGETAcknowledge contract
/api/contractsGETList all contracts
/api/offer/{id}GETGet offer with documents list
/api/offer/{id}/refusePOSTRefuse offer
/api/offersGETList all offers
/api/timestamp/POSTRequest trusted timestamp (digest + hashAlgorithm + dataSize)

Cryptographic Signing Protocol (M1/M2)

Customer Onboarding Flow

  1. App registers device → receives session token
  2. Customer enters SMS verification token to associate device
  3. Customer creates username/password
  4. Operator approves registration → push notification sent
  5. Customer gains access to offers/contracts

M1 Message (Key Registration)

rawData = ByteArray([publicKey, userName])
signedData = sign(rawData, privateKey)       // RSA-2048
ts1 = getTimestamp(ByteArray([signedData, rawData]))
→ Send: { publicKey, signedData, ts1, offerId }

Server stores M1 values, generates 2FA SMS token.

M2 Message (Signature with 2FA)

offerHashes = offers.map(pdf → hash(pdf))
ts2Data = [publicKey, offerHashes, username, smsToken, deviceInfo, photo]
ts2 = getTimestamp(ts2Data)

customerSignature = [publicKey, offerHashes, username, smsToken, deviceInfo, photo, ts2]
customerSignatureHash = hash(customerSignature)

signedTs3 = sign(ByteArray([customerSignatureHash, customerSignature]), privateKey)
ts3 = getTimestamp(ByteArray([customerSignatureHash, customerSignature, signedTs3]))
→ Send: { version, offers, publicKey, userName, smsToken, deviceInfo, photo,
          customerSignatureHash, ts3Data, signedTs3Data, ts2, ts3 }

Server Verification (Phase 4)

  1. Verify SIGNATURE1 (M1) using stored public key
  2. Verify SIGNATURE2 (M2) using stored public key
  3. Validate offer hashes match server-stored offers
  4. Validate publicKey, username, smsToken match stored values
  5. Re-calculate customerSignatureHash
  6. Verify timestamp ordering: TS1 < TS2 < TS3
  7. Verify TS3 - TS1 < 10 minutes

Post-Signing (Phase 5)

  1. Server generates signed PDF with customer signature hash
  2. Bank signs the PDF (SIGNED_CONTRACT)
  3. Server timestamps the contract
  4. Customer receives push notification, downloads signed contract
  5. App acknowledges download to server