Merge pull request #1502 from reaperhulk/fix-1285

Support decoding RFC 6979 signatures to (r, s)
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index a0d8150..b8a799a 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -16,6 +16,8 @@
 * Added :class:`~cryptography.hazmat.primitives.interfaces.MACContext` as a
   common interface for CMAC and HMAC and deprecated
   :class:`~cryptography.hazmat.primitives.interfaces.CMACContext`.
+* Added support for encoding and decoding :rfc:`6979` signatures in
+  :doc:`/hazmat/primitives/asymmetric/utils`.
 
 0.6.1 - 2014-10-15
 ~~~~~~~~~~~~~~~~~~
diff --git a/dev-requirements.txt b/dev-requirements.txt
index 4fff76b..092b991 100644
--- a/dev-requirements.txt
+++ b/dev-requirements.txt
@@ -5,7 +5,6 @@
 iso8601
 pep8-naming
 pretend
-pyasn1
 pytest
 requests
 sphinx
diff --git a/docs/hazmat/primitives/asymmetric/dsa.rst b/docs/hazmat/primitives/asymmetric/dsa.rst
index 43741ed..df3c99f 100644
--- a/docs/hazmat/primitives/asymmetric/dsa.rst
+++ b/docs/hazmat/primitives/asymmetric/dsa.rst
@@ -80,7 +80,8 @@
     >>> signature = signer.finalize()
 
 The ``signature`` is a ``bytes`` object, whose contents is DER encoded as
-described in :rfc:`6979`.
+described in :rfc:`6979`. This can be decoded using
+:func:`~cryptography.hazmat.primitives.asymmetric.utils.decode_rfc6979_signature`.
 
 Verification
 ~~~~~~~~~~~~
diff --git a/docs/hazmat/primitives/asymmetric/ec.rst b/docs/hazmat/primitives/asymmetric/ec.rst
index fd20cbb..13ab7c1 100644
--- a/docs/hazmat/primitives/asymmetric/ec.rst
+++ b/docs/hazmat/primitives/asymmetric/ec.rst
@@ -53,7 +53,9 @@
         >>> signature = signer.finalize()
 
     The ``signature`` is a ``bytes`` object, whose contents is DER encoded as
-    described in :rfc:`6979`.
+    described in :rfc:`6979`. This can be decoded using
+    :func:`~cryptography.hazmat.primitives.asymmetric.utils.decode_rfc6979_signature`.
+
 
 
 .. class:: EllipticCurvePrivateNumbers(private_value, public_numbers)
diff --git a/docs/hazmat/primitives/asymmetric/index.rst b/docs/hazmat/primitives/asymmetric/index.rst
index 6a5228b..24f0f5b 100644
--- a/docs/hazmat/primitives/asymmetric/index.rst
+++ b/docs/hazmat/primitives/asymmetric/index.rst
@@ -11,3 +11,4 @@
     rsa
     padding
     serialization
+    utils
diff --git a/docs/hazmat/primitives/asymmetric/utils.rst b/docs/hazmat/primitives/asymmetric/utils.rst
new file mode 100644
index 0000000..6b34880
--- /dev/null
+++ b/docs/hazmat/primitives/asymmetric/utils.rst
@@ -0,0 +1,26 @@
+.. hazmat::
+
+Asymmetric Utilities
+====================
+
+.. currentmodule:: cryptography.hazmat.primitives.asymmetric.utils
+
+
+.. function:: decode_rfc6979_signature(signature)
+
+    Takes in :rfc:`6979` signatures generated by the DSA/ECDSA signers and
+    returns a tuple ``(r, s)``.
+
+    :param bytes signature: The signature to decode.
+
+    :returns: The decoded tuple ``(r, s)``.
+
+.. function:: encode_rfc6979_signature(r, s)
+
+    Creates an :rfc:`6979` byte string from raw signature values.
+
+    :param int r: The raw signature value ``r``.
+
+    :param int s: The raw signature value ``s``.
+
+    :return bytes: The encoded signature.
diff --git a/setup.py b/setup.py
index 7a76909..3e2ab3e 100644
--- a/setup.py
+++ b/setup.py
@@ -36,6 +36,7 @@
 
 requirements = [
     CFFI_DEPENDENCY,
+    "pyasn1",
     SIX_DEPENDENCY,
     SETUPTOOLS_DEPENDENCY
 ]
