SecureTicketService is used to create and validate SecureTickets. SecureTickets are light-weight symmetrically signed data sets with a limited lifestpan.
The key passed to SecureTicketService is the password and the security relies heavily on its strength. It really should be a 32 byte random string as you gain integrity AND performance by using a key of 32 bytes length (it’s padded or replaced by SHA256-hashes of itself to make it 32 bytes anyways). For your convenience, classmethod create_random_key() is provided:
>>> key = SecureTicketService.create_random_key()
>>> assert len(key) == 32
>>> sts = SecureTicketService(key)
A SecureTicket ticket which is successfully validated using SecureTicketService.validate_ticket() can only be created by someone who has knowledge of key. The entire contents of ticket is securely hashed using key and any change to ticket breaks the hash validation.
>>> key = 'Io5IpK9ZTsKpG1ybaLCHkOH4kvHaTEO2imHvkqLVn7I='
>>> sts = SecureTicketService(key.decode('base64'))
>>> ticket = sts.create_ticket('someData')
>>> ticket.data
'someData'
>>> sts.validate_ticket(ticket)
True
>>> sts2 = SecureTicketService('someOtherKey')
>>> sts2.validate_ticket(ticket)
False
entropy
The optional second argument entropy to create_ticket(), which must be a string if supplied, cannot be obtained from a ticket; it’s just concatinated together with the rest of ticket when the hash is created. The same entropy value must therefore be used in SecureTicketService.validate_hash() or else validation fails.
>>> ticket = sts.create_ticket('someKey', 'someEntropy')
>>> sts.validate_ticket(ticket)
False
>>> sts.validate_ticket(ticket, 'someEntropy')
True
session
Many use cases for secure tickets involves (or should involve) the concept of a session to prevent various types of attacks. The optional second argument session to SecureTicketService() is used in the same manner as entropy, but is supplied during SecureTicketService instantiation instead of during ticket creation.
>>> sts = SecureTicketService(key, 'someSessionIdentifier')
options
Encryption, serialization and compression of ticket‘s contents is optional. Encrypted tickets will have all its data and metadata encrypted with the key supplied to SecureTicketService. Serialization allows complex data types in data instead of just strings. Compression (zlib) is useful if the data argument is inconveniently large. Options and their default values:
- serialize=False
- encrypt=False
- compress=False
Encrypted ticket attributes must be viewed through a SecureTicketService instance which provide transparent decryption:
>>> key = SecureTicketService.create_random_key()
>>> sts = SecureTicketService(key, serialize=1, compress=1, encrypt=1)
>>> ticket = sts.create_ticket(['asd', 123], 'ee')
>>> assert sts.get_data(ticket) == sts(ticket).data == ['asd', 123]
Some examples:
Create a random key (use outcommented code):
>>> #key = SecureTicketService.create_random_key()
>>> key = 'make doctests work by using static key'
Lets serialize some data and store in a ticket:
>>> sts = SecureTicketService(key, serialize=1)
>>> ticket = sts.create_ticket(['asd', 123, True, None, ['yay']])
>>> assert type(ticket.data) is list
>>> assert ticket.data[0] == 'asd'
>>> assert ticket.data[4][0] == 'yay'
Lets store a huge text payload with and without compression. First we disable serialization which means that only string-type data is allowed.
>>> sts.options.set_serialize(0)
>>> data = 'A' * 10**5 # 100k
>>> large = sts.create_ticket(data)
>>> len(large.data)
100000
>>> len(large.tostring())
100054
>>> small = sts.create_ticket(data, compress=1)
>>> len(small.data)
100000
>>> len(small.tostring())
175
>>> large.data[:5] == small.data[:5] == 'AAAAA'
True
What if we want to give a ticket containing secret information to a third party, without revealing the ticket contents? By default, the data field is in plaintext (albeit protected from manipulation thanks to hashing with our secret key). Concider the simple example below:
>>> plaintext = 'my precious'
>>> ticket = sts.create_ticket(plaintext)
>>> ticket.tostring().find('my precious')
54
If we enable encryption, all information in ticket is encrypted, including metadata, except for the bit flag marking the ticket as encrypted and the ticket length field. Encryption algorithm is AES128-CBC from pyCrypto (which must be installed) and the key used is the key supplied during SecureTicketService instantiation.
Data can still be transparently accessed using a SecureTicketService instance.
>>> sts.options.set_encrypt(1)
>>> ticket = sts.create_ticket(plaintext)
>>> ticket.tostring().find('my precious')
-1
>>> sts(ticket).data
'my precious'
To enable all features upon SecureTicketService instantiation, simply use:
>>> sts = SecureTicketService(key, serialize=1, encrypt=1, compress=1)
Create a key suitable for use in SecureTicketSession.
Create a session variable suitable for use in SecureTicketSession.
Create a SecureTicket containing data. If entropy is specified, the same entropy must be supplied when using validate_ticket(), or else validation will fail.
Options can be used to override SecureTicketService default options.
Get ticket.data and transparently decrypt and gunzip if necessary. Synonymous to sts(ticket).data, with sts being the SecureTicketService.
Get ticket.data_len and transparently decrypt if necessary. Synonymous to sts(ticket).data_len, with sts being the SecureTicketService.
Get ticket.flags and transparently decrypt if necessary. Synonymous to sts(ticket).flags, with sts being the SecureTicketService.
Get ticket.valid_until and transparently decrypt if necessary. Synonymous to sts(ticket).valid_until, with sts being the SecureTicketService.
Validate the integrity of ticket by recreating its hash and verifying that it matches ticket.hash. The validity of ticket‘s lifespan is also verified. The ‘entropy’ argument must match what was used in the call to create_ticket().
Instances of SecureTicket should generally be obtained through SecureTicketService.create_ticket(), alternatively through calls to the classmethods SecureTicket.fromstring() or SecureTicket.frombase64().
FormTicketService provides simple form validation based on SecureTicketService.
Create a form ticket:
>>> key = FormTicketService.create_random_key()
>>> session = 'CurrentUserWebSession'
>>> fts = FormTicketService(key, session)
>>> action = '/some/action'
>>> param_names = ['foo', 'bar']
>>> ticket = fts.create_ticket(action, param_names)
>>> input_params = {'foo':'123'}
Ticket validation is controlled with options. Options (and their default values) include:
- require_form_action=True
- require_param_names=True
- require_param_values=True
- allow_undeclared_param_names=False
If True, require_form_action requires the form_action specified upon ticket creation to match the form_action passed to validate_form.
If True, require_param_names requires all parameter names in both param_names and params passed to create_ticket() to be available in the params argument passed to validate_form().
If True, require_param_values requires param name and value pairs passed in params to create_ticket() to exist in params passed to validate_ticket().
If True, allow_undeclared_param_names allows other param names than what was specified in param_names and params in call to create_ticket() to be passed to validate_form().
If any of these validations fail, one of the following exceptions will be raised, containing a dict which exactly specifies what was passed and which params were invalid:
- InvalidFormActionError
- MissingParamNamesError
- InvalidParamValuesError
- UndeclaredParamNamesError
DiffieHellman implements the Diffie Hellman key exchange algorithm. Variable names in the implementation match those from Diffie-Hellman Key Agreement Method (RFC 2631), but in each method xa and ya are used for the secret and the exposed key parts in self while xb (which is never seen) and yb is the key parts of the other party.
>>> a = DiffieHellman(psize=2048) # prime size defaults to 1536
>>> b = DiffieHellman(psize=2048)
>>> ZZa = a.calc_ZZ(b.ya) # ZZ is the negotiated secret
>>> ZZb = b.calc_ZZ(a.ya)
>>> ZZa == ZZb
True
>>> type(ZZa)
<type 'long'>
>>> strZZ = tickets.crypto.util.long2str(ZZa)
>>> type(strZZ)
<type 'str'>
DiffieHellmanClient and DiffieHellmanServer implements a protocol by which two parties are able to perform a Diffie Hellman key exchange and to verify that the other party has successfully derived the same secret key.
The protocol follows the common Diffie Hellman scheme, but additionally includes generation and validation of SHA256-HMAC digests, using the negotiated key, of some of the negotiation messages. This is in a sense similar to the well known TCP three way handshake.
>>> c = DiffieHellmanClient(asize=256) # asize should be 256 for aes128
>>> s = DiffieHellmanServer() # will adapt to client in 'hello' phase
>>> A = c.client_hello()
>>> B = s.server_hello(A)
>>> C = c.client_verify(B)
>>> s.server_verify(C)
True
>>> c.negotiated_key == s.negotiated_key
True
>>> type(c.negotiated_key)
<type 'str'>