1 //
2 // Copyright (c) 2010-2023 Antmicro
3 //
4 //  This file is licensed under the MIT License.
5 //  Full license text is available in 'licenses/MIT.txt'.
6 //
7 using Antmicro.Renode.Logging;
8 using Antmicro.Renode.Peripherals.Bus;
9 using Antmicro.Renode.Utilities;
10 using Org.BouncyCastle.Crypto.Engines;
11 using Org.BouncyCastle.Crypto.Modes;
12 using Org.BouncyCastle.Crypto.Parameters;
13 using System;
14 using System.IO;
15 using System.Linq;
16 using System.Security.Cryptography;
17 
18 namespace Antmicro.Renode.Peripherals.Miscellaneous.Crypto
19 {
20     public class MessageAuthenticationServiceProvider
21     {
MessageAuthenticationServiceProvider(InternalMemoryManager manager, IBusController bus)22         public MessageAuthenticationServiceProvider(InternalMemoryManager manager, IBusController bus)
23         {
24             this.manager = manager;
25             this.bus = bus;
26         }
27 
PerformSHA()28         public void PerformSHA()
29         {
30             // Message length is hardcoded to the same value as in the software because it is not written to the internal memories
31             manager.TryReadBytes((long)MsgAuthRegisters.HashInput, SHAMsgLen, out var hashInput);
32             manager.TryWriteBytes((long)MsgAuthRegisters.HashResult, GetHashedBytes(SHAMsgLen, hashInput));
33         }
34 
PerformSHADMA()35         public void PerformSHADMA()
36         {
37             manager.TryReadDoubleWord((long)MsgAuthRegisters.SHADataBytesToProcess, out var hashInputLength);
38             manager.TryReadDoubleWord((long)MsgAuthRegisters.SHAExternalDataLocation, out var hashInputAddr);
39             var bytes = bus.ReadBytes(hashInputAddr, (int)hashInputLength);
40 
41             manager.TryReadDoubleWord((long)MsgAuthRegisters.SHAExternalDataResultLocation, out var hashResultLocation);
42             bus.WriteBytes(GetHashedBytes((int)hashInputLength, bytes), hashResultLocation);
43         }
44 
PerformHMACSHA()45         public void PerformHMACSHA()
46         {
47             RunAlgorithm(MsgAuthRegisters.SHADMAChannelConfig, NonDMAHMACSHA, DMAHMACSHA);
48         }
49 
PerformGCMMessageAuthentication()50         public void PerformGCMMessageAuthentication()
51         {
52             RunAlgorithm(MsgAuthRegisters.DMAChannelConfig, NonDMAGCM, DMAGCM);
53         }
54 
Reset()55         public void Reset()
56         {
57             manager.ResetMemories();
58         }
59 
RunAlgorithm(MsgAuthRegisters config, Action nonDMAVersion, Action dmaVersion)60         private void RunAlgorithm(MsgAuthRegisters config, Action nonDMAVersion, Action dmaVersion)
61         {
62             manager.TryReadDoubleWord((long)config, out var dmaConfig);
63             switch((WriteType)dmaConfig)
64             {
65                 case WriteType.Direct:
66                     nonDMAVersion();
67                     break;
68                 case WriteType.DMA:
69                     dmaVersion();
70                     break;
71                 default:
72                     Logger.Log(LogLevel.Warning, "Encountered unexpected DMA configuration: 0x{0:X}", dmaConfig);
73                     break;
74             }
75         }
76 
GetHashedBytes(int count, byte[] input)77         private byte[] GetHashedBytes(int count, byte[] input)
78         {
79             using(SHA256 sha256Hash = SHA256.Create())
80             {
81                 return sha256Hash.ComputeHash(input, 0, count);
82             }
83         }
84 
NonDMAHMACSHA()85         private void NonDMAHMACSHA()
86         {
87             manager.TryReadDoubleWord((long)MsgAuthRegisters.HMACSHAKeyByteCount, out var keyLength);
88             manager.TryReadBytes((long)MsgAuthRegisters.HashMACKey, (int)keyLength, out var hashKey);
89 
90             // Message length is hardcoded to the same value as in the software because it is not written to the internal memories
91             // Additionally we add 2 padding bytes to be able to reverse bytes in doublewords
92             manager.TryReadBytes((long)MsgAuthRegisters.HashMACInput, HMACSHAMsgLen + 2, out var msgBytes);
93 
94             var myhmacsha256 = new HMACSHA256(hashKey);
95             using(var stream = new MemoryStream(msgBytes, 0, HMACSHAMsgLen))
96             {
97                 var result = myhmacsha256.ComputeHash(stream);
98                 manager.TryWriteBytes((long)MsgAuthRegisters.HashResult, result);
99             }
100         }
101 
DMAHMACSHA()102         private void DMAHMACSHA()
103         {
104             manager.TryReadDoubleWord((long)MsgAuthRegisters.HMACSHAKeyByteCount, out var keyLength);
105             manager.TryReadBytes((long)MsgAuthRegisters.HashMACKey, (int)keyLength, out var hashKey);
106 
107             manager.TryReadDoubleWord((long)MsgAuthRegisters.SHADataBytesToProcess, out var hashInputLength);
108             manager.TryReadDoubleWord((long)MsgAuthRegisters.SHAExternalDataLocation, out var hashInputAddr);
109             var inputBytes = bus.ReadBytes(hashInputAddr, (int)hashInputLength);
110 
111             var myhmacsha256 = new HMACSHA256(hashKey);
112             using(var stream = new MemoryStream(inputBytes))
113             {
114                 var result = myhmacsha256.ComputeHash(stream);
115                 manager.TryReadDoubleWord((long)MsgAuthRegisters.SHAExternalDataResultLocation, out var hashResultLocation);
116                 bus.WriteBytes(result, hashResultLocation);
117             }
118         }
119 
NonDMAGCM()120         private void NonDMAGCM()
121         {
122             manager.TryReadDoubleWord((long)MsgAuthRegisters.AuthDataByteCount, out var authBytesLength);
123             manager.TryReadBytes((long)MsgAuthRegisters.AuthData, (int)authBytesLength, out var authBytes);
124 
125             manager.TryReadDoubleWord((long)MsgAuthRegisters.InputDataByteCount, out var msgBytesLength);
126             var msgBytesAddend = (msgBytesLength % 4);
127             if(msgBytesAddend != 0)
128             {
129                 msgBytesAddend = 4 - msgBytesAddend;
130                 msgBytesLength += msgBytesAddend;
131             }
132 
133             manager.TryReadBytes((long)MsgAuthRegisters.Message, (int)msgBytesLength, out var msgBytes);
134 
135             if(msgBytesAddend != 0)
136             {
137                 msgBytes = msgBytes.Take((int)(msgBytesLength - msgBytesAddend)).ToArray();
138             }
139 
140             CalculateGCM(msgBytes, authBytes, MsgAuthRegisters.InitVector, out var ciphertext, out var tag);
141 
142             manager.TryWriteBytes((long)MsgAuthRegisters.Message, ciphertext);
143             // We clear the next 4 bytes after the ciphertext to remove any unwanted data written by
144             // previous steps of the algorithm.
145             manager.TryWriteBytes((long)MsgAuthRegisters.Message + ciphertext.Length, new byte[] { 0, 0, 0, 0 });
146             manager.TryWriteBytes((long)MsgAuthRegisters.Tag, tag);
147         }
148 
DMAGCM()149         private void DMAGCM()
150         {
151             manager.TryReadDoubleWord((long)MsgAuthRegisters.KeyWordCount, out var msgByteCount);
152             manager.TryReadDoubleWord((long)MsgAuthRegisters.PointerToExternalData, out var inputDataAddr);
153             var msgBytes = bus.ReadBytes(inputDataAddr, (int)msgByteCount);
154 
155             manager.TryReadDoubleWord((long)MsgAuthRegisters.AuthDataByteCount, out var authByteCount);
156             manager.TryReadDoubleWord((long)MsgAuthRegisters.PointerToExternalAuthData, out var authDataAddr);
157             var authBytes = bus.ReadBytes(authDataAddr, (int)authByteCount);
158 
159             CalculateGCM(msgBytes, authBytes, MsgAuthRegisters.InitVectorDMA, out var ciphertext, out var tag);
160 
161             manager.TryReadDoubleWord((long)MsgAuthRegisters.PointerToExternalResultLocation, out var resultAddr);
162             bus.WriteBytes(ciphertext, resultAddr);
163             manager.TryReadDoubleWord((long)MsgAuthRegisters.PointerToExternalMACLocation, out var macAddr);
164             bus.WriteBytes(tag, macAddr);
165         }
166 
CalculateGCM(byte[] msgBytesSwapped, byte[] authBytesSwapped, MsgAuthRegisters initVector, out byte[] ciphertext, out byte[] tag)167         private void CalculateGCM(byte[] msgBytesSwapped, byte[] authBytesSwapped, MsgAuthRegisters initVector, out byte[] ciphertext, out byte[] tag)
168         {
169             var initVectorSize = GCMInitVectorSize128;
170 
171             manager.TryReadBytes((long)MsgAuthRegisters.Key, KeyLen, out var keyBytesSwapped);
172 
173             manager.TryReadBytes((long)initVector, initVectorSize, out var ivBytesSwapped);
174             // If the user has entered an initialization vector ending with [0x0, 0x0, 0x0, 0x1] (1 being the youngest byte)
175             // it means that it is in fact a 96bit key that was padded with these special bytes, to be 128bit.
176             // Unfortunately, we have to manually trim the padding here because BouncyCastle is expecting an unpadded
177             // initialization vector.
178             // See: https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38d.pdf, p.15 for further details.
179             if((ivBytesSwapped[12] == 0) && (ivBytesSwapped[13] == 0) && (ivBytesSwapped[14] == 0) && (ivBytesSwapped[15] == 1))
180             {
181                 ivBytesSwapped = ivBytesSwapped.Take(12).ToArray();
182                 initVectorSize = GCMInitVectorSize96;
183             }
184 
185             var cipher = new GcmBlockCipher(new AesEngine());
186             var parameters = new AeadParameters(new KeyParameter(keyBytesSwapped), (initVectorSize * 8), ivBytesSwapped, authBytesSwapped);
187             cipher.Init(true, parameters);
188 
189             var encryptedBytes = new byte[msgBytesSwapped.Length + initVectorSize];
190 
191             var retLen = cipher.ProcessBytes(msgBytesSwapped, 0, msgBytesSwapped.Length, encryptedBytes, 0);
192             cipher.DoFinal(encryptedBytes, retLen);
193 
194             ciphertext = new byte[msgBytesSwapped.Length];
195             tag = new byte[initVectorSize];
196 
197             Buffer.BlockCopy(encryptedBytes, 0, ciphertext, 0, msgBytesSwapped.Length);
198             Buffer.BlockCopy(encryptedBytes, msgBytesSwapped.Length, tag, 0, initVectorSize);
199         }
200 
201         private const int GCMInitVectorSize128 = 16;
202         private const int GCMInitVectorSize96 = 12;
203         private const int HMACSHAMsgLen = 34;
204         private const int SHAMsgLen = 32;
205         private const int KeyLen = 32;
206 
207         private readonly IBusController bus;
208         private readonly InternalMemoryManager manager;
209 
210         private enum WriteType
211         {
212             Direct = 0x0,
213             DMA = 0x8
214         }
215 
216         private enum MsgAuthRegisters
217         {
218             KeyWordCount = 0x8,
219             InputDataByteCount = 0x8,
220             HMACSHAKeyByteCount = 0x18,
221             SHADMAChannelConfig = 0x2C,
222             AuthDataByteCount = 0x30,
223             SHADataBytesToProcess = 0x44,
224             SHAExternalDataLocation = 0x48,
225             SHAExternalDataResultLocation = 0x4C,
226             DMAChannelConfig = 0x58,
227             PointerToExternalData = 0x60,
228             PointerToExternalAuthData = 0x64,
229             PointerToExternalResultLocation = 0x68,
230             PointerToExternalMACLocation = 0x6C,
231             OperationAuthBlock = 0x7C,
232             TagWordCount = 0x1054,
233             HashResult = 0x8064,
234             InitVector = 0x807C,
235             InitVectorDMA = 0x8080,
236             HashMACKey = 0x80A4,
237             HashInput = 0x80A8,
238             AuthData = 0x80CC,
239             Message = 0x80DC,
240             Tag = 0x811C,
241             HashMACInput = 0x81A4,
242             Key = 0x9000
243         }
244     }
245 }
246