Skip to content

Commit 86cab46

Browse files
committed
Misc. updates to support all JCA cipher operations, including wrap, unwrap and doFinal calls. Corrected pathing for init tracing to detect what mode is being set along a path. Added support for tracing the init operation mode argument to source. Since this involved creating an Operation Mode, changes were also made to make cipher block modes (CBC) more explicit (previously just called mode, but now that term is used for various purposes).
1 parent 9ac9252 commit 86cab46

File tree

2 files changed

+181
-53
lines changed
  • java/ql/lib/experimental/Quantum
  • shared/cryptography/codeql/cryptography

2 files changed

+181
-53
lines changed

java/ql/lib/experimental/Quantum/JCA.qll

Lines changed: 151 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -58,8 +58,12 @@ module JCAModel {
5858
Expr getProviderArg() { result = this.getArgument(1) }
5959
}
6060

61-
class CipherDoFinalCall extends Call {
62-
CipherDoFinalCall() { this.getCallee().hasQualifiedName("javax.crypto", "Cipher", "doFinal") }
61+
private class JCACipherOperationCall extends Call {
62+
JCACipherOperationCall() {
63+
exists(string s | s in ["doFinal", "wrap", "unwrap"] |
64+
this.getCallee().hasQualifiedName("javax.crypto", "Cipher", s)
65+
)
66+
}
6367
}
6468

6569
/**
@@ -81,7 +85,7 @@ module JCAModel {
8185
* For example, in `Cipher.getInstance(algorithm)`, this class represents `algorithm`.
8286
*/
8387
class CipherGetInstanceAlgorithmArg extends Crypto::EncryptionAlgorithmInstance,
84-
Crypto::ModeOfOperationAlgorithmInstance, Crypto::PaddingAlgorithmInstance instanceof Expr
88+
Crypto::BlockCipherModeOfOperationAlgorithmInstance, Crypto::PaddingAlgorithmInstance instanceof Expr
8589
{
8690
CipherGetInstanceCall call;
8791

@@ -98,68 +102,136 @@ module JCAModel {
98102
CipherGetInstanceCall getCall() { result = call }
99103
}
100104

101-
// TODO: what if encrypt/decrypt mode isn't known
102-
private module CipherGetInstanceToFinalizeConfig implements DataFlow::StateConfigSig {
103-
class FlowState = Crypto::TCipherOperationMode;
105+
/**
106+
* An access to the `javax.crypto.Cipher` class.
107+
*/
108+
private class CipherAccess extends TypeAccess {
109+
CipherAccess() { this.getType().(Class).hasQualifiedName("javax.crypto", "Cipher") }
110+
}
111+
112+
/**
113+
* An access to a cipher mode field of the `javax.crypto.Cipher` class,
114+
* specifically `ENCRYPT_MODE`, `DECRYPT_MODE`, `WRAP_MODE`, or `UNWRAP_MODE`.
115+
*/
116+
private class JavaxCryptoCipherOperationModeAccess extends FieldAccess {
117+
JavaxCryptoCipherOperationModeAccess() {
118+
this.getQualifier() instanceof CipherAccess and
119+
this.getField().getName() in ["ENCRYPT_MODE", "DECRYPT_MODE", "WRAP_MODE", "UNWRAP_MODE"]
120+
}
121+
}
122+
123+
private newtype TCipherModeFlowState =
124+
TUninitializedCipherModeFlowState() or
125+
TInitializedCipherModeFlowState(CipherInitCall call)
126+
127+
abstract private class CipherModeFlowState extends TCipherModeFlowState {
128+
string toString() {
129+
this = TUninitializedCipherModeFlowState() and result = "uninitialized"
130+
or
131+
this = TInitializedCipherModeFlowState(_) and result = "initialized"
132+
}
133+
134+
abstract Crypto::CipherOperationMode getCipherOperationMode();
135+
}
136+
137+
private class UninitializedCipherModeFlowState extends CipherModeFlowState,
138+
TUninitializedCipherModeFlowState
139+
{
140+
override Crypto::CipherOperationMode getCipherOperationMode() {
141+
result instanceof Crypto::UnknownCipherOperationMode
142+
}
143+
}
144+
145+
private class InitializedCipherModeFlowState extends CipherModeFlowState,
146+
TInitializedCipherModeFlowState
147+
{
148+
CipherInitCall call;
149+
DataFlow::Node node1;
150+
DataFlow::Node node2;
151+
Crypto::CipherOperationMode mode;
152+
153+
InitializedCipherModeFlowState() {
154+
this = TInitializedCipherModeFlowState(call) and
155+
DataFlow::localFlowStep(node1, node2) and
156+
node2.asExpr() = call.getQualifier() and
157+
// I would imagine this would make this predicate horribly horribly inefficient
158+
// it now binds with anything
159+
not node1.asExpr() = call.getQualifier() and
160+
mode = call.getCipherOperationModeType()
161+
}
162+
163+
CipherInitCall getCall() { result = call }
164+
165+
DataFlow::Node getFstNode() { result = node1 }
166+
167+
/**
168+
* Returns the node *to* which the state-changing step occurs
169+
*/
170+
DataFlow::Node getSndNode() { result = node2 }
171+
172+
override Crypto::CipherOperationMode getCipherOperationMode() { result = mode }
173+
}
174+
175+
/**
176+
* Trace to a cryptographic operation,
177+
* specifically `Cipher.doFinal()`, `Cipher.wrap()`, or `Cipher.unwrap()`.
178+
*/
179+
private module CipherGetInstanceToCipherOperationConfig implements DataFlow::StateConfigSig {
180+
class FlowState = TCipherModeFlowState;
104181

105182
predicate isSource(DataFlow::Node src, FlowState state) {
106-
state = Crypto::UnknownCipherOperationMode() and
183+
state instanceof UninitializedCipherModeFlowState and
107184
src.asExpr() instanceof CipherGetInstanceCall
108185
}
109186

110-
predicate isSink(DataFlow::Node sink, FlowState state) {
111-
exists(CipherDoFinalCall c | c.getQualifier() = sink.asExpr())
187+
predicate isSink(DataFlow::Node sink, FlowState state) { none() }
188+
189+
predicate isSink(DataFlow::Node sink) {
190+
exists(JCACipherOperationCall c | c.getQualifier() = sink.asExpr())
112191
}
113192

114193
predicate isAdditionalFlowStep(
115194
DataFlow::Node node1, FlowState state1, DataFlow::Node node2, FlowState state2
116195
) {
117-
exists(CipherInitCall c |
118-
c.getQualifier() = node1.asExpr() and
119-
// TODO: not taking into consideration if the mode traces to this arg
120-
exists(FieldAccess fa |
121-
c.getModeArg() = fa and
122-
(
123-
if fa.getField().getName() in ["ENCRYPT_MODE", "WRAP_MODE"]
124-
then state2 = Crypto::EncryptionMode()
125-
else (
126-
if fa.getField().getName() in ["DECRYPT_MODE", "UNWRAP_MODE"]
127-
then state2 = Crypto::DecryptionMode()
128-
else state2 = Crypto::UnknownCipherOperationMode()
129-
)
130-
)
131-
)
132-
) and
133-
node2 = node1
196+
node1 = state2.(InitializedCipherModeFlowState).getFstNode() and
197+
node2 = state2.(InitializedCipherModeFlowState).getSndNode()
198+
}
199+
200+
predicate isBarrier(DataFlow::Node node, FlowState state) {
201+
exists(CipherInitCall call | node.asExpr() = call.getQualifier() |
202+
state instanceof UninitializedCipherModeFlowState
203+
or
204+
state.(InitializedCipherModeFlowState).getCall() != call
205+
)
134206
}
135207
}
136208

137-
module CipherGetInstanceToFinalizeFlow =
138-
DataFlow::GlobalWithState<CipherGetInstanceToFinalizeConfig>;
209+
module CipherGetInstanceToCipherOperationFlow =
210+
DataFlow::GlobalWithState<CipherGetInstanceToCipherOperationConfig>;
139211

140212
class CipherEncryptionOperation extends Crypto::CipherOperationInstance instanceof Call {
141-
Crypto::TCipherOperationMode mode;
213+
Crypto::CipherOperationMode mode;
142214
Crypto::EncryptionAlgorithmInstance algorithm;
143215

144216
CipherEncryptionOperation() {
145217
exists(
146-
CipherGetInstanceToFinalizeFlow::PathNode sink,
147-
CipherGetInstanceToFinalizeFlow::PathNode src, CipherGetInstanceCall getCipher,
148-
CipherDoFinalCall doFinalize, CipherGetInstanceAlgorithmArg arg
218+
CipherGetInstanceToCipherOperationFlow::PathNode sink,
219+
CipherGetInstanceToCipherOperationFlow::PathNode src, CipherGetInstanceCall getCipher,
220+
JCACipherOperationCall doFinalize, CipherGetInstanceAlgorithmArg arg
149221
|
150-
CipherGetInstanceToFinalizeFlow::flowPath(src, sink) and
222+
CipherGetInstanceToCipherOperationFlow::flowPath(src, sink) and
151223
src.getNode().asExpr() = getCipher and
152224
sink.getNode().asExpr() = doFinalize.getQualifier() and
153-
sink.getState() = mode and
225+
sink.getState().(CipherModeFlowState).getCipherOperationMode() = mode and
154226
this = doFinalize and
155-
arg.getCall() = getCipher and
227+
arg.getCall() = getCipher and
156228
algorithm = arg
157229
)
158230
}
159231

160232
override Crypto::EncryptionAlgorithmInstance getAlgorithm() { result = algorithm }
161233

162-
override Crypto::TCipherOperationMode getCipherOperationMode() { result = mode }
234+
override Crypto::CipherOperationMode getCipherOperationMode() { result = mode }
163235
}
164236

165237
/**
@@ -177,7 +249,7 @@ module JCAModel {
177249
CipherGetInstanceAlgorithmArg instance;
178250

179251
ModeOfOperation() {
180-
this = Crypto::TModeOfOperationAlgorithm(instance) and
252+
this = Crypto::TBlockCipherModeOfOperationAlgorithm(instance) and
181253
// TODO: this currently only holds for explicitly defined modes in a string literal.
182254
// Cases with defaults, e.g., "AES", are not yet modelled.
183255
// For these cases, in a CBOM, the AES node would have an unknown edge to its mode child.
@@ -190,7 +262,7 @@ module JCAModel {
190262
// TODO: handle defaults
191263
override string getRawAlgorithmName() { result = instance.getOrigin().getMode() }
192264

193-
private predicate modeToNameMappingKnown(Crypto::TModeOperationType type, string name) {
265+
private predicate modeToNameMappingKnown(Crypto::TBlockCipherModeOperationType type, string name) {
194266
type instanceof Crypto::ECB and name = "ECB"
195267
or
196268
type instanceof Crypto::CBC and name = "CBC"
@@ -208,7 +280,7 @@ module JCAModel {
208280
type instanceof Crypto::OCB and name = "OCB"
209281
}
210282

211-
override Crypto::TModeOperationType getModeType() {
283+
override Crypto::TBlockCipherModeOperationType getModeType() {
212284
if this.modeToNameMappingKnown(_, instance.getOrigin().getMode())
213285
then this.modeToNameMappingKnown(result, instance.getOrigin().getMode())
214286
else result instanceof Crypto::OtherMode
@@ -339,14 +411,51 @@ module JCAModel {
339411
override Expr getInput() { result = this.(ClassInstanceExpr).getArgument(1) }
340412
}
341413

414+
private module JavaxCipherModeAccessToInitConfig implements DataFlow::ConfigSig {
415+
predicate isSource(DataFlow::Node src) {
416+
src.asExpr() instanceof JavaxCryptoCipherOperationModeAccess
417+
}
418+
419+
predicate isSink(DataFlow::Node sink) {
420+
exists(CipherInitCall c | c.getModeArg() = sink.asExpr())
421+
}
422+
}
423+
424+
module JavaxCipherModeAccessToInitFlow = DataFlow::Global<JavaxCipherModeAccessToInitConfig>;
425+
342426
class CipherInitCall extends MethodCall {
343427
CipherInitCall() { this.getCallee().hasQualifiedName("javax.crypto", "Cipher", "init") }
344428

345-
// TODO: this doesn't account for tracing the mode to this arg if expending this arg to have
346-
// the actual mode directly
429+
/**
430+
* Returns the mode argument to the `init` method
431+
* that is used to determine the cipher operation mode.
432+
* Note this is the raw expr and not necessarily a direct access
433+
* of a mode. Use `getModeOrigin()` to get the field access origin
434+
* flowing to this argument, if one exists (is known).
435+
*/
347436
Expr getModeArg() { result = this.getArgument(0) }
348437

349-
// TODO: need a getModeOrigin
438+
JavaxCryptoCipherOperationModeAccess getModeOrigin() {
439+
exists(DataFlow::Node src, DataFlow::Node sink |
440+
JavaxCipherModeAccessToInitFlow::flow(src, sink) and
441+
src.asExpr() = result and
442+
this.getModeArg() = sink.asExpr()
443+
)
444+
}
445+
446+
Crypto::CipherOperationMode getCipherOperationModeType() {
447+
if not exists(this.getModeOrigin())
448+
then result instanceof Crypto::UnknownCipherOperationMode
449+
else
450+
if this.getModeOrigin().getField().getName() in ["ENCRYPT_MODE", "WRAP_MODE"]
451+
then result instanceof Crypto::EncryptionMode
452+
else
453+
if this.getModeOrigin().getField().getName() in ["DECRYPT_MODE", "UNWRAP_MODE"]
454+
then result instanceof Crypto::DecryptionMode
455+
else
456+
// TODO/Question: distinguish between unknown vs unspecified? (the field access is not recognized, vs no field access is found)
457+
result instanceof Crypto::UnknownCipherOperationMode
458+
}
350459

351460
Expr getKey() {
352461
result = this.getArgument(1) and this.getMethod().getParameterType(1).hasName("Key")

shared/cryptography/codeql/cryptography/Model.qll

Lines changed: 30 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ module CryptographyBase<LocationSig Location, InputSig<Location> Input> {
8282
abstract class CipherOperationInstance extends LocatableElement {
8383
abstract EncryptionAlgorithmInstance getAlgorithm();
8484

85-
abstract TCipherOperationMode getCipherOperationMode();
85+
abstract CipherOperationMode getCipherOperationMode();
8686
}
8787

8888
abstract class EncryptionAlgorithmInstance extends LocatableElement { }
@@ -94,7 +94,7 @@ module CryptographyBase<LocationSig Location, InputSig<Location> Input> {
9494
abstract class EllipticCurveAlgorithmInstance extends LocatableElement { }
9595

9696
// Non-standalone algorithms
97-
abstract class ModeOfOperationAlgorithmInstance extends LocatableElement { }
97+
abstract class BlockCipherModeOfOperationAlgorithmInstance extends LocatableElement { }
9898

9999
abstract class PaddingAlgorithmInstance extends LocatableElement { }
100100

@@ -128,7 +128,8 @@ module CryptographyBase<LocationSig Location, InputSig<Location> Input> {
128128
TKeyDerivationAlgorithm(KeyDerivationAlgorithmInstance e) or
129129
TKeyEncapsulationAlgorithm(KeyEncapsulationAlgorithmInstance e) or
130130
// Non-standalone Algorithms (e.g., Mode, Padding)
131-
TModeOfOperationAlgorithm(ModeOfOperationAlgorithmInstance e) or
131+
// TODO: need to rename this, as "mode" is getting reused in different contexts, be precise
132+
TBlockCipherModeOfOperationAlgorithm(BlockCipherModeOfOperationAlgorithmInstance e) or
132133
TPaddingAlgorithm(PaddingAlgorithmInstance e) or
133134
// Composite and hybrid cryptosystems (e.g., RSA-OAEP used with AES, post-quantum hybrid cryptosystems)
134135
// These nodes are always parent nodes and are not modeled but rather defined via library-agnostic patterns.
@@ -685,9 +686,25 @@ module CryptographyBase<LocationSig Location, InputSig<Location> Input> {
685686
}
686687

687688
newtype TCipherOperationMode =
688-
EncryptionMode() or
689-
DecryptionMode() or
690-
UnknownCipherOperationMode()
689+
TEncryptionMode() or
690+
TDecryptionMode() or
691+
TUnknownCipherOperationMode()
692+
693+
abstract class CipherOperationMode extends TCipherOperationMode {
694+
abstract string toString();
695+
}
696+
697+
class EncryptionMode extends CipherOperationMode, TEncryptionMode {
698+
override string toString() { result = "Encryption" }
699+
}
700+
701+
class DecryptionMode extends CipherOperationMode, TDecryptionMode {
702+
override string toString() { result = "Decryption" }
703+
}
704+
705+
class UnknownCipherOperationMode extends CipherOperationMode, TUnknownCipherOperationMode {
706+
override string toString() { result = "Unknown" }
707+
}
691708

692709
/**
693710
* An encryption operation that processes plaintext to generate a ciphertext.
@@ -706,7 +723,9 @@ module CryptographyBase<LocationSig Location, InputSig<Location> Input> {
706723
result = instance.getCipherOperationMode()
707724
}
708725

709-
final override EncryptionAlgorithm getAlgorithm() { result.getInstance() = instance.getAlgorithm() }
726+
final override EncryptionAlgorithm getAlgorithm() {
727+
result.getInstance() = instance.getAlgorithm()
728+
}
710729

711730
override string getInternalType() { result = "CipherOperation" }
712731
// /**
@@ -721,7 +740,7 @@ module CryptographyBase<LocationSig Location, InputSig<Location> Input> {
721740
/**
722741
* Block cipher modes of operation algorithms
723742
*/
724-
newtype TModeOperationType =
743+
newtype TBlockCipherModeOperationType =
725744
ECB() or // Not secure, widely used
726745
CBC() or // Vulnerable to padding oracle attacks
727746
GCM() or // Widely used AEAD mode (TLS 1.3, SSH, IPsec)
@@ -732,7 +751,7 @@ module CryptographyBase<LocationSig Location, InputSig<Location> Input> {
732751
OCB() or // Efficient AEAD mode
733752
OtherMode()
734753

735-
abstract class ModeOfOperationAlgorithm extends Algorithm, TModeOfOperationAlgorithm {
754+
abstract class ModeOfOperationAlgorithm extends Algorithm, TBlockCipherModeOfOperationAlgorithm {
736755
override string getAlgorithmType() { result = "ModeOfOperation" }
737756

738757
/**
@@ -742,10 +761,10 @@ module CryptographyBase<LocationSig Location, InputSig<Location> Input> {
742761
*
743762
* If a type cannot be determined, the result is `OtherMode`.
744763
*/
745-
abstract TModeOperationType getModeType();
764+
abstract TBlockCipherModeOperationType getModeType();
746765

747766
bindingset[type]
748-
final private predicate modeToNameMapping(TModeOperationType type, string name) {
767+
final private predicate modeToNameMapping(TBlockCipherModeOperationType type, string name) {
749768
type instanceof ECB and name = "ECB"
750769
or
751770
type instanceof CBC and name = "CBC"

0 commit comments

Comments
 (0)