Skip to content

Quantum: Initial support for BouncyCastle signature algorithms #19568

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

Draft
wants to merge 9 commits into
base: main
Choose a base branch
from
Prev Previous commit
Next Next commit
Added support for BouncyCastle key generation algorithms
This commit adds the `KeyGenerationOperationInstance` and
`KeyGenerationAlgorithmInstance` types to the BouncyCastle model.

It also adds data flow support from key pairs to the corresponding
public and private components.
  • Loading branch information
fegge committed May 23, 2025
commit 949dea8cad7f14a195aaa6abea300e90b14b3313
150 changes: 142 additions & 8 deletions java/ql/lib/experimental/quantum/BouncyCastle.qll
Original file line number Diff line number Diff line change
Expand Up @@ -32,15 +32,15 @@ module Signers {
* BouncyCastle algorithms are instantiated by calling the constructor of the
* corresponding class.
*/
class NewCall = SignatureAlgorithmInstance;
private class NewCall = SignatureAlgorithmInstance;

/**
* The type is instantiated by a constructor call and initialized by a call to
* `init()` which takes two arguments. The first argument is a flag indicating
* whether the operation is signing data or verifying a signature, and the
* second is the key to use.
*/
class InitCall extends MethodCall {
private class InitCall extends MethodCall {
InitCall() { this = any(Signer signer).getAnInitCall() }

Expr getForSigningArg() { result = this.getArgument(0) }
Expand All @@ -67,7 +67,7 @@ module Signers {
* `generateSignature()` or `verifySignature()` methods are used to produce or
* verify the signature, respectively.
*/
class UseCall extends MethodCall {
private class UseCall extends MethodCall {
UseCall() { this = any(Signer signer).getAUseCall() }

predicate isIntermediate() { this.getCallee().getName() = "update" }
Expand All @@ -80,7 +80,7 @@ module Signers {
/**
* Instantiate the flow analysis module for the `Signer` class.
*/
module FlowAnalysis = NewToInitToUseFlowAnalysis<NewCall, InitCall, UseCall>;
private module FlowAnalysis = NewToInitToUseFlowAnalysis<NewCall, InitCall, UseCall>;

/**
* A signing operation instance is a call to either `update()`, `generateSignature()`,
Expand All @@ -90,7 +90,7 @@ module Signers {
SignatureOperationInstance() { not this.isIntermediate() }

override Crypto::AlgorithmValueConsumer getAnAlgorithmValueConsumer() {
result = FlowAnalysis::getInstantiationFromUse(this, _, _)
result = FlowAnalysis::getNewFromUse(this, _, _)
}

override Crypto::KeyOperationSubtype getKeyOperationSubtype() {
Expand All @@ -111,16 +111,150 @@ module Signers {

override Crypto::ArtifactOutputDataFlowNode getOutputArtifact() {
this.getKeyOperationSubtype() = Crypto::TSignMode() and
not super.isIntermediate() and
result.asExpr() = super.getOutput()
}

InitCall getInitCall() { result = FlowAnalysis::getInitFromUse(this, _, _) }

UseCall getAnUpdateCall() {
super.isIntermediate() and result = this
or
result = FlowAnalysis::getAnIntermediateUseFromFinalUse(this, _, _)
}
}
}

/**
* Models for the key generation algorithms defined by the `org.bouncycastle.crypto.generators` package.
*/
module Generators {
import Language
import BouncyCastle.FlowAnalysis
import BouncyCastle.AlgorithmInstances

/**
* A model of the `KeyGenerator` and `KeyPairGenerator` classes in Bouncy Castle.
*/
class KeyGenerator extends RefType {
Crypto::KeyArtifactType type;

KeyGenerator() {
this.getPackage().getName() = "org.bouncycastle.crypto.generators" and
(
this.getName().matches("%KeyGenerator") and type instanceof Crypto::TSymmetricKeyType
or
this.getName().matches("%KeyPairGenerator") and type instanceof Crypto::TAsymmetricKeyType
)
}

MethodCall getAnInitCall() { result = this.getAMethodCall("init") }

MethodCall getAUseCall() { result = this.getAMethodCall(["generateKey", "generateKeyPair"]) }

MethodCall getAMethodCall(string name) {
result
.getCallee()
.hasQualifiedName("org.bouncycastle.crypto.generators", this.getName(), name)
}

Crypto::KeyArtifactType getKeyType() { result = type }

string getRawAlgorithmName() {
this.getKeyType() = Crypto::TSymmetricKeyType() and
result = this.getName().splitAt("KeyGenerator", 0)
or
this.getKeyType() = Crypto::TAsymmetricKeyType() and
result = this.getName().splitAt("KeyPairGenerator", 0)
}
}

/**
* This type is used to model data flow from a key pair to the private and
* public components of the key pair.
*/
class KeyPair extends RefType {
KeyPair() {
this.getPackage().getName() = "org.bouncycastle.crypto" and
this.getName() = "%KeyPair" // `AsymmetricCipherKeyPair` or `EphemeralKeyPair`
}

MethodCall getPublicKeyCall() { result = this.getAMethodCall("getPublic") }

MethodCall getPrivateKeyCall() { result = this.getAMethodCall("getPrivate") }

MethodCall getAMethodCall(string name) {
result.getCallee().hasQualifiedName("org.bouncycastle.crypto", this.getName(), name)
}
}

/**
* BouncyCastle algorithms are instantiated by calling the constructor of the
* corresponding class.
*/
private class KeyGeneratorNewCall = KeyGenerationAlgorithmInstance;

/**
* The type is instantiated by a constructor call and initialized by a call to
* `init()` which takes a single `KeyGenerationParameters` argument.
*/
private class KeyGeneratorInitCall extends MethodCall {
KeyGenerator gen;

KeyGeneratorInitCall() { this = gen.getAnInitCall() }

// TODO: We may need to model this using the `parameters` argument passed to
// the `init()` method.
Crypto::ConsumerInputDataFlowNode getKeySizeConsumer() { none() }
}

/**
* The `generateKey()` and `generateKeyPair()` methods are used to generate
* the resulting key, depending on the type of the generator.
*/
private class KeyGeneratorUseCall extends MethodCall {
KeyGenerator gen;

KeyGeneratorUseCall() { this = gen.getAUseCall() }

// Since key generators don't have `update()` methods, this is always false.
predicate isIntermediate() { none() }

Crypto::KeyArtifactType getKeyType() { result = gen.getKeyType() }

Expr getOutput() { result = this }
}

private module KeyGeneratorFlow =
NewToInitToUseFlowAnalysis<KeyGeneratorNewCall, KeyGeneratorInitCall, KeyGeneratorUseCall>;

/**
* A key generation operation instance is a call to `generateKey()` or
* `generateKeyPair()` on a key generator defined under
* `org.bouncycastle.crypto.generators`.
*/
class KeyGenerationOperationInstance extends Crypto::KeyGenerationOperationInstance instanceof KeyGeneratorUseCall
{
override Crypto::AlgorithmValueConsumer getAnAlgorithmValueConsumer() {
result = KeyGeneratorFlow::getNewFromUse(this, _, _)
}

override Crypto::ArtifactOutputDataFlowNode getOutputKeyArtifact() {
result.asExpr() = super.getOutput()
}

override Crypto::KeyArtifactType getOutputKeyType() { result = super.getKeyType() }

override string getKeySizeFixed() {
result = KeyGeneratorFlow::getNewFromUse(this, _, _).getKeySizeFixed()
}

override Crypto::ConsumerInputDataFlowNode getKeySizeConsumer() {
result = KeyGeneratorFlow::getInitFromUse(this, _, _).getKeySizeConsumer()
}
}

class KeyGenerationParameters extends RefType {
KeyGenerationParameters() {
this.getPackage().getName() = "org.bouncycastle.crypto.generators" and
this.getName().matches("%KeyGenerationParameters")
}
}
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import java
import experimental.quantum.Language
import AlgorithmInstances.SignatureAlgorithmInstances
import AlgorithmInstances.KeyGenerationAlgorithmInstance
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import java
import experimental.quantum.Language

/**
* Key generation algorithms are implicitly defined by the constructor.
*/
abstract private class KeyGenerationAlgorithmValueConsumer extends Crypto::AlgorithmValueConsumer {
override Crypto::AlgorithmInstance getAKnownAlgorithmSource() { result = this }

override Crypto::ConsumerInputDataFlowNode getInputNode() { none() }
}

class KeyGenerationAlgorithmInstance extends Crypto::KeyOperationAlgorithmInstance,
KeyGenerationAlgorithmValueConsumer instanceof ClassInstanceExpr
{
KeyGenerationAlgorithmInstance() {
super.getConstructedType() instanceof Generators::KeyGenerator
}

override Crypto::ModeOfOperationAlgorithmInstance getModeOfOperationAlgorithm() { none() }

override Crypto::PaddingAlgorithmInstance getPaddingAlgorithm() { none() }

override Crypto::KeyOpAlg::Algorithm getAlgorithmType() {
nameToKeySizeAndAlgorithmMapping(this.getRawAlgorithmName(), _, result)
}

override string getKeySizeFixed() {
nameToKeySizeAndAlgorithmMapping(this.getRawAlgorithmName(), result, _)
}

override string getRawAlgorithmName() {
result = super.getConstructedType().(Generators::KeyGenerator).getRawAlgorithmName()
}

// This is overridden if a specific generator type has a key size consumer.
override Crypto::ConsumerInputDataFlowNode getKeySizeConsumer() { none() }
}

class CramerShoupKeyGenerationAlgorithmInstance extends KeyGenerationAlgorithmInstance {
CramerShoupKeyGenerationAlgorithmInstance() { super.getRawAlgorithmName() = "CramerShoup" }

override string getKeySizeFixed() { none() }

// TODO: Model flow from the `CramerShoupParametersGenerator::init` method
// (which takes a size of the prime order group in bits), via the
// `CramerShoupParameters` object, to the key-pair generator.
override Crypto::ConsumerInputDataFlowNode getKeySizeConsumer() { none() }

override Crypto::KeyOpAlg::Algorithm getAlgorithmType() {
result = Crypto::KeyOpAlg::TAsymmetricCipher(Crypto::KeyOpAlg::OtherAsymmetricCipherType())
}
}

class DESedeKeyGenerationAlgorithmInstance extends KeyGenerationAlgorithmInstance {
DESedeKeyGenerationAlgorithmInstance() { super.getRawAlgorithmName() = "DESede" }

// Key size is 112 or 168 bits.
override string getKeySizeFixed() { none() }

override Crypto::ConsumerInputDataFlowNode getKeySizeConsumer() { none() }

override Crypto::KeyOpAlg::Algorithm getAlgorithmType() {
result = Crypto::KeyOpAlg::TSymmetricCipher(Crypto::KeyOpAlg::TripleDES())
}
}

private predicate nameToKeySizeAndAlgorithmMapping(
string name, string keySize, Crypto::KeyOpAlg::Algorithm algorithm
) {
// DES
name = "DES" and
keySize = "56" and
algorithm = Crypto::KeyOpAlg::TSymmetricCipher(Crypto::KeyOpAlg::DES())
or
// Ed25519, Ed25519ph, and Ed25519ctx
name = "Ed25519" and
keySize = "256" and
algorithm = Crypto::KeyOpAlg::TSignature(Crypto::KeyOpAlg::Ed25519())
or
// Ed448 and Ed448ph
name = "Ed448" and
keySize = "448" and
algorithm = Crypto::KeyOpAlg::TSignature(Crypto::KeyOpAlg::Ed448())
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,48 +4,47 @@ import experimental.quantum.Language
/**
* Signature algorithms are implicitly defined by the constructor.
*/
private abstract class SignatureAlgorithmValueConsumer extends Crypto::AlgorithmValueConsumer {
override Crypto::AlgorithmInstance getAKnownAlgorithmSource() {
result = this
}
abstract private class SignatureAlgorithmValueConsumer extends Crypto::AlgorithmValueConsumer {
override Crypto::AlgorithmInstance getAKnownAlgorithmSource() { result = this }

override Crypto::ConsumerInputDataFlowNode getInputNode() {
none()
}
override Crypto::ConsumerInputDataFlowNode getInputNode() { none() }
}

class SignatureAlgorithmInstance extends Crypto::KeyOperationAlgorithmInstance, SignatureAlgorithmValueConsumer instanceof ClassInstanceExpr {
SignatureAlgorithmInstance() {
super.getConstructedType().getPackage().getName() = "org.bouncycastle.crypto.signers" and
super.getConstructedType().getName().matches("%Signer")

}
class SignatureAlgorithmInstance extends Crypto::KeyOperationAlgorithmInstance,
SignatureAlgorithmValueConsumer instanceof ClassInstanceExpr
{
SignatureAlgorithmInstance() { super.getConstructedType() instanceof Signers::Signer }

// TODO: Could potentially be used to model signature modes like Ed25519ph and Ed25519ctx.
// TODO: Could potentially be used to model signature modes like Ed25519ph and Ed25519ctx.
override Crypto::ModeOfOperationAlgorithmInstance getModeOfOperationAlgorithm() { none() }

override Crypto::PaddingAlgorithmInstance getPaddingAlgorithm() { none() }

override Crypto::ConsumerInputDataFlowNode getKeySizeConsumer() { none() }

override Crypto::KeyOpAlg::Algorithm getAlgorithmType() {
nameToTypeAndKeySizeMapping(this.getRawAlgorithmName(), _, result)
nameToKeySizeAndAlgorithmMapping(this.getRawAlgorithmName(), _, result)
}

override string getRawAlgorithmName() {
override string getRawAlgorithmName() {
result = super.getConstructedType().getName().splitAt("Signer", 0)
}

override string getKeySizeFixed() {
nameToTypeAndKeySizeMapping(this.getRawAlgorithmName(), result, _)
override string getKeySizeFixed() {
nameToKeySizeAndAlgorithmMapping(this.getRawAlgorithmName(), result, _)
}
}

predicate nameToTypeAndKeySizeMapping(string name, string keySize, Crypto::KeyOpAlg::Algorithm algorithm) {
name = "Ed25519" and keySize = "256" and algorithm = Crypto::KeyOpAlg::TSignature(Crypto::KeyOpAlg::Ed25519())
private predicate nameToKeySizeAndAlgorithmMapping(
string name, string keySize, Crypto::KeyOpAlg::Algorithm algorithm
) {
// Ed25519, Ed25519ph, and Ed25519ctx
name = "Ed25519%" and
keySize = "256" and
algorithm = Crypto::KeyOpAlg::TSignature(Crypto::KeyOpAlg::Ed25519())
or
name = "Ed448" and keySize = "448" and algorithm = Crypto::KeyOpAlg::TSignature(Crypto::KeyOpAlg::Ed448())
// Ed448 and Ed448ph
name = "Ed448%" and
keySize = "448" and
algorithm = Crypto::KeyOpAlg::TSignature(Crypto::KeyOpAlg::Ed448())
}



Loading
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy