From 0a577f26249cad3c1df631a85e7797d28f0d955d Mon Sep 17 00:00:00 2001 From: Arthur Chan Date: Wed, 22 Jun 2022 13:25:03 +0100 Subject: [PATCH] pyca-cryptography: Add more fuzzer (#7890) * Add more fuzzer Fix build script * Fix formatting for fuzzers Fix length checking for nonce in fuzz_aead * Fix fuzzer - Add more randomization to data input --- projects/cryptography/Dockerfile | 3 +- projects/cryptography/build.sh | 4 +- projects/cryptography/fuzz_aead.py | 56 +++++++++++++++++++++++ projects/cryptography/fuzz_dh.py | 66 ++++++++++++++++++++++++++++ projects/cryptography/fuzz_dsa.py | 48 ++++++++++++++++++++ projects/cryptography/fuzz_sym.py | 51 +++++++++++++++++++++ projects/cryptography/symenc_fuzz.py | 51 --------------------- 7 files changed, 226 insertions(+), 53 deletions(-) create mode 100644 projects/cryptography/fuzz_aead.py create mode 100644 projects/cryptography/fuzz_dh.py create mode 100644 projects/cryptography/fuzz_dsa.py create mode 100644 projects/cryptography/fuzz_sym.py delete mode 100644 projects/cryptography/symenc_fuzz.py diff --git a/projects/cryptography/Dockerfile b/projects/cryptography/Dockerfile index 584407b7d..411c17d3f 100644 --- a/projects/cryptography/Dockerfile +++ b/projects/cryptography/Dockerfile @@ -17,6 +17,7 @@ FROM gcr.io/oss-fuzz-base/base-builder-python RUN git clone https://github.com/pyca/cryptography +RUN apt-get update RUN apt-get install build-essential libssl-dev libffi-dev python3-dev cargo -y RUN pip3 install --upgrade pip RUN curl https://sh.rustup.rs -sSf | sh -s -- -y @@ -26,4 +27,4 @@ RUN rustup install nightly RUN rustup default nightly WORKDIR cryptography -COPY build.sh symenc_fuzz.py $SRC/ +COPY build.sh fuzz_*.py $SRC/ diff --git a/projects/cryptography/build.sh b/projects/cryptography/build.sh index 4447511b3..6fc8d0545 100644 --- a/projects/cryptography/build.sh +++ b/projects/cryptography/build.sh @@ -21,4 +21,6 @@ unset RUSTFLAGS pip3 install . cd $SRC -compile_python_fuzzer symenc_fuzz.py +for fuzzer in $(find $SRC -name 'fuzz_*.py'); do + compile_python_fuzzer $fuzzer +done diff --git a/projects/cryptography/fuzz_aead.py b/projects/cryptography/fuzz_aead.py new file mode 100644 index 000000000..7e980f41c --- /dev/null +++ b/projects/cryptography/fuzz_aead.py @@ -0,0 +1,56 @@ +#!/usr/bin/python3 + +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import sys +import atheris +with atheris.instrument_imports(): + import cryptography.hazmat.primitives.ciphers.aead as aead + +def TestInput(input_bytes): + if len(input_bytes) < 12: + return + + fdp = atheris.FuzzedDataProvider(input_bytes) + + choice = fdp.ConsumeIntInRange(1,4) + + if choice == 1: + cipher = aead.ChaCha20Poly1305(aead.ChaCha20Poly1305.generate_key()) + if choice == 2: + cipher = aead.AESGCM(aead.AESGCM.generate_key(bit_length=128)) + if choice == 3: + cipher = aead.AESOCB3(aead.AESOCB3.generate_key(bit_length=128)) + if choice == 4: + cipher = aead.AESCCM(aead.AESCCM.generate_key(bit_length=128)) + + msg = fdp.ConsumeBytes(32) + authentext = fdp.ConsumeBytes(32) + nonce = fdp.ConsumeBytes(12) + + if len(nonce) < 12: + return + + ciphertext = cipher.encrypt(nonce, msg, authentext) + plaintext = cipher.decrypt(nonce, ciphertext, authentext) + + assert (plaintext == msg), "Encryption/Decrption error!" + +def main(): + atheris.Setup(sys.argv, TestInput, enable_python_coverage=False) + atheris.instrument_all() + atheris.Fuzz() + +if __name__ == "__main__": + main() diff --git a/projects/cryptography/fuzz_dh.py b/projects/cryptography/fuzz_dh.py new file mode 100644 index 000000000..0639f9b85 --- /dev/null +++ b/projects/cryptography/fuzz_dh.py @@ -0,0 +1,66 @@ +#!/usr/bin/python3 + +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import sys +import atheris +with atheris.instrument_imports(): + from cryptography.hazmat.primitives import hashes + from cryptography.hazmat.primitives.asymmetric import dh + from cryptography.hazmat.primitives.kdf.hkdf import HKDF + +def TestInput(input_bytes): + fdp = atheris.FuzzedDataProvider(input_bytes) + + try: + parameters = dh.generate_parameters( + generator=(2 if fdp.ConsumeBool() else 5), + key_size=fdp.ConsumeInt(15) + ) + except ValueError as e: + if "DH key_size must be at least 512 bits" not in str(e): + raise e + else: + return + + server_private_key = parameters.generate_private_key() + peer_private_key = parameters.generate_private_key() + server_derived_shared_key = server_private_key.exchange(peer_private_key.public_key()) + peer_derived_shared_key = peer_private_key.exchange(server_private_key.public_key()) + + infobytes = fdp.ConsumeBytes(10) + + server_derived_key = HKDF( + algorithm=hashes.SHA256(), + length=32, + salt=None, + info=infobytes, + ).derive(server_derived_shared_key) + + peer_derived_key = HKDF( + algorithm=hashes.SHA256(), + length=32, + salt=None, + info=infobytes, + ).derive(peer_derived_shared_key) + + assert (server_derived_key == peer_derived_key), "Key Derivation Error!!" + +def main(): + atheris.Setup(sys.argv, TestInput, enable_python_coverage=False) + atheris.instrument_all() + atheris.Fuzz() + +if __name__ == "__main__": + main() diff --git a/projects/cryptography/fuzz_dsa.py b/projects/cryptography/fuzz_dsa.py new file mode 100644 index 000000000..7fc675d4f --- /dev/null +++ b/projects/cryptography/fuzz_dsa.py @@ -0,0 +1,48 @@ +#!/usr/bin/python3 + +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import sys +import atheris +with atheris.instrument_imports(): + from cryptography.hazmat.primitives import hashes + from cryptography.hazmat.primitives.asymmetric import dsa, utils + +def TestInput(input_bytes): + fdp = atheris.FuzzedDataProvider(input_bytes) + + private_key = dsa.generate_private_key(key_size=1024) + public_key = private_key.public_key() + + data = fdp.ConsumeBytes(20) + more_data = fdp.ConsumeBytes(20) + + hasher = hashes.Hash(hashes.SHA256()) + hasher.update(data) + hasher.update(more_data) + digest = hasher.finalize() + + sig1 = private_key.sign(data,hashes.SHA256()) + sig2 = private_key.sign(digest,utils.Prehashed(hashes.SHA256())) + + public_key.verify(sig1,data,hashes.SHA256()) + public_key.verify(sig2,digest,utils.Prehashed(hashes.SHA256())) + +def main(): + atheris.Setup(sys.argv, TestInput, enable_python_coverage=False) + atheris.instrument_all() + atheris.Fuzz() + +if __name__ == "__main__": + main() diff --git a/projects/cryptography/fuzz_sym.py b/projects/cryptography/fuzz_sym.py new file mode 100644 index 000000000..cd7ad12f8 --- /dev/null +++ b/projects/cryptography/fuzz_sym.py @@ -0,0 +1,51 @@ +#!/usr/bin/python3 + +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import sys +import base64 +import atheris +with atheris.instrument_imports(): + from cryptography.fernet import Fernet + from cryptography.hazmat.primitives import hashes + from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC + +def TestInput(input_bytes): + fdp = atheris.FuzzedDataProvider(input_bytes) + plaintext = fdp.ConsumeBytes(32) + + key = Fernet(Fernet.generate_key()) + token = key.encrypt(plaintext) + text = key.decrypt(token) + assert (plaintext == text), "Encryption/Decrption error!" + + password = fdp.ConsumeBytes(8) + salt = fdp.ConsumeBytes(16) + kdf = PBKDF2HMAC( + algorithm=hashes.SHA256(), + length=32, + salt=salt, + iterations=1, + ) + key = Fernet(base64.urlsafe_b64encode(kdf.derive(password))) + token = key.encrypt(plaintext) + text = key.decrypt(token) + assert (plaintext == text), "Encryption/Decrption error!" + +def main(): + atheris.Setup(sys.argv, TestInput, enable_python_coverage=False) + atheris.Fuzz() + +if __name__ == "__main__": + main() diff --git a/projects/cryptography/symenc_fuzz.py b/projects/cryptography/symenc_fuzz.py deleted file mode 100644 index 2f1934c77..000000000 --- a/projects/cryptography/symenc_fuzz.py +++ /dev/null @@ -1,51 +0,0 @@ -#!/usr/bin/python3 - -# Copyright 2022 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -import sys -import base64 -import atheris -with atheris.instrument_imports(): - from cryptography.fernet import Fernet - from cryptography.hazmat.primitives import hashes - from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC - -def TestInput(input_bytes): - fdp = atheris.FuzzedDataProvider(input_bytes) - plaintext = fdp.ConsumeBytes(32) - - key = Fernet(Fernet.generate_key()) - token = key.encrypt(plaintext) - text = key.decrypt(token) - assert (plaintext == text), "Encryption/Decrption error!" - - password = fdp.ConsumeBytes(8) - salt = fdp.ConsumeBytes(16) - kdf = PBKDF2HMAC( - algorithm=hashes.SHA256(), - length=32, - salt=salt, - iterations=1, - ) - key = Fernet(base64.urlsafe_b64encode(kdf.derive(password))) - token = key.encrypt(plaintext) - text = key.decrypt(token) - assert (plaintext == text), "Encryption/Decrption error!" - -def main(): - atheris.Setup(sys.argv, TestInput, enable_python_coverage=False) - atheris.Fuzz() - -if __name__ == "__main__": - main()