Skip to content

Python: Port py/weak-crypto-key to use type-tracking #5075

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 26 commits into from
Mar 18, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
4ab61bb
Python: Add a few tests for crypto frameworks
RasmusWL Feb 2, 2021
11cd0db
Python: Add concepts for public-key generation
RasmusWL Feb 2, 2021
1bf9f7d
Python: Add missing annotations to new crypto tests
RasmusWL Feb 2, 2021
bd40965
Python: Add modeling for `cryptography` PyPI package
RasmusWL Feb 2, 2021
6e4c627
Python: Add modeling for `pycryptodomex` PyPI package
RasmusWL Feb 2, 2021
d5ff477
Python: Add modeling for `pycryptodome` PyPI package
RasmusWL Feb 2, 2021
2429c6c
Python: Rewrite py/weak-crypto-key tests
RasmusWL Feb 2, 2021
46ad611
Python: Port py/weak-crypto-key to use type-tracking
RasmusWL Feb 2, 2021
0e9a54e
Python: Rename WeakCrypto to WeakCryptoKey
RasmusWL Feb 2, 2021
32d0790
Python: Use camelCase for RSA/DSA/ECC
RasmusWL Feb 3, 2021
8d3170b
Python: Fix bad join in crypto models
RasmusWL Feb 16, 2021
bfbaa85
Python: Add test of public_key method with cryptodome
RasmusWL Feb 16, 2021
1eabfbd
Python: Port cryptography models to use API graphs (mostly)
RasmusWL Feb 17, 2021
2a8f720
Python: Port cryptodome models to use API graphs
RasmusWL Feb 17, 2021
37f0d5a
Python: Make KeyGeneration range member overrides final
RasmusWL Feb 17, 2021
a658334
Python: Add weak crypto key example through function call
RasmusWL Feb 19, 2021
dfa223a
Python: Better IntegerLiteral tracking for weak crypto key
RasmusWL Feb 19, 2021
bfc8ead
Python: Add example of test-code with weak crypto key
RasmusWL Feb 19, 2021
d084261
Python: Ignore weak key-sizes from test-code in weak-crypto-key
RasmusWL Feb 19, 2021
40c592a
Python: Introduce DataFlowOnlyInternalUse to avoid re-evaluation
RasmusWL Feb 19, 2021
fd18fd8
Python: Apply suggestions from code review
RasmusWL Feb 23, 2021
2798771
Merge branch 'main' into crypto
RasmusWL Feb 25, 2021
c195c64
Python: Use type-tracking for integer literal tracking
RasmusWL Feb 23, 2021
4610b1b
Pyhton: Use type back-tracking for keysize on key-generation
RasmusWL Feb 24, 2021
472ff97
Docs: Add crypto to supported Python frameworks
RasmusWL Feb 25, 2021
7b92012
Python: Apply suggestions from code review
RasmusWL Mar 18, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions docs/codeql/support/reusables/frameworks.rst
Original file line number Diff line number Diff line change
Expand Up @@ -147,3 +147,6 @@ Python built-in support
MySQLdb, Database
psycopg2, Database
sqlite3, Database
cryptography, Cryptography library
pycryptodome, Cryptography library
pycryptodomex, Cryptography library
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
lgtm,codescanning
* Updated _Use of weak cryptographic key_ (`py/weak-crypto-key`) query to use the new type-tracking approach instead of points-to analysis. You may see differences in the results found by the query, but overall this change should result in a more robust and accurate analysis.
* Renamed the query file for _Use of weak cryptographic key_ (`py/weak-crypto-key`) from `WeakCrypto.ql` to `WeakCryptoKey.ql` (in the `python/ql/src/Security/CWE-326/` folder). This will affect any custom query suites that include or exclude this query using its path.
24 changes: 24 additions & 0 deletions python/ql/src/Security/CWE-326/WeakCryptoKey.ql
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/**
* @name Use of weak cryptographic key
* @description Use of a cryptographic key that is too small may allow the encryption to be broken.
* @kind problem
* @problem.severity error
* @precision high
* @id py/weak-crypto-key
* @tags security
* external/cwe/cwe-326
*/

import python
import semmle.python.Concepts
import semmle.python.dataflow.new.DataFlow
import semmle.python.filters.Tests

