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

Support decryption using AesGcm #1606

Merged
merged 12 commits into from
Jun 1, 2021
Original file line number Diff line number Diff line change
Expand Up @@ -751,7 +751,7 @@ private static string EncryptTokenPrivate(string innerJwt, EncryptingCredentials
throw LogHelper.LogExceptionMessage(new ArgumentException(TokenLogMessages.IDX10620));

byte[] wrappedKey = null;
SecurityKey securityKey = JwtTokenUtilities.GetSecurityKey(encryptingCredentials,cryptoProviderFactory, out wrappedKey);
SecurityKey securityKey = JwtTokenUtilities.GetSecurityKey(encryptingCredentials, cryptoProviderFactory, out wrappedKey);

using (var encryptionProvider = cryptoProviderFactory.CreateAuthenticatedEncryptionProvider(securityKey, encryptingCredentials.Enc))
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ public virtual AuthenticatedEncryptionProvider CreateAuthenticatedEncryptionProv
return cryptoProvider;
}

if (SupportedAlgorithms.IsSupportedAuthenticatedEncryptionAlgorithm(algorithm, key))
if (SupportedAlgorithms.IsSupportedEncryptionAlgorithm(algorithm, key))
return new AuthenticatedEncryptionProvider(key, algorithm);

throw LogHelper.LogExceptionMessage(new ArgumentException(LogHelper.FormatInvariant(LogMessages.IDX10652, algorithm), nameof(algorithm)));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,14 @@
using System;
using System.IO;
using System.Security.Cryptography;
using System.Runtime.InteropServices;
using Microsoft.IdentityModel.Logging;

namespace Microsoft.IdentityModel.Tokens
{
delegate AuthenticatedEncryptionResult EncryptionDelegate(byte[] plaintText, byte[] authenticatedData, byte[] iv);
delegate byte[] DecryptionDelegate(byte[] cipherText, byte[] authenticatedData, byte[] iv, byte[] authenticationTag);

/// <summary>
/// Provides authenticated encryption and decryption services.
/// </summary>
Expand All @@ -46,8 +50,11 @@ private struct AuthenticatedKeys
private Lazy<AuthenticatedKeys> _authenticatedkeys;
private CryptoProviderFactory _cryptoProviderFactory;
private bool _disposed;
private Lazy<bool> _keySizeIsValid;
private string _hmacAlgorithm;
private Lazy<SymmetricSignatureProvider> _symmetricSignatureProvider;
private DecryptionDelegate DecryptFunction;
private EncryptionDelegate EncryptFunction;

/// <summary>
/// Initializes a new instance of the <see cref="AuthenticatedEncryptionProvider"/> class used for encryption and decryption.
Expand All @@ -67,20 +74,126 @@ public AuthenticatedEncryptionProvider(SecurityKey key, string algorithm)
if (string.IsNullOrWhiteSpace(algorithm))
throw LogHelper.LogArgumentNullException(nameof(algorithm));

_authenticatedkeys = new Lazy<AuthenticatedKeys>(CreateAuthenticatedKeys);
_hmacAlgorithm = GetHmacAlgorithm(algorithm);
Key = key;
Algorithm = algorithm;
_cryptoProviderFactory = key.CryptoProviderFactory;
if (SupportedAlgorithms.IsSupportedEncryptionAlgorithm(algorithm, key))
{
if (SupportedAlgorithms.IsAesGcm(algorithm))
{
#if NETSTANDARD2_0
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
throw LogHelper.LogExceptionMessage(new PlatformNotSupportedException(LogHelper.FormatInvariant(LogMessages.IDX10713, algorithm)));
#endif
InitializeUsingAesGcm();
}
else
InitializeUsingAesCbc();
}
else
throw LogHelper.LogExceptionMessage(new ArgumentException(LogHelper.FormatInvariant(LogMessages.IDX10668, GetType(), algorithm, key)));
}

private void InitializeUsingAesGcm()
{
_keySizeIsValid = new Lazy<bool>(ValidKeySize);
EncryptFunction = EncryptWithAesGcm;
DecryptFunction = DecryptWithAesGcm;
}

