Skip to content

Commit

Permalink
Support for AES GCM 128
Browse files Browse the repository at this point in the history
- Fixes #1238
  • Loading branch information
AndersAbel committed Dec 16, 2020
2 parents 29c7246 + 8a5d201 commit b3b4300
Show file tree
Hide file tree
Showing 15 changed files with 324 additions and 3 deletions.
52 changes: 52 additions & 0 deletions Samples/SampleAesGcmStartupNetCore31/AesGcmAlgorithm.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
using System;
using System.Security.Cryptography;

namespace SampleAesGcmNetCore31
{
/// <summary>
/// SymmetricAlgorithm decrypting implementation for http://www.w3.org/2009/xmlenc11#aes128-gcm.
/// This is class is not a general implementation and can only do decryption.
/// </summary>
public class AesGcmAlgorithm : SymmetricAlgorithm
{
public const string AesGcm128Identifier = "http://www.w3.org/2009/xmlenc11#aes128-gcm";

// "For the purposes of this specification, AES-GCM shall be used with a 96 bit Initialization Vector (IV) and a 128 bit Authentication Tag (T)."
// Source: https://www.w3.org/TR/xmlenc-core1/#sec-AES-GCM
public const int NonceSizeInBits = 96;

private const int AuthenticationTagSizeInBits = 128;

public AesGcmAlgorithm()
{
//not sure about 128 keysize?
LegalKeySizesValue = new[] { new KeySizes(128, 128, 0) };

//iv setter checks that iv is the size of a block. Not sure if there should be other block sizes
LegalBlockSizesValue = new[] { new KeySizes(NonceSizeInBits, NonceSizeInBits, 0) };
BlockSizeValue = NonceSizeInBits;
//dummy iv value since it is accessed first in EncryptedXml.DecryptData
IV = new byte[NonceSizeInBits / 8];
}

public override ICryptoTransform CreateDecryptor(byte[] rgbKey, byte[] rgbIV)
{
return new AesGcmDecryptor(rgbKey, rgbIV, AuthenticationTagSizeInBits);
}

public override ICryptoTransform CreateEncryptor(byte[] rgbKey, byte[] rgbIV)
{
throw new NotImplementedException();
}

public override void GenerateIV()
{
throw new NotImplementedException();
}

public override void GenerateKey()
{
throw new NotImplementedException();
}
}
}
61 changes: 61 additions & 0 deletions Samples/SampleAesGcmStartupNetCore31/AesGcmDecryptor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
using System;
using System.Security.Cryptography;

namespace SampleAesGcmNetCore31
{
internal class AesGcmDecryptor : ICryptoTransform
{
private readonly byte[] key;
private readonly byte[] nonce;
private readonly int authenticationTagSizeInBits;

public AesGcmDecryptor(byte[] key, byte[] nonce, int authenticationTagSizeInBits)
{
this.key = key;
this.nonce = nonce;
this.authenticationTagSizeInBits = authenticationTagSizeInBits;
}

public bool CanReuseTransform => throw new NotImplementedException();

public bool CanTransformMultipleBlocks => throw new NotImplementedException();

public int InputBlockSize => throw new NotImplementedException();

public int OutputBlockSize => throw new NotImplementedException();

public void Dispose()
{
throw new NotImplementedException();
}

public int TransformBlock(byte[] inputBuffer, int inputOffset, int inputCount, byte[] outputBuffer, int outputOffset)
{
throw new NotImplementedException();
}

public byte[] TransformFinalBlock(byte[] inputBuffer, int inputOffset, int inputCount)
{
//inspired by https://stackoverflow.com/a/60891115

var tagSize = authenticationTagSizeInBits / 8;
var cipherSize = inputCount - tagSize;

// "The cipher text contains the IV first, followed by the encrypted octets and finally the Authentication tag."
// https://www.w3.org/TR/xmlenc-core1/#sec-AES-GCM
var encryptedData = inputBuffer.AsSpan().Slice(inputOffset, inputCount);
var tag = encryptedData.Slice(encryptedData.Length - tagSize);

var cipherBytes = encryptedData.Slice(0, cipherSize);

var plainBytes = cipherSize < 1024
? stackalloc byte[cipherSize]
: new byte[cipherSize];

using var aes = new AesGcm(key);
aes.Decrypt(nonce, cipherBytes, tag, plainBytes);

return plainBytes.ToArray();
}
}
}
26 changes: 26 additions & 0 deletions Samples/SampleAesGcmStartupNetCore31/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;

namespace SampleAesGcmNetCore31
{
public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}