@@ -43,7 +44,6 @@
 # If you add a new dep here you probably need to add it in the tox.ini as well
 test_requirements = [
     "pytest",
-    "pyasn1",
     "pretend",
     "iso8601",
 ]
diff --git a/src/cryptography/hazmat/primitives/asymmetric/utils.py b/src/cryptography/hazmat/primitives/asymmetric/utils.py
new file mode 100644
index 0000000..71f4ff8
--- /dev/null
+++ b/src/cryptography/hazmat/primitives/asymmetric/utils.py
@@ -0,0 +1,46 @@
+# This file is dual licensed under the terms of the Apache License, Version
+# 2.0, and the BSD License. See the LICENSE file in the root of this repository
+# for complete details.
+
+from __future__ import absolute_import, division, print_function
+
+from pyasn1.codec.der import decoder, encoder
+from pyasn1.error import PyAsn1Error
+from pyasn1.type import namedtype, univ
+
+import six
+
+
+class _DSSSigValue(univ.Sequence):
+    componentType = namedtype.NamedTypes(
+        namedtype.NamedType('r', univ.Integer()),
+        namedtype.NamedType('s', univ.Integer())
+    )
+
+
+def decode_rfc6979_signature(signature):
+    try:
+        data, remaining = decoder.decode(signature, asn1Spec=_DSSSigValue())
+    except PyAsn1Error:
+        raise ValueError("Invalid signature data. Unable to decode ASN.1")
+
+    if remaining:
+        raise ValueError(
+            "The signature contains bytes after the end of the ASN.1 sequence."
+        )
+    r = int(data.getComponentByName('r'))
+    s = int(data.getComponentByName('s'))
+    return (r, s)
+
+
+def encode_rfc6979_signature(r, s):
+    if (
+        not isinstance(r, six.integer_types) or
+        not isinstance(s, six.integer_types)
+    ):
+        raise ValueError("Both r and s must be integers")
+
+    sig = _DSSSigValue()
+    sig.setComponentByName('r', r)
+    sig.setComponentByName('s', s)
+    return encoder.encode(sig)
diff --git a/tests/hazmat/primitives/test_asym_utils.py b/tests/hazmat/primitives/test_asym_utils.py
new file mode 100644
index 0000000..bf55bad
--- /dev/null
+++ b/tests/hazmat/primitives/test_asym_utils.py
@@ -0,0 +1,65 @@
+# This file is dual licensed under the terms of the Apache License, Version
+# 2.0, and the BSD License. See the LICENSE file in the root of this repository
+# for complete details.
+
+from __future__ import absolute_import, division, print_function
+
+import pytest
+
+from cryptography.hazmat.primitives.asymmetric.utils import (
+    decode_rfc6979_signature, encode_rfc6979_signature
+)
+
+
+def test_rfc6979_signature():
+    sig = encode_rfc6979_signature(1, 1)
+    assert sig == b"0\x06\x02\x01\x01\x02\x01\x01"
+    assert decode_rfc6979_signature(sig) == (1, 1)
+
+    r_s1 = (
+        1037234182290683143945502320610861668562885151617,
+        559776156650501990899426031439030258256861634312
+    )
+    sig2 = encode_rfc6979_signature(*r_s1)
+    assert sig2 == (
+        b'0-\x02\x15\x00\xb5\xaf0xg\xfb\x8bT9\x00\x13\xccg\x02\r\xdf\x1f,\x0b'
+        b'\x81\x02\x14b\r;"\xabP1D\x0c>5\xea\xb6\xf4\x81)\x8f\x9e\x9f\x08'
+    )
+    assert decode_rfc6979_signature(sig2) == r_s1
+
+    sig3 = encode_rfc6979_signature(0, 0)
+    assert sig3 == b"0\x06\x02\x01\x00\x02\x01\x00"
+    assert decode_rfc6979_signature(sig3) == (0, 0)
+
+    sig4 = encode_rfc6979_signature(-1, 0)
+    assert sig4 == b"0\x06\x02\x01\xFF\x02\x01\x00"
+    assert decode_rfc6979_signature(sig4) == (-1, 0)
+
+
+def test_encode_rfc6979_non_integer():
+    with pytest.raises(ValueError):
+        encode_rfc6979_signature("h", 3)
+
+    with pytest.raises(ValueError):
+        encode_rfc6979_signature("3", "2")
+
+    with pytest.raises(ValueError):
+        encode_rfc6979_signature(3, "h")
+
+    with pytest.raises(ValueError):
+        encode_rfc6979_signature(3.3, 1.2)
+
+    with pytest.raises(ValueError):
+        encode_rfc6979_signature("hello", "world")
+
+
+def test_decode_rfc6979_trailing_bytes():
+    with pytest.raises(ValueError):
+        decode_rfc6979_signature(b"0\x06\x02\x01\x01\x02\x01\x01\x00\x00\x00")
+
+
+def test_decode_rfc6979_invalid_asn1():
+    with pytest.raises(ValueError):
+        # This byte sequence has an invalid ASN.1 sequence length as well as
+        # an invalid integer length for the second integer.
+        decode_rfc6979_signature(b"0\x07\x02\x01\x01\x02\x02\x01")
diff --git a/tests/hazmat/primitives/test_dsa.py b/tests/hazmat/primitives/test_dsa.py
index 6411b7f..f818f73 100644
--- a/tests/hazmat/primitives/test_dsa.py
+++ b/tests/hazmat/primitives/test_dsa.py
@@ -12,14 +12,17 @@
 from cryptography.hazmat.backends.interfaces import DSABackend
 from cryptography.hazmat.primitives import hashes, interfaces
 from cryptography.hazmat.primitives.asymmetric import dsa
