1 // 2 // Copyright (c) 2010-2024 Antmicro 3 // 4 // This file is licensed under the MIT License. 5 // Full license text is available in 'licenses/MIT.txt'. 6 // 7 using System; 8 using System.Collections.Generic; 9 using System.Linq; 10 using Antmicro.Renode.Core; 11 using Antmicro.Renode.Core.Structure.Registers; 12 using Antmicro.Renode.Logging; 13 using Antmicro.Renode.Utilities; 14 using Org.BouncyCastle.Crypto.Engines; 15 using Org.BouncyCastle.Crypto.Modes; 16 using Org.BouncyCastle.Crypto.Parameters; 17 18 namespace Antmicro.Renode.Peripherals.Miscellaneous.Crypto 19 { 20 // NOTE: DMA interface is not implemented, supports only AES-GCM mode 21 public class STM32H7_CRYPTO : BasicDoubleWordPeripheral, IKnownSize 22 { STM32H7_CRYPTO(IMachine machine)23 public STM32H7_CRYPTO(IMachine machine) : base(machine) 24 { 25 DefineRegisters(); 26 } 27 Reset()28 public override void Reset() 29 { 30 inputFIFO.Clear(); 31 outputFIFO.Clear(); 32 algorithmState = null; 33 // `currentMode` doesn't have to be reset 34 35 base.Reset(); 36 // Needs to be called after the RegisterCollection is reset 37 UpdateInterrupt(); 38 } 39 40 public long Size => 0x400; 41 42 public GPIO IRQ { get; } = new GPIO(); 43 DefineRegisters()44 private void DefineRegisters() 45 { 46 Registers.Control.Define(this) 47 .WithReservedBits(0, 2) 48 .WithFlag(2, out algorithmDirection, name: "Algorithm direction") 49 .WithValueField(3, 3, out algorithmModeLow, name: "Algorithm mode [0:2]") 50 .WithEnumField(6, 2, out dataType, name: "Data type selection") 51 .WithEnumField(8, 2, out keySize, name: "Key size (AES mode only)") 52 .WithReservedBits(10, 4) 53 .WithFlag(14, FieldMode.Write, 54 writeCallback: (_, value) => 55 { 56 if(!value) 57 { 58 return; 59 } 60 61 lock(executeLock) 62 { 63 inputFIFO.Clear(); 64 outputFIFO.Clear(); 65 } 66 }, 67 name: "FIFO flush") 68 .WithFlag(15, out enabled, 69 writeCallback: (_, __) => 70 { 71 lock(executeLock) 72 { 73 EnableOrDisable(); 74 // If there is any pending data in the FIFO, try sending it now 75 TryFeedPhase(); 76 UpdateInterrupt(); 77 } 78 }, 79 name: "CRYPTO Enable") 80 .WithEnumField(16, 2, out phaseGCMOrCCM, name: "GCM or CCM Phase") 81 .WithReservedBits(18, 1) 82 .WithValueField(19, 1, out algorithmModeHigh, name: "Algorithm mode [3]") 83 .WithReservedBits(20, 12); 84 85 Registers.Status.Define(this) 86 // The queues don't have fixed lengths for us, but they still report limits, as provided in the docs 87 .WithFlag(0, FieldMode.Read, valueProviderCallback: _ => inputFIFO.Count == 0, name: "Input FIFO empty (IFEM)") 88 .WithFlag(1, FieldMode.Read, valueProviderCallback: _ => inputFIFO.Count < MaximumFifoDepth, name: "Input FIFO not full (IFNF)") 89 .WithFlag(2, FieldMode.Read, valueProviderCallback: _ => outputFIFO.Count > 0, name: "Output FIFO not empty (OFNE)") 90 .WithFlag(3, FieldMode.Read, valueProviderCallback: _ => outputFIFO.Count >= MaximumFifoDepth, name: "Output FIFO full (OFFU)") 91 .WithFlag(4, FieldMode.Read, valueProviderCallback: _ => false, name: "CRYPTO is Busy") // Always false - operations are instant for us 92 .WithReservedBits(5, 27); 93 94 Registers.DataInput.Define(this) 95 .WithValueField(0, 32, 96 writeCallback: (_, value) => 97 { 98 lock(executeLock) 99 { 100 // Casts to `uint` are safe, since the field is exactly 32 bits wide 101 inputFIFO.Enqueue(DoByteSwap((uint)value)); 102 TryFeedPhase(); 103 UpdateInterrupt(); 104 } 105 }, 106 valueProviderCallback: _ => 107 { 108 lock(executeLock) 109 { 110 if(enabled.Value) 111 { 112 this.Log(LogLevel.Warning, "DataInput should not be read from, while CRYPTO is enabled"); 113 } 114 115 if(!inputFIFO.TryDequeue(out var result)) 116 { 117 this.Log(LogLevel.Warning, "Input FIFO is empty, returning 0"); 118 return 0; 119 } 120 UpdateInterrupt(); 121 return result; 122 } 123 }, 124 name: "Data input" 125 ); 126 127 Registers.DataOutput.Define(this) 128 .WithValueField(0, 32, FieldMode.Read, 129 valueProviderCallback: _ => 130 { 131 lock(executeLock) 132 { 133 if(!outputFIFO.TryDequeue(out var result)) 134 { 135 this.Log(LogLevel.Warning, "Output FIFO is empty, returning 0"); 136 return 0; 137 } 138 UpdateInterrupt(); 139 return DoByteSwap(result); 140 } 141 }, 142 name: "Data output" 143 ); 144 145 Registers.InterruptMaskSetClear.Define(this) 146 .WithFlag(0, out inputFifoIrqMask, name: "Input FIFO service interrupt mask (INIM)") 147 .WithFlag(1, out outputFifoIrqMask, name: "Output FIFO service interrupt mask (OUTIM)") 148 .WithReservedBits(2, 30) 149 .WithWriteCallback((_, __) => UpdateInterrupt()); 150 151 Registers.RawInterruptStatus.Define(this) 152 .WithFlag(0, out inputFifoIrqRaw, FieldMode.Read, name: "Input FIFO service raw interrupt status (INRIS)") 153 .WithFlag(1, out outputFifoIrqRaw, FieldMode.Read, name: "Output FIFO service raw interrupt status (OUTRIS)") 154 .WithReservedBits(2, 30); 155 156 Registers.MaskedInterruptStatus.Define(this) 157 .WithFlag(0, FieldMode.Read, valueProviderCallback: _ => inputFifoIrqRaw.Value && inputFifoIrqMask.Value, name: "Input FIFO service masked interrupt status (INMIS)") 158 .WithFlag(1, FieldMode.Read, valueProviderCallback: _ => outputFifoIrqRaw.Value && outputFifoIrqMask.Value, name: "Output FIFO service masked interrupt status (OUTMIS)") 159 .WithReservedBits(2, 30); 160 161 Registers.Key0L.DefineMany(this, 8, (register, registerIndex) => 162 register.WithValueField(0, 32, out keys[registerIndex], name: $"Key{registerIndex / 2}{(registerIndex % 2 > 0 ? "R" : "L")}") 163 ); 164 165 Registers.InitializationVector0L.DefineMany(this, 4, (register, registerIndex) => 166 register.WithValueField(0, 32, out initialVectors[registerIndex], name: $"IV{registerIndex / 2}{(registerIndex % 2 > 0 ? "R" : "L")}") 167 ); 168 } 169 TryFeedPhase()170 private void TryFeedPhase() 171 { 172 if(algorithmState != null) 173 { 174 while(inputFIFO.TryDequeue(out var result)) 175 { 176 algorithmState.FeedThePhase(result); 177 } 178 } 179 } 180 DoByteSwap(uint value)181 private uint DoByteSwap(uint value) 182 { 183 switch(dataType.Value) 184 { 185 case DataType.Bit32: 186 // No byte swapping 187 return value; 188 case DataType.Bit16: 189 return BitHelper.ReverseWords(value); 190 case DataType.Bit8: 191 return BitHelper.ReverseBytes(value); 192 case DataType.Bit1: 193 return BitHelper.ReverseBits(value); 194 default: 195 throw new InvalidOperationException($"Invalid byte swap option selected: {dataType.Value}"); 196 } 197 } 198 UpdateInterrupt()199 private void UpdateInterrupt() 200 { 201 outputFifoIrqRaw.Value = outputFIFO.Any(); 202 inputFifoIrqRaw.Value = inputFIFO.Count < MaximumFifoDepth; 203 204 bool status = (outputFifoIrqRaw.Value && outputFifoIrqMask.Value) 205 || (inputFifoIrqRaw.Value && inputFifoIrqMask.Value); 206 this.Log(LogLevel.Noisy, "IRQ set to {0}", status); 207 IRQ.Set(status); 208 } 209 EnableOrDisable()210 private void EnableOrDisable() 211 { 212 if(!enabled.Value) 213 { 214 // If we are not enabling the CRYPTO, there is no need to update the status. 215 // In fact, the current status (e.g. execution phase) needs to be preserved, when the peripheral is re-enabled again. 216 // So just exit immediately on a disabled peripheral. 217 return; 218 } 219 220 var algorithmMode = (AlgorithmMode)((algorithmModeHigh.Value << 3) | algorithmModeLow.Value); 221 // Switch algorithm, but only if not executing workaround 222 if((algorithmMode != currentMode || algorithmState == null) && !DetectGCMWorkaround(algorithmMode)) 223 { 224 if(algorithmMode != AlgorithmMode.AES_GCM) 225 { 226 this.Log(LogLevel.Error, "This model implements only: {0} mode but tried to configure it to {1}. Ignoring the operation", nameof(AlgorithmMode.AES_GCM), algorithmMode); 227 return; 228 } 229 algorithmState = new RSA_GCM_State(this); 230 currentMode = algorithmMode; 231 } 232 233 try 234 { 235 this.Log(LogLevel.Debug, "Switching to GCM phase {0}", phaseGCMOrCCM.Value); 236 switch(phaseGCMOrCCM.Value) 237 { 238 case GCMOrCCMPhase.Initialization: 239 algorithmState.InitializeInitializationPhase( 240 keys.Select(ks => (uint)ks.Value).Skip(keySizeToAesSkip[keySize.Value]).Reverse().SelectMany(e => BitConverter.GetBytes(e)).Reverse().ToArray(), 241 initialVectors.Select(ks => (uint)ks.Value).Reverse().SelectMany(e => BitConverter.GetBytes(e)).Reverse().ToArray() 242 ); 243 // According to the docs: 244 // "This bit is automatically cleared by hardware when the key preparation process ends (ALGOMODE = 0111) or after GCM/GMAC or CCM Initialization phase." 245 enabled.Value = false; 246 return; 247 case GCMOrCCMPhase.Header: 248 algorithmState.InitializeHeaderPhase(); 249 return; 250 case GCMOrCCMPhase.Payload: 251 algorithmState.InitializePayloadPhase(); 252 return; 253 case GCMOrCCMPhase.Final: 254 algorithmState.InitializeFinalPhase(); 255 return; 256 default: 257 throw new InvalidOperationException($"Invalid GCM phase: {phaseGCMOrCCM.Value}"); 258 } 259 } 260 catch(Exception e) 261 { 262 this.Log(LogLevel.Error, "Cryptography backend failed with exception: {0}", e); 263 enabled.Value = false; 264 algorithmState = null; 265 } 266 } 267 DetectGCMWorkaround(AlgorithmMode newMode)268 private bool DetectGCMWorkaround(AlgorithmMode newMode) 269 { 270 // This is a workaround for calculating correct MAC for GCM, if the last block size if less than 128 bit 271 // described in 35.4.8 (CRYP stealing and data padding) 272 if(!(algorithmState is RSA_GCM_State s) || newMode != AlgorithmMode.AES_CTR || phaseGCMOrCCM.Value != GCMOrCCMPhase.Payload) 273 { 274 return false; 275 } 276 if(s.ExecutingWorkaround) 277 { 278 return true; 279 } 280 this.Log(LogLevel.Debug, "Executing GCM workaround"); 281 // Workaround detected - don't switch to newMode, but we need to handle the bytes in a specific way. 282 // BouncyCastle can handle partial (< 128 bit) last block, but in this case, it will emit both the ciphertext and tag, as output from `DoFinal` 283 // the ciphertext from `ProcessBytes` will be invalid and needs to be discarded. 284 // Since the model should return valid data block-for-block we have to run the cipher two times: 285 // first to obtain the ciphertext, and second to obtain the MAC. 286 // This could be optimized, by extracting MAC calculation outside of the lib, if performance becomes critical. 287 s.ExecuteWorkaround(); 288 return true; 289 } 290 291 private readonly Queue<uint> inputFIFO = new Queue<uint>(); 292 private readonly Queue<uint> outputFIFO = new Queue<uint>(); 293 294 private IFlagRegisterField outputFifoIrqMask; 295 private IFlagRegisterField inputFifoIrqMask; 296 private IFlagRegisterField outputFifoIrqRaw; 297 private IFlagRegisterField inputFifoIrqRaw; 298 299 private IFlagRegisterField algorithmDirection; 300 private IFlagRegisterField enabled; 301 private IValueRegisterField algorithmModeLow; 302 private IValueRegisterField algorithmModeHigh; 303 private IEnumRegisterField<DataType> dataType; 304 private IEnumRegisterField<KeySize> keySize; 305 private IEnumRegisterField<GCMOrCCMPhase> phaseGCMOrCCM; 306 307 // These are expected to be stored in big-endian format 308 private readonly IValueRegisterField[] keys = new IValueRegisterField[8]; 309 private readonly IValueRegisterField[] initialVectors = new IValueRegisterField[4]; 310 311 private FourPhaseState algorithmState; 312 private AlgorithmMode currentMode; 313 private readonly object executeLock = new object(); 314 315 private const int MaximumFifoDepth = 8; 316 317 private readonly Dictionary<KeySize, int> keySizeToAesSkip = new Dictionary<KeySize, int>() 318 { 319 {KeySize.Bit256, 0}, 320 {KeySize.Bit192, 2}, 321 {KeySize.Bit128, 4}, 322 }; 323 324 private abstract class FourPhaseState 325 { InitializeInitializationPhase(byte[] key, byte[] iv)326 abstract public void InitializeInitializationPhase(byte[] key, byte[] iv); InitializeHeaderPhase()327 abstract public void InitializeHeaderPhase(); InitializePayloadPhase()328 abstract public void InitializePayloadPhase(); InitializeFinalPhase()329 abstract public void InitializeFinalPhase(); 330 331 // Feed data from input FIFO FeedThePhase(uint value)332 abstract public void FeedThePhase(uint value); 333 334 protected STM32H7_CRYPTO parent; 335 } 336 337 private class RSA_GCM_State : FourPhaseState 338 { RSA_GCM_State(STM32H7_CRYPTO parent)339 public RSA_GCM_State(STM32H7_CRYPTO parent) 340 { 341 this.parent = parent; 342 } 343 344 public bool ExecutingWorkaround { get; private set; } 345 FeedThePhase(uint value)346 public override void FeedThePhase(uint value) 347 { 348 var currentPhase = parent.phaseGCMOrCCM.Value; 349 if(!CheckIfInitialized()) 350 { 351 return; 352 } 353 byte[] bytes = BitConverter.GetBytes(value).Reverse().ToArray(); 354 355 switch(currentPhase) 356 { 357 case GCMOrCCMPhase.Header: 358 ProcessHeader(bytes); 359 break; 360 case GCMOrCCMPhase.Payload: 361 ProcessPayload(bytes); 362 break; 363 case GCMOrCCMPhase.Final: 364 ProcessFinal(value); 365 break; 366 } 367 } 368 InitializeInitializationPhase(byte[] key, byte[] iv)369 public override void InitializeInitializationPhase(byte[] key, byte[] iv) 370 { 371 parent.Log(LogLevel.Debug, "Initializing {0} with key: {1}, iv: {2}", nameof(RSA_GCM_State), Misc.PrettyPrintCollectionHex(key), Misc.PrettyPrintCollectionHex(iv)); 372 373 gcm = new GcmBlockCipher(new AesEngine()); 374 payload = new List<byte>(); 375 aad = new List<byte>(); 376 final = new List<uint>(); 377 ExecutingWorkaround = false; 378 payloadCounter = 0; 379 380 keyParameters = new KeyParameter(key); 381 this.iv = iv; 382 isInitialized = true; 383 } 384 InitializeHeaderPhase()385 public override void InitializeHeaderPhase() 386 { 387 if(!CheckIfInitialized()) 388 { 389 return; 390 } 391 if(BitHelper.ToUInt32(iv, 12, 4, false) != 0x2) 392 { 393 // The docs suggest to put 0x2 here. This is likely a counter block, but the crypto backend doesn't work well when supplying custom counter value 394 // So this is ignored with the assert, since it should be unlikely, that a different value will be used 395 parent.Log(LogLevel.Error, "Last block of IV is not equal to 0x2. This will be ignored, and calculation might be unreliable"); 396 } 397 iv = iv.Take(12).ToArray(); 398 // Associated data will be fed by the input FIFO later 399 finalParameters = new AeadParameters(keyParameters, MacSizeInBytes * 8, iv); 400 // It's a symmetric cipher, it's stuck in encryption mode 401 // for BouncyCastle decryption will try to compare MAC and throw errors, this is not what we want here 402 gcm.Init(true, finalParameters); 403 } 404 InitializePayloadPhase()405 public override void InitializePayloadPhase() 406 { 407 // Intentionally blank - the payload will be fed through input FIFO 408 } 409 InitializeFinalPhase()410 public override void InitializeFinalPhase() 411 { 412 // The model is expecting to get the block describing the length of the header and payload 413 // And only afterwards will it return the computed MAC 414 // So this method is intentionally blank - will be fed through input FIFO 415 } 416 ExecuteWorkaround()417 public void ExecuteWorkaround() 418 { 419 ExecutingWorkaround = true; 420 } 421 ProcessHeader(byte[] bytes)422 private void ProcessHeader(byte[] bytes) 423 { 424 aad.AddRange(bytes); 425 gcm.ProcessAadBytes(bytes, 0, bytes.Length); 426 } 427 ProcessPayload(byte[] bytes)428 private void ProcessPayload(byte[] bytes) 429 { 430 byte[] output = new byte[BlockSizeInBytes]; 431 var length = bytes.Length; 432 433 if(IsEncryption) 434 { 435 payload.AddRange(bytes); 436 } 437 gcm.ProcessBytes(bytes, 0, length, output, 0); 438 if((++payloadCounter % (BlockSizeInBytes / sizeof(uint))) != 0) 439 { 440 // Block size is 128 bits in AES-GCM, so we need to first process 4 uints worth of data 441 // before returning any data from the FIFO 442 return; 443 } 444 445 if(!IsEncryption) 446 { 447 payload.AddRange(output); 448 } 449 parent.Log(LogLevel.Debug, "Got cipher block: {0}", Misc.PrettyPrintCollectionHex(output)); 450 parent.outputFIFO.EnqueueRange(BytesToUIntAndSwapEndianness(output)); 451 } 452 ProcessFinal(uint value)453 private void ProcessFinal(uint value) 454 { 455 final.Add(value); 456 457 if(ExecutingWorkaround) 458 { 459 if(final.Count != 4) 460 { 461 // First 4 uints supplied to Final, when executing workaround, have to be discarded by us 462 // since they don't contain the header and payload length 463 // note, that they most likely have some meaning for the silicon, so you shouldn't just write bogus here on real HW 464 // but not for us, because of how our crypto backend operates 465 return; 466 } 467 final.Clear(); 468 // Return some bogus data - the driver has to discard it, according to the docs 469 parent.outputFIFO.EnqueueRange(new uint[] { 0xDEADBEEF, 0xBAADBEEF, 0xFEEDC0DE, 0xDEADC0DE }); 470 ExecutingWorkaround = false; 471 return; 472 } 473 474 if(final.Count != 4) 475 { 476 // We don't have the lengths provided just yet 477 return; 478 } 479 // Length is provided in bits, but BouncyCastle needs bytes 480 uint headerLen = final[1] / 8; 481 uint payloadLen = final[3] / 8; 482 final.Clear(); 483 parent.Log(LogLevel.Debug, "Calculating MAC for given final parameters: headerLen={0}, payloadLen={1}", headerLen, payloadLen); 484 485 var gcmTag = new GcmBlockCipher(new AesEngine()); 486 gcmTag.Init(true, finalParameters); 487 gcmTag.ProcessAadBytes(aad.ToArray(), 0, (int)headerLen); 488 489 // This is discarded by the model, but the crypto backend needs the space to performs calculations 490 var output = new byte[payload.Count]; 491 // This will contain both the MAC and the last cipherblock 492 var mac = new byte[BlockSizeInBytes + MacSizeInBytes]; 493 494 // Payload is either ciphertext or plaintext, depending on the selected mode 495 gcmTag.ProcessBytes(payload.ToArray(), 0, (int)payloadLen, output, 0); 496 gcmTag.DoFinal(mac, 0); 497 parent.outputFIFO.EnqueueRange(BytesToUIntAndSwapEndianness(gcmTag.GetMac())); 498 } 499 BytesToUIntAndSwapEndianness(byte[] bytes)500 private IEnumerable<uint> BytesToUIntAndSwapEndianness(byte[] bytes) 501 { 502 if(bytes.Length % 4 != 0) 503 { 504 // This should never happen, since AES blocks are 128 bits, but let's be sure 505 throw new InvalidOperationException($"{nameof(RSA_GCM_State)} cipher block is not a multiple of 4!"); 506 } 507 508 for(int i = 0; i < bytes.Length; i += 4) 509 { 510 // Swap endianness here - and since output is "uint" - take 4 bytes 511 yield return BitHelper.ToUInt32(bytes, i, 4, false); 512 } 513 } 514 CheckIfInitialized()515 private bool CheckIfInitialized() 516 { 517 if(isInitialized == false) 518 { 519 parent.Log(LogLevel.Error, "Initialization Phase has not been executed. Aborting"); 520 return false; 521 } 522 return true; 523 } 524 525 private bool IsEncryption => parent.algorithmDirection.Value == false; 526 527 private const int BlockSizeInBytes = 128 / 8; 528 private const int MacSizeInBytes = BlockSizeInBytes; 529 530 private bool isInitialized; 531 private int payloadCounter; 532 private GcmBlockCipher gcm; 533 private List<byte> payload; 534 private List<uint> final; 535 private List<byte> aad; 536 private byte[] iv; 537 private KeyParameter keyParameters; 538 private AeadParameters finalParameters; 539 } 540 541 private enum GCMOrCCMPhase 542 { 543 Initialization = 0b00, 544 Header = 0b01, 545 Payload = 0b10, 546 Final = 0b11 547 } 548 549 private enum DataType 550 { 551 Bit32 = 0b00, 552 Bit16 = 0b01, 553 Bit8 = 0b10, 554 Bit1 = 0b11, 555 } 556 557 private enum KeySize 558 { 559 Bit128 = 0b00, 560 Bit192 = 0b01, 561 Bit256 = 0b10, 562 Reserved = 0b11, 563 } 564 565 private enum AlgorithmMode 566 { 567 TDES_ECB = 0b0000, 568 TDES_CBC = 0b0001, 569 DES_ECB = 0b0010, 570 DES_CBC = 0b0011, 571 AES_ECB = 0b0100, 572 AES_CBC = 0b0101, 573 AES_CTR = 0b0110, 574 AES_key_prepare_EBC_CBC = 0b0111, 575 AES_GCM = 0b1000, 576 AES_CCM = 0b1001, 577 } 578 579 private enum Registers 580 { 581 Control = 0x0, 582 Status = 0x4, 583 DataInput = 0x8, 584 DataOutput = 0xC, 585 // NOTE: DMA interface is not supported 586 DMAControl = 0x10, 587 InterruptMaskSetClear = 0x14, 588 RawInterruptStatus = 0x18, 589 MaskedInterruptStatus = 0x1C, 590 Key0L = 0x20, 591 Key0R = 0x24, 592 Key1L = 0x28, 593 Key1R = 0x2C, 594 Key2L = 0x30, 595 Key2R = 0x34, 596 Key3L = 0x38, 597 Key3R = 0x3C, 598 InitializationVector0L = 0x40, 599 InitializationVector0R = 0x44, 600 InitializationVector1L = 0x48, 601 InitializationVector1R = 0x4C, 602 // NOTE: Pre-emptive Context Switching is not supported 603 ContentSwapGCM_CCM0 = 0x50, 604 ContentSwapGCM_CCM1 = 0x54, 605 ContentSwapGCM_CCM2 = 0x58, 606 ContentSwapGCM_CCM3 = 0x5C, 607 ContentSwapGCM_CCM4 = 0x60, 608 ContentSwapGCM_CCM5 = 0x64, 609 ContentSwapGCM_CCM6 = 0x68, 610 ContentSwapGCM_CCM7 = 0x6C, 611 ContentSwapGCM0 = 0x70, 612 ContentSwapGCM1 = 0x74, 613 ContentSwapGCM2 = 0x78, 614 ContentSwapGCM3 = 0x7C, 615 ContentSwapGCM4 = 0x80, 616 ContentSwapGCM5 = 0x84, 617 ContentSwapGCM6 = 0x88, 618 ContentSwapGCM7 = 0x8C, 619 } 620 } 621 } 622