from Cryptography::PublicKey::KeyGeneration keyGen, int keySize, DataFlow::Node origin
where
keySize = keyGen.getKeySizeWithOrigin(origin) and
keySize < keyGen.minimumSecureKeySize() and
not origin.getScope().getScope*() instanceof TestScope
select keyGen,
"Creation of an " + keyGen.getName() + " key uses $@ bits, which is below " +
keyGen.minimumSecureKeySize() + " and considered breakable.", origin, keySize.toString()
113 changes: 113 additions & 0 deletions python/ql/src/semmle/python/Concepts.qll
Original file line number Diff line number Diff line change
Expand Up @@ -526,3 +526,116 @@ module HTTP {
}
}
}

/** Provides models for cryptographic things. */
module Cryptography {
/** Provides models for public-key cryptography, also called asymmetric cryptography. */
module PublicKey {
/**
* A data-flow node that generates a new key-pair for use with public-key cryptography.
*
* Extend this class to refine existing API models. If you want to model new APIs,
* extend `KeyGeneration::Range` instead.
*/
class KeyGeneration extends DataFlow::Node {
KeyGeneration::Range range;

KeyGeneration() { this = range }

/** Gets the name of the cryptographic algorithm (for example `"RSA"` or `"AES"`). */
string getName() { result = range.getName() }

/** Gets the argument that specifies the size of the key in bits, if available. */
DataFlow::Node getKeySizeArg() { result = range.getKeySizeArg() }

/**
* Gets the size of the key generated (in bits), as well as the `origin` that
* explains how we obtained this specific key size.
*/
int getKeySizeWithOrigin(DataFlow::Node origin) {
result = range.getKeySizeWithOrigin(origin)
}

/** Gets the minimum key size (in bits) for this algorithm to be considered secure. */
int minimumSecureKeySize() { result = range.minimumSecureKeySize() }
}

/** Provides classes for modeling new key-pair generation APIs. */
module KeyGeneration {
/** Gets a back-reference to the keysize argument `arg` that was used to generate a new key-pair. */
DataFlow::LocalSourceNode keysizeBacktracker(DataFlow::TypeBackTracker t, DataFlow::Node arg) {
t.start() and
arg = any(KeyGeneration::Range r).getKeySizeArg() and
result = arg.getALocalSource()
or
// Due to bad performance when using normal setup with we have inlined that code and forced a join
exists(DataFlow::TypeBackTracker t2 |
exists(DataFlow::StepSummary summary |
keysizeBacktracker_first_join(t2, arg, result, summary) and
t = t2.prepend(summary)
)
)
}

pragma[nomagic]
private predicate keysizeBacktracker_first_join(
DataFlow::TypeBackTracker t2, DataFlow::Node arg, DataFlow::Node res,
DataFlow::StepSummary summary
) {
DataFlow::StepSummary::step(res, keysizeBacktracker(t2, arg), summary)
}

/** Gets a back-reference to the keysize argument `arg` that was used to generate a new key-pair. */
DataFlow::LocalSourceNode keysizeBacktracker(DataFlow::Node arg) {
result = keysizeBacktracker(DataFlow::TypeBackTracker::end(), arg)
}

/**
* A data-flow node that generates a new key-pair for use with public-key cryptography.
*
* Extend this class to model new APIs. If you want to refine existing API models,
* extend `KeyGeneration` instead.
*/
abstract class Range extends DataFlow::Node {
/** Gets the name of the cryptographic algorithm (for example `"RSA"`). */
abstract string getName();

/** Gets the argument that specifies the size of the key in bits, if available. */
abstract DataFlow::Node getKeySizeArg();

/**
* Gets the size of the key generated (in bits), as well as the `origin` that
* explains how we obtained this specific key size.
*/
int getKeySizeWithOrigin(DataFlow::Node origin) {
origin = keysizeBacktracker(this.getKeySizeArg()) and
result = origin.asExpr().(IntegerLiteral).getValue()
}

/** Gets the minimum key size (in bits) for this algorithm to be considered secure. */
abstract int minimumSecureKeySize();
}

/** A data-flow node that generates a new RSA key-pair. */
abstract class RsaRange extends Range {
final override string getName() { result = "RSA" }

final override int minimumSecureKeySize() { result = 2048 }
}

/** A data-flow node that generates a new DSA key-pair. */
abstract class DsaRange extends Range {
final override string getName() { result = "DSA" }

final override int minimumSecureKeySize() { result = 2048 }
}

/** A data-flow node that generates a new ECC key-pair. */
abstract class EccRange extends Range {
final override string getName() { result = "ECC" }

final override int minimumSecureKeySize() { result = 224 }
}
}
}
}
2 changes: 2 additions & 0 deletions python/ql/src/semmle/python/Frameworks.qll
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
* Helper file that imports all framework modeling.
*/