private void InitializeUsingAesCbc()
{
_authenticatedkeys = new Lazy<AuthenticatedKeys>(CreateAuthenticatedKeys);
_hmacAlgorithm = GetHmacAlgorithm(Algorithm);
_symmetricSignatureProvider = new Lazy<SymmetricSignatureProvider>(CreateSymmetricSignatureProvider);
EncryptFunction = EncryptWithAesCbc;
DecryptFunction = DecryptWithAesCbc;
}

private AuthenticatedKeys CreateAuthenticatedKeys()
internal bool ValidKeySize()
{
if (!IsSupportedAlgorithm(Key, Algorithm))
throw LogHelper.LogExceptionMessage(new ArgumentException(LogHelper.FormatInvariant(LogMessages.IDX10668, GetType(), Algorithm, Key)));
ValidateKeySize(Key, Algorithm);
return true;
}

private AuthenticatedEncryptionResult EncryptWithAesGcm(byte[] plaintext, byte[] authenticatedData, byte[] iv)
{
throw LogHelper.LogExceptionMessage(new NotSupportedException(LogHelper.FormatInvariant(LogMessages.IDX10715, Algorithm)));
}

private byte[] DecryptWithAesGcm(byte[] ciphertext, byte[] authenticatedData, byte[] iv, byte[] authenticationTag)
{
_ = _keySizeIsValid.Value;
byte[] clearBytes = new byte[ciphertext.Length];
using (var aes = new AesGcm(GetKeyBytes(Key)))
{
aes.Decrypt(iv, ciphertext, authenticationTag, clearBytes, authenticatedData);
}

return clearBytes;
}

private AuthenticatedEncryptionResult EncryptWithAesCbc(byte[] plaintext, byte[] authenticatedData, byte[] iv)
{
using Aes aes = Aes.Create();
aes.Mode = CipherMode.CBC;
aes.Padding = PaddingMode.PKCS7;
aes.Key = _authenticatedkeys.Value.AesKey.Key;
if (iv != null)
aes.IV = iv;

byte[] ciphertext;
try
{
ciphertext = Transform(aes.CreateEncryptor(), plaintext, 0, plaintext.Length);
}
catch (Exception ex)
{
throw LogHelper.LogExceptionMessage(new SecurityTokenEncryptionFailedException(LogHelper.FormatInvariant(LogMessages.IDX10654, ex)));
}

byte[] al = Utility.ConvertToBigEndian(authenticatedData.Length * 8);
byte[] macBytes = new byte[authenticatedData.Length + aes.IV.Length + ciphertext.Length + al.Length];
Array.Copy(authenticatedData, 0, macBytes, 0, authenticatedData.Length);
Array.Copy(aes.IV, 0, macBytes, authenticatedData.Length, aes.IV.Length);
Array.Copy(ciphertext, 0, macBytes, authenticatedData.Length + aes.IV.Length, ciphertext.Length);
Array.Copy(al, 0, macBytes, authenticatedData.Length + aes.IV.Length + ciphertext.Length, al.Length);
byte[] macHash = _symmetricSignatureProvider.Value.Sign(macBytes);
var authenticationTag = new byte[_authenticatedkeys.Value.HmacKey.Key.Length];
Array.Copy(macHash, authenticationTag, authenticationTag.Length);

return new AuthenticatedEncryptionResult(Key, ciphertext, aes.IV, authenticationTag);
}

