Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Integrate player accounts with the OpenRA forum. #15333

Merged
merged 12 commits into from Jul 28, 2018
223 changes: 223 additions & 0 deletions OpenRA.Game/CryptoUtil.cs
Expand Up @@ -9,6 +9,7 @@
*/
#endregion

using System;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
Expand All @@ -18,6 +19,228 @@ namespace OpenRA
{
public static class CryptoUtil
{
// Fixed byte pattern for the OID header
static readonly byte[] OIDHeader = { 0x30, 0xD, 0x6, 0x9, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0xD, 0x1, 0x1, 0x1, 0x5, 0x0 };

public static string PublicKeyFingerprint(RSAParameters parameters)
{
// Public key fingerprint is defined as the SHA1 of the modulus + exponent bytes
return SHA1Hash(parameters.Modulus.Append(parameters.Exponent).ToArray());
}

public static string EncodePEMPublicKey(RSAParameters parameters)
{
var data = Convert.ToBase64String(EncodePublicKey(parameters));
var output = new StringBuilder();
output.AppendLine("-----BEGIN PUBLIC KEY-----");
for (var i = 0; i < data.Length; i += 64)
output.AppendLine(data.Substring(i, Math.Min(64, data.Length - i)));
output.Append("-----END PUBLIC KEY-----");

return output.ToString();
}

public static RSAParameters DecodePEMPublicKey(string key)
{
try
{
// Reconstruct original key data
var lines = key.Split('\n');
var data = Convert.FromBase64String(lines.Skip(1).Take(lines.Length - 2).JoinWith(""));

// Pull the modulus and exponent bytes out of the ASN.1 tree
// Expect this to blow up if the key is not correctly formatted
using (var s = new MemoryStream(data))
{
// SEQUENCE
s.ReadByte();
ReadTLVLength(s);

// SEQUENCE -> fixed header junk
s.ReadByte();
var headerLength = ReadTLVLength(s);
s.Position += headerLength;

// SEQUENCE -> BIT_STRING
s.ReadByte();
ReadTLVLength(s);
s.ReadByte();

// SEQUENCE -> BIT_STRING -> SEQUENCE
s.ReadByte();
ReadTLVLength(s);

// SEQUENCE -> BIT_STRING -> SEQUENCE -> INTEGER (modulus)
s.ReadByte();
var modulusLength = ReadTLVLength(s);
s.ReadByte();
var modulus = s.ReadBytes(modulusLength - 1);

// SEQUENCE -> BIT_STRING -> SEQUENCE -> INTEGER (exponent)
s.ReadByte();
var exponentLength = ReadTLVLength(s);
s.ReadByte();
var exponent = s.ReadBytes(exponentLength - 1);

return new RSAParameters
{
Modulus = modulus,
Exponent = exponent
};
}
}
catch (Exception e)
{
throw new InvalidDataException("Invalid PEM public key", e);
}
}

static byte[] EncodePublicKey(RSAParameters parameters)
{
using (var stream = new MemoryStream())
{
var writer = new BinaryWriter(stream);

var modExpLength = TripletFullLength(parameters.Modulus.Length + 1) + TripletFullLength(parameters.Exponent.Length + 1);
var bitStringLength = TripletFullLength(modExpLength + 1);
var sequenceLength = TripletFullLength(bitStringLength + OIDHeader.Length);

// SEQUENCE
writer.Write((byte)0x30);
WriteTLVLength(writer, sequenceLength);

// SEQUENCE -> fixed header junk
writer.Write(OIDHeader);

// SEQUENCE -> BIT_STRING
writer.Write((byte)0x03);
WriteTLVLength(writer, bitStringLength);
writer.Write((byte)0x00);

// SEQUENCE -> BIT_STRING -> SEQUENCE
writer.Write((byte)0x30);
WriteTLVLength(writer, modExpLength);

// SEQUENCE -> BIT_STRING -> SEQUENCE -> INTEGER
// Modulus is padded with a zero to avoid issues with the sign bit
writer.Write((byte)0x02);
WriteTLVLength(writer, parameters.Modulus.Length + 1);
writer.Write((byte)0);
writer.Write(parameters.Modulus);

// SEQUENCE -> BIT_STRING -> SEQUENCE -> INTEGER
// Exponent is padded with a zero to avoid issues with the sign bit
writer.Write((byte)0x02);
WriteTLVLength(writer, parameters.Exponent.Length + 1);
writer.Write((byte)0);
writer.Write(parameters.Exponent);

return stream.ToArray();
}
}

static void WriteTLVLength(BinaryWriter writer, int length)
{
if (length < 0x80)
{
// Length < 128 is stored in a single byte
writer.Write((byte)length);
}
else
{
// If 128 <= length < 256**128 first byte encodes number of bytes required to hold the length
// High-bit is set as a flag to use this long-form encoding
var lengthBytes = BitConverter.GetBytes(length).Reverse().SkipWhile(b => b == 0).ToArray();
writer.Write((byte)(0x80 | lengthBytes.Length));
writer.Write(lengthBytes);
}
}

static int ReadTLVLength(Stream s)
{
var length = s.ReadByte();
if (length < 0x80)
return length;

var data = new byte[4];
s.ReadBytes(data, 0, Math.Min(length & 0x7F, 4));
return BitConverter.ToInt32(data.ToArray(), 0);
}

static int TripletFullLength(int dataLength)
{
if (dataLength < 0x80)
return 2 + dataLength;

return 2 + dataLength + BitConverter.GetBytes(dataLength).Reverse().SkipWhile(b => b == 0).Count();
}

public static string DecryptString(RSAParameters parameters, string data)
{
try
{
using (var rsa = new RSACryptoServiceProvider())
{
rsa.ImportParameters(parameters);
return Encoding.UTF8.GetString(rsa.Decrypt(Convert.FromBase64String(data), false));
}
}
catch (Exception e)
{
Log.Write("debug", "Failed to decrypt string with exception: {0}", e);
Console.WriteLine("String decryption failed: {0}", e);
return null;
}
}

public static string Sign(RSAParameters parameters, string data)
{
return Sign(parameters, Encoding.UTF8.GetBytes(data));
}

public static string Sign(RSAParameters parameters, byte[] data)
{
try
{
using (var rsa = new RSACryptoServiceProvider())
{
rsa.ImportParameters(parameters);
using (var csp = SHA1.Create())
return Convert.ToBase64String(rsa.SignHash(csp.ComputeHash(data), CryptoConfig.MapNameToOID("SHA1")));
}
}
catch (Exception e)
{
Log.Write("debug", "Failed to sign string with exception: {0}", e);
Console.WriteLine("String signing failed: {0}", e);
return null;
}
}

public static bool VerifySignature(RSAParameters parameters, string data, string signature)
{
return VerifySignature(parameters, Encoding.UTF8.GetBytes(data), signature);
}

public static bool VerifySignature(RSAParameters parameters, byte[] data, string signature)
{
try
{
using (var rsa = new RSACryptoServiceProvider())
{
rsa.ImportParameters(parameters);
using (var csp = SHA1.Create())
return rsa.VerifyHash(csp.ComputeHash(data), CryptoConfig.MapNameToOID("SHA1"), Convert.FromBase64String(signature));
}
}
catch (Exception e)
{
Log.Write("debug", "Failed to verify signature with exception: {0}", e);
Console.WriteLine("Signature validation failed: {0}", e);
return false;
}
}

public static string SHA1Hash(Stream data)
{
using (var csp = SHA1.Create())
Expand Down
8 changes: 8 additions & 0 deletions OpenRA.Game/Game.cs
Expand Up @@ -55,6 +55,7 @@ public static class Game
public static bool BenchmarkMode = false;

public static string EngineVersion { get; private set; }
public static LocalPlayerProfile LocalPlayerProfile;

static Task discoverNat;
static bool takeScreenshot = false;
Expand Down Expand Up @@ -407,6 +408,8 @@ public static void InitializeMod(string mod, Arguments args)

ModData = new ModData(Mods[mod], Mods, true);

LocalPlayerProfile = new LocalPlayerProfile(Platform.ResolvePath(Path.Combine("^", Settings.Game.AuthProfile)), ModData.Manifest.Get<PlayerDatabase>());

if (!ModData.LoadScreen.BeforeLoad())
return;

Expand Down Expand Up @@ -882,5 +885,10 @@ public static bool IsCurrentWorld(World world)
{
return OrderManager != null && OrderManager.World == world && !world.Disposing;
}

public static bool SetClipboardText(string text)
{
return Renderer.Window.SetClipboardText(text);
}
}
}