Skip to content

Instantly share code, notes, and snippets.

@barncastle
Created July 27, 2022 15:00
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save barncastle/979c12a9c5e64d810a28ad1728e7e0f9 to your computer and use it in GitHub Desktop.
Save barncastle/979c12a9c5e64d810a28ad1728e7e0f9 to your computer and use it in GitHub Desktop.
C# re-implementation of the WoW Matrix Card 2FA system available from TBC (2.0.0.5991)
class WoWMatrixCard
{
public uint Width = 8; // SECURITYMATRIX_NUM_COLUMNS in SecurityMatrix.lua, default of 8
public uint Height = 10; // SECURITYMATRIX_NUM_ROWS in SecurityMatrix.lua, default of 10
public uint DigitCount = 2; // SECURITYMATRIX_NUM_MIN_DIGITS in SecurityMatrix.lua, default of 2
public uint ChallengeCount; // number of cells/rounds to be completed
public ulong Seed;
public uint[] Coordinates;
private readonly byte[] _IPad = new byte[0x40];
private readonly byte[] _OPad = new byte[0x40];
private readonly SHA1 _SHA1 = SHA1.Create();
private readonly ARC4 _ARC4 = new();
private readonly byte[] _Buffer = new byte[1];
/// <summary>
/// Matrix card initalisation method
/// <para>Called and populated from CmdAuthLogonChallenge</para>
/// </summary>
public void SetMatrixInfo(byte width, byte height, byte digitCount, byte challengeCount, ulong seed, byte[] sessionKey)
{
Width = width;
Height = height;
DigitCount = digitCount;
ChallengeCount = challengeCount;
Seed = seed;
Coordinates = new uint[ChallengeCount];
// generate the coordinates of the cells required
GenerateCoordinates(Seed);
// MD5(seed_bytes + sessionKey)
using MD5 md5 = MD5.Create();
md5.TransformBlock(BitConverter.GetBytes(Seed), 0, 8, null, 0);
md5.TransformFinalBlock(sessionKey, 0, sessionKey.Length);
byte[] key = md5.Hash;
// initialise SARC4
_ARC4.SetKey(key);
// HMAC
Array.Fill<byte>(_IPad, 0x36);
Array.Fill<byte>(_OPad, 0x5C);
for (int i = 0; i < key.Length; i++)
{
_IPad[i] ^= key[i];
_OPad[i] ^= key[i];
}
// initialise the input hash
_SHA1.Initialize();
_SHA1.TransformBlock(_IPad, 0, _IPad.Length, null, 0);
}
/// <summary>
/// Invoked by the UI to ascertain which cell to highlight each round
/// </summary>
public bool GetMatrixCoordinates(uint round, out uint x, out uint y)
{
x = y = 0;
if (round >= ChallengeCount)
return false;
uint coord = Coordinates[round];
x = coord % Width;
y = coord / Width;
if (y >= Height)
return false;
return true;
}
/// <summary>
/// Invoked by SecurityMatrix.lua each time a digit is selected
/// </summary>
public void EnterMatrix(byte value)
{
_Buffer[0] = value;
// ARC4 encrypt our input and
// append the output to our SHA hash
_ARC4.Process(_Buffer, 1);
_SHA1.TransformBlock(_Buffer, 0, 1, null, 0);
}
/// <summary>
/// Invoked by SecurityMatrix.lua once all digits have been
/// selected and OK is pressed
/// </summary>
public byte[] FinalizeMatrix()
{
// finalise the input hash
_SHA1.TransformFinalBlock(_Buffer, 0, 0);
byte[] input_hash = _SHA1.Hash;
// compute our proof
_SHA1.Initialize();
_SHA1.TransformBlock(_OPad, 0, _OPad.Length, null, 0);
_SHA1.TransformFinalBlock(input_hash, 0, input_hash.Length);
return _SHA1.Hash;
}
/// <summary>
/// Calculates which cell will be requested at each round
/// </summary>
private void GenerateCoordinates(ulong seed)
{
uint matrixSize = Width * Height;
uint[] matrixIndicies = new uint[matrixSize];
// populate the indicies
for (uint i = 1u; i < matrixSize; i++)
matrixIndicies[i] = i;
for (uint i = 0u; i < ChallengeCount; i++)
{
uint count = matrixSize - i;
uint index = (uint)(seed % count);
Coordinates[i] = matrixIndicies[index];
// "pop" the selected entry to prevent re-use
// e.g. i(1) : [0, 1, 2] => [0, 2, 2]
for (uint j = index; j < count - 1; j++)
matrixIndicies[j] = matrixIndicies[j + 1];
seed /= count;
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment