mirror of
https://github.com/github/codeql.git
synced 2025-12-17 17:23:36 +01:00
1037 lines
38 KiB
Plaintext
1037 lines
38 KiB
Plaintext
import python
|
|
import semmle.python.ApiGraphs
|
|
import experimental.cryptography.CryptoArtifact
|
|
import experimental.cryptography.CryptoAlgorithmNames
|
|
import experimental.cryptography.utils.CallCfgNodeWithTarget
|
|
private import experimental.cryptography.utils.Utils as Utils
|
|
|
|
/**
|
|
* Provides models for the `cryptography` PyPI package.
|
|
* See https://cryptography.io/en/latest/.
|
|
*/
|
|
// -----------------------------------------------
|
|
// Hash Artifacts
|
|
// https://cryptography.io/en/latest/hazmat/primitives/cryptographic-hashes/#module-cryptography.hazmat.primitives.hashes
|
|
// -----------------------------------------------
|
|
module Hashes {
|
|
/**
|
|
* Gets a member access of cryptography.hazmat.primitives.hashes
|
|
* that is a hash algorithm invocation.
|
|
* `hashName` is the name of the hash algorithm.
|
|
*/
|
|
// Copying use of nomagic from similar predicate in codeql/main
|
|
pragma[nomagic]
|
|
DataFlow::Node cryptographyMemberHashAlgorithm(string hashName) {
|
|
result =
|
|
API::moduleImport("cryptography")
|
|
.getMember("hazmat")
|
|
.getMember("primitives")
|
|
.getMember("hashes")
|
|
.getMember(hashName)
|
|
.asSource() and
|
|
// Don't matches known non-hash members
|
|
// https://github.com/pyca/cryptography/blob/main/src/cryptography/hazmat/primitives/hashes.py#L69-L111
|
|
not hashName in [
|
|
"abc", "already_finalized", "ExtendableOutputFunction", "Hash", "HashAlgorithm",
|
|
"HashContext", "typing", "utils"
|
|
] and
|
|
// Don't match things like __file__
|
|
not hashName.regexpMatch("_.*")
|
|
}
|
|
|
|
/**
|
|
* Identifies hashing algorithm members (i.e., functions) of the `cryptography` module,
|
|
* e.g., `cryptography.hazmat.primitives.hashes.SHA256`.
|
|
* https://cryptography.io/en/latest/hazmat/primitives/cryptographic-hashes/#cryptography.hazmat.primitives.hashes.Hash
|
|
*/
|
|
class CryptographyGenericHashAlgorithm extends HashAlgorithm {
|
|
CryptographyGenericHashAlgorithm() { this = cryptographyMemberHashAlgorithm(_) }
|
|
|
|
override string getName() {
|
|
exists(string rawName | this = cryptographyMemberHashAlgorithm(rawName) |
|
|
result = super.normalizeName(rawName)
|
|
)
|
|
}
|
|
}
|
|
// NOTE: no need to model hashes used for PBKDF2HMAC (and other similar KDF HMAC), the API requires the specified algorithm
|
|
// is an instance of `HashAlgorithm` handled by `CryptographyGenericHashArtifact`
|
|
}
|
|
|
|
// https://cryptography.io/en/latest/hazmat/primitives/key-derivation-functions/#module-cryptography.hazmat.primitives.kdf
|
|
module KDF {
|
|
DataFlow::Node genericKDFArtifact(API::Node algModule, string algName) {
|
|
exists(string member |
|
|
algModule =
|
|
API::moduleImport("cryptography")
|
|
.getMember("hazmat")
|
|
.getMember("primitives")
|
|
.getMember("kdf")
|
|
.getMember(member)
|
|
.getMember(algName) and
|
|
result = algModule.asSource() and
|
|
// https://github.com/pyca/cryptography/tree/main/src/cryptography/hazmat/primitives/kdf
|
|
member in ["concatkdf", "hkdf", "kbkdf", "pbkdf2", "scrypt", "x963kdf"] and
|
|
algName in [
|
|
"ConcatKDFHash", "ConcatKDFHMAC", "HKDF", "HKDFExpand", "KBKDFCMAC", "KBKDFHMAC",
|
|
"PBKDF2HMAC", "Scrypt", "X963KDF"
|
|
]
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Identifies key derivation function members (i.e., functions) of the `cryptography` module
|
|
* https://cryptography.io/en/latest/hazmat/primitives/key-derivation-functions/#module-cryptography.hazmat.primitives.kdf
|
|
*/
|
|
class CryptographyKDFAlgorithm extends KeyDerivationAlgorithm {
|
|
CryptographyKDFAlgorithm() { this = genericKDFArtifact(_, _) }
|
|
|
|
override string getName() {
|
|
exists(string rawName | this = genericKDFArtifact(_, rawName) |
|
|
// TODO: is HKDFExpand ok to categorize as HKDF?
|
|
result = super.normalizeName(rawName)
|
|
)
|
|
}
|
|
|
|
API::Node getModule() { this = genericKDFArtifact(result, _) }
|
|
}
|
|
|
|
API::CallNode getCryptographyKDFOperation(CryptographyKDFAlgorithm kdf) {
|
|
result = kdf.getModule().getACall()
|
|
}
|
|
|
|
class CryptographyKDFOperation extends KeyDerivationOperation {
|
|
CryptographyKDFOperation() { this = getCryptographyKDFOperation(_) }
|
|
|
|
override KeyDerivationAlgorithm getAlgorithm() { this = getCryptographyKDFOperation(result) }
|
|
|
|
override predicate requiresHash() { this.getAlgorithm().getKDFName() != "KBKDFCMAC" }
|
|
|
|
override predicate requiresMode() {
|
|
this.getAlgorithm().getKDFName() in ["KBKDFCMAC", "KBKDFHMAC"]
|
|
}
|
|
|
|
override predicate requiresSalt() {
|
|
this.getAlgorithm().getKDFName() in ["PBKDF2HMAC", "CONCATKDFHMAC", "HKDF"]
|
|
}
|
|
|
|
override predicate requiresIteration() { this.getAlgorithm().getKDFName() in ["PBKDF2HMAC"] }
|
|
|
|
override DataFlow::Node getIterationSizeSrc() {
|
|
if this.requiresIteration()
|
|
then
|
|
// ASSUMPTION: ONLY EVER in arg 3 in PBKDF2HMAC
|
|
result = Utils::getUltimateSrcFromApiNode(this.getParameter(3, "iterations"))
|
|
else none()
|
|
}
|
|
|
|
override DataFlow::Node getSaltConfigSrc() {
|
|
if this.requiresSalt()
|
|
then
|
|
// SCRYPT has it in arg 1
|
|
if this.getAlgorithm().getKDFName() = "SCRYPT"
|
|
then result = Utils::getUltimateSrcFromApiNode(this.getParameter(1, "salt"))
|
|
else
|
|
// EVERYTHING ELSE that uses salt is in arg 2
|
|
result = Utils::getUltimateSrcFromApiNode(this.getParameter(2, "salt"))
|
|
else none()
|
|
}
|
|
|
|
override DataFlow::Node getHashConfigSrc() {
|
|
if this.requiresHash()
|
|
then
|
|
// ASSUMPTION: ONLY EVER in arg 0
|
|
result = Utils::getUltimateSrcFromApiNode(this.getParameter(0, "algorithm"))
|
|
else none()
|
|
}
|
|
|
|
// TODO: get encryption algorithm for CBC-based KDF?
|
|
override DataFlow::Node getDerivedKeySizeSrc() {
|
|
if this.getAlgorithm().getKDFName() in ["KBKDFHMAC", "KBKDFCMAC"]
|
|
then result = Utils::getUltimateSrcFromApiNode(this.getParameter(2, "length"))
|
|
else result = Utils::getUltimateSrcFromApiNode(this.getParameter(1, "length"))
|
|
}
|
|
|
|
override DataFlow::Node getModeSrc() {
|
|
if this.requiresMode()
|
|
then
|
|
// ASSUMPTION: ONLY EVER in arg 1
|
|
result = Utils::getUltimateSrcFromApiNode(this.getParameter(1, "mode"))
|
|
else none()
|
|
}
|
|
}
|
|
}
|
|
|
|
module Encryption {
|
|
/**
|
|
* https://cryptography.io/en/latest/hazmat/primitives/aead/#module-cryptography.hazmat.primitives.ciphers.aead
|
|
* https://cryptography.io/en/latest/fernet/
|
|
* https://cryptography.io/en/latest/hazmat/primitives/symmetric-encryption/
|
|
*/
|
|
module SymmetricEncryption {
|
|
// https://cryptography.io/en/latest/hazmat/primitives/keywrap/
|
|
module KeyWrap {
|
|
// TODO: what padding mode is used by default?
|
|
DataFlow::Node genericKeyWrapArtifact() {
|
|
exists(string opName |
|
|
result =
|
|
API::moduleImport("cryptography")
|
|
.getMember("hazmat")
|
|
.getMember("primitives")
|
|
.getMember("keywrap")
|
|
.getMember(opName)
|
|
.getACall() and
|
|
opName in [
|
|
"aes_key_wrap", "aes_key_wrap_with_padding", "aes_key_unwrap",
|
|
"aes_key_unwrap_with_padding"
|
|
]
|
|
)
|
|
}
|
|
|
|
class CryptographyKeyWrap extends KeyWrapOperation, SymmetricEncryptionAlgorithm, BlockMode,
|
|
SymmetricCipher
|
|
{
|
|
CryptographyKeyWrap() { this = genericKeyWrapArtifact() }
|
|
|
|
override string getName() {
|
|
//Cryptography Key Wrap Artifact's use ECB block mode by default:
|
|
// https://github.com/pyca/cryptography/blob/main/src/cryptography/hazmat/primitives/keywrap.py#L14
|
|
result = super.normalizeName("ECB")
|
|
or
|
|
// TODO: the actual AES used is dependent on the key size, get key size and set name accordingly
|
|
// Only allowed key sizes:
|
|
// https://github.com/pyca/cryptography/blob/main/src/cryptography/hazmat/primitives/keywrap.py#L38-L54
|
|
result = super.normalizeName("AES")
|
|
}
|
|
|
|
/**
|
|
* ECB mode is effectively no block mode and no IV is associated with this mode.
|
|
*/
|
|
override DataFlow::Node getIVorNonce() { none() }
|
|
|
|
override SymmetricEncryptionAlgorithm getEncryptionAlgorithm() { result = this }
|
|
|
|
override BlockMode getBlockMode() { result = this }
|
|
|
|
override CryptographicAlgorithm getAlgorithm() { result = this }
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Authenticated Encryption Artifacts
|
|
* https://cryptography.io/en/latest/hazmat/primitives/aead/#module-cryptography.hazmat.primitives.ciphers.aead
|
|
*/
|
|
module AuthenticatedEncryption {
|
|
API::Node genericAEADAPINode(string algName) {
|
|
result =
|
|
API::moduleImport("cryptography")
|
|
.getMember("hazmat")
|
|
.getMember("primitives")
|
|
.getMember("ciphers")
|
|
.getMember("aead")
|
|
.getMember(algName) and
|
|
algName in ["AESGCM", "AESCCM", "AESOCB3", "AESSIV", "ChaCha20Poly1305"]
|
|
}
|
|
|
|
DataFlow::Node genericAEADArtifact(API::Node algModule, string algName) {
|
|
algModule = genericAEADAPINode(algName) and
|
|
result = algModule.asSource()
|
|
}
|
|
|
|
class CryptographyAEAD extends BlockMode, AuthenticatedEncryptionAlgorithm, SymmetricCipher {
|
|
CryptographyAEAD() { this = genericAEADArtifact(_, _) }
|
|
|
|
API::Node getMember(string memberName) { result = this.getModule().getMember(memberName) }
|
|
|
|
API::Node getModule() { this = genericAEADArtifact(result, _) }
|
|
|
|
override string getName() {
|
|
exists(string rawName | genericAEADArtifact(_, rawName) = this |
|
|
result = this.normalizedBlockNames(rawName) or
|
|
result = this.normalizedEncryptionName(rawName)
|
|
)
|
|
}
|
|
|
|
bindingset[rawName]
|
|
string normalizedBlockNames(string rawName) {
|
|
// https://cryptography.io/en/latest/hazmat/primitives/aead/#module-cryptography.hazmat.primitives.ciphers.aead
|
|
if rawName = "AESGCM"
|
|
then result = super.normalizeName("GCM")
|
|
else
|
|
if rawName = "AESCCM"
|
|
then result = super.normalizeName("CCM")
|
|
else
|
|
if rawName = "AESOCB3"
|
|
then result = super.normalizeName("OCB")
|
|
else
|
|
if rawName = "AESSIV"
|
|
then result = super.normalizeName("SIV")
|
|
else result = super.normalizeName(rawName)
|
|
}
|
|
|
|
bindingset[rawName]
|
|
string normalizedEncryptionName(string rawName) {
|
|
if rawName.matches("AES%")
|
|
then result = super.normalizeName("AES")
|
|
else result = super.normalizeName(rawName)
|
|
}
|
|
|
|
/**
|
|
* Since the IV/Nonce is dependent on the API, we could attempt a dataflow to derive
|
|
* what it is internal to the library, but instead we take the stance that
|
|
* the IV/Nonce is non-existent/unknown to simplify analyses and to be
|
|
* safe in case of API changes. Uses of this API must therefore be
|
|
* individually assessed for correct IV use.
|
|
*/
|
|
override DataFlow::Node getIVorNonce() { none() }
|
|
|
|
override SymmetricEncryptionAlgorithm getEncryptionAlgorithm() { result = this }
|
|
|
|
override BlockMode getBlockMode() { result = this }
|
|
}
|
|
|
|
DataFlow::Node genericAEADKeyGen(CryptographyAEAD aead) {
|
|
aead.getMember("generate_key").getACall() = result
|
|
}
|
|
|
|
class CryptographyAEADKeyGen extends SymmetricKeyGen {
|
|
CryptographyAEADKeyGen() { this = genericAEADKeyGen(_) }
|
|
|
|
override CryptographyAEAD getAlgorithm() { this = genericAEADKeyGen(result) }
|
|
|
|
override int getKeySizeInBits(DataFlow::Node configSrc) {
|
|
if this.getAlgorithm().getAuthticatedEncryptionName() = "ChaCha20Poly1305 "
|
|
then result = 32 * 8
|
|
else (
|
|
result = configSrc.asExpr().(IntegerLiteral).getValue() and
|
|
configSrc = this.getKeyConfigSrc()
|
|
)
|
|
}
|
|
|
|
DataFlow::Node keyBitLengthSrc() {
|
|
result =
|
|
Utils::getUltimateSrcFromApiNode(this.(API::CallNode).getParameter(0, "bit_length"))
|
|
}
|
|
|
|
override DataFlow::Node getKeyConfigSrc() {
|
|
result = this.keyBitLengthSrc()
|
|
or
|
|
not exists(this.keyBitLengthSrc()) and result = this
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* https://cryptography.io/en/latest/fernet/
|
|
*/
|
|
module Fernet {
|
|
DataFlow::Node fernetConstructor() {
|
|
exists(string member | member = ["Fernet", "MultiFernet"] |
|
|
result =
|
|
API::moduleImport("cryptography").getMember("fernet").getMember(member).getACall()
|
|
)
|
|
}
|
|
|
|
class CryptographyFernet extends SymmetricPadding, BlockMode, SymmetricEncryptionAlgorithm,
|
|
SymmetricCipher
|
|
{
|
|
CryptographyFernet() { this = fernetConstructor() }
|
|
|
|
override string getName() {
|
|
result = super.normalizeName("PKCS7") or
|
|
result = super.normalizeName("AES128") or
|
|
result = super.normalizeName("CBC")
|
|
}
|
|
|
|
/**
|
|
* Since the IV/Nonce is dependent on the API, we could attempt a dataflow to derive
|
|
* what it is internal to the library, but instead we take the stance that
|
|
* the IV/Nonce is non-existent/unknown to simplify analyses and to be
|
|
* safe in case of API changes. Uses of this API must therefore be
|
|
* individually assessed for correct IV use.
|
|
*
|
|
* The current API shows the IV is set via os.urandom:
|
|
* https://github.com/pyca/cryptography/blob/main/src/cryptography/fernet.py
|
|
*/
|
|
override DataFlow::Node getIVorNonce() { none() }
|
|
|
|
override SymmetricEncryptionAlgorithm getEncryptionAlgorithm() { result = this }
|
|
|
|
override BlockMode getBlockMode() { result = this }
|
|
}
|
|
|
|
API::CallNode fernetKeyGen(CryptographyFernet fernetCall) {
|
|
result = fernetCall.(API::CallNode).getReturn().getMember("generate_key").getACall()
|
|
}
|
|
|
|
/**
|
|
* https://cryptography.io/en/latest/fernet/#cryptography.fernet.Fernet.generate_key
|
|
*/
|
|
class FernetKeyGen extends SymmetricKeyGen {
|
|
FernetKeyGen() { this = fernetKeyGen(_) }
|
|
|
|
override DataFlow::Node getKeyConfigSrc() { result = this }
|
|
|
|
override int getKeySizeInBits(DataFlow::Node configSrc) {
|
|
// https://github.com/pyca/cryptography/blob/main/src/cryptography/fernet.py#L44
|
|
// requires a 256 bit key, but only uses half of it for 128 bit encryption/signing
|
|
result = 128 and
|
|
configSrc = this
|
|
}
|
|
|
|
override CryptographyFernet getAlgorithm() { this = fernetKeyGen(result) }
|
|
}
|
|
// NOTE: not implementing MultiFernet since it operates on Fernet which is already modeled:
|
|
// https://cryptography.io/en/latest/fernet/#cryptography.fernet.MultiFernet
|
|
}
|
|
|
|
// https://cryptography.io/en/latest/hazmat/primitives/symmetric-encryption/#module-cryptography.hazmat.primitives.ciphers.modes
|
|
module GenericCryptoArtifact {
|
|
DataFlow::Node genericBlockMode(string name) {
|
|
// getACall since the typical case is to construct the block mode with initialization values
|
|
// not to pass the mode uninitialized
|
|
result =
|
|
API::moduleImport("cryptography")
|
|
.getMember("hazmat")
|
|
.getMember("primitives")
|
|
.getMember("ciphers")
|
|
.getMember("modes")
|
|
.getMember(name)
|
|
.getACall() and
|
|
not name.regexpMatch("_.*")
|
|
}
|
|
|
|
// https://cryptography.io/en/latest/hazmat/primitives/symmetric-encryption/#module-cryptography.hazmat.primitives.ciphers.modes
|
|
class CryptographyGenericBlockMode extends BlockMode {
|
|
CryptographyGenericBlockMode() { this = genericBlockMode(_) }
|
|
|
|
override string getName() {
|
|
exists(string rawName | this = genericBlockMode(rawName) |
|
|
result = super.normalizeName(rawName)
|
|
)
|
|
}
|
|
|
|
override DataFlow::Node getIVorNonce() {
|
|
exists(string paramName | paramName = ["initialization_vector", "nonce"] |
|
|
result =
|
|
Utils::getUltimateSrcFromApiNode(this.(API::CallNode).getParameter(0, paramName))
|
|
)
|
|
}
|
|
}
|
|
|
|
DataFlow::Node genericSymmetricEncryptionArtifact(string name) {
|
|
// getCall since the typical case is to construct the algorithm with initialization values (e.g., keys)
|
|
// not to pass the mode uninitialized
|
|
result =
|
|
API::moduleImport("cryptography")
|
|
.getMember("hazmat")
|
|
.getMember("primitives")
|
|
.getMember("ciphers")
|
|
.getMember("algorithms")
|
|
.getMember(name)
|
|
.getACall() and
|
|
not name.regexpMatch("_.*")
|
|
}
|
|
|
|
class CrytographyGenericSymmetricEncryption extends SymmetricEncryptionAlgorithm {
|
|
CrytographyGenericSymmetricEncryption() { this = genericSymmetricEncryptionArtifact(_) }
|
|
|
|
override string getName() {
|
|
exists(string rawName | this = genericSymmetricEncryptionArtifact(rawName) |
|
|
result = super.normalizeName(rawName)
|
|
)
|
|
}
|
|
}
|
|
|
|
// class CryptographcyGenericSymmetricKeyGen extends SymmetricKeyGen{
|
|
// CryptographcyGenericSymmetricKeyGen(){
|
|
// this = genericSymmetricEncryptionArtifact(_)
|
|
// }
|
|
// override DataFlow::Node getKeyConfigSrc(){
|
|
// result = this.(API::CallNode).getParameter(0, "key").getAValueReachingSink()
|
|
// }
|
|
// override int getKeySizeInBits(DataFlow::Node configSrc){
|
|
// // TODO: if/else over all posibilities
|
|
// }
|
|
// override string getAlgorithmName() {
|
|
// exists(SymmetricEncryptionAlgorithm a, string algName |
|
|
// this = genericSymmetricEncryptionArtifact(algName) and
|
|
// a = genericSymmetricEncryptionArtifact(algName) and
|
|
// result = a.normalizeName(algName)
|
|
// )
|
|
// }
|
|
// }
|
|
/**
|
|
* Gets a symmetric padding operation:
|
|
* https://cryptography.io/en/latest/hazmat/primitives/padding/#module-cryptography.hazmat.primitives.padding
|
|
*/
|
|
DataFlow::Node genericSymmetricPadding(string name) {
|
|
// getACall since the typical case is to construct the padding with initialization values,
|
|
// not to pass the mode uninitialized
|
|
result =
|
|
API::moduleImport("cryptography")
|
|
.getMember("hazmat")
|
|
.getMember("primitives")
|
|
.getMember("ciphers")
|
|
.getMember("padding")
|
|
.getMember(name)
|
|
.getACall() and
|
|
name != "PaddingContext" and
|
|
not name.regexpMatch("_.*")
|
|
}
|
|
|
|
class CryptographyGenericSymmetricPadding extends SymmetricPadding {
|
|
CryptographyGenericSymmetricPadding() { this = genericSymmetricPadding(_) }
|
|
|
|
override string getName() {
|
|
exists(string rawName | this = genericSymmetricPadding(rawName) |
|
|
result = super.normalizeName(rawName)
|
|
)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* https://cryptography.io/en/latest/hazmat/primitives/symmetric-encryption/#cryptography.hazmat.primitives.ciphers.Cipher
|
|
*/
|
|
class CyrptographyGenericCipher extends SymmetricCipher {
|
|
CyrptographyGenericCipher() {
|
|
this =
|
|
API::moduleImport("cryptography")
|
|
.getMember("hazmat")
|
|
.getMember("primitives")
|
|
.getMember("ciphers")
|
|
.getMember("Cipher")
|
|
.getACall()
|
|
}
|
|
|
|
override SymmetricEncryptionAlgorithm getEncryptionAlgorithm() {
|
|
result =
|
|
Utils::getUltimateSrcFromApiNode(this.(API::CallNode).getParameter(0, "algorithm"))
|
|
}
|
|
|
|
override BlockMode getBlockMode() {
|
|
result = Utils::getUltimateSrcFromApiNode(this.(API::CallNode).getParameter(1, "mode"))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
module AsymmetricEncryption {
|
|
module RSA {
|
|
/**
|
|
* Returns a member of cryptography asymmetric padding module that is
|
|
* a padding algorithm (filteres out non-padding members)
|
|
*/
|
|
DataFlow::CallCfgNode cryptographyAsymmetricPadding(string name) {
|
|
// getACall since the typical case is to construct the padding with initialization values,
|
|
// not to pass the mode uninitialized
|
|
result =
|
|
API::moduleImport("cryptography")
|
|
.getMember("hazmat")
|
|
.getMember("primitives")
|
|
.getMember("asymmetric")
|
|
.getMember("padding")
|
|
.getMember(name)
|
|
.getACall() and
|
|
(
|
|
name = "PKCS1v15" or
|
|
name = "OAEP" or
|
|
name = "PSS"
|
|
)
|
|
}
|
|
|
|
/**
|
|
* A cryptography asymmetric padding algorithm.
|
|
*/
|
|
class CryptographyAsymmetricPadding extends AsymmetricPadding {
|
|
CryptographyAsymmetricPadding() { this = cryptographyAsymmetricPadding(_) }
|
|
|
|
override string getName() {
|
|
exists(string rawName | this = cryptographyAsymmetricPadding(rawName) |
|
|
result = super.normalizeName(rawName)
|
|
)
|
|
}
|
|
}
|
|
|
|
API::CallNode getRSAKeyGenCall() {
|
|
result =
|
|
API::moduleImport("cryptography")
|
|
.getMember("hazmat")
|
|
.getMember("primitives")
|
|
.getMember("asymmetric")
|
|
.getMember("rsa")
|
|
.getMember("generate_private_key")
|
|
.getACall()
|
|
}
|
|
|
|
API::CallNode getRSAKeyLoadCall() {
|
|
result =
|
|
API::moduleImport("cryptography")
|
|
.getMember("hazmat")
|
|
.getMember("primitives")
|
|
.getMember("serialization")
|
|
.getMember("load_pem_private_key")
|
|
.getACall()
|
|
}
|
|
|
|
API::Node getRSAPrivateKey() {
|
|
result = getRSAKeyGenCall().getReturn()
|
|
or
|
|
result = getRSAKeyLoadCall().getReturn()
|
|
}
|
|
|
|
API::Node getRSAPublicKey() {
|
|
result = getRSAPrivateKey().getMember("public_key").getACall().getReturn()
|
|
}
|
|
|
|
DataFlow::Node getRSASignCall() { result = getRSAPrivateKey().getMember("sign").getACall() }
|
|
|
|
DataFlow::Node getRSAVerifyCall() {
|
|
result = getRSAPublicKey().getMember("verify").getACall()
|
|
}
|
|
|
|
DataFlow::Node getRSAEncryptCall() {
|
|
result = getRSAPublicKey().getMember("encrypt").getACall()
|
|
}
|
|
|
|
DataFlow::Node getRSADecryptCall() {
|
|
result = getRSAPrivateKey().getMember("decrypt").getACall()
|
|
}
|
|
|
|
/**
|
|
* Finds the parameter DataFlow::Node representing padding for all RSA operations
|
|
* that accept a padding parameter.
|
|
*/
|
|
API::Node getRSAPaddingParameter(API::CallNode c) {
|
|
c = getRSASignCall() and result = c.(API::CallNode).getParameter(1, "padding")
|
|
or
|
|
c = getRSAVerifyCall() and result = c.(API::CallNode).getParameter(2, "padding")
|
|
or
|
|
c = getRSAEncryptCall() and result = c.(API::CallNode).getParameter(1, "padding")
|
|
or
|
|
c = getRSADecryptCall() and result = c.(API::CallNode).getParameter(1, "padding")
|
|
}
|
|
|
|
predicate isRSAPaddingCall(API::CallNode c) {
|
|
c = getRSASignCall() or
|
|
c = getRSAVerifyCall() or
|
|
c = getRSAEncryptCall() or
|
|
c = getRSADecryptCall()
|
|
}
|
|
|
|
/**
|
|
* All unknown padding algorithms determined by
|
|
* tracing RSA operations that accept a padding parameter back to their source.
|
|
* If the source is not `cryptographyAsymmetricPadding`, then mark it as unknown.
|
|
*/
|
|
class UnknownRSAOperationAsymmetricPadding extends AsymmetricPadding {
|
|
UnknownRSAOperationAsymmetricPadding() {
|
|
// Either the padding parameter is known and it isn't a member of cryptographyAsymmetricPadding
|
|
// or the padding parameter does not exist, and the operation itself will be considered the
|
|
// source of an unknown padding algorithm.
|
|
exists(API::CallNode c, API::Node p | isRSAPaddingCall(c) |
|
|
p = getRSAPaddingParameter(c) and
|
|
this = Utils::getUltimateSrcFromApiNode(p) and
|
|
not this = cryptographyAsymmetricPadding(_)
|
|
or
|
|
not exists(getRSAPaddingParameter(c)) and
|
|
this = c
|
|
)
|
|
}
|
|
|
|
override string getName() { result = unknownAlgorithm() }
|
|
}
|
|
|
|
/**
|
|
* The result of a RSA key generation operation
|
|
*/
|
|
class CryptographyRSAKeyGen extends AsymmetricKeyGen, AsymmetricEncryptionAlgorithm {
|
|
CryptographyRSAKeyGen() { this = getRSAKeyGenCall() }
|
|
|
|
override DataFlow::Node getKeyConfigSrc() {
|
|
result =
|
|
Utils::getUltimateSrcFromApiNode(this.(API::CallNode).getParameter(1, "key_size"))
|
|
}
|
|
|
|
override int getKeySizeInBits(DataFlow::Node configSrc) {
|
|
result = configSrc.asExpr().(IntegerLiteral).getValue() and
|
|
configSrc = this.getKeyConfigSrc()
|
|
}
|
|
|
|
override AsymmetricEncryptionAlgorithm getAlgorithm() { result = this }
|
|
|
|
override string getName() { result = super.normalizeName("RSA") }
|
|
}
|
|
|
|
/**
|
|
* Identifies an RSA operation or artifact.
|
|
* https://cryptography.io/en/latest/hazmat/primitives/asymmetric/rsa/#module-cryptography.hazmat.primitives.asymmetric.rsa
|
|
* Since the use of such an operation or artifact infers the algorithm all
|
|
* operations/artifacts are identified as an RSA algorithm
|
|
*/
|
|
class CryptographyRSAAlgorithm extends AsymmetricEncryptionAlgorithm {
|
|
CryptographyRSAAlgorithm() {
|
|
this =
|
|
API::moduleImport("cryptography")
|
|
.getMember("hazmat")
|
|
.getMember("primitives")
|
|
.getMember("asymmetric")
|
|
.getMember("rsa")
|
|
.getAMember*()
|
|
.asSource()
|
|
}
|
|
|
|
override string getName() { result = super.normalizeName("RSA") }
|
|
}
|
|
}
|
|
|
|
module EllipticCurve {
|
|
/**
|
|
* Gets a call to an elliptic curve key generation operation
|
|
*/
|
|
DataFlow::Node getEllipticCurveKeyGenCall() {
|
|
result =
|
|
API::moduleImport("cryptography")
|
|
.getMember("hazmat")
|
|
.getMember("primitives")
|
|
.getMember("asymmetric")
|
|
.getMember("ec")
|
|
.getMember("generate_private_key")
|
|
.getACall()
|
|
}
|
|
|
|
/**
|
|
* Gets a predefined curve class constructor call from
|
|
* `cryptography.hazmat.primitives.asymmetric.ec`
|
|
* https://cryptography.io/en/latest/hazmat/primitives/asymmetric/ec/#elliptic-curves
|
|
*/
|
|
DataFlow::Node predefinedCurveClass(string curveName) {
|
|
// getACall since the typical case is to construct the curve with initialization values,
|
|
// not to pass the mode uninitialized
|
|
result =
|
|
API::moduleImport("cryptography")
|
|
.getMember("hazmat")
|
|
.getMember("primitives")
|
|
.getMember("asymmetric")
|
|
.getMember("ec")
|
|
.getMember(curveName)
|
|
.getACall() and
|
|
curveName =
|
|
[
|
|
"SECP256R1", "SECP384R1", "SECP521R1", "SECP224R1", "SECP192R1", "SECP256K1",
|
|
"BrainpoolP256R1", "BrainpoolP384R1", "BrainpoolP512R1", "SECT571K1", "SECT409K1",
|
|
"SECT283K1", "SECT233K1", "SECT163K1", "SECT571R1", "SECT409R1", "SECT283R1",
|
|
"SECT233R1", "SECT163R2"
|
|
]
|
|
}
|
|
|
|
/**
|
|
* Gets calls to EllipticCurvePublicNumbers
|
|
* https://cryptography.io/en/latest/hazmat/primitives/asymmetric/ec/#cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePublicNumbers
|
|
*/
|
|
DataFlow::Node getEllipticCurvePublicNumbersCall() {
|
|
result =
|
|
API::moduleImport("cryptography")
|
|
.getMember("hazmat")
|
|
.getMember("primitives")
|
|
.getMember("asymmetric")
|
|
.getMember("ec")
|
|
.getMember("EllipticCurvePublicNumbers")
|
|
.getACall()
|
|
}
|
|
|
|
/**
|
|
* Gets calls to derive_private_key
|
|
* https://cryptography.io/en/latest/hazmat/primitives/asymmetric/ec/#cryptography.hazmat.primitives.asymmetric.ec.derive_private_key
|
|
*/
|
|
DataFlow::Node getEllipticCurveDerivePrivateKeyCall() {
|
|
result =
|
|
API::moduleImport("cryptography")
|
|
.getMember("hazmat")
|
|
.getMember("primitives")
|
|
.getMember("asymmetric")
|
|
.getMember("ec")
|
|
.getMember("derive_private_key")
|
|
.getACall()
|
|
}
|
|
|
|
/**
|
|
* An elliptic curve key generation operation
|
|
*/
|
|
class CryptographyEllipticCurveKeyGen extends AsymmetricKeyGen {
|
|
CryptographyEllipticCurveKeyGen() { this = getEllipticCurveKeyGenCall() }
|
|
|
|
override DataFlow::Node getKeyConfigSrc() {
|
|
result = Utils::getUltimateSrcFromApiNode(this.(API::CallNode).getParameter(0, "curve"))
|
|
}
|
|
|
|
override EllipticCurveAlgorithm getAlgorithm() { result = this.getKeyConfigSrc() }
|
|
|
|
override int getKeySizeInBits(DataFlow::Node configSrc) {
|
|
// TODO/NOTE: there is a general issue if config sources are defined as calls vs general dataflow nodes
|
|
// The issue is if defined asSource, a call may be seen as the source, hence, this predicate
|
|
// and others like it, will not resolve, since there is no satisfying configSrc (there is a mismatch
|
|
// as observed is a call, and the Curve is actually defined using `asSource`.)
|
|
// We need to find a generalized solution to be able to have both cases happen and rely upon
|
|
// getting algorithms consistently.
|
|
isEllipticCurveAlgorithm(configSrc.(EllipticCurveAlgorithm).getCurveName(), result)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Any operation that takes in a Curve, trace the curve back to its ultimate source
|
|
* and if it is not a known curve, the curve is considered unknown an a member of this class.
|
|
*/
|
|
class CryptographyUnknownEllipticCurve extends EllipticCurveAlgorithm {
|
|
CryptographyUnknownEllipticCurve() {
|
|
(
|
|
Utils::getUltimateSrcFromApiNode(getEllipticCurvePublicNumbersCall()
|
|
.(API::CallNode)
|
|
.getParameter(2, "curve")) = this
|
|
or
|
|
Utils::getUltimateSrcFromApiNode(getEllipticCurveKeyGenCall()
|
|
.(API::CallNode)
|
|
.getParameter(0, "curve")) = this
|
|
or
|
|
Utils::getUltimateSrcFromApiNode(getEllipticCurveDerivePrivateKeyCall()
|
|
.(API::CallNode)
|
|
.getParameter(1, "curve")) = this
|
|
) and
|
|
not predefinedCurveClass(_) = this
|
|
}
|
|
|
|
override string getName() { result = unknownAlgorithm() }
|
|
}
|
|
|
|
/**
|
|
* An elliptic curve as defined in this cryptography.hazmat.primitives.asymmetric.ec module.
|
|
* Includes all members of the module thata are elliptic curves (filters out non-curve members)
|
|
*/
|
|
class CryptographyEllipticCurveAlgorithm extends EllipticCurveAlgorithm {
|
|
CryptographyEllipticCurveAlgorithm() { this = predefinedCurveClass(_) }
|
|
|
|
override string getName() {
|
|
exists(string rawName | this = predefinedCurveClass(rawName) |
|
|
result = super.normalizeName(rawName)
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
module DiffieHellman {
|
|
/**
|
|
* Any diffie-hellman module operation or artifact.
|
|
* https://cryptography.io/en/latest/hazmat/primitives/asymmetric/dh/#diffie-hellman-key-exchange
|
|
*
|
|
* Since the algorithm is hidden within all operations, all operations are identified by this class.
|
|
*/
|
|
class CryptographyDiffieHellmanAlgorithm extends KeyExchangeAlgorithm {
|
|
CryptographyDiffieHellmanAlgorithm() {
|
|
this =
|
|
API::moduleImport("cryptography")
|
|
.getMember("hazmat")
|
|
.getMember("primitives")
|
|
.getMember("asymmetric")
|
|
.getMember("dh")
|
|
.getAMember*()
|
|
.asSource()
|
|
}
|
|
|
|
override string getName() { result = super.normalizeName("DiffieHellman") }
|
|
}
|
|
|
|
class CrytographyDiffieHellmanKeyGen extends AsymmetricKeyGen, KeyExchangeAlgorithm {
|
|
CrytographyDiffieHellmanKeyGen() {
|
|
this =
|
|
API::moduleImport("cryptography")
|
|
.getMember("hazmat")
|
|
.getMember("primitives")
|
|
.getMember("asymmetric")
|
|
.getMember("dh")
|
|
.getMember("generate_parameters")
|
|
.getACall()
|
|
}
|
|
|
|
override string getName() { result = super.normalizeName("DiffieHellman") }
|
|
|
|
override DataFlow::Node getKeyConfigSrc() {
|
|
result =
|
|
Utils::getUltimateSrcFromApiNode(this.(API::CallNode).getParameter(1, "key_size"))
|
|
}
|
|
|
|
override int getKeySizeInBits(DataFlow::Node configSrc) {
|
|
result = configSrc.asExpr().(IntegerLiteral).getValue() and
|
|
configSrc = this.getKeyConfigSrc()
|
|
}
|
|
|
|
override KeyExchangeAlgorithm getAlgorithm() { result = this }
|
|
}
|
|
|
|
class CryptographyX25519 extends KeyExchangeAlgorithm, AsymmetricKeyGen,
|
|
EllipticCurveAlgorithm
|
|
{
|
|
CryptographyX25519() {
|
|
this =
|
|
API::moduleImport("cryptography")
|
|
.getMember("hazmat")
|
|
.getMember("primitives")
|
|
.getMember("asymmetric")
|
|
.getMember("x25519")
|
|
.getAMember*()
|
|
.asSource()
|
|
}
|
|
|
|
override string getName() {
|
|
result = super.normalizeName("DiffieHellman")
|
|
or
|
|
result = super.normalizeName("Curve25519")
|
|
}
|
|
|
|
override DataFlow::Node getKeyConfigSrc() { result = this }
|
|
|
|
override int getKeySizeInBits(DataFlow::Node configSrc) {
|
|
isEllipticCurveAlgorithm(this.getCurveName(), result) and
|
|
configSrc = this
|
|
}
|
|
|
|
override CryptographicAlgorithm getAlgorithm() { result = this }
|
|
}
|
|
|
|
/**
|
|
* Identifies the key exchange algorithm from x448 API:
|
|
* https://cryptography.io/en/latest/hazmat/primitives/asymmetric/x448/#x448
|
|
*
|
|
* Modeling as both an operation and algorithm because the algorithm is hidden
|
|
* within all operations. All operations are therefore modeled by this class.
|
|
*/
|
|
class CryptographyX448 extends KeyExchangeAlgorithm, AsymmetricKeyGen, EllipticCurveAlgorithm {
|
|
CryptographyX448() {
|
|
this =
|
|
API::moduleImport("cryptography")
|
|
.getMember("hazmat")
|
|
.getMember("primitives")
|
|
.getMember("asymmetric")
|
|
.getMember("x448")
|
|
.getAMember*()
|
|
.asSource()
|
|
}
|
|
|
|
override string getName() {
|
|
result = super.normalizeName("DiffieHellman")
|
|
or
|
|
result = super.normalizeName("Curve448")
|
|
}
|
|
|
|
override DataFlow::Node getKeyConfigSrc() { result = this }
|
|
|
|
override int getKeySizeInBits(DataFlow::Node configSrc) {
|
|
isEllipticCurveAlgorithm(this.getCurveName(), result) and
|
|
configSrc = this
|
|
}
|
|
|
|
override CryptographicAlgorithm getAlgorithm() { result = this }
|
|
}
|
|
}
|
|
|
|
module Signing {
|
|
class CryptographyDSAKeyGen extends AsymmetricKeyGen, SigningAlgorithm {
|
|
CryptographyDSAKeyGen() {
|
|
exists(string op | op = ["generate_private_key", "generate_parameters"] |
|
|
this =
|
|
API::moduleImport("cryptography")
|
|
.getMember("hazmat")
|
|
.getMember("primitives")
|
|
.getMember("asymmetric")
|
|
.getMember("dsa")
|
|
.getMember(op)
|
|
.getACall()
|
|
)
|
|
}
|
|
|
|
override DataFlow::Node getKeyConfigSrc() {
|
|
result =
|
|
Utils::getUltimateSrcFromApiNode(this.(API::CallNode).getParameter(0, "key_size"))
|
|
}
|
|
|
|
override int getKeySizeInBits(DataFlow::Node configSrc) {
|
|
result = configSrc.asExpr().(IntegerLiteral).getValue() and
|
|
configSrc = this.getKeyConfigSrc()
|
|
}
|
|
|
|
override SigningAlgorithm getAlgorithm() { result = this }
|
|
|
|
override string getName() { result = super.normalizeName("DSA") }
|
|
}
|
|
|
|
/**
|
|
* Identifies the signing algorithm from the ed25519 signing API:
|
|
* https://cryptography.io/en/latest/hazmat/primitives/asymmetric/ed25519/#ed25519-signing
|
|
*
|
|
* Modeling as both an operation and algorithm because the algorithm is hidden
|
|
* within all operations. All operations are therefore modeled by this class.
|
|
*/
|
|
class CryptographyED25519 extends SigningAlgorithm, AsymmetricKeyGen, EllipticCurveAlgorithm {
|
|
CryptographyED25519() {
|
|
this =
|
|
API::moduleImport("cryptography")
|
|
.getMember("hazmat")
|
|
.getMember("primitives")
|
|
.getMember("asymmetric")
|
|
.getMember("ed25519")
|
|
.getAMember*()
|
|
.asSource()
|
|
}
|
|
|
|
override string getName() {
|
|
result = super.normalizeName("EDDSA")
|
|
or
|
|
result = super.normalizeName("Curve25519")
|
|
}
|
|
|
|
override DataFlow::Node getKeyConfigSrc() { result = this }
|
|
|
|
override int getKeySizeInBits(DataFlow::Node configSrc) {
|
|
isEllipticCurveAlgorithm(this.getCurveName(), result) and
|
|
configSrc = this
|
|
}
|
|
|
|
override CryptographicAlgorithm getAlgorithm() { result = this }
|
|
}
|
|
|
|
/**
|
|
* Identifies the signing algorithm from the ed448 signing API:
|
|
* https://cryptography.io/en/latest/hazmat/primitives/asymmetric/ed448/#ed448-signing
|
|
*
|
|
* Modeling as both an operation and algorithm because the algorithm is hidden
|
|
* within all operations. All operations are therefore modeled by this class.
|
|
*/
|
|
class CryptographyED448 extends SigningAlgorithm, AsymmetricKeyGen, EllipticCurveAlgorithm {
|
|
CryptographyED448() {
|
|
this =
|
|
API::moduleImport("cryptography")
|
|
.getMember("hazmat")
|
|
.getMember("primitives")
|
|
.getMember("asymmetric")
|
|
.getMember("ed448")
|
|
.getAMember*()
|
|
.asSource()
|
|
}
|
|
|
|
override string getName() {
|
|
result = super.normalizeName("EDDSA")
|
|
or
|
|
result = super.normalizeName("Curve448")
|
|
}
|
|
|
|
override DataFlow::Node getKeyConfigSrc() { result = this }
|
|
|
|
override int getKeySizeInBits(DataFlow::Node configSrc) {
|
|
isEllipticCurveAlgorithm(this.getCurveName(), result) and
|
|
configSrc = this
|
|
}
|
|
|
|
override CryptographicAlgorithm getAlgorithm() { result = this }
|
|
}
|
|
}
|
|
}
|
|
}
|