Socialvoid.NET/Socialvoid/Security/Otp/InMemoryKey.cs

170 lines
4.9 KiB
C#

/*
* This file is part of Socialvoid.NET Project (https://github.com/Intellivoid/Socialvoid.NET).
* Copyright (c) 2021 Socialvoid.NET Authors.
*
* This library is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This library is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this source code of library.
* If not, see <http://www.gnu.org/licenses/>.
*/
/*
* Credits to Devin Martin and the original OtpSharp library:
* https://github.com/kspearrin/Otp.NET
*/
using System;
using System.Security.Cryptography;
namespace Socialvoid.Security.Otp
{
/// <summary>
/// Represents a key in memory.
/// <code> since: v0.0.0 </code>
/// </summary>
/// <remarks>
/// This will attempt to use the Windows data protection api to
/// encrypt the key in memory.
/// However, this type favors working over memory protection.
/// This is an attempt to minimize exposure in memory, nothing more.
/// This protection is flawed in many ways and is limited to Windows.
///
/// In order to use the key to compute an hmac it must be temporarily
/// decrypted, used, then re-encrypted.
/// This does expose the key in memory for a time.
/// If a memory dump occurs in this time the plaintext key will be part
/// of it. Furthermore, there are potentially artifacts from the hmac
/// computation, GC compaction, or any number of other leaks even after
/// the key is re-encrypted.
///
/// This type favors working over memory protection. If the particular
/// platform isn't supported then, unless forced by modifying the
/// <c>IsPlatformSupported</c> method, it will just store the key in a standard
/// byte array.
/// </remarks>
public class InMemoryKey : IKeyProvider
{
//-------------------------------------------------
#region field's Region
/// <summary>
/// The key data in memory.
/// <code> since: v0.0.0 </code>
/// </summary>
internal readonly byte[] _KeyData;
/// <summary>
/// The key length representing the length of the <see cref="_KeyData"/>.
/// <code> since: v0.0.0 </code>
/// </summary>
internal readonly int _keyLength;
/// <summary>
/// Used for locking.
/// <code> since: v0.0.0 </code>
/// </summary>
private readonly object _stateSync = new();
#endregion
//-------------------------------------------------
#region Constructor's Region
/// <summary>
/// Creates an instance of a key.
/// </summary>
/// <param name="key">Plaintext key data</param>
public InMemoryKey(byte[] key)
{
if (key == null || key.Length == 0)
{
throw new ArgumentException("Key cannot be empty or null",
nameof(key));
}
_keyLength = key.Length;
int paddedKeyLength = (int)Math.Ceiling((decimal)key.Length / (decimal)16) * 16;
_KeyData = new byte[paddedKeyLength];
Array.Copy(key, _KeyData, key.Length);
}
#endregion
//-------------------------------------------------
#region Get Method's Region
/// <summary>
/// Gets a copy of the plaintext key.
/// <code> since: v0.0.0 </code>
/// </summary>
/// <remarks>
/// This is internal rather than protected so that the tests can
/// use this method.
/// </remarks>
/// <returns>
/// Plaintext Key
/// </returns>
internal byte[] GetCopyOfKey()
{
var plainKey = new byte[_keyLength];
lock(_stateSync)
{
Array.Copy(_KeyData, plainKey, _keyLength);
}
return plainKey;
}
/// <summary>
/// Uses the key to get an HMAC using the specified algorithm and data.
/// <code> since: v0.0.0 </code>
/// </summary>
/// <param name="mode">
/// The HMAC algorithm to use
/// </param>
/// <param name="data">
/// The data used to compute the HMAC
/// </param>
/// <returns>
/// HMAC of the key and data
/// </returns>
public byte[] ComputeHmac(OtpHashMode mode, byte[] data)
{
byte[] hashedValue = null;
using(HMAC hmac = CreateHmacHash(mode))
{
byte[] key = this.GetCopyOfKey();
try
{
hmac.Key = key;
hashedValue = hmac.ComputeHash(data);
}
finally
{
KeyUtilities.Destroy(key);
}
}
return hashedValue;
}
#endregion
//-------------------------------------------------
#region static Method's Region
/// <summary>
/// Create an HMAC object for the specified algorithm.
/// <code> since: v0.0.0 </code>
/// </summary>
private static HMAC CreateHmacHash(OtpHashMode otpHashMode)
{
return otpHashMode switch
{
OtpHashMode.Sha256 => new HMACSHA256(),
OtpHashMode.Sha512 => new HMACSHA512(),
_ => new HMACSHA1() //OtpHashMode.Sha1
};
}
#endregion
//-------------------------------------------------
}
}