diff options
Diffstat (limited to 'tests/hazmat/primitives')
45 files changed, 7387 insertions, 1434 deletions
diff --git a/tests/hazmat/primitives/fixtures_ec.py b/tests/hazmat/primitives/fixtures_ec.py new file mode 100644 index 00000000..21c69031 --- /dev/null +++ b/tests/hazmat/primitives/fixtures_ec.py @@ -0,0 +1,296 @@ +# 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 cryptography.hazmat.primitives.asymmetric import ec + + +EC_KEY_SECT571R1 = ec.EllipticCurvePrivateNumbers( + private_value=int( + '213997069697108634621868251335076179190383272087548888968788698953' + '131928375431570122753130054966269038244076049869476736547896549201' + '7388482714521707824160638375437887802901' + ), + public_numbers=ec.EllipticCurvePublicNumbers( + curve=ec.SECT571R1(), + x=int( + '42585672410900520895287019432267514156432686681290164230262278' + '54789182447139054594501570747809649335533486119017169439209005' + '883737780433424425566023654583165324498640038089' + ), + y=int( + '13822523320209387572500458104799806851658024537477228250738334' + '46977851514777531296572763848253279034733550774927720436494321' + '97281333379623823457479233585424800362717541750' + ) + ) +) + +EC_KEY_SECT409R1 = ec.EllipticCurvePrivateNumbers( + private_value=int( + '604993237916498765317587097853603474519114726157206838874832379003' + '281871982139714656205843929472002062791572217653118715727' + ), + public_numbers=ec.EllipticCurvePublicNumbers( + curve=ec.SECT409R1(), + x=int( + '76237701339268928039087238870073679814646664010783544301589269' + '2272579213400205907766385199643053767195204247826349822350081' + ), + y=int( + '10056668929618383045204866060110626563392345494925302478351744' + '01475129090774493235522729123877384838835703483224447476728811' + ) + ) +) + +EC_KEY_SECT283R1 = ec.EllipticCurvePrivateNumbers( + private_value=int( + '589705077255658434962118789801402573495547207239917043241273753671' + '0603230261342427657' + ), + public_numbers=ec.EllipticCurvePublicNumbers( + curve=ec.SECT283R1(), + x=int( + '10694213430317013187241490088760888472172922291550831393222973' + '531614941756901942108493' + ), + y=int( + '11461553100313943515373601367527399649593366728262918214942116' + '4359557613202950705170' + ) + ) +) + +EC_KEY_SECT233R1 = ec.EllipticCurvePrivateNumbers( + private_value=int( + '343470067105388144757135261232658742142830154753739648095101899829' + '8288' + ), + public_numbers=ec.EllipticCurvePublicNumbers( + curve=ec.SECT233R1(), + x=int( + '74494951569151557692195071465128140646140765188698294062550374' + '71118267' + ), + y=int( + '48699150823022962508544923825876164485917001162461401797511748' + '44872205' + ) + ) +) + +EC_KEY_SECT163R2 = ec.EllipticCurvePrivateNumbers( + private_value=int( + '11788436193853888218177032687141056784083668635' + ), + public_numbers=ec.EllipticCurvePublicNumbers( + curve=ec.SECT163R2(), + x=int( + '5247234453330640212490501030772203801908103222463' + ), + y=int( + '3172513801099088785224248292142866317754124455206' + ) + ) +) + +EC_KEY_SECT571K1 = ec.EllipticCurvePrivateNumbers( + private_value=int( + '592811051234886966121888758661314648311634839499582476726008738218' + '165015048237934517672316204181933804884636855291118594744334592153' + '883208936227914544246799490897169723387' + ), + public_numbers=ec.EllipticCurvePublicNumbers( + curve=ec.SECT571K1(), + x=int( + '81362471461936552203898455874182916939857774872643607884250052' + '29301336524105230729653881789373412990921493551253481866317181' + '50644729351721577822595637058949405764944491655' + ), + y=int( + '14058041260812945396067821061063618047896814719828637241661260' + '31235681542401975593036630733881695595289523801041910183736211' + '587294494888450327374439795428519848065589000434' + ) + ) +) + +EC_KEY_SECT409K1 = ec.EllipticCurvePrivateNumbers( + private_value=int( + '110321743150399087059465162400463719641470113494908091197354523708' + '934106732952992153105338671368548199643686444619485307877' + ), + public_numbers=ec.EllipticCurvePublicNumbers( + curve=ec.SECT409K1(), + x=int( + '62280214209410363493525178797944995742119600145953755916426161' + '0790364158569265348038207313261547476506319796469776797725796' + ), + y=int( + '46653883749102474289095010108777579907422472804577185369332018' + '7318642669590280811057512951467298158275464566214288556375885' + ) + ) +) + +EC_KEY_SECT283K1 = ec.EllipticCurvePrivateNumbers( + private_value=int( + '182508394415444014156574733141549331538128234395356466108310015130' + '3868915489347291850' + ), + public_numbers=ec.EllipticCurvePublicNumbers( + curve=ec.SECT283K1(), + x=int( + '31141647206111886426350703123670451554123180910379592764773885' + '2959123367428352287032' + ), + y=int( + '71787460144483665964585187837283963089964760704065205376175384' + '58957627834444017112582' + ) + ) +) + +EC_KEY_SECT233K1 = ec.EllipticCurvePrivateNumbers( + private_value=int( + '172670089647474613734091436081960550801254775902629891892394471086' + '2070' + ), + public_numbers=ec.EllipticCurvePublicNumbers( + curve=ec.SECT233K1(), + x=int( + '55693911474339510991521579392202889561373678973929426354737048' + '68129172' + ), + y=int( + '11025856248546376145959939911850923631416718241836051344384802' + '737277815' + ) + ) +) + +EC_KEY_SECT163K1 = ec.EllipticCurvePrivateNumbers( + private_value=int( + '3699303791425402204035307605170569820290317991287' + ), + public_numbers=ec.EllipticCurvePublicNumbers( + curve=ec.SECT163K1(), + x=int( + '4479755902310063321544063130576409926980094120721' + ), + y=int( + '3051218481937171839039826690648109285113977745779' + ) + ) +) + +EC_KEY_SECP521R1 = ec.EllipticCurvePrivateNumbers( + private_value=int( + '662751235215460886290293902658128847495347691199214706697089140769' + '672273950767961331442265530524063943548846724348048614239791498442' + '5997823106818915698960565' + ), + public_numbers=ec.EllipticCurvePublicNumbers( + curve=ec.SECP521R1(), + x=int( + '12944742826257420846659527752683763193401384271391513286022917' + '29910013082920512632908350502247952686156279140016049549948975' + '670668730618745449113644014505462' + ), + y=int( + '10784108810271976186737587749436295782985563640368689081052886' + '16296815984553198866894145509329328086635278430266482551941240' + '591605833440825557820439734509311' + ) + ) +) + +EC_KEY_SECP384R1 = ec.EllipticCurvePrivateNumbers( + private_value=int( + '280814107134858470598753916394807521398239633534281633982576099083' + '35787109896602102090002196616273211495718603965098' + ), + public_numbers=ec.EllipticCurvePublicNumbers( + curve=ec.SECP384R1(), + x=int( + '10036914308591746758780165503819213553101287571902957054148542' + '504671046744460374996612408381962208627004841444205030' + ), + y=int( + '17337335659928075994560513699823544906448896792102247714689323' + '575406618073069185107088229463828921069465902299522926' + ) + ) +) + +EC_KEY_SECP256R1 = ec.EllipticCurvePrivateNumbers( + private_value=int( + '271032978511595617649844168316234344656921218699414461240502635010' + '25776962849' + ), + public_numbers=ec.EllipticCurvePublicNumbers( + curve=ec.SECP256R1(), + x=int( + '49325986169170464532722748935508337546545346352733747948730305' + '442770101441241' + ), + y=int( + '51709162888529903487188595007092772817469799707382623884187518' + '455962250433661' + ) + ) +) + +EC_KEY_SECP256K1 = ec.EllipticCurvePrivateNumbers( + private_value=int( + '683341569008473593765879222774207677458810362976327530563215318048' + '64380736732' + ), + public_numbers=ec.EllipticCurvePublicNumbers( + curve=ec.SECP256K1(), + x=int( + '59251322975795306609293064274738085741081547489119277536110995' + '120127593127884' + ), + y=int( + '10334192001480392039227801832201340147605940717841294644187071' + '8261641142297801' + ) + ) +) + +EC_KEY_SECP224R1 = ec.EllipticCurvePrivateNumbers( + private_value=int( + '234854340492774342642505519082413233282383066880756900834047566251' + '50' + ), + public_numbers=ec.EllipticCurvePublicNumbers( + curve=ec.SECP224R1(), + x=int( + '51165676638271204691095081341581621487998422645261573824239666' + '1214' + ), + y=int( + '14936601450555711309158397172719963843891926209168533453717969' + '1265' + ) + ) +) + +EC_KEY_SECP192R1 = ec.EllipticCurvePrivateNumbers( + private_value=int( + '4534766128536179420071447168915990251715442361606049349869' + ), + public_numbers=ec.EllipticCurvePublicNumbers( + curve=ec.SECP192R1(), + x=int( + '5415069751170397888083674339683360671310515485781457536999' + ), + y=int( + '18671605334415960797751252911958331304288357195986572776' + ) + ) +) diff --git a/tests/hazmat/primitives/fixtures_rsa.py b/tests/hazmat/primitives/fixtures_rsa.py index f93361de..a531783e 100644 --- a/tests/hazmat/primitives/fixtures_rsa.py +++ b/tests/hazmat/primitives/fixtures_rsa.py @@ -529,3 +529,75 @@ RSA_KEY_2048 = RSAPrivateNumbers( "de04fd053846ca10a223b10cc841cc80fdebee44f3114c13e886af583", 16), ) ) + +RSA_KEY_2048_ALT = RSAPrivateNumbers( + d=int( + "7522768467449591813737881904131688860626637897199391200040629" + "8641018746450502628484395471408986929218353894683769457466923" + "3079369551423094451013669595729568593462009746342148367797495" + "5529909313614750246672441810743580455199636293179539903480635" + "3091286716112931976896334411287175213124504134181121011488550" + "5290054443979198998564749640800633368957384058700741073997703" + "8877364695937023906368630297588990131009278072614118207348356" + "4640244134189285070202534488517371577359510236833464698189075" + "5160693085297816063285814039518178249628112908466649245545732" + "5791532385553960363601827996980725025898649392004494256400884" + "092073" + ), + dmp1=int( + "5847872614112935747739644055317429405973942336206460017493394" + "9737607778799766591021036792892472774720417920838206576785118" + "8889624058962939702950175807073343659386156232294197300491647" + "1029508414050591959344812347424476498076532682798598325230069" + "0925827594762920534235575029199380552228825468180187156871965" + "973" + ), + dmq1=int( + "2949536259161239302081155875068405238857801001054083407704879" + "8210876832264504685327766351157044892283801611558399025326793" + "4131638001934454489864437565651739832511702151461257267169691" + "6611992398459006200708626815153304591390855807749769768978152" + "9854112656599931724820610358669306523835327459478374630794532" + "167" + ), + iqmp=int( + "7331180989818931535458916053540252830484856703208982675535284" + "4613815808798190559315018094080936347757336989616401164752221" + "8101156529898067044923499386460167055405998646366011838018441" + "3678947694258190172377716154009305082091341215866326061721180" + "3836418654472188816187630316821692982783286322262994892003058" + "782" + ), + p=int( + "1460007723851883695617573533155574746587863843382715314919865" + "2434108956187429726002840717317310431378483921058946835896252" + "7109559207437158778332364464259678946305487699031865937075508" + "8616612925453842458055546540240601585731206561647892336916583" + "0023641764106581040198845259766246869529221084602380669333021" + "0819" + ), + q=int( + "1433897765867889178402883410610177836503402597775250087462018" + "4617952933433119527945447840336616357136736935069377619782227" + "2822380830300262175671282877680573202309319960687756231128996" + "9764855320953993690199846269451095044922353809602378616938811" + "7513900906279873343591486841303392490561500301994171338761080" + "4439" + ), + public_numbers=RSAPublicNumbers( + e=65537, + n=int( + "209350181338107812610165420955871971489973659392253291327" + "839812910252466502190690572476688311285621239204212139711" + "207388949164851984253143698667018532039612470954223918242" + "145976986600705122576087630525229796950722166468064721258" + "490916138706756006902066136471049807637157890128560592039" + "941717275079733754782848729566190631725183735944031456237" + "089928120178187552521649483240599003240074352860189285952" + "078970127554801074176375499583703254849309993132931268013" + "715070507278514207864914944621214574162116786377990456375" + "964817771730371110612100247262908550409785456157505694419" + "00451152778245269283276012328748538414051025541" + ) + ) +) diff --git a/tests/hazmat/primitives/test_3des.py b/tests/hazmat/primitives/test_3des.py index 0197353e..0f0f1470 100644 --- a/tests/hazmat/primitives/test_3des.py +++ b/tests/hazmat/primitives/test_3des.py @@ -22,13 +22,13 @@ from ...utils import load_nist_vectors @pytest.mark.supported( only_if=lambda backend: backend.cipher_supported( - algorithms.TripleDES("\x00" * 8), modes.CBC("\x00" * 8) + algorithms.TripleDES(b"\x00" * 8), modes.CBC(b"\x00" * 8) ), skip_message="Does not support TripleDES CBC", ) @pytest.mark.requires_backend_interface(interface=CipherBackend) class TestTripleDESModeCBC(object): - test_KAT = generate_encrypt_test( + test_kat = generate_encrypt_test( load_nist_vectors, os.path.join("ciphers", "3DES", "CBC"), [ @@ -42,7 +42,7 @@ class TestTripleDESModeCBC(object): lambda iv, **kwargs: modes.CBC(binascii.unhexlify(iv)), ) - test_MMT = generate_encrypt_test( + test_mmt = generate_encrypt_test( load_nist_vectors, os.path.join("ciphers", "3DES", "CBC"), [ @@ -59,13 +59,13 @@ class TestTripleDESModeCBC(object): @pytest.mark.supported( only_if=lambda backend: backend.cipher_supported( - algorithms.TripleDES("\x00" * 8), modes.OFB("\x00" * 8) + algorithms.TripleDES(b"\x00" * 8), modes.OFB(b"\x00" * 8) ), skip_message="Does not support TripleDES OFB", ) @pytest.mark.requires_backend_interface(interface=CipherBackend) class TestTripleDESModeOFB(object): - test_KAT = generate_encrypt_test( + test_kat = generate_encrypt_test( load_nist_vectors, os.path.join("ciphers", "3DES", "OFB"), [ @@ -79,7 +79,7 @@ class TestTripleDESModeOFB(object): lambda iv, **kwargs: modes.OFB(binascii.unhexlify(iv)), ) - test_MMT = generate_encrypt_test( + test_mmt = generate_encrypt_test( load_nist_vectors, os.path.join("ciphers", "3DES", "OFB"), [ @@ -96,13 +96,13 @@ class TestTripleDESModeOFB(object): @pytest.mark.supported( only_if=lambda backend: backend.cipher_supported( - algorithms.TripleDES("\x00" * 8), modes.CFB("\x00" * 8) + algorithms.TripleDES(b"\x00" * 8), modes.CFB(b"\x00" * 8) ), skip_message="Does not support TripleDES CFB", ) @pytest.mark.requires_backend_interface(interface=CipherBackend) class TestTripleDESModeCFB(object): - test_KAT = generate_encrypt_test( + test_kat = generate_encrypt_test( load_nist_vectors, os.path.join("ciphers", "3DES", "CFB"), [ @@ -116,7 +116,7 @@ class TestTripleDESModeCFB(object): lambda iv, **kwargs: modes.CFB(binascii.unhexlify(iv)), ) - test_MMT = generate_encrypt_test( + test_mmt = generate_encrypt_test( load_nist_vectors, os.path.join("ciphers", "3DES", "CFB"), [ @@ -133,13 +133,13 @@ class TestTripleDESModeCFB(object): @pytest.mark.supported( only_if=lambda backend: backend.cipher_supported( - algorithms.TripleDES("\x00" * 8), modes.CFB8("\x00" * 8) + algorithms.TripleDES(b"\x00" * 8), modes.CFB8(b"\x00" * 8) ), skip_message="Does not support TripleDES CFB8", ) @pytest.mark.requires_backend_interface(interface=CipherBackend) class TestTripleDESModeCFB8(object): - test_KAT = generate_encrypt_test( + test_kat = generate_encrypt_test( load_nist_vectors, os.path.join("ciphers", "3DES", "CFB"), [ @@ -153,7 +153,7 @@ class TestTripleDESModeCFB8(object): lambda iv, **kwargs: modes.CFB8(binascii.unhexlify(iv)), ) - test_MMT = generate_encrypt_test( + test_mmt = generate_encrypt_test( load_nist_vectors, os.path.join("ciphers", "3DES", "CFB"), [ @@ -170,13 +170,13 @@ class TestTripleDESModeCFB8(object): @pytest.mark.supported( only_if=lambda backend: backend.cipher_supported( - algorithms.TripleDES("\x00" * 8), modes.ECB() + algorithms.TripleDES(b"\x00" * 8), modes.ECB() ), skip_message="Does not support TripleDES ECB", ) @pytest.mark.requires_backend_interface(interface=CipherBackend) class TestTripleDESModeECB(object): - test_KAT = generate_encrypt_test( + test_kat = generate_encrypt_test( load_nist_vectors, os.path.join("ciphers", "3DES", "ECB"), [ @@ -190,7 +190,7 @@ class TestTripleDESModeECB(object): lambda **kwargs: modes.ECB(), ) - test_MMT = generate_encrypt_test( + test_mmt = generate_encrypt_test( load_nist_vectors, os.path.join("ciphers", "3DES", "ECB"), [ diff --git a/tests/hazmat/primitives/test_aead.py b/tests/hazmat/primitives/test_aead.py new file mode 100644 index 00000000..4f6bc7f4 --- /dev/null +++ b/tests/hazmat/primitives/test_aead.py @@ -0,0 +1,446 @@ +# 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 binascii +import os + +import pytest + +from cryptography.exceptions import InvalidTag, UnsupportedAlgorithm, _Reasons +from cryptography.hazmat.backends.interfaces import CipherBackend +from cryptography.hazmat.primitives.ciphers.aead import ( + AESCCM, AESGCM, ChaCha20Poly1305 +) + +from .utils import _load_all_params +from ...utils import ( + load_nist_ccm_vectors, load_nist_vectors, load_vectors_from_file, + raises_unsupported_algorithm +) + + +class FakeData(object): + def __len__(self): + return 2 ** 32 + 1 + + +def _aead_supported(cls): + try: + cls(b"0" * 32) + return True + except UnsupportedAlgorithm: + return False + + +@pytest.mark.skipif( + _aead_supported(ChaCha20Poly1305), + reason="Requires OpenSSL without ChaCha20Poly1305 support" +) +@pytest.mark.requires_backend_interface(interface=CipherBackend) +def test_chacha20poly1305_unsupported_on_older_openssl(backend): + with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_CIPHER): + ChaCha20Poly1305(ChaCha20Poly1305.generate_key()) + + +@pytest.mark.skipif( + not _aead_supported(ChaCha20Poly1305), + reason="Does not support ChaCha20Poly1305" +) +@pytest.mark.requires_backend_interface(interface=CipherBackend) +class TestChaCha20Poly1305(object): + def test_data_too_large(self): + key = ChaCha20Poly1305.generate_key() + chacha = ChaCha20Poly1305(key) + nonce = b"0" * 12 + + with pytest.raises(OverflowError): + chacha.encrypt(nonce, FakeData(), b"") + + with pytest.raises(OverflowError): + chacha.encrypt(nonce, b"", FakeData()) + + def test_generate_key(self): + key = ChaCha20Poly1305.generate_key() + assert len(key) == 32 + + def test_bad_key(self, backend): + with pytest.raises(TypeError): + ChaCha20Poly1305(object()) + + with pytest.raises(ValueError): + ChaCha20Poly1305(b"0" * 31) + + @pytest.mark.parametrize( + ("nonce", "data", "associated_data"), + [ + [object(), b"data", b""], + [b"0" * 12, object(), b""], + [b"0" * 12, b"data", object()] + ] + ) + def test_params_not_bytes_encrypt(self, nonce, data, associated_data, + backend): + key = ChaCha20Poly1305.generate_key() + chacha = ChaCha20Poly1305(key) + with pytest.raises(TypeError): + chacha.encrypt(nonce, data, associated_data) + + with pytest.raises(TypeError): + chacha.decrypt(nonce, data, associated_data) + + def test_nonce_not_12_bytes(self, backend): + key = ChaCha20Poly1305.generate_key() + chacha = ChaCha20Poly1305(key) + with pytest.raises(ValueError): + chacha.encrypt(b"00", b"hello", b"") + + with pytest.raises(ValueError): + chacha.decrypt(b"00", b"hello", b"") + + def test_decrypt_data_too_short(self, backend): + key = ChaCha20Poly1305.generate_key() + chacha = ChaCha20Poly1305(key) + with pytest.raises(InvalidTag): + chacha.decrypt(b"0" * 12, b"0", None) + + def test_associated_data_none_equal_to_empty_bytestring(self, backend): + key = ChaCha20Poly1305.generate_key() + chacha = ChaCha20Poly1305(key) + nonce = os.urandom(12) + ct1 = chacha.encrypt(nonce, b"some_data", None) + ct2 = chacha.encrypt(nonce, b"some_data", b"") + assert ct1 == ct2 + pt1 = chacha.decrypt(nonce, ct1, None) + pt2 = chacha.decrypt(nonce, ct2, b"") + assert pt1 == pt2 + + @pytest.mark.parametrize( + "vector", + load_vectors_from_file( + os.path.join("ciphers", "ChaCha20Poly1305", "openssl.txt"), + load_nist_vectors + ) + ) + def test_openssl_vectors(self, vector, backend): + key = binascii.unhexlify(vector["key"]) + nonce = binascii.unhexlify(vector["iv"]) + aad = binascii.unhexlify(vector["aad"]) + tag = binascii.unhexlify(vector["tag"]) + pt = binascii.unhexlify(vector["plaintext"]) + ct = binascii.unhexlify(vector["ciphertext"]) + chacha = ChaCha20Poly1305(key) + if vector.get("result") == b"CIPHERFINAL_ERROR": + with pytest.raises(InvalidTag): + chacha.decrypt(nonce, ct + tag, aad) + else: + computed_pt = chacha.decrypt(nonce, ct + tag, aad) + assert computed_pt == pt + computed_ct = chacha.encrypt(nonce, pt, aad) + assert computed_ct == ct + tag + + @pytest.mark.parametrize( + "vector", + load_vectors_from_file( + os.path.join("ciphers", "ChaCha20Poly1305", "boringssl.txt"), + load_nist_vectors + ) + ) + def test_boringssl_vectors(self, vector, backend): + key = binascii.unhexlify(vector["key"]) + nonce = binascii.unhexlify(vector["nonce"]) + if vector["ad"].startswith(b'"'): + aad = vector["ad"][1:-1] + else: + aad = binascii.unhexlify(vector["ad"]) + tag = binascii.unhexlify(vector["tag"]) + if vector["in"].startswith(b'"'): + pt = vector["in"][1:-1] + else: + pt = binascii.unhexlify(vector["in"]) + ct = binascii.unhexlify(vector["ct"].strip(b'"')) + chacha = ChaCha20Poly1305(key) + computed_pt = chacha.decrypt(nonce, ct + tag, aad) + assert computed_pt == pt + computed_ct = chacha.encrypt(nonce, pt, aad) + assert computed_ct == ct + tag + + def test_buffer_protocol(self, backend): + key = ChaCha20Poly1305.generate_key() + chacha = ChaCha20Poly1305(key) + pt = b"encrypt me" + ad = b"additional" + nonce = os.urandom(12) + ct = chacha.encrypt(nonce, pt, ad) + computed_pt = chacha.decrypt(nonce, ct, ad) + assert computed_pt == pt + chacha2 = ChaCha20Poly1305(bytearray(key)) + ct2 = chacha2.encrypt(bytearray(nonce), pt, ad) + assert ct2 == ct + computed_pt2 = chacha2.decrypt(bytearray(nonce), ct2, ad) + assert computed_pt2 == pt + + +@pytest.mark.requires_backend_interface(interface=CipherBackend) +class TestAESCCM(object): + def test_data_too_large(self): + key = AESCCM.generate_key(128) + aesccm = AESCCM(key) + nonce = b"0" * 12 + + with pytest.raises(OverflowError): + aesccm.encrypt(nonce, FakeData(), b"") + + with pytest.raises(OverflowError): + aesccm.encrypt(nonce, b"", FakeData()) + + def test_default_tag_length(self, backend): + key = AESCCM.generate_key(128) + aesccm = AESCCM(key) + nonce = os.urandom(12) + pt = b"hello" + ct = aesccm.encrypt(nonce, pt, None) + assert len(ct) == len(pt) + 16 + + def test_invalid_tag_length(self, backend): + key = AESCCM.generate_key(128) + with pytest.raises(ValueError): + AESCCM(key, tag_length=7) + + with pytest.raises(ValueError): + AESCCM(key, tag_length=2) + + with pytest.raises(TypeError): + AESCCM(key, tag_length="notanint") + + def test_invalid_nonce_length(self, backend): + key = AESCCM.generate_key(128) + aesccm = AESCCM(key) + pt = b"hello" + nonce = os.urandom(14) + with pytest.raises(ValueError): + aesccm.encrypt(nonce, pt, None) + + with pytest.raises(ValueError): + aesccm.encrypt(nonce[:6], pt, None) + + @pytest.mark.parametrize( + "vector", + _load_all_params( + os.path.join("ciphers", "AES", "CCM"), + [ + "DVPT128.rsp", "DVPT192.rsp", "DVPT256.rsp", + "VADT128.rsp", "VADT192.rsp", "VADT256.rsp", + "VNT128.rsp", "VNT192.rsp", "VNT256.rsp", + "VPT128.rsp", "VPT192.rsp", "VPT256.rsp", + ], + load_nist_ccm_vectors + ) + ) + def test_vectors(self, vector, backend): + key = binascii.unhexlify(vector["key"]) + nonce = binascii.unhexlify(vector["nonce"]) + adata = binascii.unhexlify(vector["adata"])[:vector["alen"]] + ct = binascii.unhexlify(vector["ct"]) + pt = binascii.unhexlify(vector["payload"])[:vector["plen"]] + aesccm = AESCCM(key, vector["tlen"]) + if vector.get('fail'): + with pytest.raises(InvalidTag): + aesccm.decrypt(nonce, ct, adata) + else: + computed_pt = aesccm.decrypt(nonce, ct, adata) + assert computed_pt == pt + assert aesccm.encrypt(nonce, pt, adata) == ct + + def test_roundtrip(self, backend): + key = AESCCM.generate_key(128) + aesccm = AESCCM(key) + pt = b"encrypt me" + ad = b"additional" + nonce = os.urandom(12) + ct = aesccm.encrypt(nonce, pt, ad) + computed_pt = aesccm.decrypt(nonce, ct, ad) + assert computed_pt == pt + + def test_nonce_too_long(self, backend): + key = AESCCM.generate_key(128) + aesccm = AESCCM(key) + pt = b"encrypt me" * 6600 + # pt can be no more than 65536 bytes when nonce is 13 bytes + nonce = os.urandom(13) + with pytest.raises(ValueError): + aesccm.encrypt(nonce, pt, None) + + @pytest.mark.parametrize( + ("nonce", "data", "associated_data"), + [ + [object(), b"data", b""], + [b"0" * 12, object(), b""], + [b"0" * 12, b"data", object()], + ] + ) + def test_params_not_bytes(self, nonce, data, associated_data, backend): + key = AESCCM.generate_key(128) + aesccm = AESCCM(key) + with pytest.raises(TypeError): + aesccm.encrypt(nonce, data, associated_data) + + def test_bad_key(self, backend): + with pytest.raises(TypeError): + AESCCM(object()) + + with pytest.raises(ValueError): + AESCCM(b"0" * 31) + + def test_bad_generate_key(self, backend): + with pytest.raises(TypeError): + AESCCM.generate_key(object()) + + with pytest.raises(ValueError): + AESCCM.generate_key(129) + + def test_associated_data_none_equal_to_empty_bytestring(self, backend): + key = AESCCM.generate_key(128) + aesccm = AESCCM(key) + nonce = os.urandom(12) + ct1 = aesccm.encrypt(nonce, b"some_data", None) + ct2 = aesccm.encrypt(nonce, b"some_data", b"") + assert ct1 == ct2 + pt1 = aesccm.decrypt(nonce, ct1, None) + pt2 = aesccm.decrypt(nonce, ct2, b"") + assert pt1 == pt2 + + def test_decrypt_data_too_short(self, backend): + key = AESCCM.generate_key(128) + aesccm = AESCCM(key) + with pytest.raises(InvalidTag): + aesccm.decrypt(b"0" * 12, b"0", None) + + def test_buffer_protocol(self, backend): + key = AESCCM.generate_key(128) + aesccm = AESCCM(key) + pt = b"encrypt me" + ad = b"additional" + nonce = os.urandom(12) + ct = aesccm.encrypt(nonce, pt, ad) + computed_pt = aesccm.decrypt(nonce, ct, ad) + assert computed_pt == pt + aesccm2 = AESCCM(bytearray(key)) + ct2 = aesccm2.encrypt(bytearray(nonce), pt, ad) + assert ct2 == ct + computed_pt2 = aesccm2.decrypt(bytearray(nonce), ct2, ad) + assert computed_pt2 == pt + + +def _load_gcm_vectors(): + vectors = _load_all_params( + os.path.join("ciphers", "AES", "GCM"), + [ + "gcmDecrypt128.rsp", + "gcmDecrypt192.rsp", + "gcmDecrypt256.rsp", + "gcmEncryptExtIV128.rsp", + "gcmEncryptExtIV192.rsp", + "gcmEncryptExtIV256.rsp", + ], + load_nist_vectors + ) + return [x for x in vectors if len(x["tag"]) == 32] + + +@pytest.mark.requires_backend_interface(interface=CipherBackend) +class TestAESGCM(object): + def test_data_too_large(self): + key = AESGCM.generate_key(128) + aesgcm = AESGCM(key) + nonce = b"0" * 12 + + with pytest.raises(OverflowError): + aesgcm.encrypt(nonce, FakeData(), b"") + + with pytest.raises(OverflowError): + aesgcm.encrypt(nonce, b"", FakeData()) + + @pytest.mark.parametrize("vector", _load_gcm_vectors()) + def test_vectors(self, vector): + key = binascii.unhexlify(vector["key"]) + nonce = binascii.unhexlify(vector["iv"]) + aad = binascii.unhexlify(vector["aad"]) + ct = binascii.unhexlify(vector["ct"]) + pt = binascii.unhexlify(vector.get("pt", b"")) + tag = binascii.unhexlify(vector["tag"]) + aesgcm = AESGCM(key) + if vector.get("fail") is True: + with pytest.raises(InvalidTag): + aesgcm.decrypt(nonce, ct + tag, aad) + else: + computed_ct = aesgcm.encrypt(nonce, pt, aad) + assert computed_ct[:-16] == ct + assert computed_ct[-16:] == tag + computed_pt = aesgcm.decrypt(nonce, ct + tag, aad) + assert computed_pt == pt + + @pytest.mark.parametrize( + ("nonce", "data", "associated_data"), + [ + [object(), b"data", b""], + [b"0" * 12, object(), b""], + [b"0" * 12, b"data", object()] + ] + ) + def test_params_not_bytes(self, nonce, data, associated_data, backend): + key = AESGCM.generate_key(128) + aesgcm = AESGCM(key) + with pytest.raises(TypeError): + aesgcm.encrypt(nonce, data, associated_data) + + with pytest.raises(TypeError): + aesgcm.decrypt(nonce, data, associated_data) + + def test_invalid_nonce_length(self, backend): + key = AESGCM.generate_key(128) + aesgcm = AESGCM(key) + with pytest.raises(ValueError): + aesgcm.encrypt(b"", b"hi", None) + + def test_bad_key(self, backend): + with pytest.raises(TypeError): + AESGCM(object()) + + with pytest.raises(ValueError): + AESGCM(b"0" * 31) + + def test_bad_generate_key(self, backend): + with pytest.raises(TypeError): + AESGCM.generate_key(object()) + + with pytest.raises(ValueError): + AESGCM.generate_key(129) + + def test_associated_data_none_equal_to_empty_bytestring(self, backend): + key = AESGCM.generate_key(128) + aesgcm = AESGCM(key) + nonce = os.urandom(12) + ct1 = aesgcm.encrypt(nonce, b"some_data", None) + ct2 = aesgcm.encrypt(nonce, b"some_data", b"") + assert ct1 == ct2 + pt1 = aesgcm.decrypt(nonce, ct1, None) + pt2 = aesgcm.decrypt(nonce, ct2, b"") + assert pt1 == pt2 + + def test_buffer_protocol(self, backend): + key = AESGCM.generate_key(128) + aesgcm = AESGCM(key) + pt = b"encrypt me" + ad = b"additional" + nonce = os.urandom(12) + ct = aesgcm.encrypt(nonce, pt, ad) + computed_pt = aesgcm.decrypt(nonce, ct, ad) + assert computed_pt == pt + aesgcm2 = AESGCM(bytearray(key)) + ct2 = aesgcm2.encrypt(bytearray(nonce), pt, ad) + assert ct2 == ct + computed_pt2 = aesgcm2.decrypt(bytearray(nonce), ct2, ad) + assert computed_pt2 == pt diff --git a/tests/hazmat/primitives/test_aes.py b/tests/hazmat/primitives/test_aes.py index 4d48e8ad..d99ba406 100644 --- a/tests/hazmat/primitives/test_aes.py +++ b/tests/hazmat/primitives/test_aes.py @@ -12,19 +12,54 @@ import pytest from cryptography.hazmat.backends.interfaces import CipherBackend from cryptography.hazmat.primitives.ciphers import algorithms, base, modes -from .utils import generate_aead_test, generate_encrypt_test +from .utils import _load_all_params, generate_aead_test, generate_encrypt_test +from ...doubles import DummyMode from ...utils import load_nist_vectors @pytest.mark.supported( only_if=lambda backend: backend.cipher_supported( - algorithms.AES("\x00" * 16), modes.CBC("\x00" * 16) + algorithms.AES(b"\x00" * 32), modes.XTS(b"\x00" * 16) + ), + skip_message="Does not support AES XTS", +) +@pytest.mark.requires_backend_interface(interface=CipherBackend) +class TestAESModeXTS(object): + @pytest.mark.parametrize( + "vector", + # This list comprehension excludes any vector that does not have a + # data unit length that is divisible by 8. The NIST vectors include + # tests for implementations that support encryption of data that is + # not divisible modulo 8, but OpenSSL is not such an implementation. + [x for x in _load_all_params( + os.path.join("ciphers", "AES", "XTS", "tweak-128hexstr"), + ["XTSGenAES128.rsp", "XTSGenAES256.rsp"], + load_nist_vectors + ) if int(x["dataunitlen"]) / 8.0 == int(x["dataunitlen"]) // 8] + ) + def test_xts_vectors(self, vector, backend): + key = binascii.unhexlify(vector["key"]) + tweak = binascii.unhexlify(vector["i"]) + pt = binascii.unhexlify(vector["pt"]) + ct = binascii.unhexlify(vector["ct"]) + cipher = base.Cipher(algorithms.AES(key), modes.XTS(tweak), backend) + enc = cipher.encryptor() + computed_ct = enc.update(pt) + enc.finalize() + assert computed_ct == ct + dec = cipher.decryptor() + computed_pt = dec.update(ct) + dec.finalize() + assert computed_pt == pt + + +@pytest.mark.supported( + only_if=lambda backend: backend.cipher_supported( + algorithms.AES(b"\x00" * 16), modes.CBC(b"\x00" * 16) ), skip_message="Does not support AES CBC", ) @pytest.mark.requires_backend_interface(interface=CipherBackend) class TestAESModeCBC(object): - test_CBC = generate_encrypt_test( + test_cbc = generate_encrypt_test( load_nist_vectors, os.path.join("ciphers", "AES", "CBC"), [ @@ -51,13 +86,13 @@ class TestAESModeCBC(object): @pytest.mark.supported( only_if=lambda backend: backend.cipher_supported( - algorithms.AES("\x00" * 16), modes.ECB() + algorithms.AES(b"\x00" * 16), modes.ECB() ), skip_message="Does not support AES ECB", ) @pytest.mark.requires_backend_interface(interface=CipherBackend) class TestAESModeECB(object): - test_ECB = generate_encrypt_test( + test_ecb = generate_encrypt_test( load_nist_vectors, os.path.join("ciphers", "AES", "ECB"), [ @@ -84,13 +119,13 @@ class TestAESModeECB(object): @pytest.mark.supported( only_if=lambda backend: backend.cipher_supported( - algorithms.AES("\x00" * 16), modes.OFB("\x00" * 16) + algorithms.AES(b"\x00" * 16), modes.OFB(b"\x00" * 16) ), skip_message="Does not support AES OFB", ) @pytest.mark.requires_backend_interface(interface=CipherBackend) class TestAESModeOFB(object): - test_OFB = generate_encrypt_test( + test_ofb = generate_encrypt_test( load_nist_vectors, os.path.join("ciphers", "AES", "OFB"), [ @@ -117,13 +152,13 @@ class TestAESModeOFB(object): @pytest.mark.supported( only_if=lambda backend: backend.cipher_supported( - algorithms.AES("\x00" * 16), modes.CFB("\x00" * 16) + algorithms.AES(b"\x00" * 16), modes.CFB(b"\x00" * 16) ), skip_message="Does not support AES CFB", ) @pytest.mark.requires_backend_interface(interface=CipherBackend) class TestAESModeCFB(object): - test_CFB = generate_encrypt_test( + test_cfb = generate_encrypt_test( load_nist_vectors, os.path.join("ciphers", "AES", "CFB"), [ @@ -150,13 +185,13 @@ class TestAESModeCFB(object): @pytest.mark.supported( only_if=lambda backend: backend.cipher_supported( - algorithms.AES("\x00" * 16), modes.CFB8("\x00" * 16) + algorithms.AES(b"\x00" * 16), modes.CFB8(b"\x00" * 16) ), skip_message="Does not support AES CFB8", ) @pytest.mark.requires_backend_interface(interface=CipherBackend) class TestAESModeCFB8(object): - test_CFB8 = generate_encrypt_test( + test_cfb8 = generate_encrypt_test( load_nist_vectors, os.path.join("ciphers", "AES", "CFB"), [ @@ -183,13 +218,13 @@ class TestAESModeCFB8(object): @pytest.mark.supported( only_if=lambda backend: backend.cipher_supported( - algorithms.AES("\x00" * 16), modes.CTR("\x00" * 16) + algorithms.AES(b"\x00" * 16), modes.CTR(b"\x00" * 16) ), skip_message="Does not support AES CTR", ) @pytest.mark.requires_backend_interface(interface=CipherBackend) class TestAESModeCTR(object): - test_CTR = generate_encrypt_test( + test_ctr = generate_encrypt_test( load_nist_vectors, os.path.join("ciphers", "AES", "CTR"), ["aes-128-ctr.txt", "aes-192-ctr.txt", "aes-256-ctr.txt"], @@ -200,13 +235,13 @@ class TestAESModeCTR(object): @pytest.mark.supported( only_if=lambda backend: backend.cipher_supported( - algorithms.AES("\x00" * 16), modes.GCM("\x00" * 12) + algorithms.AES(b"\x00" * 16), modes.GCM(b"\x00" * 12) ), skip_message="Does not support AES GCM", ) @pytest.mark.requires_backend_interface(interface=CipherBackend) class TestAESModeGCM(object): - test_GCM = generate_aead_test( + test_gcm = generate_aead_test( load_nist_vectors, os.path.join("ciphers", "AES", "GCM"), [ @@ -253,3 +288,174 @@ class TestAESModeGCM(object): computed_ct = encryptor.update(pt) + encryptor.finalize() assert computed_ct == ct assert encryptor.tag == tag + + def test_gcm_ciphertext_limit(self, backend): + encryptor = base.Cipher( + algorithms.AES(b"\x00" * 16), + modes.GCM(b"\x01" * 16), + backend=backend + ).encryptor() + encryptor._bytes_processed = modes.GCM._MAX_ENCRYPTED_BYTES - 16 + encryptor.update(b"0" * 16) + assert ( + encryptor._bytes_processed == modes.GCM._MAX_ENCRYPTED_BYTES + ) + with pytest.raises(ValueError): + encryptor.update(b"0") + + def test_gcm_aad_limit(self, backend): + encryptor = base.Cipher( + algorithms.AES(b"\x00" * 16), + modes.GCM(b"\x01" * 16), + backend=backend + ).encryptor() + encryptor._aad_bytes_processed = modes.GCM._MAX_AAD_BYTES - 16 + encryptor.authenticate_additional_data(b"0" * 16) + assert encryptor._aad_bytes_processed == modes.GCM._MAX_AAD_BYTES + with pytest.raises(ValueError): + encryptor.authenticate_additional_data(b"0") + + def test_gcm_ciphertext_increments(self, backend): + encryptor = base.Cipher( + algorithms.AES(b"\x00" * 16), + modes.GCM(b"\x01" * 16), + backend=backend + ).encryptor() + encryptor.update(b"0" * 8) + assert encryptor._bytes_processed == 8 + encryptor.update(b"0" * 7) + assert encryptor._bytes_processed == 15 + encryptor.update(b"0" * 18) + assert encryptor._bytes_processed == 33 + + def test_gcm_aad_increments(self, backend): + encryptor = base.Cipher( + algorithms.AES(b"\x00" * 16), + modes.GCM(b"\x01" * 16), + backend=backend + ).encryptor() + encryptor.authenticate_additional_data(b"0" * 8) + assert encryptor._aad_bytes_processed == 8 + encryptor.authenticate_additional_data(b"0" * 18) + assert encryptor._aad_bytes_processed == 26 + + def test_gcm_tag_decrypt_none(self, backend): + key = binascii.unhexlify(b"5211242698bed4774a090620a6ca56f3") + iv = binascii.unhexlify(b"b1e1349120b6e832ef976f5d") + aad = binascii.unhexlify(b"b6d729aab8e6416d7002b9faa794c410d8d2f193") + + encryptor = base.Cipher( + algorithms.AES(key), + modes.GCM(iv), + backend=backend + ).encryptor() + encryptor.authenticate_additional_data(aad) + encryptor.finalize() + + decryptor = base.Cipher( + algorithms.AES(key), + modes.GCM(iv), + backend=backend + ).decryptor() + decryptor.authenticate_additional_data(aad) + with pytest.raises(ValueError): + decryptor.finalize() + + def test_gcm_tag_decrypt_mode(self, backend): + key = binascii.unhexlify(b"5211242698bed4774a090620a6ca56f3") + iv = binascii.unhexlify(b"b1e1349120b6e832ef976f5d") + aad = binascii.unhexlify(b"b6d729aab8e6416d7002b9faa794c410d8d2f193") + + encryptor = base.Cipher( + algorithms.AES(key), + modes.GCM(iv), + backend=backend + ).encryptor() + encryptor.authenticate_additional_data(aad) + encryptor.finalize() + tag = encryptor.tag + + decryptor = base.Cipher( + algorithms.AES(key), + modes.GCM(iv, tag), + backend=backend + ).decryptor() + decryptor.authenticate_additional_data(aad) + decryptor.finalize() + + def test_gcm_tag_decrypt_finalize(self, backend): + key = binascii.unhexlify(b"5211242698bed4774a090620a6ca56f3") + iv = binascii.unhexlify(b"b1e1349120b6e832ef976f5d") + aad = binascii.unhexlify(b"b6d729aab8e6416d7002b9faa794c410d8d2f193") + + encryptor = base.Cipher( + algorithms.AES(key), + modes.GCM(iv), + backend=backend + ).encryptor() + encryptor.authenticate_additional_data(aad) + encryptor.finalize() + tag = encryptor.tag + + decryptor = base.Cipher( + algorithms.AES(key), + modes.GCM(iv), + backend=backend + ).decryptor() + decryptor.authenticate_additional_data(aad) + + decryptor.finalize_with_tag(tag) + + def test_gcm_tag_decrypt_finalize_tag_length(self, backend): + decryptor = base.Cipher( + algorithms.AES(b"0" * 16), + modes.GCM(b"0" * 12), + backend=backend + ).decryptor() + with pytest.raises(ValueError): + decryptor.finalize_with_tag(b"tagtooshort") + + def test_buffer_protocol(self, backend): + data = bytearray(b"helloworld") + enc = base.Cipher( + algorithms.AES(bytearray(b"\x00" * 16)), + modes.GCM(bytearray(b"\x00" * 12)), + backend + ).encryptor() + enc.authenticate_additional_data(bytearray(b"foo")) + ct = enc.update(data) + enc.finalize() + dec = base.Cipher( + algorithms.AES(bytearray(b"\x00" * 16)), + modes.GCM(bytearray(b"\x00" * 12), enc.tag), + backend + ).decryptor() + dec.authenticate_additional_data(bytearray(b"foo")) + pt = dec.update(ct) + dec.finalize() + assert pt == data + + +@pytest.mark.parametrize( + "mode", + [ + modes.CBC(bytearray(b"\x00" * 16)), + modes.CTR(bytearray(b"\x00" * 16)), + modes.OFB(bytearray(b"\x00" * 16)), + modes.CFB(bytearray(b"\x00" * 16)), + modes.CFB8(bytearray(b"\x00" * 16)), + modes.XTS(bytearray(b"\x00" * 16)), + # Add a dummy mode for coverage of the cipher_supported check. + DummyMode(), + ] +) +@pytest.mark.requires_backend_interface(interface=CipherBackend) +def test_buffer_protocol_alternate_modes(mode, backend): + data = bytearray(b"sixteen_byte_msg") + key = algorithms.AES(bytearray(os.urandom(32))) + if not backend.cipher_supported(key, mode): + pytest.skip("AES in {} mode not supported".format(mode.name)) + cipher = base.Cipher(key, mode, backend) + enc = cipher.encryptor() + ct = enc.update(data) + enc.finalize() + dec = cipher.decryptor() + pt = dec.update(ct) + dec.finalize() + assert pt == data diff --git a/tests/hazmat/primitives/test_arc4.py b/tests/hazmat/primitives/test_arc4.py index acd306b2..1a173444 100644 --- a/tests/hazmat/primitives/test_arc4.py +++ b/tests/hazmat/primitives/test_arc4.py @@ -18,7 +18,7 @@ from ...utils import load_nist_vectors @pytest.mark.supported( only_if=lambda backend: backend.cipher_supported( - algorithms.ARC4("\x00" * 16), None + algorithms.ARC4(b"\x00" * 16), None ), skip_message="Does not support ARC4", ) @@ -35,6 +35,7 @@ class TestARC4(object): "rfc-6229-128.txt", "rfc-6229-192.txt", "rfc-6229-256.txt", + "arc4.txt" ], lambda key, **kwargs: algorithms.ARC4(binascii.unhexlify(key)), ) diff --git a/tests/hazmat/primitives/test_asym_utils.py b/tests/hazmat/primitives/test_asym_utils.py index 35b77ca4..b49ca3f2 100644 --- a/tests/hazmat/primitives/test_asym_utils.py +++ b/tests/hazmat/primitives/test_asym_utils.py @@ -7,64 +7,69 @@ from __future__ import absolute_import, division, print_function import pytest from cryptography.hazmat.primitives.asymmetric.utils import ( - decode_rfc6979_signature, encode_rfc6979_signature + Prehashed, decode_dss_signature, encode_dss_signature ) -def test_rfc6979_signature(): - sig = encode_rfc6979_signature(1, 1) +def test_dss_signature(): + sig = encode_dss_signature(1, 1) assert sig == b"0\x06\x02\x01\x01\x02\x01\x01" - assert decode_rfc6979_signature(sig) == (1, 1) + assert decode_dss_signature(sig) == (1, 1) r_s1 = ( 1037234182290683143945502320610861668562885151617, 559776156650501990899426031439030258256861634312 ) - sig2 = encode_rfc6979_signature(*r_s1) + sig2 = encode_dss_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 + assert decode_dss_signature(sig2) == r_s1 - sig3 = encode_rfc6979_signature(0, 0) + sig3 = encode_dss_signature(0, 0) assert sig3 == b"0\x06\x02\x01\x00\x02\x01\x00" - assert decode_rfc6979_signature(sig3) == (0, 0) + assert decode_dss_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_dss_non_integer(): + with pytest.raises(ValueError): + encode_dss_signature("h", 3) -def test_encode_rfc6979_non_integer(): with pytest.raises(ValueError): - encode_rfc6979_signature("h", 3) + encode_dss_signature("3", "2") with pytest.raises(ValueError): - encode_rfc6979_signature("3", "2") + encode_dss_signature(3, "h") with pytest.raises(ValueError): - encode_rfc6979_signature(3, "h") + encode_dss_signature(3.3, 1.2) with pytest.raises(ValueError): - encode_rfc6979_signature(3.3, 1.2) + encode_dss_signature("hello", "world") + +def test_encode_dss_negative(): with pytest.raises(ValueError): - encode_rfc6979_signature("hello", "world") + encode_dss_signature(-1, 0) -def test_decode_rfc6979_trailing_bytes(): +def test_decode_dss_trailing_bytes(): with pytest.raises(ValueError): - decode_rfc6979_signature(b"0\x06\x02\x01\x01\x02\x01\x01\x00\x00\x00") + decode_dss_signature(b"0\x06\x02\x01\x01\x02\x01\x01\x00\x00\x00") -def test_decode_rfc6979_invalid_asn1(): +def test_decode_dss_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") + decode_dss_signature(b"0\x07\x02\x01\x01\x02\x02\x01") with pytest.raises(ValueError): - # This is the BER "end-of-contents octets," which older versions of - # pyasn1 are wrongly willing to return from top-level DER decoding. - decode_rfc6979_signature(b"\x00\x00") + # This is the BER "end-of-contents octets". + decode_dss_signature(b"\x00\x00") + + +def test_pass_invalid_prehashed_arg(): + with pytest.raises(TypeError): + Prehashed(object()) diff --git a/tests/hazmat/primitives/test_block.py b/tests/hazmat/primitives/test_block.py index 1b3fc1cb..37158f15 100644 --- a/tests/hazmat/primitives/test_block.py +++ b/tests/hazmat/primitives/test_block.py @@ -8,7 +8,6 @@ import binascii import pytest -from cryptography import utils from cryptography.exceptions import ( AlreadyFinalized, _Reasons ) @@ -20,23 +19,10 @@ from cryptography.hazmat.primitives.ciphers import ( from .utils import ( generate_aead_exception_test, generate_aead_tag_exception_test ) +from ...doubles import DummyCipherAlgorithm, DummyMode from ...utils import raises_unsupported_algorithm -@utils.register_interface(modes.Mode) -class DummyMode(object): - name = "dummy-mode" - - def validate_for_algorithm(self, algorithm): - pass - - -@utils.register_interface(base.CipherAlgorithm) -class DummyCipher(object): - name = "dummy-cipher" - key_size = None - - @pytest.mark.requires_backend_interface(interface=CipherBackend) class TestCipher(object): def test_creates_encryptor(self, backend): @@ -84,6 +70,19 @@ class TestCipherContext(object): with pytest.raises(AlreadyFinalized): decryptor.finalize() + def test_use_update_into_after_finalize(self, backend): + cipher = Cipher( + algorithms.AES(binascii.unhexlify(b"0" * 32)), + modes.CBC(binascii.unhexlify(b"0" * 32)), + backend + ) + encryptor = cipher.encryptor() + encryptor.update(b"a" * 16) + encryptor.finalize() + with pytest.raises(AlreadyFinalized): + buf = bytearray(31) + encryptor.update_into(b"b" * 16, buf) + def test_unaligned_block_encryption(self, backend): cipher = Cipher( algorithms.AES(binascii.unhexlify(b"0" * 32)), @@ -107,7 +106,7 @@ class TestCipherContext(object): @pytest.mark.parametrize("mode", [DummyMode(), None]) def test_nonexistent_cipher(self, backend, mode): cipher = Cipher( - DummyCipher(), mode, backend + DummyCipherAlgorithm(), mode, backend ) with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_CIPHER): cipher.encryptor() @@ -134,7 +133,7 @@ class TestCipherContext(object): @pytest.mark.supported( only_if=lambda backend: backend.cipher_supported( - algorithms.AES("\x00" * 16), modes.GCM("\x00" * 12) + algorithms.AES(b"\x00" * 16), modes.GCM(b"\x00" * 12) ), skip_message="Does not support AES GCM", ) @@ -191,3 +190,37 @@ class TestModeValidation(object): modes.CTR(b"abc"), backend, ) + + def test_gcm(self): + with pytest.raises(ValueError): + modes.GCM(b"") + + +class TestModesRequireBytes(object): + def test_cbc(self): + with pytest.raises(TypeError): + modes.CBC([1] * 16) + + def test_cfb(self): + with pytest.raises(TypeError): + modes.CFB([1] * 16) + + def test_cfb8(self): + with pytest.raises(TypeError): + modes.CFB8([1] * 16) + + def test_ofb(self): + with pytest.raises(TypeError): + modes.OFB([1] * 16) + + def test_ctr(self): + with pytest.raises(TypeError): + modes.CTR([1] * 16) + + def test_gcm_iv(self): + with pytest.raises(TypeError): + modes.GCM([1] * 16) + + def test_gcm_tag(self): + with pytest.raises(TypeError): + modes.GCM(b"\x00" * 16, [1] * 16) diff --git a/tests/hazmat/primitives/test_blowfish.py b/tests/hazmat/primitives/test_blowfish.py index de49e86d..5f7480ec 100644 --- a/tests/hazmat/primitives/test_blowfish.py +++ b/tests/hazmat/primitives/test_blowfish.py @@ -18,13 +18,13 @@ from ...utils import load_nist_vectors @pytest.mark.supported( only_if=lambda backend: backend.cipher_supported( - algorithms.Blowfish("\x00" * 56), modes.ECB() + algorithms.Blowfish(b"\x00" * 56), modes.ECB() ), skip_message="Does not support Blowfish ECB", ) @pytest.mark.requires_backend_interface(interface=CipherBackend) class TestBlowfishModeECB(object): - test_ECB = generate_encrypt_test( + test_ecb = generate_encrypt_test( load_nist_vectors, os.path.join("ciphers", "Blowfish"), ["bf-ecb.txt"], @@ -35,13 +35,13 @@ class TestBlowfishModeECB(object): @pytest.mark.supported( only_if=lambda backend: backend.cipher_supported( - algorithms.Blowfish("\x00" * 56), modes.CBC("\x00" * 8) + algorithms.Blowfish(b"\x00" * 56), modes.CBC(b"\x00" * 8) ), skip_message="Does not support Blowfish CBC", ) @pytest.mark.requires_backend_interface(interface=CipherBackend) class TestBlowfishModeCBC(object): - test_CBC = generate_encrypt_test( + test_cbc = generate_encrypt_test( load_nist_vectors, os.path.join("ciphers", "Blowfish"), ["bf-cbc.txt"], @@ -52,13 +52,13 @@ class TestBlowfishModeCBC(object): @pytest.mark.supported( only_if=lambda backend: backend.cipher_supported( - algorithms.Blowfish("\x00" * 56), modes.OFB("\x00" * 8) + algorithms.Blowfish(b"\x00" * 56), modes.OFB(b"\x00" * 8) ), skip_message="Does not support Blowfish OFB", ) @pytest.mark.requires_backend_interface(interface=CipherBackend) class TestBlowfishModeOFB(object): - test_OFB = generate_encrypt_test( + test_ofb = generate_encrypt_test( load_nist_vectors, os.path.join("ciphers", "Blowfish"), ["bf-ofb.txt"], @@ -69,13 +69,13 @@ class TestBlowfishModeOFB(object): @pytest.mark.supported( only_if=lambda backend: backend.cipher_supported( - algorithms.Blowfish("\x00" * 56), modes.CFB("\x00" * 8) + algorithms.Blowfish(b"\x00" * 56), modes.CFB(b"\x00" * 8) ), skip_message="Does not support Blowfish CFB", ) @pytest.mark.requires_backend_interface(interface=CipherBackend) class TestBlowfishModeCFB(object): - test_CFB = generate_encrypt_test( + test_cfb = generate_encrypt_test( load_nist_vectors, os.path.join("ciphers", "Blowfish"), ["bf-cfb.txt"], diff --git a/tests/hazmat/primitives/test_camellia.py b/tests/hazmat/primitives/test_camellia.py index 8bdcb309..07a735ad 100644 --- a/tests/hazmat/primitives/test_camellia.py +++ b/tests/hazmat/primitives/test_camellia.py @@ -20,13 +20,13 @@ from ...utils import ( @pytest.mark.supported( only_if=lambda backend: backend.cipher_supported( - algorithms.Camellia("\x00" * 16), modes.ECB() + algorithms.Camellia(b"\x00" * 16), modes.ECB() ), skip_message="Does not support Camellia ECB", ) @pytest.mark.requires_backend_interface(interface=CipherBackend) class TestCamelliaModeECB(object): - test_ECB = generate_encrypt_test( + test_ecb = generate_encrypt_test( load_cryptrec_vectors, os.path.join("ciphers", "Camellia"), [ @@ -41,13 +41,13 @@ class TestCamelliaModeECB(object): @pytest.mark.supported( only_if=lambda backend: backend.cipher_supported( - algorithms.Camellia("\x00" * 16), modes.CBC("\x00" * 16) + algorithms.Camellia(b"\x00" * 16), modes.CBC(b"\x00" * 16) ), skip_message="Does not support Camellia CBC", ) @pytest.mark.requires_backend_interface(interface=CipherBackend) class TestCamelliaModeCBC(object): - test_CBC = generate_encrypt_test( + test_cbc = generate_encrypt_test( load_nist_vectors, os.path.join("ciphers", "Camellia"), ["camellia-cbc.txt"], @@ -58,13 +58,13 @@ class TestCamelliaModeCBC(object): @pytest.mark.supported( only_if=lambda backend: backend.cipher_supported( - algorithms.Camellia("\x00" * 16), modes.OFB("\x00" * 16) + algorithms.Camellia(b"\x00" * 16), modes.OFB(b"\x00" * 16) ), skip_message="Does not support Camellia OFB", ) @pytest.mark.requires_backend_interface(interface=CipherBackend) class TestCamelliaModeOFB(object): - test_OFB = generate_encrypt_test( + test_ofb = generate_encrypt_test( load_nist_vectors, os.path.join("ciphers", "Camellia"), ["camellia-ofb.txt"], @@ -75,13 +75,13 @@ class TestCamelliaModeOFB(object): @pytest.mark.supported( only_if=lambda backend: backend.cipher_supported( - algorithms.Camellia("\x00" * 16), modes.CFB("\x00" * 16) + algorithms.Camellia(b"\x00" * 16), modes.CFB(b"\x00" * 16) ), skip_message="Does not support Camellia CFB", ) @pytest.mark.requires_backend_interface(interface=CipherBackend) class TestCamelliaModeCFB(object): - test_CFB = generate_encrypt_test( + test_cfb = generate_encrypt_test( load_nist_vectors, os.path.join("ciphers", "Camellia"), ["camellia-cfb.txt"], diff --git a/tests/hazmat/primitives/test_cast5.py b/tests/hazmat/primitives/test_cast5.py index 0e4f879c..87e12a33 100644 --- a/tests/hazmat/primitives/test_cast5.py +++ b/tests/hazmat/primitives/test_cast5.py @@ -18,13 +18,13 @@ from ...utils import load_nist_vectors @pytest.mark.supported( only_if=lambda backend: backend.cipher_supported( - algorithms.CAST5("\x00" * 16), modes.ECB() + algorithms.CAST5(b"\x00" * 16), modes.ECB() ), skip_message="Does not support CAST5 ECB", ) @pytest.mark.requires_backend_interface(interface=CipherBackend) class TestCAST5ModeECB(object): - test_ECB = generate_encrypt_test( + test_ecb = generate_encrypt_test( load_nist_vectors, os.path.join("ciphers", "CAST5"), ["cast5-ecb.txt"], @@ -35,13 +35,13 @@ class TestCAST5ModeECB(object): @pytest.mark.supported( only_if=lambda backend: backend.cipher_supported( - algorithms.CAST5("\x00" * 16), modes.CBC("\x00" * 8) + algorithms.CAST5(b"\x00" * 16), modes.CBC(b"\x00" * 8) ), skip_message="Does not support CAST5 CBC", ) @pytest.mark.requires_backend_interface(interface=CipherBackend) class TestCAST5ModeCBC(object): - test_CBC = generate_encrypt_test( + test_cbc = generate_encrypt_test( load_nist_vectors, os.path.join("ciphers", "CAST5"), ["cast5-cbc.txt"], @@ -52,13 +52,13 @@ class TestCAST5ModeCBC(object): @pytest.mark.supported( only_if=lambda backend: backend.cipher_supported( - algorithms.CAST5("\x00" * 16), modes.OFB("\x00" * 8) + algorithms.CAST5(b"\x00" * 16), modes.OFB(b"\x00" * 8) ), skip_message="Does not support CAST5 OFB", ) @pytest.mark.requires_backend_interface(interface=CipherBackend) class TestCAST5ModeOFB(object): - test_OFB = generate_encrypt_test( + test_ofb = generate_encrypt_test( load_nist_vectors, os.path.join("ciphers", "CAST5"), ["cast5-ofb.txt"], @@ -69,33 +69,16 @@ class TestCAST5ModeOFB(object): @pytest.mark.supported( only_if=lambda backend: backend.cipher_supported( - algorithms.CAST5("\x00" * 16), modes.CFB("\x00" * 8) + algorithms.CAST5(b"\x00" * 16), modes.CFB(b"\x00" * 8) ), skip_message="Does not support CAST5 CFB", ) @pytest.mark.requires_backend_interface(interface=CipherBackend) class TestCAST5ModeCFB(object): - test_CFB = generate_encrypt_test( + test_cfb = generate_encrypt_test( load_nist_vectors, os.path.join("ciphers", "CAST5"), ["cast5-cfb.txt"], lambda key, **kwargs: algorithms.CAST5(binascii.unhexlify((key))), lambda iv, **kwargs: modes.CFB(binascii.unhexlify(iv)) ) - - -@pytest.mark.supported( - only_if=lambda backend: backend.cipher_supported( - algorithms.CAST5("\x00" * 16), modes.CTR("\x00" * 8) - ), - skip_message="Does not support CAST5 CTR", -) -@pytest.mark.requires_backend_interface(interface=CipherBackend) -class TestCAST5ModeCTR(object): - test_CTR = generate_encrypt_test( - load_nist_vectors, - os.path.join("ciphers", "CAST5"), - ["cast5-ctr.txt"], - lambda key, **kwargs: algorithms.CAST5(binascii.unhexlify((key))), - lambda iv, **kwargs: modes.CTR(binascii.unhexlify(iv)) - ) diff --git a/tests/hazmat/primitives/test_chacha20.py b/tests/hazmat/primitives/test_chacha20.py new file mode 100644 index 00000000..7c475c0f --- /dev/null +++ b/tests/hazmat/primitives/test_chacha20.py @@ -0,0 +1,76 @@ +# 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 binascii +import os +import struct + +import pytest + +from cryptography.hazmat.backends.interfaces import CipherBackend +from cryptography.hazmat.primitives.ciphers import Cipher, algorithms + +from .utils import _load_all_params +from ...utils import load_nist_vectors + + +@pytest.mark.supported( + only_if=lambda backend: backend.cipher_supported( + algorithms.ChaCha20(b"\x00" * 32, b"0" * 16), None + ), + skip_message="Does not support ChaCha20", +) +@pytest.mark.requires_backend_interface(interface=CipherBackend) +class TestChaCha20(object): + @pytest.mark.parametrize( + "vector", + _load_all_params( + os.path.join("ciphers", "ChaCha20"), + ["rfc7539.txt"], + load_nist_vectors + ) + ) + def test_vectors(self, vector, backend): + key = binascii.unhexlify(vector["key"]) + nonce = binascii.unhexlify(vector["nonce"]) + ibc = struct.pack("<i", int(vector["initial_block_counter"])) + pt = binascii.unhexlify(vector["plaintext"]) + encryptor = Cipher( + algorithms.ChaCha20(key, ibc + nonce), None, backend + ).encryptor() + computed_ct = encryptor.update(pt) + encryptor.finalize() + assert binascii.hexlify(computed_ct) == vector["ciphertext"] + + def test_buffer_protocol(self, backend): + key = bytearray(os.urandom(32)) + nonce = bytearray(os.urandom(16)) + cipher = Cipher( + algorithms.ChaCha20(key, nonce), None, backend + ) + enc = cipher.encryptor() + ct = enc.update(bytearray(b"hello")) + enc.finalize() + dec = cipher.decryptor() + pt = dec.update(ct) + dec.finalize() + assert pt == b"hello" + + def test_key_size(self): + chacha = algorithms.ChaCha20(b"0" * 32, b"0" * 16) + assert chacha.key_size == 256 + + def test_invalid_key_size(self): + with pytest.raises(ValueError): + algorithms.ChaCha20(b"wrongsize", b"0" * 16) + + def test_invalid_nonce(self): + with pytest.raises(ValueError): + algorithms.ChaCha20(b"0" * 32, b"0") + + with pytest.raises(TypeError): + algorithms.ChaCha20(b"0" * 32, object()) + + def test_invalid_key_type(self): + with pytest.raises(TypeError, match="key must be bytes"): + algorithms.ChaCha20(u"0" * 32, b"0" * 16) diff --git a/tests/hazmat/primitives/test_ciphers.py b/tests/hazmat/primitives/test_ciphers.py index d9a07ff6..f29ba9a9 100644 --- a/tests/hazmat/primitives/test_ciphers.py +++ b/tests/hazmat/primitives/test_ciphers.py @@ -5,17 +5,21 @@ from __future__ import absolute_import, division, print_function import binascii +import os import pytest -from cryptography.exceptions import _Reasons +from cryptography.exceptions import AlreadyFinalized, _Reasons +from cryptography.hazmat.backends.interfaces import CipherBackend from cryptography.hazmat.primitives import ciphers +from cryptography.hazmat.primitives.ciphers import modes from cryptography.hazmat.primitives.ciphers.algorithms import ( AES, ARC4, Blowfish, CAST5, Camellia, IDEA, SEED, TripleDES ) -from cryptography.hazmat.primitives.ciphers.modes import ECB -from ...utils import raises_unsupported_algorithm +from ...utils import ( + load_nist_vectors, load_vectors_from_file, raises_unsupported_algorithm +) class TestAES(object): @@ -32,6 +36,34 @@ class TestAES(object): with pytest.raises(ValueError): AES(binascii.unhexlify(b"0" * 12)) + def test_invalid_key_type(self): + with pytest.raises(TypeError, match="key must be bytes"): + AES(u"0" * 32) + + +class TestAESXTS(object): + @pytest.mark.requires_backend_interface(interface=CipherBackend) + @pytest.mark.parametrize( + "mode", + (modes.CBC, modes.CTR, modes.CFB, modes.CFB8, modes.OFB) + ) + def test_invalid_key_size_with_mode(self, mode, backend): + with pytest.raises(ValueError): + ciphers.Cipher(AES(b"0" * 64), mode(b"0" * 16), backend) + + def test_xts_tweak_not_bytes(self): + with pytest.raises(TypeError): + modes.XTS(32) + + def test_xts_tweak_too_small(self): + with pytest.raises(ValueError): + modes.XTS(b"0") + + @pytest.mark.requires_backend_interface(interface=CipherBackend) + def test_xts_wrong_key_size(self, backend): + with pytest.raises(ValueError): + ciphers.Cipher(AES(b"0" * 16), modes.XTS(b"0" * 16), backend) + class TestCamellia(object): @pytest.mark.parametrize(("key", "keysize"), [ @@ -47,6 +79,10 @@ class TestCamellia(object): with pytest.raises(ValueError): Camellia(binascii.unhexlify(b"0" * 12)) + def test_invalid_key_type(self): + with pytest.raises(TypeError, match="key must be bytes"): + Camellia(u"0" * 32) + class TestTripleDES(object): @pytest.mark.parametrize("key", [ @@ -62,6 +98,10 @@ class TestTripleDES(object): with pytest.raises(ValueError): TripleDES(binascii.unhexlify(b"0" * 12)) + def test_invalid_key_type(self): + with pytest.raises(TypeError, match="key must be bytes"): + TripleDES(u"0" * 16) + class TestBlowfish(object): @pytest.mark.parametrize(("key", "keysize"), [ @@ -75,6 +115,10 @@ class TestBlowfish(object): with pytest.raises(ValueError): Blowfish(binascii.unhexlify(b"0" * 6)) + def test_invalid_key_type(self): + with pytest.raises(TypeError, match="key must be bytes"): + Blowfish(u"0" * 8) + class TestCAST5(object): @pytest.mark.parametrize(("key", "keysize"), [ @@ -88,6 +132,10 @@ class TestCAST5(object): with pytest.raises(ValueError): CAST5(binascii.unhexlify(b"0" * 34)) + def test_invalid_key_type(self): + with pytest.raises(TypeError, match="key must be bytes"): + CAST5(u"0" * 10) + class TestARC4(object): @pytest.mark.parametrize(("key", "keysize"), [ @@ -107,6 +155,10 @@ class TestARC4(object): with pytest.raises(ValueError): ARC4(binascii.unhexlify(b"0" * 34)) + def test_invalid_key_type(self): + with pytest.raises(TypeError, match="key must be bytes"): + ARC4(u"0" * 10) + class TestIDEA(object): def test_key_size(self): @@ -117,6 +169,10 @@ class TestIDEA(object): with pytest.raises(ValueError): IDEA(b"\x00" * 17) + def test_invalid_key_type(self): + with pytest.raises(TypeError, match="key must be bytes"): + IDEA(u"0" * 16) + class TestSEED(object): def test_key_size(self): @@ -127,9 +183,129 @@ class TestSEED(object): with pytest.raises(ValueError): SEED(b"\x00" * 17) + def test_invalid_key_type(self): + with pytest.raises(TypeError, match="key must be bytes"): + SEED(u"0" * 16) + def test_invalid_backend(): pretend_backend = object() with raises_unsupported_algorithm(_Reasons.BACKEND_MISSING_INTERFACE): - ciphers.Cipher(AES(b"AAAAAAAAAAAAAAAA"), ECB, pretend_backend) + ciphers.Cipher(AES(b"AAAAAAAAAAAAAAAA"), modes.ECB, pretend_backend) + + +@pytest.mark.supported( + only_if=lambda backend: backend.cipher_supported( + AES(b"\x00" * 16), modes.ECB() + ), + skip_message="Does not support AES ECB", +) +@pytest.mark.requires_backend_interface(interface=CipherBackend) +class TestCipherUpdateInto(object): + @pytest.mark.parametrize( + "params", + load_vectors_from_file( + os.path.join("ciphers", "AES", "ECB", "ECBGFSbox128.rsp"), + load_nist_vectors + ) + ) + def test_update_into(self, params, backend): + key = binascii.unhexlify(params["key"]) + pt = binascii.unhexlify(params["plaintext"]) + ct = binascii.unhexlify(params["ciphertext"]) + c = ciphers.Cipher(AES(key), modes.ECB(), backend) + encryptor = c.encryptor() + buf = bytearray(len(pt) + 15) + res = encryptor.update_into(pt, buf) + assert res == len(pt) + assert bytes(buf)[:res] == ct + + @pytest.mark.supported( + only_if=lambda backend: backend.cipher_supported( + AES(b"\x00" * 16), modes.GCM(b"0" * 12) + ), + skip_message="Does not support AES GCM", + ) + def test_update_into_gcm(self, backend): + key = binascii.unhexlify(b"e98b72a9881a84ca6b76e0f43e68647a") + iv = binascii.unhexlify(b"8b23299fde174053f3d652ba") + ct = binascii.unhexlify(b"5a3c1cf1985dbb8bed818036fdd5ab42") + pt = binascii.unhexlify(b"28286a321293253c3e0aa2704a278032") + c = ciphers.Cipher(AES(key), modes.GCM(iv), backend) + encryptor = c.encryptor() + buf = bytearray(len(pt) + 15) + res = encryptor.update_into(pt, buf) + assert res == len(pt) + assert bytes(buf)[:res] == ct + encryptor.finalize() + c = ciphers.Cipher(AES(key), modes.GCM(iv, encryptor.tag), backend) + decryptor = c.decryptor() + res = decryptor.update_into(ct, buf) + decryptor.finalize() + assert res == len(pt) + assert bytes(buf)[:res] == pt + + @pytest.mark.supported( + only_if=lambda backend: backend.cipher_supported( + AES(b"\x00" * 16), modes.GCM(b"0" * 12) + ), + skip_message="Does not support AES GCM", + ) + def test_finalize_with_tag_already_finalized(self, backend): + key = binascii.unhexlify(b"e98b72a9881a84ca6b76e0f43e68647a") + iv = binascii.unhexlify(b"8b23299fde174053f3d652ba") + encryptor = ciphers.Cipher( + AES(key), modes.GCM(iv), backend + ).encryptor() + ciphertext = encryptor.update(b"abc") + encryptor.finalize() + + decryptor = ciphers.Cipher( + AES(key), modes.GCM(iv, tag=encryptor.tag), backend + ).decryptor() + decryptor.update(ciphertext) + decryptor.finalize() + with pytest.raises(AlreadyFinalized): + decryptor.finalize_with_tag(encryptor.tag) + + @pytest.mark.parametrize( + "params", + load_vectors_from_file( + os.path.join("ciphers", "AES", "ECB", "ECBGFSbox128.rsp"), + load_nist_vectors + ) + ) + def test_update_into_multiple_calls(self, params, backend): + key = binascii.unhexlify(params["key"]) + pt = binascii.unhexlify(params["plaintext"]) + ct = binascii.unhexlify(params["ciphertext"]) + c = ciphers.Cipher(AES(key), modes.ECB(), backend) + encryptor = c.encryptor() + buf = bytearray(len(pt) + 15) + res = encryptor.update_into(pt[:3], buf) + assert res == 0 + res = encryptor.update_into(pt[3:], buf) + assert res == len(pt) + assert bytes(buf)[:res] == ct + + def test_update_into_buffer_too_small(self, backend): + key = b"\x00" * 16 + c = ciphers.Cipher(AES(key), modes.ECB(), backend) + encryptor = c.encryptor() + buf = bytearray(16) + with pytest.raises(ValueError): + encryptor.update_into(b"testing", buf) + + @pytest.mark.supported( + only_if=lambda backend: backend.cipher_supported( + AES(b"\x00" * 16), modes.GCM(b"\x00" * 12) + ), + skip_message="Does not support AES GCM", + ) + def test_update_into_buffer_too_small_gcm(self, backend): + key = b"\x00" * 16 + c = ciphers.Cipher(AES(key), modes.GCM(b"\x00" * 12), backend) + encryptor = c.encryptor() + buf = bytearray(5) + with pytest.raises(ValueError): + encryptor.update_into(b"testing", buf) diff --git a/tests/hazmat/primitives/test_cmac.py b/tests/hazmat/primitives/test_cmac.py index 08b6bf5d..e319396d 100644 --- a/tests/hazmat/primitives/test_cmac.py +++ b/tests/hazmat/primitives/test_cmac.py @@ -6,8 +6,6 @@ from __future__ import absolute_import, division, print_function import binascii -import pretend - import pytest from cryptography.exceptions import ( @@ -19,11 +17,11 @@ from cryptography.hazmat.primitives.ciphers.algorithms import ( ) from cryptography.hazmat.primitives.cmac import CMAC -from ..backends.test_multibackend import DummyCMACBackend from ...utils import ( load_nist_vectors, load_vectors_from_file, raises_unsupported_algorithm ) + vectors_aes128 = load_vectors_from_file( "CMAC/nist-800-38b-aes128.txt", load_nist_vectors) @@ -185,16 +183,18 @@ class TestCMAC(object): copy_cmac = cmac.copy() assert cmac.finalize() == copy_cmac.finalize() - -def test_copy(): - backend = DummyCMACBackend([AES]) - copied_ctx = pretend.stub() - pretend_ctx = pretend.stub(copy=lambda: copied_ctx) - key = b"2b7e151628aed2a6abf7158809cf4f3c" - cmac = CMAC(AES(key), backend=backend, ctx=pretend_ctx) - - assert cmac._backend is backend - assert cmac.copy()._backend is backend + @pytest.mark.supported( + only_if=lambda backend: backend.cmac_algorithm_supported( + AES(fake_key)), + skip_message="Does not support CMAC." + ) + def test_buffer_protocol(self, backend): + key = bytearray(b"2b7e151628aed2a6abf7158809cf4f3c") + cmac = CMAC(AES(key), backend) + cmac.update(b"6bc1bee22e409f96e93d7e117393172a") + assert cmac.finalize() == binascii.unhexlify( + b"a21e6e647bfeaf5ca0a5e1bcd957dfad" + ) def test_invalid_backend(): diff --git a/tests/hazmat/primitives/test_concatkdf.py b/tests/hazmat/primitives/test_concatkdf.py index 27e5460e..67315099 100644 --- a/tests/hazmat/primitives/test_concatkdf.py +++ b/tests/hazmat/primitives/test_concatkdf.py @@ -52,6 +52,22 @@ class TestConcatKDFHash(object): assert ckdf.derive(prk) == okm + def test_buffer_protocol(self, backend): + prk = binascii.unhexlify( + b"52169af5c485dcc2321eb8d26d5efa21fb9b93c98e38412ee2484cf14f0d0d23" + ) + + okm = binascii.unhexlify(b"1c3bc9e7c4547c5191c0d478cccaed55") + + oinfo = binascii.unhexlify( + b"a1b2c3d4e53728157e634612c12d6d5223e204aeea4341565369647bd184bcd2" + b"46f72971f292badaa2fe4124612cba" + ) + + ckdf = ConcatKDFHash(hashes.SHA256(), 16, oinfo, backend) + + assert ckdf.derive(bytearray(prk)) == okm + def test_verify(self, backend): prk = binascii.unhexlify( b"52169af5c485dcc2321eb8d26d5efa21fb9b93c98e38412ee2484cf14f0d0d23" @@ -158,6 +174,46 @@ class TestConcatKDFHMAC(object): assert ckdf.derive(prk) == okm + def test_buffer_protocol(self, backend): + prk = binascii.unhexlify( + b"013951627c1dea63ea2d7702dd24e963eef5faac6b4af7e4" + b"b831cde499dff1ce45f6179f741c728aa733583b02409208" + b"8f0af7fce1d045edbc5790931e8d5ca79c73" + ) + + okm = binascii.unhexlify(b"64ce901db10d558661f10b6836a122a7" + b"605323ce2f39bf27eaaac8b34cf89f2f") + + oinfo = binascii.unhexlify( + b"a1b2c3d4e55e600be5f367e0e8a465f4bf2704db00c9325c" + b"9fbd216d12b49160b2ae5157650f43415653696421e68e" + ) + + ckdf = ConcatKDFHMAC(hashes.SHA512(), 32, None, oinfo, backend) + + assert ckdf.derive(bytearray(prk)) == okm + + def test_derive_explicit_salt(self, backend): + prk = binascii.unhexlify( + b"013951627c1dea63ea2d7702dd24e963eef5faac6b4af7e4" + b"b831cde499dff1ce45f6179f741c728aa733583b02409208" + b"8f0af7fce1d045edbc5790931e8d5ca79c73" + ) + + okm = binascii.unhexlify(b"64ce901db10d558661f10b6836a122a7" + b"605323ce2f39bf27eaaac8b34cf89f2f") + + oinfo = binascii.unhexlify( + b"a1b2c3d4e55e600be5f367e0e8a465f4bf2704db00c9325c" + b"9fbd216d12b49160b2ae5157650f43415653696421e68e" + ) + + ckdf = ConcatKDFHMAC( + hashes.SHA512(), 32, b"\x00" * 128, oinfo, backend + ) + + assert ckdf.derive(prk) == okm + def test_verify(self, backend): prk = binascii.unhexlify( b"013951627c1dea63ea2d7702dd24e963eef5faac6b4af7e4" diff --git a/tests/hazmat/primitives/test_dh.py b/tests/hazmat/primitives/test_dh.py index d8869de9..43f2ce5c 100644 --- a/tests/hazmat/primitives/test_dh.py +++ b/tests/hazmat/primitives/test_dh.py @@ -4,22 +4,42 @@ from __future__ import absolute_import, division, print_function +import binascii +import itertools +import os + import pytest +from cryptography.hazmat.backends.interfaces import ( + DERSerializationBackend, DHBackend, PEMSerializationBackend) +from cryptography.hazmat.primitives import serialization from cryptography.hazmat.primitives.asymmetric import dh +from cryptography.utils import int_from_bytes + +from ...doubles import DummyKeySerializationEncryption +from ...utils import load_nist_vectors, load_vectors_from_file + + +def _skip_dhx_unsupported(backend, is_dhx): + if not is_dhx: + return + if not backend.dh_x942_serialization_supported(): + pytest.skip( + "DH x9.42 serialization is not supported" + ) def test_dh_parameternumbers(): params = dh.DHParameterNumbers( - 65537, 3 + 65537, 2 ) assert params.p == 65537 - assert params.g == 3 + assert params.g == 2 with pytest.raises(TypeError): dh.DHParameterNumbers( - None, 3 + None, 2 ) with pytest.raises(TypeError): @@ -32,10 +52,28 @@ def test_dh_parameternumbers(): None, None ) + with pytest.raises(ValueError): + dh.DHParameterNumbers( + 65537, 1 + ) + + params = dh.DHParameterNumbers( + 65537, 7, 1245 + ) + + assert params.p == 65537 + assert params.g == 7 + assert params.q == 1245 + + with pytest.raises(TypeError): + dh.DHParameterNumbers( + 65537, 2, "hello" + ) + def test_dh_numbers(): params = dh.DHParameterNumbers( - 65537, 3 + 65537, 2 ) public = dh.DHPublicNumbers( @@ -74,14 +112,18 @@ def test_dh_numbers(): def test_dh_parameter_numbers_equality(): - assert dh.DHParameterNumbers(65537, 3) == dh.DHParameterNumbers(65537, 3) - assert dh.DHParameterNumbers(6, 3) != dh.DHParameterNumbers(65537, 3) - assert dh.DHParameterNumbers(65537, 0) != dh.DHParameterNumbers(65537, 3) - assert dh.DHParameterNumbers(65537, 0) != object() + assert dh.DHParameterNumbers(65537, 2) == dh.DHParameterNumbers(65537, 2) + assert dh.DHParameterNumbers(65537, 7, 12345) == dh.DHParameterNumbers( + 65537, 7, 12345) + assert dh.DHParameterNumbers(6, 2) != dh.DHParameterNumbers(65537, 2) + assert dh.DHParameterNumbers(65537, 2, 123) != dh.DHParameterNumbers( + 65537, 2, 456) + assert dh.DHParameterNumbers(65537, 5) != dh.DHParameterNumbers(65537, 2) + assert dh.DHParameterNumbers(65537, 2) != object() def test_dh_private_numbers_equality(): - params = dh.DHParameterNumbers(65537, 3) + params = dh.DHParameterNumbers(65537, 2) public = dh.DHPublicNumbers(1, params) private = dh.DHPrivateNumbers(2, public) @@ -89,16 +131,752 @@ def test_dh_private_numbers_equality(): assert private != dh.DHPrivateNumbers(0, public) assert private != dh.DHPrivateNumbers(2, dh.DHPublicNumbers(0, params)) assert private != dh.DHPrivateNumbers( - 2, dh.DHPublicNumbers(1, dh.DHParameterNumbers(65537, 0)) + 2, dh.DHPublicNumbers(1, dh.DHParameterNumbers(65537, 5)) ) assert private != object() def test_dh_public_numbers_equality(): - params = dh.DHParameterNumbers(65537, 3) + params = dh.DHParameterNumbers(65537, 2) public = dh.DHPublicNumbers(1, params) assert public == dh.DHPublicNumbers(1, params) assert public != dh.DHPublicNumbers(0, params) - assert public != dh.DHPublicNumbers(1, dh.DHParameterNumbers(65537, 0)) + assert public != dh.DHPublicNumbers(1, dh.DHParameterNumbers(65537, 5)) assert public != object() + + +@pytest.mark.requires_backend_interface(interface=DHBackend) +class TestDH(object): + def test_small_key_generate_dh(self, backend): + with pytest.raises(ValueError): + dh.generate_parameters(2, 511, backend) + + def test_unsupported_generator_generate_dh(self, backend): + with pytest.raises(ValueError): + dh.generate_parameters(7, 512, backend) + + def test_dh_parameters_supported(self, backend): + valid_p = int( + b"907c7211ae61aaaba1825ff53b6cb71ac6df9f1a424c033f4a0a41ac42fad3a9" + b"bcfc7f938a269710ed69e330523e4039029b7900977c740990d46efed79b9bbe" + b"73505ae878808944ce4d9c6c52daecc0a87dc889c53499be93db8551ee685f30" + b"349bf1b443d4ebaee0d5e8b441a40d4e8178f8f612f657a5eb91e0a8e" + b"107755f", 16 + ) + assert backend.dh_parameters_supported(valid_p, 5) + assert not backend.dh_parameters_supported(23, 22) + + @pytest.mark.parametrize( + "vector", + load_vectors_from_file( + os.path.join("asymmetric", "DH", "rfc3526.txt"), + load_nist_vectors + ) + ) + def test_dh_parameters_allows_rfc3526_groups(self, backend, vector): + p = int_from_bytes(binascii.unhexlify(vector["p"]), 'big') + params = dh.DHParameterNumbers(p, int(vector["g"])) + param = params.parameters(backend) + key = param.generate_private_key() + # This confirms that a key generated with this group + # will pass DH_check when we serialize and de-serialize it via + # the Numbers path. + roundtripped_key = key.private_numbers().private_key(backend) + assert key.private_numbers() == roundtripped_key.private_numbers() + + @pytest.mark.parametrize( + "vector", + load_vectors_from_file( + os.path.join("asymmetric", "DH", "RFC5114.txt"), + load_nist_vectors)) + def test_dh_parameters_supported_with_q(self, backend, vector): + assert backend.dh_parameters_supported(int(vector["p"], 16), + int(vector["g"], 16), + int(vector["q"], 16)) + + @pytest.mark.parametrize("with_q", [False, True]) + def test_convert_to_numbers(self, backend, with_q): + if with_q: + vector = load_vectors_from_file( + os.path.join("asymmetric", "DH", "RFC5114.txt"), + load_nist_vectors)[0] + p = int(vector["p"], 16) + g = int(vector["g"], 16) + q = int(vector["q"], 16) + else: + parameters = backend.generate_dh_private_key_and_parameters(2, 512) + + private = parameters.private_numbers() + + p = private.public_numbers.parameter_numbers.p + g = private.public_numbers.parameter_numbers.g + q = None + + params = dh.DHParameterNumbers(p, g, q) + public = dh.DHPublicNumbers(1, params) + private = dh.DHPrivateNumbers(2, public) + + deserialized_params = params.parameters(backend) + deserialized_public = public.public_key(backend) + deserialized_private = private.private_key(backend) + + assert isinstance(deserialized_params, + dh.DHParametersWithSerialization) + assert isinstance(deserialized_public, + dh.DHPublicKeyWithSerialization) + assert isinstance(deserialized_private, + dh.DHPrivateKeyWithSerialization) + + def test_numbers_unsupported_parameters(self, backend): + # p is set to 21 because when calling private_key we want it to + # fail the DH_check call OpenSSL does. Originally this was 23, but + # we are allowing p % 24 to == 23 with this PR (see #3768 for more) + # By setting it to 21 it fails later in DH_check in a primality check + # which triggers the code path we want to test + params = dh.DHParameterNumbers(21, 2) + public = dh.DHPublicNumbers(1, params) + private = dh.DHPrivateNumbers(2, public) + + with pytest.raises(ValueError): + private.private_key(backend) + + @pytest.mark.parametrize("with_q", [False, True]) + def test_generate_dh(self, backend, with_q): + if with_q: + vector = load_vectors_from_file( + os.path.join("asymmetric", "DH", "RFC5114.txt"), + load_nist_vectors)[0] + p = int(vector["p"], 16) + g = int(vector["g"], 16) + q = int(vector["q"], 16) + parameters = dh.DHParameterNumbers(p, g, q).parameters(backend) + key_size = 1024 + else: + generator = 2 + key_size = 512 + + parameters = dh.generate_parameters(generator, key_size, backend) + assert isinstance(parameters, dh.DHParameters) + + key = parameters.generate_private_key() + assert isinstance(key, dh.DHPrivateKey) + assert key.key_size == key_size + + public = key.public_key() + assert isinstance(public, dh.DHPublicKey) + assert public.key_size == key_size + + assert isinstance(parameters, dh.DHParametersWithSerialization) + parameter_numbers = parameters.parameter_numbers() + assert isinstance(parameter_numbers, dh.DHParameterNumbers) + assert parameter_numbers.p.bit_length() == key_size + + assert isinstance(public, dh.DHPublicKeyWithSerialization) + assert isinstance(public.public_numbers(), dh.DHPublicNumbers) + assert isinstance(public.parameters(), dh.DHParameters) + + assert isinstance(key, dh.DHPrivateKeyWithSerialization) + assert isinstance(key.private_numbers(), dh.DHPrivateNumbers) + assert isinstance(key.parameters(), dh.DHParameters) + + def test_exchange(self, backend): + parameters = dh.generate_parameters(2, 512, backend) + assert isinstance(parameters, dh.DHParameters) + + key1 = parameters.generate_private_key() + key2 = parameters.generate_private_key() + + symkey1 = key1.exchange(key2.public_key()) + assert symkey1 + assert len(symkey1) == 512 // 8 + + symkey2 = key2.exchange(key1.public_key()) + assert symkey1 == symkey2 + + def test_exchange_algorithm(self, backend): + parameters = dh.generate_parameters(2, 512, backend) + + key1 = parameters.generate_private_key() + key2 = parameters.generate_private_key() + + shared_key_bytes = key2.exchange(key1.public_key()) + symkey = int_from_bytes(shared_key_bytes, 'big') + + symkey_manual = pow(key1.public_key().public_numbers().y, + key2.private_numbers().x, + parameters.parameter_numbers().p) + + assert symkey == symkey_manual + + def test_symmetric_key_padding(self, backend): + """ + This test has specific parameters that produce a symmetric key + In length 63 bytes instead 64. We make sure here that we add + padding to the key. + """ + p = int("11859949538425015739337467917303613431031019140213666" + "129025407300654026585086345323066284800963463204246390" + "256567934582260424238844463330887962689642467123") + g = 2 + y = int("32155788395534640648739966373159697798396966919821525" + "72238852825117261342483718574508213761865276905503199" + "969908098203345481366464874759377454476688391248") + x = int("409364065449673443397833358558926598469347813468816037" + "268451847116982490733450463194921405069999008617231539" + "7147035896687401350877308899732826446337707128") + parameters = dh.DHParameterNumbers(p, g) + public = dh.DHPublicNumbers(y, parameters) + private = dh.DHPrivateNumbers(x, public) + key = private.private_key(backend) + symkey = key.exchange(public.public_key(backend)) + assert len(symkey) == 512 // 8 + assert symkey[:1] == b'\x00' + + @pytest.mark.parametrize( + "vector", + load_vectors_from_file( + os.path.join("asymmetric", "DH", "bad_exchange.txt"), + load_nist_vectors)) + def test_bad_exchange(self, backend, vector): + parameters1 = dh.DHParameterNumbers(int(vector["p1"]), + int(vector["g"])) + public1 = dh.DHPublicNumbers(int(vector["y1"]), parameters1) + private1 = dh.DHPrivateNumbers(int(vector["x1"]), public1) + key1 = private1.private_key(backend) + pub_key1 = key1.public_key() + + parameters2 = dh.DHParameterNumbers(int(vector["p2"]), + int(vector["g"])) + public2 = dh.DHPublicNumbers(int(vector["y2"]), parameters2) + private2 = dh.DHPrivateNumbers(int(vector["x2"]), public2) + key2 = private2.private_key(backend) + pub_key2 = key2.public_key() + + if pub_key2.public_numbers().y >= parameters1.p: + with pytest.raises(ValueError): + key1.exchange(pub_key2) + else: + symkey1 = key1.exchange(pub_key2) + assert symkey1 + + symkey2 = key2.exchange(pub_key1) + + assert symkey1 != symkey2 + + @pytest.mark.parametrize( + "vector", + load_vectors_from_file( + os.path.join("asymmetric", "DH", "vec.txt"), + load_nist_vectors)) + def test_dh_vectors(self, backend, vector): + parameters = dh.DHParameterNumbers(int(vector["p"]), + int(vector["g"])) + public = dh.DHPublicNumbers(int(vector["y"]), parameters) + private = dh.DHPrivateNumbers(int(vector["x"]), public) + key = private.private_key(backend) + symkey = key.exchange(public.public_key(backend)) + + assert int_from_bytes(symkey, 'big') == int(vector["k"], 16) + + @pytest.mark.parametrize( + "vector", + load_vectors_from_file( + os.path.join("asymmetric", "DH", "RFC5114.txt"), + load_nist_vectors)) + def test_dh_vectors_with_q(self, backend, vector): + parameters = dh.DHParameterNumbers(int(vector["p"], 16), + int(vector["g"], 16), + int(vector["q"], 16)) + public1 = dh.DHPublicNumbers(int(vector["ystatcavs"], 16), parameters) + private1 = dh.DHPrivateNumbers(int(vector["xstatcavs"], 16), public1) + public2 = dh.DHPublicNumbers(int(vector["ystatiut"], 16), parameters) + private2 = dh.DHPrivateNumbers(int(vector["xstatiut"], 16), public2) + key1 = private1.private_key(backend) + key2 = private2.private_key(backend) + symkey1 = key1.exchange(public2.public_key(backend)) + symkey2 = key2.exchange(public1.public_key(backend)) + + assert int_from_bytes(symkey1, 'big') == int(vector["z"], 16) + assert int_from_bytes(symkey2, 'big') == int(vector["z"], 16) + + +@pytest.mark.requires_backend_interface(interface=DHBackend) +@pytest.mark.requires_backend_interface(interface=PEMSerializationBackend) +@pytest.mark.requires_backend_interface(interface=DERSerializationBackend) +class TestDHPrivateKeySerialization(object): + + @pytest.mark.parametrize( + ("encoding", "loader_func"), + [ + [ + serialization.Encoding.PEM, + serialization.load_pem_private_key + ], + [ + serialization.Encoding.DER, + serialization.load_der_private_key + ], + ] + ) + def test_private_bytes_unencrypted(self, backend, encoding, + loader_func): + parameters = dh.generate_parameters(2, 512, backend) + key = parameters.generate_private_key() + serialized = key.private_bytes( + encoding, serialization.PrivateFormat.PKCS8, + serialization.NoEncryption() + ) + loaded_key = loader_func(serialized, None, backend) + loaded_priv_num = loaded_key.private_numbers() + priv_num = key.private_numbers() + assert loaded_priv_num == priv_num + + @pytest.mark.parametrize( + ("encoding", "fmt"), + [ + (serialization.Encoding.Raw, serialization.PrivateFormat.PKCS8), + (serialization.Encoding.DER, serialization.PrivateFormat.Raw), + (serialization.Encoding.Raw, serialization.PrivateFormat.Raw), + (serialization.Encoding.X962, serialization.PrivateFormat.PKCS8), + ] + ) + def test_private_bytes_rejects_invalid(self, encoding, fmt, backend): + parameters = dh.generate_parameters(2, 512, backend) + key = parameters.generate_private_key() + with pytest.raises(ValueError): + key.private_bytes(encoding, fmt, serialization.NoEncryption()) + + @pytest.mark.parametrize( + ("key_path", "loader_func", "encoding", "is_dhx"), + [ + ( + os.path.join("asymmetric", "DH", "dhkey.pem"), + serialization.load_pem_private_key, + serialization.Encoding.PEM, + False, + ), ( + os.path.join("asymmetric", "DH", "dhkey.der"), + serialization.load_der_private_key, + serialization.Encoding.DER, + False, + ), ( + os.path.join("asymmetric", "DH", "dhkey_rfc5114_2.pem"), + serialization.load_pem_private_key, + serialization.Encoding.PEM, + True, + ), ( + os.path.join("asymmetric", "DH", "dhkey_rfc5114_2.der"), + serialization.load_der_private_key, + serialization.Encoding.DER, + True, + ) + ] + ) + def test_private_bytes_match(self, key_path, loader_func, + encoding, is_dhx, backend): + _skip_dhx_unsupported(backend, is_dhx) + key_bytes = load_vectors_from_file( + key_path, + lambda pemfile: pemfile.read(), mode="rb" + ) + key = loader_func(key_bytes, None, backend) + serialized = key.private_bytes( + encoding, serialization.PrivateFormat.PKCS8, + serialization.NoEncryption() + ) + assert serialized == key_bytes + + @pytest.mark.parametrize( + ("key_path", "loader_func", "vec_path", "is_dhx"), + [ + ( + os.path.join("asymmetric", "DH", "dhkey.pem"), + serialization.load_pem_private_key, + os.path.join("asymmetric", "DH", "dhkey.txt"), + False, + ), ( + os.path.join("asymmetric", "DH", "dhkey.der"), + serialization.load_der_private_key, + os.path.join("asymmetric", "DH", "dhkey.txt"), + False, + ), ( + os.path.join("asymmetric", "DH", "dhkey_rfc5114_2.pem"), + serialization.load_pem_private_key, + os.path.join("asymmetric", "DH", "dhkey_rfc5114_2.txt"), + True, + ), ( + os.path.join("asymmetric", "DH", "dhkey_rfc5114_2.der"), + serialization.load_der_private_key, + os.path.join("asymmetric", "DH", "dhkey_rfc5114_2.txt"), + True, + ) + ] + ) + def test_private_bytes_values(self, key_path, loader_func, + vec_path, is_dhx, backend): + _skip_dhx_unsupported(backend, is_dhx) + key_bytes = load_vectors_from_file( + key_path, + lambda pemfile: pemfile.read(), mode="rb" + ) + vec = load_vectors_from_file(vec_path, load_nist_vectors)[0] + key = loader_func(key_bytes, None, backend) + private_numbers = key.private_numbers() + assert private_numbers.x == int(vec["x"], 16) + assert private_numbers.public_numbers.y == int(vec["y"], 16) + assert private_numbers.public_numbers.parameter_numbers.g == int( + vec["g"], 16) + assert private_numbers.public_numbers.parameter_numbers.p == int( + vec["p"], 16) + if "q" in vec: + assert private_numbers.public_numbers.parameter_numbers.q == int( + vec["q"], 16) + else: + assert private_numbers.public_numbers.parameter_numbers.q is None + + def test_private_bytes_traditional_openssl_invalid(self, backend): + parameters = dh.generate_parameters(2, 512, backend) + key = parameters.generate_private_key() + with pytest.raises(ValueError): + key.private_bytes( + serialization.Encoding.PEM, + serialization.PrivateFormat.TraditionalOpenSSL, + serialization.NoEncryption() + ) + + def test_private_bytes_invalid_encoding(self, backend): + parameters = dh.generate_parameters(2, 512, backend) + key = parameters.generate_private_key() + with pytest.raises(TypeError): + key.private_bytes( + "notencoding", + serialization.PrivateFormat.PKCS8, + serialization.NoEncryption() + ) + + def test_private_bytes_invalid_format(self, backend): + parameters = dh.generate_parameters(2, 512, backend) + key = parameters.generate_private_key() + with pytest.raises(ValueError): + key.private_bytes( + serialization.Encoding.PEM, + "invalidformat", + serialization.NoEncryption() + ) + + def test_private_bytes_invalid_encryption_algorithm(self, backend): + parameters = dh.generate_parameters(2, 512, backend) + key = parameters.generate_private_key() + with pytest.raises(TypeError): + key.private_bytes( + serialization.Encoding.PEM, + serialization.PrivateFormat.PKCS8, + "notanencalg" + ) + + def test_private_bytes_unsupported_encryption_type(self, backend): + parameters = dh.generate_parameters(2, 512, backend) + key = parameters.generate_private_key() + with pytest.raises(ValueError): + key.private_bytes( + serialization.Encoding.PEM, + serialization.PrivateFormat.PKCS8, + DummyKeySerializationEncryption() + ) + + +@pytest.mark.requires_backend_interface(interface=DHBackend) +@pytest.mark.requires_backend_interface(interface=PEMSerializationBackend) +@pytest.mark.requires_backend_interface(interface=DERSerializationBackend) +class TestDHPublicKeySerialization(object): + + @pytest.mark.parametrize( + ("encoding", "loader_func"), + [ + [ + serialization.Encoding.PEM, + serialization.load_pem_public_key + ], + [ + serialization.Encoding.DER, + serialization.load_der_public_key + ], + ] + ) + def test_public_bytes(self, backend, encoding, + loader_func): + parameters = dh.generate_parameters(2, 512, backend) + key = parameters.generate_private_key().public_key() + serialized = key.public_bytes( + encoding, serialization.PublicFormat.SubjectPublicKeyInfo + ) + loaded_key = loader_func(serialized, backend) + loaded_pub_num = loaded_key.public_numbers() + pub_num = key.public_numbers() + assert loaded_pub_num == pub_num + + @pytest.mark.parametrize( + ("key_path", "loader_func", "encoding", "is_dhx"), + [ + ( + os.path.join("asymmetric", "DH", "dhpub.pem"), + serialization.load_pem_public_key, + serialization.Encoding.PEM, + False, + ), ( + os.path.join("asymmetric", "DH", "dhpub.der"), + serialization.load_der_public_key, + serialization.Encoding.DER, + False, + ), ( + os.path.join("asymmetric", "DH", "dhpub_rfc5114_2.pem"), + serialization.load_pem_public_key, + serialization.Encoding.PEM, + True, + ), ( + os.path.join("asymmetric", "DH", "dhpub_rfc5114_2.der"), + serialization.load_der_public_key, + serialization.Encoding.DER, + True, + ) + ] + ) + def test_public_bytes_match(self, key_path, loader_func, + encoding, is_dhx, backend): + _skip_dhx_unsupported(backend, is_dhx) + key_bytes = load_vectors_from_file( + key_path, + lambda pemfile: pemfile.read(), mode="rb" + ) + pub_key = loader_func(key_bytes, backend) + serialized = pub_key.public_bytes( + encoding, + serialization.PublicFormat.SubjectPublicKeyInfo, + ) + assert serialized == key_bytes + + @pytest.mark.parametrize( + ("key_path", "loader_func", "vec_path", "is_dhx"), + [ + ( + os.path.join("asymmetric", "DH", "dhpub.pem"), + serialization.load_pem_public_key, + os.path.join("asymmetric", "DH", "dhkey.txt"), + False, + ), ( + os.path.join("asymmetric", "DH", "dhpub.der"), + serialization.load_der_public_key, + os.path.join("asymmetric", "DH", "dhkey.txt"), + False, + ), ( + os.path.join("asymmetric", "DH", "dhpub_rfc5114_2.pem"), + serialization.load_pem_public_key, + os.path.join("asymmetric", "DH", "dhkey_rfc5114_2.txt"), + True, + ), ( + os.path.join("asymmetric", "DH", "dhpub_rfc5114_2.der"), + serialization.load_der_public_key, + os.path.join("asymmetric", "DH", "dhkey_rfc5114_2.txt"), + True, + ) + ] + ) + def test_public_bytes_values(self, key_path, loader_func, + vec_path, is_dhx, backend): + _skip_dhx_unsupported(backend, is_dhx) + key_bytes = load_vectors_from_file( + key_path, + lambda pemfile: pemfile.read(), mode="rb" + ) + vec = load_vectors_from_file(vec_path, load_nist_vectors)[0] + pub_key = loader_func(key_bytes, backend) + public_numbers = pub_key.public_numbers() + assert public_numbers.y == int(vec["y"], 16) + assert public_numbers.parameter_numbers.g == int(vec["g"], 16) + assert public_numbers.parameter_numbers.p == int(vec["p"], 16) + if "q" in vec: + assert public_numbers.parameter_numbers.q == int(vec["q"], 16) + else: + assert public_numbers.parameter_numbers.q is None + + def test_public_bytes_invalid_encoding(self, backend): + parameters = dh.generate_parameters(2, 512, backend) + key = parameters.generate_private_key().public_key() + with pytest.raises(TypeError): + key.public_bytes( + "notencoding", + serialization.PublicFormat.SubjectPublicKeyInfo + ) + + def test_public_bytes_pkcs1_unsupported(self, backend): + parameters = dh.generate_parameters(2, 512, backend) + key = parameters.generate_private_key().public_key() + with pytest.raises(ValueError): + key.public_bytes( + serialization.Encoding.PEM, serialization.PublicFormat.PKCS1 + ) + + +@pytest.mark.requires_backend_interface(interface=DHBackend) +@pytest.mark.requires_backend_interface(interface=PEMSerializationBackend) +@pytest.mark.requires_backend_interface(interface=DERSerializationBackend) +class TestDHParameterSerialization(object): + + @pytest.mark.parametrize( + ("encoding", "loader_func"), + [ + [ + serialization.Encoding.PEM, + serialization.load_pem_parameters + ], + [ + serialization.Encoding.DER, + serialization.load_der_parameters + ], + ] + ) + def test_parameter_bytes(self, backend, encoding, + loader_func): + parameters = dh.generate_parameters(2, 512, backend) + serialized = parameters.parameter_bytes( + encoding, serialization.ParameterFormat.PKCS3 + ) + loaded_key = loader_func(serialized, backend) + loaded_param_num = loaded_key.parameter_numbers() + assert loaded_param_num == parameters.parameter_numbers() + + @pytest.mark.parametrize( + ("param_path", "loader_func", "encoding", "is_dhx"), + [ + ( + os.path.join("asymmetric", "DH", "dhp.pem"), + serialization.load_pem_parameters, + serialization.Encoding.PEM, + False, + ), ( + os.path.join("asymmetric", "DH", "dhp.der"), + serialization.load_der_parameters, + serialization.Encoding.DER, + False, + ), ( + os.path.join("asymmetric", "DH", "dhp_rfc5114_2.pem"), + serialization.load_pem_parameters, + serialization.Encoding.PEM, + True, + ), ( + os.path.join("asymmetric", "DH", "dhp_rfc5114_2.der"), + serialization.load_der_parameters, + serialization.Encoding.DER, + True, + ) + ] + ) + def test_parameter_bytes_match(self, param_path, loader_func, + encoding, backend, is_dhx): + _skip_dhx_unsupported(backend, is_dhx) + param_bytes = load_vectors_from_file( + param_path, + lambda pemfile: pemfile.read(), mode="rb" + ) + parameters = loader_func(param_bytes, backend) + serialized = parameters.parameter_bytes( + encoding, + serialization.ParameterFormat.PKCS3, + ) + assert serialized == param_bytes + + @pytest.mark.parametrize( + ("param_path", "loader_func", "vec_path", "is_dhx"), + [ + ( + os.path.join("asymmetric", "DH", "dhp.pem"), + serialization.load_pem_parameters, + os.path.join("asymmetric", "DH", "dhkey.txt"), + False, + ), ( + os.path.join("asymmetric", "DH", "dhp.der"), + serialization.load_der_parameters, + os.path.join("asymmetric", "DH", "dhkey.txt"), + False, + ), ( + os.path.join("asymmetric", "DH", "dhp_rfc5114_2.pem"), + serialization.load_pem_parameters, + os.path.join("asymmetric", "DH", "dhkey_rfc5114_2.txt"), + True, + ), ( + os.path.join("asymmetric", "DH", "dhp_rfc5114_2.der"), + serialization.load_der_parameters, + os.path.join("asymmetric", "DH", "dhkey_rfc5114_2.txt"), + True, + ) + ] + ) + def test_public_bytes_values(self, param_path, loader_func, + vec_path, backend, is_dhx): + _skip_dhx_unsupported(backend, is_dhx) + key_bytes = load_vectors_from_file( + param_path, + lambda pemfile: pemfile.read(), mode="rb" + ) + vec = load_vectors_from_file(vec_path, load_nist_vectors)[0] + parameters = loader_func(key_bytes, backend) + parameter_numbers = parameters.parameter_numbers() + assert parameter_numbers.g == int(vec["g"], 16) + assert parameter_numbers.p == int(vec["p"], 16) + if "q" in vec: + assert parameter_numbers.q == int(vec["q"], 16) + else: + assert parameter_numbers.q is None + + @pytest.mark.parametrize( + ("encoding", "fmt"), + [ + ( + serialization.Encoding.Raw, + serialization.PublicFormat.SubjectPublicKeyInfo + ), + (serialization.Encoding.Raw, serialization.PublicFormat.PKCS1), + ] + list(itertools.product( + [ + serialization.Encoding.Raw, + serialization.Encoding.X962, + serialization.Encoding.PEM, + serialization.Encoding.DER + ], + [ + serialization.PublicFormat.Raw, + serialization.PublicFormat.UncompressedPoint, + serialization.PublicFormat.CompressedPoint + ] + )) + ) + def test_public_bytes_rejects_invalid(self, encoding, fmt, backend): + parameters = dh.generate_parameters(2, 512, backend) + key = parameters.generate_private_key().public_key() + with pytest.raises(ValueError): + key.public_bytes(encoding, fmt) + + def test_parameter_bytes_invalid_encoding(self, backend): + parameters = dh.generate_parameters(2, 512, backend) + with pytest.raises(TypeError): + parameters.parameter_bytes( + "notencoding", + serialization.ParameterFormat.PKCS3 + ) + + def test_parameter_bytes_invalid_format(self, backend): + parameters = dh.generate_parameters(2, 512, backend) + with pytest.raises(ValueError): + parameters.parameter_bytes( + serialization.Encoding.PEM, + "notformat" + ) + + def test_parameter_bytes_openssh_unsupported(self, backend): + parameters = dh.generate_parameters(2, 512, backend) + with pytest.raises(TypeError): + parameters.parameter_bytes( + serialization.Encoding.OpenSSH, + serialization.ParameterFormat.PKCS3 + ) diff --git a/tests/hazmat/primitives/test_dsa.py b/tests/hazmat/primitives/test_dsa.py index 5c83d5c7..9e1acf93 100644 --- a/tests/hazmat/primitives/test_dsa.py +++ b/tests/hazmat/primitives/test_dsa.py @@ -9,7 +9,6 @@ import os import pytest -from cryptography import utils from cryptography.exceptions import AlreadyFinalized, InvalidSignature from cryptography.hazmat.backends.interfaces import ( DSABackend, PEMSerializationBackend @@ -17,37 +16,34 @@ from cryptography.hazmat.backends.interfaces import ( from cryptography.hazmat.primitives import hashes, serialization from cryptography.hazmat.primitives.asymmetric import dsa from cryptography.hazmat.primitives.asymmetric.utils import ( - encode_rfc6979_signature + Prehashed, encode_dss_signature ) -from cryptography.utils import bit_length +from cryptography.utils import CryptographyDeprecationWarning from .fixtures_dsa import ( DSA_KEY_1024, DSA_KEY_2048, DSA_KEY_3072 ) +from ...doubles import DummyHashAlgorithm, DummyKeySerializationEncryption from ...utils import ( load_fips_dsa_key_pair_vectors, load_fips_dsa_sig_vectors, load_vectors_from_file, ) -def _skip_if_no_serialization(key, backend): - if not isinstance( - key, - (dsa.DSAPrivateKeyWithSerialization, dsa.DSAPublicKeyWithSerialization) +def _skip_if_dsa_not_supported(backend, algorithm, p, q, g): + if ( + not backend.dsa_parameters_supported(p, q, g) or + not backend.dsa_hash_supported(algorithm) ): pytest.skip( - "{0} does not support DSA key serialization".format(backend) + "{} does not support the provided parameters".format(backend) ) -def test_skip_if_no_serialization(): +@pytest.mark.requires_backend_interface(interface=DSABackend) +def test_skip_if_dsa_not_supported(backend): with pytest.raises(pytest.skip.Exception): - _skip_if_no_serialization("notakeywithserialization", "backend") - - -@utils.register_interface(serialization.KeySerializationEncryption) -class DummyKeyEncryption(object): - pass + _skip_if_dsa_not_supported(backend, DummyHashAlgorithm(), 1, 1, 1) @pytest.mark.requires_backend_interface(interface=DSABackend) @@ -75,476 +71,282 @@ class TestDSA(object): g=vector['g'] ).parameters(backend) skey = parameters.generate_private_key() - if isinstance(skey, dsa.DSAPrivateKeyWithSerialization): - numbers = skey.private_numbers() - skey_parameters = numbers.public_numbers.parameter_numbers - pkey = skey.public_key() - parameters = pkey.parameters() - parameter_numbers = parameters.parameter_numbers() - assert parameter_numbers.p == skey_parameters.p - assert parameter_numbers.q == skey_parameters.q - assert parameter_numbers.g == skey_parameters.g - assert skey_parameters.p == vector['p'] - assert skey_parameters.q == vector['q'] - assert skey_parameters.g == vector['g'] - assert skey.key_size == bit_length(vector['p']) - assert pkey.key_size == skey.key_size - public_numbers = pkey.public_numbers() - assert numbers.public_numbers.y == public_numbers.y - assert numbers.public_numbers.y == pow( - skey_parameters.g, numbers.x, skey_parameters.p - ) + numbers = skey.private_numbers() + skey_parameters = numbers.public_numbers.parameter_numbers + pkey = skey.public_key() + parameters = pkey.parameters() + parameter_numbers = parameters.parameter_numbers() + assert parameter_numbers.p == skey_parameters.p + assert parameter_numbers.q == skey_parameters.q + assert parameter_numbers.g == skey_parameters.g + assert skey_parameters.p == vector['p'] + assert skey_parameters.q == vector['q'] + assert skey_parameters.g == vector['g'] + assert skey.key_size == vector['p'].bit_length() + assert pkey.key_size == skey.key_size + public_numbers = pkey.public_numbers() + assert numbers.public_numbers.y == public_numbers.y + assert numbers.public_numbers.y == pow( + skey_parameters.g, numbers.x, skey_parameters.p + ) def test_generate_dsa_private_key_and_parameters(self, backend): skey = dsa.generate_private_key(1024, backend) assert skey - if isinstance(skey, dsa.DSAPrivateKeyWithSerialization): - numbers = skey.private_numbers() - skey_parameters = numbers.public_numbers.parameter_numbers - assert numbers.public_numbers.y == pow( - skey_parameters.g, numbers.x, skey_parameters.p - ) - - def test_invalid_parameters_values(self, backend): - # Test a p < 1024 bits in length - with pytest.raises(ValueError): - dsa.DSAParameterNumbers( - p=2 ** 1000, - q=DSA_KEY_1024.public_numbers.parameter_numbers.q, - g=DSA_KEY_1024.public_numbers.parameter_numbers.g, - ).parameters(backend) - - # Test a p < 2048 bits in length - with pytest.raises(ValueError): - dsa.DSAParameterNumbers( - p=2 ** 2000, - q=DSA_KEY_2048.public_numbers.parameter_numbers.q, - g=DSA_KEY_2048.public_numbers.parameter_numbers.g, - ).parameters(backend) - - # Test a p < 3072 bits in length - with pytest.raises(ValueError): - dsa.DSAParameterNumbers( - p=2 ** 3000, - q=DSA_KEY_3072.public_numbers.parameter_numbers.q, - g=DSA_KEY_3072.public_numbers.parameter_numbers.g, - ).parameters(backend) - - # Test a p > 3072 bits in length - with pytest.raises(ValueError): - dsa.DSAParameterNumbers( - p=2 ** 3100, - q=DSA_KEY_3072.public_numbers.parameter_numbers.q, - g=DSA_KEY_3072.public_numbers.parameter_numbers.g, - ).parameters(backend) - - # Test a q < 160 bits in length - with pytest.raises(ValueError): - dsa.DSAParameterNumbers( - p=DSA_KEY_1024.public_numbers.parameter_numbers.p, - q=2 ** 150, - g=DSA_KEY_1024.public_numbers.parameter_numbers.g, - ).parameters(backend) - - # Test a q < 256 bits in length - with pytest.raises(ValueError): - dsa.DSAParameterNumbers( - p=DSA_KEY_2048.public_numbers.parameter_numbers.p, - q=2 ** 250, - g=DSA_KEY_2048.public_numbers.parameter_numbers.g - ).parameters(backend) - - # Test a q > 256 bits in length - with pytest.raises(ValueError): - dsa.DSAParameterNumbers( - p=DSA_KEY_3072.public_numbers.parameter_numbers.p, - q=2 ** 260, - g=DSA_KEY_3072.public_numbers.parameter_numbers.g, - ).parameters(backend) - - # Test a g < 1 - with pytest.raises(ValueError): - dsa.DSAParameterNumbers( - p=DSA_KEY_1024.public_numbers.parameter_numbers.p, - q=DSA_KEY_1024.public_numbers.parameter_numbers.q, - g=0 - ).parameters(backend) - - # Test a g = 1 - with pytest.raises(ValueError): - dsa.DSAParameterNumbers( - p=DSA_KEY_1024.public_numbers.parameter_numbers.p, - q=DSA_KEY_1024.public_numbers.parameter_numbers.q, - g=1 - ).parameters(backend) - - # Test a g > p - with pytest.raises(ValueError): - dsa.DSAParameterNumbers( - p=DSA_KEY_1024.public_numbers.parameter_numbers.p, - q=DSA_KEY_1024.public_numbers.parameter_numbers.q, - g=2 ** 1200 - ).parameters(backend) - - def test_invalid_dsa_private_key_arguments(self, backend): - # Test a p < 1024 bits in length - with pytest.raises(ValueError): - dsa.DSAPrivateNumbers( - public_numbers=dsa.DSAPublicNumbers( - parameter_numbers=dsa.DSAParameterNumbers( - p=2 ** 1000, - q=DSA_KEY_1024.public_numbers.parameter_numbers.q, - g=DSA_KEY_1024.public_numbers.parameter_numbers.g, - ), - y=DSA_KEY_1024.public_numbers.y - ), - x=DSA_KEY_1024.x - ).private_key(backend) - - # Test a p < 2048 bits in length - with pytest.raises(ValueError): - dsa.DSAPrivateNumbers( - public_numbers=dsa.DSAPublicNumbers( - parameter_numbers=dsa.DSAParameterNumbers( - p=2 ** 2000, - q=DSA_KEY_2048.public_numbers.parameter_numbers.q, - g=DSA_KEY_2048.public_numbers.parameter_numbers.g, - ), - y=DSA_KEY_2048.public_numbers.y - ), - x=DSA_KEY_2048.x, - ).private_key(backend) - - # Test a p < 3072 bits in length - with pytest.raises(ValueError): - dsa.DSAPrivateNumbers( - public_numbers=dsa.DSAPublicNumbers( - parameter_numbers=dsa.DSAParameterNumbers( - p=2 ** 3000, - q=DSA_KEY_3072.public_numbers.parameter_numbers.q, - g=DSA_KEY_3072.public_numbers.parameter_numbers.g, - ), - y=DSA_KEY_3072.public_numbers.y - ), - x=DSA_KEY_3072.x, - ).private_key(backend) - - # Test a p > 3072 bits in length - with pytest.raises(ValueError): - dsa.DSAPrivateNumbers( - public_numbers=dsa.DSAPublicNumbers( - parameter_numbers=dsa.DSAParameterNumbers( - p=2 ** 3100, - q=DSA_KEY_3072.public_numbers.parameter_numbers.q, - g=DSA_KEY_3072.public_numbers.parameter_numbers.g, - ), - y=DSA_KEY_3072.public_numbers.y - ), - x=DSA_KEY_3072.x, - ).private_key(backend) - - # Test a q < 160 bits in length - with pytest.raises(ValueError): - dsa.DSAPrivateNumbers( - public_numbers=dsa.DSAPublicNumbers( - parameter_numbers=dsa.DSAParameterNumbers( - p=DSA_KEY_1024.public_numbers.parameter_numbers.p, - q=2 ** 150, - g=DSA_KEY_1024.public_numbers.parameter_numbers.g, - ), - y=DSA_KEY_1024.public_numbers.y - ), - x=DSA_KEY_1024.x, - ).private_key(backend) - - # Test a q < 256 bits in length - with pytest.raises(ValueError): - dsa.DSAPrivateNumbers( - public_numbers=dsa.DSAPublicNumbers( - parameter_numbers=dsa.DSAParameterNumbers( - p=DSA_KEY_2048.public_numbers.parameter_numbers.p, - q=2 ** 250, - g=DSA_KEY_2048.public_numbers.parameter_numbers.g, - ), - y=DSA_KEY_2048.public_numbers.y - ), - x=DSA_KEY_2048.x, - ).private_key(backend) - - # Test a q > 256 bits in length - with pytest.raises(ValueError): - dsa.DSAPrivateNumbers( - public_numbers=dsa.DSAPublicNumbers( - parameter_numbers=dsa.DSAParameterNumbers( - p=DSA_KEY_3072.public_numbers.parameter_numbers.p, - q=2 ** 260, - g=DSA_KEY_3072.public_numbers.parameter_numbers.g, - ), - y=DSA_KEY_3072.public_numbers.y - ), - x=DSA_KEY_3072.x, - ).private_key(backend) - - # Test a g < 1 - with pytest.raises(ValueError): - dsa.DSAPrivateNumbers( - public_numbers=dsa.DSAPublicNumbers( - parameter_numbers=dsa.DSAParameterNumbers( - p=DSA_KEY_1024.public_numbers.parameter_numbers.p, - q=DSA_KEY_1024.public_numbers.parameter_numbers.q, - g=0, - ), - y=DSA_KEY_1024.public_numbers.y - ), - x=DSA_KEY_1024.x, - ).private_key(backend) - - # Test a g = 1 - with pytest.raises(ValueError): - dsa.DSAPrivateNumbers( - public_numbers=dsa.DSAPublicNumbers( - parameter_numbers=dsa.DSAParameterNumbers( - p=DSA_KEY_1024.public_numbers.parameter_numbers.p, - q=DSA_KEY_1024.public_numbers.parameter_numbers.q, - g=1, - ), - y=DSA_KEY_1024.public_numbers.y - ), - x=DSA_KEY_1024.x, - ).private_key(backend) - - # Test a g > p - with pytest.raises(ValueError): - dsa.DSAPrivateNumbers( - public_numbers=dsa.DSAPublicNumbers( - parameter_numbers=dsa.DSAParameterNumbers( - p=DSA_KEY_1024.public_numbers.parameter_numbers.p, - q=DSA_KEY_1024.public_numbers.parameter_numbers.q, - g=2 ** 1200, - ), - y=DSA_KEY_1024.public_numbers.y - ), - x=DSA_KEY_1024.x, - ).private_key(backend) - - # Test x = 0 - with pytest.raises(ValueError): - dsa.DSAPrivateNumbers( - public_numbers=dsa.DSAPublicNumbers( - parameter_numbers=dsa.DSAParameterNumbers( - p=DSA_KEY_1024.public_numbers.parameter_numbers.p, - q=DSA_KEY_1024.public_numbers.parameter_numbers.q, - g=DSA_KEY_1024.public_numbers.parameter_numbers.g, - ), - y=DSA_KEY_1024.public_numbers.y - ), - x=0, - ).private_key(backend) - - # Test x < 0 - with pytest.raises(ValueError): - dsa.DSAPrivateNumbers( - public_numbers=dsa.DSAPublicNumbers( - parameter_numbers=dsa.DSAParameterNumbers( - p=DSA_KEY_1024.public_numbers.parameter_numbers.p, - q=DSA_KEY_1024.public_numbers.parameter_numbers.q, - g=DSA_KEY_1024.public_numbers.parameter_numbers.g, - ), - y=DSA_KEY_1024.public_numbers.y - ), - x=-2, - ).private_key(backend) - - # Test x = q - with pytest.raises(ValueError): - dsa.DSAPrivateNumbers( - public_numbers=dsa.DSAPublicNumbers( - parameter_numbers=dsa.DSAParameterNumbers( - p=DSA_KEY_1024.public_numbers.parameter_numbers.p, - q=DSA_KEY_1024.public_numbers.parameter_numbers.q, - g=DSA_KEY_1024.public_numbers.parameter_numbers.g, - ), - y=DSA_KEY_1024.public_numbers.y - ), - x=2 ** 159, - ).private_key(backend) + numbers = skey.private_numbers() + skey_parameters = numbers.public_numbers.parameter_numbers + assert numbers.public_numbers.y == pow( + skey_parameters.g, numbers.x, skey_parameters.p + ) - # Test x > q + @pytest.mark.parametrize( + ("p", "q", "g"), + [ + ( + 2 ** 1000, + DSA_KEY_1024.public_numbers.parameter_numbers.q, + DSA_KEY_1024.public_numbers.parameter_numbers.g, + ), + ( + 2 ** 2000, + DSA_KEY_2048.public_numbers.parameter_numbers.q, + DSA_KEY_2048.public_numbers.parameter_numbers.g, + ), + ( + 2 ** 3000, + DSA_KEY_3072.public_numbers.parameter_numbers.q, + DSA_KEY_3072.public_numbers.parameter_numbers.g, + ), + ( + 2 ** 3100, + DSA_KEY_3072.public_numbers.parameter_numbers.q, + DSA_KEY_3072.public_numbers.parameter_numbers.g, + ), + ( + DSA_KEY_1024.public_numbers.parameter_numbers.p, + 2 ** 150, + DSA_KEY_1024.public_numbers.parameter_numbers.g, + ), + ( + DSA_KEY_2048.public_numbers.parameter_numbers.p, + 2 ** 250, + DSA_KEY_2048.public_numbers.parameter_numbers.g + ), + ( + DSA_KEY_3072.public_numbers.parameter_numbers.p, + 2 ** 260, + DSA_KEY_3072.public_numbers.parameter_numbers.g, + ), + ( + DSA_KEY_1024.public_numbers.parameter_numbers.p, + DSA_KEY_1024.public_numbers.parameter_numbers.q, + 0 + ), + ( + DSA_KEY_1024.public_numbers.parameter_numbers.p, + DSA_KEY_1024.public_numbers.parameter_numbers.q, + 1 + ), + ( + DSA_KEY_1024.public_numbers.parameter_numbers.p, + DSA_KEY_1024.public_numbers.parameter_numbers.q, + 2 ** 1200 + ), + ] + ) + def test_invalid_parameters_values(self, p, q, g, backend): with pytest.raises(ValueError): - dsa.DSAPrivateNumbers( - public_numbers=dsa.DSAPublicNumbers( - parameter_numbers=dsa.DSAParameterNumbers( - p=DSA_KEY_1024.public_numbers.parameter_numbers.p, - q=DSA_KEY_1024.public_numbers.parameter_numbers.q, - g=DSA_KEY_1024.public_numbers.parameter_numbers.g, - ), - y=DSA_KEY_1024.public_numbers.y - ), - x=2 ** 200, - ).private_key(backend) + dsa.DSAParameterNumbers(p, q, g).parameters(backend) - # Test y != (g ** x) % p + @pytest.mark.parametrize( + ("p", "q", "g", "y", "x"), + [ + ( + 2 ** 1000, + DSA_KEY_1024.public_numbers.parameter_numbers.q, + DSA_KEY_1024.public_numbers.parameter_numbers.g, + DSA_KEY_1024.public_numbers.y, + DSA_KEY_1024.x, + ), + ( + 2 ** 2000, + DSA_KEY_2048.public_numbers.parameter_numbers.q, + DSA_KEY_2048.public_numbers.parameter_numbers.g, + DSA_KEY_2048.public_numbers.y, + DSA_KEY_2048.x, + ), + ( + 2 ** 3000, + DSA_KEY_3072.public_numbers.parameter_numbers.q, + DSA_KEY_3072.public_numbers.parameter_numbers.g, + DSA_KEY_3072.public_numbers.y, + DSA_KEY_3072.x, + ), + ( + 2 ** 3100, + DSA_KEY_3072.public_numbers.parameter_numbers.q, + DSA_KEY_3072.public_numbers.parameter_numbers.g, + DSA_KEY_3072.public_numbers.y, + DSA_KEY_3072.x, + ), + ( + DSA_KEY_1024.public_numbers.parameter_numbers.p, + 2 ** 150, + DSA_KEY_1024.public_numbers.parameter_numbers.g, + DSA_KEY_1024.public_numbers.y, + DSA_KEY_1024.x, + ), + ( + DSA_KEY_2048.public_numbers.parameter_numbers.p, + 2 ** 250, + DSA_KEY_2048.public_numbers.parameter_numbers.g, + DSA_KEY_2048.public_numbers.y, + DSA_KEY_2048.x, + ), + ( + DSA_KEY_3072.public_numbers.parameter_numbers.p, + 2 ** 260, + DSA_KEY_3072.public_numbers.parameter_numbers.g, + DSA_KEY_3072.public_numbers.y, + DSA_KEY_3072.x, + ), + ( + DSA_KEY_1024.public_numbers.parameter_numbers.p, + DSA_KEY_1024.public_numbers.parameter_numbers.q, + 0, + DSA_KEY_1024.public_numbers.y, + DSA_KEY_1024.x, + ), + ( + DSA_KEY_1024.public_numbers.parameter_numbers.p, + DSA_KEY_1024.public_numbers.parameter_numbers.q, + 1, + DSA_KEY_1024.public_numbers.y, + DSA_KEY_1024.x, + ), + ( + DSA_KEY_1024.public_numbers.parameter_numbers.p, + DSA_KEY_1024.public_numbers.parameter_numbers.q, + 2 ** 1200, + DSA_KEY_1024.public_numbers.y, + DSA_KEY_1024.x, + ), + ( + DSA_KEY_1024.public_numbers.parameter_numbers.p, + DSA_KEY_1024.public_numbers.parameter_numbers.q, + DSA_KEY_1024.public_numbers.parameter_numbers.g, + DSA_KEY_1024.public_numbers.y, + 0, + ), + ( + DSA_KEY_1024.public_numbers.parameter_numbers.p, + DSA_KEY_1024.public_numbers.parameter_numbers.q, + DSA_KEY_1024.public_numbers.parameter_numbers.g, + DSA_KEY_1024.public_numbers.y, + -2, + ), + ( + DSA_KEY_1024.public_numbers.parameter_numbers.p, + DSA_KEY_1024.public_numbers.parameter_numbers.q, + DSA_KEY_1024.public_numbers.parameter_numbers.g, + DSA_KEY_1024.public_numbers.y, + 2 ** 159, + ), + ( + DSA_KEY_1024.public_numbers.parameter_numbers.p, + DSA_KEY_1024.public_numbers.parameter_numbers.q, + DSA_KEY_1024.public_numbers.parameter_numbers.g, + DSA_KEY_1024.public_numbers.y, + 2 ** 200, + ), + ( + DSA_KEY_1024.public_numbers.parameter_numbers.p, + DSA_KEY_1024.public_numbers.parameter_numbers.q, + DSA_KEY_1024.public_numbers.parameter_numbers.g, + 2 ** 100, + DSA_KEY_1024.x + ), + ] + ) + def test_invalid_dsa_private_key_arguments(self, p, q, g, y, x, backend): with pytest.raises(ValueError): dsa.DSAPrivateNumbers( public_numbers=dsa.DSAPublicNumbers( - parameter_numbers=dsa.DSAParameterNumbers( - p=DSA_KEY_1024.public_numbers.parameter_numbers.p, - q=DSA_KEY_1024.public_numbers.parameter_numbers.q, - g=DSA_KEY_1024.public_numbers.parameter_numbers.g, - ), - y=2 ** 100 - ), - x=DSA_KEY_1024.x, + parameter_numbers=dsa.DSAParameterNumbers(p=p, q=q, g=g), + y=y + ), x=x ).private_key(backend) - # Test a non-integer y value - with pytest.raises(TypeError): - dsa.DSAPrivateNumbers( - public_numbers=dsa.DSAPublicNumbers( - parameter_numbers=dsa.DSAParameterNumbers( - p=DSA_KEY_1024.public_numbers.parameter_numbers.p, - q=DSA_KEY_1024.public_numbers.parameter_numbers.q, - g=DSA_KEY_1024.public_numbers.parameter_numbers.g, - ), - y=None - ), - x=DSA_KEY_1024.x, - ).private_key(backend) - - # Test a non-integer x value - with pytest.raises(TypeError): - dsa.DSAPrivateNumbers( - public_numbers=dsa.DSAPublicNumbers( - parameter_numbers=dsa.DSAParameterNumbers( - p=DSA_KEY_1024.public_numbers.parameter_numbers.p, - q=DSA_KEY_1024.public_numbers.parameter_numbers.q, - g=DSA_KEY_1024.public_numbers.parameter_numbers.g, - ), - y=DSA_KEY_1024.public_numbers.y - ), - x=None, - ).private_key(backend) - - def test_invalid_dsa_public_key_arguments(self, backend): - # Test a p < 1024 bits in length - with pytest.raises(ValueError): - dsa.DSAPublicNumbers( - parameter_numbers=dsa.DSAParameterNumbers( - p=2 ** 1000, - q=DSA_KEY_1024.public_numbers.parameter_numbers.q, - g=DSA_KEY_1024.public_numbers.parameter_numbers.g, - ), - y=DSA_KEY_1024.public_numbers.y - ).public_key(backend) - - # Test a p < 2048 bits in length - with pytest.raises(ValueError): - dsa.DSAPublicNumbers( - parameter_numbers=dsa.DSAParameterNumbers( - p=2 ** 2000, - q=DSA_KEY_2048.public_numbers.parameter_numbers.q, - g=DSA_KEY_2048.public_numbers.parameter_numbers.g, - ), - y=DSA_KEY_2048.public_numbers.y - ).public_key(backend) - - # Test a p < 3072 bits in length - with pytest.raises(ValueError): - dsa.DSAPublicNumbers( - parameter_numbers=dsa.DSAParameterNumbers( - p=2 ** 3000, - q=DSA_KEY_3072.public_numbers.parameter_numbers.q, - g=DSA_KEY_3072.public_numbers.parameter_numbers.g, - ), - y=DSA_KEY_3072.public_numbers.y - ).public_key(backend) - - # Test a p > 3072 bits in length - with pytest.raises(ValueError): - dsa.DSAPublicNumbers( - parameter_numbers=dsa.DSAParameterNumbers( - p=2 ** 3100, - q=DSA_KEY_3072.public_numbers.parameter_numbers.q, - g=DSA_KEY_3072.public_numbers.parameter_numbers.g, - ), - y=DSA_KEY_3072.public_numbers.y - ).public_key(backend) - - # Test a q < 160 bits in length - with pytest.raises(ValueError): - dsa.DSAPublicNumbers( - parameter_numbers=dsa.DSAParameterNumbers( - p=DSA_KEY_1024.public_numbers.parameter_numbers.p, - q=2 ** 150, - g=DSA_KEY_1024.public_numbers.parameter_numbers.g, - ), - y=DSA_KEY_1024.public_numbers.y - ).public_key(backend) - - # Test a q < 256 bits in length - with pytest.raises(ValueError): - dsa.DSAPublicNumbers( - parameter_numbers=dsa.DSAParameterNumbers( - p=DSA_KEY_2048.public_numbers.parameter_numbers.p, - q=2 ** 250, - g=DSA_KEY_2048.public_numbers.parameter_numbers.g, - ), - y=DSA_KEY_2048.public_numbers.y - ).public_key(backend) - - # Test a q > 256 bits in length - with pytest.raises(ValueError): - dsa.DSAPublicNumbers( - parameter_numbers=dsa.DSAParameterNumbers( - p=DSA_KEY_3072.public_numbers.parameter_numbers.p, - q=2 ** 260, - g=DSA_KEY_3072.public_numbers.parameter_numbers.g, - ), - y=DSA_KEY_3072.public_numbers.y - ).public_key(backend) - - # Test a g < 1 - with pytest.raises(ValueError): - dsa.DSAPublicNumbers( - parameter_numbers=dsa.DSAParameterNumbers( - p=DSA_KEY_1024.public_numbers.parameter_numbers.p, - q=DSA_KEY_1024.public_numbers.parameter_numbers.q, - g=0, - ), - y=DSA_KEY_1024.public_numbers.y - ).public_key(backend) - - # Test a g = 1 - with pytest.raises(ValueError): - dsa.DSAPublicNumbers( - parameter_numbers=dsa.DSAParameterNumbers( - p=DSA_KEY_1024.public_numbers.parameter_numbers.p, - q=DSA_KEY_1024.public_numbers.parameter_numbers.q, - g=1, - ), - y=DSA_KEY_1024.public_numbers.y - ).public_key(backend) - - # Test a g > p + @pytest.mark.parametrize( + ("p", "q", "g", "y"), + [ + ( + 2 ** 1000, + DSA_KEY_1024.public_numbers.parameter_numbers.q, + DSA_KEY_1024.public_numbers.parameter_numbers.g, + DSA_KEY_1024.public_numbers.y, + ), + ( + 2 ** 2000, + DSA_KEY_2048.public_numbers.parameter_numbers.q, + DSA_KEY_2048.public_numbers.parameter_numbers.g, + DSA_KEY_2048.public_numbers.y, + ), + ( + 2 ** 3000, + DSA_KEY_3072.public_numbers.parameter_numbers.q, + DSA_KEY_3072.public_numbers.parameter_numbers.g, + DSA_KEY_3072.public_numbers.y, + ), + ( + 2 ** 3100, + DSA_KEY_3072.public_numbers.parameter_numbers.q, + DSA_KEY_3072.public_numbers.parameter_numbers.g, + DSA_KEY_3072.public_numbers.y, + ), + ( + DSA_KEY_1024.public_numbers.parameter_numbers.p, + 2 ** 150, + DSA_KEY_1024.public_numbers.parameter_numbers.g, + DSA_KEY_1024.public_numbers.y, + ), + ( + DSA_KEY_2048.public_numbers.parameter_numbers.p, + 2 ** 250, + DSA_KEY_2048.public_numbers.parameter_numbers.g, + DSA_KEY_2048.public_numbers.y, + ), + ( + DSA_KEY_3072.public_numbers.parameter_numbers.p, + 2 ** 260, + DSA_KEY_3072.public_numbers.parameter_numbers.g, + DSA_KEY_3072.public_numbers.y, + ), + ( + DSA_KEY_1024.public_numbers.parameter_numbers.p, + DSA_KEY_1024.public_numbers.parameter_numbers.q, + 0, + DSA_KEY_1024.public_numbers.y, + ), + ( + DSA_KEY_1024.public_numbers.parameter_numbers.p, + DSA_KEY_1024.public_numbers.parameter_numbers.q, + 1, + DSA_KEY_1024.public_numbers.y, + ), + ( + DSA_KEY_1024.public_numbers.parameter_numbers.p, + DSA_KEY_1024.public_numbers.parameter_numbers.q, + 2 ** 1200, + DSA_KEY_1024.public_numbers.y, + ), + ] + ) + def test_invalid_dsa_public_key_arguments(self, p, q, g, y, backend): with pytest.raises(ValueError): dsa.DSAPublicNumbers( - parameter_numbers=dsa.DSAParameterNumbers( - p=DSA_KEY_1024.public_numbers.parameter_numbers.p, - q=DSA_KEY_1024.public_numbers.parameter_numbers.q, - g=2 ** 1200, - ), - y=DSA_KEY_1024.public_numbers.y - ).public_key(backend) - - # Test a non-integer y value - with pytest.raises(TypeError): - dsa.DSAPublicNumbers( - parameter_numbers=dsa.DSAParameterNumbers( - p=DSA_KEY_1024.public_numbers.parameter_numbers.p, - q=DSA_KEY_1024.public_numbers.parameter_numbers.q, - g=DSA_KEY_1024.public_numbers.parameter_numbers.g, - ), - y=None + parameter_numbers=dsa.DSAParameterNumbers(p=p, q=q, g=g), + y=y ).public_key(backend) @@ -569,14 +371,10 @@ class TestDSAVerification(object): def test_dsa_verification(self, vector, backend): digest_algorithm = vector['digest_algorithm'].replace("-", "") algorithm = self._algorithms_dict[digest_algorithm] - if ( - not backend.dsa_parameters_supported( - vector['p'], vector['q'], vector['g'] - ) or not backend.dsa_hash_supported(algorithm) - ): - pytest.skip( - "{0} does not support the provided parameters".format(backend) - ) + + _skip_if_dsa_not_supported( + backend, algorithm, vector['p'], vector['q'], vector['g'] + ) public_key = dsa.DSAPublicNumbers( parameter_numbers=dsa.DSAParameterNumbers( @@ -584,25 +382,29 @@ class TestDSAVerification(object): ), y=vector['y'] ).public_key(backend) - sig = encode_rfc6979_signature(vector['r'], vector['s']) - verifier = public_key.verifier(sig, algorithm()) - verifier.update(vector['msg']) + sig = encode_dss_signature(vector['r'], vector['s']) + if vector['result'] == "F": with pytest.raises(InvalidSignature): - verifier.verify() + public_key.verify(sig, vector['msg'], algorithm()) else: - verifier.verify() + public_key.verify(sig, vector['msg'], algorithm()) def test_dsa_verify_invalid_asn1(self, backend): public_key = DSA_KEY_1024.public_numbers.public_key(backend) - verifier = public_key.verifier(b'fakesig', hashes.SHA1()) - verifier.update(b'fakesig') with pytest.raises(InvalidSignature): - verifier.verify() + public_key.verify(b'fakesig', b'fakemsg', hashes.SHA1()) + + def test_signature_not_bytes(self, backend): + public_key = DSA_KEY_1024.public_numbers.public_key(backend) + with pytest.raises(TypeError), \ + pytest.warns(CryptographyDeprecationWarning): + public_key.verifier(1234, hashes.SHA1()) def test_use_after_finalize(self, backend): public_key = DSA_KEY_1024.public_numbers.public_key(backend) - verifier = public_key.verifier(b'fakesig', hashes.SHA1()) + with pytest.warns(CryptographyDeprecationWarning): + verifier = public_key.verifier(b'fakesig', hashes.SHA1()) verifier.update(b'irrelevant') with pytest.raises(InvalidSignature): verifier.verify() @@ -611,6 +413,50 @@ class TestDSAVerification(object): with pytest.raises(AlreadyFinalized): verifier.update(b"more data") + def test_verify(self, backend): + message = b"one little message" + algorithm = hashes.SHA1() + private_key = DSA_KEY_1024.private_key(backend) + signature = private_key.sign(message, algorithm) + public_key = private_key.public_key() + public_key.verify(signature, message, algorithm) + + def test_prehashed_verify(self, backend): + private_key = DSA_KEY_1024.private_key(backend) + message = b"one little message" + h = hashes.Hash(hashes.SHA1(), backend) + h.update(message) + digest = h.finalize() + prehashed_alg = Prehashed(hashes.SHA1()) + signature = private_key.sign(message, hashes.SHA1()) + public_key = private_key.public_key() + public_key.verify(signature, digest, prehashed_alg) + + def test_prehashed_digest_mismatch(self, backend): + private_key = DSA_KEY_1024.private_key(backend) + public_key = private_key.public_key() + message = b"one little message" + h = hashes.Hash(hashes.SHA1(), backend) + h.update(message) + digest = h.finalize() + prehashed_alg = Prehashed(hashes.SHA224()) + with pytest.raises(ValueError): + public_key.verify(b"\x00" * 128, digest, prehashed_alg) + + def test_prehashed_unsupported_in_signer_ctx(self, backend): + private_key = DSA_KEY_1024.private_key(backend) + with pytest.raises(TypeError), \ + pytest.warns(CryptographyDeprecationWarning): + private_key.signer(Prehashed(hashes.SHA1())) + + def test_prehashed_unsupported_in_verifier_ctx(self, backend): + public_key = DSA_KEY_1024.private_key(backend).public_key() + with pytest.raises(TypeError), \ + pytest.warns(CryptographyDeprecationWarning): + public_key.verifier( + b"0" * 64, Prehashed(hashes.SHA1()) + ) + @pytest.mark.requires_backend_interface(interface=DSABackend) class TestDSASignature(object): @@ -632,14 +478,10 @@ class TestDSASignature(object): def test_dsa_signing(self, vector, backend): digest_algorithm = vector['digest_algorithm'].replace("-", "") algorithm = self._algorithms_dict[digest_algorithm] - if ( - not backend.dsa_parameters_supported( - vector['p'], vector['q'], vector['g'] - ) or not backend.dsa_hash_supported(algorithm) - ): - pytest.skip( - "{0} does not support the provided parameters".format(backend) - ) + + _skip_if_dsa_not_supported( + backend, algorithm, vector['p'], vector['q'], vector['g'] + ) private_key = dsa.DSAPrivateNumbers( public_numbers=dsa.DSAPublicNumbers( @@ -650,19 +492,15 @@ class TestDSASignature(object): ), x=vector['x'] ).private_key(backend) - signer = private_key.signer(algorithm()) - signer.update(vector['msg']) - signature = signer.finalize() + signature = private_key.sign(vector['msg'], algorithm()) assert signature - public_key = private_key.public_key() - verifier = public_key.verifier(signature, algorithm()) - verifier.update(vector['msg']) - verifier.verify() + private_key.public_key().verify(signature, vector['msg'], algorithm()) def test_use_after_finalize(self, backend): private_key = DSA_KEY_1024.private_key(backend) - signer = private_key.signer(hashes.SHA1()) + with pytest.warns(CryptographyDeprecationWarning): + signer = private_key.signer(hashes.SHA1()) signer.update(b"data") signer.finalize() with pytest.raises(AlreadyFinalized): @@ -670,6 +508,35 @@ class TestDSASignature(object): with pytest.raises(AlreadyFinalized): signer.update(b"more data") + def test_sign(self, backend): + private_key = DSA_KEY_1024.private_key(backend) + message = b"one little message" + algorithm = hashes.SHA1() + signature = private_key.sign(message, algorithm) + public_key = private_key.public_key() + public_key.verify(signature, message, algorithm) + + def test_prehashed_sign(self, backend): + private_key = DSA_KEY_1024.private_key(backend) + message = b"one little message" + h = hashes.Hash(hashes.SHA1(), backend) + h.update(message) + digest = h.finalize() + prehashed_alg = Prehashed(hashes.SHA1()) + signature = private_key.sign(digest, prehashed_alg) + public_key = private_key.public_key() + public_key.verify(signature, message, hashes.SHA1()) + + def test_prehashed_digest_mismatch(self, backend): + private_key = DSA_KEY_1024.private_key(backend) + message = b"one little message" + h = hashes.Hash(hashes.SHA1(), backend) + h.update(message) + digest = h.finalize() + prehashed_alg = Prehashed(hashes.SHA224()) + with pytest.raises(ValueError): + private_key.sign(digest, prehashed_alg) + class TestDSANumbers(object): def test_dsa_parameter_numbers(self): @@ -730,6 +597,21 @@ class TestDSANumbers(object): with pytest.raises(TypeError): dsa.DSAPrivateNumbers(x=None, public_numbers=public_numbers) + def test_repr(self): + parameter_numbers = dsa.DSAParameterNumbers(p=1, q=2, g=3) + assert ( + repr(parameter_numbers) == "<DSAParameterNumbers(p=1, q=2, g=3)>" + ) + + public_numbers = dsa.DSAPublicNumbers( + y=4, + parameter_numbers=parameter_numbers + ) + assert repr(public_numbers) == ( + "<DSAPublicNumbers(y=4, parameter_numbers=<DSAParameterNumbers(p=1" + ", q=2, g=3)>)>" + ) + class TestDSANumberEquality(object): def test_parameter_numbers_eq(self): @@ -819,7 +701,6 @@ class TestDSASerialization(object): lambda pemfile: pemfile.read().encode() ) key = serialization.load_pem_private_key(key_bytes, None, backend) - _skip_if_no_serialization(key, backend) serialized = key.private_bytes( serialization.Encoding.PEM, fmt, @@ -833,6 +714,20 @@ class TestDSASerialization(object): assert loaded_priv_num == priv_num @pytest.mark.parametrize( + ("encoding", "fmt"), + [ + (serialization.Encoding.Raw, serialization.PrivateFormat.PKCS8), + (serialization.Encoding.DER, serialization.PrivateFormat.Raw), + (serialization.Encoding.Raw, serialization.PrivateFormat.Raw), + (serialization.Encoding.X962, serialization.PrivateFormat.PKCS8), + ] + ) + def test_private_bytes_rejects_invalid(self, encoding, fmt, backend): + key = DSA_KEY_1024.private_key(backend) + with pytest.raises(ValueError): + key.private_bytes(encoding, fmt, serialization.NoEncryption()) + + @pytest.mark.parametrize( ("fmt", "password"), [ [serialization.PrivateFormat.PKCS8, b"s"], @@ -847,7 +742,6 @@ class TestDSASerialization(object): lambda pemfile: pemfile.read().encode() ) key = serialization.load_pem_private_key(key_bytes, None, backend) - _skip_if_no_serialization(key, backend) serialized = key.private_bytes( serialization.Encoding.DER, fmt, @@ -888,7 +782,6 @@ class TestDSASerialization(object): def test_private_bytes_unencrypted(self, backend, encoding, fmt, loader_func): key = DSA_KEY_1024.private_key(backend) - _skip_if_no_serialization(key, backend) serialized = key.private_bytes( encoding, fmt, serialization.NoEncryption() ) @@ -934,7 +827,6 @@ class TestDSASerialization(object): def test_private_bytes_traditional_der_encrypted_invalid(self, backend): key = DSA_KEY_1024.private_key(backend) - _skip_if_no_serialization(key, backend) with pytest.raises(ValueError): key.private_bytes( serialization.Encoding.DER, @@ -949,7 +841,6 @@ class TestDSASerialization(object): pemfile.read().encode(), None, backend ) ) - _skip_if_no_serialization(key, backend) with pytest.raises(TypeError): key.private_bytes( "notencoding", @@ -964,7 +855,6 @@ class TestDSASerialization(object): pemfile.read().encode(), None, backend ) ) - _skip_if_no_serialization(key, backend) with pytest.raises(TypeError): key.private_bytes( serialization.Encoding.PEM, @@ -979,7 +869,6 @@ class TestDSASerialization(object): pemfile.read().encode(), None, backend ) ) - _skip_if_no_serialization(key, backend) with pytest.raises(TypeError): key.private_bytes( serialization.Encoding.PEM, @@ -994,12 +883,11 @@ class TestDSASerialization(object): pemfile.read().encode(), None, backend ) ) - _skip_if_no_serialization(key, backend) with pytest.raises(ValueError): key.private_bytes( serialization.Encoding.PEM, serialization.PrivateFormat.TraditionalOpenSSL, - DummyKeyEncryption() + DummyKeySerializationEncryption() ) @@ -1030,15 +918,36 @@ class TestDSAPEMPublicKeySerialization(object): key_path, lambda pemfile: pemfile.read(), mode="rb" ) key = loader_func(key_bytes, backend) - _skip_if_no_serialization(key, backend) serialized = key.public_bytes( encoding, serialization.PublicFormat.SubjectPublicKeyInfo, ) assert serialized == key_bytes + def test_public_bytes_openssh(self, backend): + key_bytes = load_vectors_from_file( + os.path.join("asymmetric", "PKCS8", "unenc-dsa-pkcs8.pub.pem"), + lambda pemfile: pemfile.read(), mode="rb" + ) + key = serialization.load_pem_public_key(key_bytes, backend) + + ssh_bytes = key.public_bytes( + serialization.Encoding.OpenSSH, serialization.PublicFormat.OpenSSH + ) + assert ssh_bytes == ( + b"ssh-dss AAAAB3NzaC1kc3MAAACBAKoJMMwUWCUiHK/6KKwolBlqJ4M95ewhJweR" + b"aJQgd3Si57I4sNNvGySZosJYUIPrAUMpJEGNhn+qIS3RBx1NzrJ4J5StOTzAik1K" + b"2n9o1ug5pfzTS05ALYLLioy0D+wxkRv5vTYLA0yqy0xelHmSVzyekAmcGw8FlAyr" + b"5dLeSaFnAAAAFQCtwOhps28KwBOmgf301ImdaYIEUQAAAIEAjGtFia+lOk0QSL/D" + b"RtHzhsp1UhzPct2qJRKGiA7hMgH/SIkLv8M9ebrK7HHnp3hQe9XxpmQi45QVvgPn" + b"EUG6Mk9bkxMZKRgsiKn6QGKDYGbOvnS1xmkMfRARBsJAq369VOTjMB/Qhs5q2ski" + b"+ycTorCIfLoTubxozlz/8kHNMkYAAACAKyYOqX3GoSrpMsZA5989j/BKigWgMk+N" + b"Xxsj8V+hcP8/QgYRJO/yWGyxG0moLc3BuQ/GqE+xAQnLZ9tdLalxrq8Xvl43KEVj" + b"5MZNnl/ISAJYsxnw3inVTYNQcNnih5FNd9+BSR9EI7YtqYTrP0XrKin86l2uUlrG" + b"q2vM4Ev99bY=" + ) + def test_public_bytes_invalid_encoding(self, backend): key = DSA_KEY_2048.private_key(backend).public_key() - _skip_if_no_serialization(key, backend) with pytest.raises(TypeError): key.public_bytes( "notencoding", @@ -1047,14 +956,39 @@ class TestDSAPEMPublicKeySerialization(object): def test_public_bytes_invalid_format(self, backend): key = DSA_KEY_2048.private_key(backend).public_key() - _skip_if_no_serialization(key, backend) with pytest.raises(TypeError): key.public_bytes(serialization.Encoding.PEM, "invalidformat") def test_public_bytes_pkcs1_unsupported(self, backend): key = DSA_KEY_2048.private_key(backend).public_key() - _skip_if_no_serialization(key, backend) with pytest.raises(ValueError): key.public_bytes( serialization.Encoding.PEM, serialization.PublicFormat.PKCS1 ) + + @pytest.mark.parametrize( + ("encoding", "fmt"), + [ + ( + serialization.Encoding.Raw, + serialization.PublicFormat.SubjectPublicKeyInfo + ), + (serialization.Encoding.Raw, serialization.PublicFormat.PKCS1), + ] + list(itertools.product( + [ + serialization.Encoding.Raw, + serialization.Encoding.X962, + serialization.Encoding.PEM, + serialization.Encoding.DER + ], + [ + serialization.PublicFormat.Raw, + serialization.PublicFormat.UncompressedPoint, + serialization.PublicFormat.CompressedPoint + ] + )) + ) + def test_public_bytes_rejects_invalid(self, encoding, fmt, backend): + key = DSA_KEY_2048.private_key(backend).public_key() + with pytest.raises(ValueError): + key.public_bytes(encoding, fmt) diff --git a/tests/hazmat/primitives/test_ec.py b/tests/hazmat/primitives/test_ec.py index cc185145..987c0ff0 100644 --- a/tests/hazmat/primitives/test_ec.py +++ b/tests/hazmat/primitives/test_ec.py @@ -4,24 +4,30 @@ from __future__ import absolute_import, division, print_function +import binascii import itertools import os +from binascii import hexlify import pytest -from cryptography import exceptions, utils +from cryptography import exceptions, utils, x509 from cryptography.hazmat.backends.interfaces import ( EllipticCurveBackend, PEMSerializationBackend ) from cryptography.hazmat.primitives import hashes, serialization from cryptography.hazmat.primitives.asymmetric import ec from cryptography.hazmat.primitives.asymmetric.utils import ( - encode_rfc6979_signature + Prehashed, encode_dss_signature ) +from cryptography.utils import CryptographyDeprecationWarning +from .fixtures_ec import EC_KEY_SECP384R1 +from ...doubles import DummyKeySerializationEncryption from ...utils import ( load_fips_ecdsa_key_pair_vectors, load_fips_ecdsa_signing_vectors, - load_vectors_from_file, raises_unsupported_algorithm + load_kasvs_ecdh_vectors, load_nist_vectors, load_vectors_from_file, + raises_unsupported_algorithm ) _HASH_TYPES = { @@ -33,25 +39,13 @@ _HASH_TYPES = { } -def _skip_if_no_serialization(key, backend): - if not isinstance( - key, ( - ec.EllipticCurvePrivateKeyWithSerialization, - ec.EllipticCurvePublicKeyWithSerialization - ) - ): - pytest.skip( - "{0} does not support EC key serialization".format(backend) - ) - - def _skip_ecdsa_vector(backend, curve_type, hash_type): if not backend.elliptic_curve_signature_algorithm_supported( ec.ECDSA(hash_type()), curve_type() ): pytest.skip( - "ECDSA not supported with this hash {0} and curve {1}".format( + "ECDSA not supported with this hash {} and curve {}".format( hash_type().name, curve_type().name ) ) @@ -60,12 +54,29 @@ def _skip_ecdsa_vector(backend, curve_type, hash_type): def _skip_curve_unsupported(backend, curve): if not backend.elliptic_curve_supported(curve): pytest.skip( - "Curve {0} is not supported by this backend {1}".format( + "Curve {} is not supported by this backend {}".format( curve.name, backend ) ) +def _skip_exchange_algorithm_unsupported(backend, algorithm, curve): + if not backend.elliptic_curve_exchange_algorithm_supported( + algorithm, curve + ): + pytest.skip( + "Exchange with {} curve is not supported by {}".format( + curve.name, backend + ) + ) + + +def test_get_curve_for_oid(): + assert ec.get_curve_for_oid(ec.EllipticCurveOID.SECP256R1) == ec.SECP256R1 + with pytest.raises(LookupError): + ec.get_curve_for_oid(x509.ObjectIdentifier("1.1.1.1")) + + @utils.register_interface(ec.EllipticCurve) class DummyCurve(object): name = "dummy-curve" @@ -77,20 +88,51 @@ class DummySignatureAlgorithm(object): algorithm = None -@utils.register_interface(serialization.KeySerializationEncryption) -class DummyKeyEncryption(object): - pass - - @pytest.mark.requires_backend_interface(interface=EllipticCurveBackend) def test_skip_curve_unsupported(backend): with pytest.raises(pytest.skip.Exception): _skip_curve_unsupported(backend, DummyCurve()) -def test_skip_no_serialization(): +@pytest.mark.requires_backend_interface(interface=EllipticCurveBackend) +def test_skip_exchange_algorithm_unsupported(backend): + with pytest.raises(pytest.skip.Exception): + _skip_exchange_algorithm_unsupported(backend, ec.ECDH(), DummyCurve()) + + +@pytest.mark.requires_backend_interface(interface=EllipticCurveBackend) +def test_skip_ecdsa_vector(backend): with pytest.raises(pytest.skip.Exception): - _skip_if_no_serialization("fakebackend", "fakekey") + _skip_ecdsa_vector(backend, DummyCurve, hashes.SHA256) + + +@pytest.mark.requires_backend_interface(interface=EllipticCurveBackend) +def test_derive_private_key_success(backend): + curve = ec.SECP256K1() + _skip_curve_unsupported(backend, curve) + + private_numbers = ec.generate_private_key(curve, backend).private_numbers() + + derived_key = ec.derive_private_key( + private_numbers.private_value, curve, backend + ) + + assert private_numbers == derived_key.private_numbers() + + +@pytest.mark.requires_backend_interface(interface=EllipticCurveBackend) +def test_derive_private_key_errors(backend): + curve = ec.SECP256K1() + _skip_curve_unsupported(backend, curve) + + with pytest.raises(TypeError): + ec.derive_private_key('one', curve, backend) + + with pytest.raises(TypeError): + ec.derive_private_key(10, 'five', backend) + + with pytest.raises(ValueError): + ec.derive_private_key(-7, curve, backend) def test_ec_numbers(): @@ -106,43 +148,135 @@ def test_ec_numbers(): assert numbers.public_numbers.y == 3 assert isinstance(numbers.public_numbers.curve, DummyCurve) + +@pytest.mark.parametrize( + ("private_value", "x", "y", "curve"), + [ + (None, 2, 3, DummyCurve()), + (1, None, 3, DummyCurve()), + (1, 2, None, DummyCurve()), + (1, 2, 3, None), + ] +) +def test_invalid_ec_numbers_args(private_value, x, y, curve): with pytest.raises(TypeError): ec.EllipticCurvePrivateNumbers( - None, - ec.EllipticCurvePublicNumbers( - 2, 3, DummyCurve() - ) + private_value, ec.EllipticCurvePublicNumbers(x, y, curve) ) + +def test_invalid_private_numbers_public_numbers(): with pytest.raises(TypeError): - ec.EllipticCurvePrivateNumbers( - 1, - ec.EllipticCurvePublicNumbers( - None, 3, DummyCurve() - ) + ec.EllipticCurvePrivateNumbers(1, None) + + +def test_encode_point(): + # secp256r1 point + x = int( + '233ea3b0027127084cd2cd336a13aeef69c598d8af61369a36454a17c6c22aec', + 16 + ) + y = int( + '3ea2c10a84153862be4ec82940f0543f9ba866af9751a6ee79d38460b35f442e', + 16 + ) + pn = ec.EllipticCurvePublicNumbers(x, y, ec.SECP256R1()) + with pytest.warns(utils.PersistentlyDeprecated2019): + data = pn.encode_point() + assert data == binascii.unhexlify( + "04233ea3b0027127084cd2cd336a13aeef69c598d8af61369a36454a17c6c22ae" + "c3ea2c10a84153862be4ec82940f0543f9ba866af9751a6ee79d38460b35f442e" + ) + + +def test_from_encoded_point(): + # secp256r1 point + data = binascii.unhexlify( + "04233ea3b0027127084cd2cd336a13aeef69c598d8af61369a36454a17c6c22ae" + "c3ea2c10a84153862be4ec82940f0543f9ba866af9751a6ee79d38460b35f442e" + ) + with pytest.warns(CryptographyDeprecationWarning): + pn = ec.EllipticCurvePublicNumbers.from_encoded_point( + ec.SECP256R1(), data ) + assert pn.x == int( + '233ea3b0027127084cd2cd336a13aeef69c598d8af61369a36454a17c6c22aec', + 16 + ) + assert pn.y == int( + '3ea2c10a84153862be4ec82940f0543f9ba866af9751a6ee79d38460b35f442e', + 16 + ) - with pytest.raises(TypeError): - ec.EllipticCurvePrivateNumbers( - 1, - ec.EllipticCurvePublicNumbers( - 2, None, DummyCurve() + +def test_from_encoded_point_invalid_length(): + bad_data = binascii.unhexlify( + "04233ea3b0027127084cd2cd336a13aeef69c598d8af61369a36454a17c6c22ae" + "c3ea2c10a84153862be4ec82940f0543f9ba866af9751a6ee79d38460" + ) + with pytest.raises(ValueError): + with pytest.warns(CryptographyDeprecationWarning): + ec.EllipticCurvePublicNumbers.from_encoded_point( + ec.SECP384R1(), bad_data ) - ) - with pytest.raises(TypeError): - ec.EllipticCurvePrivateNumbers( - 1, - ec.EllipticCurvePublicNumbers( - 2, 3, None + +def test_from_encoded_point_unsupported_point_no_backend(): + # set to point type 2. + unsupported_type = binascii.unhexlify( + "02233ea3b0027127084cd2cd336a13aeef69c598d8af61369a36454a17c6c22a" + ) + with pytest.raises(ValueError): + with pytest.warns(CryptographyDeprecationWarning): + ec.EllipticCurvePublicNumbers.from_encoded_point( + ec.SECP256R1(), unsupported_type ) - ) + +def test_from_encoded_point_not_a_curve(): with pytest.raises(TypeError): - ec.EllipticCurvePrivateNumbers( - 1, - None - ) + with pytest.warns(CryptographyDeprecationWarning): + ec.EllipticCurvePublicNumbers.from_encoded_point( + "notacurve", b"\x04data" + ) + + +def test_ec_public_numbers_repr(): + pn = ec.EllipticCurvePublicNumbers(2, 3, ec.SECP256R1()) + assert repr(pn) == "<EllipticCurvePublicNumbers(curve=secp256r1, x=2, y=3>" + + +def test_ec_public_numbers_hash(): + pn1 = ec.EllipticCurvePublicNumbers(2, 3, ec.SECP256R1()) + pn2 = ec.EllipticCurvePublicNumbers(2, 3, ec.SECP256R1()) + pn3 = ec.EllipticCurvePublicNumbers(1, 3, ec.SECP256R1()) + + assert hash(pn1) == hash(pn2) + assert hash(pn1) != hash(pn3) + + +def test_ec_private_numbers_hash(): + numbers1 = ec.EllipticCurvePrivateNumbers( + 1, ec.EllipticCurvePublicNumbers(2, 3, DummyCurve()) + ) + numbers2 = ec.EllipticCurvePrivateNumbers( + 1, ec.EllipticCurvePublicNumbers(2, 3, DummyCurve()) + ) + numbers3 = ec.EllipticCurvePrivateNumbers( + 2, ec.EllipticCurvePublicNumbers(2, 3, DummyCurve()) + ) + + assert hash(numbers1) == hash(numbers2) + assert hash(numbers1) != hash(numbers3) + + +@pytest.mark.requires_backend_interface(interface=EllipticCurveBackend) +def test_ec_key_key_size(backend): + curve = ec.SECP256R1() + _skip_curve_unsupported(backend, curve) + key = ec.generate_private_key(curve, backend) + assert key.key_size == 256 + assert key.public_key().key_size == 256 @pytest.mark.requires_backend_interface(interface=EllipticCurveBackend) @@ -173,12 +307,11 @@ class TestECWithNumbers(object): ).private_key(backend) assert key - if isinstance(key, ec.EllipticCurvePrivateKeyWithSerialization): - priv_num = key.private_numbers() - assert priv_num.private_value == vector['d'] - assert priv_num.public_numbers.x == vector['x'] - assert priv_num.public_numbers.y == vector['y'] - assert curve_type().name == priv_num.public_numbers.curve.name + priv_num = key.private_numbers() + assert priv_num.private_value == vector['d'] + assert priv_num.public_numbers.x == vector['x'] + assert priv_num.public_numbers.y == vector['y'] + assert curve_type().name == priv_num.public_numbers.curve.name @pytest.mark.requires_backend_interface(interface=EllipticCurveBackend) @@ -212,11 +345,13 @@ class TestECDSAVectors(object): pkey = key.public_key() assert pkey - signer = key.signer(ec.ECDSA(hash_type())) + with pytest.warns(CryptographyDeprecationWarning): + signer = key.signer(ec.ECDSA(hash_type())) signer.update(b"YELLOW SUBMARINE") signature = signer.finalize() - verifier = pkey.verifier(signature, ec.ECDSA(hash_type())) + with pytest.warns(CryptographyDeprecationWarning): + verifier = pkey.verifier(signature, ec.ECDSA(hash_type())) verifier.update(b"YELLOW SUBMARINE") verifier.verify() @@ -254,14 +389,26 @@ class TestECDSAVectors(object): with raises_unsupported_algorithm( exceptions._Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM - ): + ), pytest.warns(CryptographyDeprecationWarning): key.signer(DummySignatureAlgorithm()) with raises_unsupported_algorithm( exceptions._Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM ): + key.sign(b"somedata", DummySignatureAlgorithm()) + + with raises_unsupported_algorithm( + exceptions._Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM + ), pytest.warns(CryptographyDeprecationWarning): key.public_key().verifier(b"", DummySignatureAlgorithm()) + with raises_unsupported_algorithm( + exceptions._Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM + ): + key.public_key().verify( + b"signature", b"data", DummySignatureAlgorithm() + ) + assert backend.elliptic_curve_signature_algorithm_supported( DummySignatureAlgorithm(), ec.SECP192R1() @@ -303,6 +450,35 @@ class TestECDSAVectors(object): with pytest.raises(ValueError): numbers.private_key(backend) + def test_load_invalid_public_ec_key_from_numbers(self, backend): + _skip_curve_unsupported(backend, ec.SECP521R1()) + + # Bad X coordinate + numbers = ec.EllipticCurvePublicNumbers( + int("000003647356b91f8ace114c7247ecf4f4a622553fc025e04a178f179ef27" + "9090c184af678a4c78f635483bdd8aa544851c6ef291c1f0d6a241ebfd145" + "77d1d30d9903ce", 16), + int("000001499bc7e079322ea0fcfbd6b40103fa6a1536c2257b182db0df4b369" + "6ec643adf100eb4f2025d1b873f82e5a475d6e4400ba777090eeb4563a115" + "09e4c87319dc26", 16), + ec.SECP521R1() + ) + with pytest.raises(ValueError): + numbers.public_key(backend) + + # Bad Y coordinate + numbers = ec.EllipticCurvePublicNumbers( + int("0000019aadc221cc0525118ab6d5aa1f64720603de0be128cbfea0b381ad8" + "02a2facc6370bb58cf88b3f0c692bc654ee19d6cad198f10d4b681b396f20" + "d2e40603fa945b", 16), + int("0000025da392803a320717a08d4cb3dea932039badff363b71bdb8064e726" + "6c7f4f4b748d4d425347fc33e3885d34b750fa7fcd5691f4d90c89522ce33" + "feff5db10088a5", 16), + ec.SECP521R1() + ) + with pytest.raises(ValueError): + numbers.public_key(backend) + @pytest.mark.parametrize( "vector", itertools.chain( @@ -330,14 +506,13 @@ class TestECDSAVectors(object): curve_type() ).public_key(backend) - signature = encode_rfc6979_signature(vector['r'], vector['s']) + signature = encode_dss_signature(vector['r'], vector['s']) - verifier = key.verifier( + key.verify( signature, + vector['message'], ec.ECDSA(hash_type()) ) - verifier.update(vector['message']) - assert verifier.verify() @pytest.mark.parametrize( "vector", @@ -359,19 +534,107 @@ class TestECDSAVectors(object): curve_type() ).public_key(backend) - signature = encode_rfc6979_signature(vector['r'], vector['s']) - - verifier = key.verifier( - signature, - ec.ECDSA(hash_type()) - ) - verifier.update(vector['message']) + signature = encode_dss_signature(vector['r'], vector['s']) if vector["fail"] is True: with pytest.raises(exceptions.InvalidSignature): - verifier.verify() + key.verify( + signature, + vector['message'], + ec.ECDSA(hash_type()) + ) else: - verifier.verify() + key.verify( + signature, + vector['message'], + ec.ECDSA(hash_type()) + ) + + def test_sign(self, backend): + _skip_curve_unsupported(backend, ec.SECP256R1()) + message = b"one little message" + algorithm = ec.ECDSA(hashes.SHA1()) + private_key = ec.generate_private_key(ec.SECP256R1(), backend) + signature = private_key.sign(message, algorithm) + public_key = private_key.public_key() + public_key.verify(signature, message, algorithm) + + def test_sign_prehashed(self, backend): + _skip_curve_unsupported(backend, ec.SECP256R1()) + message = b"one little message" + h = hashes.Hash(hashes.SHA1(), backend) + h.update(message) + data = h.finalize() + algorithm = ec.ECDSA(Prehashed(hashes.SHA1())) + private_key = ec.generate_private_key(ec.SECP256R1(), backend) + signature = private_key.sign(data, algorithm) + public_key = private_key.public_key() + public_key.verify(signature, message, ec.ECDSA(hashes.SHA1())) + + def test_sign_prehashed_digest_mismatch(self, backend): + _skip_curve_unsupported(backend, ec.SECP256R1()) + message = b"one little message" + h = hashes.Hash(hashes.SHA1(), backend) + h.update(message) + data = h.finalize() + algorithm = ec.ECDSA(Prehashed(hashes.SHA256())) + private_key = ec.generate_private_key(ec.SECP256R1(), backend) + with pytest.raises(ValueError): + private_key.sign(data, algorithm) + + def test_verify(self, backend): + _skip_curve_unsupported(backend, ec.SECP256R1()) + message = b"one little message" + algorithm = ec.ECDSA(hashes.SHA1()) + private_key = ec.generate_private_key(ec.SECP256R1(), backend) + signature = private_key.sign(message, algorithm) + public_key = private_key.public_key() + public_key.verify(signature, message, algorithm) + + def test_verify_prehashed(self, backend): + _skip_curve_unsupported(backend, ec.SECP256R1()) + message = b"one little message" + algorithm = ec.ECDSA(hashes.SHA1()) + private_key = ec.generate_private_key(ec.SECP256R1(), backend) + signature = private_key.sign(message, algorithm) + h = hashes.Hash(hashes.SHA1(), backend) + h.update(message) + data = h.finalize() + public_key = private_key.public_key() + public_key.verify( + signature, data, ec.ECDSA(Prehashed(hashes.SHA1())) + ) + + def test_verify_prehashed_digest_mismatch(self, backend): + _skip_curve_unsupported(backend, ec.SECP256R1()) + message = b"one little message" + private_key = ec.generate_private_key(ec.SECP256R1(), backend) + h = hashes.Hash(hashes.SHA1(), backend) + h.update(message) + data = h.finalize() + public_key = private_key.public_key() + with pytest.raises(ValueError): + public_key.verify( + b"\x00" * 32, data, ec.ECDSA(Prehashed(hashes.SHA256())) + ) + + def test_prehashed_unsupported_in_signer_ctx(self, backend): + _skip_curve_unsupported(backend, ec.SECP256R1()) + private_key = ec.generate_private_key(ec.SECP256R1(), backend) + with pytest.raises(TypeError), \ + pytest.warns(CryptographyDeprecationWarning): + private_key.signer(ec.ECDSA(Prehashed(hashes.SHA1()))) + + def test_prehashed_unsupported_in_verifier_ctx(self, backend): + _skip_curve_unsupported(backend, ec.SECP256R1()) + private_key = ec.generate_private_key(ec.SECP256R1(), backend) + public_key = private_key.public_key() + with pytest.raises(TypeError), \ + pytest.warns(CryptographyDeprecationWarning): + public_key.verifier( + b"0" * 64, + ec.ECDSA(Prehashed(hashes.SHA1())) + ) class TestECNumbersEquality(object): @@ -437,7 +700,6 @@ class TestECSerialization(object): lambda pemfile: pemfile.read().encode() ) key = serialization.load_pem_private_key(key_bytes, None, backend) - _skip_if_no_serialization(key, backend) serialized = key.private_bytes( serialization.Encoding.PEM, fmt, @@ -451,6 +713,21 @@ class TestECSerialization(object): assert loaded_priv_num == priv_num @pytest.mark.parametrize( + ("encoding", "fmt"), + [ + (serialization.Encoding.Raw, serialization.PrivateFormat.PKCS8), + (serialization.Encoding.DER, serialization.PrivateFormat.Raw), + (serialization.Encoding.Raw, serialization.PrivateFormat.Raw), + (serialization.Encoding.X962, serialization.PrivateFormat.PKCS8), + ] + ) + def test_private_bytes_rejects_invalid(self, encoding, fmt, backend): + _skip_curve_unsupported(backend, ec.SECP256R1()) + key = ec.generate_private_key(ec.SECP256R1(), backend) + with pytest.raises(ValueError): + key.private_bytes(encoding, fmt, serialization.NoEncryption()) + + @pytest.mark.parametrize( ("fmt", "password"), [ [serialization.PrivateFormat.PKCS8, b"s"], @@ -467,7 +744,6 @@ class TestECSerialization(object): lambda pemfile: pemfile.read().encode() ) key = serialization.load_pem_private_key(key_bytes, None, backend) - _skip_if_no_serialization(key, backend) serialized = key.private_bytes( serialization.Encoding.DER, fmt, @@ -514,7 +790,6 @@ class TestECSerialization(object): lambda pemfile: pemfile.read().encode() ) key = serialization.load_pem_private_key(key_bytes, None, backend) - _skip_if_no_serialization(key, backend) serialized = key.private_bytes( encoding, fmt, serialization.NoEncryption() ) @@ -566,7 +841,6 @@ class TestECSerialization(object): pemfile.read().encode(), None, backend ) ) - _skip_if_no_serialization(key, backend) with pytest.raises(ValueError): key.private_bytes( serialization.Encoding.DER, @@ -583,7 +857,6 @@ class TestECSerialization(object): pemfile.read().encode(), None, backend ) ) - _skip_if_no_serialization(key, backend) with pytest.raises(TypeError): key.private_bytes( "notencoding", @@ -600,7 +873,6 @@ class TestECSerialization(object): pemfile.read().encode(), None, backend ) ) - _skip_if_no_serialization(key, backend) with pytest.raises(TypeError): key.private_bytes( serialization.Encoding.PEM, @@ -617,7 +889,6 @@ class TestECSerialization(object): pemfile.read().encode(), None, backend ) ) - _skip_if_no_serialization(key, backend) with pytest.raises(TypeError): key.private_bytes( serialization.Encoding.PEM, @@ -634,12 +905,11 @@ class TestECSerialization(object): pemfile.read().encode(), None, backend ) ) - _skip_if_no_serialization(key, backend) with pytest.raises(ValueError): key.private_bytes( serialization.Encoding.PEM, serialization.PrivateFormat.TraditionalOpenSSL, - DummyKeyEncryption() + DummyKeySerializationEncryption() ) def test_public_bytes_from_derived_public_key(self, backend): @@ -651,7 +921,6 @@ class TestECSerialization(object): pemfile.read().encode(), None, backend ) ) - _skip_if_no_serialization(key, backend) public = key.public_key() pem = public.public_bytes( serialization.Encoding.PEM, @@ -689,12 +958,39 @@ class TestEllipticCurvePEMPublicKeySerialization(object): key_path, lambda pemfile: pemfile.read(), mode="rb" ) key = loader_func(key_bytes, backend) - _skip_if_no_serialization(key, backend) serialized = key.public_bytes( encoding, serialization.PublicFormat.SubjectPublicKeyInfo, ) assert serialized == key_bytes + def test_public_bytes_openssh(self, backend): + _skip_curve_unsupported(backend, ec.SECP192R1()) + _skip_curve_unsupported(backend, ec.SECP256R1()) + + key_bytes = load_vectors_from_file( + os.path.join( + "asymmetric", "PEM_Serialization", "ec_public_key.pem" + ), + lambda pemfile: pemfile.read(), mode="rb" + ) + key = serialization.load_pem_public_key(key_bytes, backend) + + ssh_bytes = key.public_bytes( + serialization.Encoding.OpenSSH, serialization.PublicFormat.OpenSSH + ) + assert ssh_bytes == ( + b"ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAy" + b"NTYAAABBBCS8827s9rUZyxZTi/um01+oIlWrwLHOjQxRU9CDAndom00zVAw5BRrI" + b"KtHB+SWD4P+sVJTARSq1mHt8kOIWrPc=" + ) + + key = ec.generate_private_key(ec.SECP192R1(), backend).public_key() + with pytest.raises(ValueError): + key.public_bytes( + serialization.Encoding.OpenSSH, + serialization.PublicFormat.OpenSSH + ) + def test_public_bytes_invalid_encoding(self, backend): _skip_curve_unsupported(backend, ec.SECP256R1()) key = load_vectors_from_file( @@ -705,13 +1001,40 @@ class TestEllipticCurvePEMPublicKeySerialization(object): pemfile.read().encode(), backend ) ) - _skip_if_no_serialization(key, backend) with pytest.raises(TypeError): key.public_bytes( "notencoding", serialization.PublicFormat.SubjectPublicKeyInfo ) + @pytest.mark.parametrize( + ("encoding", "fmt"), + list(itertools.product( + [ + serialization.Encoding.Raw, + serialization.Encoding.X962, + serialization.Encoding.PEM, + serialization.Encoding.DER + ], + [ + serialization.PublicFormat.Raw, + ] + )) + list(itertools.product( + [serialization.Encoding.Raw], + [ + serialization.PublicFormat.SubjectPublicKeyInfo, + serialization.PublicFormat.PKCS1, + serialization.PublicFormat.UncompressedPoint, + serialization.PublicFormat.CompressedPoint, + ] + )) + ) + def test_public_bytes_rejects_invalid(self, encoding, fmt, backend): + _skip_curve_unsupported(backend, ec.SECP256R1()) + key = ec.generate_private_key(ec.SECP256R1(), backend).public_key() + with pytest.raises(ValueError): + key.public_bytes(encoding, fmt) + def test_public_bytes_invalid_format(self, backend): _skip_curve_unsupported(backend, ec.SECP256R1()) key = load_vectors_from_file( @@ -722,7 +1045,6 @@ class TestEllipticCurvePEMPublicKeySerialization(object): pemfile.read().encode(), backend ) ) - _skip_if_no_serialization(key, backend) with pytest.raises(TypeError): key.public_bytes(serialization.Encoding.PEM, "invalidformat") @@ -736,8 +1058,250 @@ class TestEllipticCurvePEMPublicKeySerialization(object): pemfile.read().encode(), backend ) ) - _skip_if_no_serialization(key, backend) with pytest.raises(ValueError): key.public_bytes( serialization.Encoding.PEM, serialization.PublicFormat.PKCS1 ) + + @pytest.mark.parametrize( + "vector", + load_vectors_from_file( + os.path.join("asymmetric", "EC", "compressed_points.txt"), + load_nist_vectors + ) + ) + def test_from_encoded_point_compressed(self, vector, backend): + curve = { + b"SECP256R1": ec.SECP256R1(), + b"SECP256K1": ec.SECP256K1(), + }[vector["curve"]] + _skip_curve_unsupported(backend, curve) + point = binascii.unhexlify(vector["point"]) + pn = ec.EllipticCurvePublicKey.from_encoded_point(curve, point) + public_num = pn.public_numbers() + assert public_num.x == int(vector["x"], 16) + assert public_num.y == int(vector["y"], 16) + + def test_from_encoded_point_notoncurve(self): + uncompressed_point = binascii.unhexlify( + "047399336a9edf2197c2f8eb3d39aed9c34a66e45d918a07dc7684c42c9b37ac" + "686699ececc4f5f0d756d3c450708a0694eb0a07a68b805070b40b058d27271f" + "6e" + ) + with pytest.raises(ValueError): + ec.EllipticCurvePublicKey.from_encoded_point( + ec.SECP256R1(), uncompressed_point + ) + + def test_from_encoded_point_uncompressed(self): + uncompressed_point = binascii.unhexlify( + "047399336a9edf2197c2f8eb3d39aed9c34a66e45d918a07dc7684c42c9b37ac" + "686699ececc4f5f0d756d3c450708a0694eb0a07a68b805070b40b058d27271f" + "6d" + ) + pn = ec.EllipticCurvePublicKey.from_encoded_point( + ec.SECP256R1(), uncompressed_point + ) + assert pn.public_numbers().x == int( + '7399336a9edf2197c2f8eb3d39aed9c34a66e45d918a07dc7684c42c9b37ac68', + 16 + ) + assert pn.public_numbers().y == int( + '6699ececc4f5f0d756d3c450708a0694eb0a07a68b805070b40b058d27271f6d', + 16 + ) + + def test_from_encoded_point_invalid_length(self): + bad_data = binascii.unhexlify( + "047399336a9edf2197c2f8eb3d39aed9c34a66e45d918a07dc7684c42c9b37ac" + "686699ececc4f5f0d756d3c450708a0694eb0a07a68b805070b40b058d27271f" + "6d" + ) + with pytest.raises(ValueError): + ec.EllipticCurvePublicKey.from_encoded_point( + ec.SECP384R1(), bad_data + ) + + def test_from_encoded_point_empty_byte_string(self): + with pytest.raises(ValueError): + ec.EllipticCurvePublicKey.from_encoded_point( + ec.SECP384R1(), b"" + ) + + def test_from_encoded_point_not_a_curve(self): + with pytest.raises(TypeError): + ec.EllipticCurvePublicKey.from_encoded_point( + "notacurve", b"\x04data" + ) + + def test_from_encoded_point_unsupported_encoding(self): + unsupported_type = binascii.unhexlify( + "057399336a9edf2197c2f8eb3d39aed9c34a66e45d918a07dc7684c42c9b37ac6" + "8" + ) + with pytest.raises(ValueError): + ec.EllipticCurvePublicKey.from_encoded_point( + ec.SECP256R1(), unsupported_type + ) + + @pytest.mark.parametrize( + "vector", + load_vectors_from_file( + os.path.join("asymmetric", "EC", "compressed_points.txt"), + load_nist_vectors + ) + ) + def test_serialize_point(self, vector, backend): + curve = { + b"SECP256R1": ec.SECP256R1(), + b"SECP256K1": ec.SECP256K1(), + }[vector["curve"]] + _skip_curve_unsupported(backend, curve) + point = binascii.unhexlify(vector["point"]) + key = ec.EllipticCurvePublicKey.from_encoded_point(curve, point) + key2 = ec.EllipticCurvePublicKey.from_encoded_point( + curve, + key.public_bytes( + serialization.Encoding.X962, + serialization.PublicFormat.UncompressedPoint + ) + ) + assert key.public_bytes( + serialization.Encoding.X962, + serialization.PublicFormat.CompressedPoint + ) == point + assert key2.public_bytes( + serialization.Encoding.X962, + serialization.PublicFormat.CompressedPoint + ) == point + + +@pytest.mark.requires_backend_interface(interface=EllipticCurveBackend) +class TestECDSAVerification(object): + def test_signature_not_bytes(self, backend): + _skip_curve_unsupported(backend, ec.SECP256R1()) + key = ec.generate_private_key(ec.SECP256R1(), backend) + public_key = key.public_key() + with pytest.raises(TypeError), \ + pytest.warns(CryptographyDeprecationWarning): + public_key.verifier(1234, ec.ECDSA(hashes.SHA256())) + + +@pytest.mark.requires_backend_interface(interface=EllipticCurveBackend) +class TestECDH(object): + @pytest.mark.parametrize( + "vector", + load_vectors_from_file( + os.path.join( + "asymmetric", "ECDH", + "KASValidityTest_ECCStaticUnified_NOKC_ZZOnly_init.fax"), + load_kasvs_ecdh_vectors + ) + ) + def test_key_exchange_with_vectors(self, backend, vector): + _skip_exchange_algorithm_unsupported( + backend, ec.ECDH(), ec._CURVE_TYPES[vector['curve']] + ) + + key_numbers = vector['IUT'] + private_numbers = ec.EllipticCurvePrivateNumbers( + key_numbers['d'], + ec.EllipticCurvePublicNumbers( + key_numbers['x'], + key_numbers['y'], + ec._CURVE_TYPES[vector['curve']]() + ) + ) + # Errno 5-7 indicates a bad public or private key, this doesn't test + # the ECDH code at all + if vector['fail'] and vector['errno'] in [5, 6, 7]: + with pytest.raises(ValueError): + private_numbers.private_key(backend) + return + else: + private_key = private_numbers.private_key(backend) + + peer_numbers = vector['CAVS'] + public_numbers = ec.EllipticCurvePublicNumbers( + peer_numbers['x'], + peer_numbers['y'], + ec._CURVE_TYPES[vector['curve']]() + ) + # Errno 1 and 2 indicates a bad public key, this doesn't test the ECDH + # code at all + if vector['fail'] and vector['errno'] in [1, 2]: + with pytest.raises(ValueError): + public_numbers.public_key(backend) + return + else: + peer_pubkey = public_numbers.public_key(backend) + + z = private_key.exchange(ec.ECDH(), peer_pubkey) + z = int(hexlify(z).decode('ascii'), 16) + # At this point fail indicates that one of the underlying keys was + # changed. This results in a non-matching derived key. + if vector['fail']: + # Errno 8 indicates Z should be changed. + assert vector['errno'] == 8 + assert z != vector['Z'] + else: + assert z == vector['Z'] + + @pytest.mark.parametrize( + "vector", + load_vectors_from_file( + os.path.join("asymmetric", "ECDH", "brainpool.txt"), + load_nist_vectors + ) + ) + def test_brainpool_kex(self, backend, vector): + curve = ec._CURVE_TYPES[vector['curve'].decode('ascii')] + _skip_exchange_algorithm_unsupported(backend, ec.ECDH(), curve) + key = ec.EllipticCurvePrivateNumbers( + int(vector['da'], 16), + ec.EllipticCurvePublicNumbers( + int(vector['x_qa'], 16), int(vector['y_qa'], 16), curve() + ) + ).private_key(backend) + peer = ec.EllipticCurvePrivateNumbers( + int(vector['db'], 16), + ec.EllipticCurvePublicNumbers( + int(vector['x_qb'], 16), int(vector['y_qb'], 16), curve() + ) + ).private_key(backend) + shared_secret = key.exchange(ec.ECDH(), peer.public_key()) + assert shared_secret == binascii.unhexlify(vector["x_z"]) + shared_secret_2 = peer.exchange(ec.ECDH(), key.public_key()) + assert shared_secret_2 == binascii.unhexlify(vector["x_z"]) + + def test_exchange_unsupported_algorithm(self, backend): + _skip_curve_unsupported(backend, ec.SECP256R1()) + + key = load_vectors_from_file( + os.path.join( + "asymmetric", "PKCS8", "ec_private_key.pem"), + lambda pemfile: serialization.load_pem_private_key( + pemfile.read().encode(), None, backend + ) + ) + + with raises_unsupported_algorithm( + exceptions._Reasons.UNSUPPORTED_EXCHANGE_ALGORITHM + ): + key.exchange(None, key.public_key()) + + def test_exchange_non_matching_curve(self, backend): + _skip_curve_unsupported(backend, ec.SECP256R1()) + _skip_curve_unsupported(backend, ec.SECP384R1()) + + key = load_vectors_from_file( + os.path.join( + "asymmetric", "PKCS8", "ec_private_key.pem"), + lambda pemfile: serialization.load_pem_private_key( + pemfile.read().encode(), None, backend + ) + ) + public_key = EC_KEY_SECP384R1.public_numbers.public_key(backend) + + with pytest.raises(ValueError): + key.exchange(ec.ECDH(), public_key) diff --git a/tests/hazmat/primitives/test_ed25519.py b/tests/hazmat/primitives/test_ed25519.py new file mode 100644 index 00000000..aecc8572 --- /dev/null +++ b/tests/hazmat/primitives/test_ed25519.py @@ -0,0 +1,226 @@ +# 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 binascii +import os + +import pytest + +from cryptography.exceptions import InvalidSignature, _Reasons +from cryptography.hazmat.primitives import serialization +from cryptography.hazmat.primitives.asymmetric.ed25519 import ( + Ed25519PrivateKey, Ed25519PublicKey +) + +from ...utils import ( + load_vectors_from_file, raises_unsupported_algorithm +) + + +def load_ed25519_vectors(vector_data): + """ + djb's ed25519 vectors are structured as a colon delimited array: + 0: secret key (32 bytes) + public key (32 bytes) + 1: public key (32 bytes) + 2: message (0+ bytes) + 3: signature + message (64+ bytes) + """ + data = [] + for line in vector_data: + secret_key, public_key, message, signature, _ = line.split(':') + secret_key = secret_key[0:64] + signature = signature[0:128] + data.append({ + "secret_key": secret_key, + "public_key": public_key, + "message": message, + "signature": signature + }) + return data + + +@pytest.mark.supported( + only_if=lambda backend: not backend.ed25519_supported(), + skip_message="Requires OpenSSL without Ed25519 support" +) +def test_ed25519_unsupported(backend): + with raises_unsupported_algorithm( + _Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM + ): + Ed25519PublicKey.from_public_bytes(b"0" * 32) + + with raises_unsupported_algorithm( + _Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM + ): + Ed25519PrivateKey.from_private_bytes(b"0" * 32) + + with raises_unsupported_algorithm( + _Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM + ): + Ed25519PrivateKey.generate() + + +@pytest.mark.supported( + only_if=lambda backend: backend.ed25519_supported(), + skip_message="Requires OpenSSL with Ed25519 support" +) +class TestEd25519Signing(object): + @pytest.mark.parametrize( + "vector", + load_vectors_from_file( + os.path.join("asymmetric", "Ed25519", "sign.input"), + load_ed25519_vectors + ) + ) + def test_sign_verify_input(self, vector, backend): + sk = binascii.unhexlify(vector["secret_key"]) + pk = binascii.unhexlify(vector["public_key"]) + message = binascii.unhexlify(vector["message"]) + signature = binascii.unhexlify(vector["signature"]) + private_key = Ed25519PrivateKey.from_private_bytes(sk) + computed_sig = private_key.sign(message) + assert computed_sig == signature + public_key = private_key.public_key() + assert public_key.public_bytes( + serialization.Encoding.Raw, serialization.PublicFormat.Raw + ) == pk + public_key.verify(signature, message) + + def test_invalid_signature(self, backend): + key = Ed25519PrivateKey.generate() + signature = key.sign(b"test data") + with pytest.raises(InvalidSignature): + key.public_key().verify(signature, b"wrong data") + + with pytest.raises(InvalidSignature): + key.public_key().verify(b"0" * 64, b"test data") + + def test_generate(self, backend): + key = Ed25519PrivateKey.generate() + assert key + assert key.public_key() + + def test_load_public_bytes(self, backend): + public_key = Ed25519PrivateKey.generate().public_key() + public_bytes = public_key.public_bytes( + serialization.Encoding.Raw, serialization.PublicFormat.Raw + ) + public_key2 = Ed25519PublicKey.from_public_bytes(public_bytes) + assert public_bytes == public_key2.public_bytes( + serialization.Encoding.Raw, serialization.PublicFormat.Raw + ) + + def test_invalid_type_public_bytes(self, backend): + with pytest.raises(TypeError): + Ed25519PublicKey.from_public_bytes(object()) + + def test_invalid_type_private_bytes(self, backend): + with pytest.raises(TypeError): + Ed25519PrivateKey.from_private_bytes(object()) + + def test_invalid_length_from_public_bytes(self, backend): + with pytest.raises(ValueError): + Ed25519PublicKey.from_public_bytes(b"a" * 31) + with pytest.raises(ValueError): + Ed25519PublicKey.from_public_bytes(b"a" * 33) + + def test_invalid_length_from_private_bytes(self, backend): + with pytest.raises(ValueError): + Ed25519PrivateKey.from_private_bytes(b"a" * 31) + with pytest.raises(ValueError): + Ed25519PrivateKey.from_private_bytes(b"a" * 33) + + def test_invalid_private_bytes(self, backend): + key = Ed25519PrivateKey.generate() + with pytest.raises(ValueError): + key.private_bytes( + serialization.Encoding.Raw, + serialization.PrivateFormat.Raw, + None + ) + + with pytest.raises(ValueError): + key.private_bytes( + serialization.Encoding.Raw, + serialization.PrivateFormat.PKCS8, + None + ) + + with pytest.raises(ValueError): + key.private_bytes( + serialization.Encoding.PEM, + serialization.PrivateFormat.Raw, + serialization.NoEncryption() + ) + + def test_invalid_public_bytes(self, backend): + key = Ed25519PrivateKey.generate().public_key() + with pytest.raises(ValueError): + key.public_bytes( + serialization.Encoding.Raw, + serialization.PublicFormat.SubjectPublicKeyInfo + ) + + with pytest.raises(ValueError): + key.public_bytes( + serialization.Encoding.PEM, + serialization.PublicFormat.PKCS1 + ) + + with pytest.raises(ValueError): + key.public_bytes( + serialization.Encoding.PEM, + serialization.PublicFormat.Raw + ) + + @pytest.mark.parametrize( + ("encoding", "fmt", "encryption", "passwd", "load_func"), + [ + ( + serialization.Encoding.PEM, + serialization.PrivateFormat.PKCS8, + serialization.BestAvailableEncryption(b"password"), + b"password", + serialization.load_pem_private_key + ), + ( + serialization.Encoding.DER, + serialization.PrivateFormat.PKCS8, + serialization.BestAvailableEncryption(b"password"), + b"password", + serialization.load_der_private_key + ), + ( + serialization.Encoding.PEM, + serialization.PrivateFormat.PKCS8, + serialization.NoEncryption(), + None, + serialization.load_pem_private_key + ), + ( + serialization.Encoding.DER, + serialization.PrivateFormat.PKCS8, + serialization.NoEncryption(), + None, + serialization.load_der_private_key + ), + ] + ) + def test_round_trip_private_serialization(self, encoding, fmt, encryption, + passwd, load_func, backend): + key = Ed25519PrivateKey.generate() + serialized = key.private_bytes(encoding, fmt, encryption) + loaded_key = load_func(serialized, passwd, backend) + assert isinstance(loaded_key, Ed25519PrivateKey) + + def test_buffer_protocol(self, backend): + private_bytes = os.urandom(32) + key = Ed25519PrivateKey.from_private_bytes(bytearray(private_bytes)) + assert key.private_bytes( + serialization.Encoding.Raw, + serialization.PrivateFormat.Raw, + serialization.NoEncryption() + ) == private_bytes diff --git a/tests/hazmat/primitives/test_ed448.py b/tests/hazmat/primitives/test_ed448.py new file mode 100644 index 00000000..b02f821a --- /dev/null +++ b/tests/hazmat/primitives/test_ed448.py @@ -0,0 +1,239 @@ +# 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 binascii +import os + +import pytest + +from cryptography.exceptions import InvalidSignature, _Reasons +from cryptography.hazmat.primitives import serialization +from cryptography.hazmat.primitives.asymmetric.ed448 import ( + Ed448PrivateKey, Ed448PublicKey +) + +from ...utils import ( + load_nist_vectors, load_vectors_from_file, raises_unsupported_algorithm +) + + +@pytest.mark.supported( + only_if=lambda backend: not backend.ed448_supported(), + skip_message="Requires OpenSSL without Ed448 support" +) +def test_ed448_unsupported(backend): + with raises_unsupported_algorithm( + _Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM + ): + Ed448PublicKey.from_public_bytes(b"0" * 57) + + with raises_unsupported_algorithm( + _Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM + ): + Ed448PrivateKey.from_private_bytes(b"0" * 57) + + with raises_unsupported_algorithm( + _Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM + ): + Ed448PrivateKey.generate() + + +@pytest.mark.supported( + only_if=lambda backend: backend.ed448_supported(), + skip_message="Requires OpenSSL with Ed448 support" +) +class TestEd448Signing(object): + @pytest.mark.parametrize( + "vector", + load_vectors_from_file( + os.path.join("asymmetric", "Ed448", "rfc8032.txt"), + load_nist_vectors + ) + ) + def test_sign_input(self, vector, backend): + if vector.get("context") is not None: + pytest.skip("ed448 contexts are not currently supported") + + sk = binascii.unhexlify(vector["secret"]) + pk = binascii.unhexlify(vector["public"]) + message = binascii.unhexlify(vector["message"]) + signature = binascii.unhexlify(vector["signature"]) + private_key = Ed448PrivateKey.from_private_bytes(sk) + computed_sig = private_key.sign(message) + assert computed_sig == signature + public_key = private_key.public_key() + assert public_key.public_bytes( + serialization.Encoding.Raw, serialization.PublicFormat.Raw + ) == pk + public_key.verify(signature, message) + + def test_invalid_signature(self, backend): + key = Ed448PrivateKey.generate() + signature = key.sign(b"test data") + with pytest.raises(InvalidSignature): + key.public_key().verify(signature, b"wrong data") + + with pytest.raises(InvalidSignature): + key.public_key().verify(b"0" * 64, b"test data") + + def test_generate(self, backend): + key = Ed448PrivateKey.generate() + assert key + assert key.public_key() + + @pytest.mark.parametrize( + "vector", + load_vectors_from_file( + os.path.join("asymmetric", "Ed448", "rfc8032.txt"), + load_nist_vectors + ) + ) + def test_pub_priv_bytes_raw(self, vector, backend): + sk = binascii.unhexlify(vector["secret"]) + pk = binascii.unhexlify(vector["public"]) + private_key = Ed448PrivateKey.from_private_bytes(sk) + assert private_key.private_bytes( + serialization.Encoding.Raw, + serialization.PrivateFormat.Raw, + serialization.NoEncryption() + ) == sk + assert private_key.public_key().public_bytes( + serialization.Encoding.Raw, serialization.PublicFormat.Raw + ) == pk + public_key = Ed448PublicKey.from_public_bytes(pk) + assert public_key.public_bytes( + serialization.Encoding.Raw, serialization.PublicFormat.Raw + ) == pk + + @pytest.mark.parametrize( + ("encoding", "fmt", "encryption", "passwd", "load_func"), + [ + ( + serialization.Encoding.PEM, + serialization.PrivateFormat.PKCS8, + serialization.BestAvailableEncryption(b"password"), + b"password", + serialization.load_pem_private_key + ), + ( + serialization.Encoding.DER, + serialization.PrivateFormat.PKCS8, + serialization.BestAvailableEncryption(b"password"), + b"password", + serialization.load_der_private_key + ), + ( + serialization.Encoding.PEM, + serialization.PrivateFormat.PKCS8, + serialization.NoEncryption(), + None, + serialization.load_pem_private_key + ), + ( + serialization.Encoding.DER, + serialization.PrivateFormat.PKCS8, + serialization.NoEncryption(), + None, + serialization.load_der_private_key + ), + ] + ) + def test_round_trip_private_serialization(self, encoding, fmt, encryption, + passwd, load_func, backend): + key = Ed448PrivateKey.generate() + serialized = key.private_bytes(encoding, fmt, encryption) + loaded_key = load_func(serialized, passwd, backend) + assert isinstance(loaded_key, Ed448PrivateKey) + + def test_invalid_type_public_bytes(self, backend): + with pytest.raises(TypeError): + Ed448PublicKey.from_public_bytes(object()) + + def test_invalid_type_private_bytes(self, backend): + with pytest.raises(TypeError): + Ed448PrivateKey.from_private_bytes(object()) + + def test_invalid_length_from_public_bytes(self, backend): + with pytest.raises(ValueError): + Ed448PublicKey.from_public_bytes(b"a" * 56) + with pytest.raises(ValueError): + Ed448PublicKey.from_public_bytes(b"a" * 58) + + def test_invalid_length_from_private_bytes(self, backend): + with pytest.raises(ValueError): + Ed448PrivateKey.from_private_bytes(b"a" * 56) + with pytest.raises(ValueError): + Ed448PrivateKey.from_private_bytes(b"a" * 58) + + def test_invalid_private_bytes(self, backend): + key = Ed448PrivateKey.generate() + with pytest.raises(ValueError): + key.private_bytes( + serialization.Encoding.Raw, + serialization.PrivateFormat.Raw, + None + ) + + with pytest.raises(ValueError): + key.private_bytes( + serialization.Encoding.Raw, + serialization.PrivateFormat.PKCS8, + None + ) + + with pytest.raises(ValueError): + key.private_bytes( + serialization.Encoding.PEM, + serialization.PrivateFormat.Raw, + serialization.NoEncryption() + ) + + def test_invalid_public_bytes(self, backend): + key = Ed448PrivateKey.generate().public_key() + with pytest.raises(ValueError): + key.public_bytes( + serialization.Encoding.Raw, + serialization.PublicFormat.SubjectPublicKeyInfo + ) + + with pytest.raises(ValueError): + key.public_bytes( + serialization.Encoding.PEM, + serialization.PublicFormat.PKCS1 + ) + + with pytest.raises(ValueError): + key.public_bytes( + serialization.Encoding.PEM, + serialization.PublicFormat.Raw + ) + + def test_buffer_protocol(self, backend): + private_bytes = os.urandom(57) + key = Ed448PrivateKey.from_private_bytes(bytearray(private_bytes)) + assert key.private_bytes( + serialization.Encoding.Raw, + serialization.PrivateFormat.Raw, + serialization.NoEncryption() + ) == private_bytes + + def test_malleability(self, backend): + # This is a signature where r > the group order. It should be + # rejected to prevent signature malleability issues. This test can + # be removed when wycheproof grows ed448 vectors + public_bytes = binascii.unhexlify( + "fedb02a658d74990244d9d10cf338e977565cbbda6b24c716829ed6ee1e4f28cf" + "2620c052db8d878f6243bffc22242816c1aaa67d2f3603600" + ) + signature = binascii.unhexlify( + "0cc16ba24d69277f927c1554b0f08a2a711bbdd20b058ccc660d00ca13542a3ce" + "f9e5c44c54ab23a2eb14f947e167b990b080863e28b399380f30db6e54d5d1406" + "d23378ffde11b1fb81b2b438a3b8e8aa7f7f4e1befcc905023fab5a5465053844" + "f04cf0c1b51d84760f869588687f57500" + ) + key = Ed448PublicKey.from_public_bytes(public_bytes) + with pytest.raises(InvalidSignature): + key.verify(signature, b"8") diff --git a/tests/hazmat/primitives/test_hash_vectors.py b/tests/hazmat/primitives/test_hash_vectors.py index c6e98283..0698f413 100644 --- a/tests/hazmat/primitives/test_hash_vectors.py +++ b/tests/hazmat/primitives/test_hash_vectors.py @@ -4,6 +4,7 @@ from __future__ import absolute_import, division, print_function +import binascii import os import pytest @@ -11,8 +12,8 @@ import pytest from cryptography.hazmat.backends.interfaces import HashBackend from cryptography.hazmat.primitives import hashes -from .utils import generate_hash_test, generate_long_string_hash_test -from ...utils import load_hash_vectors +from .utils import _load_all_params, generate_hash_test +from ...utils import load_hash_vectors, load_nist_vectors @pytest.mark.supported( @@ -21,7 +22,7 @@ from ...utils import load_hash_vectors ) @pytest.mark.requires_backend_interface(interface=HashBackend) class TestSHA1(object): - test_SHA1 = generate_hash_test( + test_sha1 = generate_hash_test( load_hash_vectors, os.path.join("hashes", "SHA1"), [ @@ -38,7 +39,7 @@ class TestSHA1(object): ) @pytest.mark.requires_backend_interface(interface=HashBackend) class TestSHA224(object): - test_SHA224 = generate_hash_test( + test_sha224 = generate_hash_test( load_hash_vectors, os.path.join("hashes", "SHA2"), [ @@ -55,7 +56,7 @@ class TestSHA224(object): ) @pytest.mark.requires_backend_interface(interface=HashBackend) class TestSHA256(object): - test_SHA256 = generate_hash_test( + test_sha256 = generate_hash_test( load_hash_vectors, os.path.join("hashes", "SHA2"), [ @@ -72,7 +73,7 @@ class TestSHA256(object): ) @pytest.mark.requires_backend_interface(interface=HashBackend) class TestSHA384(object): - test_SHA384 = generate_hash_test( + test_sha384 = generate_hash_test( load_hash_vectors, os.path.join("hashes", "SHA2"), [ @@ -89,7 +90,7 @@ class TestSHA384(object): ) @pytest.mark.requires_backend_interface(interface=HashBackend) class TestSHA512(object): - test_SHA512 = generate_hash_test( + test_sha512 = generate_hash_test( load_hash_vectors, os.path.join("hashes", "SHA2"), [ @@ -101,46 +102,36 @@ class TestSHA512(object): @pytest.mark.supported( - only_if=lambda backend: backend.hash_supported(hashes.RIPEMD160()), - skip_message="Does not support RIPEMD160", + only_if=lambda backend: backend.hash_supported(hashes.SHA512_224()), + skip_message="Does not support SHA512/224", ) @pytest.mark.requires_backend_interface(interface=HashBackend) -class TestRIPEMD160(object): - test_RIPEMD160 = generate_hash_test( +class TestSHA512224(object): + test_sha512_224 = generate_hash_test( load_hash_vectors, - os.path.join("hashes", "ripemd160"), + os.path.join("hashes", "SHA2"), [ - "ripevectors.txt", + "SHA512_224LongMsg.rsp", + "SHA512_224ShortMsg.rsp", ], - hashes.RIPEMD160(), - ) - - test_RIPEMD160_long_string = generate_long_string_hash_test( - hashes.RIPEMD160(), - "52783243c1697bdbe16d37f97f68f08325dc1528", + hashes.SHA512_224(), ) @pytest.mark.supported( - only_if=lambda backend: backend.hash_supported(hashes.Whirlpool()), - skip_message="Does not support Whirlpool", + only_if=lambda backend: backend.hash_supported(hashes.SHA512_256()), + skip_message="Does not support SHA512/256", ) @pytest.mark.requires_backend_interface(interface=HashBackend) -class TestWhirlpool(object): - test_whirlpool = generate_hash_test( +class TestSHA512256(object): + test_sha512_256 = generate_hash_test( load_hash_vectors, - os.path.join("hashes", "whirlpool"), + os.path.join("hashes", "SHA2"), [ - "iso-test-vectors.txt", + "SHA512_256LongMsg.rsp", + "SHA512_256ShortMsg.rsp", ], - hashes.Whirlpool(), - ) - - test_whirlpool_long_string = generate_long_string_hash_test( - hashes.Whirlpool(), - ("0c99005beb57eff50a7cf005560ddf5d29057fd86b2" - "0bfd62deca0f1ccea4af51fc15490eddc47af32bb2b" - "66c34ff9ad8c6008ad677f77126953b226e4ed8b01"), + hashes.SHA512_256(), ) @@ -158,3 +149,177 @@ class TestMD5(object): ], hashes.MD5(), ) + + +@pytest.mark.supported( + only_if=lambda backend: backend.hash_supported( + hashes.BLAKE2b(digest_size=64)), + skip_message="Does not support BLAKE2b", +) +@pytest.mark.requires_backend_interface(interface=HashBackend) +class TestBLAKE2b(object): + test_b2b = generate_hash_test( + load_hash_vectors, + os.path.join("hashes", "blake2"), + [ + "blake2b.txt", + ], + hashes.BLAKE2b(digest_size=64), + ) + + +@pytest.mark.supported( + only_if=lambda backend: backend.hash_supported( + hashes.BLAKE2s(digest_size=32)), + skip_message="Does not support BLAKE2s", +) +@pytest.mark.requires_backend_interface(interface=HashBackend) +class TestBLAKE2s256(object): + test_b2s = generate_hash_test( + load_hash_vectors, + os.path.join("hashes", "blake2"), + [ + "blake2s.txt", + ], + hashes.BLAKE2s(digest_size=32), + ) + + +@pytest.mark.supported( + only_if=lambda backend: backend.hash_supported(hashes.SHA3_224()), + skip_message="Does not support SHA3_224", +) +@pytest.mark.requires_backend_interface(interface=HashBackend) +class TestSHA3224(object): + test_sha3_224 = generate_hash_test( + load_hash_vectors, + os.path.join("hashes", "SHA3"), + [ + "SHA3_224LongMsg.rsp", + "SHA3_224ShortMsg.rsp", + ], + hashes.SHA3_224(), + ) + + +@pytest.mark.supported( + only_if=lambda backend: backend.hash_supported(hashes.SHA3_256()), + skip_message="Does not support SHA3_256", +) +@pytest.mark.requires_backend_interface(interface=HashBackend) +class TestSHA3256(object): + test_sha3_256 = generate_hash_test( + load_hash_vectors, + os.path.join("hashes", "SHA3"), + [ + "SHA3_256LongMsg.rsp", + "SHA3_256ShortMsg.rsp", + ], + hashes.SHA3_256(), + ) + + +@pytest.mark.supported( + only_if=lambda backend: backend.hash_supported(hashes.SHA3_384()), + skip_message="Does not support SHA3_384", +) +@pytest.mark.requires_backend_interface(interface=HashBackend) +class TestSHA3384(object): + test_sha3_384 = generate_hash_test( + load_hash_vectors, + os.path.join("hashes", "SHA3"), + [ + "SHA3_384LongMsg.rsp", + "SHA3_384ShortMsg.rsp", + ], + hashes.SHA3_384(), + ) + + +@pytest.mark.supported( + only_if=lambda backend: backend.hash_supported(hashes.SHA3_512()), + skip_message="Does not support SHA3_512", +) +@pytest.mark.requires_backend_interface(interface=HashBackend) +class TestSHA3512(object): + test_sha3_512 = generate_hash_test( + load_hash_vectors, + os.path.join("hashes", "SHA3"), + [ + "SHA3_512LongMsg.rsp", + "SHA3_512ShortMsg.rsp", + ], + hashes.SHA3_512(), + ) + + +@pytest.mark.supported( + only_if=lambda backend: backend.hash_supported( + hashes.SHAKE128(digest_size=16)), + skip_message="Does not support SHAKE128", +) +@pytest.mark.requires_backend_interface(interface=HashBackend) +class TestSHAKE128(object): + test_shake128 = generate_hash_test( + load_hash_vectors, + os.path.join("hashes", "SHAKE"), + [ + "SHAKE128LongMsg.rsp", + "SHAKE128ShortMsg.rsp", + ], + hashes.SHAKE128(digest_size=16), + ) + + @pytest.mark.parametrize( + "vector", + _load_all_params( + os.path.join("hashes", "SHAKE"), + [ + "SHAKE128VariableOut.rsp", + ], + load_nist_vectors, + ) + ) + def test_shake128_variable(self, vector, backend): + output_length = int(vector['outputlen']) // 8 + msg = binascii.unhexlify(vector['msg']) + shake = hashes.SHAKE128(digest_size=output_length) + m = hashes.Hash(shake, backend=backend) + m.update(msg) + assert m.finalize() == binascii.unhexlify(vector['output']) + + +@pytest.mark.supported( + only_if=lambda backend: backend.hash_supported( + hashes.SHAKE256(digest_size=32)), + skip_message="Does not support SHAKE256", +) +@pytest.mark.requires_backend_interface(interface=HashBackend) +class TestSHAKE256(object): + test_shake256 = generate_hash_test( + load_hash_vectors, + os.path.join("hashes", "SHAKE"), + [ + "SHAKE256LongMsg.rsp", + "SHAKE256ShortMsg.rsp", + ], + hashes.SHAKE256(digest_size=32), + ) + + @pytest.mark.parametrize( + "vector", + _load_all_params( + os.path.join("hashes", "SHAKE"), + [ + "SHAKE256VariableOut.rsp", + ], + load_nist_vectors, + ) + ) + def test_shake256_variable(self, vector, backend): + output_length = int(vector['outputlen']) // 8 + msg = binascii.unhexlify(vector['msg']) + shake = hashes.SHAKE256(digest_size=output_length) + m = hashes.Hash(shake, backend=backend) + m.update(msg) + assert m.finalize() == binascii.unhexlify(vector['output']) diff --git a/tests/hazmat/primitives/test_hashes.py b/tests/hazmat/primitives/test_hashes.py index 8f7fdb18..a743045c 100644 --- a/tests/hazmat/primitives/test_hashes.py +++ b/tests/hazmat/primitives/test_hashes.py @@ -4,27 +4,19 @@ from __future__ import absolute_import, division, print_function -import pretend +import binascii import pytest -from cryptography import utils from cryptography.exceptions import AlreadyFinalized, _Reasons from cryptography.hazmat.backends.interfaces import HashBackend from cryptography.hazmat.primitives import hashes from .utils import generate_base_hash_test -from ..backends.test_multibackend import DummyHashBackend +from ...doubles import DummyHashAlgorithm from ...utils import raises_unsupported_algorithm -@utils.register_interface(hashes.HashAlgorithm) -class UnsupportedDummyHash(object): - name = "unsupported-dummy-hash" - block_size = None - digest_size = None - - @pytest.mark.requires_backend_interface(interface=HashBackend) class TestHashContext(object): def test_hash_reject_unicode(self, backend): @@ -32,14 +24,6 @@ class TestHashContext(object): with pytest.raises(TypeError): m.update(u"\u00FC") - def test_copy_backend_object(self): - backend = DummyHashBackend([hashes.SHA1]) - copied_ctx = pretend.stub() - pretend_ctx = pretend.stub(copy=lambda: copied_ctx) - h = hashes.Hash(hashes.SHA1(), backend=backend, ctx=pretend_ctx) - assert h._backend is backend - assert h.copy()._backend is h._backend - def test_hash_algorithm_instance(self, backend): with pytest.raises(TypeError): hashes.Hash(hashes.SHA1, backend=backend) @@ -59,7 +43,7 @@ class TestHashContext(object): def test_unsupported_hash(self, backend): with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_HASH): - hashes.Hash(UnsupportedDummyHash(), backend) + hashes.Hash(DummyHashAlgorithm(), backend) @pytest.mark.supported( @@ -68,10 +52,9 @@ class TestHashContext(object): ) @pytest.mark.requires_backend_interface(interface=HashBackend) class TestSHA1(object): - test_SHA1 = generate_base_hash_test( + test_sha1 = generate_base_hash_test( hashes.SHA1(), digest_size=20, - block_size=64, ) @@ -81,10 +64,9 @@ class TestSHA1(object): ) @pytest.mark.requires_backend_interface(interface=HashBackend) class TestSHA224(object): - test_SHA224 = generate_base_hash_test( + test_sha224 = generate_base_hash_test( hashes.SHA224(), digest_size=28, - block_size=64, ) @@ -94,10 +76,9 @@ class TestSHA224(object): ) @pytest.mark.requires_backend_interface(interface=HashBackend) class TestSHA256(object): - test_SHA256 = generate_base_hash_test( + test_sha256 = generate_base_hash_test( hashes.SHA256(), digest_size=32, - block_size=64, ) @@ -107,10 +88,9 @@ class TestSHA256(object): ) @pytest.mark.requires_backend_interface(interface=HashBackend) class TestSHA384(object): - test_SHA384 = generate_base_hash_test( + test_sha384 = generate_base_hash_test( hashes.SHA384(), digest_size=48, - block_size=128, ) @@ -120,54 +100,103 @@ class TestSHA384(object): ) @pytest.mark.requires_backend_interface(interface=HashBackend) class TestSHA512(object): - test_SHA512 = generate_base_hash_test( + test_sha512 = generate_base_hash_test( hashes.SHA512(), digest_size=64, - block_size=128, ) @pytest.mark.supported( - only_if=lambda backend: backend.hash_supported(hashes.RIPEMD160()), - skip_message="Does not support RIPEMD160", + only_if=lambda backend: backend.hash_supported(hashes.MD5()), + skip_message="Does not support MD5", ) @pytest.mark.requires_backend_interface(interface=HashBackend) -class TestRIPEMD160(object): - test_RIPEMD160 = generate_base_hash_test( - hashes.RIPEMD160(), - digest_size=20, - block_size=64, +class TestMD5(object): + test_md5 = generate_base_hash_test( + hashes.MD5(), + digest_size=16, ) @pytest.mark.supported( - only_if=lambda backend: backend.hash_supported(hashes.Whirlpool()), - skip_message="Does not support Whirlpool", + only_if=lambda backend: backend.hash_supported( + hashes.BLAKE2b(digest_size=64)), + skip_message="Does not support BLAKE2b", ) @pytest.mark.requires_backend_interface(interface=HashBackend) -class TestWhirlpool(object): - test_Whirlpool = generate_base_hash_test( - hashes.Whirlpool(), +class TestBLAKE2b(object): + test_blake2b = generate_base_hash_test( + hashes.BLAKE2b(digest_size=64), digest_size=64, - block_size=64, ) + def test_invalid_digest_size(self, backend): + with pytest.raises(ValueError): + hashes.BLAKE2b(digest_size=65) + + with pytest.raises(ValueError): + hashes.BLAKE2b(digest_size=0) + + with pytest.raises(ValueError): + hashes.BLAKE2b(digest_size=-1) + @pytest.mark.supported( - only_if=lambda backend: backend.hash_supported(hashes.MD5()), - skip_message="Does not support MD5", + only_if=lambda backend: backend.hash_supported( + hashes.BLAKE2s(digest_size=32)), + skip_message="Does not support BLAKE2s", ) @pytest.mark.requires_backend_interface(interface=HashBackend) -class TestMD5(object): - test_MD5 = generate_base_hash_test( - hashes.MD5(), - digest_size=16, - block_size=64, +class TestBLAKE2s(object): + test_blake2s = generate_base_hash_test( + hashes.BLAKE2s(digest_size=32), + digest_size=32, ) + def test_invalid_digest_size(self, backend): + with pytest.raises(ValueError): + hashes.BLAKE2s(digest_size=33) + + with pytest.raises(ValueError): + hashes.BLAKE2s(digest_size=0) + + with pytest.raises(ValueError): + hashes.BLAKE2s(digest_size=-1) + def test_invalid_backend(): pretend_backend = object() with raises_unsupported_algorithm(_Reasons.BACKEND_MISSING_INTERFACE): hashes.Hash(hashes.SHA1(), pretend_backend) + + +@pytest.mark.requires_backend_interface(interface=HashBackend) +def test_buffer_protocol_hash(backend): + data = binascii.unhexlify(b"b4190e") + h = hashes.Hash(hashes.SHA256(), backend) + h.update(bytearray(data)) + assert h.finalize() == binascii.unhexlify( + b"dff2e73091f6c05e528896c4c831b9448653dc2ff043528f6769437bc7b975c2" + ) + + +class TestSHAKE(object): + @pytest.mark.parametrize( + "xof", + [hashes.SHAKE128, hashes.SHAKE256] + ) + def test_invalid_digest_type(self, xof): + with pytest.raises(TypeError): + xof(digest_size=object()) + + @pytest.mark.parametrize( + "xof", + [hashes.SHAKE128, hashes.SHAKE256] + ) + def test_invalid_digest_size(self, xof): + with pytest.raises(ValueError): + xof(digest_size=-5) + + with pytest.raises(ValueError): + xof(digest_size=0) diff --git a/tests/hazmat/primitives/test_hkdf.py b/tests/hazmat/primitives/test_hkdf.py index e33529c9..195bfb3a 100644 --- a/tests/hazmat/primitives/test_hkdf.py +++ b/tests/hazmat/primitives/test_hkdf.py @@ -5,6 +5,7 @@ from __future__ import absolute_import, division, print_function import binascii +import os import pytest @@ -15,13 +16,15 @@ from cryptography.hazmat.backends.interfaces import HMACBackend from cryptography.hazmat.primitives import hashes from cryptography.hazmat.primitives.kdf.hkdf import HKDF, HKDFExpand -from ...utils import raises_unsupported_algorithm +from ...utils import ( + load_nist_vectors, load_vectors_from_file, raises_unsupported_algorithm +) @pytest.mark.requires_backend_interface(interface=HMACBackend) class TestHKDF(object): def test_length_limit(self, backend): - big_length = 255 * (hashes.SHA256().digest_size // 8) + 1 + big_length = 255 * hashes.SHA256().digest_size + 1 with pytest.raises(ValueError): HKDF( @@ -142,6 +145,47 @@ class TestHKDF(object): hkdf.verify(b"foo", u"bar") + def test_derive_short_output(self, backend): + hkdf = HKDF( + hashes.SHA256(), + 4, + salt=None, + info=None, + backend=backend + ) + + assert hkdf.derive(b"\x01" * 16) == b"gJ\xfb{" + + def test_derive_long_output(self, backend): + vector = load_vectors_from_file( + os.path.join("KDF", "hkdf-generated.txt"), load_nist_vectors + )[0] + hkdf = HKDF( + hashes.SHA256(), + int(vector["l"]), + salt=vector["salt"], + info=vector["info"], + backend=backend + ) + ikm = binascii.unhexlify(vector["ikm"]) + + assert hkdf.derive(ikm) == binascii.unhexlify(vector["okm"]) + + def test_buffer_protocol(self, backend): + vector = load_vectors_from_file( + os.path.join("KDF", "hkdf-generated.txt"), load_nist_vectors + )[0] + hkdf = HKDF( + hashes.SHA256(), + int(vector["l"]), + salt=vector["salt"], + info=vector["info"], + backend=backend + ) + ikm = bytearray(binascii.unhexlify(vector["ikm"])) + + assert hkdf.derive(ikm) == binascii.unhexlify(vector["okm"]) + @pytest.mark.requires_backend_interface(interface=HMACBackend) class TestHKDFExpand(object): @@ -158,6 +202,19 @@ class TestHKDFExpand(object): assert binascii.hexlify(hkdf.derive(prk)) == okm + def test_buffer_protocol(self, backend): + prk = bytearray(binascii.unhexlify( + b"077709362c2e32df0ddc3f0dc47bba6390b6c73bb50f9c3122ec844ad7c2b3e5" + )) + + okm = (b"3cb25f25faacd57a90434f64d0362f2a2d2d0a90cf1a5a4c5db02d56ecc4c" + b"5bf34007208d5b887185865") + + info = binascii.unhexlify(b"f0f1f2f3f4f5f6f7f8f9") + hkdf = HKDFExpand(hashes.SHA256(), 42, info, backend) + + assert binascii.hexlify(hkdf.derive(prk)) == okm + def test_verify(self, backend): prk = binascii.unhexlify( b"077709362c2e32df0ddc3f0dc47bba6390b6c73bb50f9c3122ec844ad7c2b3e5" diff --git a/tests/hazmat/primitives/test_hkdf_vectors.py b/tests/hazmat/primitives/test_hkdf_vectors.py index 74bf9291..290cefbf 100644 --- a/tests/hazmat/primitives/test_hkdf_vectors.py +++ b/tests/hazmat/primitives/test_hkdf_vectors.py @@ -21,7 +21,7 @@ from ...utils import load_nist_vectors ) @pytest.mark.requires_backend_interface(interface=HMACBackend) class TestHKDFSHA1(object): - test_HKDFSHA1 = generate_hkdf_test( + test_hkdfsha1 = generate_hkdf_test( load_nist_vectors, os.path.join("KDF"), ["rfc-5869-HKDF-SHA1.txt"], @@ -35,7 +35,7 @@ class TestHKDFSHA1(object): ) @pytest.mark.requires_backend_interface(interface=HMACBackend) class TestHKDFSHA256(object): - test_HKDFSHA1 = generate_hkdf_test( + test_hkdfsha256 = generate_hkdf_test( load_nist_vectors, os.path.join("KDF"), ["rfc-5869-HKDF-SHA256.txt"], diff --git a/tests/hazmat/primitives/test_hmac.py b/tests/hazmat/primitives/test_hmac.py index 83b18cbc..0e2fe688 100644 --- a/tests/hazmat/primitives/test_hmac.py +++ b/tests/hazmat/primitives/test_hmac.py @@ -4,11 +4,10 @@ from __future__ import absolute_import, division, print_function -import pretend +import binascii import pytest -from cryptography import utils from cryptography.exceptions import ( AlreadyFinalized, InvalidSignature, _Reasons ) @@ -16,17 +15,10 @@ from cryptography.hazmat.backends.interfaces import HMACBackend from cryptography.hazmat.primitives import hashes, hmac from .utils import generate_base_hmac_test -from ..backends.test_multibackend import DummyHMACBackend +from ...doubles import DummyHashAlgorithm from ...utils import raises_unsupported_algorithm -@utils.register_interface(hashes.HashAlgorithm) -class UnsupportedDummyHash(object): - name = "unsupported-dummy-hash" - block_size = None - digest_size = None - - @pytest.mark.supported( only_if=lambda backend: backend.hmac_supported(hashes.MD5()), skip_message="Does not support MD5", @@ -45,14 +37,6 @@ class TestHMAC(object): with pytest.raises(TypeError): h.update(u"\u00FC") - def test_copy_backend_object(self): - backend = DummyHMACBackend([hashes.SHA1]) - copied_ctx = pretend.stub() - pretend_ctx = pretend.stub(copy=lambda: copied_ctx) - h = hmac.HMAC(b"key", hashes.SHA1(), backend=backend, ctx=pretend_ctx) - assert h._backend is backend - assert h.copy()._backend is backend - def test_hmac_algorithm_instance(self, backend): with pytest.raises(TypeError): hmac.HMAC(b"key", hashes.SHA1, backend=backend) @@ -95,7 +79,15 @@ class TestHMAC(object): def test_unsupported_hash(self, backend): with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_HASH): - hmac.HMAC(b"key", UnsupportedDummyHash(), backend) + hmac.HMAC(b"key", DummyHashAlgorithm(), backend) + + def test_buffer_protocol(self, backend): + key = bytearray(b"2b7e151628aed2a6abf7158809cf4f3c") + h = hmac.HMAC(key, hashes.SHA256(), backend) + h.update(bytearray(b"6bc1bee22e409f96e93d7e117393172a")) + assert h.finalize() == binascii.unhexlify( + b"a1bf7169c56a501c6585190ff4f07cad6e492a3ee187c0372614fb444b9fc3f0" + ) def test_invalid_backend(): diff --git a/tests/hazmat/primitives/test_hmac_vectors.py b/tests/hazmat/primitives/test_hmac_vectors.py index 8704e724..6ff71fe3 100644 --- a/tests/hazmat/primitives/test_hmac_vectors.py +++ b/tests/hazmat/primitives/test_hmac_vectors.py @@ -4,10 +4,12 @@ from __future__ import absolute_import, division, print_function +import binascii + import pytest from cryptography.hazmat.backends.interfaces import HMACBackend -from cryptography.hazmat.primitives import hashes +from cryptography.hazmat.primitives import hashes, hmac from .utils import generate_hmac_test from ...utils import load_hash_vectors @@ -110,16 +112,26 @@ class TestHMACSHA512(object): @pytest.mark.supported( - only_if=lambda backend: backend.hmac_supported(hashes.RIPEMD160()), - skip_message="Does not support RIPEMD160", + only_if=lambda backend: backend.hmac_supported(hashes.BLAKE2b( + digest_size=64 + )), + skip_message="Does not support BLAKE2", ) @pytest.mark.requires_backend_interface(interface=HMACBackend) -class TestHMACRIPEMD160(object): - test_hmac_ripemd160 = generate_hmac_test( - load_hash_vectors, - "HMAC", - [ - "rfc-2286-ripemd160.txt", - ], - hashes.RIPEMD160(), - ) +class TestHMACBLAKE2(object): + def test_blake2b(self, backend): + h = hmac.HMAC(b"0" * 64, hashes.BLAKE2b(digest_size=64), backend) + h.update(b"test") + digest = h.finalize() + assert digest == binascii.unhexlify( + b"b5319122f8a24ba134a0c9851922448104e25be5d1b91265c0c68b22722f0f29" + b"87dba4aeaa69e6bed7edc44f48d6b1be493a3ce583f9c737c53d6bacc09e2f32" + ) + + def test_blake2s(self, backend): + h = hmac.HMAC(b"0" * 32, hashes.BLAKE2s(digest_size=32), backend) + h.update(b"test") + digest = h.finalize() + assert digest == binascii.unhexlify( + b"51477cc5bdf1faf952cf97bb934ee936de1f4d5d7448a84eeb6f98d23b392166" + ) diff --git a/tests/hazmat/primitives/test_idea.py b/tests/hazmat/primitives/test_idea.py index 1b15ed67..6b8a2a87 100644 --- a/tests/hazmat/primitives/test_idea.py +++ b/tests/hazmat/primitives/test_idea.py @@ -18,13 +18,13 @@ from ...utils import load_nist_vectors @pytest.mark.supported( only_if=lambda backend: backend.cipher_supported( - algorithms.IDEA("\x00" * 16), modes.ECB() + algorithms.IDEA(b"\x00" * 16), modes.ECB() ), skip_message="Does not support IDEA ECB", ) @pytest.mark.requires_backend_interface(interface=CipherBackend) class TestIDEAModeECB(object): - test_ECB = generate_encrypt_test( + test_ecb = generate_encrypt_test( load_nist_vectors, os.path.join("ciphers", "IDEA"), ["idea-ecb.txt"], @@ -35,13 +35,13 @@ class TestIDEAModeECB(object): @pytest.mark.supported( only_if=lambda backend: backend.cipher_supported( - algorithms.IDEA("\x00" * 16), modes.CBC("\x00" * 8) + algorithms.IDEA(b"\x00" * 16), modes.CBC(b"\x00" * 8) ), skip_message="Does not support IDEA CBC", ) @pytest.mark.requires_backend_interface(interface=CipherBackend) class TestIDEAModeCBC(object): - test_CBC = generate_encrypt_test( + test_cbc = generate_encrypt_test( load_nist_vectors, os.path.join("ciphers", "IDEA"), ["idea-cbc.txt"], @@ -52,13 +52,13 @@ class TestIDEAModeCBC(object): @pytest.mark.supported( only_if=lambda backend: backend.cipher_supported( - algorithms.IDEA("\x00" * 16), modes.OFB("\x00" * 8) + algorithms.IDEA(b"\x00" * 16), modes.OFB(b"\x00" * 8) ), skip_message="Does not support IDEA OFB", ) @pytest.mark.requires_backend_interface(interface=CipherBackend) class TestIDEAModeOFB(object): - test_OFB = generate_encrypt_test( + test_ofb = generate_encrypt_test( load_nist_vectors, os.path.join("ciphers", "IDEA"), ["idea-ofb.txt"], @@ -69,13 +69,13 @@ class TestIDEAModeOFB(object): @pytest.mark.supported( only_if=lambda backend: backend.cipher_supported( - algorithms.IDEA("\x00" * 16), modes.CFB("\x00" * 8) + algorithms.IDEA(b"\x00" * 16), modes.CFB(b"\x00" * 8) ), skip_message="Does not support IDEA CFB", ) @pytest.mark.requires_backend_interface(interface=CipherBackend) class TestIDEAModeCFB(object): - test_CFB = generate_encrypt_test( + test_cfb = generate_encrypt_test( load_nist_vectors, os.path.join("ciphers", "IDEA"), ["idea-cfb.txt"], diff --git a/tests/hazmat/primitives/test_kbkdf.py b/tests/hazmat/primitives/test_kbkdf.py new file mode 100644 index 00000000..a16f1768 --- /dev/null +++ b/tests/hazmat/primitives/test_kbkdf.py @@ -0,0 +1,158 @@ +# 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.exceptions import ( + AlreadyFinalized, InvalidKey, _Reasons +) +from cryptography.hazmat.backends.interfaces import HMACBackend +from cryptography.hazmat.primitives import hashes +from cryptography.hazmat.primitives.kdf.kbkdf import ( + CounterLocation, KBKDFHMAC, Mode +) + +from ...doubles import DummyHashAlgorithm +from ...utils import raises_unsupported_algorithm + + +@pytest.mark.requires_backend_interface(interface=HMACBackend) +class TestKBKDFHMAC(object): + def test_invalid_key(self, backend): + kdf = KBKDFHMAC(hashes.SHA256(), Mode.CounterMode, 32, 4, 4, + CounterLocation.BeforeFixed, b'label', b'context', + None, backend=backend) + + key = kdf.derive(b"material") + + kdf = KBKDFHMAC(hashes.SHA256(), Mode.CounterMode, 32, 4, 4, + CounterLocation.BeforeFixed, b'label', b'context', + None, backend=backend) + + with pytest.raises(InvalidKey): + kdf.verify(b"material2", key) + + def test_already_finalized(self, backend): + kdf = KBKDFHMAC(hashes.SHA256(), Mode.CounterMode, 32, 4, 4, + CounterLocation.BeforeFixed, b'label', b'context', + None, backend=backend) + + kdf.derive(b'material') + + with pytest.raises(AlreadyFinalized): + kdf.derive(b'material2') + + kdf = KBKDFHMAC(hashes.SHA256(), Mode.CounterMode, 32, 4, 4, + CounterLocation.BeforeFixed, b'label', b'context', + None, backend=backend) + + key = kdf.derive(b'material') + + with pytest.raises(AlreadyFinalized): + kdf.verify(b'material', key) + + kdf = KBKDFHMAC(hashes.SHA256(), Mode.CounterMode, 32, 4, 4, + CounterLocation.BeforeFixed, b'label', b'context', + None, backend=backend) + kdf.verify(b'material', key) + + with pytest.raises(AlreadyFinalized): + kdf.verify(b"material", key) + + def test_key_length(self, backend): + kdf = KBKDFHMAC(hashes.SHA1(), Mode.CounterMode, 85899345920, 4, 4, + CounterLocation.BeforeFixed, b'label', b'context', + None, backend=backend) + + with pytest.raises(ValueError): + kdf.derive(b'material') + + def test_rlen(self, backend): + with pytest.raises(ValueError): + KBKDFHMAC(hashes.SHA256(), Mode.CounterMode, 32, 5, 4, + CounterLocation.BeforeFixed, b'label', b'context', + None, backend=backend) + + def test_r_type(self, backend): + with pytest.raises(TypeError): + KBKDFHMAC(hashes.SHA1(), Mode.CounterMode, 32, b'r', 4, + CounterLocation.BeforeFixed, b'label', b'context', + None, backend=backend) + + def test_l_type(self, backend): + with pytest.raises(TypeError): + KBKDFHMAC(hashes.SHA1(), Mode.CounterMode, 32, 4, b'l', + CounterLocation.BeforeFixed, b'label', b'context', + None, backend=backend) + + def test_l(self, backend): + with pytest.raises(ValueError): + KBKDFHMAC(hashes.SHA1(), Mode.CounterMode, 32, 4, None, + CounterLocation.BeforeFixed, b'label', b'context', + None, backend=backend) + + def test_unsupported_mode(self, backend): + with pytest.raises(TypeError): + KBKDFHMAC(hashes.SHA256(), None, 32, 4, 4, + CounterLocation.BeforeFixed, b'label', b'context', + None, backend=backend) + + def test_unsupported_location(self, backend): + with pytest.raises(TypeError): + KBKDFHMAC(hashes.SHA256(), Mode.CounterMode, 32, 4, 4, + None, b'label', b'context', None, + backend=backend) + + def test_unsupported_parameters(self, backend): + with pytest.raises(ValueError): + KBKDFHMAC(hashes.SHA256(), Mode.CounterMode, 32, 4, 4, + CounterLocation.BeforeFixed, b'label', b'context', + b'fixed', backend=backend) + + def test_unsupported_hash(self, backend): + with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_HASH): + KBKDFHMAC(object(), Mode.CounterMode, 32, 4, 4, + CounterLocation.BeforeFixed, b'label', b'context', + None, backend=backend) + + def test_unsupported_algorithm(self, backend): + with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_HASH): + KBKDFHMAC(DummyHashAlgorithm(), Mode.CounterMode, 32, 4, 4, + CounterLocation.BeforeFixed, b'label', b'context', + None, backend=backend) + + def test_invalid_backend(self, backend): + with raises_unsupported_algorithm(_Reasons.BACKEND_MISSING_INTERFACE): + KBKDFHMAC(hashes.SHA256(), Mode.CounterMode, 32, 4, 4, + CounterLocation.BeforeFixed, b'label', b'context', + None, backend=object()) + + def test_unicode_error_label(self, backend): + with pytest.raises(TypeError): + KBKDFHMAC(hashes.SHA256(), Mode.CounterMode, 32, 4, 4, + CounterLocation.BeforeFixed, u'label', b'context', + backend=backend) + + def test_unicode_error_context(self, backend): + with pytest.raises(TypeError): + KBKDFHMAC(hashes.SHA256(), Mode.CounterMode, 32, 4, 4, + CounterLocation.BeforeFixed, b'label', u'context', + None, backend=backend) + + def test_unicode_error_key_material(self, backend): + with pytest.raises(TypeError): + kdf = KBKDFHMAC(hashes.SHA256(), Mode.CounterMode, 32, 4, 4, + CounterLocation.BeforeFixed, b'label', + b'context', None, backend=backend) + kdf.derive(u'material') + + def test_buffer_protocol(self, backend): + kdf = KBKDFHMAC(hashes.SHA256(), Mode.CounterMode, 10, 4, 4, + CounterLocation.BeforeFixed, b'label', b'context', + None, backend=backend) + + key = kdf.derive(bytearray(b"material")) + assert key == b'\xb7\x01\x05\x98\xf5\x1a\x12L\xc7.' diff --git a/tests/hazmat/primitives/test_kbkdf_vectors.py b/tests/hazmat/primitives/test_kbkdf_vectors.py new file mode 100644 index 00000000..7bdbbdc7 --- /dev/null +++ b/tests/hazmat/primitives/test_kbkdf_vectors.py @@ -0,0 +1,23 @@ +# 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 os + +import pytest + +from cryptography.hazmat.backends.interfaces import HMACBackend + +from .utils import generate_kbkdf_counter_mode_test +from ...utils import load_nist_kbkdf_vectors + + +@pytest.mark.requires_backend_interface(interface=HMACBackend) +class TestCounterKDFCounterMode(object): + test_kbkdfctr = generate_kbkdf_counter_mode_test( + load_nist_kbkdf_vectors, + os.path.join("KDF"), + ["nist-800-108-KBKDF-CTR.txt"] + ) diff --git a/tests/hazmat/primitives/test_keywrap.py b/tests/hazmat/primitives/test_keywrap.py new file mode 100644 index 00000000..c74b144b --- /dev/null +++ b/tests/hazmat/primitives/test_keywrap.py @@ -0,0 +1,207 @@ +# 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 binascii +import os + +import pytest + +from cryptography.hazmat.backends.interfaces import CipherBackend +from cryptography.hazmat.primitives import keywrap +from cryptography.hazmat.primitives.ciphers import algorithms, modes + +from .utils import _load_all_params +from ...utils import load_nist_vectors + + +@pytest.mark.requires_backend_interface(interface=CipherBackend) +class TestAESKeyWrap(object): + @pytest.mark.parametrize( + "params", + _load_all_params( + os.path.join("keywrap", "kwtestvectors"), + ["KW_AE_128.txt", "KW_AE_192.txt", "KW_AE_256.txt"], + load_nist_vectors + ) + ) + @pytest.mark.supported( + only_if=lambda backend: backend.cipher_supported( + algorithms.AES(b"\x00" * 16), modes.ECB() + ), + skip_message="Does not support AES key wrap (RFC 3394) because AES-ECB" + " is unsupported", + ) + def test_wrap(self, backend, params): + wrapping_key = binascii.unhexlify(params["k"]) + key_to_wrap = binascii.unhexlify(params["p"]) + wrapped_key = keywrap.aes_key_wrap(wrapping_key, key_to_wrap, backend) + assert params["c"] == binascii.hexlify(wrapped_key) + + @pytest.mark.parametrize( + "params", + _load_all_params( + os.path.join("keywrap", "kwtestvectors"), + ["KW_AD_128.txt", "KW_AD_192.txt", "KW_AD_256.txt"], + load_nist_vectors + ) + ) + @pytest.mark.supported( + only_if=lambda backend: backend.cipher_supported( + algorithms.AES(b"\x00" * 16), modes.ECB() + ), + skip_message="Does not support AES key wrap (RFC 3394) because AES-ECB" + " is unsupported", + ) + def test_unwrap(self, backend, params): + wrapping_key = binascii.unhexlify(params["k"]) + wrapped_key = binascii.unhexlify(params["c"]) + if params.get("fail") is True: + with pytest.raises(keywrap.InvalidUnwrap): + keywrap.aes_key_unwrap(wrapping_key, wrapped_key, backend) + else: + unwrapped_key = keywrap.aes_key_unwrap( + wrapping_key, wrapped_key, backend + ) + assert params["p"] == binascii.hexlify(unwrapped_key) + + @pytest.mark.supported( + only_if=lambda backend: backend.cipher_supported( + algorithms.AES(b"\x00" * 16), modes.ECB() + ), + skip_message="Does not support AES key wrap (RFC 3394) because AES-ECB" + " is unsupported", + ) + def test_wrap_invalid_key_length(self, backend): + # The wrapping key must be of length [16, 24, 32] + with pytest.raises(ValueError): + keywrap.aes_key_wrap(b"badkey", b"sixteen_byte_key", backend) + + @pytest.mark.supported( + only_if=lambda backend: backend.cipher_supported( + algorithms.AES(b"\x00" * 16), modes.ECB() + ), + skip_message="Does not support AES key wrap (RFC 3394) because AES-ECB" + " is unsupported", + ) + def test_unwrap_invalid_key_length(self, backend): + with pytest.raises(ValueError): + keywrap.aes_key_unwrap(b"badkey", b"\x00" * 24, backend) + + @pytest.mark.supported( + only_if=lambda backend: backend.cipher_supported( + algorithms.AES(b"\x00" * 16), modes.ECB() + ), + skip_message="Does not support AES key wrap (RFC 3394) because AES-ECB" + " is unsupported", + ) + def test_wrap_invalid_key_to_wrap_length(self, backend): + # Keys to wrap must be at least 16 bytes long + with pytest.raises(ValueError): + keywrap.aes_key_wrap(b"sixteen_byte_key", b"\x00" * 15, backend) + + # Keys to wrap must be a multiple of 8 bytes + with pytest.raises(ValueError): + keywrap.aes_key_wrap(b"sixteen_byte_key", b"\x00" * 23, backend) + + def test_unwrap_invalid_wrapped_key_length(self, backend): + # Keys to unwrap must be at least 24 bytes + with pytest.raises(keywrap.InvalidUnwrap): + keywrap.aes_key_unwrap(b"sixteen_byte_key", b"\x00" * 16, backend) + + # Keys to unwrap must be a multiple of 8 bytes + with pytest.raises(keywrap.InvalidUnwrap): + keywrap.aes_key_unwrap(b"sixteen_byte_key", b"\x00" * 27, backend) + + +@pytest.mark.supported( + only_if=lambda backend: backend.cipher_supported( + algorithms.AES(b"\x00" * 16), modes.ECB() + ), + skip_message="Does not support AES key wrap (RFC 5649) because AES-ECB" + " is unsupported", +) +@pytest.mark.requires_backend_interface(interface=CipherBackend) +class TestAESKeyWrapWithPadding(object): + @pytest.mark.parametrize( + "params", + _load_all_params( + os.path.join("keywrap", "kwtestvectors"), + ["KWP_AE_128.txt", "KWP_AE_192.txt", "KWP_AE_256.txt"], + load_nist_vectors + ) + ) + def test_wrap(self, backend, params): + wrapping_key = binascii.unhexlify(params["k"]) + key_to_wrap = binascii.unhexlify(params["p"]) + wrapped_key = keywrap.aes_key_wrap_with_padding( + wrapping_key, key_to_wrap, backend + ) + assert params["c"] == binascii.hexlify(wrapped_key) + + @pytest.mark.parametrize( + "params", + _load_all_params("keywrap", ["kwp_botan.txt"], load_nist_vectors) + ) + def test_wrap_additional_vectors(self, backend, params): + wrapping_key = binascii.unhexlify(params["key"]) + key_to_wrap = binascii.unhexlify(params["input"]) + wrapped_key = keywrap.aes_key_wrap_with_padding( + wrapping_key, key_to_wrap, backend + ) + assert wrapped_key == binascii.unhexlify(params["output"]) + + @pytest.mark.parametrize( + "params", + _load_all_params( + os.path.join("keywrap", "kwtestvectors"), + ["KWP_AD_128.txt", "KWP_AD_192.txt", "KWP_AD_256.txt"], + load_nist_vectors + ) + ) + def test_unwrap(self, backend, params): + wrapping_key = binascii.unhexlify(params["k"]) + wrapped_key = binascii.unhexlify(params["c"]) + if params.get("fail") is True: + with pytest.raises(keywrap.InvalidUnwrap): + keywrap.aes_key_unwrap_with_padding( + wrapping_key, wrapped_key, backend + ) + else: + unwrapped_key = keywrap.aes_key_unwrap_with_padding( + wrapping_key, wrapped_key, backend + ) + assert params["p"] == binascii.hexlify(unwrapped_key) + + @pytest.mark.parametrize( + "params", + _load_all_params("keywrap", ["kwp_botan.txt"], load_nist_vectors) + ) + def test_unwrap_additional_vectors(self, backend, params): + wrapping_key = binascii.unhexlify(params["key"]) + wrapped_key = binascii.unhexlify(params["output"]) + unwrapped_key = keywrap.aes_key_unwrap_with_padding( + wrapping_key, wrapped_key, backend + ) + assert unwrapped_key == binascii.unhexlify(params["input"]) + + def test_unwrap_invalid_wrapped_key_length(self, backend): + # Keys to unwrap must be at least 16 bytes + with pytest.raises( + keywrap.InvalidUnwrap, match='Must be at least 16 bytes' + ): + keywrap.aes_key_unwrap_with_padding( + b"sixteen_byte_key", b"\x00" * 15, backend + ) + + def test_wrap_invalid_key_length(self, backend): + with pytest.raises(ValueError, match='must be a valid AES key length'): + keywrap.aes_key_wrap_with_padding(b"badkey", b"\x00", backend) + + def test_unwrap_invalid_key_length(self, backend): + with pytest.raises(ValueError, match='must be a valid AES key length'): + keywrap.aes_key_unwrap_with_padding( + b"badkey", b"\x00" * 16, backend + ) diff --git a/tests/hazmat/primitives/test_padding.py b/tests/hazmat/primitives/test_padding.py index 392ea737..fb72a794 100644 --- a/tests/hazmat/primitives/test_padding.py +++ b/tests/hazmat/primitives/test_padding.py @@ -6,6 +6,8 @@ from __future__ import absolute_import, division, print_function import pytest +import six + from cryptography.exceptions import AlreadyFinalized from cryptography.hazmat.primitives import padding @@ -99,3 +101,109 @@ class TestPKCS7(object): unpadder.update(b"") with pytest.raises(AlreadyFinalized): unpadder.finalize() + + def test_large_padding(self): + padder = padding.PKCS7(2040).padder() + padded_data = padder.update(b"") + padded_data += padder.finalize() + + for i in six.iterbytes(padded_data): + assert i == 255 + + unpadder = padding.PKCS7(2040).unpadder() + data = unpadder.update(padded_data) + data += unpadder.finalize() + + assert data == b"" + + +class TestANSIX923(object): + @pytest.mark.parametrize("size", [127, 4096, -2]) + def test_invalid_block_size(self, size): + with pytest.raises(ValueError): + padding.ANSIX923(size) + + @pytest.mark.parametrize(("size", "padded"), [ + (128, b"1111"), + (128, b"1111111111111111"), + (128, b"111111111111111\x06"), + (128, b"1111111111\x06\x06\x06\x06\x06\x06"), + (128, b""), + (128, b"\x06" * 6), + (128, b"\x00" * 16), + ]) + def test_invalid_padding(self, size, padded): + unpadder = padding.ANSIX923(size).unpadder() + with pytest.raises(ValueError): + unpadder.update(padded) + unpadder.finalize() + + def test_non_bytes(self): + padder = padding.ANSIX923(128).padder() + with pytest.raises(TypeError): + padder.update(u"abc") + unpadder = padding.ANSIX923(128).unpadder() + with pytest.raises(TypeError): + unpadder.update(u"abc") + + @pytest.mark.parametrize(("size", "unpadded", "padded"), [ + ( + 128, + b"1111111111", + b"1111111111\x00\x00\x00\x00\x00\x06", + ), + ( + 128, + b"111111111111111122222222222222", + b"111111111111111122222222222222\x00\x02", + ), + ( + 128, + b"1" * 16, + b"1" * 16 + b"\x00" * 15 + b"\x10", + ), + ( + 128, + b"1" * 17, + b"1" * 17 + b"\x00" * 14 + b"\x0F", + ) + ]) + def test_pad(self, size, unpadded, padded): + padder = padding.ANSIX923(size).padder() + result = padder.update(unpadded) + result += padder.finalize() + assert result == padded + + @pytest.mark.parametrize(("size", "unpadded", "padded"), [ + ( + 128, + b"1111111111", + b"1111111111\x00\x00\x00\x00\x00\x06", + ), + ( + 128, + b"111111111111111122222222222222", + b"111111111111111122222222222222\x00\x02", + ), + ]) + def test_unpad(self, size, unpadded, padded): + unpadder = padding.ANSIX923(size).unpadder() + result = unpadder.update(padded) + result += unpadder.finalize() + assert result == unpadded + + def test_use_after_finalize(self): + padder = padding.ANSIX923(128).padder() + b = padder.finalize() + with pytest.raises(AlreadyFinalized): + padder.update(b"") + with pytest.raises(AlreadyFinalized): + padder.finalize() + + unpadder = padding.ANSIX923(128).unpadder() + unpadder.update(b) + unpadder.finalize() + with pytest.raises(AlreadyFinalized): + unpadder.update(b"") + with pytest.raises(AlreadyFinalized): + unpadder.finalize() diff --git a/tests/hazmat/primitives/test_pbkdf2hmac.py b/tests/hazmat/primitives/test_pbkdf2hmac.py index 7fb6bbd6..0254b216 100644 --- a/tests/hazmat/primitives/test_pbkdf2hmac.py +++ b/tests/hazmat/primitives/test_pbkdf2hmac.py @@ -6,7 +6,6 @@ from __future__ import absolute_import, division, print_function import pytest -from cryptography import utils from cryptography.exceptions import ( AlreadyFinalized, InvalidKey, _Reasons ) @@ -14,16 +13,10 @@ from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives import hashes from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC +from ...doubles import DummyHashAlgorithm from ...utils import raises_unsupported_algorithm -@utils.register_interface(hashes.HashAlgorithm) -class DummyHash(object): - name = "dummy-hash" - block_size = None - digest_size = None - - class TestPBKDF2HMAC(object): def test_already_finalized(self): kdf = PBKDF2HMAC(hashes.SHA1(), 20, b"salt", 10, default_backend()) @@ -43,7 +36,9 @@ class TestPBKDF2HMAC(object): def test_unsupported_algorithm(self): with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_HASH): - PBKDF2HMAC(DummyHash(), 20, b"salt", 10, default_backend()) + PBKDF2HMAC( + DummyHashAlgorithm(), 20, b"salt", 10, default_backend() + ) def test_invalid_key(self): kdf = PBKDF2HMAC(hashes.SHA1(), 20, b"salt", 10, default_backend()) @@ -62,6 +57,11 @@ class TestPBKDF2HMAC(object): with pytest.raises(TypeError): kdf.derive(u"unicode here") + def test_buffer_protocol(self, backend): + kdf = PBKDF2HMAC(hashes.SHA1(), 10, b"salt", 10, default_backend()) + data = bytearray(b"data") + assert kdf.derive(data) == b"\xe9n\xaa\x81\xbbt\xa4\xf6\x08\xce" + def test_invalid_backend(): pretend_backend = object() diff --git a/tests/hazmat/primitives/test_pkcs12.py b/tests/hazmat/primitives/test_pkcs12.py new file mode 100644 index 00000000..0bb76e25 --- /dev/null +++ b/tests/hazmat/primitives/test_pkcs12.py @@ -0,0 +1,139 @@ +# 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 os + +import pytest + +from cryptography import x509 +from cryptography.hazmat.backends.interfaces import DERSerializationBackend +from cryptography.hazmat.backends.openssl.backend import _RC2 +from cryptography.hazmat.primitives.serialization import load_pem_private_key +from cryptography.hazmat.primitives.serialization.pkcs12 import ( + load_key_and_certificates +) + +from .utils import load_vectors_from_file + + +@pytest.mark.requires_backend_interface(interface=DERSerializationBackend) +class TestPKCS12(object): + def _test_load_pkcs12_ec_keys(self, filename, password, backend): + cert = load_vectors_from_file( + os.path.join("x509", "custom", "ca", "ca.pem"), + lambda pemfile: x509.load_pem_x509_certificate( + pemfile.read(), backend + ), mode="rb" + ) + key = load_vectors_from_file( + os.path.join("x509", "custom", "ca", "ca_key.pem"), + lambda pemfile: load_pem_private_key( + pemfile.read(), None, backend + ), mode="rb" + ) + parsed_key, parsed_cert, parsed_more_certs = load_vectors_from_file( + os.path.join("pkcs12", filename), + lambda derfile: load_key_and_certificates( + derfile.read(), password, backend + ), mode="rb" + ) + assert parsed_cert == cert + assert parsed_key.private_numbers() == key.private_numbers() + assert parsed_more_certs == [] + + @pytest.mark.parametrize( + ("filename", "password"), + [ + ("cert-key-aes256cbc.p12", b"cryptography"), + ("cert-none-key-none.p12", b"cryptography"), + ] + ) + def test_load_pkcs12_ec_keys(self, filename, password, backend): + self._test_load_pkcs12_ec_keys(filename, password, backend) + + @pytest.mark.parametrize( + ("filename", "password"), + [ + ("cert-rc2-key-3des.p12", b"cryptography"), + ("no-password.p12", None), + ] + ) + @pytest.mark.supported( + only_if=lambda backend: backend.cipher_supported(_RC2(), None), + skip_message="Does not support RC2" + ) + def test_load_pkcs12_ec_keys_rc2(self, filename, password, backend): + self._test_load_pkcs12_ec_keys(filename, password, backend) + + def test_load_pkcs12_cert_only(self, backend): + cert = load_vectors_from_file( + os.path.join("x509", "custom", "ca", "ca.pem"), + lambda pemfile: x509.load_pem_x509_certificate( + pemfile.read(), backend + ), mode="rb" + ) + parsed_key, parsed_cert, parsed_more_certs = load_vectors_from_file( + os.path.join("pkcs12", "cert-aes256cbc-no-key.p12"), + lambda data: load_key_and_certificates( + data.read(), b"cryptography", backend + ), + mode="rb" + ) + assert parsed_cert is None + assert parsed_key is None + assert parsed_more_certs == [cert] + + def test_load_pkcs12_key_only(self, backend): + key = load_vectors_from_file( + os.path.join("x509", "custom", "ca", "ca_key.pem"), + lambda pemfile: load_pem_private_key( + pemfile.read(), None, backend + ), mode="rb" + ) + parsed_key, parsed_cert, parsed_more_certs = load_vectors_from_file( + os.path.join("pkcs12", "no-cert-key-aes256cbc.p12"), + lambda data: load_key_and_certificates( + data.read(), b"cryptography", backend + ), + mode="rb" + ) + assert parsed_key.private_numbers() == key.private_numbers() + assert parsed_cert is None + assert parsed_more_certs == [] + + def test_non_bytes(self, backend): + with pytest.raises(TypeError): + load_key_and_certificates( + b"irrelevant", object(), backend + ) + + def test_not_a_pkcs12(self, backend): + with pytest.raises(ValueError): + load_key_and_certificates( + b"invalid", b"pass", backend + ) + + def test_invalid_password(self, backend): + with pytest.raises(ValueError): + load_vectors_from_file( + os.path.join("pkcs12", "cert-key-aes256cbc.p12"), + lambda derfile: load_key_and_certificates( + derfile.read(), b"invalid", backend + ), mode="rb" + ) + + def test_buffer_protocol(self, backend): + p12 = load_vectors_from_file( + os.path.join("pkcs12", "cert-key-aes256cbc.p12"), + lambda derfile: derfile.read(), mode="rb" + ) + p12buffer = bytearray(p12) + parsed_key, parsed_cert, parsed_more_certs = load_key_and_certificates( + p12buffer, bytearray(b"cryptography"), backend + ) + assert parsed_key is not None + assert parsed_cert is not None + assert parsed_more_certs == [] diff --git a/tests/hazmat/primitives/test_poly1305.py b/tests/hazmat/primitives/test_poly1305.py new file mode 100644 index 00000000..edca4623 --- /dev/null +++ b/tests/hazmat/primitives/test_poly1305.py @@ -0,0 +1,152 @@ +# 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 binascii +import os + +import pytest + +from cryptography.exceptions import ( + AlreadyFinalized, InvalidSignature, _Reasons +) +from cryptography.hazmat.primitives.poly1305 import Poly1305 + +from ...utils import ( + load_nist_vectors, load_vectors_from_file, raises_unsupported_algorithm +) + + +@pytest.mark.supported( + only_if=lambda backend: not backend.poly1305_supported(), + skip_message="Requires OpenSSL without poly1305 support" +) +def test_poly1305_unsupported(backend): + with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_MAC): + Poly1305(b"0" * 32) + + +@pytest.mark.supported( + only_if=lambda backend: backend.poly1305_supported(), + skip_message="Requires OpenSSL with poly1305 support" +) +class TestPoly1305(object): + @pytest.mark.parametrize( + "vector", + load_vectors_from_file( + os.path.join("poly1305", "rfc7539.txt"), load_nist_vectors + ) + ) + def test_vectors(self, vector, backend): + key = binascii.unhexlify(vector["key"]) + msg = binascii.unhexlify(vector["msg"]) + tag = binascii.unhexlify(vector["tag"]) + poly = Poly1305(key) + poly.update(msg) + assert poly.finalize() == tag + + assert Poly1305.generate_tag(key, msg) == tag + Poly1305.verify_tag(key, msg, tag) + + def test_key_with_no_additional_references(self, backend): + poly = Poly1305(os.urandom(32)) + assert len(poly.finalize()) == 16 + + def test_raises_after_finalize(self, backend): + poly = Poly1305(b"0" * 32) + poly.finalize() + + with pytest.raises(AlreadyFinalized): + poly.update(b"foo") + + with pytest.raises(AlreadyFinalized): + poly.finalize() + + def test_reject_unicode(self, backend): + poly = Poly1305(b"0" * 32) + with pytest.raises(TypeError): + poly.update(u'') + + with pytest.raises(TypeError): + Poly1305.generate_tag(b"0" * 32, u'') + + def test_verify(self, backend): + poly = Poly1305(b"0" * 32) + poly.update(b"msg") + tag = poly.finalize() + + with pytest.raises(AlreadyFinalized): + poly.verify(b"") + + poly2 = Poly1305(b"0" * 32) + poly2.update(b"msg") + poly2.verify(tag) + + Poly1305.verify_tag(b"0" * 32, b"msg", tag) + + def test_invalid_verify(self, backend): + poly = Poly1305(b"0" * 32) + poly.update(b"msg") + with pytest.raises(InvalidSignature): + poly.verify(b"") + + p2 = Poly1305(b"0" * 32) + p2.update(b"msg") + with pytest.raises(InvalidSignature): + p2.verify(b"\x00" * 16) + + with pytest.raises(InvalidSignature): + Poly1305.verify_tag(b"0" * 32, b"msg", b"\x00" * 16) + + def test_verify_reject_unicode(self, backend): + poly = Poly1305(b"0" * 32) + with pytest.raises(TypeError): + poly.verify(u'') + + with pytest.raises(TypeError): + Poly1305.verify_tag(b"0" * 32, b"msg", u'') + + def test_invalid_key_type(self, backend): + with pytest.raises(TypeError): + Poly1305(object()) + + with pytest.raises(TypeError): + Poly1305.generate_tag(object(), b"msg") + + def test_invalid_key_length(self, backend): + with pytest.raises(ValueError): + Poly1305(b"0" * 31) + + with pytest.raises(ValueError): + Poly1305.generate_tag(b"0" * 31, b"msg") + + with pytest.raises(ValueError): + Poly1305(b"0" * 33) + + with pytest.raises(ValueError): + Poly1305.generate_tag(b"0" * 33, b"msg") + + def test_buffer_protocol(self, backend): + key = binascii.unhexlify( + b"1c9240a5eb55d38af333888604f6b5f0473917c1402b80099dca5cb" + b"c207075c0" + ) + msg = binascii.unhexlify( + b"2754776173206272696c6c69672c20616e642074686520736c69746" + b"87920746f7665730a446964206779726520616e642067696d626c65" + b"20696e2074686520776162653a0a416c6c206d696d7379207765726" + b"52074686520626f726f676f7665732c0a416e6420746865206d6f6d" + b"65207261746873206f757467726162652e" + ) + key = bytearray(key) + poly = Poly1305(key) + poly.update(bytearray(msg)) + assert poly.finalize() == binascii.unhexlify( + b"4541669a7eaaee61e708dc7cbcc5eb62" + ) + + assert Poly1305.generate_tag(key, msg) == binascii.unhexlify( + b"4541669a7eaaee61e708dc7cbcc5eb62" + ) diff --git a/tests/hazmat/primitives/test_rsa.py b/tests/hazmat/primitives/test_rsa.py index eb12df8d..e6482651 100644 --- a/tests/hazmat/primitives/test_rsa.py +++ b/tests/hazmat/primitives/test_rsa.py @@ -6,12 +6,10 @@ from __future__ import absolute_import, division, print_function import binascii import itertools -import math import os import pytest -from cryptography import utils from cryptography.exceptions import ( AlreadyFinalized, InvalidSignature, _Reasons ) @@ -19,38 +17,43 @@ from cryptography.hazmat.backends.interfaces import ( PEMSerializationBackend, RSABackend ) from cryptography.hazmat.primitives import hashes, serialization -from cryptography.hazmat.primitives.asymmetric import padding, rsa +from cryptography.hazmat.primitives.asymmetric import ( + padding, rsa, utils as asym_utils +) from cryptography.hazmat.primitives.asymmetric.rsa import ( RSAPrivateNumbers, RSAPublicNumbers ) +from cryptography.utils import CryptographyDeprecationWarning from .fixtures_rsa import ( RSA_KEY_1024, RSA_KEY_1025, RSA_KEY_1026, RSA_KEY_1027, RSA_KEY_1028, RSA_KEY_1029, RSA_KEY_1030, RSA_KEY_1031, RSA_KEY_1536, RSA_KEY_2048, - RSA_KEY_512, RSA_KEY_512_ALT, RSA_KEY_522, RSA_KEY_599, RSA_KEY_745, - RSA_KEY_768, + RSA_KEY_2048_ALT, RSA_KEY_512, RSA_KEY_512_ALT, RSA_KEY_522, RSA_KEY_599, + RSA_KEY_745, RSA_KEY_768, ) from .utils import ( _check_rsa_private_numbers, generate_rsa_verification_test ) +from ...doubles import ( + DummyAsymmetricPadding, DummyHashAlgorithm, DummyKeySerializationEncryption +) from ...utils import ( - load_pkcs1_vectors, load_rsa_nist_vectors, load_vectors_from_file, - raises_unsupported_algorithm + load_nist_vectors, load_pkcs1_vectors, load_rsa_nist_vectors, + load_vectors_from_file, raises_unsupported_algorithm ) -@utils.register_interface(padding.AsymmetricPadding) -class DummyPadding(object): - name = "UNSUPPORTED-PADDING" - - class DummyMGF(object): _salt_length = 0 -@utils.register_interface(serialization.KeySerializationEncryption) -class DummyKeyEncryption(object): - pass +def _check_rsa_private_numbers_if_serializable(key): + if isinstance(key, rsa.RSAPrivateKeyWithSerialization): + _check_rsa_private_numbers(key.private_numbers()) + + +def test_check_rsa_private_numbers_if_serializable(): + _check_rsa_private_numbers_if_serializable("notserializable") def _flatten_pkcs1_examples(vectors): @@ -64,6 +67,60 @@ def _flatten_pkcs1_examples(vectors): return flattened_vectors +def _build_oaep_sha2_vectors(): + base_path = os.path.join("asymmetric", "RSA", "oaep-custom") + vectors = [] + hashalgs = [ + hashes.SHA1(), + hashes.SHA224(), + hashes.SHA256(), + hashes.SHA384(), + hashes.SHA512(), + ] + for mgf1alg, oaepalg in itertools.product(hashalgs, hashalgs): + if mgf1alg.name == "sha1" and oaepalg.name == "sha1": + # We need to generate the cartesian product of the permutations + # of all the SHAs above, but SHA1/SHA1 is something we already + # tested previously and thus did not generate custom vectors for. + continue + + examples = _flatten_pkcs1_examples( + load_vectors_from_file( + os.path.join( + base_path, + "oaep-{}-{}.txt".format( + mgf1alg.name, oaepalg.name + ) + ), + load_pkcs1_vectors + ) + ) + # We've loaded the files, but the loaders don't give us any information + # about the mgf1 or oaep hash algorithms. We know this info so we'll + # just add that to the end of the tuple + for private, public, vector in examples: + vectors.append((private, public, vector, mgf1alg, oaepalg)) + return vectors + + +def _skip_pss_hash_algorithm_unsupported(backend, hash_alg): + if not backend.rsa_padding_supported( + padding.PSS( + mgf=padding.MGF1(hash_alg), + salt_length=padding.PSS.MAX_LENGTH + ) + ): + pytest.skip( + "Does not support {} in MGF1 using PSS.".format(hash_alg.name) + ) + + +@pytest.mark.requires_backend_interface(interface=RSABackend) +def test_skip_pss_hash_algorithm_unsupported(backend): + with pytest.raises(pytest.skip.Exception): + _skip_pss_hash_algorithm_unsupported(backend, DummyHashAlgorithm()) + + def test_modular_inverse(): p = int( "d1f9f6c09fd3d38987f7970247b85a6da84907753d42ec52bc23b745093f4fff5cff3" @@ -85,21 +142,6 @@ def test_modular_inverse(): ) -def _skip_if_no_serialization(key, backend): - if not isinstance( - key, - (rsa.RSAPrivateKeyWithSerialization, rsa.RSAPublicKeyWithSerialization) - ): - pytest.skip( - "{0} does not support RSA key serialization".format(backend) - ) - - -def test_skip_if_no_serialization(): - with pytest.raises(pytest.skip.Exception): - _skip_if_no_serialization("notakeywithserialization", "backend") - - @pytest.mark.requires_backend_interface(interface=RSABackend) class TestRSA(object): @pytest.mark.parametrize( @@ -113,10 +155,9 @@ class TestRSA(object): skey = rsa.generate_private_key(public_exponent, key_size, backend) assert skey.key_size == key_size - if isinstance(skey, rsa.RSAPrivateKeyWithSerialization): - _check_rsa_private_numbers(skey.private_numbers()) - pkey = skey.public_key() - assert isinstance(pkey.public_numbers(), rsa.RSAPublicNumbers) + _check_rsa_private_numbers_if_serializable(skey) + pkey = skey.public_key() + assert isinstance(pkey.public_numbers(), rsa.RSAPublicNumbers) def test_generate_bad_public_exponent(self, backend): with pytest.raises(ValueError): @@ -177,6 +218,133 @@ class TestRSA(object): assert public_num.n == public_num2.n assert public_num.e == public_num2.e + @pytest.mark.parametrize( + "vector", + load_vectors_from_file( + os.path.join("asymmetric", "RSA", "oaep-label.txt"), + load_nist_vectors) + ) + @pytest.mark.supported( + only_if=lambda backend: backend.rsa_padding_supported( + padding.OAEP( + mgf=padding.MGF1(algorithm=hashes.SHA256()), + algorithm=hashes.SHA256(), + label=b"label" + ) + ), + skip_message="Does not support RSA OAEP labels" + ) + def test_oaep_label_decrypt(self, vector, backend): + private_key = serialization.load_der_private_key( + binascii.unhexlify(vector["key"]), None, backend + ) + assert vector["oaepdigest"] == b"SHA512" + decrypted = private_key.decrypt( + binascii.unhexlify(vector["input"]), + padding.OAEP( + mgf=padding.MGF1(algorithm=hashes.SHA512()), + algorithm=hashes.SHA512(), + label=binascii.unhexlify(vector["oaeplabel"]) + ) + ) + assert vector["output"][1:-1] == decrypted + + @pytest.mark.parametrize( + ("msg", "label"), + [ + (b"amazing encrypted msg", b"some label"), + (b"amazing encrypted msg", b""), + ] + ) + @pytest.mark.supported( + only_if=lambda backend: backend.rsa_padding_supported( + padding.OAEP( + mgf=padding.MGF1(algorithm=hashes.SHA256()), + algorithm=hashes.SHA256(), + label=b"label" + ) + ), + skip_message="Does not support RSA OAEP labels" + ) + def test_oaep_label_roundtrip(self, msg, label, backend): + private_key = RSA_KEY_2048.private_key(backend) + ct = private_key.public_key().encrypt( + msg, + padding.OAEP( + mgf=padding.MGF1(algorithm=hashes.SHA256()), + algorithm=hashes.SHA256(), + label=label + ) + ) + pt = private_key.decrypt( + ct, + padding.OAEP( + mgf=padding.MGF1(algorithm=hashes.SHA256()), + algorithm=hashes.SHA256(), + label=label + ) + ) + assert pt == msg + + @pytest.mark.parametrize( + ("enclabel", "declabel"), + [ + (b"label1", b"label2"), + (b"label3", b""), + (b"", b"label4"), + ] + ) + @pytest.mark.supported( + only_if=lambda backend: backend.rsa_padding_supported( + padding.OAEP( + mgf=padding.MGF1(algorithm=hashes.SHA256()), + algorithm=hashes.SHA256(), + label=b"label" + ) + ), + skip_message="Does not support RSA OAEP labels" + ) + def test_oaep_wrong_label(self, enclabel, declabel, backend): + private_key = RSA_KEY_2048.private_key(backend) + msg = b"test" + ct = private_key.public_key().encrypt( + msg, padding.OAEP( + mgf=padding.MGF1(algorithm=hashes.SHA256()), + algorithm=hashes.SHA256(), + label=enclabel + ) + ) + with pytest.raises(ValueError): + private_key.decrypt( + ct, padding.OAEP( + mgf=padding.MGF1(algorithm=hashes.SHA256()), + algorithm=hashes.SHA256(), + label=declabel + ) + ) + + @pytest.mark.supported( + only_if=lambda backend: not backend.rsa_padding_supported( + padding.OAEP( + mgf=padding.MGF1(algorithm=hashes.SHA256()), + algorithm=hashes.SHA256(), + label=b"label" + ) + ), + skip_message="Requires backend without RSA OAEP label support" + ) + def test_unsupported_oaep_label_decrypt(self, backend): + private_key = RSA_KEY_512.private_key(backend) + with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_PADDING): + private_key.decrypt( + b"0" * 64, + padding.OAEP( + mgf=padding.MGF1(algorithm=hashes.SHA1()), + algorithm=hashes.SHA1(), + label=b"label" + ) + ) + def test_rsa_generate_invalid_backend(): pretend_backend = object() @@ -215,9 +383,11 @@ class TestRSASignature(object): n=private["modulus"] ) ).private_key(backend) - signer = private_key.signer(padding.PKCS1v15(), hashes.SHA1()) - signer.update(binascii.unhexlify(example["message"])) - signature = signer.finalize() + signature = private_key.sign( + binascii.unhexlify(example["message"]), + padding.PKCS1v15(), + hashes.SHA1() + ) assert binascii.hexlify(signature) == example["signature"] @pytest.mark.supported( @@ -255,56 +425,43 @@ class TestRSASignature(object): e=public["public_exponent"], n=public["modulus"] ).public_key(backend) - signer = private_key.signer( + signature = private_key.sign( + binascii.unhexlify(example["message"]), padding.PSS( mgf=padding.MGF1(algorithm=hashes.SHA1()), salt_length=padding.PSS.MAX_LENGTH ), hashes.SHA1() ) - signer.update(binascii.unhexlify(example["message"])) - signature = signer.finalize() - assert len(signature) == math.ceil(private_key.key_size / 8.0) + assert len(signature) == (private_key.key_size + 7) // 8 # PSS signatures contain randomness so we can't do an exact # signature check. Instead we'll verify that the signature created # successfully verifies. - verifier = public_key.verifier( + public_key.verify( signature, + binascii.unhexlify(example["message"]), padding.PSS( mgf=padding.MGF1(algorithm=hashes.SHA1()), salt_length=padding.PSS.MAX_LENGTH ), hashes.SHA1(), ) - verifier.update(binascii.unhexlify(example["message"])) - verifier.verify() @pytest.mark.parametrize( "hash_alg", [hashes.SHA224(), hashes.SHA256(), hashes.SHA384(), hashes.SHA512()] ) def test_pss_signing_sha2(self, hash_alg, backend): - if not backend.rsa_padding_supported( - padding.PSS( - mgf=padding.MGF1(hash_alg), - salt_length=padding.PSS.MAX_LENGTH - ) - ): - pytest.skip( - "Does not support {0} in MGF1 using PSS.".format(hash_alg.name) - ) + _skip_pss_hash_algorithm_unsupported(backend, hash_alg) private_key = RSA_KEY_768.private_key(backend) public_key = private_key.public_key() pss = padding.PSS( mgf=padding.MGF1(hash_alg), salt_length=padding.PSS.MAX_LENGTH ) - signer = private_key.signer(pss, hash_alg) - signer.update(b"testing signature") - signature = signer.finalize() - verifier = public_key.verifier(signature, pss, hash_alg) - verifier.update(b"testing signature") - verifier.verify() + msg = b"testing signature" + signature = private_key.sign(msg, pss, hash_alg) + public_key.verify(signature, msg, pss, hash_alg) @pytest.mark.supported( only_if=lambda backend: ( @@ -320,15 +477,14 @@ class TestRSASignature(object): ) def test_pss_minimum_key_size_for_digest(self, backend): private_key = RSA_KEY_522.private_key(backend) - signer = private_key.signer( + private_key.sign( + b"no failure", padding.PSS( mgf=padding.MGF1(hashes.SHA1()), salt_length=padding.PSS.MAX_LENGTH ), hashes.SHA512() ) - signer.update(b"no failure") - signer.finalize() @pytest.mark.supported( only_if=lambda backend: backend.rsa_padding_supported( @@ -346,7 +502,8 @@ class TestRSASignature(object): def test_pss_signing_digest_too_large_for_key_size(self, backend): private_key = RSA_KEY_512.private_key(backend) with pytest.raises(ValueError): - private_key.signer( + private_key.sign( + b"msg", padding.PSS( mgf=padding.MGF1(hashes.SHA1()), salt_length=padding.PSS.MAX_LENGTH @@ -365,16 +522,15 @@ class TestRSASignature(object): ) def test_pss_signing_salt_length_too_long(self, backend): private_key = RSA_KEY_512.private_key(backend) - signer = private_key.signer( - padding.PSS( - mgf=padding.MGF1(hashes.SHA1()), - salt_length=1000000 - ), - hashes.SHA1() - ) - signer.update(b"failure coming") with pytest.raises(ValueError): - signer.finalize() + private_key.sign( + b"failure coming", + padding.PSS( + mgf=padding.MGF1(hashes.SHA1()), + salt_length=1000000 + ), + hashes.SHA1() + ) @pytest.mark.supported( only_if=lambda backend: backend.rsa_padding_supported( @@ -384,7 +540,8 @@ class TestRSASignature(object): ) def test_use_after_finalize(self, backend): private_key = RSA_KEY_512.private_key(backend) - signer = private_key.signer(padding.PKCS1v15(), hashes.SHA1()) + with pytest.warns(CryptographyDeprecationWarning): + signer = private_key.signer(padding.PKCS1v15(), hashes.SHA1()) signer.update(b"sign me") signer.finalize() with pytest.raises(AlreadyFinalized): @@ -395,12 +552,12 @@ class TestRSASignature(object): def test_unsupported_padding(self, backend): private_key = RSA_KEY_512.private_key(backend) with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_PADDING): - private_key.signer(DummyPadding(), hashes.SHA1()) + private_key.sign(b"msg", DummyAsymmetricPadding(), hashes.SHA1()) def test_padding_incorrect_type(self, backend): private_key = RSA_KEY_512.private_key(backend) with pytest.raises(TypeError): - private_key.signer("notpadding", hashes.SHA1()) + private_key.sign(b"msg", "notpadding", hashes.SHA1()) @pytest.mark.supported( only_if=lambda backend: backend.rsa_padding_supported( @@ -411,7 +568,8 @@ class TestRSASignature(object): def test_unsupported_pss_mgf(self, backend): private_key = RSA_KEY_512.private_key(backend) with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_MGF): - private_key.signer( + private_key.sign( + b"msg", padding.PSS( mgf=DummyMGF(), salt_length=padding.PSS.MAX_LENGTH @@ -427,13 +585,12 @@ class TestRSASignature(object): ) def test_pkcs1_digest_too_large_for_key_size(self, backend): private_key = RSA_KEY_599.private_key(backend) - signer = private_key.signer( - padding.PKCS1v15(), - hashes.SHA512() - ) - signer.update(b"failure coming") with pytest.raises(ValueError): - signer.finalize() + private_key.sign( + b"failure coming", + padding.PKCS1v15(), + hashes.SHA512() + ) @pytest.mark.supported( only_if=lambda backend: backend.rsa_padding_supported( @@ -443,12 +600,104 @@ class TestRSASignature(object): ) def test_pkcs1_minimum_key_size(self, backend): private_key = RSA_KEY_745.private_key(backend) - signer = private_key.signer( + private_key.sign( + b"no failure", padding.PKCS1v15(), hashes.SHA512() ) - signer.update(b"no failure") - signer.finalize() + + def test_sign(self, backend): + private_key = RSA_KEY_512.private_key(backend) + message = b"one little message" + pkcs = padding.PKCS1v15() + algorithm = hashes.SHA1() + signature = private_key.sign(message, pkcs, algorithm) + public_key = private_key.public_key() + public_key.verify(signature, message, pkcs, algorithm) + + @pytest.mark.supported( + only_if=lambda backend: backend.rsa_padding_supported( + padding.PSS(mgf=padding.MGF1(hashes.SHA1()), salt_length=0) + ), + skip_message="Does not support PSS." + ) + def test_prehashed_sign(self, backend): + private_key = RSA_KEY_512.private_key(backend) + message = b"one little message" + h = hashes.Hash(hashes.SHA1(), backend) + h.update(message) + digest = h.finalize() + pss = padding.PSS(mgf=padding.MGF1(hashes.SHA1()), salt_length=0) + prehashed_alg = asym_utils.Prehashed(hashes.SHA1()) + signature = private_key.sign(digest, pss, prehashed_alg) + public_key = private_key.public_key() + public_key.verify(signature, message, pss, hashes.SHA1()) + + @pytest.mark.supported( + only_if=lambda backend: backend.hash_supported( + hashes.BLAKE2s(digest_size=32)), + skip_message="Does not support BLAKE2s", + ) + @pytest.mark.supported( + only_if=lambda backend: backend.rsa_padding_supported( + padding.PSS(mgf=padding.MGF1(hashes.SHA1()), salt_length=0) + ), + skip_message="Does not support PSS." + ) + def test_unsupported_hash(self, backend): + private_key = RSA_KEY_512.private_key(backend) + message = b"one little message" + pss = padding.PSS(mgf=padding.MGF1(hashes.SHA256()), salt_length=0) + with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_HASH): + private_key.sign(message, pss, hashes.BLAKE2s(32)) + + @pytest.mark.supported( + only_if=lambda backend: backend.rsa_padding_supported( + padding.PSS(mgf=padding.MGF1(hashes.SHA1()), salt_length=0) + ), + skip_message="Does not support PSS." + ) + def test_prehashed_digest_mismatch(self, backend): + private_key = RSA_KEY_512.private_key(backend) + message = b"one little message" + h = hashes.Hash(hashes.SHA512(), backend) + h.update(message) + digest = h.finalize() + pss = padding.PSS(mgf=padding.MGF1(hashes.SHA1()), salt_length=0) + prehashed_alg = asym_utils.Prehashed(hashes.SHA1()) + with pytest.raises(ValueError): + private_key.sign(digest, pss, prehashed_alg) + + @pytest.mark.supported( + only_if=lambda backend: backend.rsa_padding_supported( + padding.PKCS1v15() + ), + skip_message="Does not support PKCS1v1.5." + ) + def test_prehashed_unsupported_in_signer_ctx(self, backend): + private_key = RSA_KEY_512.private_key(backend) + with pytest.raises(TypeError), \ + pytest.warns(CryptographyDeprecationWarning): + private_key.signer( + padding.PKCS1v15(), + asym_utils.Prehashed(hashes.SHA1()) + ) + + @pytest.mark.supported( + only_if=lambda backend: backend.rsa_padding_supported( + padding.PKCS1v15() + ), + skip_message="Does not support PKCS1v1.5." + ) + def test_prehashed_unsupported_in_verifier_ctx(self, backend): + public_key = RSA_KEY_512.private_key(backend).public_key() + with pytest.raises(TypeError), \ + pytest.warns(CryptographyDeprecationWarning): + public_key.verifier( + b"0" * 64, + padding.PKCS1v15(), + asym_utils.Prehashed(hashes.SHA1()) + ) @pytest.mark.requires_backend_interface(interface=RSABackend) @@ -473,13 +722,12 @@ class TestRSAVerification(object): e=public["public_exponent"], n=public["modulus"] ).public_key(backend) - verifier = public_key.verifier( + public_key.verify( binascii.unhexlify(example["signature"]), + binascii.unhexlify(example["message"]), padding.PKCS1v15(), hashes.SHA1() ) - verifier.update(binascii.unhexlify(example["message"])) - verifier.verify() @pytest.mark.supported( only_if=lambda backend: backend.rsa_padding_supported( @@ -490,17 +738,51 @@ class TestRSAVerification(object): def test_invalid_pkcs1v15_signature_wrong_data(self, backend): private_key = RSA_KEY_512.private_key(backend) public_key = private_key.public_key() - signer = private_key.signer(padding.PKCS1v15(), hashes.SHA1()) - signer.update(b"sign me") - signature = signer.finalize() - verifier = public_key.verifier( - signature, - padding.PKCS1v15(), - hashes.SHA1() + signature = private_key.sign( + b"sign me", padding.PKCS1v15(), hashes.SHA1() ) - verifier.update(b"incorrect data") with pytest.raises(InvalidSignature): - verifier.verify() + public_key.verify( + signature, + b"incorrect data", + padding.PKCS1v15(), + hashes.SHA1() + ) + + def test_invalid_signature_sequence_removed(self, backend): + """ + This test comes from wycheproof + """ + key_der = binascii.unhexlify( + b"30820122300d06092a864886f70d01010105000382010f003082010a02820101" + b"00a2b451a07d0aa5f96e455671513550514a8a5b462ebef717094fa1fee82224" + b"e637f9746d3f7cafd31878d80325b6ef5a1700f65903b469429e89d6eac88450" + b"97b5ab393189db92512ed8a7711a1253facd20f79c15e8247f3d3e42e46e48c9" + b"8e254a2fe9765313a03eff8f17e1a029397a1fa26a8dce26f490ed81299615d9" + b"814c22da610428e09c7d9658594266f5c021d0fceca08d945a12be82de4d1ece" + b"6b4c03145b5d3495d4ed5411eb878daf05fd7afc3e09ada0f1126422f590975a" + b"1969816f48698bcbba1b4d9cae79d460d8f9f85e7975005d9bc22c4e5ac0f7c1" + b"a45d12569a62807d3b9a02e5a530e773066f453d1f5b4c2e9cf7820283f742b9" + b"d50203010001" + ) + sig = binascii.unhexlify( + b"498209f59a0679a1f926eccf3056da2cba553d7ab3064e7c41ad1d739f038249" + b"f02f5ad12ee246073d101bc3cdb563e8b6be61562056422b7e6c16ad53deb12a" + b"f5de744197753a35859833f41bb59c6597f3980132b7478fd0b95fd27dfad64a" + b"20fd5c25312bbd41a85286cd2a83c8df5efa0779158d01b0747ff165b055eb28" + b"80ea27095700a295593196d8c5922cf6aa9d7e29b5056db5ded5eb20aeb31b89" + b"42e26b15a5188a4934cd7e39cfe379a197f49a204343a493452deebca436ee61" + b"4f4daf989e355544489f7e69ffa8ccc6a1e81cf0ab33c3e6d7591091485a6a31" + b"bda3b33946490057b9a3003d3fd9daf7c4778b43fd46144d945d815f12628ff4" + ) + public_key = serialization.load_der_public_key(key_der, backend) + with pytest.raises(InvalidSignature): + public_key.verify( + sig, + binascii.unhexlify(b"313233343030"), + padding.PKCS1v15(), + hashes.SHA256() + ) @pytest.mark.supported( only_if=lambda backend: backend.rsa_padding_supported( @@ -512,17 +794,12 @@ class TestRSAVerification(object): private_key = RSA_KEY_512.private_key(backend) private_key2 = RSA_KEY_512_ALT.private_key(backend) public_key = private_key2.public_key() - signer = private_key.signer(padding.PKCS1v15(), hashes.SHA1()) - signer.update(b"sign me") - signature = signer.finalize() - verifier = public_key.verifier( - signature, - padding.PKCS1v15(), - hashes.SHA1() - ) - verifier.update(b"sign me") + msg = b"sign me" + signature = private_key.sign(msg, padding.PKCS1v15(), hashes.SHA1()) with pytest.raises(InvalidSignature): - verifier.verify() + public_key.verify( + signature, msg, padding.PKCS1v15(), hashes.SHA1() + ) @pytest.mark.supported( only_if=lambda backend: backend.rsa_padding_supported( @@ -547,16 +824,15 @@ class TestRSAVerification(object): e=public["public_exponent"], n=public["modulus"] ).public_key(backend) - verifier = public_key.verifier( + public_key.verify( binascii.unhexlify(example["signature"]), + binascii.unhexlify(example["message"]), padding.PSS( mgf=padding.MGF1(algorithm=hashes.SHA1()), salt_length=20 ), hashes.SHA1() ) - verifier.update(binascii.unhexlify(example["message"])) - verifier.verify() @pytest.mark.supported( only_if=lambda backend: backend.rsa_padding_supported( @@ -580,17 +856,16 @@ class TestRSAVerification(object): b"0e68c3649df91c5bc3665f96e157efa75b71934aaa514d91e94ca8418d100f45" b"6f05288e58525f99666bab052adcffdf7186eb40f583bd38d98c97d3d524808b" ) - verifier = public_key.verifier( - signature, - padding.PSS( - mgf=padding.MGF1(algorithm=hashes.SHA1()), - salt_length=padding.PSS.MAX_LENGTH - ), - hashes.SHA1() - ) - verifier.update(b"incorrect data") with pytest.raises(InvalidSignature): - verifier.verify() + public_key.verify( + signature, + b"incorrect data", + padding.PSS( + mgf=padding.MGF1(algorithm=hashes.SHA1()), + salt_length=padding.PSS.MAX_LENGTH + ), + hashes.SHA1() + ) @pytest.mark.supported( only_if=lambda backend: backend.rsa_padding_supported( @@ -616,17 +891,16 @@ class TestRSAVerification(object): ), e=65537 ).public_key(backend) - verifier = public_key.verifier( - signature, - padding.PSS( - mgf=padding.MGF1(algorithm=hashes.SHA1()), - salt_length=padding.PSS.MAX_LENGTH - ), - hashes.SHA1() - ) - verifier.update(b"sign me") with pytest.raises(InvalidSignature): - verifier.verify() + public_key.verify( + signature, + b"sign me", + padding.PSS( + mgf=padding.MGF1(algorithm=hashes.SHA1()), + salt_length=padding.PSS.MAX_LENGTH + ), + hashes.SHA1() + ) @pytest.mark.supported( only_if=lambda backend: backend.rsa_padding_supported( @@ -652,17 +926,16 @@ class TestRSAVerification(object): ), e=65537 ).public_key(backend) - verifier = public_key.verifier( - signature, - padding.PSS( - mgf=padding.MGF1(algorithm=hashes.SHA1()), - salt_length=padding.PSS.MAX_LENGTH - ), - hashes.SHA1() - ) - verifier.update(b"sign me") with pytest.raises(InvalidSignature): - verifier.verify() + public_key.verify( + signature, + b"sign me", + padding.PSS( + mgf=padding.MGF1(algorithm=hashes.SHA1()), + salt_length=padding.PSS.MAX_LENGTH + ), + hashes.SHA1() + ) @pytest.mark.supported( only_if=lambda backend: backend.rsa_padding_supported( @@ -673,15 +946,16 @@ class TestRSAVerification(object): def test_use_after_finalize(self, backend): private_key = RSA_KEY_512.private_key(backend) public_key = private_key.public_key() - signer = private_key.signer(padding.PKCS1v15(), hashes.SHA1()) - signer.update(b"sign me") - signature = signer.finalize() - - verifier = public_key.verifier( - signature, - padding.PKCS1v15(), - hashes.SHA1() + signature = private_key.sign( + b"sign me", padding.PKCS1v15(), hashes.SHA1() ) + + with pytest.warns(CryptographyDeprecationWarning): + verifier = public_key.verifier( + signature, + padding.PKCS1v15(), + hashes.SHA1() + ) verifier.update(b"sign me") verifier.verify() with pytest.raises(AlreadyFinalized): @@ -693,13 +967,33 @@ class TestRSAVerification(object): private_key = RSA_KEY_512.private_key(backend) public_key = private_key.public_key() with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_PADDING): - public_key.verifier(b"sig", DummyPadding(), hashes.SHA1()) + public_key.verify( + b"sig", b"msg", DummyAsymmetricPadding(), hashes.SHA1() + ) + + @pytest.mark.supported( + only_if=lambda backend: backend.rsa_padding_supported( + padding.PKCS1v15() + ), + skip_message="Does not support PKCS1v1.5." + ) + def test_signature_not_bytes(self, backend): + public_key = RSA_KEY_512.public_numbers.public_key(backend) + signature = 1234 + + with pytest.raises(TypeError), \ + pytest.warns(CryptographyDeprecationWarning): + public_key.verifier( + signature, + padding.PKCS1v15(), + hashes.SHA1() + ) def test_padding_incorrect_type(self, backend): private_key = RSA_KEY_512.private_key(backend) public_key = private_key.public_key() with pytest.raises(TypeError): - public_key.verifier(b"sig", "notpadding", hashes.SHA1()) + public_key.verify(b"sig", b"msg", "notpadding", hashes.SHA1()) @pytest.mark.supported( only_if=lambda backend: backend.rsa_padding_supported( @@ -711,8 +1005,9 @@ class TestRSAVerification(object): private_key = RSA_KEY_512.private_key(backend) public_key = private_key.public_key() with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_MGF): - public_key.verifier( + public_key.verify( b"sig", + b"msg", padding.PSS( mgf=DummyMGF(), salt_length=padding.PSS.MAX_LENGTH @@ -741,8 +1036,9 @@ class TestRSAVerification(object): ) public_key = private_key.public_key() with pytest.raises(ValueError): - public_key.verifier( + public_key.verify( signature, + b"msg doesn't matter", padding.PSS( mgf=padding.MGF1(algorithm=hashes.SHA1()), salt_length=padding.PSS.MAX_LENGTH @@ -772,19 +1068,50 @@ class TestRSAVerification(object): ), e=65537 ).public_key(backend) - verifier = public_key.verifier( - signature, - padding.PSS( - mgf=padding.MGF1( - algorithm=hashes.SHA1(), - ), - salt_length=1000000 - ), - hashes.SHA1() - ) - verifier.update(b"sign me") with pytest.raises(InvalidSignature): - verifier.verify() + public_key.verify( + signature, + b"sign me", + padding.PSS( + mgf=padding.MGF1( + algorithm=hashes.SHA1(), + ), + salt_length=1000000 + ), + hashes.SHA1() + ) + + def test_verify(self, backend): + private_key = RSA_KEY_512.private_key(backend) + message = b"one little message" + pkcs = padding.PKCS1v15() + algorithm = hashes.SHA1() + signature = private_key.sign(message, pkcs, algorithm) + public_key = private_key.public_key() + public_key.verify(signature, message, pkcs, algorithm) + + def test_prehashed_verify(self, backend): + private_key = RSA_KEY_512.private_key(backend) + message = b"one little message" + h = hashes.Hash(hashes.SHA1(), backend) + h.update(message) + digest = h.finalize() + prehashed_alg = asym_utils.Prehashed(hashes.SHA1()) + pkcs = padding.PKCS1v15() + signature = private_key.sign(message, pkcs, hashes.SHA1()) + public_key = private_key.public_key() + public_key.verify(signature, digest, pkcs, prehashed_alg) + + def test_prehashed_digest_mismatch(self, backend): + public_key = RSA_KEY_512.private_key(backend).public_key() + message = b"one little message" + h = hashes.Hash(hashes.SHA1(), backend) + h.update(message) + data = h.finalize() + prehashed_alg = asym_utils.Prehashed(hashes.SHA512()) + pkcs = padding.PKCS1v15() + with pytest.raises(ValueError): + public_key.verify(b"\x00" * 64, data, pkcs, prehashed_alg) @pytest.mark.requires_backend_interface(interface=RSABackend) @@ -1009,6 +1336,10 @@ class TestRSAPKCS1Verification(object): class TestPSS(object): + def test_calculate_max_pss_salt_length(self): + with pytest.raises(TypeError): + padding.calculate_max_pss_salt_length(object(), hashes.SHA256()) + def test_invalid_salt_length_not_integer(self): with pytest.raises(TypeError): padding.PSS( @@ -1096,14 +1427,14 @@ class TestRSADecryption(object): ) ).private_key(backend) ciphertext = binascii.unhexlify(example["encryption"]) - assert len(ciphertext) == math.ceil(skey.key_size / 8.0) + assert len(ciphertext) == (skey.key_size + 7) // 8 message = skey.decrypt(ciphertext, padding.PKCS1v15()) assert message == binascii.unhexlify(example["message"]) def test_unsupported_padding(self, backend): private_key = RSA_KEY_512.private_key(backend) with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_PADDING): - private_key.decrypt(b"0" * 64, DummyPadding()) + private_key.decrypt(b"0" * 64, DummyAsymmetricPadding()) @pytest.mark.supported( only_if=lambda backend: backend.rsa_padding_supported( @@ -1193,6 +1524,119 @@ class TestRSADecryption(object): ) assert message == binascii.unhexlify(example["message"]) + @pytest.mark.supported( + only_if=lambda backend: backend.rsa_padding_supported( + padding.OAEP( + mgf=padding.MGF1(algorithm=hashes.SHA224()), + algorithm=hashes.SHA224(), + label=None + ) + ), + skip_message="Does not support OAEP using SHA224 MGF1 and SHA224 hash." + ) + @pytest.mark.parametrize( + "vector", + _build_oaep_sha2_vectors() + ) + def test_decrypt_oaep_sha2_vectors(self, vector, backend): + private, public, example, mgf1_alg, hash_alg = vector + skey = rsa.RSAPrivateNumbers( + p=private["p"], + q=private["q"], + d=private["private_exponent"], + dmp1=private["dmp1"], + dmq1=private["dmq1"], + iqmp=private["iqmp"], + public_numbers=rsa.RSAPublicNumbers( + e=private["public_exponent"], + n=private["modulus"] + ) + ).private_key(backend) + message = skey.decrypt( + binascii.unhexlify(example["encryption"]), + padding.OAEP( + mgf=padding.MGF1(algorithm=mgf1_alg), + algorithm=hash_alg, + label=None + ) + ) + assert message == binascii.unhexlify(example["message"]) + + @pytest.mark.supported( + only_if=lambda backend: backend.rsa_padding_supported( + padding.OAEP( + mgf=padding.MGF1(algorithm=hashes.SHA1()), + algorithm=hashes.SHA1(), + label=None + ) + ), + skip_message="Does not support OAEP." + ) + def test_invalid_oaep_decryption(self, backend): + # More recent versions of OpenSSL may raise RSA_R_OAEP_DECODING_ERROR + # This test triggers it and confirms that we properly handle it. Other + # backends should also return the proper ValueError. + private_key = RSA_KEY_512.private_key(backend) + + ciphertext = private_key.public_key().encrypt( + b'secure data', + padding.OAEP( + mgf=padding.MGF1(algorithm=hashes.SHA1()), + algorithm=hashes.SHA1(), + label=None + ) + ) + + private_key_alt = RSA_KEY_512_ALT.private_key(backend) + + with pytest.raises(ValueError): + private_key_alt.decrypt( + ciphertext, + padding.OAEP( + mgf=padding.MGF1(algorithm=hashes.SHA1()), + algorithm=hashes.SHA1(), + label=None + ) + ) + + @pytest.mark.supported( + only_if=lambda backend: backend.rsa_padding_supported( + padding.OAEP( + mgf=padding.MGF1(algorithm=hashes.SHA1()), + algorithm=hashes.SHA1(), + label=None + ) + ), + skip_message="Does not support OAEP." + ) + def test_invalid_oaep_decryption_data_to_large_for_modulus(self, backend): + key = RSA_KEY_2048_ALT.private_key(backend) + + ciphertext = ( + b'\xb1ph\xc0\x0b\x1a|\xe6\xda\xea\xb5\xd7%\x94\x07\xf96\xfb\x96' + b'\x11\x9b\xdc4\xea.-\x91\x80\x13S\x94\x04m\xe9\xc5/F\x1b\x9b:\\' + b'\x1d\x04\x16ML\xae\xb32J\x01yuA\xbb\x83\x1c\x8f\xf6\xa5\xdbp\xcd' + b'\nx\xc7\xf6\x15\xb2/\xdcH\xae\xe7\x13\x13by\r4t\x99\x0fc\x1f\xc1' + b'\x1c\xb1\xdd\xc5\x08\xd1\xee\xa1XQ\xb8H@L5v\xc3\xaf\xf2\r\x97' + b'\xed\xaa\xe7\xf1\xd4xai\xd3\x83\xd9\xaa9\xbfx\xe1\x87F \x01\xff' + b'L\xccv}ae\xb3\xfa\xf2B\xb8\xf9\x04H\x94\x85\xcb\x86\xbb\\ghx!W31' + b'\xc7;t\na_E\xc2\x16\xb0;\xa1\x18\t\x1b\xe1\xdb\x80>)\x15\xc6\x12' + b'\xcb\xeeg`\x8b\x9b\x1b\x05y4\xb0\x84M6\xcd\xa1\x827o\xfd\x96\xba' + b'Z#\x8d\xae\x01\xc9\xf2\xb6\xde\x89{8&eQ\x1e8\x03\x01#?\xb66\\' + b'\xad.\xe9\xfa!\x95 c{\xcaz\xe0*\tP\r\x91\x9a)B\xb5\xadN\xf4$\x83' + b'\t\xb5u\xab\x19\x99' + ) + + with pytest.raises(ValueError): + key.decrypt( + ciphertext, + padding.OAEP( + algorithm=hashes.SHA1(), + mgf=padding.MGF1(hashes.SHA1()), + label=None + ) + ) + def test_unsupported_oaep_mgf(self, backend): private_key = RSA_KEY_512.private_key(backend) with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_MGF): @@ -1239,7 +1683,48 @@ class TestRSAEncryption(object): public_key = private_key.public_key() ct = public_key.encrypt(pt, pad) assert ct != pt - assert len(ct) == math.ceil(public_key.key_size / 8.0) + assert len(ct) == (public_key.key_size + 7) // 8 + recovered_pt = private_key.decrypt(ct, pad) + assert recovered_pt == pt + + @pytest.mark.supported( + only_if=lambda backend: backend.rsa_padding_supported( + padding.OAEP( + mgf=padding.MGF1(algorithm=hashes.SHA256()), + algorithm=hashes.SHA512(), + label=None + ) + ), + skip_message="Does not support OAEP using SHA256 MGF1 and SHA512 hash." + ) + @pytest.mark.parametrize( + ("mgf1hash", "oaephash"), + itertools.product([ + hashes.SHA1(), + hashes.SHA224(), + hashes.SHA256(), + hashes.SHA384(), + hashes.SHA512(), + ], [ + hashes.SHA1(), + hashes.SHA224(), + hashes.SHA256(), + hashes.SHA384(), + hashes.SHA512(), + ]) + ) + def test_rsa_encrypt_oaep_sha2(self, mgf1hash, oaephash, backend): + pad = padding.OAEP( + mgf=padding.MGF1(algorithm=mgf1hash), + algorithm=oaephash, + label=None + ) + private_key = RSA_KEY_2048.private_key(backend) + pt = b"encrypt me using sha2 hashes!" + public_key = private_key.public_key() + ct = public_key.encrypt(pt, pad) + assert ct != pt + assert len(ct) == (public_key.key_size + 7) // 8 recovered_pt = private_key.decrypt(ct, pad) assert recovered_pt == pt @@ -1264,7 +1749,7 @@ class TestRSAEncryption(object): public_key = private_key.public_key() ct = public_key.encrypt(pt, pad) assert ct != pt - assert len(ct) == math.ceil(public_key.key_size / 8.0) + assert len(ct) == (public_key.key_size + 7) // 8 recovered_pt = private_key.decrypt(ct, pad) assert recovered_pt == pt @@ -1306,7 +1791,7 @@ class TestRSAEncryption(object): public_key = private_key.public_key() with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_PADDING): - public_key.encrypt(b"somedata", DummyPadding()) + public_key.encrypt(b"somedata", DummyAsymmetricPadding()) with pytest.raises(TypeError): public_key.encrypt(b"somedata", padding=object()) @@ -1367,304 +1852,81 @@ class TestRSANumbers(object): with pytest.raises(TypeError): rsa.RSAPublicNumbers(e=1, n=None) - def test_private_numbers_invalid_types(self): - public_numbers = rsa.RSAPublicNumbers(e=1, n=15) - - with pytest.raises(TypeError): - rsa.RSAPrivateNumbers( - p=None, - q=5, - d=1, - dmp1=1, - dmq1=1, - iqmp=2, - public_numbers=public_numbers - ) - - with pytest.raises(TypeError): - rsa.RSAPrivateNumbers( - p=3, - q=None, - d=1, - dmp1=1, - dmq1=1, - iqmp=2, - public_numbers=public_numbers - ) - - with pytest.raises(TypeError): - rsa.RSAPrivateNumbers( - p=3, - q=5, - d=None, - dmp1=1, - dmq1=1, - iqmp=2, - public_numbers=public_numbers - ) - - with pytest.raises(TypeError): - rsa.RSAPrivateNumbers( - p=3, - q=5, - d=1, - dmp1=None, - dmq1=1, - iqmp=2, - public_numbers=public_numbers - ) - - with pytest.raises(TypeError): - rsa.RSAPrivateNumbers( - p=3, - q=5, - d=1, - dmp1=1, - dmq1=None, - iqmp=2, - public_numbers=public_numbers - ) - + @pytest.mark.parametrize( + ("p", "q", "d", "dmp1", "dmq1", "iqmp", "public_numbers"), + [ + (None, 5, 1, 1, 1, 2, rsa.RSAPublicNumbers(e=1, n=15)), + (3, None, 1, 1, 1, 2, rsa.RSAPublicNumbers(e=1, n=15)), + (3, 5, None, 1, 1, 2, rsa.RSAPublicNumbers(e=1, n=15)), + (3, 5, 1, None, 1, 2, rsa.RSAPublicNumbers(e=1, n=15)), + (3, 5, 1, 1, None, 2, rsa.RSAPublicNumbers(e=1, n=15)), + (3, 5, 1, 1, 1, None, rsa.RSAPublicNumbers(e=1, n=15)), + (3, 5, 1, 1, 1, 2, None), + ] + ) + def test_private_numbers_invalid_types(self, p, q, d, dmp1, dmq1, iqmp, + public_numbers): with pytest.raises(TypeError): rsa.RSAPrivateNumbers( - p=3, - q=5, - d=1, - dmp1=1, - dmq1=1, - iqmp=None, + p=p, q=q, + d=d, + dmp1=dmp1, + dmq1=dmq1, + iqmp=iqmp, public_numbers=public_numbers ) - with pytest.raises(TypeError): - rsa.RSAPrivateNumbers( - p=3, - q=5, - d=1, - dmp1=1, - dmq1=1, - iqmp=2, - public_numbers=None - ) - - def test_invalid_public_numbers_argument_values(self, backend): + @pytest.mark.parametrize( + ("e", "n"), + [ + (7, 2), # modulus < 3 + (1, 15), # public_exponent < 3 + (17, 15), # public_exponent > modulus + (14, 15), # public_exponent not odd + ] + ) + def test_invalid_public_numbers_argument_values(self, e, n, backend): # Start with public_exponent=7, modulus=15. Then change one value at a # time to test the bounds. - # Test a modulus < 3. - - with pytest.raises(ValueError): - rsa.RSAPublicNumbers(e=7, n=2).public_key(backend) - - # Test a public_exponent < 3 with pytest.raises(ValueError): - rsa.RSAPublicNumbers(e=1, n=15).public_key(backend) + rsa.RSAPublicNumbers(e=e, n=n).public_key(backend) - # Test a public_exponent > modulus - with pytest.raises(ValueError): - rsa.RSAPublicNumbers(e=17, n=15).public_key(backend) - - # Test a public_exponent that is not odd. - with pytest.raises(ValueError): - rsa.RSAPublicNumbers(e=14, n=15).public_key(backend) - - def test_invalid_private_numbers_argument_values(self, backend): + @pytest.mark.parametrize( + ("p", "q", "d", "dmp1", "dmq1", "iqmp", "e", "n"), + [ + (3, 11, 3, 1, 3, 2, 7, 2), # modulus < 3 + (3, 11, 3, 1, 3, 2, 7, 35), # modulus != p * q + (37, 11, 3, 1, 3, 2, 7, 33), # p > modulus + (3, 37, 3, 1, 3, 2, 7, 33), # q > modulus + (3, 11, 3, 35, 3, 2, 7, 33), # dmp1 > modulus + (3, 11, 3, 1, 35, 2, 7, 33), # dmq1 > modulus + (3, 11, 3, 1, 3, 35, 7, 33), # iqmp > modulus + (3, 11, 37, 1, 3, 2, 7, 33), # d > modulus + (3, 11, 3, 1, 3, 2, 1, 33), # public_exponent < 3 + (3, 11, 3, 1, 3, 35, 65537, 33), # public_exponent > modulus + (3, 11, 3, 1, 3, 2, 6, 33), # public_exponent is not odd + (3, 11, 3, 2, 3, 2, 7, 33), # dmp1 is not odd + (3, 11, 3, 1, 4, 2, 7, 33), # dmq1 is not odd + ] + ) + def test_invalid_private_numbers_argument_values(self, p, q, d, dmp1, dmq1, + iqmp, e, n, backend): # Start with p=3, q=11, private_exponent=3, public_exponent=7, # modulus=33, dmp1=1, dmq1=3, iqmp=2. Then change one value at # a time to test the bounds. - # Test a modulus < 3. - with pytest.raises(ValueError): - rsa.RSAPrivateNumbers( - p=3, - q=11, - d=3, - dmp1=1, - dmq1=3, - iqmp=2, - public_numbers=rsa.RSAPublicNumbers( - e=7, - n=2 - ) - ).private_key(backend) - - # Test a modulus != p * q. - with pytest.raises(ValueError): - rsa.RSAPrivateNumbers( - p=3, - q=11, - d=3, - dmp1=1, - dmq1=3, - iqmp=2, - public_numbers=rsa.RSAPublicNumbers( - e=7, - n=35 - ) - ).private_key(backend) - - # Test a p > modulus. - with pytest.raises(ValueError): - rsa.RSAPrivateNumbers( - p=37, - q=11, - d=3, - dmp1=1, - dmq1=3, - iqmp=2, - public_numbers=rsa.RSAPublicNumbers( - e=7, - n=33 - ) - ).private_key(backend) - - # Test a q > modulus. - with pytest.raises(ValueError): - rsa.RSAPrivateNumbers( - p=3, - q=37, - d=3, - dmp1=1, - dmq1=3, - iqmp=2, - public_numbers=rsa.RSAPublicNumbers( - e=7, - n=33 - ) - ).private_key(backend) - - # Test a dmp1 > modulus. - with pytest.raises(ValueError): - rsa.RSAPrivateNumbers( - p=3, - q=11, - d=3, - dmp1=35, - dmq1=3, - iqmp=2, - public_numbers=rsa.RSAPublicNumbers( - e=7, - n=33 - ) - ).private_key(backend) - - # Test a dmq1 > modulus. - with pytest.raises(ValueError): - rsa.RSAPrivateNumbers( - p=3, - q=11, - d=3, - dmp1=1, - dmq1=35, - iqmp=2, - public_numbers=rsa.RSAPublicNumbers( - e=7, - n=33 - ) - ).private_key(backend) - - # Test an iqmp > modulus. - with pytest.raises(ValueError): - rsa.RSAPrivateNumbers( - p=3, - q=11, - d=3, - dmp1=1, - dmq1=3, - iqmp=35, - public_numbers=rsa.RSAPublicNumbers( - e=7, - n=33 - ) - ).private_key(backend) - - # Test a private_exponent > modulus - with pytest.raises(ValueError): - rsa.RSAPrivateNumbers( - p=3, - q=11, - d=37, - dmp1=1, - dmq1=3, - iqmp=2, - public_numbers=rsa.RSAPublicNumbers( - e=7, - n=33 - ) - ).private_key(backend) - - # Test a public_exponent < 3 - with pytest.raises(ValueError): - rsa.RSAPrivateNumbers( - p=3, - q=11, - d=3, - dmp1=1, - dmq1=3, - iqmp=2, - public_numbers=rsa.RSAPublicNumbers( - e=1, - n=33 - ) - ).private_key(backend) - - # Test a public_exponent > modulus with pytest.raises(ValueError): rsa.RSAPrivateNumbers( - p=3, - q=11, - d=3, - dmp1=1, - dmq1=3, - iqmp=35, + p=p, + q=q, + d=d, + dmp1=dmp1, + dmq1=dmq1, + iqmp=iqmp, public_numbers=rsa.RSAPublicNumbers( - e=65537, - n=33 - ) - ).private_key(backend) - - # Test a public_exponent that is not odd. - with pytest.raises(ValueError): - rsa.RSAPrivateNumbers( - p=3, - q=11, - d=3, - dmp1=1, - dmq1=3, - iqmp=2, - public_numbers=rsa.RSAPublicNumbers( - e=6, - n=33 - ) - ).private_key(backend) - - # Test a dmp1 that is not odd. - with pytest.raises(ValueError): - rsa.RSAPrivateNumbers( - p=3, - q=11, - d=3, - dmp1=2, - dmq1=3, - iqmp=2, - public_numbers=rsa.RSAPublicNumbers( - e=7, - n=33 - ) - ).private_key(backend) - - # Test a dmq1 that is not odd. - with pytest.raises(ValueError): - rsa.RSAPrivateNumbers( - p=3, - q=11, - d=3, - dmp1=1, - dmq1=4, - iqmp=2, - public_numbers=rsa.RSAPublicNumbers( - e=7, - n=33 + e=e, + n=n ) ).private_key(backend) @@ -1721,6 +1983,22 @@ class TestRSANumbersEquality(object): ) assert num != object() + def test_public_numbers_hash(self): + pub1 = RSAPublicNumbers(3, 17) + pub2 = RSAPublicNumbers(3, 17) + pub3 = RSAPublicNumbers(7, 21) + + assert hash(pub1) == hash(pub2) + assert hash(pub1) != hash(pub3) + + def test_private_numbers_hash(self): + priv1 = RSAPrivateNumbers(1, 2, 3, 4, 5, 6, RSAPublicNumbers(1, 2)) + priv2 = RSAPrivateNumbers(1, 2, 3, 4, 5, 6, RSAPublicNumbers(1, 2)) + priv3 = RSAPrivateNumbers(1, 2, 3, 4, 5, 6, RSAPublicNumbers(1, 3)) + + assert hash(priv1) == hash(priv2) + assert hash(priv1) != hash(priv3) + class TestRSAPrimeFactorRecovery(object): @pytest.mark.parametrize( @@ -1739,10 +2017,11 @@ class TestRSAPrimeFactorRecovery(object): private["private_exponent"] ) # Unfortunately there is no convention on which prime should be p - # and which one q. The function we use always makes p < q, but the - # NIST vectors are not so consistent. Accordingly we verify we've + # and which one q. The function we use always makes p > q, but the + # NIST vectors are not so consistent. Accordingly, we verify we've # recovered the proper (p, q) by sorting them and asserting on that. assert sorted([p, q]) == sorted([private["p"], private["q"]]) + assert p > q def test_invalid_recover_prime_factors(self): with pytest.raises(ValueError): @@ -1769,7 +2048,6 @@ class TestRSAPrivateKeySerialization(object): ) def test_private_bytes_encrypted_pem(self, backend, fmt, password): key = RSA_KEY_2048.private_key(backend) - _skip_if_no_serialization(key, backend) serialized = key.private_bytes( serialization.Encoding.PEM, fmt, @@ -1783,6 +2061,20 @@ class TestRSAPrivateKeySerialization(object): assert loaded_priv_num == priv_num @pytest.mark.parametrize( + ("encoding", "fmt"), + [ + (serialization.Encoding.Raw, serialization.PrivateFormat.PKCS8), + (serialization.Encoding.DER, serialization.PrivateFormat.Raw), + (serialization.Encoding.Raw, serialization.PrivateFormat.Raw), + (serialization.Encoding.X962, serialization.PrivateFormat.PKCS8), + ] + ) + def test_private_bytes_rejects_invalid(self, encoding, fmt, backend): + key = RSA_KEY_2048.private_key(backend) + with pytest.raises(ValueError): + key.private_bytes(encoding, fmt, serialization.NoEncryption()) + + @pytest.mark.parametrize( ("fmt", "password"), [ [serialization.PrivateFormat.PKCS8, b"s"], @@ -1793,7 +2085,6 @@ class TestRSAPrivateKeySerialization(object): ) def test_private_bytes_encrypted_der(self, backend, fmt, password): key = RSA_KEY_2048.private_key(backend) - _skip_if_no_serialization(key, backend) serialized = key.private_bytes( serialization.Encoding.DER, fmt, @@ -1834,7 +2125,6 @@ class TestRSAPrivateKeySerialization(object): def test_private_bytes_unencrypted(self, backend, encoding, fmt, loader_func): key = RSA_KEY_2048.private_key(backend) - _skip_if_no_serialization(key, backend) serialized = key.private_bytes( encoding, fmt, serialization.NoEncryption() ) @@ -1878,7 +2168,6 @@ class TestRSAPrivateKeySerialization(object): def test_private_bytes_traditional_der_encrypted_invalid(self, backend): key = RSA_KEY_2048.private_key(backend) - _skip_if_no_serialization(key, backend) with pytest.raises(ValueError): key.private_bytes( serialization.Encoding.DER, @@ -1888,7 +2177,6 @@ class TestRSAPrivateKeySerialization(object): def test_private_bytes_invalid_encoding(self, backend): key = RSA_KEY_2048.private_key(backend) - _skip_if_no_serialization(key, backend) with pytest.raises(TypeError): key.private_bytes( "notencoding", @@ -1898,7 +2186,6 @@ class TestRSAPrivateKeySerialization(object): def test_private_bytes_invalid_format(self, backend): key = RSA_KEY_2048.private_key(backend) - _skip_if_no_serialization(key, backend) with pytest.raises(TypeError): key.private_bytes( serialization.Encoding.PEM, @@ -1908,7 +2195,6 @@ class TestRSAPrivateKeySerialization(object): def test_private_bytes_invalid_encryption_algorithm(self, backend): key = RSA_KEY_2048.private_key(backend) - _skip_if_no_serialization(key, backend) with pytest.raises(TypeError): key.private_bytes( serialization.Encoding.PEM, @@ -1918,12 +2204,11 @@ class TestRSAPrivateKeySerialization(object): def test_private_bytes_unsupported_encryption_type(self, backend): key = RSA_KEY_2048.private_key(backend) - _skip_if_no_serialization(key, backend) with pytest.raises(ValueError): key.private_bytes( serialization.Encoding.PEM, serialization.PrivateFormat.TraditionalOpenSSL, - DummyKeyEncryption() + DummyKeySerializationEncryption() ) @@ -1966,18 +2251,78 @@ class TestRSAPEMPublicKeySerialization(object): key_path, lambda pemfile: pemfile.read(), mode="rb" ) key = loader_func(key_bytes, backend) - _skip_if_no_serialization(key, backend) serialized = key.public_bytes(encoding, format) assert serialized == key_bytes + def test_public_bytes_openssh(self, backend): + key_bytes = load_vectors_from_file( + os.path.join("asymmetric", "public", "PKCS1", "rsa.pub.pem"), + lambda pemfile: pemfile.read(), mode="rb" + ) + key = serialization.load_pem_public_key(key_bytes, backend) + + ssh_bytes = key.public_bytes( + serialization.Encoding.OpenSSH, serialization.PublicFormat.OpenSSH + ) + assert ssh_bytes == ( + b"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQC7JHoJfg6yNzLMOWet8Z49a4KD" + b"0dCspMAYvo2YAMB7/wdEycocujbhJ2n/seONi+5XqTqqFkM5VBl8rmkkFPZk/7x0" + b"xmdsTPECSWnHK+HhoaNDFPR3j8jQhVo1laxiqcEhAHegi5cwtFosuJAvSKAFKEvy" + b"D43si00DQnXWrYHAEQ==" + ) + + with pytest.raises(ValueError): + key.public_bytes( + serialization.Encoding.PEM, serialization.PublicFormat.OpenSSH + ) + with pytest.raises(ValueError): + key.public_bytes( + serialization.Encoding.DER, serialization.PublicFormat.OpenSSH + ) + with pytest.raises(ValueError): + key.public_bytes( + serialization.Encoding.OpenSSH, + serialization.PublicFormat.PKCS1, + ) + with pytest.raises(ValueError): + key.public_bytes( + serialization.Encoding.OpenSSH, + serialization.PublicFormat.SubjectPublicKeyInfo, + ) + def test_public_bytes_invalid_encoding(self, backend): key = RSA_KEY_2048.private_key(backend).public_key() - _skip_if_no_serialization(key, backend) with pytest.raises(TypeError): key.public_bytes("notencoding", serialization.PublicFormat.PKCS1) def test_public_bytes_invalid_format(self, backend): key = RSA_KEY_2048.private_key(backend).public_key() - _skip_if_no_serialization(key, backend) with pytest.raises(TypeError): key.public_bytes(serialization.Encoding.PEM, "invalidformat") + + @pytest.mark.parametrize( + ("encoding", "fmt"), + [ + ( + serialization.Encoding.Raw, + serialization.PublicFormat.SubjectPublicKeyInfo + ), + (serialization.Encoding.Raw, serialization.PublicFormat.PKCS1), + ] + list(itertools.product( + [ + serialization.Encoding.Raw, + serialization.Encoding.X962, + serialization.Encoding.PEM, + serialization.Encoding.DER + ], + [ + serialization.PublicFormat.Raw, + serialization.PublicFormat.UncompressedPoint, + serialization.PublicFormat.CompressedPoint + ] + )) + ) + def test_public_bytes_rejects_invalid(self, encoding, fmt, backend): + key = RSA_KEY_2048.private_key(backend).public_key() + with pytest.raises(ValueError): + key.public_bytes(encoding, fmt) diff --git a/tests/hazmat/primitives/test_scrypt.py b/tests/hazmat/primitives/test_scrypt.py new file mode 100644 index 00000000..8f3a14ed --- /dev/null +++ b/tests/hazmat/primitives/test_scrypt.py @@ -0,0 +1,183 @@ +# 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 binascii +import os + +import pytest + +from cryptography.exceptions import ( + AlreadyFinalized, InvalidKey, UnsupportedAlgorithm +) +from cryptography.hazmat.backends.interfaces import ScryptBackend +from cryptography.hazmat.primitives.kdf.scrypt import Scrypt, _MEM_LIMIT + +from tests.utils import load_nist_vectors, load_vectors_from_file + +vectors = load_vectors_from_file( + os.path.join("KDF", "scrypt.txt"), load_nist_vectors) + + +def _skip_if_memory_limited(memory_limit, params): + # Memory calc adapted from OpenSSL (URL split over 2 lines, thanks PEP8) + # https://github.com/openssl/openssl/blob/6286757141a8c6e14d647ec733634a + # e0c83d9887/crypto/evp/scrypt.c#L189-L221 + blen = int(params["p"]) * 128 * int(params["r"]) + vlen = 32 * int(params["r"]) * (int(params["n"]) + 2) * 4 + memory_required = blen + vlen + if memory_limit < memory_required: + pytest.skip("Test exceeds Scrypt memory limit. " + "This is likely a 32-bit platform.") + + +def test_memory_limit_skip(): + with pytest.raises(pytest.skip.Exception): + _skip_if_memory_limited(1000, {"p": 16, "r": 64, "n": 1024}) + + _skip_if_memory_limited(2 ** 31, {"p": 16, "r": 64, "n": 1024}) + + +@pytest.mark.requires_backend_interface(interface=ScryptBackend) +class TestScrypt(object): + @pytest.mark.parametrize("params", vectors) + def test_derive(self, backend, params): + _skip_if_memory_limited(_MEM_LIMIT, params) + password = params["password"] + work_factor = int(params["n"]) + block_size = int(params["r"]) + parallelization_factor = int(params["p"]) + length = int(params["length"]) + salt = params["salt"] + derived_key = params["derived_key"] + + scrypt = Scrypt(salt, length, work_factor, block_size, + parallelization_factor, backend) + assert binascii.hexlify(scrypt.derive(password)) == derived_key + + def test_unsupported_backend(self): + work_factor = 1024 + block_size = 8 + parallelization_factor = 16 + length = 64 + salt = b"NaCl" + backend = object() + + with pytest.raises(UnsupportedAlgorithm): + Scrypt(salt, length, work_factor, block_size, + parallelization_factor, backend) + + def test_salt_not_bytes(self, backend): + work_factor = 1024 + block_size = 8 + parallelization_factor = 16 + length = 64 + salt = 1 + + with pytest.raises(TypeError): + Scrypt(salt, length, work_factor, block_size, + parallelization_factor, backend) + + def test_scrypt_malloc_failure(self, backend): + password = b"NaCl" + work_factor = 1024 ** 3 + block_size = 589824 + parallelization_factor = 16 + length = 64 + salt = b"NaCl" + + scrypt = Scrypt(salt, length, work_factor, block_size, + parallelization_factor, backend) + + with pytest.raises(MemoryError): + scrypt.derive(password) + + def test_password_not_bytes(self, backend): + password = 1 + work_factor = 1024 + block_size = 8 + parallelization_factor = 16 + length = 64 + salt = b"NaCl" + + scrypt = Scrypt(salt, length, work_factor, block_size, + parallelization_factor, backend) + + with pytest.raises(TypeError): + scrypt.derive(password) + + def test_buffer_protocol(self, backend): + password = bytearray(b"password") + work_factor = 256 + block_size = 8 + parallelization_factor = 16 + length = 10 + salt = b"NaCl" + + scrypt = Scrypt(salt, length, work_factor, block_size, + parallelization_factor, backend) + + assert scrypt.derive(password) == b'\xf4\x92\x86\xb2\x06\x0c\x848W\x87' + + @pytest.mark.parametrize("params", vectors) + def test_verify(self, backend, params): + _skip_if_memory_limited(_MEM_LIMIT, params) + password = params["password"] + work_factor = int(params["n"]) + block_size = int(params["r"]) + parallelization_factor = int(params["p"]) + length = int(params["length"]) + salt = params["salt"] + derived_key = params["derived_key"] + + scrypt = Scrypt(salt, length, work_factor, block_size, + parallelization_factor, backend) + assert scrypt.verify(password, binascii.unhexlify(derived_key)) is None + + def test_invalid_verify(self, backend): + password = b"password" + work_factor = 1024 + block_size = 8 + parallelization_factor = 16 + length = 64 + salt = b"NaCl" + derived_key = b"fdbabe1c9d3472007856e7190d01e9fe7c6ad7cbc8237830e773" + + scrypt = Scrypt(salt, length, work_factor, block_size, + parallelization_factor, backend) + + with pytest.raises(InvalidKey): + scrypt.verify(password, binascii.unhexlify(derived_key)) + + def test_already_finalized(self, backend): + password = b"password" + work_factor = 1024 + block_size = 8 + parallelization_factor = 16 + length = 64 + salt = b"NaCl" + + scrypt = Scrypt(salt, length, work_factor, block_size, + parallelization_factor, backend) + scrypt.derive(password) + with pytest.raises(AlreadyFinalized): + scrypt.derive(password) + + def test_invalid_n(self, backend): + # n is less than 2 + with pytest.raises(ValueError): + Scrypt(b"NaCl", 64, 1, 8, 16, backend) + + # n is not a power of 2 + with pytest.raises(ValueError): + Scrypt(b"NaCl", 64, 3, 8, 16, backend) + + def test_invalid_r(self, backend): + with pytest.raises(ValueError): + Scrypt(b"NaCl", 64, 2, 0, 16, backend) + + def test_invalid_p(self, backend): + with pytest.raises(ValueError): + Scrypt(b"NaCl", 64, 2, 8, 0, backend) diff --git a/tests/hazmat/primitives/test_seed.py b/tests/hazmat/primitives/test_seed.py index 7697bd8b..2d03d774 100644 --- a/tests/hazmat/primitives/test_seed.py +++ b/tests/hazmat/primitives/test_seed.py @@ -18,13 +18,13 @@ from ...utils import load_nist_vectors @pytest.mark.supported( only_if=lambda backend: backend.cipher_supported( - algorithms.SEED("\x00" * 16), modes.ECB() + algorithms.SEED(b"\x00" * 16), modes.ECB() ), skip_message="Does not support SEED ECB", ) @pytest.mark.requires_backend_interface(interface=CipherBackend) class TestSEEDModeECB(object): - test_ECB = generate_encrypt_test( + test_ecb = generate_encrypt_test( load_nist_vectors, os.path.join("ciphers", "SEED"), ["rfc-4269.txt"], @@ -35,13 +35,13 @@ class TestSEEDModeECB(object): @pytest.mark.supported( only_if=lambda backend: backend.cipher_supported( - algorithms.SEED("\x00" * 16), modes.CBC("\x00" * 16) + algorithms.SEED(b"\x00" * 16), modes.CBC(b"\x00" * 16) ), skip_message="Does not support SEED CBC", ) @pytest.mark.requires_backend_interface(interface=CipherBackend) class TestSEEDModeCBC(object): - test_CBC = generate_encrypt_test( + test_cbc = generate_encrypt_test( load_nist_vectors, os.path.join("ciphers", "SEED"), ["rfc-4196.txt"], @@ -52,13 +52,13 @@ class TestSEEDModeCBC(object): @pytest.mark.supported( only_if=lambda backend: backend.cipher_supported( - algorithms.SEED("\x00" * 16), modes.OFB("\x00" * 16) + algorithms.SEED(b"\x00" * 16), modes.OFB(b"\x00" * 16) ), skip_message="Does not support SEED OFB", ) @pytest.mark.requires_backend_interface(interface=CipherBackend) class TestSEEDModeOFB(object): - test_OFB = generate_encrypt_test( + test_ofb = generate_encrypt_test( load_nist_vectors, os.path.join("ciphers", "SEED"), ["seed-ofb.txt"], @@ -69,13 +69,13 @@ class TestSEEDModeOFB(object): @pytest.mark.supported( only_if=lambda backend: backend.cipher_supported( - algorithms.SEED("\x00" * 16), modes.CFB("\x00" * 16) + algorithms.SEED(b"\x00" * 16), modes.CFB(b"\x00" * 16) ), skip_message="Does not support SEED CFB", ) @pytest.mark.requires_backend_interface(interface=CipherBackend) class TestSEEDModeCFB(object): - test_CFB = generate_encrypt_test( + test_cfb = generate_encrypt_test( load_nist_vectors, os.path.join("ciphers", "SEED"), ["seed-cfb.txt"], diff --git a/tests/hazmat/primitives/test_serialization.py b/tests/hazmat/primitives/test_serialization.py index 22c2145c..8e8e15af 100644 --- a/tests/hazmat/primitives/test_serialization.py +++ b/tests/hazmat/primitives/test_serialization.py @@ -16,10 +16,15 @@ from cryptography.hazmat.backends.interfaces import ( DERSerializationBackend, DSABackend, EllipticCurveBackend, PEMSerializationBackend, RSABackend ) -from cryptography.hazmat.primitives.asymmetric import dsa, ec, rsa +from cryptography.hazmat.primitives.asymmetric import ( + dsa, ec, ed25519, ed448, rsa, x25519, x448 +) from cryptography.hazmat.primitives.serialization import ( - BestAvailableEncryption, load_der_private_key, load_der_public_key, - load_pem_private_key, load_pem_public_key, load_ssh_public_key + BestAvailableEncryption, Encoding, NoEncryption, + PrivateFormat, PublicFormat, + load_der_parameters, load_der_private_key, + load_der_public_key, load_pem_parameters, load_pem_private_key, + load_pem_public_key, load_ssh_public_key ) @@ -31,6 +36,55 @@ from .utils import ( from ...utils import raises_unsupported_algorithm +class TestBufferProtocolSerialization(object): + @pytest.mark.requires_backend_interface(interface=RSABackend) + @pytest.mark.parametrize( + ("key_path", "password"), + [ + (["DER_Serialization", "enc-rsa-pkcs8.der"], bytearray(b"foobar")), + (["DER_Serialization", "enc2-rsa-pkcs8.der"], bytearray(b"baz")), + (["DER_Serialization", "unenc-rsa-pkcs8.der"], None), + (["DER_Serialization", "testrsa.der"], None), + ] + ) + def test_load_der_rsa_private_key(self, key_path, password, backend): + data = load_vectors_from_file( + os.path.join("asymmetric", *key_path), + lambda derfile: derfile.read(), mode="rb" + ) + key = load_der_private_key(bytearray(data), password, backend) + assert key + assert isinstance(key, rsa.RSAPrivateKey) + _check_rsa_private_numbers(key.private_numbers()) + + @pytest.mark.requires_backend_interface(interface=RSABackend) + @pytest.mark.parametrize( + ("key_path", "password"), + [ + ( + ["PEM_Serialization", "rsa_private_key.pem"], + bytearray(b"123456") + ), + (["PKCS8", "unenc-rsa-pkcs8.pem"], None), + (["PKCS8", "enc-rsa-pkcs8.pem"], bytearray(b"foobar")), + (["PKCS8", "enc2-rsa-pkcs8.pem"], bytearray(b"baz")), + ( + ["Traditional_OpenSSL_Serialization", "key1.pem"], + bytearray(b"123456") + ), + ] + ) + def test_load_pem_rsa_private_key(self, key_path, password, backend): + data = load_vectors_from_file( + os.path.join("asymmetric", *key_path), + lambda pemfile: pemfile.read(), mode="rb" + ) + key = load_pem_private_key(bytearray(data), password, backend) + assert key + assert isinstance(key, rsa.RSAPrivateKey) + _check_rsa_private_numbers(key.private_numbers()) + + @pytest.mark.requires_backend_interface(interface=DERSerializationBackend) class TestDERSerialization(object): @pytest.mark.requires_backend_interface(interface=RSABackend) @@ -53,8 +107,7 @@ class TestDERSerialization(object): ) assert key assert isinstance(key, rsa.RSAPrivateKey) - if isinstance(key, rsa.RSAPrivateKeyWithSerialization): - _check_rsa_private_numbers(key.private_numbers()) + _check_rsa_private_numbers(key.private_numbers()) @pytest.mark.requires_backend_interface(interface=DSABackend) @pytest.mark.parametrize( @@ -76,8 +129,27 @@ class TestDERSerialization(object): ) assert key assert isinstance(key, dsa.DSAPrivateKey) - if isinstance(key, dsa.DSAPrivateKeyWithSerialization): - _check_dsa_private_numbers(key.private_numbers()) + _check_dsa_private_numbers(key.private_numbers()) + + @pytest.mark.parametrize( + "key_path", + [ + ["DER_Serialization", "enc-rsa-pkcs8.der"], + ] + ) + @pytest.mark.requires_backend_interface(interface=RSABackend) + def test_password_not_bytes(self, key_path, backend): + key_file = os.path.join("asymmetric", *key_path) + password = u"this password is not bytes" + + with pytest.raises(TypeError): + load_vectors_from_file( + key_file, + lambda derfile: load_der_private_key( + derfile.read(), password, backend + ), + mode="rb" + ) @pytest.mark.parametrize( ("key_path", "password"), @@ -247,9 +319,8 @@ class TestDERSerialization(object): ) assert key assert isinstance(key, rsa.RSAPublicKey) - if isinstance(key, rsa.RSAPublicKeyWithSerialization): - numbers = key.public_numbers() - assert numbers.e == 65537 + numbers = key.public_numbers() + assert numbers.e == 65537 def test_load_der_invalid_public_key(self, backend): with pytest.raises(ValueError): @@ -293,6 +364,14 @@ class TestDERSerialization(object): assert key.curve.name == "secp256r1" assert key.curve.key_size == 256 + def test_wrong_parameters_format(self, backend): + param_data = b"---- NOT A KEY ----\n" + + with pytest.raises(ValueError): + load_der_parameters( + param_data, backend + ) + @pytest.mark.requires_backend_interface(interface=PEMSerializationBackend) class TestPEMSerialization(object): @@ -330,8 +409,7 @@ class TestPEMSerialization(object): assert key assert isinstance(key, rsa.RSAPrivateKey) - if isinstance(key, rsa.RSAPrivateKeyWithSerialization): - _check_rsa_private_numbers(key.private_numbers()) + _check_rsa_private_numbers(key.private_numbers()) @pytest.mark.parametrize( ("key_path", "password"), @@ -352,8 +430,7 @@ class TestPEMSerialization(object): ) assert key assert isinstance(key, dsa.DSAPrivateKey) - if isinstance(key, dsa.DSAPrivateKeyWithSerialization): - _check_dsa_private_numbers(key.private_numbers()) + _check_dsa_private_numbers(key.private_numbers()) @pytest.mark.parametrize( ("key_path", "password"), @@ -397,9 +474,8 @@ class TestPEMSerialization(object): ) assert key assert isinstance(key, rsa.RSAPublicKey) - if isinstance(key, rsa.RSAPublicKeyWithSerialization): - numbers = key.public_numbers() - assert numbers.e == 65537 + numbers = key.public_numbers() + assert numbers.e == 65537 @pytest.mark.parametrize( ("key_file"), @@ -498,6 +574,43 @@ class TestPEMSerialization(object): ) ) + def test_invalid_encoding_with_traditional(self, backend): + key_file = os.path.join( + "asymmetric", "Traditional_OpenSSL_Serialization", "testrsa.pem" + ) + key = load_vectors_from_file( + key_file, + lambda pemfile: load_pem_private_key( + pemfile.read(), None, backend + ), + mode="rb" + ) + + for enc in (Encoding.OpenSSH, Encoding.Raw, Encoding.X962): + with pytest.raises(ValueError): + key.private_bytes( + enc, PrivateFormat.TraditionalOpenSSL, NoEncryption() + ) + + @pytest.mark.parametrize( + "key_path", + [ + ["Traditional_OpenSSL_Serialization", "testrsa-encrypted.pem"], + ["PKCS8", "enc-rsa-pkcs8.pem"] + ] + ) + def test_password_not_bytes(self, key_path, backend): + key_file = os.path.join("asymmetric", *key_path) + password = u"this password is not bytes" + + with pytest.raises(TypeError): + load_vectors_from_file( + key_file, + lambda pemfile: load_pem_private_key( + pemfile.read().encode(), password, backend + ) + ) + @pytest.mark.parametrize( "key_path", [ @@ -558,6 +671,12 @@ class TestPEMSerialization(object): with pytest.raises(ValueError): load_pem_public_key(key_data, backend) + def test_wrong_parameters_format(self, backend): + param_data = b"---- NOT A KEY ----\n" + + with pytest.raises(ValueError): + load_pem_parameters(param_data, backend) + def test_corrupt_traditional_format(self, backend): # privkey.pem with a bunch of data missing. key_data = textwrap.dedent("""\ @@ -770,41 +889,40 @@ class TestPEMSerialization(object): params = key.parameters() assert isinstance(params, dsa.DSAParameters) - if isinstance(params, dsa.DSAParametersWithNumbers): - num = key.private_numbers() - pub = num.public_numbers - parameter_numbers = pub.parameter_numbers - assert num.x == int("00a535a8e1d0d91beafc8bee1d9b2a3a8de3311203", - 16) - assert pub.y == int( - "2b260ea97dc6a12ae932c640e7df3d8ff04a8a05a0324f8d5f1b23f15fa1" - "70ff3f42061124eff2586cb11b49a82dcdc1b90fc6a84fb10109cb67db5d" - "2da971aeaf17be5e37284563e4c64d9e5fc8480258b319f0de29d54d8350" - "70d9e287914d77df81491f4423b62da984eb3f45eb2a29fcea5dae525ac6" - "ab6bcce04bfdf5b6", - 16 - ) + num = key.private_numbers() + pub = num.public_numbers + parameter_numbers = pub.parameter_numbers + assert num.x == int("00a535a8e1d0d91beafc8bee1d9b2a3a8de3311203", + 16) + assert pub.y == int( + "2b260ea97dc6a12ae932c640e7df3d8ff04a8a05a0324f8d5f1b23f15fa1" + "70ff3f42061124eff2586cb11b49a82dcdc1b90fc6a84fb10109cb67db5d" + "2da971aeaf17be5e37284563e4c64d9e5fc8480258b319f0de29d54d8350" + "70d9e287914d77df81491f4423b62da984eb3f45eb2a29fcea5dae525ac6" + "ab6bcce04bfdf5b6", + 16 + ) - assert parameter_numbers.p == int( - "00aa0930cc145825221caffa28ac2894196a27833de5ec21270791689420" - "7774a2e7b238b0d36f1b2499a2c2585083eb01432924418d867faa212dd1" - "071d4dceb2782794ad393cc08a4d4ada7f68d6e839a5fcd34b4e402d82cb" - "8a8cb40fec31911bf9bd360b034caacb4c5e947992573c9e90099c1b0f05" - "940cabe5d2de49a167", - 16 - ) + assert parameter_numbers.p == int( + "00aa0930cc145825221caffa28ac2894196a27833de5ec21270791689420" + "7774a2e7b238b0d36f1b2499a2c2585083eb01432924418d867faa212dd1" + "071d4dceb2782794ad393cc08a4d4ada7f68d6e839a5fcd34b4e402d82cb" + "8a8cb40fec31911bf9bd360b034caacb4c5e947992573c9e90099c1b0f05" + "940cabe5d2de49a167", + 16 + ) - assert parameter_numbers.q == int( - "00adc0e869b36f0ac013a681fdf4d4899d69820451", 16) + assert parameter_numbers.q == int( + "00adc0e869b36f0ac013a681fdf4d4899d69820451", 16) - assert parameter_numbers.g == int( - "008c6b4589afa53a4d1048bfc346d1f386ca75521ccf72ddaa251286880e" - "e13201ff48890bbfc33d79bacaec71e7a778507bd5f1a66422e39415be03" - "e71141ba324f5b93131929182c88a9fa4062836066cebe74b5c6690c7d10" - "1106c240ab7ebd54e4e3301fd086ce6adac922fb2713a2b0887cba13b9bc" - "68ce5cfff241cd3246", - 16 - ) + assert parameter_numbers.g == int( + "008c6b4589afa53a4d1048bfc346d1f386ca75521ccf72ddaa251286880e" + "e13201ff48890bbfc33d79bacaec71e7a778507bd5f1a66422e39415be03" + "e71141ba324f5b93131929182c88a9fa4062836066cebe74b5c6690c7d10" + "1106c240ab7ebd54e4e3301fd086ce6adac922fb2713a2b0887cba13b9bc" + "68ce5cfff241cd3246", + 16 + ) @pytest.mark.parametrize( ("key_file", "password"), @@ -813,9 +931,7 @@ class TestPEMSerialization(object): ] ) def test_load_bad_oid_key(self, key_file, password, backend): - with raises_unsupported_algorithm( - _Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM - ): + with pytest.raises(ValueError): load_vectors_from_file( os.path.join( "asymmetric", "PKCS8", key_file), @@ -861,7 +977,18 @@ class TestRSASSHSerialization(object): with pytest.raises(ValueError): load_ssh_public_key(ssh_key, backend) - def test_load_ssh_public_key_rsa_extra_string_after_comment(self, backend): + def test_load_ssh_public_key_truncated_int(self, backend): + ssh_key = b'ssh-rsa AAAAB3NzaC1yc2EAAAA=' + + with pytest.raises(ValueError): + load_ssh_public_key(ssh_key, backend) + + ssh_key = b'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAACKr+IHXo' + + with pytest.raises(ValueError): + load_ssh_public_key(ssh_key, backend) + + def test_load_ssh_public_key_rsa_comment_with_spaces(self, backend): ssh_key = ( b"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDDu/XRP1kyK6Cgt36gts9XAk" b"FiiuJLW6RU0j3KKVZSs1I7Z3UmU9/9aVh/rZV43WQG8jaR6kkcP4stOR0DEtll" @@ -873,8 +1000,7 @@ class TestRSASSHSerialization(object): b"2MzHvnbv testkey@localhost extra" ) - with pytest.raises(ValueError): - load_ssh_public_key(ssh_key, backend) + load_ssh_public_key(ssh_key, backend) def test_load_ssh_public_key_rsa_extra_data_after_modulo(self, backend): ssh_key = ( @@ -950,7 +1076,7 @@ class TestDSSSSHSerialization(object): with pytest.raises(ValueError): load_ssh_public_key(ssh_key, backend) - def test_load_ssh_public_key_dss_extra_string_after_comment(self, backend): + def test_load_ssh_public_key_dss_comment_with_spaces(self, backend): ssh_key = ( b"ssh-dss AAAAB3NzaC1kc3MAAACBALmwUtfwdjAUjU2Dixd5DvT0NDcjjr69UD" b"LqSD/Xt5Al7D3GXr1WOrWGpjO0NE9qzRCvMTU7zykRH6XjuNXB6Hvv48Zfm4vm" @@ -964,8 +1090,7 @@ class TestDSSSSHSerialization(object): b"z53N7tPF/IhHTjBHb1Ol7IFu9p9A== testkey@localhost extra" ) - with pytest.raises(ValueError): - load_ssh_public_key(ssh_key, backend) + load_ssh_public_key(ssh_key, backend) def test_load_ssh_public_key_dss_extra_data_after_modulo(self, backend): ssh_key = ( @@ -1169,6 +1294,53 @@ class TestECDSASSHSerialization(object): load_ssh_public_key(ssh_key, backend) +@pytest.mark.supported( + only_if=lambda backend: backend.ed25519_supported(), + skip_message="Requires OpenSSL with Ed25519 support" +) +class TestEd25519SSHSerialization(object): + def test_load_ssh_public_key(self, backend): + ssh_key = ( + b"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIG2fgpmpYO61qeAxGd0wgRaN/E4" + b"GR+xWvBmvxjxrB1vG user@chiron.local" + ) + key = load_ssh_public_key(ssh_key, backend) + assert isinstance(key, ed25519.Ed25519PublicKey) + assert key.public_bytes( + Encoding.Raw, PublicFormat.Raw + ) == ( + b"m\x9f\x82\x99\xa9`\xee\xb5\xa9\xe01\x19\xdd0\x81\x16\x8d\xfc" + b"N\x06G\xecV\xbc\x19\xaf\xc6<k\x07[\xc6" + ) + + def test_public_bytes_openssh(self, backend): + ssh_key = ( + b"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIG2fgpmpYO61qeAxGd0wgRaN/E4" + b"GR+xWvBmvxjxrB1vG" + ) + key = load_ssh_public_key(ssh_key, backend) + assert isinstance(key, ed25519.Ed25519PublicKey) + assert key.public_bytes( + Encoding.OpenSSH, PublicFormat.OpenSSH + ) == ssh_key + + def test_load_ssh_public_key_not_32_bytes(self, backend): + ssh_key = ( + b"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI22fgpmpYO61qeAxGd0wgRaN/E4" + b"GR+xWvBmvxjxrB1vGaGVs user@chiron.local" + ) + with pytest.raises(ValueError): + load_ssh_public_key(ssh_key, backend) + + def test_load_ssh_public_key_trailing_data(self, backend): + ssh_key = ( + b"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIG2fgpmpYO61qeAxGd0wgRa" + b"N/E4GR+xWvBmvxjxrB1vGdHJhaWxpbmdkYXRh user@chiron.local" + ) + with pytest.raises(ValueError): + load_ssh_public_key(ssh_key, backend) + + class TestKeySerializationEncryptionTypes(object): def test_non_bytes_password(self): with pytest.raises(ValueError): @@ -1177,3 +1349,363 @@ class TestKeySerializationEncryptionTypes(object): def test_encryption_with_zero_length_password(self): with pytest.raises(ValueError): BestAvailableEncryption(b"") + + +@pytest.mark.supported( + only_if=lambda backend: backend.ed25519_supported(), + skip_message="Requires OpenSSL with Ed25519 support" +) +class TestEd25519Serialization(object): + def test_load_der_private_key(self, backend): + data = load_vectors_from_file( + os.path.join("asymmetric", "Ed25519", "ed25519-pkcs8-enc.der"), + lambda derfile: derfile.read(), + mode="rb" + ) + unencrypted = load_vectors_from_file( + os.path.join("asymmetric", "Ed25519", "ed25519-pkcs8.der"), + lambda derfile: derfile.read(), + mode="rb" + ) + key = load_der_private_key(data, b"password", backend) + assert key.private_bytes( + Encoding.DER, PrivateFormat.PKCS8, NoEncryption() + ) == unencrypted + + def test_load_pem_private_key(self, backend): + data = load_vectors_from_file( + os.path.join("asymmetric", "Ed25519", "ed25519-pkcs8-enc.pem"), + lambda pemfile: pemfile.read(), + mode="rb" + ) + unencrypted = load_vectors_from_file( + os.path.join("asymmetric", "Ed25519", "ed25519-pkcs8.pem"), + lambda pemfile: pemfile.read(), + mode="rb" + ) + key = load_pem_private_key(data, b"password", backend) + assert key.private_bytes( + Encoding.PEM, PrivateFormat.PKCS8, NoEncryption() + ) == unencrypted + + @pytest.mark.parametrize( + ("key_path", "encoding", "loader"), + [ + ( + ["Ed25519", "ed25519-pub.pem"], + Encoding.PEM, + load_pem_public_key + ), + ( + ["Ed25519", "ed25519-pub.der"], + Encoding.DER, + load_der_public_key + ), + ] + ) + def test_load_public_key(self, key_path, encoding, loader, backend): + data = load_vectors_from_file( + os.path.join("asymmetric", *key_path), + lambda pemfile: pemfile.read(), + mode="rb" + ) + public_key = loader(data, backend) + assert public_key.public_bytes( + encoding, PublicFormat.SubjectPublicKeyInfo + ) == data + + def test_openssl_serialization_unsupported(self, backend): + key = ed25519.Ed25519PrivateKey.generate() + with pytest.raises(ValueError): + key.private_bytes( + Encoding.PEM, PrivateFormat.TraditionalOpenSSL, NoEncryption(), + ) + with pytest.raises(ValueError): + key.private_bytes( + Encoding.DER, PrivateFormat.TraditionalOpenSSL, NoEncryption(), + ) + + +@pytest.mark.supported( + only_if=lambda backend: backend.x448_supported(), + skip_message="Requires OpenSSL with X448 support" +) +class TestX448Serialization(object): + def test_load_der_private_key(self, backend): + data = load_vectors_from_file( + os.path.join("asymmetric", "X448", "x448-pkcs8-enc.der"), + lambda derfile: derfile.read(), + mode="rb" + ) + unencrypted = load_vectors_from_file( + os.path.join("asymmetric", "X448", "x448-pkcs8.der"), + lambda derfile: derfile.read(), + mode="rb" + ) + key = load_der_private_key(data, b"password", backend) + assert key.private_bytes( + Encoding.DER, PrivateFormat.PKCS8, NoEncryption() + ) == unencrypted + + def test_load_pem_private_key(self, backend): + data = load_vectors_from_file( + os.path.join("asymmetric", "X448", "x448-pkcs8-enc.pem"), + lambda pemfile: pemfile.read(), + mode="rb" + ) + unencrypted = load_vectors_from_file( + os.path.join("asymmetric", "X448", "x448-pkcs8.pem"), + lambda pemfile: pemfile.read(), + mode="rb" + ) + key = load_pem_private_key(data, b"password", backend) + assert key.private_bytes( + Encoding.PEM, PrivateFormat.PKCS8, NoEncryption() + ) == unencrypted + + @pytest.mark.parametrize( + ("key_path", "encoding", "loader"), + [ + ( + ["X448", "x448-pub.pem"], + Encoding.PEM, + load_pem_public_key + ), + ( + ["X448", "x448-pub.der"], + Encoding.DER, + load_der_public_key + ), + ] + ) + def test_load_public_key(self, key_path, encoding, loader, backend): + data = load_vectors_from_file( + os.path.join("asymmetric", *key_path), + lambda pemfile: pemfile.read(), + mode="rb" + ) + public_key = loader(data, backend) + assert public_key.public_bytes( + encoding, PublicFormat.SubjectPublicKeyInfo + ) == data + + def test_openssl_serialization_unsupported(self, backend): + key = x448.X448PrivateKey.generate() + with pytest.raises(ValueError): + key.private_bytes( + Encoding.PEM, PrivateFormat.TraditionalOpenSSL, NoEncryption(), + ) + with pytest.raises(ValueError): + key.private_bytes( + Encoding.DER, PrivateFormat.TraditionalOpenSSL, NoEncryption(), + ) + + +@pytest.mark.supported( + only_if=lambda backend: backend.x25519_supported(), + skip_message="Requires OpenSSL with X25519 support" +) +class TestX25519Serialization(object): + def test_load_der_private_key(self, backend): + data = load_vectors_from_file( + os.path.join("asymmetric", "X25519", "x25519-pkcs8-enc.der"), + lambda derfile: derfile.read(), + mode="rb" + ) + unencrypted = load_vectors_from_file( + os.path.join("asymmetric", "X25519", "x25519-pkcs8.der"), + lambda derfile: derfile.read(), + mode="rb" + ) + key = load_der_private_key(data, b"password", backend) + assert key.private_bytes( + Encoding.DER, PrivateFormat.PKCS8, NoEncryption() + ) == unencrypted + + def test_load_pem_private_key(self, backend): + data = load_vectors_from_file( + os.path.join("asymmetric", "X25519", "x25519-pkcs8-enc.pem"), + lambda pemfile: pemfile.read(), + mode="rb" + ) + unencrypted = load_vectors_from_file( + os.path.join("asymmetric", "X25519", "x25519-pkcs8.pem"), + lambda pemfile: pemfile.read(), + mode="rb" + ) + key = load_pem_private_key(data, b"password", backend) + assert key.private_bytes( + Encoding.PEM, PrivateFormat.PKCS8, NoEncryption() + ) == unencrypted + + @pytest.mark.parametrize( + ("key_path", "encoding", "loader"), + [ + ( + ["X25519", "x25519-pub.pem"], + Encoding.PEM, + load_pem_public_key + ), + ( + ["X25519", "x25519-pub.der"], + Encoding.DER, + load_der_public_key + ), + ] + ) + def test_load_public_key(self, key_path, encoding, loader, backend): + data = load_vectors_from_file( + os.path.join("asymmetric", *key_path), + lambda pemfile: pemfile.read(), + mode="rb" + ) + public_key = loader(data, backend) + assert public_key.public_bytes( + encoding, PublicFormat.SubjectPublicKeyInfo + ) == data + + def test_openssl_serialization_unsupported(self, backend): + key = x25519.X25519PrivateKey.generate() + with pytest.raises(ValueError): + key.private_bytes( + Encoding.PEM, PrivateFormat.TraditionalOpenSSL, NoEncryption(), + ) + with pytest.raises(ValueError): + key.private_bytes( + Encoding.DER, PrivateFormat.TraditionalOpenSSL, NoEncryption(), + ) + + +@pytest.mark.supported( + only_if=lambda backend: backend.ed448_supported(), + skip_message="Requires OpenSSL with Ed448 support" +) +class TestEd448Serialization(object): + def test_load_der_private_key(self, backend): + data = load_vectors_from_file( + os.path.join("asymmetric", "Ed448", "ed448-pkcs8-enc.der"), + lambda derfile: derfile.read(), + mode="rb" + ) + unencrypted = load_vectors_from_file( + os.path.join("asymmetric", "Ed448", "ed448-pkcs8.der"), + lambda derfile: derfile.read(), + mode="rb" + ) + key = load_der_private_key(data, b"password", backend) + assert key.private_bytes( + Encoding.DER, PrivateFormat.PKCS8, NoEncryption() + ) == unencrypted + + def test_load_pem_private_key(self, backend): + data = load_vectors_from_file( + os.path.join("asymmetric", "Ed448", "ed448-pkcs8-enc.pem"), + lambda pemfile: pemfile.read(), + mode="rb" + ) + unencrypted = load_vectors_from_file( + os.path.join("asymmetric", "Ed448", "ed448-pkcs8.pem"), + lambda pemfile: pemfile.read(), + mode="rb" + ) + key = load_pem_private_key(data, b"password", backend) + assert key.private_bytes( + Encoding.PEM, PrivateFormat.PKCS8, NoEncryption() + ) == unencrypted + + @pytest.mark.parametrize( + ("key_path", "encoding", "loader"), + [ + ( + ["Ed448", "ed448-pub.pem"], + Encoding.PEM, + load_pem_public_key + ), + ( + ["Ed448", "ed448-pub.der"], + Encoding.DER, + load_der_public_key + ), + ] + ) + def test_load_public_key(self, key_path, encoding, loader, backend): + data = load_vectors_from_file( + os.path.join("asymmetric", *key_path), + lambda pemfile: pemfile.read(), + mode="rb" + ) + public_key = loader(data, backend) + assert public_key.public_bytes( + encoding, PublicFormat.SubjectPublicKeyInfo + ) == data + + def test_openssl_serialization_unsupported(self, backend): + key = ed448.Ed448PrivateKey.generate() + with pytest.raises(ValueError): + key.private_bytes( + Encoding.PEM, PrivateFormat.TraditionalOpenSSL, NoEncryption(), + ) + with pytest.raises(ValueError): + key.private_bytes( + Encoding.DER, PrivateFormat.TraditionalOpenSSL, NoEncryption(), + ) + + def test_openssh_serialization_unsupported(self, backend): + key = ed448.Ed448PrivateKey.generate() + with pytest.raises(ValueError): + key.public_key().public_bytes( + Encoding.OpenSSH, PublicFormat.OpenSSH, + ) + + +class TestDHSerialization(object): + """Test all options with least-supported key type. + """ + def test_dh_public_key(self, backend): + data = load_vectors_from_file( + os.path.join("asymmetric", "DH", "dhkey.pem"), + lambda pemfile: pemfile.read(), + mode="rb" + ) + public_key = load_pem_private_key(data, None, backend).public_key() + for enc in ( + Encoding.PEM, Encoding.DER, Encoding.OpenSSH, + Encoding.Raw, Encoding.X962 + ): + for fmt in ( + PublicFormat.SubjectPublicKeyInfo, PublicFormat.PKCS1, + PublicFormat.OpenSSH, PublicFormat.Raw, + PublicFormat.CompressedPoint, PublicFormat.UncompressedPoint, + ): + if ( + enc in (Encoding.PEM, Encoding.DER) and + fmt == PublicFormat.SubjectPublicKeyInfo + ): + # tested elsewhere + continue + with pytest.raises(ValueError): + public_key.public_bytes(enc, fmt) + + def test_dh_private_key(self, backend): + data = load_vectors_from_file( + os.path.join("asymmetric", "DH", "dhkey.pem"), + lambda pemfile: pemfile.read(), + mode="rb" + ) + private_key = load_pem_private_key(data, None, backend) + for enc in ( + Encoding.PEM, Encoding.DER, Encoding.OpenSSH, + Encoding.Raw, Encoding.X962 + ): + for fmt in ( + PrivateFormat.PKCS8, PrivateFormat.TraditionalOpenSSL, + PrivateFormat.Raw + ): + if ( + enc in (Encoding.PEM, Encoding.DER) and + fmt is PrivateFormat.PKCS8 + ): + # tested elsewhere + continue + with pytest.raises(ValueError): + private_key.private_bytes(enc, fmt, NoEncryption()) diff --git a/tests/hazmat/primitives/test_x25519.py b/tests/hazmat/primitives/test_x25519.py new file mode 100644 index 00000000..30dc2818 --- /dev/null +++ b/tests/hazmat/primitives/test_x25519.py @@ -0,0 +1,260 @@ +# 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 binascii +import os + +import pytest + +from cryptography.exceptions import _Reasons +from cryptography.hazmat.primitives import serialization +from cryptography.hazmat.primitives.asymmetric.x25519 import ( + X25519PrivateKey, X25519PublicKey +) + +from ...utils import ( + load_nist_vectors, load_vectors_from_file, raises_unsupported_algorithm +) + + +@pytest.mark.supported( + only_if=lambda backend: not backend.x25519_supported(), + skip_message="Requires OpenSSL without X25519 support" +) +def test_x25519_unsupported(backend): + with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_EXCHANGE_ALGORITHM): + X25519PublicKey.from_public_bytes(b"0" * 32) + + with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_EXCHANGE_ALGORITHM): + X25519PrivateKey.from_private_bytes(b"0" * 32) + + with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_EXCHANGE_ALGORITHM): + X25519PrivateKey.generate() + + +@pytest.mark.supported( + only_if=lambda backend: backend.x25519_supported(), + skip_message="Requires OpenSSL with X25519 support" +) +class TestX25519Exchange(object): + @pytest.mark.parametrize( + "vector", + load_vectors_from_file( + os.path.join("asymmetric", "X25519", "rfc7748.txt"), + load_nist_vectors + ) + ) + def test_rfc7748(self, vector, backend): + private = binascii.unhexlify(vector["input_scalar"]) + public = binascii.unhexlify(vector["input_u"]) + shared_key = binascii.unhexlify(vector["output_u"]) + private_key = X25519PrivateKey.from_private_bytes(private) + public_key = X25519PublicKey.from_public_bytes(public) + computed_shared_key = private_key.exchange(public_key) + assert computed_shared_key == shared_key + + def test_rfc7748_1000_iteration(self, backend): + old_private = private = public = binascii.unhexlify( + b"090000000000000000000000000000000000000000000000000000000000" + b"0000" + ) + shared_key = binascii.unhexlify( + b"684cf59ba83309552800ef566f2f4d3c1c3887c49360e3875f2eb94d9953" + b"2c51" + ) + private_key = X25519PrivateKey.from_private_bytes(private) + public_key = X25519PublicKey.from_public_bytes(public) + for _ in range(1000): + computed_shared_key = private_key.exchange(public_key) + private_key = X25519PrivateKey.from_private_bytes( + computed_shared_key + ) + public_key = X25519PublicKey.from_public_bytes(old_private) + old_private = computed_shared_key + + assert computed_shared_key == shared_key + + def test_null_shared_key_raises_error(self, backend): + """ + The vector used here is taken from wycheproof's x25519 test vectors + """ + public = binascii.unhexlify( + "5f9c95bca3508c24b1d0b1559c83ef5b04445cc4581c8e86d8224eddd09f1157" + ) + private = binascii.unhexlify( + "78f1e8edf14481b389448dac8f59c70b038e7cf92ef2c7eff57a72466e115296" + ) + private_key = X25519PrivateKey.from_private_bytes( + private + ) + public_key = X25519PublicKey.from_public_bytes(public) + with pytest.raises(ValueError): + private_key.exchange(public_key) + + def test_public_bytes_bad_args(self, backend): + key = X25519PrivateKey.generate().public_key() + with pytest.raises(ValueError): + key.public_bytes(None, serialization.PublicFormat.Raw) + with pytest.raises(TypeError): + key.public_bytes(serialization.Encoding.Raw) + + # These vectors are also from RFC 7748 + # https://tools.ietf.org/html/rfc7748#section-6.1 + @pytest.mark.parametrize( + ("private_bytes", "public_bytes"), + [ + ( + binascii.unhexlify( + b"77076d0a7318a57d3c16c17251b26645df4c2f87ebc0992ab177fba" + b"51db92c2a" + ), + binascii.unhexlify( + b"8520f0098930a754748b7ddcb43ef75a0dbf3a0d26381af4eba4a98" + b"eaa9b4e6a" + ) + ), + ( + binascii.unhexlify( + b"5dab087e624a8a4b79e17f8b83800ee66f3bb1292618b6fd1c2f8b2" + b"7ff88e0eb" + ), + binascii.unhexlify( + b"de9edb7d7b7dc1b4d35b61c2ece435373f8343c85b78674dadfc7e1" + b"46f882b4f" + ) + ) + ] + ) + def test_pub_priv_bytes_raw(self, private_bytes, public_bytes, backend): + private_key = X25519PrivateKey.from_private_bytes(private_bytes) + assert private_key.private_bytes( + serialization.Encoding.Raw, + serialization.PrivateFormat.Raw, + serialization.NoEncryption() + ) == private_bytes + assert private_key.public_key().public_bytes( + serialization.Encoding.Raw, serialization.PublicFormat.Raw + ) == public_bytes + public_key = X25519PublicKey.from_public_bytes(public_bytes) + assert public_key.public_bytes( + serialization.Encoding.Raw, serialization.PublicFormat.Raw + ) == public_bytes + + def test_generate(self, backend): + key = X25519PrivateKey.generate() + assert key + assert key.public_key() + + def test_invalid_type_exchange(self, backend): + key = X25519PrivateKey.generate() + with pytest.raises(TypeError): + key.exchange(object()) + + def test_invalid_length_from_public_bytes(self, backend): + with pytest.raises(ValueError): + X25519PublicKey.from_public_bytes(b"a" * 31) + + with pytest.raises(ValueError): + X25519PublicKey.from_public_bytes(b"a" * 33) + + def test_invalid_length_from_private_bytes(self, backend): + with pytest.raises(ValueError): + X25519PrivateKey.from_private_bytes(b"a" * 31) + + with pytest.raises(ValueError): + X25519PrivateKey.from_private_bytes(b"a" * 33) + + def test_invalid_private_bytes(self, backend): + key = X25519PrivateKey.generate() + with pytest.raises(ValueError): + key.private_bytes( + serialization.Encoding.Raw, + serialization.PrivateFormat.Raw, + None + ) + + with pytest.raises(ValueError): + key.private_bytes( + serialization.Encoding.Raw, + serialization.PrivateFormat.PKCS8, + None + ) + + with pytest.raises(ValueError): + key.private_bytes( + serialization.Encoding.PEM, + serialization.PrivateFormat.Raw, + serialization.NoEncryption() + ) + + def test_invalid_public_bytes(self, backend): + key = X25519PrivateKey.generate().public_key() + with pytest.raises(ValueError): + key.public_bytes( + serialization.Encoding.Raw, + serialization.PublicFormat.SubjectPublicKeyInfo + ) + + with pytest.raises(ValueError): + key.public_bytes( + serialization.Encoding.PEM, + serialization.PublicFormat.PKCS1 + ) + + with pytest.raises(ValueError): + key.public_bytes( + serialization.Encoding.PEM, + serialization.PublicFormat.Raw + ) + + @pytest.mark.parametrize( + ("encoding", "fmt", "encryption", "passwd", "load_func"), + [ + ( + serialization.Encoding.PEM, + serialization.PrivateFormat.PKCS8, + serialization.BestAvailableEncryption(b"password"), + b"password", + serialization.load_pem_private_key + ), + ( + serialization.Encoding.DER, + serialization.PrivateFormat.PKCS8, + serialization.BestAvailableEncryption(b"password"), + b"password", + serialization.load_der_private_key + ), + ( + serialization.Encoding.PEM, + serialization.PrivateFormat.PKCS8, + serialization.NoEncryption(), + None, + serialization.load_pem_private_key + ), + ( + serialization.Encoding.DER, + serialization.PrivateFormat.PKCS8, + serialization.NoEncryption(), + None, + serialization.load_der_private_key + ), + ] + ) + def test_round_trip_private_serialization(self, encoding, fmt, encryption, + passwd, load_func, backend): + key = X25519PrivateKey.generate() + serialized = key.private_bytes(encoding, fmt, encryption) + loaded_key = load_func(serialized, passwd, backend) + assert isinstance(loaded_key, X25519PrivateKey) + + def test_buffer_protocol(self, backend): + private_bytes = bytearray(os.urandom(32)) + key = X25519PrivateKey.from_private_bytes(private_bytes) + assert key.private_bytes( + serialization.Encoding.Raw, + serialization.PrivateFormat.Raw, + serialization.NoEncryption() + ) == private_bytes diff --git a/tests/hazmat/primitives/test_x448.py b/tests/hazmat/primitives/test_x448.py new file mode 100644 index 00000000..472d49cd --- /dev/null +++ b/tests/hazmat/primitives/test_x448.py @@ -0,0 +1,239 @@ +# 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 binascii +import os + +import pytest + +from cryptography.exceptions import _Reasons +from cryptography.hazmat.primitives import serialization +from cryptography.hazmat.primitives.asymmetric.x448 import ( + X448PrivateKey, X448PublicKey +) + +from ...utils import ( + load_nist_vectors, load_vectors_from_file, raises_unsupported_algorithm +) + + +@pytest.mark.supported( + only_if=lambda backend: not backend.x448_supported(), + skip_message="Requires OpenSSL without X448 support" +) +def test_x448_unsupported(backend): + with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_EXCHANGE_ALGORITHM): + X448PublicKey.from_public_bytes(b"0" * 56) + + with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_EXCHANGE_ALGORITHM): + X448PrivateKey.from_private_bytes(b"0" * 56) + + with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_EXCHANGE_ALGORITHM): + X448PrivateKey.generate() + + +@pytest.mark.supported( + only_if=lambda backend: backend.x448_supported(), + skip_message="Requires OpenSSL with X448 support" +) +class TestX448Exchange(object): + @pytest.mark.parametrize( + "vector", + load_vectors_from_file( + os.path.join("asymmetric", "X448", "rfc7748.txt"), + load_nist_vectors + ) + ) + def test_rfc7748(self, vector, backend): + private = binascii.unhexlify(vector["input_scalar"]) + public = binascii.unhexlify(vector["input_u"]) + shared_key = binascii.unhexlify(vector["output_u"]) + private_key = X448PrivateKey.from_private_bytes(private) + public_key = X448PublicKey.from_public_bytes(public) + computed_shared_key = private_key.exchange(public_key) + assert computed_shared_key == shared_key + + def test_rfc7748_1000_iteration(self, backend): + old_private = private = public = binascii.unhexlify( + b"05000000000000000000000000000000000000000000000000000000" + b"00000000000000000000000000000000000000000000000000000000" + ) + shared_key = binascii.unhexlify( + b"aa3b4749d55b9daf1e5b00288826c467274ce3ebbdd5c17b975e09d4" + b"af6c67cf10d087202db88286e2b79fceea3ec353ef54faa26e219f38" + ) + private_key = X448PrivateKey.from_private_bytes(private) + public_key = X448PublicKey.from_public_bytes(public) + for _ in range(1000): + computed_shared_key = private_key.exchange(public_key) + private_key = X448PrivateKey.from_private_bytes( + computed_shared_key + ) + public_key = X448PublicKey.from_public_bytes(old_private) + old_private = computed_shared_key + + assert computed_shared_key == shared_key + + # These vectors are also from RFC 7748 + # https://tools.ietf.org/html/rfc7748#section-6.2 + @pytest.mark.parametrize( + ("private_bytes", "public_bytes"), + [ + ( + binascii.unhexlify( + b"9a8f4925d1519f5775cf46b04b5800d4ee9ee8bae8bc5565d498c28d" + b"d9c9baf574a9419744897391006382a6f127ab1d9ac2d8c0a598726b" + ), + binascii.unhexlify( + b"9b08f7cc31b7e3e67d22d5aea121074a273bd2b83de09c63faa73d2c" + b"22c5d9bbc836647241d953d40c5b12da88120d53177f80e532c41fa0" + ) + ), + ( + binascii.unhexlify( + b"1c306a7ac2a0e2e0990b294470cba339e6453772b075811d8fad0d1d" + b"6927c120bb5ee8972b0d3e21374c9c921b09d1b0366f10b65173992d" + ), + binascii.unhexlify( + b"3eb7a829b0cd20f5bcfc0b599b6feccf6da4627107bdb0d4f345b430" + b"27d8b972fc3e34fb4232a13ca706dcb57aec3dae07bdc1c67bf33609" + ) + ) + ] + ) + def test_pub_priv_bytes_raw(self, private_bytes, public_bytes, backend): + private_key = X448PrivateKey.from_private_bytes(private_bytes) + assert private_key.private_bytes( + serialization.Encoding.Raw, + serialization.PrivateFormat.Raw, + serialization.NoEncryption() + ) == private_bytes + assert private_key.public_key().public_bytes( + serialization.Encoding.Raw, serialization.PublicFormat.Raw + ) == public_bytes + public_key = X448PublicKey.from_public_bytes(public_bytes) + assert public_key.public_bytes( + serialization.Encoding.Raw, serialization.PublicFormat.Raw + ) == public_bytes + + @pytest.mark.parametrize( + ("encoding", "fmt", "encryption", "passwd", "load_func"), + [ + ( + serialization.Encoding.PEM, + serialization.PrivateFormat.PKCS8, + serialization.BestAvailableEncryption(b"password"), + b"password", + serialization.load_pem_private_key + ), + ( + serialization.Encoding.DER, + serialization.PrivateFormat.PKCS8, + serialization.BestAvailableEncryption(b"password"), + b"password", + serialization.load_der_private_key + ), + ( + serialization.Encoding.PEM, + serialization.PrivateFormat.PKCS8, + serialization.NoEncryption(), + None, + serialization.load_pem_private_key + ), + ( + serialization.Encoding.DER, + serialization.PrivateFormat.PKCS8, + serialization.NoEncryption(), + None, + serialization.load_der_private_key + ), + ] + ) + def test_round_trip_private_serialization(self, encoding, fmt, encryption, + passwd, load_func, backend): + key = X448PrivateKey.generate() + serialized = key.private_bytes(encoding, fmt, encryption) + loaded_key = load_func(serialized, passwd, backend) + assert isinstance(loaded_key, X448PrivateKey) + + def test_generate(self, backend): + key = X448PrivateKey.generate() + assert key + assert key.public_key() + + def test_invalid_type_exchange(self, backend): + key = X448PrivateKey.generate() + with pytest.raises(TypeError): + key.exchange(object()) + + def test_invalid_length_from_public_bytes(self, backend): + with pytest.raises(ValueError): + X448PublicKey.from_public_bytes(b"a" * 55) + + with pytest.raises(ValueError): + X448PublicKey.from_public_bytes(b"a" * 57) + + def test_invalid_length_from_private_bytes(self, backend): + with pytest.raises(ValueError): + X448PrivateKey.from_private_bytes(b"a" * 55) + + with pytest.raises(ValueError): + X448PrivateKey.from_private_bytes(b"a" * 57) + + def test_invalid_private_bytes(self, backend): + key = X448PrivateKey.generate() + with pytest.raises(ValueError): + key.private_bytes( + serialization.Encoding.Raw, + serialization.PrivateFormat.Raw, + None + ) + + with pytest.raises(ValueError): + key.private_bytes( + serialization.Encoding.Raw, + serialization.PrivateFormat.PKCS8, + None + ) + + with pytest.raises(ValueError): + key.private_bytes( + serialization.Encoding.PEM, + serialization.PrivateFormat.Raw, + serialization.NoEncryption() + ) + + def test_invalid_public_bytes(self, backend): + key = X448PrivateKey.generate().public_key() + with pytest.raises(ValueError): + key.public_bytes( + serialization.Encoding.Raw, + serialization.PublicFormat.SubjectPublicKeyInfo + ) + + with pytest.raises(ValueError): + key.public_bytes( + serialization.Encoding.PEM, + serialization.PublicFormat.PKCS1 + ) + + with pytest.raises(ValueError): + key.public_bytes( + serialization.Encoding.PEM, + serialization.PublicFormat.Raw + ) + + def test_buffer_protocol(self, backend): + private_bytes = binascii.unhexlify( + b"9a8f4925d1519f5775cf46b04b5800d4ee9ee8bae8bc5565d498c28d" + b"d9c9baf574a9419744897391006382a6f127ab1d9ac2d8c0a598726b" + ) + key = X448PrivateKey.from_private_bytes(bytearray(private_bytes)) + assert key.private_bytes( + serialization.Encoding.Raw, + serialization.PrivateFormat.Raw, + serialization.NoEncryption() + ) == private_bytes diff --git a/tests/hazmat/primitives/test_x963_vectors.py b/tests/hazmat/primitives/test_x963_vectors.py new file mode 100644 index 00000000..c75afa41 --- /dev/null +++ b/tests/hazmat/primitives/test_x963_vectors.py @@ -0,0 +1,66 @@ +# 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 binascii +import os + +import pytest + +from cryptography.hazmat.backends import default_backend +from cryptography.hazmat.backends.interfaces import HashBackend +from cryptography.hazmat.primitives import hashes +from cryptography.hazmat.primitives.kdf.x963kdf import X963KDF + +from ...doubles import DummyHashAlgorithm +from ...utils import load_vectors_from_file, load_x963_vectors + + +def _skip_hashfn_unsupported(backend, hashfn): + if not backend.hash_supported(hashfn): + pytest.skip( + "Hash {} is not supported by this backend {}".format( + hashfn.name, backend + ) + ) + + +@pytest.mark.requires_backend_interface(interface=HashBackend) +class TestX963(object): + _algorithms_dict = { + 'SHA-1': hashes.SHA1, + 'SHA-224': hashes.SHA224, + 'SHA-256': hashes.SHA256, + 'SHA-384': hashes.SHA384, + 'SHA-512': hashes.SHA512 + } + + @pytest.mark.parametrize( + ("vector"), + load_vectors_from_file( + os.path.join("KDF", "ansx963_2001.txt"), + load_x963_vectors + ) + ) + def test_x963(self, backend, vector): + hashfn = self._algorithms_dict[vector["hash"]] + _skip_hashfn_unsupported(backend, hashfn()) + + key = binascii.unhexlify(vector["Z"]) + sharedinfo = None + if vector["sharedinfo_length"] != 0: + sharedinfo = binascii.unhexlify(vector["sharedinfo"]) + key_data_len = vector["key_data_length"] // 8 + key_data = binascii.unhexlify(vector["key_data"]) + + xkdf = X963KDF(algorithm=hashfn(), + length=key_data_len, + sharedinfo=sharedinfo, + backend=default_backend()) + xkdf.verify(key, key_data) + + def test_unsupported_hash(self, backend): + with pytest.raises(pytest.skip.Exception): + _skip_hashfn_unsupported(backend, DummyHashAlgorithm()) diff --git a/tests/hazmat/primitives/test_x963kdf.py b/tests/hazmat/primitives/test_x963kdf.py new file mode 100644 index 00000000..c4dd8925 --- /dev/null +++ b/tests/hazmat/primitives/test_x963kdf.py @@ -0,0 +1,131 @@ +# 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 binascii + +import pytest + +from cryptography.exceptions import ( + AlreadyFinalized, InvalidKey, _Reasons +) +from cryptography.hazmat.backends.interfaces import HashBackend +from cryptography.hazmat.primitives import hashes +from cryptography.hazmat.primitives.kdf.x963kdf import X963KDF + +from ...utils import raises_unsupported_algorithm + + +@pytest.mark.requires_backend_interface(interface=HashBackend) +class TestX963KDF(object): + def test_length_limit(self, backend): + big_length = hashes.SHA256().digest_size * (2 ** 32 - 1) + 1 + + with pytest.raises(ValueError): + X963KDF(hashes.SHA256(), big_length, None, backend) + + def test_already_finalized(self, backend): + xkdf = X963KDF(hashes.SHA256(), 16, None, backend) + + xkdf.derive(b"\x01" * 16) + + with pytest.raises(AlreadyFinalized): + xkdf.derive(b"\x02" * 16) + + def test_derive(self, backend): + key = binascii.unhexlify( + b"96c05619d56c328ab95fe84b18264b08725b85e33fd34f08" + ) + + derivedkey = binascii.unhexlify(b"443024c3dae66b95e6f5670601558f71") + + xkdf = X963KDF(hashes.SHA256(), 16, None, backend) + + assert xkdf.derive(key) == derivedkey + + def test_buffer_protocol(self, backend): + key = bytearray(binascii.unhexlify( + b"96c05619d56c328ab95fe84b18264b08725b85e33fd34f08" + )) + + derivedkey = binascii.unhexlify(b"443024c3dae66b95e6f5670601558f71") + + xkdf = X963KDF(hashes.SHA256(), 16, None, backend) + + assert xkdf.derive(key) == derivedkey + + def test_verify(self, backend): + key = binascii.unhexlify( + b"22518b10e70f2a3f243810ae3254139efbee04aa57c7af7d" + ) + + sharedinfo = binascii.unhexlify(b"75eef81aa3041e33b80971203d2c0c52") + + derivedkey = binascii.unhexlify( + b"c498af77161cc59f2962b9a713e2b215152d139766ce34a776df11866a69bf2e" + b"52a13d9c7c6fc878c50c5ea0bc7b00e0da2447cfd874f6cf92f30d0097111485" + b"500c90c3af8b487872d04685d14c8d1dc8d7fa08beb0ce0ababc11f0bd496269" + b"142d43525a78e5bc79a17f59676a5706dc54d54d4d1f0bd7e386128ec26afc21" + ) + + xkdf = X963KDF(hashes.SHA256(), 128, sharedinfo, backend) + + assert xkdf.verify(key, derivedkey) is None + + def test_invalid_verify(self, backend): + key = binascii.unhexlify( + b"96c05619d56c328ab95fe84b18264b08725b85e33fd34f08" + ) + + xkdf = X963KDF(hashes.SHA256(), 16, None, backend) + + with pytest.raises(InvalidKey): + xkdf.verify(key, b"wrong derived key") + + def test_unicode_typeerror(self, backend): + with pytest.raises(TypeError): + X963KDF( + hashes.SHA256(), + 16, + sharedinfo=u"foo", + backend=backend + ) + + with pytest.raises(TypeError): + xkdf = X963KDF( + hashes.SHA256(), + 16, + sharedinfo=None, + backend=backend + ) + + xkdf.derive(u"foo") + + with pytest.raises(TypeError): + xkdf = X963KDF( + hashes.SHA256(), + 16, + sharedinfo=None, + backend=backend + ) + + xkdf.verify(u"foo", b"bar") + + with pytest.raises(TypeError): + xkdf = X963KDF( + hashes.SHA256(), + 16, + sharedinfo=None, + backend=backend + ) + + xkdf.verify(b"foo", u"bar") + + +def test_invalid_backend(): + pretend_backend = object() + + with raises_unsupported_algorithm(_Reasons.BACKEND_MISSING_INTERFACE): + X963KDF(hashes.SHA256(), 16, None, pretend_backend) diff --git a/tests/hazmat/primitives/twofactor/test_hotp.py b/tests/hazmat/primitives/twofactor/test_hotp.py index ab5f93c5..14cb08a8 100644 --- a/tests/hazmat/primitives/twofactor/test_hotp.py +++ b/tests/hazmat/primitives/twofactor/test_hotp.py @@ -35,6 +35,10 @@ class TestHOTP(object): with pytest.raises(ValueError): HOTP(secret, 6, SHA1(), backend) + def test_unenforced_invalid_kwy_length(self, backend): + secret = os.urandom(10) + HOTP(secret, 6, SHA1(), backend, enforce_key_length=False) + def test_invalid_hotp_length(self, backend): secret = os.urandom(16) @@ -105,6 +109,11 @@ class TestHOTP(object): "GNBVGY3TQOJQGEZDGNBVGY3TQOJQ&algorithm=SHA1&issuer=Foo" "&counter=1") + def test_buffer_protocol(self, backend): + key = bytearray(b"a long key with lots of entropy goes here") + hotp = HOTP(key, 6, SHA1(), backend) + assert hotp.generate(10) == b"559978" + def test_invalid_backend(): secret = b"12345678901234567890" diff --git a/tests/hazmat/primitives/twofactor/test_totp.py b/tests/hazmat/primitives/twofactor/test_totp.py index 95829713..59d875af 100644 --- a/tests/hazmat/primitives/twofactor/test_totp.py +++ b/tests/hazmat/primitives/twofactor/test_totp.py @@ -139,6 +139,12 @@ class TestTOTP(object): "DGNBVGY3TQOJQGEZDGNBVGY3TQOJQ&algorithm=SHA1&issuer=World" "&period=30") + def test_buffer_protocol(self, backend): + key = bytearray(b"a long key with lots of entropy goes here") + totp = TOTP(key, 8, hashes.SHA512(), 30, backend) + time = 60 + assert totp.generate(time) == b"53049576" + def test_invalid_backend(): secret = b"12345678901234567890" diff --git a/tests/hazmat/primitives/utils.py b/tests/hazmat/primitives/utils.py index e148bc63..4aa5ce71 100644 --- a/tests/hazmat/primitives/utils.py +++ b/tests/hazmat/primitives/utils.py @@ -18,6 +18,9 @@ from cryptography.hazmat.primitives import hashes, hmac from cryptography.hazmat.primitives.asymmetric import rsa from cryptography.hazmat.primitives.ciphers import Cipher from cryptography.hazmat.primitives.kdf.hkdf import HKDF, HKDFExpand +from cryptography.hazmat.primitives.kdf.kbkdf import ( + CounterLocation, KBKDFHMAC, Mode +) from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC from ...utils import load_vectors_from_file @@ -44,6 +47,10 @@ def generate_encrypt_test(param_loader, path, file_names, cipher_factory, def encrypt_test(backend, cipher_factory, mode_factory, params): + assert backend.cipher_supported( + cipher_factory(**params), mode_factory(**params) + ) + plaintext = params["plaintext"] ciphertext = params["ciphertext"] cipher = Cipher( @@ -161,16 +168,15 @@ def hash_test(backend, algorithm, params): assert m.finalize() == binascii.unhexlify(expected_md) -def generate_base_hash_test(algorithm, digest_size, block_size): +def generate_base_hash_test(algorithm, digest_size): def test_base_hash(self, backend): - base_hash_test(backend, algorithm, digest_size, block_size) + base_hash_test(backend, algorithm, digest_size) return test_base_hash -def base_hash_test(backend, algorithm, digest_size, block_size): +def base_hash_test(backend, algorithm, digest_size): m = hashes.Hash(algorithm, backend=backend) assert m.algorithm.digest_size == digest_size - assert m.algorithm.block_size == block_size m_copy = m.copy() assert m != m_copy assert m._ctx != m_copy._ctx @@ -182,18 +188,6 @@ def base_hash_test(backend, algorithm, digest_size, block_size): assert copy.finalize() == m.finalize() -def generate_long_string_hash_test(hash_factory, md): - def test_long_string_hash(self, backend): - long_string_hash_test(backend, hash_factory, md) - return test_long_string_hash - - -def long_string_hash_test(backend, algorithm, md): - m = hashes.Hash(algorithm, backend=backend) - m.update(b"a" * 1000000) - assert m.finalize() == binascii.unhexlify(md.lower().encode("ascii")) - - def generate_base_hmac_test(hash_cls): def test_base_hmac(self, backend): base_hmac_test(backend, hash_cls) @@ -296,8 +290,6 @@ def aead_tag_exception_test(backend, cipher_factory, mode_factory): mode_factory(binascii.unhexlify(b"0" * 24)), backend ) - with pytest.raises(ValueError): - cipher.decryptor() with pytest.raises(ValueError): mode_factory(binascii.unhexlify(b"0" * 24), b"000") @@ -370,6 +362,57 @@ def generate_hkdf_test(param_loader, path, file_names, algorithm): return test_hkdf +def generate_kbkdf_counter_mode_test(param_loader, path, file_names): + all_params = _load_all_params(path, file_names, param_loader) + + @pytest.mark.parametrize("params", all_params) + def test_kbkdf(self, backend, params): + kbkdf_counter_mode_test(backend, params) + return test_kbkdf + + +def kbkdf_counter_mode_test(backend, params): + supported_algorithms = { + 'hmac_sha1': hashes.SHA1, + 'hmac_sha224': hashes.SHA224, + 'hmac_sha256': hashes.SHA256, + 'hmac_sha384': hashes.SHA384, + 'hmac_sha512': hashes.SHA512, + } + + supported_counter_locations = { + "before_fixed": CounterLocation.BeforeFixed, + "after_fixed": CounterLocation.AfterFixed, + } + + algorithm = supported_algorithms.get(params.get('prf')) + if algorithm is None or not backend.hmac_supported(algorithm()): + pytest.skip("KBKDF does not support algorithm: {}".format( + params.get('prf') + )) + + ctr_loc = supported_counter_locations.get(params.get("ctrlocation")) + if ctr_loc is None or not isinstance(ctr_loc, CounterLocation): + pytest.skip("Does not support counter location: {}".format( + params.get('ctrlocation') + )) + + ctrkdf = KBKDFHMAC( + algorithm(), + Mode.CounterMode, + params['l'] // 8, + params['rlen'] // 8, + None, + ctr_loc, + None, + None, + binascii.unhexlify(params['fixedinputdata']), + backend=backend) + + ko = ctrkdf.derive(binascii.unhexlify(params['ki'])) + assert binascii.hexlify(ko) == params["ko"] + + def generate_rsa_verification_test(param_loader, path, file_names, hash_alg, pad_factory): all_params = _load_all_params(path, file_names, param_loader) @@ -390,17 +433,23 @@ def rsa_verification_test(backend, params, hash_alg, pad_factory): ) public_key = public_numbers.public_key(backend) pad = pad_factory(params, hash_alg) - verifier = public_key.verifier( - binascii.unhexlify(params["s"]), - pad, - hash_alg - ) - verifier.update(binascii.unhexlify(params["msg"])) + signature = binascii.unhexlify(params["s"]) + msg = binascii.unhexlify(params["msg"]) if params["fail"]: with pytest.raises(InvalidSignature): - verifier.verify() + public_key.verify( + signature, + msg, + pad, + hash_alg + ) else: - verifier.verify() + public_key.verify( + signature, + msg, + pad, + hash_alg + ) def _check_rsa_private_numbers(skey): |
