Skip to content

Commit b417436

Browse files
committed
quantum-c#: Add support for HMACs
1 parent 6763f18 commit b417436

File tree

9 files changed

+278
-4
lines changed

9 files changed

+278
-4
lines changed

csharp/ql/lib/experimental/quantum/dotnet/Cryptography.qll

Lines changed: 98 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,8 @@ private predicate hashAlgorithmToFamily(
177177
hashName = "SHA3_384" and hashFamily = Crypto::SHA3() and digestLength = 384
178178
or
179179
hashName = "SHA3_512" and hashFamily = Crypto::SHA3() and digestLength = 512
180-
// TODO: is there an idiomatic way to add a default type here?
180+
or
181+
hashName = "RIPEMD160" and hashFamily = Crypto::RIPEMD160() and digestLength = 160
181182
}
182183

183184
class HashAlgorithmNameUser extends MethodCall {
@@ -630,3 +631,99 @@ class CryptoStreamUse extends MethodCall {
630631
result = this.getArgument(0)
631632
}
632633
}
634+
635+
class MacAlgorithmType extends CryptographyType {
636+
MacAlgorithmType() { this.getName().matches(["HMAC%", "KeyedHashAlgorithm"]) }
637+
}
638+
639+
class HMACCreation extends ObjectCreation {
640+
HMACCreation() { this.getObjectType() instanceof MacAlgorithmType }
641+
642+
Expr getKeyArg() { if this.hasNoArguments() then result = this else result = this.getArgument(0) }
643+
644+
string getRawAlgorithmName() { result = this.getObjectType().getName() }
645+
}
646+
647+
class MacUse extends Crypto::AlgorithmValueConsumer instanceof MethodCall {
648+
MacUse() {
649+
this.getQualifier().getType() instanceof MacAlgorithmType and
650+
this.getTarget().hasName(["ComputeHash", "ComputeHashAsync", "HashData", "HashDataAsync"])
651+
}
652+
653+
predicate isIntermediate() { none() }
654+
655+
Expr getOutput() {
656+
not this.isIntermediate() and
657+
// some functions receive the destination as a parameter
658+
if
659+
super.getTarget().getName() = ["HashData"] and super.getNumberOfArguments() = 3
660+
or
661+
super.getTarget().getName() = ["HashDataAsync"] and super.getNumberOfArguments() = 4
662+
then result = super.getArgument(2)
663+
else result = this
664+
}
665+
666+
private Expr getDataArg() {
667+
// ComputeHash and ComputeHashAsync take the data as the first argument.
668+
if super.getTarget().getName().matches("ComputeHash%")
669+
then result = super.getArgument(0)
670+
else result = super.getArgument(1)
671+
}
672+
673+
Expr getInputArg() {
674+
result = this.getDataArg() and result.getType() instanceof ByteArrayOrReadOnlyByteSpanType
675+
}
676+
677+
Expr getStreamArg() { result = this.getDataArg() and result.getType() instanceof Stream }
678+
679+
Expr getKeyArg() {
680+
if not super.getTarget().getName().matches("ComputeHash%")
681+
then result = super.getArgument(0)
682+
else result = HMACFlow::getCreationFromUse(this, _, _).getKeyArg()
683+
}
684+
685+
override Crypto::AlgorithmInstance getAKnownAlgorithmSource() { result = super.getQualifier() }
686+
687+
override Crypto::ConsumerInputDataFlowNode getInputNode() { none() }
688+
689+
Expr getQualifier() { result = super.getQualifier() }
690+
}
691+
692+
class HMACAlgorithmInstance extends Crypto::MACAlgorithmInstance instanceof Expr {
693+
HMACAlgorithmInstance() { this = any(MacUse c).getQualifier() }
694+
695+
override Crypto::TMACType getMACType() { result instanceof Crypto::THMAC }
696+
697+
override string getRawMACAlgorithmName() { result = super.getType().getName() }
698+
}
699+
700+
class HMACAlgorithmQualifier extends Crypto::HMACAlgorithmInstance, Crypto::AlgorithmValueConsumer,
701+
HMACAlgorithmInstance, Crypto::HashAlgorithmInstance instanceof Expr
702+
{
703+
override Crypto::AlgorithmValueConsumer getHashAlgorithmValueConsumer() { result = this }
704+
705+
override Crypto::AlgorithmInstance getAKnownAlgorithmSource() { result = this }
706+
707+
override Crypto::ConsumerInputDataFlowNode getInputNode() { none() }
708+
709+
override Crypto::THashType getHashFamily() {
710+
result = getHashFamily(this.getRawHashAlgorithmName())
711+
}
712+
713+
override string getRawHashAlgorithmName() {
714+
if super.getType().hasName("KeyedHashAlgorithm")
715+
then result = this.getOriginalRawHashAlgorithmName()
716+
else result = super.getType().getName().replaceAll("HMAC", "")
717+
}
718+
719+
override int getFixedDigestLength() {
720+
hashAlgorithmToFamily(this.getRawHashAlgorithmName(), _, result)
721+
}
722+
723+
private string getOriginalRawHashAlgorithmName() {
724+
exists(MacUse use |
725+
use.getQualifier() = this and
726+
result = HMACFlow::getCreationFromUse(use, _, _).getRawAlgorithmName().replaceAll("HMAC", "")
727+
)
728+
}
729+
}

