aboutsummaryrefslogblamecommitdiff
path: root/frozen_deps/ecdsa/test_der.py
blob: 746d9277e3808d61a30d1afcd3ed1bd2d940a2c8 (plain) (tree)
1
2
3
4
5



                                                                    
 









                                      








                     






                                                                    
                                                 


                                              
                                             

                                             
                                                        





                                                    
                                                     


                                              
                                         


                                              
                                 

                                    
                                                    




                                   
                                                    




                                   
                                                        









                                                                              
                                                        


                                              
                                      


                                              
                                      


                                              
                                  

                                            
                                                              


                                              
                                          

                                         
                                                                


                                              
                              


                                              
                                          







                                                                         
                                       
                                                       
                                               

                                       


                                                                     
 
                                                  


                                                  
                                       
                                         
                                              



                                                                
                                                  




                                                                           
                                       
                                         
                                                     



                                                                
                                                  

                                        
                                              
 
                                                      

                                
                                                                   


                                           
                                            


                                                    
                                    


                                           
                                        




                                                       
                                       
                                                       
                                                              

                                       


                                                                            
 

                                           

                                       
                                       
                                         
                                                                 


                                       

                                       

                                              
                                       
                                         
                                                                    


                                       

                                            

                                
                                                               
 

                                           


                                              
                                                       


                                              
                                               


                                              
                                       


                                              
                                           


                                                    
                                                    


                                                   
                                                           


                                                    
                                                   


                                              
                                                       



                                         
                                                       

                         
                                                        

                             
                                                                   










                                                                            


                                                                  



                                      
                                                      


                                   
                                                  


                              
                                              










                                                            
                                              












                                                                
                                   


                                                       

                                                                 



                                                       

                                                          



                                              
                                              


                                              
                                                      


                                                        
                                                  

                                   

                                                      


                                       

                                                  



                                              
                              


                                              
                                  


                                              
                                      


                                              
                                      


                                              
                                              


                                              
                                              


             
                                                  










                                                                       




                                                                            






                                                  
                      
                             
# compatibility with Python 2.6, for that we need unittest2 package,
# which is not available on 3.3 or 3.4
import warnings
from binascii import hexlify

try:
    import unittest2 as unittest
except ImportError:
    import unittest
from six import b
import hypothesis.strategies as st
from hypothesis import given, example
import pytest
from ._compat import str_idx_as_int
from .curves import NIST256p, NIST224p
from .der import (
    remove_integer,
    UnexpectedDER,
    read_length,
    encode_bitstring,
    remove_bitstring,
    remove_object,
    encode_oid,
)


class TestRemoveInteger(unittest.TestCase):
    # DER requires the integers to be 0-padded only if they would be
    # interpreted as negative, check if those errors are detected
    def test_non_minimal_encoding(self):
        with self.assertRaises(UnexpectedDER):
            remove_integer(b("\x02\x02\x00\x01"))

    def test_negative_with_high_bit_set(self):
        with self.assertRaises(UnexpectedDER):
            remove_integer(b("\x02\x01\x80"))

    def test_minimal_with_high_bit_set(self):
        val, rem = remove_integer(b("\x02\x02\x00\x80"))

        self.assertEqual(val, 0x80)
        self.assertFalse(rem)

    def test_two_zero_bytes_with_high_bit_set(self):
        with self.assertRaises(UnexpectedDER):
            remove_integer(b("\x02\x03\x00\x00\xff"))

    def test_zero_length_integer(self):
        with self.assertRaises(UnexpectedDER):
            remove_integer(b("\x02\x00"))

    def test_empty_string(self):
        with self.assertRaises(UnexpectedDER):
            remove_integer(b(""))

    def test_encoding_of_zero(self):
        val, rem = remove_integer(b("\x02\x01\x00"))

        self.assertEqual(val, 0)
        self.assertFalse(rem)

    def test_encoding_of_127(self):
        val, rem = remove_integer(b("\x02\x01\x7f"))

        self.assertEqual(val, 127)
        self.assertFalse(rem)

    def test_encoding_of_128(self):
        val, rem = remove_integer(b("\x02\x02\x00\x80"))

        self.assertEqual(val, 128)
        self.assertFalse(rem)


