Merge pull request #5075 from RasmusWL/crypto

Python: Port py/weak-crypto-key to use type-tracking
This commit is contained in:
yoff
2021-03-18 20:53:28 +01:00
committed by GitHub
32 changed files with 998 additions and 45 deletions

View File

@@ -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()

View File

@@ -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 }
}
}
}
}

View File

@@ -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

View File

@@ -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() }
}
}

View File

@@ -0,0 +1,184 @@
/**
* Provides classes modeling security-relevant aspects of the `cryptography` PyPI package.
* See https://cryptography.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 `cryptography` PyPI package.
* See https://cryptography.io/en/latest/.
*/
private module CryptographyModel {
/**
* Provides helper predicates for the eliptic curve cryptography parts in
* `cryptography.hazmat.primitives.asymmetric.ec`.
*/
module Ecc {
/**
* Gets a predefined curve class from
* `cryptography.hazmat.primitives.asymmetric.ec` with a specific key size (in bits).
*/
private DataFlow::Node curveClassWithKeySize(int keySize) {
exists(string curveName |
result =
API::moduleImport("cryptography")
.getMember("hazmat")
.getMember("primitives")
.getMember("asymmetric")
.getMember("ec")
.getMember(curveName)
.getAUse()
|
// obtained by manually looking at source code in
// https://github.com/pyca/cryptography/blob/cba69f1922803f4f29a3fde01741890d88b8e217/src/cryptography/hazmat/primitives/asymmetric/ec.py#L208-L300
curveName = "SECT571R1" and keySize = 570 // Indeed the numbers do not match.
or
curveName = "SECT409R1" and keySize = 409
or
curveName = "SECT283R1" and keySize = 283
or
curveName = "SECT233R1" and keySize = 233
or
curveName = "SECT163R2" and keySize = 163
or
curveName = "SECT571K1" and keySize = 571
or
curveName = "SECT409K1" and keySize = 409
or
curveName = "SECT283K1" and keySize = 283
or
curveName = "SECT233K1" and keySize = 233
or
curveName = "SECT163K1" and keySize = 163
or
curveName = "SECP521R1" and keySize = 521
or
curveName = "SECP384R1" and keySize = 384
or
curveName = "SECP256R1" and keySize = 256
or
curveName = "SECP256K1" and keySize = 256
or
curveName = "SECP224R1" and keySize = 224
or
curveName = "SECP192R1" and keySize = 192
or
curveName = "BrainpoolP256R1" and keySize = 256
or
curveName = "BrainpoolP384R1" and keySize = 384
or
curveName = "BrainpoolP512R1" and keySize = 512
)
}
/** Gets a reference to a predefined curve class instance with a specific key size (in bits), as well as the origin of the class. */
private DataFlow::Node curveClassInstanceWithKeySize(
DataFlow::TypeTracker t, int keySize, DataFlow::Node origin
) {
t.start() and
result.asCfgNode().(CallNode).getFunction() = curveClassWithKeySize(keySize).asCfgNode() and
origin = result
or
// Due to bad performance when using normal setup with we have inlined that code and forced a join
exists(DataFlow::TypeTracker t2 |
exists(DataFlow::StepSummary summary |
curveClassInstanceWithKeySize_first_join(t2, keySize, origin, result, summary) and
t = t2.append(summary)
)
)
}
pragma[nomagic]
private predicate curveClassInstanceWithKeySize_first_join(
DataFlow::TypeTracker t2, int keySize, DataFlow::Node origin, DataFlow::Node res,
DataFlow::StepSummary summary
) {
DataFlow::StepSummary::step(curveClassInstanceWithKeySize(t2, keySize, origin), res, summary)
}
/** Gets a reference to a predefined curve class instance with a specific key size (in bits), as well as the origin of the class. */
DataFlow::Node curveClassInstanceWithKeySize(int keySize, DataFlow::Node origin) {
result = curveClassInstanceWithKeySize(DataFlow::TypeTracker::end(), keySize, origin)
}
}
// ---------------------------------------------------------------------------
/**
* A call to `cryptography.hazmat.primitives.asymmetric.rsa.generate_private_key`
*
* See https://cryptography.io/en/latest/hazmat/primitives/asymmetric/rsa.html#cryptography.hazmat.primitives.asymmetric.rsa.generate_private_key
*/
class CryptographyRsaGeneratePrivateKeyCall extends Cryptography::PublicKey::KeyGeneration::RsaRange,
DataFlow::CallCfgNode {
CryptographyRsaGeneratePrivateKeyCall() {
this =
API::moduleImport("cryptography")
.getMember("hazmat")
.getMember("primitives")
.getMember("asymmetric")
.getMember("rsa")
.getMember("generate_private_key")
.getACall()
}
override DataFlow::Node getKeySizeArg() {
result in [this.getArg(1), this.getArgByName("key_size")]
}
}
/**
* A call to `cryptography.hazmat.primitives.asymmetric.dsa.generate_private_key`
*
* See https://cryptography.io/en/latest/hazmat/primitives/asymmetric/dsa.html#cryptography.hazmat.primitives.asymmetric.dsa.generate_private_key
*/
class CryptographyDsaGeneratePrivateKeyCall extends Cryptography::PublicKey::KeyGeneration::DsaRange,
DataFlow::CallCfgNode {
CryptographyDsaGeneratePrivateKeyCall() {
this =
API::moduleImport("cryptography")
.getMember("hazmat")
.getMember("primitives")
.getMember("asymmetric")
.getMember("dsa")
.getMember("generate_private_key")
.getACall()
}
override DataFlow::Node getKeySizeArg() {
result in [this.getArg(0), this.getArgByName("key_size")]
}
}
/**
* A call to `cryptography.hazmat.primitives.asymmetric.ec.generate_private_key`
*
* See https://cryptography.io/en/latest/hazmat/primitives/asymmetric/ec.html#cryptography.hazmat.primitives.asymmetric.ec.generate_private_key
*/
class CryptographyEcGeneratePrivateKeyCall extends Cryptography::PublicKey::KeyGeneration::EccRange,
DataFlow::CallCfgNode {
CryptographyEcGeneratePrivateKeyCall() {
this =
API::moduleImport("cryptography")
.getMember("hazmat")
.getMember("primitives")
.getMember("asymmetric")
.getMember("ec")
.getMember("generate_private_key")
.getACall()
}
/** Gets the argument that specifies the curve to use. */
DataFlow::Node getCurveArg() { result in [this.getArg(0), this.getArgByName("curve")] }
override int getKeySizeWithOrigin(DataFlow::Node origin) {
this.getCurveArg() = Ecc::curveClassInstanceWithKeySize(result, origin)
}
// Note: There is not really a key-size argument, since it's always specified by the curve.
override DataFlow::Node getKeySizeArg() { none() }
}
}