Go to file
2018-03-27 23:07:46 +02:00
config Initial commit 2018-03-27 23:07:46 +02:00
sql Initial commit 2018-03-27 23:07:46 +02:00
src Initial commit 2018-03-27 23:07:46 +02:00
.eslintrc Initial commit 2018-03-27 23:07:46 +02:00
.gitignore Initial commit 2018-03-27 23:07:46 +02:00
index.js Initial commit 2018-03-27 23:07:46 +02:00
license-server.njsproj Initial commit 2018-03-27 23:07:46 +02:00
license-server.sln Initial commit 2018-03-27 23:07:46 +02:00
package-lock.json Initial commit 2018-03-27 23:07:46 +02:00
package.json Initial commit 2018-03-27 23:07:46 +02:00
README.md Initial commit 2018-03-27 23:07:46 +02:00

License server

This is reference implementation of license server that allows per-machine software licensing while it also manages software and data updates for the client machines. The protocol is described below.

Preactivation request

When application is run for first time it is not activated. First it sends its system params (Fletcher64 hashes) to the server to check if the computer has not been preactivated

POST /activate0 HTTP/1.1

Example request body:

{
  "appId": "coc",
  "systemParams": {
    "biosSerialNum": "8690a8fb436070a9",
    "computerUUID": "13cfc3b6f8f7fdd2",
    "diskSerialNum": "63a58b9728485155",
    "nicMac": "4b2856a1e9e8f43e",
    "osId": "ec4fe2f3023d1f21"
  }
}

The server checks if the system is preactivated by checking if any of the system parameters exists in PreactivationParams table. For example for license XXXX-YYYY there can be single entry for biosSerialNum parameters which matches the system of the requestor. Therefore the activation will happen using the license number XXXX-YYYY and the system will response with license file that the client system should store locally. We will describe the structure later.

Example Response body:

{
  "success": true,
  "licenseFile": "fG2HUWhE5kT10Ono3qgO/pzHNU1KAnUlEszz3B1pP5FdSQlIukg3P3xUgfHDQX1OBuAFH68WeXe2T0YP1dbjSsAyDJfpUltJnncwMMLOkfR3YbvyAVNmScgASLWwyQxQAVID6GOQ2weVNo3tAcbj2Ted7rx0HL36seSzyY5xuV/SnfJN6q5acqyJmibQcsrPQLZBvIb00Cy9KENnkVFVH70kC406pKxVZ3Ghqg8vTxgBK6sdbwqd7XrpupcQ4frwwm/NPesCIBYRG+6C/9oMroQoPZ+NEzOYQq2PADOZgOSvaMp4FUNe30IqNckobQO7N2TmW4BJDKVzhG5OYQRfs+xvzG0lF5gOBtmBhDf3yStgJj++jc2pNwe0rDSP2J+Yjs+V8wepyuCJ08cfZOh1feqnzmAarzAD80W8wRFd9U2lHDsyFt7Ke/3aPX454jxqITn/Wu6MK0paX8YAATWWAONyUsWTdA3UkJm45gNHJibcQcZLwKErZEPr2XBNy8nrImHRGFFtr3KC"
}

If the Preactivation is not available for given client, the system will response with { success: false }

Activation request