private import semmle.python.frameworks.Cryptodome
private import semmle.python.frameworks.Cryptography
private import semmle.python.frameworks.Dill
private import semmle.python.frameworks.Django
private import semmle.python.frameworks.Fabric
Expand Down
104 changes: 104 additions & 0 deletions python/ql/src/semmle/python/frameworks/Cryptodome.qll
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
/**
* Provides classes modeling security-relevant aspects of
* - the `pycryptodome` PyPI package (imported as `Crypto`)
* - the `pycryptodomex` PyPI package (imported as `Cryptodome`)
* See https://pycryptodome.readthedocs.io/en/latest/.
*/

private import python
private import semmle.python.dataflow.new.DataFlow
private import semmle.python.Concepts
private import semmle.python.ApiGraphs

/**
* Provides models for
* - the `pycryptodome` PyPI package (imported as `Crypto`)
* - the `pycryptodomex` PyPI package (imported as `Cryptodome`)
* See https://pycryptodome.readthedocs.io/en/latest/
*/
private module CryptodomeModel {
// ---------------------------------------------------------------------------
/**
* A call to `Cryptodome.PublicKey.RSA.generate`/`Crypto.PublicKey.RSA.generate`
*
* See https://pycryptodome.readthedocs.io/en/latest/src/public_key/rsa.html#Crypto.PublicKey.RSA.generate
*/
class CryptodomePublicKeyRsaGenerateCall extends Cryptography::PublicKey::KeyGeneration::RsaRange,
DataFlow::CallCfgNode {
CryptodomePublicKeyRsaGenerateCall() {
this =
API::moduleImport(["Crypto", "Cryptodome"])
.getMember("PublicKey")
.getMember("RSA")
.getMember("generate")
.getACall()
}

override DataFlow::Node getKeySizeArg() {
result in [this.getArg(0), this.getArgByName("bits")]
}
}

/**
* A call to `Cryptodome.PublicKey.DSA.generate`/`Crypto.PublicKey.DSA.generate`
*
* See https://pycryptodome.readthedocs.io/en/latest/src/public_key/dsa.html#Crypto.PublicKey.DSA.generate
*/
class CryptodomePublicKeyDsaGenerateCall extends Cryptography::PublicKey::KeyGeneration::DsaRange,
DataFlow::CallCfgNode {
CryptodomePublicKeyDsaGenerateCall() {
this =
API::moduleImport(["Crypto", "Cryptodome"])
.getMember("PublicKey")
.getMember("DSA")
.getMember("generate")
.getACall()
}

override DataFlow::Node getKeySizeArg() {
result in [this.getArg(0), this.getArgByName("bits")]
}
}

/**
* A call to `Cryptodome.PublicKey.ECC.generate`/`Crypto.PublicKey.ECC.generate`
*
* See https://pycryptodome.readthedocs.io/en/latest/src/public_key/ecc.html#Crypto.PublicKey.ECC.generate
*/
class CryptodomePublicKeyEccGenerateCall extends Cryptography::PublicKey::KeyGeneration::EccRange,
DataFlow::CallCfgNode {
CryptodomePublicKeyEccGenerateCall() {
this =
API::moduleImport(["Crypto", "Cryptodome"])
.getMember("PublicKey")
.getMember("ECC")
.getMember("generate")
.getACall()
}

/** Gets the argument that specifies the curve to use (a string). */
DataFlow::Node getCurveArg() { result = this.getArgByName("curve") }

/** Gets the name of the curve to use, as well as the origin that explains how we obtained this name. */
string getCurveWithOrigin(DataFlow::Node origin) {
exists(StrConst str | origin = DataFlow::exprNode(str) |
origin = this.getCurveArg().getALocalSource() and
result = str.getText()
)
}

override int getKeySizeWithOrigin(DataFlow::Node origin) {
exists(string curve | curve = this.getCurveWithOrigin(origin) |
// using list from https://pycryptodome.readthedocs.io/en/latest/src/public_key/ecc.html
curve in ["NIST P-256", "p256", "P-256", "prime256v1", "secp256r1"] and result = 256
or
curve in ["NIST P-384", "p384", "P-384", "prime384v1", "secp384r1"] and result = 384
or
curve in ["NIST P-521", "p521", "P-521", "prime521v1", "secp521r1"] and result = 521
)
}

// Note: There is not really a key-size argument, since it's always specified by the curve.
override DataFlow::Node getKeySizeArg() { none() }
}
}
Loading