diff --git a/docs/signing.rst b/docs/signing.rst index a7c847e3e..6f6e2a446 100644 --- a/docs/signing.rst +++ b/docs/signing.rst @@ -17,16 +17,15 @@ use it to validate that your messages are actually authentic. Example ------- -Signing and verifying a message without encoding the key or message - Signer's perspective (:class:`~nacl.signing.SigningKey`) .. testcode:: - from nacl.signing import SigningKey + import nacl.encoding + import nacl.signing # Generate a new random signing key - signing_key = SigningKey.generate() + signing_key = nacl.signing.SigningKey.generate() # Sign a message with the signing key signed = signing_key.sign(b"Attack at Dawn") @@ -35,21 +34,21 @@ Signer's perspective (:class:`~nacl.signing.SigningKey`) verify_key = signing_key.verify_key # Serialize the verify key to send it to a third party - verify_key_bytes = verify_key.encode() + verify_key_hex = nacl.encoding.HexEncoder.encode(bytes(verify_key)) Verifier's perspective (:class:`~nacl.signing.VerifyKey`) .. testcode:: - from nacl.signing import VerifyKey + import nacl.signing # Create a VerifyKey object from a hex serialized public key - verify_key = VerifyKey(verify_key_bytes) + decoded_key = nacl.encoding.HexEncoder.decode(verify_key_hex) + verify_key = nacl.signing.VerifyKey(decoded_key) # Check the validity of a message's signature - # The message and the signature can either be passed together, or - # separately if the signature is decoded to raw bytes. - # These are equivalent: + # The message and the signature can either be passed separately or + # concatenated together. These are equivalent: verify_key.verify(signed) verify_key.verify(signed.message, signed.signature) @@ -66,122 +65,10 @@ Verifier's perspective (:class:`~nacl.signing.VerifyKey`) nacl.exceptions.BadSignatureError: Signature was forged or corrupt -Example -------- - -Signing and verifying a message encoded with HexEncoder - -Signer's perspective (:class:`~nacl.signing.SigningKey`) - -.. testcode:: - - from nacl.encoding import HexEncoder - from nacl.signing import SigningKey - - # Generate a new random signing key - signing_key = SigningKey.generate() - - # Sign a message with the signing key - signed_hex = signing_key.sign(b"Attack at Dawn", encoder=HexEncoder) - - # Obtain the verify key for a given signing key - verify_key = signing_key.verify_key - - # Serialize the verify key to send it to a third party - verify_key_hex = verify_key.encode(encoder=HexEncoder) - -Verifier's perspective (:class:`~nacl.signing.VerifyKey`) - -.. testcode:: - - from nacl.encoding import HexEncoder - from nacl.signing import VerifyKey - - # Create a VerifyKey object from a hex serialized public key - verify_key = VerifyKey(verify_key_hex, encoder=HexEncoder) - - # Check the validity of a message's signature - # The message and the signature can either be passed together, or - # separately if the signature is decoded to raw bytes. - # These are equivalent: - verify_key.verify(signed_hex, encoder=HexEncoder) - signature_bytes = HexEncoder.decode(signed_hex.signature) - verify_key.verify(signed_hex.message, signature_bytes, - encoder=HexEncoder) - - # Alter the signed message text - forged = signed_hex[:-1] + bytes([int(signed_hex[-1]) ^ 1]) - # Will raise nacl.exceptions.BadSignatureError, since the signature check - # is failing - verify_key.verify(forged) - -.. testoutput:: - - Traceback (most recent call last): - ... - nacl.exceptions.BadSignatureError: Signature was forged or corrupt - - -Example -------- - -Signing and verifying a message encoded with Base64Encoder - -Signer's perspective (:class:`~nacl.signing.SigningKey`) - -.. testcode:: - - from nacl.encoding import Base64Encoder - from nacl.signing import SigningKey - - # Generate a new random signing key - signing_key = SigningKey.generate() - - # Sign a message with the signing key - signed_b64 = signing_key.sign(b"Attack at Dawn", encoder=Base64Encoder) - - # Obtain the verify key for a given signing key - verify_key = signing_key.verify_key - - # Serialize the verify key to send it to a third party - verify_key_b64 = verify_key.encode(encoder=Base64Encoder) - -Verifier's perspective (:class:`~nacl.signing.VerifyKey`) - -.. testcode:: - - from nacl.encoding import Base64Encoder - from nacl.signing import VerifyKey - - # Create a VerifyKey object from a base64 serialized public key - verify_key = VerifyKey(verify_key_b64, encoder=Base64Encoder) - - # Check the validity of a message's signature - # The message and the signature can either be passed together, or - # separately if the signature is decoded to raw bytes. - # These are equivalent: - verify_key.verify(signed_b64, encoder=Base64Encoder) - signature_bytes = Base64Encoder.decode(signed_b64.signature) - verify_key.verify(signed_b64.message, signature_bytes, - encoder=Base64Encoder) - - # Alter the signed message text - forged = signed_b64[:-1] + bytes([int(signed_b64[-1]) ^ 1]) - # Will raise nacl.exceptions.BadSignatureError, since the signature check - # is failing - verify_key.verify(forged) - -.. testoutput:: - - Traceback (most recent call last): - ... - nacl.exceptions.BadSignatureError: Signature was forged or corrupt - - Reference --------- -.. class:: SigningKey(seed, encoder) +.. class:: SigningKey(seed) Private key for producing digital signatures using the Ed25519 algorithm. @@ -194,7 +81,6 @@ Reference masquerade as you. :param bytes seed: Random 32-byte value (i.e. private key). - :param encoder: A class that is able to decode the ``seed``. .. attribute:: verify_key @@ -207,24 +93,22 @@ Reference :return: An instance of :class:`~nacl.signing.SigningKey`. - .. method:: sign(message, encoder) + .. method:: sign(message) Sign a message using this key. :param bytes message: The data to be signed. - :param encoder: A class that is able to decode the signed message. :return: An instance of :class:`~nacl.signing.SignedMessage`. -.. class:: VerifyKey(key, encoder) +.. class:: VerifyKey(key) The public key counterpart to an Ed25519 :class:`~nacl.signing.SigningKey` for producing digital signatures. :param bytes key: A serialized Ed25519 public key. - :param encoder: A class that is able to decode the ``key``. - .. method:: verify(smessage, signature, encoder) + .. method:: verify(smessage, signature) Verifies the signature of a signed message. @@ -233,8 +117,6 @@ Reference :param bytes signature: The signature of the message to verify against. If the value of ``smessage`` is the concated signature and message, this parameter can be ``None``. - :param encoder: A class that is able to decode the secret message and - signature. :return bytes: The message if successfully verified. diff --git a/src/nacl/signing.py b/src/nacl/signing.py index 497fd3352..1ace5fdf3 100644 --- a/src/nacl/signing.py +++ b/src/nacl/signing.py @@ -14,12 +14,14 @@ from __future__ import absolute_import, division, print_function +from warnings import warn + import nacl.bindings from nacl import encoding from nacl import exceptions as exc from nacl.public import (PrivateKey as _Curve25519_PrivateKey, PublicKey as _Curve25519_PublicKey) -from nacl.utils import StringFixer, random +from nacl.utils import PyNaclDeprecated, StringFixer, random class SignedMessage(bytes): @@ -56,10 +58,18 @@ class VerifyKey(encoding.Encodable, StringFixer, object): signatures. :param key: [:class:`bytes`] Serialized Ed25519 public key - :param encoder: A class that is able to decode the `key` + :param encoder: (Deprecated) A class that is able to decode the `key` """ - def __init__(self, key, encoder=encoding.RawEncoder): + def __init__(self, key, encoder=None): + if encoder is None: + encoder = encoding.RawEncoder + else: + warn(("Implicit encoding/decoding of signing keys " + "is deprecated and will be removed in a future release. " + "Remove explicit 'encoder={}' calling parameter").format( + encoder.__class__), + PyNaclDeprecated) # Decode the key key = encoder.decode(key) if not isinstance(key, bytes): @@ -87,7 +97,7 @@ def __eq__(self, other): def __ne__(self, other): return not (self == other) - def verify(self, smessage, signature=None, encoder=encoding.RawEncoder): + def verify(self, smessage, signature=None, encoder=None): """ Verifies the signature of a signed message, returning the message if it has not been tampered with else raising @@ -97,17 +107,26 @@ def verify(self, smessage, signature=None, encoder=encoding.RawEncoder): signature and message concated together. :param signature: [:class:`bytes`] If an unsigned message is given for smessage then the detached signature must be provided. - :param encoder: A class that is able to decode the secret message and - signature. + :param encoder: (Deprecated) A class that is able to decode the secret + message and signature. :rtype: :class:`bytes` """ + if encoder is None: + encoder = encoding.RawEncoder + else: + warn(("Automatic encoding/decoding of signed messages " + "is deprecated and will be removed in a future release. " + "Remove explicit 'encoder={}' calling parameter").format( + encoder.__class__), + PyNaclDeprecated) + if signature is not None: # If we were given the message and signature separately, combine # them. - smessage = signature + encoder.decode(smessage) - else: - # Decode the signed message - smessage = encoder.decode(smessage) + smessage = signature + smessage + + # Decode the signed message + smessage = encoder.decode(smessage) return nacl.bindings.crypto_sign_open(smessage, self._key) @@ -135,13 +154,22 @@ class SigningKey(encoding.Encodable, StringFixer, object): masquerade as you. :param seed: [:class:`bytes`] Random 32-byte value (i.e. private key) - :param encoder: A class that is able to decode the seed + :param encoder: (Deprecated) A class that is able to decode the seed :ivar: verify_key: [:class:`~nacl.signing.VerifyKey`] The verify (i.e. public) key that corresponds with this signing key. """ - def __init__(self, seed, encoder=encoding.RawEncoder): + def __init__(self, seed, encoder=None): + if encoder is None: + encoder = encoding.RawEncoder + else: + warn(("Implicit encoding/decoding of signing keys " + "is deprecated and will be removed in a future release. " + "Remove explicit 'encoder={}' calling parameter").format( + encoder.__class__), + PyNaclDeprecated) + # Decode the seed seed = encoder.decode(seed) if not isinstance(seed, bytes): @@ -182,19 +210,26 @@ def generate(cls): :rtype: :class:`~nacl.signing.SigningKey` """ - return cls( - random(nacl.bindings.crypto_sign_SEEDBYTES), - encoder=encoding.RawEncoder, - ) + return cls(random(nacl.bindings.crypto_sign_SEEDBYTES)) - def sign(self, message, encoder=encoding.RawEncoder): + def sign(self, message, encoder=None): """ Sign a message using this key. :param message: [:class:`bytes`] The data to be signed. - :param encoder: A class that is used to encode the signed message. + :param encoder: (Deprecated) A class that is used to encode + the signed message. :rtype: :class:`~nacl.signing.SignedMessage` """ + if encoder is None: + encoder = encoding.RawEncoder + else: + warn(("Automatic encoding/decoding of signed messages " + "is deprecated and will be removed in a future release. " + "Remove explicit 'encoder={}' calling parameter").format( + encoder.__class__), + PyNaclDeprecated) + raw_signed = nacl.bindings.crypto_sign(message, self._signing_key) crypto_sign_BYTES = nacl.bindings.crypto_sign_BYTES diff --git a/src/nacl/utils.py b/src/nacl/utils.py index 552b1edb3..cc41ffc8c 100644 --- a/src/nacl/utils.py +++ b/src/nacl/utils.py @@ -1,4 +1,4 @@ -# Copyright 2013 Donald Stufft and individual contributors +# Copyright 2013-2019 Donald Stufft and individual contributors # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -19,6 +19,10 @@ import six +class PyNaclDeprecated(UserWarning): + pass + + class EncryptedMessage(bytes): """ A bytes subclass that holds a messaged that has been encrypted by a diff --git a/tests/test_signing.py b/tests/test_signing.py index 4304afb92..56cf8fba4 100644 --- a/tests/test_signing.py +++ b/tests/test_signing.py @@ -21,9 +21,10 @@ from utils import assert_equal, assert_not_equal, read_crypto_test_vectors from nacl.bindings import crypto_sign_PUBLICKEYBYTES, crypto_sign_SEEDBYTES -from nacl.encoding import Base64Encoder, HexEncoder +from nacl.encoding import HexEncoder from nacl.exceptions import BadSignatureError from nacl.signing import SignedMessage, SigningKey, VerifyKey +from nacl.utils import PyNaclDeprecated def tohex(b): @@ -78,30 +79,42 @@ def test_different_keys_are_not_equal(self, k2): k1 = SigningKey(b"\x00" * crypto_sign_SEEDBYTES) assert_not_equal(k1, k2) - @pytest.mark.parametrize("seed", [ + @pytest.mark.parametrize("hseed", [ b"77076d0a7318a57d3c16c17251b26645df4c2f87ebc0992ab177fba51db92c2a", ]) - def test_initialization_with_seed(self, seed): - SigningKey(seed, encoder=HexEncoder) + def test_initialization_with_seed(self, hseed): + seed = binascii.unhexlify(hseed) + SigningKey(seed) @pytest.mark.parametrize( - ("seed", "_public_key", "message", "signature", "expected"), + ("hseed", "_public_key", "message", "signature", "expected"), ed25519_known_answers() ) - def test_message_signing(self, seed, _public_key, + def test_message_signing(self, hseed, _public_key, message, signature, expected): - signing_key = SigningKey( - seed, - encoder=HexEncoder, - ) + seed = binascii.unhexlify(hseed) + signing_key = SigningKey(seed) signed = signing_key.sign( binascii.unhexlify(message), - encoder=HexEncoder, ) - assert signed == expected - assert signed.message == message - assert signed.signature == signature + assert signed == binascii.unhexlify(expected) + assert signed.message == binascii.unhexlify(message) + assert signed.signature == binascii.unhexlify(signature) + + @pytest.mark.parametrize("hseed", [ + b"77076d0a7318a57d3c16c17251b26645df4c2f87ebc0992ab177fba51db92c2a", + ]) + def test_deprecation_of_encoder_parameter(self, hseed): + with pytest.warns(PyNaclDeprecated): + SigningKey(hseed, + encoder=HexEncoder, + ) + sk = SigningKey.generate() + unsigned = b"A test message!" + with pytest.warns(PyNaclDeprecated): + sk.sign(unsigned, + encoder=HexEncoder) class TestVerifyKey: @@ -135,23 +148,20 @@ def test_different_keys_are_not_equal(self, k2): assert_not_equal(k1, k2) @pytest.mark.parametrize( - ("_seed", "public_key", "message", "signature", "signed"), + ("_seed", "hpublic_key", "hmessage", "hsignature", "hsigned"), ed25519_known_answers() ) def test_valid_signed_message( - self, _seed, public_key, message, signature, signed): - key = VerifyKey( - public_key, - encoder=HexEncoder, - ) + self, _seed, hpublic_key, hmessage, hsignature, hsigned): + public_key = binascii.unhexlify(hpublic_key) + key = VerifyKey(public_key) - assert binascii.hexlify( - key.verify(signed, encoder=HexEncoder), - ) == message - assert binascii.hexlify( - key.verify(message, HexEncoder.decode(signature), - encoder=HexEncoder), - ) == message + signed = binascii.unhexlify(hsigned) + message = binascii.unhexlify(hmessage) + signature = binascii.unhexlify(hsignature) + + assert key.verify(signed) == message + assert key.verify(message, signature) == message def test_invalid_signed_message(self): skey = SigningKey.generate() @@ -168,38 +178,6 @@ def test_invalid_signed_message(self): forged = SignedMessage(signature + message) skey.verify_key.verify(forged) - def test_base64_smessage_with_detached_sig_matches_with_attached_sig(self): - sk = SigningKey.generate() - vk = sk.verify_key - - smsg = sk.sign(b"Hello World in base64", encoder=Base64Encoder) - - msg = smsg.message - b64sig = smsg.signature - - sig = Base64Encoder.decode(b64sig) - - assert vk.verify(msg, sig, encoder=Base64Encoder) == \ - vk.verify(smsg, encoder=Base64Encoder) - - assert Base64Encoder.decode(msg) == b"Hello World in base64" - - def test_hex_smessage_with_detached_sig_matches_with_attached_sig(self): - sk = SigningKey.generate() - vk = sk.verify_key - - smsg = sk.sign(b"Hello World in hex", encoder=HexEncoder) - - msg = smsg.message - hexsig = smsg.signature - - sig = HexEncoder.decode(hexsig) - - assert vk.verify(msg, sig, encoder=HexEncoder) == \ - vk.verify(smsg, encoder=HexEncoder) - - assert HexEncoder.decode(msg) == b"Hello World in hex" - def test_key_conversion(self): keypair_seed = (b"421151a459faeade3d247115f94aedae" b"42318124095afabe4d1451a559faedee") @@ -215,6 +193,20 @@ def test_key_conversion(self): assert tohex(public_key) == ("f1814f0e8ff1043d8a44d25babff3ced" "cae6c22c3edaa48f857ae70de2baae50") + def test_deprecation_of_encoder_parameter(self): + sk = SigningKey.generate() + unsigned = b"A test message!" + signed = sk.sign(unsigned) + hsigned = HexEncoder.encode(signed) + hpub = HexEncoder.encode(bytes(sk.verify_key)) + with pytest.warns(PyNaclDeprecated): + VerifyKey(hpub, + encoder=HexEncoder, + ) + with pytest.warns(PyNaclDeprecated): + sk.verify_key.verify(hsigned, + encoder=HexEncoder) + def check_type_error(expected, f, *args): with pytest.raises(TypeError) as e: