Skip to content

Commit 536e551

Browse files
authored
Add GetMachineCode method (#5)
* Add GetMachineCode method * Add IsOnRightMachine method * Update package info * Update docs
1 parent 72323e4 commit 536e551

File tree

6 files changed

+106
-31
lines changed

6 files changed

+106
-31
lines changed

README.md

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,8 @@ The code below will work exactly as the one explained in the [key verification t
1717
First, we need to add the namespaces:
1818

1919
```python
20-
from licensing.helpers import Helpers
21-
from licensing.models import Response, RSAPublicKey
22-
from licensing.methods import Key
20+
from licensing.models import *
21+
from licensing.methods import Key, Helpers
2322
```
2423

2524
Now we can perform the actual key verification:
@@ -29,10 +28,10 @@ pubKey = "<RSAKeyValue><Modulus>sGbvxwdlDbqFXOMlVUnAF5ew0t0WpPW7rFpI5jHQOFkht/32
2928

3029
res = Key.activate(token="WyIyNTU1IiwiRjdZZTB4RmtuTVcrQlNqcSszbmFMMHB3aWFJTlBsWW1Mbm9raVFyRyJd",\
3130
rsa_pub_key=pubKey,\
32-
product_id=3349, key="ICVLD-VVSZR-ZTICT-YKGXL", machine_code="test")
31+
product_id=3349, key="ICVLD-VVSZR-ZTICT-YKGXL", machine_code=Helpers.GetMachineCode())
3332

34-
if res[0] == None:
35-
print("An error occured: {0}".format(res[1]))
33+
if res[0] == None not Helpers.IsOnRightMachine(res[0]):
34+
print("An error occurred: {0}".format(res[1]))
3635
else:
3736
print("Success")
3837

@@ -45,7 +44,7 @@ else:
4544
* `token` - the access token (can be found [here](https://app.cryptolens.io/docs/api/v3/QuickStart#api-keys), in *API Keys* section).
4645
* `product_id` - the id of the product can be found on the product page.
4746
* `key` - the license key to be verified
48-
* `machine_code` - the unique id of the device (we are working on adding a method similar to `Helpers.GetMachineCode()`).
47+
* `machine_code` - the unique id of the device.
4948

5049
### Offline activation (saving/loading licenses)
5150

@@ -66,6 +65,9 @@ When loading it back, we can use the code below:
6665
with open('licensefile.skm', 'r') as f:
6766
license_key = LicenseKey.load_from_string(pubKey, f.read())
6867

69-
print("Feature 1: " + str(license_key.f1))
70-
print("License expires: " + str(license_key.expires))
68+
if not Helpers.IsOnRightMachine(license_key):
69+
print("NOTE: This license file does not belong to this machine.")
70+
else:
71+
print("Feature 1: " + str(license_key.f1))
72+
print("License expires: " + str(license_key.expires))
7173
```

licensing/internal.py

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,17 +9,25 @@
99
from Crypto.Hash import SHA256
1010
from Crypto.PublicKey import RSA
1111
import urllib.request
12+
import hashlib
1213

13-
class Helpers:
14+
class HelperMethods:
1415

1516
server_address = "https://app.cryptolens.io/api/"
1617

18+
def get_SHA256(string):
19+
"""
20+
Compute the SHA256 signature of a string.
21+
"""
22+
return hashlib.sha256(string.encode("utf-8")).hexdigest()
23+
24+
1725
def verify_signature(response, rsaPublicKey):
1826
"""
1927
Verifies a signature from .NET RSACryptoServiceProvider.
2028
"""
21-
cryptoPubKey = RSA.construct((Helpers.base642int(rsaPublicKey.modulus),\
22-
Helpers.base642int(rsaPublicKey.exponent)))
29+
cryptoPubKey = RSA.construct((HelperMethods.base642int(rsaPublicKey.modulus),\
30+
HelperMethods.base642int(rsaPublicKey.exponent)))
2331
h = SHA256.new(base64.b64decode(response.license_key.encode("utf-8")))
2432
verifier = PKCS1_v1_5.new(cryptoPubKey)
2533
return verifier.verify(h, base64.b64decode(response.signature.encode("utf-8")))
@@ -38,7 +46,7 @@ def send_request(method, params):
3846
method: the path of the method, eg. key/activate
3947
params: a dictionary of parameters
4048
"""
41-
return urllib.request.urlopen(Helpers.server_address + method, \
49+
return urllib.request.urlopen(HelperMethods.server_address + method, \
4250
urllib.parse.urlencode(params)\
4351
.encode("utf-8")).read().decode("utf-8")
4452

licensing/methods.py

Lines changed: 46 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@
55
@author: Artem Los
66
"""
77

8-
from licensing.internal import Helpers
8+
import platform
9+
import sys
10+
from licensing.internal import HelperMethods
911
from licensing.models import *
1012

1113
class Key:
@@ -14,7 +16,6 @@ class Key:
1416
License key related methods. More docs: https://app.cryptolens.io/docs/api/v3/Key.
1517
"""
1618

17-
1819
def activate(token, rsa_pub_key, product_id, key, machine_code, fields_to_return = 0,\
1920
metadata = False, floating_time_interval = 0,\
2021
max_overdraft = 0):
@@ -30,7 +31,7 @@ def activate(token, rsa_pub_key, product_id, key, machine_code, fields_to_return
3031
response = Response("","","","")
3132

3233
try:
33-
response = Response.from_string(Helpers.send_request("key/activate", {"token":token,\
34+
response = Response.from_string(HelperMethods.send_request("key/activate", {"token":token,\
3435
"ProductId":product_id,\
3536
"key":key,\
3637
"MachineCode":machine_code,\
@@ -49,9 +50,49 @@ def activate(token, rsa_pub_key, product_id, key, machine_code, fields_to_return
4950
return (None, response.message)
5051
else:
5152
try:
52-
if Helpers.verify_signature(response, pubkey):
53+
if HelperMethods.verify_signature(response, pubkey):
5354
return (LicenseKey.from_response(response), response.message)
5455
else:
5556
return (None, "The signature check failed.")
5657
except Exception:
57-
return (None, "The signature check failed.")
58+
return (None, "The signature check failed.")
59+
60+
61+
class Helpers:
62+
63+
def GetMachineCode():
64+
65+
"""
66+
Get a unique identifier for this device.
67+
"""
68+
69+
res = []
70+
res.append(platform.machine())
71+
res.append(platform.machine())
72+
res.append(platform.processor())
73+
res.append(platform.system())
74+
res.append(platform.architecture()[1])
75+
# safer than using architecture()[0]
76+
# see https://docs.python.org/3/library/platform.html#platform.architecture
77+
res.append(str(sys.maxsize > 2**32))
78+
79+
return HelperMethods.get_SHA256(":".join(res))
80+
81+
def IsOnRightMachine(license_key):
82+
83+
"""
84+
Check if the device is registered with the license key.
85+
"""
86+
87+
current_mid = Helpers.GetMachineCode()
88+
89+
if license_key.activated_machines == None:
90+
return False
91+
92+
for act_machine in license_key.activated_machines:
93+
94+
if current_mid == act_machine.Mid:
95+
return True
96+
97+
return False
98+

licensing/models.py

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,15 @@
1010
import datetime
1111
import copy
1212

13-
from licensing.internal import Helpers
13+
from licensing.internal import HelperMethods
14+
15+
class ActivatedMachine:
16+
def __init__(self, IP, Mid, Time):
17+
self.IP = IP
18+
self.Mid = Mid
19+
20+
# TODO: check if time is int, and convert to datetime in this case.
21+
self.Time = Time
1422

1523
class LicenseKey:
1624

@@ -52,11 +60,13 @@ def from_response(response):
5260

5361
obj = json.loads(base64.b64decode(response.license_key).decode('utf-8'))
5462

63+
64+
5565
return LicenseKey(obj["ProductId"], obj["ID"], obj["Key"], datetime.datetime.fromtimestamp(obj["Created"]),\
5666
datetime.datetime.fromtimestamp(obj["Expires"]), obj["Period"], obj["F1"], obj["F2"], \
5767
obj["F3"], obj["F4"],obj["F5"],obj["F6"], obj["F7"], \
5868
obj["F8"], obj["Notes"], obj["Block"], obj["GlobalId"],\
59-
obj["Customer"], obj["ActivatedMachines"], obj["TrialActivation"], \
69+
obj["Customer"], LicenseKey.__load_activated_machines(obj["ActivatedMachines"]), obj["TrialActivation"], \
6070
obj["MaxNoOfMachines"], obj["AllowedMachines"], obj["DataObjects"], \
6171
datetime.datetime.fromtimestamp(obj["SignDate"]), response)
6272

@@ -89,14 +99,24 @@ def load_from_string(rsa_pub_key, string):
8999
else:
90100
try:
91101
pubKey = RSAPublicKey.from_string(rsa_pub_key)
92-
if Helpers.verify_signature(response, pubKey):
102+
if HelperMethods.verify_signature(response, pubKey):
93103
return LicenseKey.from_response(response)
94104
else:
95105
return None
96106
except Exception:
97107
return None
108+
109+
def __load_activated_machines(obj):
110+
111+
if obj == None:
112+
return None
113+
114+
arr = []
98115

116+
for item in obj:
117+
arr.append(ActivatedMachine(**item))
99118

119+
return arr
100120

101121
class Response:
102122

setup.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,13 @@
22
setup(
33
name = 'licensing', # How you named your package folder (MyLib)
44
packages = ['licensing'], # Chose the same as "name"
5-
version = '0.3', # Start with a small number and increase it with every change you make
5+
version = '0.4', # Start with a small number and increase it with every change you make
66
license='MIT', # Chose a license from here: https://help.github.com/articles/licensing-a-repository
77
description = 'Client library for Cryptolens licensing Web API.', # Give a short description about your library
88
author = 'Cryptolens AB', # Type in your name
99
author_email = '[email protected]', # Type in your E-Mail
1010
url = 'https://cryptolens.io', # Provide either the link to your github or to your website
11-
download_url = 'https://github.com/Cryptolens/cryptolens-python/archive/v_03.tar.gz', # I explain this later on
11+
download_url = 'https://github.com/Cryptolens/cryptolens-python/archive/v_04.tar.gz', # I explain this later on
1212
keywords = ['software licensing', 'licensing library', 'cryptolens'], # Keywords that define your package best
1313
install_requires=[ # I get to this in a second
1414
'pycrypto'

test.py

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,16 @@
55
@author: Artem Los
66
"""
77

8-
from licensing.internal import Helpers
9-
from licensing.models import Response, RSAPublicKey, LicenseKey
10-
from licensing.methods import Key
8+
from licensing.models import *
9+
from licensing.methods import Key, Helpers
1110

1211
pubKey = "<RSAKeyValue><Modulus>sGbvxwdlDbqFXOMlVUnAF5ew0t0WpPW7rFpI5jHQOFkht/326dvh7t74RYeMpjy357NljouhpTLA3a6idnn4j6c3jmPWBkjZndGsPL4Bqm+fwE48nKpGPjkj4q/yzT4tHXBTyvaBjA8bVoCTnu+LiC4XEaLZRThGzIn5KQXKCigg6tQRy0GXE13XYFVz/x1mjFbT9/7dS8p85n8BuwlY5JvuBIQkKhuCNFfrUxBWyu87CFnXWjIupCD2VO/GbxaCvzrRjLZjAngLCMtZbYBALksqGPgTUN7ZM24XbPWyLtKPaXF2i4XRR9u6eTj5BfnLbKAU5PIVfjIS+vNYYogteQ==</Modulus><Exponent>AQAB</Exponent></RSAKeyValue>"
1312

1413
res = Key.activate(token="WyIyNjA1IiwiTjhZQUpIYXJPaVdBV0ozQUlKVC9tamdDbFZDRzhZRHpaU243L2R2eCJd",\
1514
rsa_pub_key=pubKey,\
16-
product_id=3349, key="ICVLD-VVSZR-ZTICT-YKGXL", machine_code="test")
15+
product_id=3349, key="ICVLD-VVSZR-ZTICT-YKGXL", machine_code=Helpers.GetMachineCode())
1716

18-
if res[0] == None:
17+
if res[0] == None or not Helpers.IsOnRightMachine(res[0]):
1918
print("An error occured: {0}".format(res[1]))
2019
else:
2120
print("Success")
@@ -35,6 +34,11 @@
3534
with open('licensefile.skm', 'r') as f:
3635
license_key = LicenseKey.load_from_string(pubKey, f.read())
3736

38-
print("Feature 1: " + str(license_key.f1))
39-
print("License expires: " + str(license_key.expires))
40-
37+
if not Helpers.IsOnRightMachine(license_key):
38+
print("NOTE: This license file does not belong to this machine.")
39+
else:
40+
print("Feature 1: " + str(license_key.f1))
41+
print("License expires: " + str(license_key.expires))
42+
43+
print(Helpers.GetMachineCode())
44+

0 commit comments

Comments
 (0)