+from cryptography.hazmat.primitives.asymmetric.utils import (
+    encode_rfc6979_signature
+)
 from cryptography.utils import bit_length
 
 from .fixtures_dsa import (
     DSA_KEY_1024, DSA_KEY_2048, DSA_KEY_3072
 )
 from ...utils import (
-    der_encode_dsa_signature, load_fips_dsa_key_pair_vectors,
-    load_fips_dsa_sig_vectors, load_vectors_from_file,
+    load_fips_dsa_key_pair_vectors, load_fips_dsa_sig_vectors,
+    load_vectors_from_file,
 )
 
 
@@ -557,7 +560,7 @@
             ),
             y=vector['y']
         ).public_key(backend)
-        sig = der_encode_dsa_signature(vector['r'], vector['s'])
+        sig = encode_rfc6979_signature(vector['r'], vector['s'])
         verifier = public_key.verifier(sig, algorithm())
         verifier.update(vector['msg'])
         if vector['result'] == "F":
diff --git a/tests/hazmat/primitives/test_ec.py b/tests/hazmat/primitives/test_ec.py
index 3080a6c..a006f01 100644
--- a/tests/hazmat/primitives/test_ec.py
+++ b/tests/hazmat/primitives/test_ec.py
@@ -13,11 +13,13 @@
 from cryptography.hazmat.backends.interfaces import EllipticCurveBackend
 from cryptography.hazmat.primitives import hashes, interfaces
 from cryptography.hazmat.primitives.asymmetric import ec
+from cryptography.hazmat.primitives.asymmetric.utils import (
+    encode_rfc6979_signature
+)
 
 from ...utils import (
-    der_encode_dsa_signature, load_fips_ecdsa_key_pair_vectors,
-    load_fips_ecdsa_signing_vectors, load_vectors_from_file,
-    raises_unsupported_algorithm
+    load_fips_ecdsa_key_pair_vectors, load_fips_ecdsa_signing_vectors,
+    load_vectors_from_file, raises_unsupported_algorithm
 )
 
 _HASH_TYPES = {
@@ -305,10 +307,7 @@
             curve_type()
         ).public_key(backend)
 