public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
{
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:50565",
"sslPort": 0
}
},
"profiles": {
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"SampleAesGcmStartupNetCore31": {
"commandName": "Project",
"launchBrowser": true,
"applicationUrl": "http://localhost:5000",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}
7 changes: 7 additions & 0 deletions Samples/SampleAesGcmStartupNetCore31/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Adding AES-GCM support for SustainSys.Saml2

To add AES-GCM support for decrypting SAML messages, a SymmetricAlgorithm implementation must be provided and registered with `System.Security.Cryptography.CryptoConfig.AddAlgorithm()`

This sample project demonstrates how such implementation could be done in .Net Core 3.1.

See `AesGcmAlgorithm.cs` for the SymmetricAlgorithm implementation. It is a wrapper for the actual AES-GCM implementation. The used AES-GCM implementation is available in .Net Core 3.1 and .Net Standard 2.1. For .Net Core 2.x and .Net Framework, the some other implementation must be used (for examle, BouncyCastle).
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk.Web">

<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
</PropertyGroup>

</Project>
44 changes: 44 additions & 0 deletions Samples/SampleAesGcmStartupNetCore31/Startup.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using System.Security.Cryptography;

namespace SampleAesGcmNetCore31
{
public class Startup
{
// This method gets called by the runtime. Use this method to add services to the container.
// For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
public void ConfigureServices(IServiceCollection services)
{
CryptoConfig.AddAlgorithm(typeof(AesGcmAlgorithm), AesGcmAlgorithm.AesGcm128Identifier);
}

// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}

app.UseRouting();

app.UseEndpoints(endpoints =>
{
endpoints.MapGet("/", async context =>
{
var aesGcm = CryptoConfig.CreateFromName(AesGcmAlgorithm.AesGcm128Identifier);
await context.Response.WriteAsync(
$"Resolved '{AesGcmAlgorithm.AesGcm128Identifier}' to {aesGcm.GetType().FullName}");
});
});
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
}
}
10 changes: 10 additions & 0 deletions Samples/SampleAesGcmStartupNetCore31/appsettings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"AllowedHosts": "*"
}
7 changes: 7 additions & 0 deletions Sustainsys.Saml2.sln
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "config-elements", "config-e
docs\config-elements\sustainsys-saml2.rst = docs\config-elements\sustainsys-saml2.rst
EndProjectSection
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SampleAesGcmNetCore31", "Samples\SampleAesGcmStartupNetCore31\SampleAesGcmNetCore31.csproj", "{309367E0-32C7-4916-9287-7C8CF7D626DF}"
EndProject
Global
GlobalSection(SharedMSBuildProjectFiles) = preSolution
Tests\Tests.Shared\Tests.Shared.projitems*{82f84e61-1292-47cf-b0dc-59f26ec56c32}*SharedItemsImports = 13
Expand Down Expand Up @@ -184,6 +186,10 @@ Global
{A6DA495F-3AE8-482B-BC63-1F70BC752654}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A6DA495F-3AE8-482B-BC63-1F70BC752654}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A6DA495F-3AE8-482B-BC63-1F70BC752654}.Release|Any CPU.Build.0 = Release|Any CPU
{309367E0-32C7-4916-9287-7C8CF7D626DF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{309367E0-32C7-4916-9287-7C8CF7D626DF}.Debug|Any CPU.Build.0 = Debug|Any CPU
{309367E0-32C7-4916-9287-7C8CF7D626DF}.Release|Any CPU.ActiveCfg = Release|Any CPU
{309367E0-32C7-4916-9287-7C8CF7D626DF}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand All @@ -204,6 +210,7 @@ Global
{19ED65C5-A184-4AA8-9CBB-464E7433075A} = {09639770-8C2B-4D30-BBFC-4F6DD1A92C89}
{A6DA495F-3AE8-482B-BC63-1F70BC752654} = {09639770-8C2B-4D30-BBFC-4F6DD1A92C89}
{04FAC06E-5E04-4D87-96BE-254FEBAEA0DE} = {46971976-411C-4DD6-A45E-194E309BC94F}
{309367E0-32C7-4916-9287-7C8CF7D626DF} = {09639770-8C2B-4D30-BBFC-4F6DD1A92C89}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {A8812E38-A212-46DD-B51F-AB653F39E4ED}
Expand Down
26 changes: 26 additions & 0 deletions Sustainsys.Saml2/Internal/RSAEncryptedXml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,5 +41,31 @@ public override byte[] DecryptEncryptedKey(EncryptedKey encryptedKey)

return DecryptKey(encryptedKey.CipherData.CipherValue, privateKey, useOaep);
}

