Skip to content

Commit 2994504

Browse files
committed
Added initial support for BouncyCastle signers
1 parent 5df50c0 commit 2994504

File tree

5 files changed

+385
-0
lines changed

5 files changed

+385
-0
lines changed
Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
2+
import java
3+
4+
/**
5+
* Models for the signature algorithms defined by the `org.bouncycastle.crypto.signers` package.
6+
*
7+
*/
8+
module Signers {
9+
import Language
10+
import BouncyCastle.FlowAnalysis
11+
import BouncyCastle.AlgorithmInstances
12+
13+
abstract class SignatureAlgorithmValueConsumer extends Crypto::AlgorithmValueConsumer { }
14+
15+
/**
16+
* A model of the `Signer` class in Bouncy Castle.
17+
*/
18+
class Signer extends RefType {
19+
Signer() {
20+
this.getPackage().getName() = "org.bouncycastle.crypto.signers" and
21+
this.getName().matches("%Signer")
22+
}
23+
24+
MethodCall getAnInitCall() {
25+
result = this.getAMethodCall("init")
26+
}
27+
28+
MethodCall getAUseCall() {
29+
result = this.getAMethodCall(["update", "generateSignature", "verifySignature"])
30+
}
31+
32+
MethodCall getAMethodCall(string name) {
33+
result.getCallee().hasQualifiedName("org.bouncycastle.crypto.signers", this.getName(), name)
34+
}
35+
}
36+
37+
/**
38+
* BouncyCastle algorithms are instantiated by calling the constructor of the
39+
* corresponding class. The algorithm is implicitly defined by the constructor
40+
* call.
41+
*/
42+
class NewCall extends SignatureAlgorithmValueConsumer instanceof ClassInstanceExpr {
43+
NewCall() {
44+
this.getConstructedType() instanceof Signer
45+
}
46+
47+
override Crypto::AlgorithmInstance getAKnownAlgorithmSource() {
48+
result = getSignatureAlgorithmInstanceFromType(super.getConstructedType())
49+
}
50+
51+
// TODO: Since the algorithm is implicitly defined by the constructor, should
52+
// the input node be `this`?
53+
override Crypto::ConsumerInputDataFlowNode getInputNode() {
54+
result.asExpr() = this
55+
}
56+
}
57+
58+
/**
59+
* The type is instantiated by a constructor call and initialized by a call to
60+
* `init()` which takes two arguments. The first argument is a flag indicating
61+
* whether the operation is signing data or verifying a signature, and the
62+
* second is the key to use.
63+
*/
64+
class InitCall extends MethodCall {
65+
InitCall() {
66+
this = any(Signer signer).getAnInitCall()
67+
}
68+
69+
Expr getForSigningArg() { result = this.getArgument(0) }
70+
71+
Expr getKeyArg() { result = this.getArgument(1) }
72+
73+
Crypto::KeyOperationAlgorithmInstance getAlgorithm() {
74+
result = getSignatureAlgorithmInstanceFromType(this.getReceiverType())
75+
}
76+
77+
Crypto::KeyOperationSubtype getKeyOperationSubtype() {
78+
(
79+
this.getForSigningArg().(BooleanLiteral).getBooleanValue() = true and
80+
result = Crypto::TSignMode()
81+
) or (
82+
this.getForSigningArg().(BooleanLiteral).getBooleanValue() = false and
83+
result = Crypto::TVerifyMode()
84+
) or (
85+
result = Crypto::TUnknownKeyOperationMode()
86+
)
87+
}
88+
}
89+
90+
/**
91+
* The `update()` method is used to pass message data to the signer, and the
92+
* `generateSignature()` or `verifySignature()` methods are used to produce or
93+
* verify the signature, respectively.
94+
*/
95+
class UseCall extends MethodCall {
96+
UseCall() {
97+
this = any(Signer signer).getAUseCall()
98+
}
99+
100+
predicate isIntermediate() {
101+
this.getCallee().getName() = "update"
102+
}
103+
104+
Expr getInput() { result = this.getArgument(0) }
105+
106+
Expr getOutput() {
107+
result = this
108+
}
109+
}
110+
111+
/**
112+
* Instantiate the flow analysis module for the `Signer` class.
113+
*/
114+
module FlowAnalysis = NewToInitToUseFlowAnalysis<NewCall, InitCall, UseCall>;
115+
116+
UseCall getIntermediateUse(UseCall use) {
117+
result = FlowAnalysis::getAnIntermediateUseFromFinalUse(use, _, _)
118+
}
119+
120+
/**
121+
* A signing operation instance is a call to either `update()`, `generateSignature()`,
122+
* or `verifySignature()` on a `Signer` instance.
123+
*/
124+
class SignatureOperationInstance extends Crypto::KeyOperationInstance instanceof UseCall {
125+
126+
override Crypto::AlgorithmValueConsumer getAnAlgorithmValueConsumer() {
127+
result = FlowAnalysis::getInstantiationFromInit(this.getInitCall(), _, _)
128+
}
129+
override Crypto::KeyOperationSubtype getKeyOperationSubtype() {
130+
if FlowAnalysis::hasInit(this)
131+
then result = this.getInitCall().getKeyOperationSubtype()
132+
else result = Crypto::TUnknownKeyOperationMode()
133+
}
134+
135+
override Crypto::ConsumerInputDataFlowNode getKeyConsumer() {
136+
result.asExpr() = this.getInitCall().getKeyArg()
137+
}
138+
139+
override Crypto::ConsumerInputDataFlowNode getNonceConsumer() { none() }
140+
141+
override Crypto::ConsumerInputDataFlowNode getInputConsumer() {
142+
result.asExpr() = this.getAnUpdateCall().getInput()
143+
}
144+
145+
override Crypto::ArtifactOutputDataFlowNode getOutputArtifact() {
146+
this.getKeyOperationSubtype() = Crypto::TSignMode() and
147+
not super.isIntermediate() and
148+
result.asExpr() = super.getOutput()
149+
}
150+
151+
InitCall getInitCall() {
152+
result = FlowAnalysis::getInitFromUse(this, _, _)
153+
}
154+
155+
UseCall getAnUpdateCall() {
156+
(
157+
super.isIntermediate() and result = this
158+
) or (
159+
result = FlowAnalysis::getAnIntermediateUseFromFinalUse(this, _, _)
160+
)
161+
}
162+
}
163+
}
164+
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import java
2+
import experimental.quantum.Language
3+
import AlgorithmInstances.SignatureAlgorithmInstances
4+
5+
SignatureAlgorithmInstance getSignatureAlgorithmInstanceFromType(Type t) {
6+
t.getName() = "Ed25519Signer" and result instanceof Ed25519AlgorithmInstance
7+
or
8+
t.getName() = "Ed448Signer" and result instanceof Ed448AlgorithmInstance
9+
}
10+
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import java
2+
import experimental.quantum.Language
3+
4+
abstract class SignatureAlgorithmInstance extends Crypto::KeyOperationAlgorithmInstance {
5+
6+
// TODO: Could potentially be used to model signature modes like Ed25519ph and Ed25519ctx.
7+
override Crypto::ModeOfOperationAlgorithmInstance getModeOfOperationAlgorithm() { none() }
8+
9+
override Crypto::PaddingAlgorithmInstance getPaddingAlgorithm() { none() }
10+
11+
override Crypto::ConsumerInputDataFlowNode getKeySizeConsumer() { none() }
12+
}
13+
14+
class Ed25519AlgorithmInstance extends SignatureAlgorithmInstance instanceof ClassInstanceExpr {
15+
Ed25519AlgorithmInstance() {
16+
this.getConstructedType().hasQualifiedName("org.bouncycastle.crypto.signers", "Ed25519Signer")
17+
}
18+
19+
override string getRawAlgorithmName() { result = "Ed25519" }
20+
21+
override Crypto::KeyOpAlg::Algorithm getAlgorithmType() {
22+
result = Crypto::KeyOpAlg::TSignature(Crypto::KeyOpAlg::Ed25519())
23+
}
24+
25+
// TODO: May be redundant.
26+
override string getKeySizeFixed() { result = "256" }
27+
}
28+
29+
class Ed448AlgorithmInstance extends SignatureAlgorithmInstance instanceof ClassInstanceExpr {
30+
Ed448AlgorithmInstance() {
31+
this.getConstructedType().hasQualifiedName("org.bouncycastle.crypto.signers", "Ed448Signer")
32+
}
33+
34+
override string getRawAlgorithmName() { result = "Ed448" }
35+
36+
override Crypto::KeyOpAlg::Algorithm getAlgorithmType() {
37+
result = Crypto::KeyOpAlg::TSignature(Crypto::KeyOpAlg::Ed448())
38+
}
39+
40+
// TODO: May be redundant.
41+
override string getKeySizeFixed() { result = "448" }
42+
}
43+
44+
Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
import java
2+
import semmle.code.java.dataflow.DataFlow
3+
4+
/**
5+
* A signature for the `getInstance()` method calls used in JCA, or direct
6+
* constructor calls used in BouncyCastle.
7+
*/
8+
signature class NewCallSig instanceof Call;
9+
10+
signature class InitCallSig instanceof MethodCall;
11+
12+
signature class UseCallSig instanceof MethodCall {
13+
/**
14+
* Holds if the use is not a final use, such as an `update()` call before `doFinal()`
15+
*/
16+
predicate isIntermediate();
17+
}
18+
19+
/**
20+
* An generic analysis module for analyzing the `getInstance()` to `initialize()`
21+
* to `doOperation()` pattern in the JCA, and similar patterns in other libraries.
22+
*
23+
* For example:
24+
* ```
25+
* kpg = KeyPairGenerator.getInstance();
26+
* kpg.initialize(...);
27+
* kpg.generate(...);
28+
* ```
29+
*/
30+
module NewToInitToUseFlowAnalysis<NewCallSig GetInstance, InitCallSig Init, UseCallSig Use>
31+
{
32+
newtype TFlowState =
33+
TUninitialized() or
34+
TInitialized(Init call) or
35+
TIntermediateUse(Use call)
36+
37+
abstract class InitFlowState extends TFlowState {
38+
string toString() {
39+
this = TUninitialized() and result = "Uninitialized"
40+
or
41+
this = TInitialized(_) and result = "Initialized"
42+
// TODO: add intermediate use
43+
}
44+
}
45+
46+
class UninitializedFlowState extends InitFlowState, TUninitialized { }
47+
48+
class InitializedFlowState extends InitFlowState, TInitialized {
49+
Init call;
50+
DataFlow::Node node1;
51+
DataFlow::Node node2;
52+
53+
InitializedFlowState() {
54+
this = TInitialized(call) and
55+
node2.asExpr() = call.(MethodCall).getQualifier() and
56+
DataFlow::localFlowStep(node1, node2) and
57+
node1 != node2
58+
}
59+
60+
Init getInitCall() { result = call }
61+
62+
DataFlow::Node getFstNode() { result = node1 }
63+
64+
DataFlow::Node getSndNode() { result = node2 }
65+
}
66+
67+
class IntermediateUseState extends InitFlowState, TIntermediateUse {
68+
Use call;
69+
DataFlow::Node node1;
70+
DataFlow::Node node2;
71+
72+
IntermediateUseState() {
73+
this = TIntermediateUse(call) and
74+
call.isIntermediate() and
75+
node1.asExpr() = call.(MethodCall).getQualifier() and
76+
node2 = node1
77+
}
78+
79+
Use getUseCall() { result = call }
80+
81+
DataFlow::Node getFstNode() { result = node1 }
82+
83+
DataFlow::Node getSndNode() { result = node2 }
84+
}
85+
86+
module GetInstanceToInitToUseConfig implements DataFlow::StateConfigSig {
87+
class FlowState = InitFlowState;
88+
89+
predicate isSource(DataFlow::Node src, FlowState state) {
90+
state instanceof UninitializedFlowState and
91+
src.asExpr() instanceof GetInstance
92+
or
93+
src = state.(InitializedFlowState).getSndNode()
94+
or
95+
src = state.(IntermediateUseState).getSndNode()
96+
}
97+
98+
// TODO: document this, but this is intentional (avoid cross products?)
99+
predicate isSink(DataFlow::Node sink, FlowState state) { none() }
100+
101+
predicate isSink(DataFlow::Node sink) {
102+
exists(Init c | c.(MethodCall).getQualifier() = sink.asExpr())
103+
or
104+
exists(Use c | not c.isIntermediate() and c.(MethodCall).getQualifier() = sink.asExpr())
105+
}
106+
107+
predicate isAdditionalFlowStep(
108+
DataFlow::Node node1, FlowState state1, DataFlow::Node node2, FlowState state2
109+
) {
110+
state1 = state1 and
111+
(
112+
node1 = state2.(InitializedFlowState).getFstNode() and
113+
node2 = state2.(InitializedFlowState).getSndNode()
114+
or
115+
node1 = state2.(IntermediateUseState).getFstNode() and
116+
node2 = state2.(IntermediateUseState).getSndNode()
117+
)
118+
}
119+
120+
predicate isBarrier(DataFlow::Node node, FlowState state) {
121+
exists(Init call | node.asExpr() = call.(MethodCall).getQualifier() |
122+
state instanceof UninitializedFlowState
123+
or
124+
state.(InitializedFlowState).getInitCall() != call
125+
)
126+
}
127+
}
128+
129+
module GetInstanceToInitToUseFlow = DataFlow::GlobalWithState<GetInstanceToInitToUseConfig>;
130+
131+
GetInstance getInstantiationFromUse(
132+
Use use, GetInstanceToInitToUseFlow::PathNode src, GetInstanceToInitToUseFlow::PathNode sink
133+
) {
134+
src.getNode().asExpr() = result and
135+
sink.getNode().asExpr() = use.(MethodCall).getQualifier() and
136+
GetInstanceToInitToUseFlow::flowPath(src, sink)
137+
}
138+
139+
GetInstance getInstantiationFromInit(
140+
Init init, GetInstanceToInitToUseFlow::PathNode src, GetInstanceToInitToUseFlow::PathNode sink
141+
) {
142+
src.getNode().asExpr() = result and
143+
sink.getNode().asExpr() = init.(MethodCall).getQualifier() and
144+
GetInstanceToInitToUseFlow::flowPath(src, sink)
145+
}
146+
147+
Init getInitFromUse(
148+
Use use, GetInstanceToInitToUseFlow::PathNode src, GetInstanceToInitToUseFlow::PathNode sink
149+
) {
150+
src.getNode().asExpr() = result.(MethodCall).getQualifier() and
151+
sink.getNode().asExpr() = use.(MethodCall).getQualifier() and
152+
GetInstanceToInitToUseFlow::flowPath(src, sink)
153+
}
154+
155+
predicate hasInit(Use use) { exists(getInitFromUse(use, _, _)) }
156+
157+
Use getAnIntermediateUseFromFinalUse(
158+
Use final, GetInstanceToInitToUseFlow::PathNode src, GetInstanceToInitToUseFlow::PathNode sink
159+
) {
160+
not final.isIntermediate() and
161+
result.isIntermediate() and
162+
src.getNode().asExpr() = result.(MethodCall).getQualifier() and
163+
sink.getNode().asExpr() = final.(MethodCall).getQualifier() and
164+
GetInstanceToInitToUseFlow::flowPath(src, sink)
165+
}
166+
}

java/ql/lib/experimental/quantum/Language.qll

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,3 +216,4 @@ module ArtifactFlow = DataFlow::Global<ArtifactFlowConfig>;
216216

217217
// Import library-specific modeling
218218
import JCA
219+
import BouncyCastle

0 commit comments

Comments
 (0)