private byte[] DecryptWithAesCbc(byte[] ciphertext, byte[] authenticatedData, byte[] iv, byte[] authenticationTag)
{
// Verify authentication Tag
byte[] al = Utility.ConvertToBigEndian(authenticatedData.Length * 8);
byte[] macBytes = new byte[authenticatedData.Length + iv.Length + ciphertext.Length + al.Length];
Array.Copy(authenticatedData, 0, macBytes, 0, authenticatedData.Length);
Array.Copy(iv, 0, macBytes, authenticatedData.Length, iv.Length);
Array.Copy(ciphertext, 0, macBytes, authenticatedData.Length + iv.Length, ciphertext.Length);
Array.Copy(al, 0, macBytes, authenticatedData.Length + iv.Length + ciphertext.Length, al.Length);
if (!_symmetricSignatureProvider.Value.Verify(macBytes, authenticationTag, _authenticatedkeys.Value.HmacKey.Key.Length))
throw LogHelper.LogExceptionMessage(new SecurityTokenDecryptionFailedException(LogHelper.FormatInvariant(LogMessages.IDX10650, Base64UrlEncoder.Encode(authenticatedData), Base64UrlEncoder.Encode(iv), Base64UrlEncoder.Encode(authenticationTag))));

using Aes aes = Aes.Create();
aes.Mode = CipherMode.CBC;
aes.Padding = PaddingMode.PKCS7;
aes.Key = _authenticatedkeys.Value.AesKey.Key;
aes.IV = iv;
try
{
return Transform(aes.CreateDecryptor(), ciphertext, 0, ciphertext.Length);
}
catch (Exception ex)
{
throw LogHelper.LogExceptionMessage(new SecurityTokenDecryptionFailedException(LogHelper.FormatInvariant(LogMessages.IDX10654, ex)));
}
}