internal const string AesGcm128Identifier = "http://www.w3.org/2009/xmlenc11#aes128-gcm";

/// <summary>
/// AES-GCM Nonce size defined in https://www.w3.org/TR/xmlenc-core1/#sec-AES-GCM
/// </summary>
internal const int AesGcm128NonceSizeInBits = 96;

public override byte[] GetDecryptionIV(EncryptedData encryptedData, string symmetricAlgorithmUri)
{
//adapted from https://github.com/dotnet/runtime/blob/a5192d4963531579166d7f43df2a1ed44a96900f/src/libraries/System.Security.Cryptography.Xml/src/System/Security/Cryptography/Xml/EncryptedXml.cs#L267
if (symmetricAlgorithmUri == null && encryptedData.EncryptionMethod != null)
{
symmetricAlgorithmUri = encryptedData.EncryptionMethod.KeyAlgorithm;
}

if (symmetricAlgorithmUri == AesGcm128Identifier)
{
const int initBytesSize = AesGcm128NonceSizeInBits / 8;
var iv = new byte[initBytesSize];
var cipherValue = encryptedData.CipherData.CipherValue;
Buffer.BlockCopy(cipherValue, 0, iv, 0, iv.Length);
return iv;
}
return base.GetDecryptionIV(encryptedData, symmetricAlgorithmUri);
}
}
}
41 changes: 41 additions & 0 deletions Tests/Tests.Shared/Internal/RSAEncryptedXmlTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Security.Cryptography.Xml;
using System.Xml;
using FluentAssertions;
using Sustainsys.Saml2.Internal;
using System.Linq;

namespace Sustainsys.Saml2.Tests.Internal
{

[TestClass]
public class RSAEncryptedXmlTests
{
[TestMethod]
public void AesGcmNonceIsSupported()
{
var aesGcm128NonceSizeInBytes = RSAEncryptedXml.AesGcm128NonceSizeInBits / 8;
var dummyBytes = Enumerable.Range(42, aesGcm128NonceSizeInBytes + 1).Select(i => (byte)i).ToArray();
var expectedIv = Enumerable.Range(42, aesGcm128NonceSizeInBytes);
var dummyData = new EncryptedData();
dummyData.CipherData = new CipherData(dummyBytes);
var rex = new RSAEncryptedXml(new XmlDocument(), null );
var iv = rex.GetDecryptionIV(dummyData, RSAEncryptedXml.AesGcm128Identifier);
iv.Should().NotBeNull();
iv.Should().BeEquivalentTo(expectedIv);
}

[TestMethod]
public void NonAesGcmAlgorithmsAreHandledByBaseClass()
{
var dummyBytes = Enumerable.Range(17, 20).Select(i => (byte)i).ToArray();
var dummyData = new EncryptedData();
dummyData.CipherData = new CipherData(dummyBytes);
var rex = new RSAEncryptedXml(new XmlDocument(), null );
var iv = rex.GetDecryptionIV(dummyData, EncryptedXml.XmlEncAES256Url);
iv.Should().NotBeNull();
iv.Should().BeEquivalentTo(Enumerable.Range(17, 16));
}
}
}
1 change: 1 addition & 0 deletions Tests/Tests.Shared/Tests.Shared.projitems
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
<Compile Include="$(MSBuildThisFileDirectory)Internal\DateTimeHelperTests.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Internal\DelimitedStringTests.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Internal\EntityIdEqualityComparerTests.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Internal\RSAEncryptedXmlTests.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Internal\EnumeratorTests.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Internal\PathHelperTests.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Internal\QueryStringHelperTests.cs" />
Expand Down
6 changes: 3 additions & 3 deletions VersionInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,6 @@
// Patch Number
//

[assembly: AssemblyVersion("2.7.0")]
[assembly: AssemblyFileVersion("2.7.0")]
[assembly: AssemblyInformationalVersion("2.7.0")]
[assembly: AssemblyVersion("2.8.0")]
[assembly: AssemblyFileVersion("2.8.0")]
[assembly: AssemblyInformationalVersion("2.8.0")]
3 changes: 3 additions & 0 deletions nuget/ReleaseNotes.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
Version 2.8.0
* Enable adding support for AES-GCM (http://www.w3.org/2009/xmlenc11#aes128-gcm) in incoming SAML messages

Version 2.7.0
* Security Fix: Ensure received token is a bearer token.
* Compatibility flag to allow unsigned logout responses
Expand Down

0 comments on commit b3b4300

Please sign in to comment.