Skip to content

Commit 98aae6a

Browse files
committed
Crypto: Add EVP key gen and signature operation (work in progress). Add initial signature tests (no expected files yet). Add new openssl .h stubs. Clean up of OperationBase and associated uses. Update test case stubs to be closer to the actual stubs. Fix unncessary instanceof check in signatures.
1 parent f952f90 commit 98aae6a

26 files changed

+3206
-679
lines changed

cpp/ql/lib/experimental/quantum/OpenSSL/AlgorithmInstances/SignatureAlgorithmInstance.qll

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ class KnownOpenSSLSignatureConstantAlgorithmInstance extends OpenSSLAlgorithmIns
4242
// 1) The source is a literal and flows to a getter, then we know we have an instance
4343
// 2) The source is a KnownOpenSSLAlgorithm call, and we know we have an instance immediately from that
4444
// Possibility 1:
45-
this instanceof KnownOpenSSLPaddingAlgorithmExpr and
45+
this instanceof OpenSSLAlgorithmLiteral and
4646
exists(DataFlow::Node src, DataFlow::Node sink |
4747
// Sink is an argument to a signature getter call
4848
sink = getterCall.getInputNode() and

cpp/ql/lib/experimental/quantum/OpenSSL/Operations/EVPCipherOperation.qll

Lines changed: 4 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -94,24 +94,6 @@ class EVP_CipherInit_SKEY_Call extends EVP_EX2_Initializer {
9494
override Expr getOperationSubtypeArg() { result = this.(Call).getArgument(5) }
9595
}
9696

97-
class EVPCipherInitializerAlgorithmArgument extends Expr {
98-
EVPCipherInitializerAlgorithmArgument() {
99-
exists(EVP_Cipher_Initializer initCall | this = initCall.getAlgorithmArg())
100-
}
101-
}
102-
103-
class EVPCipherInitializerKeyArgument extends Expr {
104-
EVPCipherInitializerKeyArgument() {
105-
exists(EVP_Cipher_Initializer initCall | this = initCall.getKeyArg())
106-
}
107-
}
108-
109-
class EVPCipherInitializerIVArgument extends Expr {
110-
EVPCipherInitializerIVArgument() {
111-
exists(EVP_Cipher_Initializer initCall | this = initCall.getIVArg())
112-
}
113-
}
114-
11597
class EVP_Cipher_Update_Call extends EVPUpdate {
11698
EVP_Cipher_Update_Call() {
11799
this.(Call).getTarget().getName() in [
@@ -164,6 +146,8 @@ class EVP_Cipher_Call extends EVPOperation, EVP_Cipher_Operation {
164146
EVP_Cipher_Call() { this.(Call).getTarget().getName() = "EVP_Cipher" }
165147

166148
override Expr getInputArg() { result = this.(Call).getArgument(2) }
149+
150+
override Expr getAlgorithmArg() { result = this.getInitCall().getAlgorithmArg() }
167151
}
168152

169153
class EVP_Cipher_Final_Call extends EVPFinal, EVP_Cipher_Operation {
@@ -182,4 +166,6 @@ class EVP_Cipher_Final_Call extends EVPFinal, EVP_Cipher_Operation {
182166
or
183167
result = EVP_Cipher_Operation.super.getOutputArg()
184168
}
169+
170+
override Expr getAlgorithmArg() { result = this.getInitCall().getAlgorithmArg() }
185171
}

cpp/ql/lib/experimental/quantum/OpenSSL/Operations/EVPHashOperation.qll

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,4 +93,6 @@ class EVP_Digest_Final_Call extends EVPFinal, Crypto::HashOperationInstance {
9393
override Crypto::ConsumerInputDataFlowNode getInputConsumer() {
9494
result = EVPFinal.super.getInputConsumer()
9595
}
96+
97+
override Expr getAlgorithmArg() { result = this.getInitCall().getAlgorithmArg() }
9698
}
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
private import experimental.quantum.Language
2+
private import experimental.quantum.OpenSSL.CtxFlow
3+
private import OpenSSLOperationBase
4+
private import experimental.quantum.OpenSSL.AlgorithmValueConsumers.OpenSSLAlgorithmValueConsumers
5+
private import semmle.code.cpp.dataflow.new.DataFlow
6+
7+
class EVPKeyGenInitialize extends EVPInitialize {
8+
EVPKeyGenInitialize() {
9+
this.(Call).getTarget().getName() in [
10+
"EVP_PKEY_keygen_init",
11+
"EVP_PKEY_paramgen_init"
12+
]
13+
}
14+
15+
override Expr getAlgorithmArg() {
16+
// The context argument encodes the algorithm
17+
result = this.getContextArg()
18+
}
19+
}
20+
21+
// /**
22+
// * All calls that can be tracked via ctx.
23+
// * For example calls used to set parameters like a key size.
24+
// */
25+
// class EVPKeyGenUpdate extends Call {
26+
// EVPKeyGenUpdate() {
27+
// this.(Call).getTarget().getName() in [
28+
// "EVP_PKEY_CTX_set_rsa_keygen_bits",
29+
// // TODO: "EVP_PKEY_CTX_set_params"
30+
// ]
31+
// }
32+
// /**
33+
// * No input in our meaning.
34+
// */
35+
// override Expr getInputArg() { none() }
36+
// /**
37+
// * No output in our meaning.
38+
// */
39+
// override Expr getOutputArg() { none() }
40+
// Expr getKeySizeArg() {
41+
// this.(Call).getTarget().getName() = "EVP_PKEY_CTX_set_rsa_keygen_bits" and
42+
// result = this.(Call).getArgument(1)
43+
// }
44+
// }
45+
class EVPKeyGenOperation extends EVPOperation, Crypto::KeyGenerationOperationInstance {
46+
DataFlow::Node keyResultNode;
47+
48+
EVPKeyGenOperation() {
49+
this.(Call).getTarget().getName() in ["EVP_RSA_gen", "EVP_PKEY_Q_keygen"] and
50+
keyResultNode.asExpr() = this
51+
or
52+
this.(Call).getTarget().getName() in [
53+
"EVP_PKEY_generate", "EVP_PKEY_keygen", "EVP_PKEY_paramgen"
54+
] and
55+
keyResultNode.asDefiningArgument() = this.(Call).getArgument(1)
56+
}
57+
58+
override Expr getAlgorithmArg() {
59+
this.(Call).getTarget().getName() = "EVP_PKEY_Q_keygen" and
60+
result = this.(Call).getArgument(0)
61+
or
62+
result = this.getInitCall().getAlgorithmArg()
63+
}
64+
65+
override Crypto::KeyArtifactType getOutputKeyType() { result = Crypto::TAsymmetricKeyType() }
66+
67+
override Expr getInputArg() { none() }
68+
69+
override Expr getOutputArg() { result = this.(Call).getArgument(1) }
70+
71+
override Crypto::ArtifactOutputDataFlowNode getOutputKeyArtifact() { result = keyResultNode }
72+
73+
override Crypto::ConsumerInputDataFlowNode getKeySizeConsumer() {
74+
none()
75+
// if this.(Call).getTarget().getName() = "EVP_PKEY_Q_keygen"
76+
// then result = DataFlow::exprNode(this.(Call).getArgument(3)) // TODO: may be wrong for EC keys
77+
// else
78+
// if this.(Call).getTarget().getName() = "EVP_RSA_gen"
79+
// then result = DataFlow::exprNode(this.(Call).getArgument(0))
80+
// else result = DataFlow::exprNode(this.getUpdateCalls().(EVPKeyGenUpdate).getKeySizeArg())
81+
}
82+
83+
override int getKeySizeFixed() {
84+
none() // TODO
85+
}
86+
}
Lines changed: 227 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,227 @@
1+
/**
2+
* Provides classes for modeling OpenSSL's EVP signature operations
3+
*/
4+
5+
private import experimental.quantum.Language
6+
private import OpenSSLOperationBase
7+
private import experimental.quantum.OpenSSL.CtxFlow
8+
private import experimental.quantum.OpenSSL.AlgorithmValueConsumers.OpenSSLAlgorithmValueConsumers
9+
private import experimental.quantum.OpenSSL.Operations.OpenSSLOperations
10+
11+
module OpenSSLKeyGenToArgConfig implements DataFlow::ConfigSig {
12+
predicate isSource(DataFlow::Node source) {
13+
exists(Crypto::KeyGenerationOperationInstance keygen | keygen.getOutputKeyArtifact() = source)
14+
}
15+
16+
predicate isSink(DataFlow::Node sink) { exists(Call c | c.getAnArgument() = sink.asExpr()) }
17+
}
18+
19+
module OpenSSLKeyGenToArgFlow = TaintTracking::Global<OpenSSLKeyGenToArgConfig>;
20+
21+
// TODO: verification functions
22+
class EVP_Signature_Initializer extends EVPInitialize {
23+
EVP_Signature_Initializer() {
24+
this.(Call).getTarget().getName() in [
25+
"EVP_DigestSignInit", "EVP_DigestSignInit_ex", "EVP_SignInit", "EVP_SignInit_ex",
26+
"EVP_PKEY_sign_init", "EVP_PKEY_sign_init_ex", "EVP_PKEY_sign_init_ex2",
27+
"EVP_PKEY_sign_message_init"
28+
]
29+
}
30+
31+
/**
32+
* Gets the algorithm associated with this initialization by following
33+
* where the algorithm is set through the context argument.
34+
*/
35+
Expr getAlgorithmArgFromCtx() {
36+
// exists(EVPPKeyAlgorithmConsumer source, DataFlow::Node sink |
37+
// result = source.getInputNode().asExpr() and
38+
// sink.asExpr() = this.getContextArg() and
39+
// OpenSSLCtxSourceToArgumentFlow::flow(source.getResultNode(), sink)
40+
// )
41+
// or
42+
result = this.getAlgorithmArgFromKey(this.getKeyArgFromCtx())
43+
}
44+
45+
Expr getAlgorithmArgFromKey(Expr keyArg) {
46+
exists(Crypto::KeyGenerationOperationInstance keygen |
47+
OpenSSLKeyGenToArgFlow::flow(keygen.getOutputKeyArtifact(), DataFlow::exprNode(keyArg)) and
48+
result = keygen.(OpenSSLOperation).getAlgorithmArg()
49+
)
50+
}
51+
52+
/**
53+
* Gets the argument ingesting a key
54+
* by tracing the context arg back to a context creation
55+
*/
56+
Expr getKeyArgFromCtx() {
57+
exists(Call contextCreationCall |
58+
ctxArgOrRetFlowsToCtxArg(contextCreationCall, this.getContextArg()) and
59+
(
60+
contextCreationCall.getTarget().getName() = "EVP_PKEY_CTX_new" and
61+
result = contextCreationCall.getArgument(0)
62+
or
63+
contextCreationCall.getTarget().getName() = "EVP_PKEY_CTX_new_from_pkey" and
64+
result = contextCreationCall.getArgument(1)
65+
)
66+
)
67+
}
68+
69+
override Expr getAlgorithmArg() {
70+
// explicit algorithm as argument
71+
this.(Call).getTarget().getName() in ["EVP_PKEY_sign_init_ex2", "EVP_PKEY_sign_message_init"] and
72+
result = this.(Call).getArgument(1)
73+
// or
74+
// // algorithm (and key) specified in the context
75+
// this.(Call).getTarget().getName() in ["EVP_PKEY_sign_init", "EVP_PKEY_sign_init_ex"] and
76+
// result = getAlgorithmArgFromCtx()
77+
// or
78+
// // algorithm specified by the key
79+
// this.(Call).getTarget().getName() in ["EVP_DigestSignInit", "EVP_DigestSignInit_ex"] and
80+
// result = getAlgorithmArgFromKey()
81+
// // NOTE: for EVP_SignInit and EVP_SignInit_ex the algorithm is not specified
82+
// // rather the algorithm is specified by the key used for signing later in a final call.
83+
}
84+
85+
/**
86+
* Returns the key argument if there is one.
87+
* If the key was provided via the context, we track it to the context.
88+
*/
89+
override Expr getKeyArg() {
90+
this.(Call).getTarget().getName() = "EVP_DigestSignInit" and
91+
result = this.(Call).getArgument(4)
92+
or
93+
this.(Call).getTarget().getName() = "EVP_DigestSignInit_ex" and
94+
result = this.(Call).getArgument(5)
95+
or
96+
this.(Call).getTarget().getName().matches("EVP_PKEY_%") and
97+
result = this.getKeyArgFromCtx()
98+
}
99+
100+
/**
101+
* Signing, verification or unknown.
102+
*/
103+
override Crypto::KeyOperationSubtype getKeyOperationSubtype() {
104+
if this.(Call).getTarget().getName().toLowerCase().matches("%sign%")
105+
then result instanceof Crypto::TSignMode
106+
else
107+
if this.(Call).getTarget().getName().toLowerCase().matches("%verify%")
108+
then result instanceof Crypto::TVerifyMode
109+
else result instanceof Crypto::TUnknownKeyOperationMode
110+
}
111+
}
112+
113+
class EVP_Signature_Update_Call extends EVPUpdate {
114+
EVP_Signature_Update_Call() {
115+
this.(Call).getTarget().getName() in [
116+
"EVP_DigestSignUpdate", "EVP_SignUpdate", "EVP_PKEY_sign_message_update"
117+
]
118+
}
119+
120+
/**
121+
* Input is the message to sign.
122+
*/
123+
override Expr getInputArg() { result = this.(Call).getArgument(1) }
124+
}
125+
126+
/**
127+
* We model output explicit output arguments as predicate to use it in constructors.
128+
* The predicate must cover all EVP_Signature_Operation subclasses.
129+
*/
130+
private Expr signatureOperationOutputArg(Call call) {
131+
if call.getTarget().getName() = "EVP_SignFinal_ex"
132+
then result = call.getArgument(2)
133+
else result = call.getArgument(1)
134+
}
135+
136+
/**
137+
* Base configuration for all EVP signature operations.
138+
*/
139+
abstract class EVP_Signature_Operation extends EVPOperation, Crypto::SignatureOperationInstance {
140+
EVP_Signature_Operation() {
141+
this.(Call).getTarget().getName().matches("EVP_%") and
142+
// NULL output argument means the call is to get the size of the signature and such call is not an operation
143+
(
144+
not exists(signatureOperationOutputArg(this).getValue())
145+
or
146+
signatureOperationOutputArg(this).getValue() != "0"
147+
)
148+
}
149+
150+
/**
151+
* Signing, verification or unknown.
152+
*/
153+
override Crypto::KeyOperationSubtype getKeyOperationSubtype() {
154+
// TODO: if this KeyOperationSubtype does not match initialization call's KeyOperationSubtype then we found a bug
155+
if this.(Call).getTarget().getName().toLowerCase().matches("%sign%")
156+
then result instanceof Crypto::TSignMode
157+
else
158+
if this.(Call).getTarget().getName().toLowerCase().matches("%verify%")
159+
then result instanceof Crypto::TVerifyMode
160+
else result instanceof Crypto::TUnknownKeyOperationMode
161+
}
162+
163+
override Crypto::ConsumerInputDataFlowNode getNonceConsumer() {
164+
// TODO: some signing operations may have explicit nonce generators
165+
none()
166+
}
167+
168+
/**
169+
* Keys provided in the initialization call or in a context are found by this method.
170+
* Keys in explicit arguments are found by overriden methods in extending classes.
171+
*/
172+
override Crypto::ConsumerInputDataFlowNode getKeyConsumer() {
173+
result = DataFlow::exprNode(this.getInitCall().getKeyArg())
174+
}
175+
176+
override Crypto::ArtifactOutputDataFlowNode getOutputArtifact() {
177+
result = EVPOperation.super.getOutputArtifact()
178+
}
179+
180+
override Crypto::ConsumerInputDataFlowNode getInputConsumer() {
181+
result = EVPOperation.super.getInputConsumer()
182+
}
183+
184+
/**
185+
* TODO: only signing operations for now, change when verificaiton is added
186+
*/
187+
override Crypto::ConsumerInputDataFlowNode getSignatureConsumer() { none() }
188+
}
189+
// class EVP_Signature_Call extends EVPOperation, EVP_Signature_Operation {
190+
// EVP_Signature_Call() { this.(Call).getTarget().getName() in ["EVP_DigestSign", "EVP_PKEY_sign"] }
191+
// /**
192+
// * Output is the signature.
193+
// */
194+
// override Expr getOutputArg() { result = signatureOperationOutputArg(this) }
195+
// /**
196+
// * Input is the message to sign.
197+
// */
198+
// override Expr getInputArg() { result = this.(Call).getArgument(3) }
199+
// }
200+
// class EVP_Signature_Final_Call extends EVPFinal, EVP_Signature_Operation {
201+
// EVP_Signature_Final_Call() {
202+
// this.(Call).getTarget().getName() in [
203+
// "EVP_DigestSignFinal", "EVP_SignFinal_ex", "EVP_SignFinal", "EVP_PKEY_sign_message_final"
204+
// ]
205+
// }
206+
// override Expr getAlgorithmArg() {
207+
// none()
208+
// // // algorithm specified by the key and the key is provided in this operation
209+
// // if this.(Call).getTarget().getName() in ["EVP_SignFinal", "EVP_SignFinal_ex"]
210+
// // then result = getAlgorithmFromKey(this.getKeyConsumer().asExpr())
211+
// // else
212+
// // // or find algorithm in the initialization call
213+
// // result = EVP_Signature_Operation.super.getAlgorithmArg()
214+
// }
215+
// override Crypto::ConsumerInputDataFlowNode getKeyConsumer() {
216+
// // key provided as an argument
217+
// if this.(Call).getTarget().getName() in ["EVP_SignFinal", "EVP_SignFinal_ex"]
218+
// then result = DataFlow::exprNode(this.(Call).getArgument(3))
219+
// else
220+
// // or find key in the initialization call
221+
// result = EVP_Signature_Operation.super.getKeyConsumer()
222+
// }
223+
// /**
224+
// * Output is the signature.
225+
// */
226+
// override Expr getOutputArg() { result = signatureOperationOutputArg(this) }
227+
// }

0 commit comments

Comments
 (0)