diff --git a/docs/codeql/support/reusables/frameworks.rst b/docs/codeql/support/reusables/frameworks.rst index be9949aad77..b1f07766703 100644 --- a/docs/codeql/support/reusables/frameworks.rst +++ b/docs/codeql/support/reusables/frameworks.rst @@ -169,3 +169,4 @@ Python built-in support cryptography, Cryptography library pycryptodome, Cryptography library pycryptodomex, Cryptography library + rsa, Cryptography library diff --git a/python/change-notes/2021-06-09-rsa-add-modeling.md b/python/change-notes/2021-06-09-rsa-add-modeling.md new file mode 100644 index 00000000000..44bd191e8ec --- /dev/null +++ b/python/change-notes/2021-06-09-rsa-add-modeling.md @@ -0,0 +1,2 @@ +lgtm,codescanning +* Added modeling of the PyPI package `rsa`. diff --git a/python/ql/src/semmle/python/Frameworks.qll b/python/ql/src/semmle/python/Frameworks.qll index 3115c3ffac6..13836031b89 100644 --- a/python/ql/src/semmle/python/Frameworks.qll +++ b/python/ql/src/semmle/python/Frameworks.qll @@ -16,6 +16,7 @@ private import semmle.python.frameworks.MysqlConnectorPython private import semmle.python.frameworks.MySQLdb private import semmle.python.frameworks.Psycopg2 private import semmle.python.frameworks.PyMySQL +private import semmle.python.frameworks.Rsa private import semmle.python.frameworks.Simplejson private import semmle.python.frameworks.Stdlib private import semmle.python.frameworks.Tornado diff --git a/python/ql/src/semmle/python/frameworks/Rsa.qll b/python/ql/src/semmle/python/frameworks/Rsa.qll new file mode 100644 index 00000000000..926959b8bbc --- /dev/null +++ b/python/ql/src/semmle/python/frameworks/Rsa.qll @@ -0,0 +1,141 @@ +/** + * Provides classes modeling security-relevant aspects of the `rsa` PyPI package. + * See https://stuvel.eu/python-rsa-doc/. + */ + +private import python +private import semmle.python.dataflow.new.DataFlow +private import semmle.python.dataflow.new.TaintTracking +private import semmle.python.Concepts +private import semmle.python.ApiGraphs + +/** + * Provides models for the `rsa` PyPI package. + * See https://stuvel.eu/python-rsa-doc/. + */ +private module Rsa { + /** + * A call to `rsa.newkeys` + * + * See https://stuvel.eu/python-rsa-doc/reference.html#rsa.newkeys + */ + class RsaNewkeysCall extends Cryptography::PublicKey::KeyGeneration::RsaRange, + DataFlow::CallCfgNode { + RsaNewkeysCall() { this = API::moduleImport("rsa").getMember("newkeys").getACall() } + + override DataFlow::Node getKeySizeArg() { + result in [this.getArg(0), this.getArgByName("nbits")] + } + } + + /** + * A call to `rsa.encrypt` + * + * See https://stuvel.eu/python-rsa-doc/reference.html#rsa.encrypt + */ + class RsaEncryptCall extends Cryptography::CryptographicOperation::Range, DataFlow::CallCfgNode { + RsaEncryptCall() { this = API::moduleImport("rsa").getMember("encrypt").getACall() } + + override Cryptography::CryptographicAlgorithm getAlgorithm() { result.getName() = "RSA" } + + override DataFlow::Node getAnInput() { + result in [this.getArg(0), this.getArgByName("message")] + } + } + + /** + * A call to `rsa.decrypt` + * + * See https://stuvel.eu/python-rsa-doc/reference.html#rsa.decrypt + */ + class RsaDecryptCall extends Cryptography::CryptographicOperation::Range, DataFlow::CallCfgNode { + RsaDecryptCall() { this = API::moduleImport("rsa").getMember("decrypt").getACall() } + + override Cryptography::CryptographicAlgorithm getAlgorithm() { result.getName() = "RSA" } + + override DataFlow::Node getAnInput() { result in [this.getArg(0), this.getArgByName("crypto")] } + } + + /** + * A call to `rsa.sign`, which both hashes and signs in the input message. + * + * See https://stuvel.eu/python-rsa-doc/reference.html#rsa.sign + */ + class RsaSignCall extends Cryptography::CryptographicOperation::Range, DataFlow::CallCfgNode { + RsaSignCall() { this = API::moduleImport("rsa").getMember("sign").getACall() } + + override Cryptography::CryptographicAlgorithm getAlgorithm() { + // signature part + result.getName() = "RSA" + or + // hashing part + exists(StrConst str, DataFlow::Node hashNameArg | + hashNameArg in [this.getArg(2), this.getArgByName("hash_method")] and + DataFlow::exprNode(str).(DataFlow::LocalSourceNode).flowsTo(hashNameArg) and + result.matchesName(str.getText()) + ) + } + + override DataFlow::Node getAnInput() { + result in [this.getArg(0), this.getArgByName("message")] + } + } + + /** + * A call to `rsa.verify` + * + * See https://stuvel.eu/python-rsa-doc/reference.html#rsa.verify + */ + class RsaVerifyCall extends Cryptography::CryptographicOperation::Range, DataFlow::CallCfgNode { + RsaVerifyCall() { this = API::moduleImport("rsa").getMember("verify").getACall() } + + override Cryptography::CryptographicAlgorithm getAlgorithm() { + // note that technically there is also a hashing operation going on but we don't + // know what algorithm is used up front, since it is encoded in the signature + result.getName() = "RSA" + } + + override DataFlow::Node getAnInput() { + result in [this.getArg(0), this.getArgByName("message")] + or + result in [this.getArg(1), this.getArgByName("signature")] + } + } + + /** + * A call to `rsa.compute_hash` + * + * See https://stuvel.eu/python-rsa-doc/reference.html#rsa.compute_hash + */ + class RsaComputeHashCall extends Cryptography::CryptographicOperation::Range, + DataFlow::CallCfgNode { + RsaComputeHashCall() { this = API::moduleImport("rsa").getMember("compute_hash").getACall() } + + override Cryptography::CryptographicAlgorithm getAlgorithm() { + exists(StrConst str, DataFlow::Node hashNameArg | + hashNameArg in [this.getArg(1), this.getArgByName("method_name")] and + DataFlow::exprNode(str).(DataFlow::LocalSourceNode).flowsTo(hashNameArg) and + result.matchesName(str.getText()) + ) + } + + override DataFlow::Node getAnInput() { + result in [this.getArg(0), this.getArgByName("message")] + } + } + + /** + * A call to `rsa.sign_hash` + * + * See https://stuvel.eu/python-rsa-doc/reference.html#rsa.sign_hash + */ + class RsaSignHashCall extends Cryptography::CryptographicOperation::Range, DataFlow::CallCfgNode { + RsaSignHashCall() { this = API::moduleImport("rsa").getMember("sign_hash").getACall() } + + override Cryptography::CryptographicAlgorithm getAlgorithm() { result.getName() = "RSA" } + + override DataFlow::Node getAnInput() { + result in [this.getArg(0), this.getArgByName("hash_value")] + } + } +} diff --git a/python/ql/test/library-tests/frameworks/rsa/test_rsa.py b/python/ql/test/library-tests/frameworks/rsa/test_rsa.py index d518e35b539..a6a9f0b3b5d 100644 --- a/python/ql/test/library-tests/frameworks/rsa/test_rsa.py +++ b/python/ql/test/library-tests/frameworks/rsa/test_rsa.py @@ -2,8 +2,8 @@ import rsa # using a rather low keysize, since otherwise it takes quite long to run. -(public_key, private_key) = rsa.newkeys(512) # $ MISSING: PublicKeyGeneration keySize=2048 -(public_key, private_key) = rsa.newkeys(nbits=512) # $ MISSING: PublicKeyGeneration keySize=2048 +(public_key, private_key) = rsa.newkeys(512) # $ PublicKeyGeneration keySize=512 +(public_key, private_key) = rsa.newkeys(nbits=512) # $ PublicKeyGeneration keySize=512 # ------------------------------------------------------------------------------ @@ -16,15 +16,15 @@ print("encrypt/decrypt") secret_message = b"secret message" -encrypted = rsa.encrypt(secret_message, public_key) # $ MISSING: CryptographicOperation CryptographicOperationAlgorithm=RSA CryptographicOperationInput=secret_message -encrypted = rsa.encrypt(message=secret_message, pub_key=public_key) # $ MISSING: CryptographicOperation CryptographicOperationAlgorithm=RSA CryptographicOperationInput=secret_message +encrypted = rsa.encrypt(secret_message, public_key) # $ CryptographicOperation CryptographicOperationAlgorithm=RSA CryptographicOperationInput=secret_message +encrypted = rsa.encrypt(message=secret_message, pub_key=public_key) # $ CryptographicOperation CryptographicOperationAlgorithm=RSA CryptographicOperationInput=secret_message print("encrypted={}".format(encrypted)) print() -decrypted = rsa.decrypt(encrypted, private_key) # $ MISSING: CryptographicOperation CryptographicOperationAlgorithm=RSA CryptographicOperationInput=encrypted -decrypted = rsa.decrypt(crypto=encrypted, priv_key=private_key) # $ MISSING: CryptographicOperation CryptographicOperationAlgorithm=RSA CryptographicOperationInput=encrypted +decrypted = rsa.decrypt(encrypted, private_key) # $ CryptographicOperation CryptographicOperationAlgorithm=RSA CryptographicOperationInput=encrypted +decrypted = rsa.decrypt(crypto=encrypted, priv_key=private_key) # $ CryptographicOperation CryptographicOperationAlgorithm=RSA CryptographicOperationInput=encrypted print("decrypted={}".format(decrypted)) assert decrypted == secret_message @@ -42,13 +42,13 @@ print("sign/verify") message = b"message" other_message = b"other message" -hash = rsa.compute_hash(message, "SHA-256") # $ MISSING: CryptographicOperation CryptographicOperationAlgorithm=SHA256 CryptographicOperationInput=message -hash = rsa.compute_hash(message=message, method_name="SHA-256") # $ MISSING: CryptographicOperation CryptographicOperationAlgorithm=SHA256 CryptographicOperationInput=message -signature_from_hash = rsa.sign_hash(hash, private_key, "SHA-256") # $ MISSING: CryptographicOperation CryptographicOperationAlgorithm=RSA CryptographicOperationInput=hash -signature_from_hash = rsa.sign_hash(hash_value=hash, priv_key=private_key, hash_method="SHA-256") # $ MISSING: CryptographicOperation CryptographicOperationAlgorithm=RSA CryptographicOperationInput=hash +hash = rsa.compute_hash(message, "SHA-256") # $ CryptographicOperation CryptographicOperationAlgorithm=SHA256 CryptographicOperationInput=message +hash = rsa.compute_hash(message=message, method_name="SHA-256") # $ CryptographicOperation CryptographicOperationAlgorithm=SHA256 CryptographicOperationInput=message +signature_from_hash = rsa.sign_hash(hash, private_key, "SHA-256") # $ CryptographicOperation CryptographicOperationAlgorithm=RSA CryptographicOperationInput=hash +signature_from_hash = rsa.sign_hash(hash_value=hash, priv_key=private_key, hash_method="SHA-256") # $ CryptographicOperation CryptographicOperationAlgorithm=RSA CryptographicOperationInput=hash -signature = rsa.sign(message, private_key, "SHA-256") # $ MISSING: CryptographicOperation CryptographicOperationAlgorithm=RSA CryptographicOperationAlgorithm=SHA256 CryptographicOperationInput=message -signature = rsa.sign(message=message, priv_key=private_key, hash_method="SHA-256") # $ MISSING: CryptographicOperation CryptographicOperationAlgorithm=RSA CryptographicOperationAlgorithm=SHA256 CryptographicOperationInput=message +signature = rsa.sign(message, private_key, "SHA-256") # $ CryptographicOperation CryptographicOperationAlgorithm=RSA CryptographicOperationAlgorithm=SHA256 CryptographicOperationInput=message +signature = rsa.sign(message=message, priv_key=private_key, hash_method="SHA-256") # $ CryptographicOperation CryptographicOperationAlgorithm=RSA CryptographicOperationAlgorithm=SHA256 CryptographicOperationInput=message assert signature == signature_from_hash @@ -56,13 +56,13 @@ print("signature={}".format(signature)) print() -rsa.verify(message, signature, public_key) # $ MISSING: CryptographicOperation CryptographicOperationAlgorithm=RSA CryptographicOperationInput=message CryptographicOperationInput=signature -rsa.verify(message=message, signature=signature, pub_key=public_key) # $ MISSING: CryptographicOperation CryptographicOperationAlgorithm=RSA CryptographicOperationInput=message CryptographicOperationInput=signature +rsa.verify(message, signature, public_key) # $ CryptographicOperation CryptographicOperationAlgorithm=RSA CryptographicOperationInput=message CryptographicOperationInput=signature +rsa.verify(message=message, signature=signature, pub_key=public_key) # $ CryptographicOperation CryptographicOperationAlgorithm=RSA CryptographicOperationInput=message CryptographicOperationInput=signature print("Signature verified (as expected)") try: - rsa.verify(other_message, signature, public_key) # $ MISSING: CryptographicOperation CryptographicOperationAlgorithm=RSA CryptographicOperationInput=other_message CryptographicOperationInput=signature + rsa.verify(other_message, signature, public_key) # $ CryptographicOperation CryptographicOperationAlgorithm=RSA CryptographicOperationInput=other_message CryptographicOperationInput=signature raise Exception("Signature verified (unexpected)") except rsa.VerificationError: print("Signature mismatch (as expected)")