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 using System; 7 using System.Collections.Generic; 8 using System.Security.Cryptography; 9 using Antmicro.Renode.Core; 10 using Antmicro.Renode.Core.Structure.Registers; 11 using Antmicro.Renode.Debugging; 12 using Antmicro.Renode.Peripherals.Bus; 13 using Antmicro.Renode.Logging; 14 using Antmicro.Renode.Utilities; 15 16 namespace Antmicro.Renode.Peripherals.Miscellaneous 17 { 18 // OpenTitan HMACi HWIP as per https://docs.opentitan.org/hw/ip/hmac/doc/ (30.06.2021) 19 public class OpenTitan_HMAC: BasicDoubleWordPeripheral, IWordPeripheral, IBytePeripheral, IKnownSize 20 { OpenTitan_HMAC(IMachine machine)21 public OpenTitan_HMAC(IMachine machine) : base(machine) 22 { 23 key = new byte[SecretKeyLength * 4]; 24 digest = new byte[DigestLength * 4]; 25 26 packer = new Packer(this); 27 IRQ = new GPIO(); 28 FatalAlert = new GPIO(); 29 30 DefineRegisters(); 31 } 32 Reset()33 public override void Reset() 34 { 35 base.Reset(); 36 37 Array.Clear(key, 0, key.Length); 38 Array.Clear(digest, 0, digest.Length); 39 40 UpdateInterrupts(); 41 FatalAlert.Unset(); 42 } 43 WriteDoubleWord(long offset, uint value)44 public override void WriteDoubleWord(long offset, uint value) 45 { 46 if(IsInFifoWindowRange(offset)) 47 { 48 PushData(value, 4); 49 } 50 else 51 { 52 base.WriteDoubleWord(offset, value); 53 } 54 } 55 WriteWord(long offset, ushort value)56 public void WriteWord(long offset, ushort value) 57 { 58 if(IsInFifoWindowRange(offset)) 59 { 60 PushData(value, 2); 61 } 62 else 63 { 64 this.Log(LogLevel.Warning, "Tried to write value 0x{0:X} at offset 0x{1:X}, but word access to registers is not supported", value, offset); 65 } 66 } 67 WriteByte(long offset, byte value)68 public void WriteByte(long offset, byte value) 69 { 70 if(IsInFifoWindowRange(offset)) 71 { 72 PushData(value, 1); 73 } 74 else 75 { 76 this.Log(LogLevel.Warning, "Tried to write value 0x{0:X} at offset 0x{1:X}, but byte access to registers is not supported", value, offset); 77 } 78 } 79 ReadDoubleWord(long offset)80 public override uint ReadDoubleWord(long offset) 81 { 82 if(IsInFifoWindowRange(offset)) 83 { 84 this.Log(LogLevel.Warning, $"Returning 0"); 85 return 0; 86 } 87 return base.ReadDoubleWord(offset); 88 } 89 90 //Below methods are required to properly register writes for byte and word accesses ReadWord(long offset)91 public ushort ReadWord(long offset) 92 { 93 this.Log(LogLevel.Warning, "Tried to read value at offset 0x{0:X}, but word access to registers is not supported", offset); 94 return 0; 95 } 96 ReadByte(long offset)97 public byte ReadByte(long offset) 98 { 99 this.Log(LogLevel.Warning, "Tried to read value at offset 0x{0:X}, but byte access to registers is not supported", offset); 100 return 0; 101 } 102 103 public long Size => 0x1000; 104 105 public GPIO IRQ { get; } 106 public GPIO FatalAlert { get; private set;} 107 HashProcess()108 private void HashProcess() 109 { 110 this.Log(LogLevel.Debug, "Received a 'hash_process' command"); 111 112 var message = packer.DataToArray(endianSwap.Value); 113 byte[] hash; 114 115 if(hmacEnabled.Value) 116 { 117 using(var hmac = new HMACSHA256(key)) 118 { 119 hash = hmac.ComputeHash(message); 120 } 121 } 122 else 123 { 124 using(var sha = SHA256.Create()) 125 { 126 hash = sha.ComputeHash(message); 127 } 128 } 129 130 if(digestSwap.Value) 131 { 132 Misc.EndiannessSwapInPlace(hash, sizeof(uint)); 133 } 134 135 Array.Copy(hash, 0, digest, 0, digest.Length); 136 hmacDoneInterrupt.Value = true; 137 UpdateInterrupts(); 138 } 139 UpdateInterrupts()140 private void UpdateInterrupts() 141 { 142 var hmacDone = hmacDoneInterrupt.Value && hmacDoneInterruptEnable.Value; 143 var fifoEmpty = fifoEmptyInterrupt.Value && fifoEmptyInterruptEnable.Value; 144 var hmacError = hmacErrorInterrupt.Value && hmacErrorInterruptEnable.Value; 145 IRQ.Set(hmacDone || fifoEmpty || hmacError); 146 } 147 IsInFifoWindowRange(long offset)148 private bool IsInFifoWindowRange(long offset) 149 { 150 return offset >= (long)Registers.FifoWindow && offset < ((long)Registers.FifoWindow + 0x800); 151 } 152 PushData(uint value, int accessByteCount)153 private void PushData(uint value, int accessByteCount) 154 { 155 this.Log(LogLevel.Noisy, "Pushing {0} bytes from value 0x{1:X}", accessByteCount, value); 156 157 packer.PushData(value, accessByteCount); 158 159 // There should be blink on fifoFull bit, but we pretend that we handle hashing fast enough for software not to notice that fifo is ever full 160 // We just raise fifoEmptyInterrupt to make sure it keeps them bytes coming 161 fifoEmptyInterrupt.Value = true; 162 UpdateInterrupts(); 163 } 164 DefineRegisters()165 private void DefineRegisters() 166 { 167 Registers.InterruptStatus.Define(this) 168 .WithFlag(0, out hmacDoneInterrupt, FieldMode.Read | FieldMode.WriteOneToClear, name: "hmac_done") 169 .WithFlag(1, out fifoEmptyInterrupt, FieldMode.Read | FieldMode.WriteOneToClear, name: "fifo_empty") 170 .WithFlag(2, out hmacErrorInterrupt, FieldMode.Read | FieldMode.WriteOneToClear, name: "hmac_err") 171 .WithIgnoredBits(3, 29) 172 .WithWriteCallback((_, __) => UpdateInterrupts()); 173 174 Registers.InterruptEnable.Define(this) 175 .WithFlag(0, out hmacDoneInterruptEnable, name: "hmac_done") 176 .WithFlag(1, out fifoEmptyInterruptEnable, name: "fifo_empty") 177 .WithFlag(2, out hmacErrorInterruptEnable, name: "hmac_err") 178 .WithIgnoredBits(3, 29) 179 .WithWriteCallback((_, __) => UpdateInterrupts()); 180 181 Registers.InterruptTest.Define(this) 182 .WithFlag(0, FieldMode.Write, writeCallback: (_, value) => { if(value) hmacDoneInterrupt.Value = true; }, name: "hmac_done") 183 .WithFlag(1, FieldMode.Write, writeCallback: (_, value) => { if(value) fifoEmptyInterrupt.Value = true; }, name: "fifo_empty") 184 .WithFlag(2, FieldMode.Write, writeCallback: (_, value) => { if(value) hmacErrorInterrupt.Value = true; }, name: "hmac_err") 185 .WithIgnoredBits(3, 29) 186 .WithWriteCallback((_, __) => UpdateInterrupts()); 187 188 Registers.AlertTest.Define(this) 189 .WithFlag(0, FieldMode.Write, writeCallback: (_, val) => { if(val) FatalAlert.Blink(); }, name: "fatal_fault") 190 .WithIgnoredBits(1, 31); 191 192 Registers.ConfigurationRegister.Define(this, 0x4) 193 .WithFlag(0, out hmacEnabled, name: "hmac_en") 194 .WithFlag(1, out shaEnabled, name: "sha_en") 195 .WithFlag(2, out endianSwap, name: "endian_swap") //0 - big endian; 1 - little endian 196 .WithFlag(3, out digestSwap, name: "digest_swap") //1 - big-endian 197 .WithIgnoredBits(4, 28) 198 .WithWriteCallback((_, __) => 199 { 200 this.Log(LogLevel.Debug, "Configuration set to hmac_en: {0}, sha_en: {1}, endian_swap: {2}, digest_swap: {3}", hmacEnabled.Value, shaEnabled.Value, endianSwap.Value, digestSwap.Value); 201 }); 202 203 Registers.Command.Define(this) 204 .WithFlag(0, FieldMode.Write, writeCallback: (_, value) => { }, name: "hash_start") // intentionally do nothing 205 .WithFlag(1, FieldMode.Write, writeCallback: (_, value) => { if(value) HashProcess(); }, name: "hash_process") 206 .WithIgnoredBits(2, 30); 207 208 Registers.Status.Define(this) 209 .WithFlag(0, FieldMode.Read, name: "fifo_empty", valueProviderCallback: _ => true) // fifo is always empty 210 .WithFlag(1, FieldMode.Read, name: "fifo_full", valueProviderCallback: _ => false) // fifo is never full 211 .WithReservedBits(2, 2) 212 .WithValueField(4, 5, FieldMode.Read, name: "fifo_depth", valueProviderCallback: _ => 0) 213 .WithIgnoredBits(9, 23); 214 215 Registers.ErrorCode.Define(this) 216 .WithTag("err_code", 0, 32); 217 218 Registers.RandomizationInput.Define(this) 219 .WithTag("secret", 0, 32); 220 221 Registers.SecretKey_0.DefineMany(this, SecretKeyLength, (register, idx) => 222 { 223 register 224 .WithValueField(0, 32, FieldMode.Write, writeCallback: (_, value) => SetKeyPart(idx, (uint)value), name: "key"); 225 }); 226 227 Registers.Digest_0.DefineMany(this, DigestLength, (register, idx) => 228 { 229 register 230 .WithValueField(0, 32, FieldMode.Read, valueProviderCallback: _ => GetDigestPart(idx), name: "digest"); 231 }); 232 233 Registers.MessageLengthLowerPart.Define(this) 234 .WithValueField(0, 32, FieldMode.Read, valueProviderCallback: _ => (uint)ReceivedLengthInBits, name: "v"); 235 236 Registers.MessageLengthUpperPart.Define(this) 237 .WithValueField(0, 32, FieldMode.Read, valueProviderCallback: _ => (uint)(ReceivedLengthInBits >> 32), name: "v"); 238 } 239 SetKeyPart(int part, uint value)240 private void SetKeyPart(int part, uint value) 241 { 242 DebugHelper.Assert(part >= 0 && part < SecretKeyLength); 243 244 this.Log(LogLevel.Noisy, "Setting key_{0} to 0x{1:X2}", part, value); 245 var offset = part * 4; 246 for(int i = 3; i >= 0; i--) 247 { 248 key[i + offset] = (byte)value; 249 value = value >> 8; 250 } 251 } 252 GetDigestPart(int part)253 private uint GetDigestPart(int part) 254 { 255 DebugHelper.Assert(part >= 0 && part < DigestLength); 256 return BitHelper.ToUInt32(digest, part * 4, 4, false); 257 } 258 259 private ulong ReceivedLengthInBits => (ulong)packer.Count * 8; 260 261 private readonly byte[] digest; 262 private readonly byte[] key; 263 private readonly Packer packer; 264 265 private IFlagRegisterField hmacDoneInterrupt; 266 private IFlagRegisterField fifoEmptyInterrupt; 267 private IFlagRegisterField hmacErrorInterrupt; 268 private IFlagRegisterField hmacDoneInterruptEnable; 269 private IFlagRegisterField fifoEmptyInterruptEnable; 270 private IFlagRegisterField hmacErrorInterruptEnable; 271 private IFlagRegisterField hmacEnabled; 272 private IFlagRegisterField shaEnabled; 273 private IFlagRegisterField endianSwap; 274 private IFlagRegisterField digestSwap; 275 276 private const int SecretKeyLength = 8; 277 private const int DigestLength = 8; 278 279 private class Packer 280 { Packer(IEmulationElement parent)281 public Packer(IEmulationElement parent) 282 { 283 this.parent = parent; 284 byteQueue = new Queue<byte>(); 285 queueLock = new object(); 286 } 287 PushData(uint data, int accessByteCount)288 public void PushData(uint data, int accessByteCount) 289 { 290 lock(queueLock) 291 { 292 for(int i = 0; i < accessByteCount; i++) 293 { 294 var b = (byte)(data >> (8 * i)); 295 byteQueue.Enqueue(b); 296 parent.Log(LogLevel.Noisy, "Pushed byte 0x{0:X2}", b); 297 } 298 } 299 } 300 DataToArray(bool reverse)301 public byte[] DataToArray(bool reverse) 302 { 303 var byteList = new List<byte>(); 304 lock(queueLock) 305 { 306 while(byteQueue.Count > 0) 307 { 308 byteList.Add(byteQueue.Dequeue()); 309 } 310 } 311 312 var output = byteList.ToArray(); 313 if(reverse) 314 { 315 Array.Reverse(output); 316 } 317 318 return output; 319 } 320 321 public ulong Count => (ulong)byteQueue.Count; 322 323 private readonly IEmulationElement parent; 324 private readonly Queue<byte> byteQueue; 325 private readonly object queueLock; 326 } 327 328 private enum Registers 329 { 330 InterruptStatus = 0x0, 331 InterruptEnable = 0x4, 332 InterruptTest = 0x8, 333 AlertTest = 0xC, 334 ConfigurationRegister = 0x10, 335 Command = 0x14, 336 Status = 0x18, 337 ErrorCode = 0x1C, 338 RandomizationInput = 0x20, 339 SecretKey_0 = 0x24, 340 SecretKey_1 = 0x28, 341 SecretKey_2 = 0x2C, 342 SecretKey_3 = 0x30, 343 SecretKey_4 = 0x34, 344 SecretKey_5 = 0x38, 345 SecretKey_6 = 0x3C, 346 SecretKey_7 = 0x40, 347 Digest_0 = 0x44, 348 Digest_1 = 0x48, 349 Digest_2 = 0x4C, 350 Digest_3 = 0x50, 351 Digest_4 = 0x54, 352 Digest_5 = 0x58, 353 Digest_6 = 0x5C, 354 Digest_7 = 0x60, 355 MessageLengthLowerPart = 0x64, 356 MessageLengthUpperPart = 0x68, 357 FifoWindow = 0x800, 358 } 359 } 360 } 361