private AuthenticatedKeys CreateAuthenticatedKeys()
{
ValidateKeySize(Key, Algorithm);

return GetAlgorithmParameters(Key, Algorithm);
Expand Down Expand Up @@ -158,34 +271,7 @@ public virtual AuthenticatedEncryptionResult Encrypt(byte[] plaintext, byte[] au
if (_disposed)
throw LogHelper.LogExceptionMessage(new ObjectDisposedException(GetType().ToString()));

using Aes aes = Aes.Create();
aes.Mode = CipherMode.CBC;
aes.Padding = PaddingMode.PKCS7;
aes.Key = _authenticatedkeys.Value.AesKey.Key;
if (iv != null)
aes.IV = iv;

byte[] ciphertext;
try
{
ciphertext = Transform(aes.CreateEncryptor(), plaintext, 0, plaintext.Length);
}
catch(Exception ex)
{
throw LogHelper.LogExceptionMessage(new SecurityTokenEncryptionFailedException(LogHelper.FormatInvariant(LogMessages.IDX10654, ex)));
}

byte[] al = Utility.ConvertToBigEndian(authenticatedData.Length * 8);
byte[] macBytes = new byte[authenticatedData.Length + aes.IV.Length + ciphertext.Length + al.Length];
Array.Copy(authenticatedData, 0, macBytes, 0, authenticatedData.Length);
Array.Copy(aes.IV, 0, macBytes, authenticatedData.Length, aes.IV.Length);
Array.Copy(ciphertext, 0, macBytes, authenticatedData.Length + aes.IV.Length, ciphertext.Length);
Array.Copy(al, 0, macBytes, authenticatedData.Length + aes.IV.Length + ciphertext.Length, al.Length);
byte[] macHash = _symmetricSignatureProvider.Value.Sign(macBytes);
var authenticationTag = new byte[_authenticatedkeys.Value.HmacKey.Key.Length];
Array.Copy(macHash, authenticationTag, authenticationTag.Length);

return new AuthenticatedEncryptionResult(Key, ciphertext, aes.IV, authenticationTag);
return EncryptFunction(plaintext, authenticatedData, iv);
}

/// <summary>
Expand Down Expand Up @@ -220,29 +306,7 @@ public virtual byte[] Decrypt(byte[] ciphertext, byte[] authenticatedData, byte[
if (_disposed)
throw LogHelper.LogExceptionMessage(new ObjectDisposedException(GetType().ToString()));

// Verify authentication Tag
byte[] al = Utility.ConvertToBigEndian(authenticatedData.Length * 8);
byte[] macBytes = new byte[authenticatedData.Length + iv.Length + ciphertext.Length + al.Length];
Array.Copy(authenticatedData, 0, macBytes, 0, authenticatedData.Length);
Array.Copy(iv, 0, macBytes, authenticatedData.Length, iv.Length);
Array.Copy(ciphertext, 0, macBytes, authenticatedData.Length + iv.Length, ciphertext.Length);
Array.Copy(al, 0, macBytes, authenticatedData.Length + iv.Length + ciphertext.Length, al.Length);
if (!_symmetricSignatureProvider.Value.Verify(macBytes, authenticationTag, _authenticatedkeys.Value.HmacKey.Key.Length))
throw LogHelper.LogExceptionMessage(new SecurityTokenDecryptionFailedException(LogHelper.FormatInvariant(LogMessages.IDX10650, Base64UrlEncoder.Encode(authenticatedData), Base64UrlEncoder.Encode(iv), Base64UrlEncoder.Encode(authenticationTag))));

using Aes aes = Aes.Create();
aes.Mode = CipherMode.CBC;
aes.Padding = PaddingMode.PKCS7;
aes.Key = _authenticatedkeys.Value.AesKey.Key;
aes.IV = iv;
try
{
return Transform(aes.CreateDecryptor(), ciphertext, 0, ciphertext.Length);
}
catch (Exception ex)
{
throw LogHelper.LogExceptionMessage(new SecurityTokenDecryptionFailedException(LogHelper.FormatInvariant(LogMessages.IDX10654, ex)));
}
return DecryptFunction(ciphertext, authenticatedData, iv, authenticationTag);
}

/// <summary>
Expand Down Expand Up @@ -276,7 +340,7 @@ protected virtual void Dispose(bool disposing)
/// <returns>true if 'key, algorithm' pair is supported.</returns>
protected virtual bool IsSupportedAlgorithm(SecurityKey key, string algorithm)
{
return SupportedAlgorithms.IsSupportedAuthenticatedEncryptionAlgorithm(algorithm, key);
return SupportedAlgorithms.IsSupportedEncryptionAlgorithm(algorithm, key);
}

private AuthenticatedKeys GetAlgorithmParameters(SecurityKey key, string algorithm)
Expand Down Expand Up @@ -342,8 +406,8 @@ protected virtual byte[] GetKeyBytes(SecurityKey key)
if (key is SymmetricSecurityKey symmetricSecurityKey)
return symmetricSecurityKey.Key;

if (key is JsonWebKey jsonWebKey && jsonWebKey.K != null && jsonWebKey.Kty == JsonWebAlgorithmsKeyTypes.Octet)
return Base64UrlEncoder.DecodeBytes(jsonWebKey.K);
if (key is JsonWebKey jsonWebKey && JsonWebKeyConverter.TryConvertToSymmetricSecurityKey(jsonWebKey, out SecurityKey securityKey))
return GetKeyBytes(securityKey);

throw LogHelper.LogExceptionMessage(new ArgumentException(LogHelper.FormatInvariant(LogMessages.IDX10667, key)));
}
Expand Down Expand Up @@ -404,6 +468,30 @@ protected virtual void ValidateKeySize(SecurityKey key, string algorithm)
return;
}

if (SecurityAlgorithms.Aes128Gcm.Equals(algorithm, StringComparison.Ordinal))
RojaEnnam marked this conversation as resolved.
Show resolved Hide resolved
{
if (key.KeySize < 128)
throw LogHelper.LogExceptionMessage(new ArgumentOutOfRangeException(nameof(key), LogHelper.FormatInvariant(LogMessages.IDX10653, SecurityAlgorithms.Aes128Gcm, 128, key.KeyId, key.KeySize)));

return;
}

if (SecurityAlgorithms.Aes192Gcm.Equals(algorithm, StringComparison.Ordinal))
{
if (key.KeySize < 192)
throw LogHelper.LogExceptionMessage(new ArgumentOutOfRangeException(nameof(key), LogHelper.FormatInvariant(LogMessages.IDX10653, SecurityAlgorithms.Aes192Gcm, 192, key.KeyId, key.KeySize)));

return;
}

