1# -*- coding: utf-8 -*-
2'''
3nacling.py raet protocol nacl (crypto) management classes
4'''
5
6# Import python libs
7import sys
8import time
9import binascii
10import six
11import libnacl
12
13from ioflo.base.consoling import getConsole
14console = getConsole()
15
16# Import raet libs
17# pylint: disable=wildcard-import,unused-wildcard-import,redefined-builtin
18from .abiding import *  # import globals
19# pylint: enable=wildcard-import,unused-wildcard-import,redefined-builtin
20from . import encoding
21
22
23class CryptoError(Exception):
24    """
25    Base exception for all nacl related errors
26    """
27
28
29class BadSignatureError(CryptoError):
30    """
31    Raised when the signature was forged or otherwise corrupt.
32    """
33
34
35class EncryptedMessage(six.binary_type):
36    """
37    A bytes subclass that holds a messaged that has been encrypted by a
38    :class:`SecretBox`.
39    """
40
41    @classmethod
42    def _from_parts(cls, nonce, ciphertext, combined):
43        obj = cls(combined)
44        obj._nonce = nonce
45        obj._ciphertext = ciphertext
46        return obj
47
48    @property
49    def nonce(self):
50        """
51        The nonce used during the encryption of the :class:`EncryptedMessage`.
52        """
53        return self._nonce
54
55    @property
56    def ciphertext(self):
57        """
58        The ciphertext contained within the :class:`EncryptedMessage`.
59        """
60        return self._ciphertext
61
62
63class StringFixer(object):
64    '''
65    Python 3 support
66    '''
67    def __str__(self):
68        if six.PY3:
69            return self.__unicode__()
70        else:
71            return self.__bytes__()
72
73
74class PublicKey(encoding.Encodable, StringFixer, object):
75    """
76    The public key counterpart to an Curve25519 :class:`PrivateKey`
77    for encrypting messages.
78
79    :param public_key: [:class:`bytes`] Encoded Curve25519 public key
80    :param encoder: A class that is able to decode the `public_key`
81
82    :cvar SIZE: The size that the public key is required to be
83    """
84
85    SIZE = libnacl.crypto_box_PUBLICKEYBYTES
86
87    def __init__(self, public_key, encoder=encoding.RawEncoder):
88        self._public_key = encoder.decode(public_key)
89
90        if len(self._public_key) != self.SIZE:
91            raise ValueError("The public key must be exactly %s bytes long" %
92                             self.SIZE)
93
94    def __bytes__(self):
95        return self._public_key
96
97
98class PrivateKey(encoding.Encodable, StringFixer, object):
99    """
100    Private key for decrypting messages using the Curve25519 algorithm.
101
102    .. warning:: This **must** be protected and remain secret. Anyone who
103        knows the value of your :class:`~PrivateKey` can decrypt
104        any message encrypted by the corresponding
105        :class:`~PublicKey`
106
107    :param private_key: The private key used to decrypt messages
108    :param encoder: The encoder class used to decode the given keys
109
110    :cvar SIZE: The size that the private key is required to be
111    """
112
113    SIZE = libnacl.crypto_box_SECRETKEYBYTES
114
115    def __init__(self, private_key, encoder=encoding.RawEncoder):
116        # Decode the secret_key
117        private_key = encoder.decode(private_key)
118
119        # Verify that our seed is the proper size
120        if len(private_key) != self.SIZE:
121            raise ValueError(
122                "The secret key must be exactly %d bytes long" % self.SIZE)
123
124        raw_public_key = libnacl.crypto_scalarmult_base(private_key)
125
126        self._private_key = private_key
127        self.public_key = PublicKey(raw_public_key)
128
129    def __bytes__(self):
130        return self._private_key
131
132    @classmethod
133    def generate(cls):
134        """
135        Generates a random :class:`~PrivateKey` object
136
137        :rtype: :class:`~PrivateKey`
138        """
139        return cls(libnacl.randombytes(PrivateKey.SIZE), encoder=encoding.RawEncoder)
140
141
142class Box(encoding.Encodable, StringFixer, object):
143    """
144    The Box class boxes and unboxes messages between a pair of keys
145
146    The ciphertexts generated by :class:`~Box` include a 16
147    byte authenticator which is checked as part of the decryption. An invalid
148    authenticator will cause the decrypt function to raise an exception. The
149    authenticator is not a signature. Once you've decrypted the message you've
150    demonstrated the ability to create arbitrary valid message, so messages you
151    send are repudiable. For non-repudiable messages, sign them after
152    encryption.
153
154    :param private_key: :class:`~PrivateKey` used to encrypt and
155        decrypt messages
156    :param public_key: :class:`~PublicKey` used to encrypt and
157        decrypt messages
158
159    :cvar NONCE_SIZE: The size that the nonce is required to be.
160    """
161
162    NONCE_SIZE = libnacl.crypto_box_NONCEBYTES
163
164    def __init__(self, private_key, public_key):
165        if private_key and public_key:
166            self._shared_key = libnacl.crypto_box_beforenm(
167                public_key.encode(encoder=encoding.RawEncoder),
168                private_key.encode(encoder=encoding.RawEncoder),
169            )
170        else:
171            self._shared_key = None
172
173    def __bytes__(self):
174        return self._shared_key
175
176    @classmethod
177    def decode(cls, encoded, encoder=encoding.RawEncoder):
178        # Create an empty box
179        box = cls(None, None)
180
181        # Assign our decoded value to the shared key of the box
182        box._shared_key = encoder.decode(encoded)
183
184        return box
185
186    def encrypt(self, plaintext, nonce, encoder=encoding.RawEncoder):
187        """
188        Encrypts the plaintext message using the given `nonce` and returns
189        the ciphertext encoded with the encoder.
190
191        .. warning:: It is **VITALLY** important that the nonce is a nonce,
192            i.e. it is a number used only once for any given key. If you fail
193            to do this, you compromise the privacy of the messages encrypted.
194
195        :param plaintext: [:class:`bytes`] The plaintext message to encrypt
196        :param nonce: [:class:`bytes`] The nonce to use in the encryption
197        :param encoder: The encoder to use to encode the ciphertext
198        :rtype: [:class:`nacl.utils.EncryptedMessage`]
199        """
200        if len(nonce) != self.NONCE_SIZE:
201            raise ValueError("The nonce must be exactly %s bytes long" %
202                             self.NONCE_SIZE)
203
204        ciphertext = libnacl.crypto_box_afternm(
205            plaintext,
206            nonce,
207            self._shared_key,
208        )
209
210        encoded_nonce = encoder.encode(nonce)
211        encoded_ciphertext = encoder.encode(ciphertext)
212
213        return EncryptedMessage._from_parts(
214            encoded_nonce,
215            encoded_ciphertext,
216            encoder.encode(nonce + ciphertext),
217        )
218
219    def decrypt(self, ciphertext, nonce=None, encoder=encoding.RawEncoder):
220        """
221        Decrypts the ciphertext using the given nonce and returns the
222        plaintext message.
223
224        :param ciphertext: [:class:`bytes`] The encrypted message to decrypt
225        :param nonce: [:class:`bytes`] The nonce used when encrypting the
226            ciphertext
227        :param encoder: The encoder used to decode the ciphertext.
228        :rtype: [:class:`bytes`]
229        """
230        # Decode our ciphertext
231        ciphertext = encoder.decode(ciphertext)
232
233        if nonce is None:
234            # If we were given the nonce and ciphertext combined, split them.
235            nonce = ciphertext[:self.NONCE_SIZE]
236            ciphertext = ciphertext[self.NONCE_SIZE:]
237
238        if len(nonce) != self.NONCE_SIZE:
239            raise ValueError("The nonce must be exactly %s bytes long" %
240                             self.NONCE_SIZE)
241
242        plaintext = libnacl.crypto_box_open_afternm(
243            ciphertext,
244            nonce,
245            self._shared_key,
246        )
247
248        return plaintext
249
250
251class SignedMessage(six.binary_type):
252    """
253    A bytes subclass that holds a messaged that has been signed by a
254    :class:`SigningKey`.
255    """
256
257    @classmethod
258    def _from_parts(cls, signature, message, combined):
259        obj = cls(combined)
260        obj._signature = signature
261        obj._message = message
262        return obj
263
264    @property
265    def signature(self):
266        """
267        The signature contained within the :class:`SignedMessage`.
268        """
269        return self._signature
270
271    @property
272    def message(self):
273        """
274        The message contained within the :class:`SignedMessage`.
275        """
276        return self._message
277
278
279class VerifyKey(encoding.Encodable, StringFixer, object):
280    """
281    The public key counterpart to an Ed25519 SigningKey for producing digital
282    signatures.
283
284    :param key: [:class:`bytes`] Serialized Ed25519 public key
285    :param encoder: A class that is able to decode the `key`
286    """
287
288    def __init__(self, key, encoder=encoding.RawEncoder):
289        # Decode the key
290        key = encoder.decode(key)
291
292        if len(key) != libnacl.crypto_sign_PUBLICKEYBYTES:
293            raise ValueError(
294                "The key must be exactly %s bytes long" %
295                libnacl.crypto_sign_PUBLICKEYBYTES,
296            )
297
298        self._key = key
299
300    def __bytes__(self):
301        return self._key
302
303    def verify(self, smessage, signature=None, encoder=encoding.RawEncoder):
304        """
305        Verifies the signature of a signed message, returning the message
306        if it has not been tampered with else raising
307        :class:`~ValueError`.
308
309        :param smessage: [:class:`bytes`] Either the original messaged or a
310            signature and message concated together.
311        :param signature: [:class:`bytes`] If an unsigned message is given for
312            smessage then the detached signature must be provded.
313        :param encoder: A class that is able to decode the secret message and
314            signature.
315        :rtype: :class:`bytes`
316        """
317        if signature is not None:
318            # If we were given the message and signature separately, combine
319            #   them.
320            smessage = signature + smessage
321
322        # Decode the signed message
323        smessage = encoder.decode(smessage)
324
325        return libnacl.crypto_sign_open(smessage, self._key)
326
327
328class SigningKey(encoding.Encodable, StringFixer, object):
329    """
330    Private key for producing digital signatures using the Ed25519 algorithm.
331
332    Signing keys are produced from a 32-byte (256-bit) random seed value. This
333    value can be passed into the :class:`~SigningKey` as a
334    :func:`bytes` whose length is 32.
335
336    .. warning:: This **must** be protected and remain secret. Anyone who knows
337        the value of your :class:`~SigningKey` or it's seed can
338        masquerade as you.
339
340    :param seed: [:class:`bytes`] Random 32-byte value (i.e. private key)
341    :param encoder: A class that is able to decode the seed
342
343    :ivar: verify_key: [:class:`~VerifyKey`] The verify
344        (i.e. public) key that corresponds with this signing key.
345    """
346
347    def __init__(self, seed, encoder=encoding.RawEncoder):
348        # Decode the seed
349        seed = encoder.decode(seed)
350
351        # Verify that our seed is the proper size
352        if len(seed) != libnacl.crypto_sign_SEEDBYTES:
353            raise ValueError(
354                "The seed must be exactly %d bytes long" %
355                libnacl.crypto_sign_SEEDBYTES
356            )
357
358        public_key, secret_key = libnacl.crypto_sign_seed_keypair(seed)
359
360        self._seed = seed
361        self._signing_key = secret_key
362        self.verify_key = VerifyKey(public_key)
363
364    def __bytes__(self):
365        return self._seed
366
367    @classmethod
368    def generate(cls):
369        """
370        Generates a random :class:`~SigningKey` object.
371
372        :rtype: :class:`~SigningKey`
373        """
374        return cls(
375            libnacl.randombytes(libnacl.crypto_sign_SEEDBYTES),
376            encoder=encoding.RawEncoder,
377        )
378
379    def sign(self, message, encoder=encoding.RawEncoder):
380        """
381        Sign a message using this key.
382
383        :param message: [:class:`bytes`] The data to be signed.
384        :param encoder: A class that is used to encode the signed message.
385        :rtype: :class:`~SignedMessage`
386        """
387        raw_signed = libnacl.crypto_sign(message, self._signing_key)
388
389        signature = encoder.encode(raw_signed[:libnacl.crypto_sign_BYTES])
390        message = encoder.encode(raw_signed[libnacl.crypto_sign_BYTES:])
391        signed = encoder.encode(raw_signed)
392
393        return SignedMessage._from_parts(signature, message, signed)
394
395
396class Signer(object):
397    '''
398    Used to sign messages with nacl digital signature
399    '''
400    def __init__(self, key=None):
401        if key:
402            if not isinstance(key, SigningKey):  # not key so seed to regenerate
403                if len(key) == 32:
404                    key = SigningKey(seed=key, encoder=encoding.RawEncoder)
405                else:
406                    key = SigningKey(seed=key, encoder=encoding.HexEncoder)
407        else:
408            key = SigningKey.generate()
409        self.key = key
410        self.keyhex = self.key.encode(encoding.HexEncoder)  # seed
411        self.keyraw = self.key.encode(encoding.RawEncoder)  # seed
412        self.verhex = self.key.verify_key.encode(encoding.HexEncoder)
413        self.verraw = self.key.verify_key.encode(encoding.RawEncoder)
414
415    def sign(self, msg):
416        '''
417        Sign the message
418        '''
419        return self.key.sign(msg)
420
421    def signature(self, msg):
422        '''
423        Return only the signature string resulting from signing the message
424        '''
425        return self.key.sign(msg).signature
426
427
428class Verifier(object):
429    '''
430    Used to verify messages with nacl digital signature
431    '''
432    def __init__(self, key=None):
433        if key:
434            if not isinstance(key, VerifyKey):
435                if len(key) == 32:
436                    key = VerifyKey(key, encoding.RawEncoder)
437                else:
438                    key = VerifyKey(key, encoding.HexEncoder)
439        self.key = key
440        if isinstance(self.key, VerifyKey):
441            self.keyhex = self.key.encode(encoding.HexEncoder)
442            self.keyraw = self.key.encode(encoding.RawEncoder)
443        else:
444            self.keyhex = ''
445            self.keyraw = ''
446
447    def verify(self, signature, msg):
448        '''
449        Verify the message
450        '''
451        if not self.key:
452            return False
453        try:
454            self.key.verify(signature + msg)
455        except ValueError:
456            return False
457        return True
458
459
460class Publican(object):
461    '''
462    Container to manage remote nacl public key
463        .key is the public key
464    Intelligently converts hex encoded to object
465    '''
466    def __init__(self, key=None):
467        if key:
468            if not isinstance(key, PublicKey):
469                if len(key) == 32:
470                    key = PublicKey(key, encoding.RawEncoder)
471                else:
472                    key = PublicKey(key, encoding.HexEncoder)
473        self.key = key
474        if isinstance(self.key, PublicKey):
475            self.keyhex = self.key.encode(encoding.HexEncoder)
476            self.keyraw = self.key.encode(encoding.RawEncoder)
477        else:
478            self.keyhex = ''
479            self.keyraw = ''
480
481
482class Privateer(object):
483    '''
484    Container for local nacl key pair
485        .key is the private key
486    '''
487    def __init__(self, key=None):
488        if key:
489            if not isinstance(key, PrivateKey):
490                if len(key) == 32:
491                    key = PrivateKey(key, encoding.RawEncoder)
492                else:
493                    key = PrivateKey(key, encoding.HexEncoder)
494        else:
495            key = PrivateKey.generate()
496        self.key = key
497        self.keyhex = self.key.encode(encoding.HexEncoder)
498        self.keyraw = self.key.encode(encoding.RawEncoder)
499        self.pubhex = self.key.public_key.encode(encoding.HexEncoder)
500        self.pubraw = self.key.public_key.encode(encoding.RawEncoder)
501
502    def nonce(self):
503        '''
504        Generate a safe nonce value (safe assuming only this method is used to
505        create nonce values)
506        '''
507        return libnacl.randombytes(Box.NONCE_SIZE)
508
509    def encrypt(self, msg, pubkey, enhex=False):
510        '''
511        Return duple of (cyphertext, nonce) resulting from encrypting the message
512        using shared key generated from the .key and the pubkey
513        If pubkey is hex encoded it is converted first
514        If enhex is True then use HexEncoder otherwise use RawEncoder
515
516        Intended for the owner of the passed in public key
517
518        msg is string
519        pub is Publican instance
520        '''
521        if not isinstance(pubkey, PublicKey):
522            if len(pubkey) == 32:
523                pubkey = PublicKey(pubkey, encoding.RawEncoder)
524            else:
525                pubkey = PublicKey(pubkey, encoding.HexEncoder)
526        box = Box(self.key, pubkey)
527        nonce = self.nonce()
528        encoder = encoding.HexEncoder if enhex else encoding.RawEncoder
529        encrypted = box.encrypt(msg, nonce, encoder)
530        return (encrypted.ciphertext, encrypted.nonce)
531
532    def decrypt(self, cipher, nonce, pubkey, dehex=False):
533        '''
534        Return decrypted msg contained in cypher using nonce and shared key
535        generated from .key and pubkey.
536        If pubkey is hex encoded it is converted first
537        If dehex is True then use HexEncoder otherwise use RawEncoder
538
539        Intended for the owner of .key
540
541        cypher is string
542        nonce is string
543        pub is Publican instance
544        '''
545        if not isinstance(pubkey, PublicKey):
546            if len(pubkey) == 32:
547                pubkey = PublicKey(pubkey, encoding.RawEncoder)
548            else:
549                pubkey = PublicKey(pubkey, encoding.HexEncoder)
550        box = Box(self.key, pubkey)
551        decoder = encoding.HexEncoder if dehex else encoding.RawEncoder
552        if dehex and len(nonce) != box.NONCE_SIZE:
553            nonce = decoder.decode(nonce)
554        return box.decrypt(cipher, nonce, decoder)
555
556
557def uuid(size=16):
558    '''
559    Generate universally unique id hex string with size characters
560    Timebased with random bytes
561    Minimum size is 16
562
563    Uses time.clock instead of time.time on windows.
564    Tests of rapid uuid generation fail to generate unique uuids
565    on Windows with time.time().
566    See http://www.pythoncentral.io/measure-time-in-python-time-time-vs-time-clock/
567    for discussion.
568    '''
569    size = max(int(size), 16)
570    if sys.platform == 'win32':
571        front = ns2b("{0:0x}".format(int(time.clock() * 1000000)))  # microseconds
572    else:
573        front = ns2b("{0:0x}".format(int(time.time() * 1000000)))  # microseconds
574    extra = size - len(front)
575    back = binascii.hexlify(libnacl.randombytes(extra // 2 + extra % 2))
576    return ((front + back)[:size]).decode(encoding='ISO-8859-1')
577