If the system is not preactivated the client application should ask use to enter the license key (Or the license key should be somehow received by the client system. The license key is 24 characters in base32 (RFC 4648) format. For simplicity it can be sent to user as e.g. T3HZ-IFAT-HLN5-2I57-HAGL-V24R which is easy to read and enter manually. Dashes in between the characters are ignored.

The client is supposed to send JSON request similar as in preactivation with just one parameter added licenseNumber which should be uppercase without dashes so the server can directly query the database.

Example

POST /activate HTTP/1.1
Host: localhost:3000
Accept: application/json
Content-Type: application/json
Content-Length: 219
Charsets: utf-8

Request data:

{
  "appId": "coc",
  "systemParams": {
    "biosSerialNum": "8690a8fb436070a9",
    "computerUUID": "13cfc3b6f8f7fdd2",
    "diskSerialNum": "63a58b9728485155",
    "nicMac": "4b2856a1e9e8f43e",
    "osId": "ec4fe2f3023d1f21"
  },
  "licenseNumber": "JK33BTBSBKSKV63YEVLMQMBZ"
}

Response:

{
  "success": true,
  "licenseFile": "CmiN19MDaeCtA4L+cqhrnL71GWjBqTA6cqP8pUyfwRY+r/s/CyODkgHnO9eg3yaLLNxnNFMOnZOtxgtz8hNMNUIsTAKzus068sz9dJhV2yLrmkvhi1KjEJdOua4ZXuKSzjGKxM+VFXokFfFTqxvVpPt5sMkwq9kG/cZSwpBw7POhR+ncHeF11jjkbKVUnVgjGq8EDHDQFANYAVB3qbo7PY9CG3Gm25nORMUMpqwKieadVmklBZYs09EUqqwxAxxpD44Hw2DaRwoaVMuKTC//wH+3oS3zoL2mx+panJ/HPCN7ZtdBje+v6HSlfUoHgCHpFrr0+9/YqvxAQBWz0Q8dSyzyHkuzEkb4Ob7uWSeVhmKJ1TfzX8CAchth9f4CLCuLdzmCpRVvIAM6ZS4o0t3n/3AAJAmBuKFr9OrHTnN1EgXUYB1TVjiHqvBXR1jEyvYyvkAC4CTel7/Y1LRK2y6mq5is/uLcuKyKpWItMr5p9/3qJAove7tZpT1KrNLYaIPGudIwcnYin8EBAK09uUrqRg=="
}

Check for updates

Another functionality that server supports is check for updates. Client should send versions of its application modules (e.g. application, data) together with its activation ID, system parameters. Server checks whether the system with given activation ID and system parameters is still valid and if yes it looks for updates of the licensed modules. The typical request should look like this:

{
  "systemParams": {
    "biosSerialNum": "8690a8fb436070a9",
    "computerUUID": "13cfc3b6f8f7fdd2",
    "diskSerialNum": "63a58b9728485155",
    "nicMac": "4b2856a1e9e8f43e",
    "osId": "ec4fe2f3023d1f21"
  },
  "activationId": "f0d68f64-4bc5-33b0-6ab3-e9b446baea08",
  "moduleVersions": {
    "coc-testdata": 2
  }
}

The response contains success: true if the system is still activated with supplied activation number and system parameters.

If the number of licensed modules has changed since the activation, the server can reactivate the installation and return back also new licenseFile.

If there are newer versions available there should be list of updates attached. Here is the explanation of update parameters that are not obvious:

  • flag - bit flag (bit 1: signals whether it is incremental update, bit 2: signals whether restart is required after update)
  • checksum - SHA256 checksum of the zip file
{
  "success": true,
  "moduleUpdates": [
    {
      "moduleId": "coc-testdata",
      "version": 3,
      "flag": 0,
      "checksum": "6c878854d349752eceb0d52658e8838c2ae3cca53962c942a276e8944da25731",
      "updateUri": "http://localhost:3000/static/testsite-v3.zip",
      "instPath": "data"
    },
    {
      "moduleId": "coc-testdata",
      "version": 4,
      "flag": 1,
      "checksum": "a515353daae35dc1b3e9e06e52b95a53690984cc3172bb4e6b44c6b516afa040",
      "updateUri": "http://localhost:3000/static/testsite-v4-incremental.zip",
      "instPath": "data"
    }
  ]
}

License file

License file is zlib deflated and AES256-GCM encrypted of following structure

{
  "data": "{\"activationId\":\"e8cb99bc-9827-5c3a-b944-94ac97c81366\",\"appId\":\"coc\",\"systemParams\":{\"biosSerialNum\":\"8690a8fb436070a9\",\"computerUUID\":\"13cfc3b6f8f7fdd2\",\"diskSerialNum\":\"63a58b9728485155\",\"nicMac\":\"4b2856a1e9e8f43e\",\"osId\":\"ec4fe2f3023d1f21\"},\"licensedModules\":[\"coc-engine\",\"coc-testdata\"],\"nonce\":\"pSvFZJ8q3tqBzp0pLEmCSg==\"}",
  "signature": "304c02240163a4b1a6e9a366672df1a17418ab44fb3471fefc4234b4220de8079d0a59ee3c05ce3502240346e0d66d388e3f6cef1cffe14c6be6930a858c72190359fa57aa755c5767b3688d9d88"
}

The signature is ECDSA+SHA256 signature of the serialized data node. The data node extracts further below:

{
  "activationId": "e8cb99bc-9827-5c3a-b944-94ac97c81366",
  "appId": "coc",
  "systemParams": {
    "biosSerialNum": "8690a8fb436070a9",
    "computerUUID": "13cfc3b6f8f7fdd2",
    "diskSerialNum": "63a58b9728485155",
    "nicMac": "4b2856a1e9e8f43e",
    "osId": "ec4fe2f3023d1f21"
  },
  "licensedModules": [
    "coc-engine",
    "coc-testdata"
  ]
}

As you can see the license file specifies activation number with all system parameters the license is activated to. It also lists modules that are licensed for the given system. Client system should save the license file received from the server localy and should upon every start check whether the system parameters are the same as specified in the license file. The license file is protected from tempering by ECDSA (public/private key cryptography) signature while the client only knows the public key of the server.

Data model