class TestReadLength(unittest.TestCase):
    # DER requires the lengths between 0 and 127 to be encoded using the short
    # form and lengths above that encoded with minimal number of bytes
    # necessary
    def test_zero_length(self):
        self.assertEqual((0, 1), read_length(b("\x00")))

    def test_two_byte_zero_length(self):
        with self.assertRaises(UnexpectedDER):
            read_length(b("\x81\x00"))

    def test_two_byte_small_length(self):
        with self.assertRaises(UnexpectedDER):
            read_length(b("\x81\x7f"))

    def test_long_form_with_zero_length(self):
        with self.assertRaises(UnexpectedDER):
            read_length(b("\x80"))

    def test_smallest_two_byte_length(self):
        self.assertEqual((128, 2), read_length(b("\x81\x80")))

    def test_zero_padded_length(self):
        with self.assertRaises(UnexpectedDER):
            read_length(b("\x82\x00\x80"))

    def test_two_three_byte_length(self):
        self.assertEqual((256, 3), read_length(b"\x82\x01\x00"))

    def test_empty_string(self):
        with self.assertRaises(UnexpectedDER):
            read_length(b(""))

    def test_length_overflow(self):
        with self.assertRaises(UnexpectedDER):
            read_length(b("\x83\x01\x00"))


class TestEncodeBitstring(unittest.TestCase):
    # DER requires BIT STRINGS to include a number of padding bits in the
    # encoded byte string, that padding must be between 0 and 7

    def test_old_call_convention(self):
        """This is the old way to use the function."""
        warnings.simplefilter("always")
        with pytest.warns(DeprecationWarning) as warns:
            der = encode_bitstring(b"\x00\xff")

        self.assertEqual(len(warns), 1)
        self.assertIn(
            "unused= needs to be specified", warns[0].message.args[0]
        )

        self.assertEqual(der, b"\x03\x02\x00\xff")

    def test_new_call_convention(self):
        """This is how it should be called now."""
        warnings.simplefilter("always")
        with pytest.warns(None) as warns:
            der = encode_bitstring(b"\xff", 0)

        # verify that new call convention doesn't raise Warnings
        self.assertEqual(len(warns), 0)

        self.assertEqual(der, b"\x03\x02\x00\xff")

    def test_implicit_unused_bits(self):
        """
        Writing bit string with already included the number of unused bits.
        """
        warnings.simplefilter("always")
        with pytest.warns(None) as warns:
            der = encode_bitstring(b"\x00\xff", None)

        # verify that new call convention doesn't raise Warnings
        self.assertEqual(len(warns), 0)

        self.assertEqual(der, b"\x03\x02\x00\xff")

    def test_explicit_unused_bits(self):
        der = encode_bitstring(b"\xff\xf0", 4)

        self.assertEqual(der, b"\x03\x03\x04\xff\xf0")

    def test_empty_string(self):
        self.assertEqual(encode_bitstring(b"", 0), b"\x03\x01\x00")

    def test_invalid_unused_count(self):
        with self.assertRaises(ValueError):
            encode_bitstring(b"\xff\x00", 8)

    def test_invalid_unused_with_empty_string(self):
        with self.assertRaises(ValueError):
            encode_bitstring(b"", 1)

    def test_non_zero_padding_bits(self):
        with self.assertRaises(ValueError):
            encode_bitstring(b"\xff", 2)


