├── .gitignore ├── README.md ├── lib └── licepy │ ├── __init__.py │ ├── __main__.py │ ├── _cart.py │ └── _info.py ├── setup.cfg ├── setup.py └── tests └── __init__.py /.gitignore: -------------------------------------------------------------------------------- 1 | /dist 2 | 3 | *.pyc 4 | *.sublime-* 5 | *.egg* 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 工具包合集 2 | - 3 | 4 | ##### 1.Licenses tools help 5 | 6 | #### 安装 (Installation): 7 | 8 | python setup.py install 9 | 10 | #### 使用 (Usage): 11 | 12 | 1. 查看帮助 (Help command): 13 | 14 | python -m licepy -h 15 | 16 | 2. 创建证书 (Creating a Certificate): 17 | 18 | 创建证书和私钥 (To Create a new Certificate **and** Private Key): 19 | 20 | python -m licepy cart certificate.pem CN=T2,O=T2cloud -newkey --issuer-key private-key.pem - 21 | 22 | 创建证书指定私钥 (To Create a Certificate but use an *existing* Private Key): 23 | 24 | python -m licepy cart certificate.pem CN=T2,O=T2cloud --issuer-key private-key.pem - 25 | 26 | 27 | 28 | 29 | 3. 申请licenses授权 (Apply for licenses): 30 | 31 | python -m licepy issue license.key not_before=2017-01-01T00:00:00,not_after=2026-01-01T00:00:00 --issuer-certificate certificate.pem --issuer-key private-key.pem - --license-file-password - --digest digest.txt 32 | 33 | 4. 查看licenses文件的授权信息 (Look at the license info): 34 | 35 | python -m licepy show license.key --issuer-certificate certificate.pem --license-file-password - 36 | -------------------------------------------------------------------------------- /lib/licepy/__init__.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | 3 | from xml.etree.ElementTree import tostring as _tostring 4 | from xml.etree.ElementTree import fromstring 5 | 6 | import sys 7 | 8 | if sys.version_info.major > 2: 9 | def tostring(e): 10 | return str(_tostring(e), 'ascii') 11 | else: 12 | tostring = _tostring 13 | 14 | from ._info import * 15 | from ._cart import createKeyPair, createCertRequest, createCertificate 16 | -------------------------------------------------------------------------------- /lib/licepy/__main__.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | import argparse 3 | import getpass 4 | import sys 5 | 6 | import cryptography.hazmat.primitives.serialization 7 | import cryptography.x509 8 | 9 | from cryptography.hazmat import backends 10 | 11 | from truepy import License, LicenseData 12 | from OpenSSL import crypto 13 | 14 | from . import createKeyPair, createCertRequest, createCertificate 15 | 16 | 17 | def main(action, action_arguments, **args): 18 | try: 19 | action(*action_arguments, **args) 20 | except TypeError: 21 | raise RuntimeError( 22 | '%s requires additional arguments', 23 | action.__name__) 24 | 25 | return 0 26 | 27 | 28 | ACTIONS = {} 29 | 30 | 31 | def action(f): 32 | ACTIONS[f.__name__] = f 33 | return f 34 | 35 | 36 | @action 37 | def show(license_file, issuer_certificate, license_file_password, **args): 38 | """show [license file] 39 | Verifies the signature of a license file and shows information about it. 40 | You must specify the issuer certificate as --issuer-certificate on the 41 | command line, and the license file password as --license-file-password. 42 | """ 43 | with open(license_file, 'rb') as f: 44 | try: 45 | license = License.load(f, license_file_password) 46 | except Exception as e: 47 | raise RuntimeError('Failed to load license file: %s', e) 48 | 49 | try: 50 | license.verify(issuer_certificate) 51 | except Exception as e: 52 | raise RuntimeError('Failed to verify license: %s', e) 53 | 54 | print('License information') 55 | print('\tissued by:\t"%s"' % str(license.data.issuer)) 56 | print('\tissued to:\t"%s"' % str(license.data.holder)) 57 | print('\tvalid from:\t%s' % str(license.data.not_before)) 58 | print('\tvalid to:\t%s' % str(license.data.not_after)) 59 | print('\tsubject:\t%s' % ( 60 | '"%s"' % license.data.subject 61 | if license.data.subject 62 | else '')) 63 | print('\tconsumer_type:\t%s' % ( 64 | '"%s"' % license.data.consumer_type 65 | if license.data.consumer_type 66 | else '')) 67 | print('\tinformation:\t%s' % ( 68 | '"%s"' % license.data.info 69 | if license.data.info 70 | else '')) 71 | print('\textra data:\t%s' % ( 72 | '"%s"' % license.data.extra 73 | if license.data.extra 74 | else '')) 75 | 76 | 77 | @action 78 | def cart(cart_file, cart_description, issuer_key, **args): 79 | """cart [file name] [cart description] 80 | Issues a new cart. You must specify the 81 | issuer private key as --issuer-key on the command line, 82 | or the new private key as -newkey. 83 | 84 | [cart description] must be one command line argument on the form 85 | CN=T2,O=T2cloud,... containing cart data fields. 86 | 87 | - The cart description of the subject of the request, possible 88 | arguments are: 89 | C - Country name 90 | ST - State or province name 91 | L - Locality name 92 | O - Organization name 93 | OU - Organizational unit name 94 | CN - Common name 95 | emailAddress - E-mail address 96 | """ 97 | try: 98 | cart_data_parameters = dict( 99 | (p.strip() for p in i.split('=', 1)) 100 | for i in cart_description.split(',')) 101 | except Exception as e: 102 | raise RuntimeError( 103 | 'Invalid cart data description (%s): %s', 104 | cart_data_parameters, 105 | e) 106 | 107 | careq = createCertRequest(issuer_key, **cart_data_parameters) 108 | 109 | # Default CA certificate is valid for twenty years. 110 | cacert = createCertificate(careq, (careq, issuer_key), 0, (0, 60 * 60 * 24 * 365 * 20)) 111 | 112 | with open(cart_file, 'w') as ca: 113 | ca.write( 114 | crypto.dump_certificate(crypto.FILETYPE_PEM, cacert).decode('utf-8') 115 | ) 116 | print('Creating Certificate Authority certificate in "%s"' % cart_file) 117 | 118 | 119 | @action 120 | def issue(license_file, license_description, issuer_certificate, issuer_key, 121 | license_file_password, **args): 122 | """issue [license file] [digest] [license description] 123 | Issues a new license and shows information about it. You must specify the 124 | issuer certificate and key as --issuer-certificate/key on the command line, 125 | and the license file password as --license-file-password. 126 | 127 | [digest] The environment digest file. 128 | 129 | [license description] must be one command line argument on the form 130 | not_before=2014-01-01T00:00:00,not_after=2016-01-01T00:00:00,... containing 131 | license data fields. 132 | 133 | - The license of the subject of the request, possible 134 | arguments are: 135 | node - Authorization Number of nodes, default 10000 136 | not_before - The timestamp when this license starts to be valid. 137 | not_after - The timestamp when this license ceases to be valid. 138 | This must be strictly after `not_before`. 139 | issued - The timestamp when this license was issued. This 140 | defaults to not_before. 141 | issuer - The issuer of this certificate. If not specified, 142 | UNKNOWN_NAME will be used. 143 | holder - The holder of this certificate. If not specified, 144 | UNKNOWN_NAME will be used. 145 | subject - Free-form string data to associate with the 146 | license. This value will be stringified. 147 | consumer_type - Free-form string data to associate with the 148 | license. This value will be stringified. 149 | info - Free-form string data to associate with the 150 | license. This value will be stringified. 151 | extra - Any type of data to store in the license. If this 152 | is not a string, it will be JSON serialised. 153 | 154 | 155 | """ 156 | try: 157 | license_data_parameters = dict( 158 | (p.strip() for p in i.split('=', 1)) 159 | for i in license_description.split(',')) 160 | except Exception as e: 161 | raise RuntimeError( 162 | 'Invalid license data description (%s): %s', 163 | license_description, 164 | e) 165 | 166 | extra = {'node': license_data_parameters.pop('node', 10000), 167 | 'digests': args.get('digest'), 168 | 'other_extra': license_data_parameters.get('extra')} 169 | license_data_parameters['extra'] = extra 170 | 171 | try: 172 | license_data = LicenseData(**license_data_parameters) 173 | except TypeError as e: 174 | raise RuntimeError( 175 | 'Incomplete license data description (%s): %s', 176 | license_description, 177 | e) 178 | 179 | license = License.issue(issuer_certificate, issuer_key, 180 | license_data=license_data) 181 | with open(license_file, 'wb') as f: 182 | license.store(f, license_file_password) 183 | 184 | show(license_file, issuer_certificate, license_file_password) 185 | 186 | 187 | class PasswordAction(argparse.Action): 188 | def __call__(self, parser, namespace, value, option_string=None): 189 | password = value[-1] if isinstance(value, list) else value 190 | destination = ' '.join( 191 | s 192 | for s in self.dest.split('_') 193 | if not s == 'password') 194 | if password == '-': 195 | password = getpass.getpass( 196 | 'Please enter password for %s:' % destination) 197 | 198 | if namespace.newkey: 199 | setattr(namespace, self.dest, self.generate_key( 200 | value[:-1] if isinstance(value, list) else [value], password)) 201 | else: 202 | setattr(namespace, self.dest, self.get_value( 203 | value[:-1] if isinstance(value, list) else [value], password)) 204 | 205 | def get_value(self, value, password): 206 | return password 207 | 208 | def generate_key(self, value, password): 209 | return password 210 | 211 | 212 | class CertificateAction(argparse.Action): 213 | def __call__(self, parser, namespace, value, option_string=None): 214 | with open(value, 'rb') as f: 215 | data = f.read() 216 | certificate = None 217 | for file_type in ( 218 | 'pem', 219 | 'der'): 220 | try: 221 | loader = getattr( 222 | cryptography.x509, 223 | 'load_%s_x509_certificate' % file_type) 224 | certificate = loader(data, backends.default_backend()) 225 | break 226 | except: 227 | pass 228 | if certificate is None: 229 | raise argparse.ArgumentError( 230 | self, 231 | 'Failed to load certificate') 232 | else: 233 | setattr(namespace, self.dest, certificate) 234 | 235 | 236 | class KeyAction(PasswordAction): 237 | def get_value(self, value, password): 238 | with open(value[0], 'rb') as f: 239 | data = f.read() 240 | try: 241 | loader = getattr( 242 | cryptography.hazmat.primitives.serialization, 243 | 'load_pem_private_key') 244 | return loader(data, password, backends.default_backend()) 245 | except: 246 | pass 247 | raise argparse.ArgumentError( 248 | self, 249 | 'Failed to load key') 250 | 251 | def generate_key(self, value, password): 252 | cakey = createKeyPair(crypto.TYPE_RSA, 4096) 253 | with open(value[0], 'w') as capkey: 254 | capkey.write( 255 | crypto.dump_privatekey(crypto.FILETYPE_PEM, 256 | cakey, 257 | cipher='AES-256-CFB', 258 | passphrase=password).decode('utf-8') 259 | ) 260 | print('Creating Certificate Authority private key in "%s"' % value[0]) 261 | print("\033[0;31;40m WARNING (PLEASE SAFEKEEPING): Once you have a certificate and" 262 | " a private key, you can start issuing licepy. \033[0m") 263 | return cakey 264 | 265 | 266 | class ActionAction(argparse.Action): 267 | def __call__(self, parser, namespace, value, option_string=None): 268 | try: 269 | action = ACTIONS[value[0]] 270 | except KeyError: 271 | raise argparse.ArgumentError( 272 | self, 273 | 'Unknown action') 274 | setattr(namespace, self.dest, action) 275 | 276 | 277 | class DigestAction(argparse.Action): 278 | def __call__(self, parser, namespace, value, option_string=None): 279 | with open(value, 'r') as f: 280 | digests = [] 281 | for line in f.readlines(): 282 | digests.append(line.strip('\n')) 283 | 284 | if not digests: 285 | raise argparse.ArgumentError( 286 | self, 287 | 'Failed to load certificate') 288 | else: 289 | setattr(namespace, self.dest, digests) 290 | 291 | 292 | parser = argparse.ArgumentParser( 293 | prog='licepy', 294 | description='Creates and verifies TrueLicense version 1 licenses', 295 | formatter_class=argparse.RawDescriptionHelpFormatter, 296 | epilog='Actions\n=======\n%s' % ( 297 | '\n\n'.join(action.__doc__ for action in ACTIONS.values()))) 298 | 299 | parser.add_argument( 300 | '--issuer-certificate', 301 | help='The issuer certificate.', 302 | action=CertificateAction) 303 | 304 | parser.add_argument( 305 | '--issuer-key', 306 | help='The private key to the certificate and the password; pass "-" as ' 307 | 'password to read it from stdin.', 308 | nargs=2, 309 | const=None, 310 | action=KeyAction) 311 | 312 | parser.add_argument( 313 | '--license-file-password', 314 | help='The password of the license file; pass "-" to read from stdin.', 315 | const=None, 316 | action=PasswordAction) 317 | 318 | parser.add_argument( 319 | '--verbose', 320 | help='Show a stack trace on error.', 321 | action='store_true') 322 | 323 | parser.add_argument( 324 | '--digest', 325 | help='The environment digest.', 326 | action=DigestAction) 327 | 328 | parser.add_argument( 329 | '-newkey', 330 | help='new a private key.', 331 | default=False, 332 | action='store_true') 333 | 334 | parser.add_argument( 335 | 'action', 336 | help='The action to perform; this can be any of %s' % ', '.join( 337 | ACTIONS.keys()), 338 | nargs=1, 339 | action=ActionAction) 340 | 341 | parser.add_argument( 342 | 'action_arguments', 343 | help='Arguments to the action. See below for more information.', 344 | nargs='*', 345 | default=[]) 346 | 347 | try: 348 | namespace = parser.parse_args() 349 | sys.exit(main(**vars(namespace))) 350 | except Exception as e: 351 | try: 352 | sys.stderr.write('%s\n' % e.args[0] % e.args[1:]) 353 | except: 354 | sys.stderr.write('%s\n' % str(e)) 355 | if namespace and namespace.verbose: 356 | import traceback 357 | 358 | traceback.print_exc() 359 | sys.exit(1) 360 | -------------------------------------------------------------------------------- /lib/licepy/_cart.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | from OpenSSL import crypto 3 | 4 | 5 | def createKeyPair(type, bits): 6 | """ 7 | Create a public/private key pair. 8 | Arguments: type - Key type, must be one of TYPE_RSA and TYPE_DSA 9 | bits - Number of bits to use in the key 10 | Returns: The public/private key pair in a PKey object 11 | """ 12 | pkey = crypto.PKey() 13 | pkey.generate_key(type, bits) 14 | return pkey 15 | 16 | 17 | def createCertRequest(pkey, digest="sha256", **name): 18 | """ 19 | Create a certificate request. 20 | Arguments: pkey - The key to associate with the request 21 | digest - Digestion method to use for signing, default is sha256 22 | **name - The name of the subject of the request, possible 23 | arguments are: 24 | C - Country name 25 | ST - State or province name 26 | L - Locality name 27 | O - Organization name 28 | OU - Organizational unit name 29 | CN - Common name 30 | emailAddress - E-mail address 31 | Returns: The certificate request in an X509Req object 32 | """ 33 | req = crypto.X509Req() 34 | subj = req.get_subject() 35 | 36 | for key, value in name.items(): 37 | setattr(subj, key, value) 38 | 39 | req.set_pubkey(pkey) 40 | req.sign(pkey, digest) 41 | return req 42 | 43 | 44 | def createCertificate(req, issuerCertKey, serial, validityPeriod, 45 | digest="sha256"): 46 | """ 47 | Generate a certificate given a certificate request. 48 | Arguments: req - Certificate request to use 49 | issuerCert - The certificate of the issuer 50 | issuerKey - The private key of the issuer 51 | serial - Serial number for the certificate 52 | notBefore - Timestamp (relative to now) when the certificate 53 | starts being valid 54 | notAfter - Timestamp (relative to now) when the certificate 55 | stops being valid 56 | digest - Digest method to use for signing, default is sha256 57 | Returns: The signed certificate in an X509 object 58 | """ 59 | issuerCert, issuerKey = issuerCertKey 60 | notBefore, notAfter = validityPeriod 61 | cert = crypto.X509() 62 | cert.set_serial_number(serial) 63 | cert.gmtime_adj_notBefore(notBefore) 64 | cert.gmtime_adj_notAfter(notAfter) 65 | cert.set_issuer(issuerCert.get_subject()) 66 | cert.set_subject(req.get_subject()) 67 | cert.set_pubkey(req.get_pubkey()) 68 | cert.sign(issuerKey, digest) 69 | return cert 70 | -------------------------------------------------------------------------------- /lib/licepy/_info.py: -------------------------------------------------------------------------------- 1 | # coding: utf8 2 | 3 | __author__ = u'jingh' 4 | __version__ = (1, 0, 0) 5 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [build_sphinx] 2 | source-dir = docs 3 | build-dir = build/docs 4 | all-files = 1 5 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # coding: utf8 3 | 4 | import os 5 | import setuptools 6 | import sys 7 | 8 | sys.path.append(os.path.dirname(__file__)) 9 | sys.path.append(os.path.join(os.path.dirname(__file__), 'lib')) 10 | 11 | # The directories in which the packages can be found 12 | PACKAGE_DIR = { 13 | 'licepy': 'lib/licepy'} 14 | 15 | REQUIREMENTS = [ 16 | 'cryptography ==2.1.4', 17 | 'truepy ==2.0.2', 18 | 'pyOpenSSL ==17.5.0'] 19 | 20 | 21 | def setup(**kwargs): 22 | global INFO, README, CHANGES, PACKAGE_DATA, PACKAGE_DIR 23 | setuptools.setup( 24 | name='licepy', 25 | version='.'.join(str(i) for i in INFO['version']), 26 | description='A Python library to create TrueLicense license files.', 27 | long_description=README + '\n\n' + CHANGES, 28 | 29 | install_requires=REQUIREMENTS, 30 | setup_requires=REQUIREMENTS, 31 | 32 | packages=setuptools.find_packages( 33 | os.path.join( 34 | os.path.dirname(__file__), 35 | 'lib')), 36 | package_dir=PACKAGE_DIR, 37 | zip_safe=True, 38 | 39 | test_suite='tests', 40 | 41 | license='GPLv3', 42 | platforms=['linux', 'windows'], 43 | classifiers=[], 44 | 45 | **kwargs) 46 | 47 | 48 | # Read globals from licepy._info without loading it 49 | INFO = {} 50 | with open(os.path.join( 51 | os.path.dirname(__file__), 52 | 'lib', 53 | 'licepy', 54 | '_info.py')) as f: 55 | for line in f: 56 | try: 57 | name, value = (i.strip() for i in line.split('=')) 58 | if name.startswith('__') and name.endswith('__'): 59 | INFO[name[2:-2]] = eval(value) 60 | except ValueError: 61 | pass 62 | 63 | try: 64 | # Read README 65 | with open(os.path.join( 66 | os.path.dirname(__file__), 67 | 'README.rst')) as f: 68 | README = f.read() 69 | 70 | # Read CHANGES 71 | with open(os.path.join( 72 | os.path.dirname(__file__), 73 | 'CHANGES.rst')) as f: 74 | CHANGES = f.read() 75 | except IOError: 76 | README = '' 77 | CHANGES = '' 78 | 79 | # Arguments passed to setup 80 | setup_arguments = {} 81 | 82 | setup(**setup_arguments) 83 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/knitmesh/licepy/f706e70fde59ee412830eac138f214f36f354fcf/tests/__init__.py --------------------------------------------------------------------------------