csharp/ql/lib/experimental/quantum/dotnet/FlowAnalysis.qll

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,8 @@ module CryptoStreamFlow = CreationToUseFlow<CryptoStreamCreation, CryptoStreamUs
7070

7171
module AeadFlow = CreationToUseFlow<AeadCreation, AeadUse>;
7272

73+
module HMACFlow = CreationToUseFlow<HMACCreation, MacUse>;
74+
7375
module SymmetricAlgorithmFlow =
7476
CreationToUseFlow<SymmetricAlgorithmCreation, SymmetricAlgorithmUse>;
7577

csharp/ql/lib/experimental/quantum/dotnet/OperationInstances.qll

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,9 @@ class SymmetricAlgorithmOperationInstance extends Crypto::KeyOperationInstance i
9999
*/
100100
class CryptoStreamOperationInstance extends Crypto::KeyOperationInstance instanceof CryptoStreamCreation
101101
{
102-
override Crypto::AlgorithmValueConsumer getAnAlgorithmValueConsumer() { result = this.getCryptoTransform() }
102+
override Crypto::AlgorithmValueConsumer getAnAlgorithmValueConsumer() {
103+
result = this.getCryptoTransform()
104+
}
103105

104106
override Crypto::KeyOperationSubtype getKeyOperationSubtype() {
105107
if this.getCryptoTransform().isEncryptor()
@@ -117,7 +119,8 @@ class CryptoStreamOperationInstance extends Crypto::KeyOperationInstance instanc
117119
if exists(this.getCryptoTransform().getKeyArg())
118120
then result.asExpr() = this.getCryptoTransform().getKeyArg()
119121
else (
120-
result.asExpr() = SymmetricAlgorithmFlow::getIntermediateUseFromUse(this.getCryptoTransform(), _, _) and
122+
result.asExpr() =
123+
SymmetricAlgorithmFlow::getIntermediateUseFromUse(this.getCryptoTransform(), _, _) and
121124
result.asExpr().(SymmetricAlgorithmUse).isKeyConsumer()
122125
)
123126
}
@@ -129,7 +132,8 @@ class CryptoStreamOperationInstance extends Crypto::KeyOperationInstance instanc
129132
if exists(this.getCryptoTransform().getIvArg())
130133
then result.asExpr() = this.getCryptoTransform().getIvArg()
131134
else (
132-
result.asExpr() = SymmetricAlgorithmFlow::getIntermediateUseFromUse(this.getCryptoTransform(), _, _) and
135+
result.asExpr() =
136+
SymmetricAlgorithmFlow::getIntermediateUseFromUse(this.getCryptoTransform(), _, _) and
133137
result.asExpr().(SymmetricAlgorithmUse).isIvConsumer()
134138
)
135139
}
@@ -205,3 +209,20 @@ class AeadOperationInstance extends Crypto::KeyOperationInstance instanceof Aead
205209
result.asExpr() = super.getOutputArg()
206210
}
207211
}
212+
213+
class HMACOperationInstance extends Crypto::MACOperationInstance instanceof MacUse {
214+
HMACOperationInstance() { not super.isIntermediate() }
215+
216+
override Crypto::AlgorithmValueConsumer getAnAlgorithmValueConsumer() {
217+
result = super.getQualifier()
218+
}
219+
220+
override Crypto::ConsumerInputDataFlowNode getMessageConsumer() {
221+
result.asExpr() = super.getInputArg() or
222+
result.asExpr() = StreamFlow::getEarlierUse(super.getStreamArg(), _, _).getInputArg()
223+
}
224+
225+
override Crypto::ConsumerInputDataFlowNode getKeyConsumer() {
226+
result.asExpr() = super.getKeyArg()
227+
}
228+
}
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
using System;
2+
using System.IO;
3+
using System.Security.Cryptography;
4+
using System.Text;
5+
6+
namespace QuantumExamples.Cryptography
7+
{
8+
public class HMACExample
9+
{
10+
public static void RunExample()
11+
{
12+
const string originalMessage = "This is a message to authenticate!";
13+
14+
// Demonstrate various MAC approaches
15+
DemonstrateHMACMethods(originalMessage);
16+
DemonstrateKeyedHashAlgorithm(originalMessage);
17+
DemonstrateOneShotMAC(originalMessage);
18+
DemonstrateStreamBasedMAC(originalMessage);
19+
}
20+
21+
private static void DemonstrateHMACMethods(string message)
22+
{
23+
Console.WriteLine("=== HMAC Methods ===");
24+
byte[] messageBytes = Encoding.UTF8.GetBytes(message);
25+
byte[] key = GenerateKey(32); // 256-bit key
26+
27+
Console.WriteLine($"Original message: {message}");
28+
Console.WriteLine($"Key: {Convert.ToBase64String(key)}");
29+
30+
// HMAC-SHA256 using HMACSHA256 class
31+
using (HMACSHA256 hmacSha256 = new HMACSHA256(key))
32+
{
33+
byte[] hash = hmacSha256.ComputeHash(messageBytes);
34+
Console.WriteLine($"HMAC-SHA256: {Convert.ToBase64String(hash)}");
35+
}
36+
37+
// HMAC-SHA1 using HMACSHA1 class
38+
using (HMACSHA1 hmacSha1 = new HMACSHA1(key))
39+
{
40+
byte[] hash = hmacSha1.ComputeHash(messageBytes);
41+
Console.WriteLine($"HMAC-SHA1: {Convert.ToBase64String(hash)}");
42+
}
43+
44+
// HMAC-SHA384 using HMACSHA384 class
45+
using (HMACSHA384 hmacSha384 = new HMACSHA384(key))
46+
{
47+
byte[] hash = hmacSha384.ComputeHash(messageBytes);
48+
Console.WriteLine($"HMAC-SHA384: {Convert.ToBase64String(hash)}");
49+
}
50+
51+
// HMAC-SHA512 using HMACSHA512 class
52+
using (HMACSHA512 hmacSha512 = new HMACSHA512(key))
53+
{
54+
byte[] hash = hmacSha512.ComputeHash(messageBytes);
55+
Console.WriteLine($"HMAC-SHA512: {Convert.ToBase64String(hash)}");
56+
}
57+
}
58+
59+
private static void DemonstrateKeyedHashAlgorithm(string message)
60+
{
61+
Console.WriteLine("\n=== KeyedHashAlgorithm Base Class ===");
62+
byte[] messageBytes = Encoding.UTF8.GetBytes(message);
63+
byte[] key = GenerateKey(32);
64+
65+
// Using KeyedHashAlgorithm base class reference
66+
using (KeyedHashAlgorithm keyedHash = new HMACSHA256(key))
67+
{
68+
byte[] hash = keyedHash.ComputeHash(messageBytes);
69+
Console.WriteLine($"KeyedHashAlgorithm (HMAC-SHA256): {Convert.ToBase64String(hash)}");
70+
Console.WriteLine($"Algorithm name: {keyedHash.GetType().Name}");
71+
Console.WriteLine($"Hash size: {keyedHash.HashSize} bits");
72+
}
73+
}
74+
75+
private static void DemonstrateOneShotMAC(string message)
76+
{
77+
Console.WriteLine("\n=== One-Shot MAC Methods ===");
78+
byte[] messageBytes = Encoding.UTF8.GetBytes(message);
79+
byte[] key = GenerateKey(32);
80+
81+
byte[] hmacSha256OneShot = HMACSHA256.HashData(key, messageBytes);
82+
Console.WriteLine($"One-shot HMAC-SHA256: {Convert.ToBase64String(hmacSha256OneShot)}");
83+
84+
byte[] hmacSha1OneShot = HMACSHA1.HashData(key, messageBytes);
85+
Console.WriteLine($"One-shot HMAC-SHA1: {Convert.ToBase64String(hmacSha1OneShot)}");
86+
87+
byte[] hmacSha384OneShot = HMACSHA384.HashData(key, messageBytes);
88+
Console.WriteLine($"One-shot HMAC-SHA384: {Convert.ToBase64String(hmacSha384OneShot)}");
89+
90+
byte[] hmacSha512OneShot = HMACSHA512.HashData(key, messageBytes);
91+
Console.WriteLine($"One-shot HMAC-SHA512: {Convert.ToBase64String(hmacSha512OneShot)}");
92+
}
93+
94+
private static void DemonstrateStreamBasedMAC(string message)
95+
{
96+
Console.WriteLine("\n=== Stream-Based MAC ===");
97+
byte[] key = GenerateKey(32);
98+
99+
using (HMACSHA256 hmac = new HMACSHA256(key))
100+
using (MemoryStream stream = new MemoryStream())
101+
{
102+
byte[] messageBytes = Encoding.UTF8.GetBytes(message);
103+
stream.Write(messageBytes, 0, messageBytes.Length);
104+
stream.Position = 0;
105+
106+
byte[] hash = hmac.ComputeHash(stream);
107+
Console.WriteLine($"Stream-based HMAC-SHA256: {Convert.ToBase64String(hash)}");
108+
}
109+
}
110+
111+
private static byte[] GenerateKey(int sizeInBytes)
112+
{
113+
byte[] key = new byte[sizeInBytes];
114+
using (RandomNumberGenerator rng = RandomNumberGenerator.Create())
115+
{
116+
rng.GetBytes(key);
117+
}
118+
return key;
119+
}
120+
}
121+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
| HMACExample.cs:33:31:33:66 | MACOperation | HMACSHA256 | HMAC |
2+
| HMACExample.cs:40:31:40:64 | MACOperation | HMACSHA1 | HMAC |
3+
| HMACExample.cs:47:31:47:66 | MACOperation | HMACSHA384 | HMAC |
4+
| HMACExample.cs:54:31:54:66 | MACOperation | HMACSHA512 | HMAC |
5+
| HMACExample.cs:68:31:68:65 | MACOperation | KeyedHashAlgorithm | HMAC |
6+
| HMACExample.cs:81:40:81:77 | MACOperation | HMACSHA256 | HMAC |
7+
| HMACExample.cs:84:38:84:73 | MACOperation | HMACSHA1 | HMAC |
8+
| HMACExample.cs:87:40:87:77 | MACOperation | HMACSHA384 | HMAC |
9+
| HMACExample.cs:90:40:90:77 | MACOperation | HMACSHA512 | HMAC |
10+
| HMACExample.cs:106:31:106:54 | MACOperation | HMACSHA256 | HMAC |
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import csharp
2+
import experimental.quantum.Language
3+
4+
from Crypto::MACOperationNode n, Crypto::AlgorithmNode algo
5+
where n.getAKnownAlgorithm() = algo
6+
select n, algo.getRawAlgorithmName(), algo.getAlgorithmName()
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
| HMACExample.cs:33:31:33:66 | MACOperation | HMACExample.cs:31:59:31:61 | Key | HMACExample.cs:33:54:33:65 | Message |
2+
| HMACExample.cs:40:31:40:64 | MACOperation | HMACExample.cs:38:53:38:55 | Key | HMACExample.cs:40:52:40:63 | Message |
3+
| HMACExample.cs:47:31:47:66 | MACOperation | HMACExample.cs:45:59:45:61 | Key | HMACExample.cs:47:54:47:65 | Message |
4+
| HMACExample.cs:54:31:54:66 | MACOperation | HMACExample.cs:52:59:52:61 | Key | HMACExample.cs:54:54:54:65 | Message |
5+
| HMACExample.cs:68:31:68:65 | MACOperation | HMACExample.cs:66:66:66:68 | Key | HMACExample.cs:68:53:68:64 | Message |
6+
| HMACExample.cs:81:40:81:77 | MACOperation | HMACExample.cs:81:60:81:62 | Key | HMACExample.cs:81:65:81:76 | Message |
7+
| HMACExample.cs:84:38:84:73 | MACOperation | HMACExample.cs:84:56:84:58 | Key | HMACExample.cs:84:61:84:72 | Message |
8+
| HMACExample.cs:87:40:87:77 | MACOperation | HMACExample.cs:87:60:87:62 | Key | HMACExample.cs:87:65:87:76 | Message |
9+
| HMACExample.cs:90:40:90:77 | MACOperation | HMACExample.cs:90:60:90:62 | Key | HMACExample.cs:90:65:90:76 | Message |
10+
| HMACExample.cs:106:31:106:54 | MACOperation | HMACExample.cs:99:53:99:55 | Key | HMACExample.cs:103:30:103:41 | Message |
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import csharp
2+
import experimental.quantum.Language
3+
4+
from Crypto::MACOperationNode n
5+
select n, n.getAKey(), n.getAMessage()
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
semmle-extractor-options: /nostdlib /noconfig
2+
semmle-extractor-options: --load-sources-from-project:${testdir}/../../../../../resources/stubs/_frameworks/Microsoft.NETCore.App/Microsoft.NETCore.App.csproj

0 commit comments

Comments
 (0)