class TestRemoveBitstring(unittest.TestCase):
    def test_old_call_convention(self):
        """This is the old way to call the function."""
        warnings.simplefilter("always")
        with pytest.warns(DeprecationWarning) as warns:
            bits, rest = remove_bitstring(b"\x03\x02\x00\xff")

        self.assertEqual(len(warns), 1)
        self.assertIn(
            "expect_unused= needs to be specified", warns[0].message.args[0]
        )

        self.assertEqual(bits, b"\x00\xff")
        self.assertEqual(rest, b"")

    def test_new_call_convention(self):
        warnings.simplefilter("always")
        with pytest.warns(None) as warns:
            bits, rest = remove_bitstring(b"\x03\x02\x00\xff", 0)

        self.assertEqual(len(warns), 0)

        self.assertEqual(bits, b"\xff")
        self.assertEqual(rest, b"")

    def test_implicit_unexpected_unused(self):
        warnings.simplefilter("always")
        with pytest.warns(None) as warns:
            bits, rest = remove_bitstring(b"\x03\x02\x00\xff", None)

        self.assertEqual(len(warns), 0)

        self.assertEqual(bits, (b"\xff", 0))
        self.assertEqual(rest, b"")

    def test_with_padding(self):
        ret, rest = remove_bitstring(b"\x03\x02\x04\xf0", None)

        self.assertEqual(ret, (b"\xf0", 4))
        self.assertEqual(rest, b"")

    def test_not_a_bitstring(self):
        with self.assertRaises(UnexpectedDER):
            remove_bitstring(b"\x02\x02\x00\xff", None)

    def test_empty_encoding(self):
        with self.assertRaises(UnexpectedDER):
            remove_bitstring(b"\x03\x00", None)

    def test_empty_string(self):
        with self.assertRaises(UnexpectedDER):
            remove_bitstring(b"", None)

    def test_no_length(self):
        with self.assertRaises(UnexpectedDER):
            remove_bitstring(b"\x03", None)

    def test_unexpected_number_of_unused_bits(self):
        with self.assertRaises(UnexpectedDER):
            remove_bitstring(b"\x03\x02\x00\xff", 1)

    def test_invalid_encoding_of_unused_bits(self):
        with self.assertRaises(UnexpectedDER):
            remove_bitstring(b"\x03\x03\x08\xff\x00", None)

    def test_invalid_encoding_of_empty_string(self):
        with self.assertRaises(UnexpectedDER):
            remove_bitstring(b"\x03\x01\x01", None)

    def test_invalid_padding_bits(self):
        with self.assertRaises(UnexpectedDER):
            remove_bitstring(b"\x03\x02\x01\xff", None)


class TestStrIdxAsInt(unittest.TestCase):
    def test_str(self):
        self.assertEqual(115, str_idx_as_int("str", 0))

    def test_bytes(self):
        self.assertEqual(115, str_idx_as_int(b"str", 0))

    def test_bytearray(self):
        self.assertEqual(115, str_idx_as_int(bytearray(b"str"), 0))


class TestEncodeOid(unittest.TestCase):
    def test_pub_key_oid(self):
        oid_ecPublicKey = encode_oid(1, 2, 840, 10045, 2, 1)
        self.assertEqual(hexlify(oid_ecPublicKey), b("06072a8648ce3d0201"))

    def test_nist224p_oid(self):
        self.assertEqual(hexlify(NIST224p.encoded_oid), b("06052b81040021"))

    def test_nist256p_oid(self):
        self.assertEqual(
            hexlify(NIST256p.encoded_oid), b"06082a8648ce3d030107"
        )

    def test_large_second_subid(self):
        # from X.690, section 8.19.5
        oid = encode_oid(2, 999, 3)
        self.assertEqual(oid, b"\x06\x03\x88\x37\x03")

    def test_with_two_subids(self):
        oid = encode_oid(2, 999)
        self.assertEqual(oid, b"\x06\x02\x88\x37")

    def test_zero_zero(self):
        oid = encode_oid(0, 0)
        self.assertEqual(oid, b"\x06\x01\x00")

    def test_with_wrong_types(self):
        with self.assertRaises((TypeError, AssertionError)):
            encode_oid(0, None)

    def test_with_small_first_large_second(self):
        with self.assertRaises(AssertionError):
            encode_oid(1, 40)

    def test_small_first_max_second(self):
        oid = encode_oid(1, 39)
        self.assertEqual(oid, b"\x06\x01\x4f")

    def test_with_invalid_first(self):
        with self.assertRaises(AssertionError):
            encode_oid(3, 39)


