diff --git a/src/nacl/_lib/crypto_sign.h b/src/nacl/_lib/crypto_sign.h new file mode 100644 index 0000000000000000000000000000000000000000..e401567981b434bd6d112b3f34fb7c16274c2c81 --- /dev/null +++ b/src/nacl/_lib/crypto_sign.h @@ -0,0 +1,33 @@ +/* Copyright 2013 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +size_t crypto_sign_bytes(); +// size_t crypto_sign_seedbytes(); +size_t crypto_sign_publickeybytes(); +size_t crypto_sign_secretkeybytes(); + + +int crypto_sign_keypair(unsigned char *pk, unsigned char *sk); + +int crypto_sign_seed_keypair(unsigned char *pk, unsigned char *sk, + const unsigned char *seed); + +int crypto_sign(unsigned char *sm, unsigned long long *smlen, + const unsigned char *m, unsigned long long mlen, + const unsigned char *sk); + +int crypto_sign_open(unsigned char *m, unsigned long long *mlen, + const unsigned char *sm, unsigned long long smlen, + const unsigned char *pk); diff --git a/src/nacl/c/__init__.py b/src/nacl/c/__init__.py index 377192aec45d53e59c4f487dd37dc20ca0ee416f..eb584354fa2fdf4830e65da806d6ac0e0e69df81 100644 --- a/src/nacl/c/__init__.py +++ b/src/nacl/c/__init__.py @@ -28,6 +28,11 @@ from nacl.c.crypto_secretbox import ( crypto_secretbox_ZEROBYTES, crypto_secretbox_BOXZEROBYTES, crypto_secretbox, crypto_secretbox_open, ) +from nacl.c.crypto_sign import ( + crypto_sign_BYTES, crypto_sign_SEEDBYTES, crypto_sign_PUBLICKEYBYTES, + crypto_sign_SECRETKEYBYTES, crypto_sign_keypair, crypto_sign_seed_keypair, + crypto_sign, crypto_sign_open, +) from nacl.c.randombytes import randombytes @@ -56,5 +61,14 @@ __all__ = [ "crypto_secretbox", "crypto_secretbox_open", + "crypto_sign_BYTES", + "crypto_sign_SEEDBYTES", + "crypto_sign_PUBLICKEYBYTES", + "crypto_sign_SECRETKEYBYTES", + "crypto_sign_keypair", + "crypto_sign_seed_keypair", + "crypto_sign", + "crypto_sign_open", + "randombytes", ] diff --git a/src/nacl/c/crypto_sign.py b/src/nacl/c/crypto_sign.py new file mode 100644 index 0000000000000000000000000000000000000000..505d2658e46d5443bbddb3cd6a83f7cb02899633 --- /dev/null +++ b/src/nacl/c/crypto_sign.py @@ -0,0 +1,101 @@ +# Copyright 2013 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. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +from __future__ import absolute_import, division, print_function + +from nacl import _lib as lib +from nacl.exceptions import BadSignatureError, CryptoError + + +crypto_sign_BYTES = lib.crypto_sign_bytes() +# crypto_sign_SEEDBYTES = lib.crypto_sign_seedbytes() +crypto_sign_SEEDBYTES = lib.crypto_sign_secretkeybytes() // 2 +crypto_sign_PUBLICKEYBYTES = lib.crypto_sign_publickeybytes() +crypto_sign_SECRETKEYBYTES = lib.crypto_sign_secretkeybytes() + + +def crypto_sign_keypair(): + """ + Returns a randomly generated secret key and public key. + + :rtype: (bytes(secret_key), bytes(public_key)) + """ + sk = lib.ffi.new("unsigned char[]", crypto_sign_SECRETKEYBYTES) + pk = lib.ffi.new("unsigned char[]", crypto_sign_PUBLICKEYBYTES) + + if lib.crypto_sign_keypair(pk, sk) != 0: + raise CryptoError("An error occurred while generating keypairs") + + return ( + lib.ffi.buffer(sk, crypto_sign_SECRETKEYBYTES)[:], + lib.ffi.buffer(pk, crypto_sign_PUBLICKEYBYTES)[:], + ) + + +def crypto_sign_seed_keypair(seed): + """ + Computes and returns the secret key and public key using the seed ``seed``. + + :param seed: bytes + :rtype: (bytes(secret_key), bytes(public_key)) + """ + if len(seed) != crypto_sign_SEEDBYTES: + raise ValueError("Invalid seed") + + sk = lib.ffi.new("unsigned char[]", crypto_sign_SECRETKEYBYTES) + pk = lib.ffi.new("unsigned char[]", crypto_sign_PUBLICKEYBYTES) + + if lib.crypto_sign_seed_keypair(pk, sk, seed) != 0: + raise CryptoError("An error occured while generating keypairs") + + return ( + lib.ffi.buffer(sk, crypto_sign_SECRETKEYBYTES)[:], + lib.ffi.buffer(pk, crypto_sign_PUBLICKEYBYTES)[:], + ) + + +def crypto_sign(sk, message): + """ + Signs the message ``message`` using the secret key ``sk`` and returns the + signed message. + + :param sk: bytes + :param message: bytes + :rtype: bytes + """ + signed = lib.ffi.new("unsigned char[]", len(message) + crypto_sign_BYTES) + signed_len = lib.ffi.new("unsigned long long *") + + if lib.crypto_sign(signed, signed_len, message, len(message), sk) != 0: + raise CryptoError("Failed to sign the message") + + return lib.ffi.buffer(signed, signed_len[0])[:] + + +def crypto_sign_open(pk, signed): + """ + Verifies the signature of the signed message ``signed`` using the public + key ``pkg`` and returns the unsigned message. + + :param pk: bytes + :param signed: bytes + :rtype: bytes + """ + message = lib.ffi.new("unsigned char[]", len(signed)) + message_len = lib.ffi.new("unsigned long long *") + + if lib.crypto_sign_open( + message, message_len, signed, len(signed), pk) != 0: + raise BadSignatureError("Signature was forged or corrupt") + + return lib.ffi.buffer(message, message_len[0])[:] diff --git a/src/nacl/exceptions.py b/src/nacl/exceptions.py index a50656b63e47faff20d4ed3d602b54a492f4ceca..ae8630f5ebb3845921a34def820b7afc642c7b5d 100644 --- a/src/nacl/exceptions.py +++ b/src/nacl/exceptions.py @@ -19,3 +19,9 @@ class CryptoError(Exception): """ Base exception for all nacl related errors """ + + +class BadSignatureError(CryptoError): + """ + Raised when the signature was forged or otherwise corrupt. + """ diff --git a/src/nacl/signing.py b/src/nacl/signing.py index 5791fdd2256aa7d8a7bc16815a8e9ce8842c7497..036b4d76466a55c1ce1ea0fadec40faa5ccf44ac 100644 --- a/src/nacl/signing.py +++ b/src/nacl/signing.py @@ -16,18 +16,12 @@ from __future__ import division import six +import nacl.c + from . import encoding -from .c import _lib as nacl -from .exceptions import CryptoError from .utils import StringFixer, random -class BadSignatureError(CryptoError): - """ - Raised when the signature was forged or otherwise corrupt. - """ - - class SignedMessage(six.binary_type): """ A bytes subclass that holds a messaged that has been signed by a @@ -69,9 +63,9 @@ class VerifyKey(encoding.Encodable, StringFixer, object): # Decode the key key = encoder.decode(key) - if len(key) != nacl.lib.crypto_sign_PUBLICKEYBYTES: + if len(key) != nacl.c.crypto_sign_PUBLICKEYBYTES: raise ValueError("The key must be exactly %s bytes long" % - nacl.lib.crypto_sign_PUBLICKEYBYTES) + nacl.c.crypto_sign_PUBLICKEYBYTES) self._key = key @@ -100,13 +94,7 @@ class VerifyKey(encoding.Encodable, StringFixer, object): # Decode the signed message smessage = encoder.decode(smessage) - message = nacl.ffi.new("unsigned char[]", len(smessage)) - message_len = nacl.ffi.new("unsigned long long *") - - if not nacl.lib.crypto_sign_open(message, message_len, smessage, len(smessage), self._key): - raise BadSignatureError("Signature was forged or corrupt") - - return nacl.ffi.buffer(message, message_len[0])[:] + return nacl.c.crypto_sign_open(self._key, smessage) class SigningKey(encoding.Encodable, StringFixer, object): @@ -133,23 +121,15 @@ class SigningKey(encoding.Encodable, StringFixer, object): seed = encoder.decode(seed) # Verify that our seed is the proper size - seed_size = nacl.lib.crypto_sign_SECRETKEYBYTES // 2 - if len(seed) != seed_size: - raise ValueError( - 'The seed must be exactly %d bytes long' % (seed_size,)) - - pk = nacl.ffi.new("unsigned char[]", nacl.lib.crypto_sign_PUBLICKEYBYTES) - sk = nacl.ffi.new("unsigned char[]", nacl.lib.crypto_sign_SECRETKEYBYTES) + if len(seed) != nacl.c.crypto_sign_SEEDBYTES: + raise ValueError("The seed must be exactly %d bytes long" % + nacl.c.crypto_sign_SEEDBYTES) - if not nacl.lib.crypto_sign_seed_keypair(pk, sk, seed): - raise CryptoError("Failed to generate a key pair") + secret_key, public_key = nacl.c.crypto_sign_seed_keypair(seed) - # Secret values self._seed = seed - self._signing_key = nacl.ffi.buffer(sk, nacl.lib.crypto_sign_SECRETKEYBYTES)[:] - - # Public values - self.verify_key = VerifyKey(nacl.ffi.buffer(pk, nacl.lib.crypto_sign_PUBLICKEYBYTES)[:]) + self._signing_key = secret_key + self.verify_key = VerifyKey(public_key) def __bytes__(self): return self._seed @@ -161,9 +141,10 @@ class SigningKey(encoding.Encodable, StringFixer, object): :rtype: :class:`~nacl.signing.SigningKey` """ - return cls(random(nacl.lib.crypto_sign_SECRETKEYBYTES // 2), - encoder=encoding.RawEncoder, - ) + return cls( + random(nacl.c.crypto_sign_SEEDBYTES), + encoder=encoding.RawEncoder, + ) def sign(self, message, encoder=encoding.RawEncoder): """ @@ -173,16 +154,10 @@ class SigningKey(encoding.Encodable, StringFixer, object): :param encoder: A class that is used to encode the signed message. :rtype: :class:`~nacl.signing.SignedMessage` """ - sm = nacl.ffi.new("unsigned char[]", len(message) + nacl.lib.crypto_sign_BYTES) - smlen = nacl.ffi.new("unsigned long long *") - - if not nacl.lib.crypto_sign(sm, smlen, message, len(message), self._signing_key): - raise CryptoError("Failed to sign the message") - - raw_signed = nacl.ffi.buffer(sm, smlen[0])[:] + raw_signed = nacl.c.crypto_sign(self._signing_key, message) - signature = encoder.encode(raw_signed[:nacl.lib.crypto_sign_BYTES]) - message = encoder.encode(raw_signed[nacl.lib.crypto_sign_BYTES:]) + signature = encoder.encode(raw_signed[:nacl.c.crypto_sign_BYTES]) + message = encoder.encode(raw_signed[nacl.c.crypto_sign_BYTES:]) signed = encoder.encode(raw_signed) return SignedMessage._from_parts(signature, message, signed) diff --git a/tests/test_signing.py b/tests/test_signing.py index 92ca3f043108551020a2309a0504886bf1b04e36..803f3617b006fe9aaba889ad722307d93beb16f6 100644 --- a/tests/test_signing.py +++ b/tests/test_signing.py @@ -21,7 +21,7 @@ import pytest import nacl.signing import nacl.encoding -import nacl.nacl +import nacl.exceptions def ed25519_known_answers(): @@ -86,9 +86,9 @@ class TestVerifyKey: # Small sanity check assert skey.verify_key.verify(smessage) - with pytest.raises(nacl.signing.BadSignatureError): + with pytest.raises(nacl.exceptions.BadSignatureError): skey.verify_key.verify(message, signature) - with pytest.raises(nacl.signing.BadSignatureError): + with pytest.raises(nacl.exceptions.BadSignatureError): forged = nacl.signing.SignedMessage(signature + message) skey.verify_key.verify(forged)