if (SecurityAlgorithms.Aes256Gcm.Equals(algorithm, StringComparison.Ordinal))
{
if (key.KeySize < 256)
throw LogHelper.LogExceptionMessage(new ArgumentOutOfRangeException(nameof(key), LogHelper.FormatInvariant(LogMessages.IDX10653, SecurityAlgorithms.Aes256Gcm, 256, key.KeyId, key.KeySize)));

return;
}

throw LogHelper.LogExceptionMessage(new ArgumentException(LogHelper.FormatInvariant(LogMessages.IDX10652, algorithm)));
}
}
Expand Down
1 change: 1 addition & 0 deletions src/Microsoft.IdentityModel.Tokens/GlobalSuppressions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -55,3 +55,4 @@
[assembly: SuppressMessage("Design", "CA1031:Do not catch general exception types", Justification = "Appropriate exception will be caught.", Scope = "member", Target = "~M:Microsoft.IdentityModel.Tokens.EventBasedLRUCache`2.OnStart")]
[assembly: SuppressMessage("Design", "CA1031:Do not catch general exception types", Justification = "Used as validation", Scope = "member", Target = "~M:Microsoft.IdentityModel.Tokens.InternalValidators.ValidateLifetimeAndIssuerAfterSignatureNotValidatedJwt(Microsoft.IdentityModel.Tokens.SecurityToken,System.Nullable{System.DateTime},System.Nullable{System.DateTime},System.String,Microsoft.IdentityModel.Tokens.TokenValidationParameters,System.Text.StringBuilder)")]
[assembly: SuppressMessage("Design", "CA1031:Do not catch general exception types", Justification = "Used as validation", Scope = "member", Target = "~M:Microsoft.IdentityModel.Tokens.InternalValidators.ValidateLifetimeAndIssuerAfterSignatureNotValidatedSaml(Microsoft.IdentityModel.Tokens.SecurityToken,System.Nullable{System.DateTime},System.Nullable{System.DateTime},System.String,Microsoft.IdentityModel.Tokens.TokenValidationParameters,System.Text.StringBuilder)")]
[assembly: SuppressMessage("Globalization", "CA1305:Specify IFormatProvider", Justification = "Not using Globalization", Scope = "member", Target = "~M:Microsoft.IdentityModel.Tokens.Interop.Kernel32.GetMessage(System.Int32,System.IntPtr)~System.String")]
3 changes: 3 additions & 0 deletions src/Microsoft.IdentityModel.Tokens/LogMessages.cs
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,9 @@ internal static class LogMessages
public const string IDX10710 = "IDX10710: Computing a JWK thumbprint is supported only on SymmetricSecurityKey, JsonWebKey, RsaSecurityKey, X509SecurityKey, and ECDsaSecurityKey.";
public const string IDX10711 = "IDX10711: Unable to Decrypt, Internal DecryptionFunction is not available.";
public const string IDX10712 = "IDX10712: Unable to Encrypt, Internal EncryptionFunction is not available.";
public const string IDX10713 = "IDX10713: Encrytion/Decryption using algorithm '{0}' is only supported on Windows platform.";
public const string IDX10714 = "IDX10714: Unable to perform the decryption. There is a authentication tag mismatch.";
public const string IDX10715 = "IDX10715: Encryption using algorithm: '{0}' is not supported.";

// Json specific errors
//public const string IDX10801 = "IDX10801:"
Expand Down
3 changes: 3 additions & 0 deletions src/Microsoft.IdentityModel.Tokens/SecurityAlgorithms.cs
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,9 @@ public static class SecurityAlgorithms
public const string Aes128CbcHmacSha256 = "A128CBC-HS256";
public const string Aes192CbcHmacSha384 = "A192CBC-HS384";
public const string Aes256CbcHmacSha512 = "A256CBC-HS512";
public const string Aes128Gcm = "A128GCM";
public const string Aes192Gcm = "A192GCM";
public const string Aes256Gcm = "A256GCM";

internal const string DefaultAsymmetricKeyWrapAlgorithm = RsaOaepKeyWrap;
internal const string DefaultSymmetricEncryptionAlgorithm = Aes128CbcHmacSha256;
Expand Down
Loading