Skip to content

Commit 07a3032

Browse files
committed
quantum-c#: initial support for ECDSA and RSA signatures
1 parent 66eee73 commit 07a3032

File tree

7 files changed

+253
-0
lines changed

7 files changed

+253
-0
lines changed

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,3 +64,5 @@ module ArtifactFlowConfig implements Language::DataFlow::ConfigSig {
6464
}
6565

6666
module ArtifactFlow = Language::DataFlow::Global<ArtifactFlowConfig>;
67+
68+
import dotnet
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import dotnet.AlgorithmInstances
2+
import dotnet.AlgorithmValueConsumers
3+
import dotnet.FlowAnalysis
4+
import dotnet.Cryptography
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
private import csharp
2+
private import experimental.quantum.Language
3+
private import AlgorithmValueConsumers
4+
private import Cryptography
5+
private import FlowAnalysis
6+
7+
class SigningNamedCurveAlgorithmInstance extends Crypto::EllipticCurveInstance instanceof SigningNamedCurvePropertyAccess
8+
{
9+
ECDsaAlgorithmValueConsumer consumer;
10+
11+
SigningNamedCurveAlgorithmInstance() {
12+
SigningNamedCurveToSignatureCreateFlow::flow(DataFlow::exprNode(this), consumer.getInputNode())
13+
}
14+
15+
ECDsaAlgorithmValueConsumer getConsumer() { result = consumer }
16+
17+
override string getRawEllipticCurveName() { result = super.getCurveName() }
18+
19+
override Crypto::TEllipticCurveType getEllipticCurveType() {
20+
Crypto::ellipticCurveNameToKeySizeAndFamilyMapping(this.getRawEllipticCurveName(), _, result)
21+
}
22+
23+
override int getKeySize() {
24+
Crypto::ellipticCurveNameToKeySizeAndFamilyMapping(this.getRawEllipticCurveName(), result, _)
25+
}
26+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
private import csharp
2+
private import experimental.quantum.Language
3+
private import AlgorithmInstances
4+
private import Cryptography
5+
6+
class ECDsaAlgorithmValueConsumer extends Crypto::AlgorithmValueConsumer {
7+
ECDsaCreateCall call;
8+
9+
ECDsaAlgorithmValueConsumer() { this = call.getAlgorithmArg() }
10+
11+
override Crypto::ConsumerInputDataFlowNode getInputNode() { result.asExpr() = this }
12+
13+
override Crypto::AlgorithmInstance getAKnownAlgorithmSource() {
14+
exists(SigningNamedCurveAlgorithmInstance l | l.getConsumer() = this and result = l)
15+
}
16+
}
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
private import csharp
2+
3+
// This class models Create calls for the ECDsa and RSA classes in .NET.
4+
class CryptographyCreateCall extends MethodCall {
5+
CryptographyCreateCall() {
6+
this.getTarget().getName() = "Create" and
7+
this.getQualifier().getType().hasFullyQualifiedName("System.Security.Cryptography", _)
8+
}
9+
10+
Expr getAlgorithmArg() {
11+
this.hasNoArguments() and result = this
12+
or
13+
result = this.(ECDsaCreateCallWithParameters).getArgument(0)
14+
or
15+
result = this.(ECDsaCreateCallWithECCurve).getArgument(0)
16+
}
17+
}
18+
19+
class ECDsaCreateCall extends CryptographyCreateCall {
20+
ECDsaCreateCall() { this.getQualifier().getType().hasName("ECDsa") }
21+
}
22+
23+
class RSACreateCall extends CryptographyCreateCall {
24+
RSACreateCall() { this.getQualifier().getType().hasName("RSA") }
25+
}
26+
27+
class CryptographyType extends Type {
28+
CryptographyType() { this.hasFullyQualifiedName("System.Security.Cryptography", _) }
29+
}
30+
31+
class ECParameters extends CryptographyType {
32+
ECParameters() { this.hasName("ECParameters") }
33+
}
34+
35+
class RSAParameters extends CryptographyType {
36+
RSAParameters() { this.hasName("RSAParameters") }
37+
}
38+
39+
class ECCurve extends CryptographyType {
40+
ECCurve() { this.hasName("ECCurve") }
41+
}
42+
43+
// This class is used to model the `ECDsa.Create(ECParameters)` call
44+
class ECDsaCreateCallWithParameters extends ECDsaCreateCall {
45+
ECDsaCreateCallWithParameters() { this.getArgument(0).getType() instanceof ECParameters }
46+
}
47+
48+
class ECDsaCreateCallWithECCurve extends ECDsaCreateCall {
49+
ECDsaCreateCallWithECCurve() { this.getArgument(0).getType() instanceof ECCurve }
50+
}
51+
52+
class SigningNamedCurvePropertyAccess extends PropertyAccess {
53+
string curveName;
54+
55+
SigningNamedCurvePropertyAccess() {
56+
super.getType().getName() = "ECCurve" and
57+
eccurveNameMapping(super.getProperty().toString().toUpperCase(), curveName)
58+
}
59+
60+
string getCurveName() { result = curveName }
61+
}
62+
63+
/**
64+
* Private predicate mapping NIST names to SEC names and leaving all others the same.
65+
*/
66+
bindingset[nist]
67+
private predicate eccurveNameMapping(string nist, string secp) {
68+
if nist.matches("NIST%")
69+
then
70+
nist = "NISTP256" and secp = "secp256r1"
71+
or
72+
nist = "NISTP384" and secp = "secp384r1"
73+
or
74+
nist = "NISTP521" and secp = "secp521r1"
75+
else secp = nist
76+
}
77+
78+
// OPERATION INSTANCES
79+
private class ECDsaClass extends Type {
80+
ECDsaClass() { this.hasFullyQualifiedName("System.Security.Cryptography", "ECDsa") }
81+
}
82+
83+
private class RSAClass extends Type {
84+
RSAClass() { this.hasFullyQualifiedName("System.Security.Cryptography", "RSA") }
85+
}
86+
87+
// TODO
88+
// class ByteArrayTypeUnionReadOnlyByteSpan extends ArrayType {
89+
// ByteArrayTypeUnionReadOnlyByteSpan() {
90+
// this.hasFullyQualifiedName("System", "Byte[]") or
91+
// this.hasFullyQualifiedName("System", "ReadOnlySpan`1") or
92+
// }
93+
// }
94+
abstract class DotNetSigner extends MethodCall {
95+
DotNetSigner() { this.getTarget().getName().matches(["Verify%", "Sign%"]) }
96+
97+
Expr getMessageArg() {
98+
// Both Sign and Verify methods take the message as the first argument.
99+
// Some cases the message is a hash.
100+
result = this.getArgument(0)
101+
}
102+
103+
Expr getSignatureArg() {
104+
this.isVerifier() and
105+
// TODO: Should replace getAChild* with the proper two types byte[] and ReadOnlySpan<byte>
106+
(result = this.getArgument([1, 3]) and result.getType().getAChild*() instanceof ByteType)
107+
}
108+
109+
Expr getSignatureOutput() {
110+
this.isSigner() and
111+
result = this
112+
}
113+
114+
predicate isSigner() { this.getTarget().getName().matches("Sign%") }
115+
116+
predicate isVerifier() { this.getTarget().getName().matches("Verify%") }
117+
}
118+
119+
private class ECDsaSigner extends DotNetSigner {
120+
ECDsaSigner() { this.getQualifier().getType() instanceof ECDsaClass }
121+
}
122+
123+
private class RSASigner extends DotNetSigner {
124+
RSASigner() { this.getQualifier().getType() instanceof RSAClass }
125+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
private import csharp
2+
private import Cryptography
3+
private import AlgorithmValueConsumers
4+
5+
/**
6+
* Flow from a known ECDsa property access to a `ECDsa.Create(sink)` call.
7+
*/
8+
module SigningNamedCurveToSignatureCreateFlowConfig implements DataFlow::ConfigSig {
9+
predicate isSource(DataFlow::Node src) { src.asExpr() instanceof SigningNamedCurvePropertyAccess }
10+
11+
predicate isSink(DataFlow::Node sink) {
12+
exists(ECDsaAlgorithmValueConsumer consumer | sink = consumer.getInputNode())
13+
}
14+
}
15+
16+
module SigningNamedCurveToSignatureCreateFlow =
17+
DataFlow::Global<SigningNamedCurveToSignatureCreateFlowConfig>;
18+
19+
/**
20+
* Flow from a known ECDsa property access to a `ECDsa.Create(sink)` call.
21+
*/
22+
private module CreateToUseFlowConfig implements DataFlow::ConfigSig {
23+
predicate isSource(DataFlow::Node src) { src.asExpr() instanceof CryptographyCreateCall }
24+
25+
predicate isSink(DataFlow::Node sink) { sink.asExpr() instanceof DotNetSigner }
26+
27+
predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) {
28+
node2.asExpr().(DotNetSigner).getQualifier() = node1.asExpr()
29+
}
30+
}
31+
32+
module CryptographyCreateToUseFlow = DataFlow::Global<CreateToUseFlowConfig>;
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
private import csharp
2+
private import experimental.quantum.Language
3+
private import DataFlow
4+
private import FlowAnalysis
5+
private import Cryptography
6+
7+
class ECDsaORRSASigningOperationInstance extends Crypto::SignatureOperationInstance instanceof DotNetSigner
8+
{
9+
CryptographyCreateCall creator;
10+
11+
ECDsaORRSASigningOperationInstance() {
12+
this instanceof DotNetSigner and
13+
CryptographyCreateToUseFlow::flow(DataFlow::exprNode(creator), DataFlow::exprNode(this))
14+
}
15+
16+
// TODO FIXME
17+
override Crypto::AlgorithmValueConsumer getAnAlgorithmValueConsumer() {
18+
result = creator.getAlgorithmArg()
19+
}
20+
21+
override Crypto::KeyOperationSubtype getKeyOperationSubtype() {
22+
if super.isSigner()
23+
then result = Crypto::TSignMode()
24+
else
25+
if super.isVerifier()
26+
then result = Crypto::TVerifyMode()
27+
else result = Crypto::TUnknownKeyOperationMode()
28+
}
29+
30+
// TODO FIXME
31+
override Crypto::ConsumerInputDataFlowNode getKeyConsumer() {
32+
result.asExpr() = creator.getAlgorithmArg()
33+
}
34+
35+
override Crypto::ConsumerInputDataFlowNode getNonceConsumer() { none() }
36+
37+
override Crypto::ConsumerInputDataFlowNode getInputConsumer() {
38+
result.asExpr() = super.getMessageArg()
39+
}
40+
41+
override Crypto::ConsumerInputDataFlowNode getSignatureConsumer() {
42+
result.asExpr() = super.getSignatureArg()
43+
}
44+
45+
override Crypto::ArtifactOutputDataFlowNode getOutputArtifact() {
46+
result.asExpr() = super.getSignatureOutput()
47+
}
48+
}

0 commit comments

Comments
 (0)