diff --git a/RimeLib.Cmd/Commands/Game/SignatureCreateCommand.cs b/RimeLib.Cmd/Commands/Game/SignatureCreateCommand.cs new file mode 100644 index 000000000..b27036916 --- /dev/null +++ b/RimeLib.Cmd/Commands/Game/SignatureCreateCommand.cs @@ -0,0 +1,20 @@ +using RimeLib.Cmd.Attributes; +using System; +using System.IO; + +namespace RimeLib.Cmd.Commands.Game +{ + [CommandDescription("Creates a new signature for a toc file.")] + public class SignatureCreateCommand : Command + { + [CommandArgument(Description = "Private RSA key for signing.")] + public FileInfo? PrivateKey { get; set; } + + [CommandArgument(Description = "Frostbite TOC file to be signed.")] + public FileInfo? TocFile { get; set; } + public override bool Execute(ref ExecutionContext p_Context, TextWriter p_Writer) + { + throw new NotImplementedException(); + } + } +} diff --git a/RimeLib.Cmd/Commands/Game/SignatureValidateCommand.cs b/RimeLib.Cmd/Commands/Game/SignatureValidateCommand.cs new file mode 100644 index 000000000..8a3b90fc2 --- /dev/null +++ b/RimeLib.Cmd/Commands/Game/SignatureValidateCommand.cs @@ -0,0 +1,18 @@ +using RimeLib.Cmd.Attributes; +using System; +using System.IO; + +namespace RimeLib.Cmd.Commands.Game +{ + [CommandDescription("Validate signature for a toc file.")] + public class SignatureValidateCommand : Command + { + [CommandArgument(Description = "Frostbite TOC file to be validated.")] + public FileInfo? TocFile { get; set; } + + public override bool Execute(ref ExecutionContext p_Context, TextWriter p_Writer) + { + throw new NotImplementedException(); + } + } +} diff --git a/RimeLib.Cmd/Contexts/GameContext.cs b/RimeLib.Cmd/Contexts/GameContext.cs index caa0571de..65b1d228b 100644 --- a/RimeLib.Cmd/Contexts/GameContext.cs +++ b/RimeLib.Cmd/Contexts/GameContext.cs @@ -43,6 +43,8 @@ public GameContext(BaseContext p_Parent, int p_Id, IEngineMounter p_Mounter) RegisterCommand(); RegisterCommand(); RegisterCommand(); + RegisterCommand(); + RegisterCommand(); } public override string GetShortDescription() @@ -187,5 +189,11 @@ internal IReadOnlyDictionary> GetMounte { return m_Mounter.GetResources(); } + + internal bool ValidateTocSignature(FileInfo p_TocFile, FileInfo p_PublicKey) + { + // RimeLib.Utils.CryptoUtils.VerifySignature() + return false; + } } } diff --git a/RimeLibLite/Utils/CryptoUtils.cs b/RimeLibLite/Utils/CryptoUtils.cs new file mode 100644 index 000000000..106ee4919 --- /dev/null +++ b/RimeLibLite/Utils/CryptoUtils.cs @@ -0,0 +1,245 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using System.Security.Cryptography; +using System.Text; +using System.Threading.Tasks; + +namespace RimeLib.Utils +{ + /// + /// Cryptography class to handle Frostbite file verification + /// + public class CryptoUtils + { + /// + /// The BCryptOpenAlgorithmProvider function loads and initializes a CNG provider. + /// + /// A pointer to a BCRYPT_ALG_HANDLE variable that receives the CNG provider handle. When you have finished using this handle, release it by passing it to the BCryptCloseAlgorithmProvider function. + /// A pointer to a null-terminated Unicode string that identifies the requested cryptographic algorithm. This can be one of the standard CNG Algorithm Identifiers or the identifier for another registered algorithm. + /// A pointer to a null-terminated Unicode string that identifies the specific provider to load. This is the registered alias of the cryptographic primitive provider. This parameter is optional and can be NULL if it is not needed. If this parameter is NULL, the default provider for the specified algorithm will be loaded. + /// Flags that modify the behavior of the function. + /// + [DllImport("bcrypt.dll")] + public static extern UInt32 BCryptOpenAlgorithmProvider(ref IntPtr p_PhAlgorithm, [MarshalAs(UnmanagedType.LPWStr)] String p_PszAlgId, [MarshalAs(UnmanagedType.LPWStr)] String p_PszImplementation, UInt32 p_DwFlags); + + /// + /// The BCryptImportKeyPair function imports a public/private key pair from a key BLOB. The BCryptImportKey function is used to import a symmetric key pair. + /// + /// The handle of the algorithm provider to import the key. This handle is obtained by calling the BCryptOpenAlgorithmProvider function. + /// This parameter is not currently used and should be NULL. + /// A null-terminated Unicode string that contains an identifier that specifies the type of BLOB that is contained in the pbInput buffer. This can be one of the following values. + /// A pointer to a BCRYPT_KEY_HANDLE that receives the handle of the imported key. This handle is used in subsequent functions that require a key, such as BCryptSignHash. This handle must be released when it is no longer needed by passing it to the BCryptDestroyKey function. + /// The address of a buffer that contains the key BLOB to import. The cbInput parameter contains the size of this buffer. The pszBlobType parameter specifies the type of key BLOB this buffer contains. + /// The size, in bytes, of the pbInput buffer. + /// A set of flags that modify the behavior of this function. + /// + [DllImport("bcrypt.dll")] + public static extern UInt32 BCryptImportKeyPair(IntPtr p_PhAlgorithm, IntPtr p_HImportKey, [MarshalAs(UnmanagedType.LPWStr)] String p_PszBlobType, ref IntPtr p_PhKey, Byte[] p_PbInput, UInt32 p_CbInput, UInt32 p_DwFlags); + + /// + /// The BCryptSignHash function creates a signature of a hash value. + /// + /// The handle of the key to use to sign the hash. + /// A pointer to a structure that contains padding information. The actual type of structure this parameter points to depends on the value of the dwFlags parameter. This parameter is only used with asymmetric keys and must be NULL otherwise. + /// A pointer to a buffer that contains the hash value to sign. The cbInput parameter contains the size of this buffer. + /// The number of bytes in the pbInput buffer to sign. + /// The address of a buffer to receive the signature produced by this function. The cbOutput parameter contains the size of this buffer. + /// The size, in bytes, of the pbOutput buffer. + /// A pointer to a ULONG variable that receives the number of bytes copied to the pbOutput buffer. + /// A set of flags that modify the behavior of this function. + /// + [DllImport("bcrypt.dll")] + public static extern UInt32 BCryptSignHash(IntPtr p_HKey, [MarshalAs(UnmanagedType.LPWStr)] ref String p_PPaddingInfo, Byte[] p_PbInput, UInt32 p_CbInput, Byte[] p_PbOutput, UInt32 p_CbOutput, ref UInt32 p_PcbResult, UInt32 p_DwFlags); + + /// + /// The BCryptVerifySignature function verifies that the specified signature matches the specified hash. + /// + /// The handle of the key to use to decrypt the signature. + /// A pointer to a structure that contains padding information. + /// The address of a buffer that contains the hash of the data. + /// The size, in bytes, of the pbHash buffer. + /// The address of a buffer that contains the signed hash of the data. + /// The size, in bytes, of the pbSignature buffer. + /// A set of flags that modify the behavior of this function. + /// + [DllImport("bcrypt.dll")] + public static extern UInt32 BCryptVerifySignature(IntPtr p_HKey, [MarshalAs(UnmanagedType.LPWStr)] ref String p_PPaddingInfo, Byte[] p_PbHash, UInt32 p_CbHash, Byte[] p_PbSignature, UInt32 p_CbSignature, UInt32 p_DwFlags); + + /// + /// The BCryptDestroyKey function destroys a key. + /// + /// The handle of the key to destroy. + /// Returns a status code that indicates the success or failure of the function. + [DllImport("bcrypt.dll")] + public static extern UInt32 BCryptDestroyKey(IntPtr p_HKey); + + /// + /// + /// + /// + /// + /// + [DllImport("bcrypt.dll")] + public static extern UInt32 BCryptCloseAlgorithmProvider(IntPtr p_HAlgorithm, UInt32 p_DwFlags); + + // BCrypt constants + const string BcryptRsaAlgorithm = "RSA"; + const string BcryptRsapublicBlob = "RSAPUBLICBLOB"; + const string BcryptRsaprivateBlob = "RSAPRIVATEBLOB"; + const string BcryptSha1Algorithm = "SHA1"; + const uint BcryptPadPkcs1 = 0x00000002; + + // NTSTATUS constants + const uint StatusSuccess = 0; + const uint StatusInvalidSignature = 0xC000A000; + + /// + /// Verifies a RSA HMACSHA1 signature with the data and message + /// + /// Data to check + /// Signature to check for the data + /// Public key to check the data + /// Message to check the data + /// true if successful, false if bad signature + public static bool VerifySignature(byte[] p_Data, byte[] p_Signature, byte[] p_PublicKey, byte[] p_Message) + { + var s_Key = IntPtr.Zero; // BCRYPT_KEY_HANDLE + var s_Algorithm = IntPtr.Zero; // BCRYPT_ALG_HANDLE + + // Open up a new algorithm provider + var s_Ret = BCryptOpenAlgorithmProvider(ref s_Algorithm, BcryptRsaAlgorithm, "", 0); + if (s_Ret != StatusSuccess) + { + // TODO: Error + return false; + } + + // Import the RSA public key + s_Ret = BCryptImportKeyPair(s_Algorithm, IntPtr.Zero, BcryptRsapublicBlob, ref s_Key, p_PublicKey, (uint)p_PublicKey.Length, 0); + if (s_Ret != StatusSuccess) + { + // Cleanup + BCryptCloseAlgorithmProvider(s_Algorithm, 0); + s_Algorithm = IntPtr.Zero; + + // TODO: Error + return false; + } + + // Create our new HMACSHA generator, using the message as the key + var s_Hmac = new HMACSHA1(p_Message); + + // Compute the has of the data using the previously set up hmac sha1. + var s_Hash = s_Hmac.ComputeHash(p_Data); + + // BCrypt stuff + var s_PaddingInfo = BcryptSha1Algorithm; + + // Verify the signature + s_Ret = BCryptVerifySignature(s_Key, ref s_PaddingInfo, s_Hash, (uint)s_Hash.Length, p_Signature, (uint)p_Signature.Length, BcryptPadPkcs1); + if (s_Ret != StatusSuccess) + { + BCryptDestroyKey(s_Key); + BCryptCloseAlgorithmProvider(s_Algorithm, 0); + s_Key = IntPtr.Zero; + s_Algorithm = IntPtr.Zero; + + if (s_Ret == StatusInvalidSignature) + { + // TODO: Invalid Signature Message + } + // TODO: Error + return false; + } + + // Lets clean up everything that we used thus far + BCryptDestroyKey(s_Key); + BCryptCloseAlgorithmProvider(s_Algorithm, 0); + s_Key = IntPtr.Zero; + s_Algorithm = IntPtr.Zero; + s_Hmac.Clear(); + + // Signatures good ;) + return true; + } + + /// + /// Creates a RSA HMAC SHA1 signature + /// + /// Data to take the hash of + /// Private key data + /// Message to sign with + /// + public static byte[] CreateSignature(byte[] p_Data, byte[] p_PrivateKey, byte[] p_Message) + { + // Variables + var s_Key = IntPtr.Zero; // BCRYPT_KEY_HANDLE + var s_Algorithm = IntPtr.Zero; // BCRYPT_ALG_HANDLE + + // Open up a new algorithm provider + var s_Ret = BCryptOpenAlgorithmProvider(ref s_Algorithm, BcryptRsaAlgorithm, "", 0); + if (s_Ret != StatusSuccess) + { + // TODO: Error + return null; + } + + // Import the RSA public key + s_Ret = BCryptImportKeyPair(s_Algorithm, IntPtr.Zero, BcryptRsaprivateBlob, ref s_Key, p_PrivateKey, (uint)p_PrivateKey.Length, 0); + if (s_Ret != StatusSuccess) + { + // Cleanup + BCryptCloseAlgorithmProvider(s_Algorithm, 0); + s_Algorithm = IntPtr.Zero; + + // TODO: Error + return null; + } + + // Create our new HMACSHA generator, using the message as the key + var s_Hmac = new HMACSHA1(p_Message); + + // Compute the has of the data using the previously set up hmac sha1. + var s_Hash = s_Hmac.ComputeHash(p_Data); + + // BCrypt stuff + var s_PaddingInfo = BcryptSha1Algorithm; + uint s_SignatureSize = 0; + + // Call sign hash one time to get the length of the signature + s_Ret = BCryptSignHash(s_Key, ref s_PaddingInfo, s_Hash, (uint)s_Hash.Length, null, 0, ref s_SignatureSize, BcryptPadPkcs1); + if (s_Ret != StatusSuccess) + { + BCryptDestroyKey(s_Key); + BCryptCloseAlgorithmProvider(s_Algorithm, 0); + + return null; + } + + // Create a place to hold the newly generated signature + var s_Signature = new byte[s_SignatureSize]; + uint s_Size = 0; + + // Call BCryptSignHash again to actually generate the signature + s_Ret = BCryptSignHash(s_Key, ref s_PaddingInfo, s_Hash, (uint)s_Hash.Length, s_Signature, s_SignatureSize, ref s_Size, BcryptPadPkcs1); + if (s_Ret != StatusSuccess) + { + BCryptDestroyKey(s_Key); + BCryptCloseAlgorithmProvider(s_Algorithm, 0); + + return null; + } + + // Cleanup + BCryptDestroyKey(s_Key); + BCryptCloseAlgorithmProvider(s_Algorithm, 0); + s_Key = IntPtr.Zero; + s_Algorithm = IntPtr.Zero; + + return s_Signature; + } + } + +}