-        signature = der_encode_dsa_signature(
-            vector['r'],
-            vector['s']
-        )
+        signature = encode_rfc6979_signature(vector['r'], vector['s'])
 
         verifier = key.verifier(
             signature,
@@ -337,10 +336,7 @@
             curve_type()
         ).public_key(backend)
 
-        signature = der_encode_dsa_signature(
-            vector['r'],
-            vector['s']
-        )
+        signature = encode_rfc6979_signature(vector['r'], vector['s'])
 
         verifier = key.verifier(
             signature,
diff --git a/tests/test_utils.py b/tests/test_utils.py
index 637c42b..bc5f2e1 100644
--- a/tests/test_utils.py
+++ b/tests/test_utils.py
@@ -18,7 +18,7 @@
 import cryptography_vectors
 
 from .utils import (
-    check_backend_support, der_encode_dsa_signature, load_cryptrec_vectors,
+    check_backend_support, load_cryptrec_vectors,
     load_fips_dsa_key_pair_vectors, load_fips_dsa_sig_vectors,
     load_fips_ecdsa_key_pair_vectors, load_fips_ecdsa_signing_vectors,
     load_hash_vectors, load_kasvs_dh_vectors, load_nist_vectors,
@@ -110,26 +110,6 @@
         check_backend_support(item)
 
 
-def test_der_encode_dsa_signature_values():
-    sig = der_encode_dsa_signature(1, 1)
-    assert sig == b"0\x06\x02\x01\x01\x02\x01\x01"
-
-    sig2 = der_encode_dsa_signature(
-        1037234182290683143945502320610861668562885151617,
-        559776156650501990899426031439030258256861634312
-    )
-    assert sig2 == (
-        b'0-\x02\x15\x00\xb5\xaf0xg\xfb\x8bT9\x00\x13\xccg\x02\r\xdf\x1f,\x0b'
-        b'\x81\x02\x14b\r;"\xabP1D\x0c>5\xea\xb6\xf4\x81)\x8f\x9e\x9f\x08'
-    )
-
-    sig3 = der_encode_dsa_signature(0, 0)
-    assert sig3 == b"0\x06\x02\x01\x00\x02\x01\x00"
-
-    sig4 = der_encode_dsa_signature(-1, 0)
-    assert sig4 == b"0\x06\x02\x01\xFF\x02\x01\x00"
-
-
 def test_load_nist_vectors():
     vector_data = textwrap.dedent("""
     # CAVS 11.1
diff --git a/tests/utils.py b/tests/utils.py
index 01ab4e6..37efc58 100644
--- a/tests/utils.py
+++ b/tests/utils.py
@@ -9,9 +9,6 @@
 import re
 from contextlib import contextmanager
 
-from pyasn1.codec.der import encoder
-from pyasn1.type import namedtype, univ
-
 import pytest
 
 import six
@@ -73,20 +70,6 @@
     assert exc_info.value._reason is reason
 
 
-class _DSSSigValue(univ.Sequence):
-    componentType = namedtype.NamedTypes(
-        namedtype.NamedType('r', univ.Integer()),
-        namedtype.NamedType('s', univ.Integer())
-    )
-
-
-def der_encode_dsa_signature(r, s):
-    sig = _DSSSigValue()
-    sig.setComponentByName('r', r)
-    sig.setComponentByName('s', s)
-    return encoder.encode(sig)
-
-
 def load_vectors_from_file(filename, loader, mode="r"):
     with cryptography_vectors.open_vector_file(filename, mode) as vector_file:
         return loader(vector_file)
diff --git a/tox.ini b/tox.ini
index 151b553..85a095b 100644
--- a/tox.ini
+++ b/tox.ini
@@ -7,7 +7,6 @@
     coverage
     iso8601
     pretend
-    pyasn1
     pytest
     ./vectors
 commands =