class TestRemoveObject(unittest.TestCase):
    @classmethod
    def setUpClass(cls):
        cls.oid_ecPublicKey = encode_oid(1, 2, 840, 10045, 2, 1)

    def test_pub_key_oid(self):
        oid, rest = remove_object(self.oid_ecPublicKey)
        self.assertEqual(rest, b"")
        self.assertEqual(oid, (1, 2, 840, 10045, 2, 1))

    def test_with_extra_bytes(self):
        oid, rest = remove_object(self.oid_ecPublicKey + b"more")
        self.assertEqual(rest, b"more")
        self.assertEqual(oid, (1, 2, 840, 10045, 2, 1))

    def test_with_large_second_subid(self):
        # from X.690, section 8.19.5
        oid, rest = remove_object(b"\x06\x03\x88\x37\x03")
        self.assertEqual(rest, b"")
        self.assertEqual(oid, (2, 999, 3))

    def test_with_padded_first_subid(self):
        with self.assertRaises(UnexpectedDER):
            remove_object(b"\x06\x02\x80\x00")

    def test_with_padded_second_subid(self):
        with self.assertRaises(UnexpectedDER):
            remove_object(b"\x06\x04\x88\x37\x80\x01")

    def test_with_missing_last_byte_of_multi_byte(self):
        with self.assertRaises(UnexpectedDER):
            remove_object(b"\x06\x03\x88\x37\x83")

    def test_with_two_subids(self):
        oid, rest = remove_object(b"\x06\x02\x88\x37")
        self.assertEqual(rest, b"")
        self.assertEqual(oid, (2, 999))

    def test_zero_zero(self):
        oid, rest = remove_object(b"\x06\x01\x00")
        self.assertEqual(rest, b"")
        self.assertEqual(oid, (0, 0))

    def test_empty_string(self):
        with self.assertRaises(UnexpectedDER):
            remove_object(b"")

    def test_missing_length(self):
        with self.assertRaises(UnexpectedDER):
            remove_object(b"\x06")

    def test_empty_oid(self):
        with self.assertRaises(UnexpectedDER):
            remove_object(b"\x06\x00")

    def test_empty_oid_overflow(self):
        with self.assertRaises(UnexpectedDER):
            remove_object(b"\x06\x01")

    def test_with_wrong_type(self):
        with self.assertRaises(UnexpectedDER):
            remove_object(b"\x04\x02\x88\x37")

    def test_with_too_long_length(self):
        with self.assertRaises(UnexpectedDER):
            remove_object(b"\x06\x03\x88\x37")


@st.composite
def st_oid(draw, max_value=2 ** 512, max_size=50):
    """
    Hypothesis strategy that returns valid OBJECT IDENTIFIERs as tuples

    :param max_value: maximum value of any single sub-identifier
    :param max_size: maximum length of the generated OID
    """
    first = draw(st.integers(min_value=0, max_value=2))
    if first < 2:
        second = draw(st.integers(min_value=0, max_value=39))
    else:
        second = draw(st.integers(min_value=0, max_value=max_value))
    rest = draw(
        st.lists(
            st.integers(min_value=0, max_value=max_value), max_size=max_size
        )
    )
    return (first, second) + tuple(rest)


@given(st_oid())
def test_oids(ids):
    encoded_oid = encode_oid(*ids)
    decoded_oid, rest = remove_object(encoded_oid)
    assert rest == b""
    assert decoded_oid == ids