Skip to content

Commit c1122ea

Browse files
committed
Certificate rollover for decryption certificate
1 parent a1816da commit c1122ea

File tree

17 files changed

+83
-50
lines changed

17 files changed

+83
-50
lines changed

src/ITfoxtec.Identity.Saml2.Mvc/ITfoxtec.Identity.Saml2.Mvc.csproj

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,10 @@ Support the Danish NemLog-in 2 / OIOSAML 2 and NemLog-in 3 / OIOSAML 3.</Descrip
2626
<PackageTags>SAML SAML 2.0 SAML2.0 SAML2 SAML 2 SAML-P SAMLP SSO Identity Provider (IdP) and Relying Party (RP) Authentication Metadata OIOSAML OIOSAML 2 OIOSAML 3 NemLogin NemLog-in 2 NemLog-in 3 ASP.NET MVC</PackageTags>
2727
<NeutralLanguage>en-US</NeutralLanguage>
2828
<PackageIconUrl>https://itfoxtec.com/favicon.ico</PackageIconUrl>
29-
<AssemblyVersion>4.10.0.0</AssemblyVersion>
30-
<FileVersion>4.10.0.0</FileVersion>
29+
<AssemblyVersion>4.10.1.0</AssemblyVersion>
30+
<FileVersion>4.10.1.0</FileVersion>
3131
<Copyright>Copyright © 2023</Copyright>
32-
<Version>4.10.0-beta1</Version>
32+
<Version>4.10.1-beta1</Version>
3333
<SignAssembly>true</SignAssembly>
3434
<AssemblyOriginatorKeyFile>ITfoxtec.SAML2.snk</AssemblyOriginatorKeyFile>
3535
<DelaySign>false</DelaySign>

src/ITfoxtec.Identity.Saml2.MvcCore/ITfoxtec.Identity.Saml2.MvcCore.csproj

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,10 @@ Support the Danish NemLog-in 2 / OIOSAML 2 and NemLog-in 3 / OIOSAML 3.</Descrip
3030
<PackageTags>SAML SAML 2.0 SAML2.0 SAML2 SAML 2 SAML-P SAMLP SSO Identity Provider (IdP) Relying Party (RP) Authentication Metadata OIOSAML OIOSAML 2 OIOSAML 3 NemLogin NemLog-in 2 NemLog-in 3 ASP.NET MVC Core</PackageTags>
3131
<NeutralLanguage>en-US</NeutralLanguage>
3232
<PackageIconUrl>https://itfoxtec.com/favicon.ico</PackageIconUrl>
33-
<AssemblyVersion>4.10.0.0</AssemblyVersion>
34-
<FileVersion>4.10.0.0</FileVersion>
33+
<AssemblyVersion>4.10.1.0</AssemblyVersion>
34+
<FileVersion>4.10.1.0</FileVersion>
3535
<Copyright>Copyright © 2023</Copyright>
36-
<Version>4.10.0-beta1</Version>
36+
<Version>4.10.1-beta1</Version>
3737
<SignAssembly>true</SignAssembly>
3838
<AssemblyOriginatorKeyFile>ITfoxtec.SAML2.snk</AssemblyOriginatorKeyFile>
3939
<DelaySign>false</DelaySign>

src/ITfoxtec.Identity.Saml2/Configuration/Saml2Configuration.cs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using System.ServiceModel.Security;
66
using System.IdentityModel.Selectors;
77
using System.Security.Cryptography.Xml;
8+
using System.Linq;
89
#if NETFULL
910
using System.IdentityModel.Configuration;
1011
#else
@@ -32,7 +33,13 @@ public class Saml2Configuration
3233
public string XmlCanonicalizationMethod { get; set; } = SignedXml.XmlDsigExcC14NTransformUrl;
3334

3435
public X509Certificate2 SigningCertificate { get; set; }
35-
public X509Certificate2 DecryptionCertificate { get; set; }
36+
[Obsolete("DecryptionCertificate are obsolete to support multiple decryption certificates. Use DecryptionCertificates instead.")]
37+
public X509Certificate2 DecryptionCertificate
38+
{
39+
get { return DecryptionCertificates?.FirstOrDefault(); }
40+
set { DecryptionCertificates = new List<X509Certificate2> { value }; }
41+
}
42+
public List<X509Certificate2> DecryptionCertificates { get; set; } = new List<X509Certificate2>();
3643
/// <summary>
3744
/// If set the authn responses created by the library is encrypt.
3845
/// </summary>

src/ITfoxtec.Identity.Saml2/Configuration/Saml2IdentityConfiguration.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System.ServiceModel.Security;
2+
using System.Collections.Generic;
23
#if NETFULL
34
using ITfoxtec.Identity.Saml2.Tokens;
45
using System;
@@ -27,7 +28,7 @@ public class Saml2IdentityConfiguration :
2728
#if !NETFULL
2829
public X509CertificateValidator CertificateValidator { get; set; }
2930

30-
public X509Certificate2 DecryptionCertificate { get; set; }
31+
public IEnumerable<X509Certificate2> DecryptionCertificates { get; set; }
3132
#endif
3233

3334
public static Saml2IdentityConfiguration GetIdentityConfiguration(Saml2Configuration config)
@@ -75,7 +76,7 @@ public static Saml2IdentityConfiguration GetIdentityConfiguration(Saml2Configura
7576
CertificateValidationMode = config.CertificateValidationMode,
7677
RevocationMode = config.RevocationMode,
7778
};
78-
configuration.DecryptionCertificate = config.DecryptionCertificate;
79+
configuration.DecryptionCertificates = config.DecryptionCertificates;
7980
SetCustomCertificateValidator(configuration, config);
8081
#endif
8182

src/ITfoxtec.Identity.Saml2/ITfoxtec.Identity.Saml2.csproj

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,10 @@ Support the Danish NemLog-in 2 / OIOSAML 2 and NemLog-in 3 / OIOSAML 3.</Descrip
3131
<PackageTags>SAML SAML 2.0 SAML2.0 SAML2 SAML 2 SAML-P SAMLP SSO Identity Provider (IdP) Relying Party (RP) Authentication Metadata OIOSAML OIOSAML 2 OIOSAML 3 NemLogin NemLog-in 2 NemLog-in 3</PackageTags>
3232
<NeutralLanguage>en-US</NeutralLanguage>
3333
<PackageIconUrl>https://itfoxtec.com/favicon.ico</PackageIconUrl>
34-
<AssemblyVersion>4.10.0.0</AssemblyVersion>
35-
<FileVersion>4.10.0.0</FileVersion>
34+
<AssemblyVersion>4.10.1.0</AssemblyVersion>
35+
<FileVersion>4.10.1.0</FileVersion>
3636
<Copyright>Copyright © 2023</Copyright>
37-
<Version>4.10.0-beta1</Version>
37+
<Version>4.10.1-beta1</Version>
3838
<SignAssembly>true</SignAssembly>
3939
<AssemblyOriginatorKeyFile>ITfoxtec.SAML2.snk</AssemblyOriginatorKeyFile>
4040
<DelaySign>false</DelaySign>

src/ITfoxtec.Identity.Saml2/Request/Saml2AuthnResponse.cs

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ public class Saml2AuthnResponse : Saml2Response
2626
{
2727
public override string ElementName => Schemas.Saml2Constants.Message.AuthnResponse;
2828

29-
internal X509Certificate2 DecryptionCertificate { get; private set; }
29+
internal IEnumerable<X509Certificate2> DecryptionCertificates { get; private set; }
3030
internal X509Certificate2 EncryptionCertificate { get; private set; }
3131

3232
/// <summary>
@@ -60,12 +60,12 @@ public Saml2AuthnResponse(Saml2Configuration config) : base(config)
6060

6161
Destination = config.SingleSignOnDestination;
6262

63-
if (config.DecryptionCertificate != null)
63+
if (config.DecryptionCertificates?.Count() > 0)
6464
{
65-
DecryptionCertificate = config.DecryptionCertificate;
66-
if (config.DecryptionCertificate.GetSamlRSAPrivateKey() == null)
65+
DecryptionCertificates = config.DecryptionCertificates.Where(c => c.GetSamlRSAPrivateKey() != null);
66+
if (!(DecryptionCertificates?.Count() > 0))
6767
{
68-
throw new ArgumentException("No RSA Private Key present in Decryption Certificate or missing private key read credentials.");
68+
throw new ArgumentException("No RSA Private Key present in Decryption Certificates or missing private key read credentials.");
6969
}
7070
}
7171
if(config.EncryptionCertificate != null)
@@ -353,9 +353,23 @@ private ClaimsIdentity ReadClaimsIdentity(string tokenString, bool detectReplaye
353353

354354
protected override void DecryptMessage()
355355
{
356-
if (DecryptionCertificate != null)
356+
if (DecryptionCertificates?.Count() > 0)
357357
{
358-
new Saml2EncryptedXml(XmlDocument, DecryptionCertificate.GetSamlRSAPrivateKey()).DecryptDocument();
358+
var exceptions = new List<Exception>();
359+
foreach(var decryptionCertificate in DecryptionCertificates)
360+
{
361+
try
362+
{
363+
new Saml2EncryptedXml(XmlDocument, decryptionCertificate.GetSamlRSAPrivateKey()).DecryptDocument();
364+
// Stop the look when the message successfully decrypted.
365+
return;
366+
}
367+
catch (Exception e)
368+
{
369+
exceptions.Add(e);
370+
}
371+
}
372+
throw new AggregateException("Failed to decrypt message", exceptions);
359373
#if DEBUG
360374
Debug.WriteLine("Saml2P (Decrypted): " + XmlDocument.OuterXml);
361375
#endif

src/ITfoxtec.Identity.Saml2/Tokens/Saml2ResponseSecurityTokenHandler.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ public static Saml2ResponseSecurityTokenHandler GetSaml2SecurityTokenHandler(Sam
4646
handler.SamlSecurityTokenRequirement.NameClaimType = ClaimTypes.NameIdentifier;
4747
#else
4848
handler.TokenValidationParameters = configuration;
49-
handler.Serializer = new Saml2TokenSerializer(configuration.DecryptionCertificate);
49+
handler.Serializer = new Saml2TokenSerializer(configuration.DecryptionCertificates);
5050
#endif
5151
return handler;
5252
}

src/ITfoxtec.Identity.Saml2/Tokens/Saml2TokenSerializer.cs

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,30 +3,44 @@
33
using Microsoft.IdentityModel.Tokens.Saml2;
44
using Microsoft.IdentityModel.Xml;
55
using System;
6+
using System.Collections.Generic;
7+
using System.Linq;
68
using System.Security.Cryptography.X509Certificates;
79
using System.Xml;
810

911
namespace ITfoxtec.Identity.Saml2.Tokens
1012
{
1113
internal class Saml2TokenSerializer : Saml2Serializer
1214
{
13-
private readonly X509Certificate2 decryptionCertificate;
15+
private readonly IEnumerable<X509Certificate2> decryptionCertificates;
1416

15-
public Saml2TokenSerializer(X509Certificate2 decryptionCertificate) : base()
17+
public Saml2TokenSerializer(IEnumerable<X509Certificate2> decryptionCertificates) : base()
1618
{
17-
this.decryptionCertificate = decryptionCertificate;
19+
this.decryptionCertificates = decryptionCertificates;
1820
}
1921

2022
protected override Saml2NameIdentifier ReadEncryptedId(XmlDictionaryReader reader)
2123
{
22-
if (decryptionCertificate != null)
24+
if (decryptionCertificates?.Count() > 0)
2325
{
2426
var xmlDocument = reader.ReadOuterXml().ToXmlDocument();
2527

26-
new Saml2EncryptedXml(xmlDocument, decryptionCertificate.GetSamlRSAPrivateKey()).DecryptDocument();
27-
28-
var decryptedReader = XmlDictionaryReader.CreateDictionaryReader(new XmlNodeReader(xmlDocument.DocumentElement.FirstChild));
29-
return ReadNameIdentifier(decryptedReader, null);
28+
var exceptions = new List<Exception>();
29+
foreach (var decryptionCertificate in decryptionCertificates)
30+
{
31+
try
32+
{
33+
new Saml2EncryptedXml(xmlDocument, decryptionCertificate.GetSamlRSAPrivateKey()).DecryptDocument();
34+
// Stop the look when the message successfully decrypted.
35+
var decryptedReader = XmlDictionaryReader.CreateDictionaryReader(new XmlNodeReader(xmlDocument.DocumentElement.FirstChild));
36+
return ReadNameIdentifier(decryptedReader, null);
37+
}
38+
catch (Exception e)
39+
{
40+
exceptions.Add(e);
41+
}
42+
}
43+
throw new AggregateException("Failed to decrypt message", exceptions);
3044
}
3145
else
3246
{

test/TestIdPCore/Controllers/AuthController.cs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -243,8 +243,11 @@ private Saml2Configuration GetRpSaml2Configuration(RelyingParty relyingParty = n
243243

244244
if (relyingParty != null)
245245
{
246-
rpConfig.SignatureValidationCertificates.Add(relyingParty.SignatureValidationCertificate);
247-
rpConfig.EncryptionCertificate = relyingParty.EncryptionCertificate;
246+
rpConfig.SignatureValidationCertificates.AddRange(relyingParty.SignatureValidationCertificates);
247+
if (relyingParty.EecryptionCertificates?.Count() > 0)
248+
{
249+
rpConfig.EncryptionCertificate = relyingParty.EecryptionCertificates.LastOrDefault();
250+
}
248251
}
249252

250253
return rpConfig;
@@ -273,7 +276,8 @@ private async Task LoadRelyingPartyAsync(RelyingParty rp, CancellationTokenSourc
273276
rp.AcsDestination = entityDescriptor.SPSsoDescriptor.AssertionConsumerServices.Where(a => a.IsDefault).OrderBy(a => a.Index).First().Location;
274277
var singleLogoutService = entityDescriptor.SPSsoDescriptor.SingleLogoutServices.First();
275278
rp.SingleLogoutDestination = singleLogoutService.ResponseLocation ?? singleLogoutService.Location;
276-
rp.SignatureValidationCertificate = entityDescriptor.SPSsoDescriptor.SigningCertificates.First();
279+
rp.SignatureValidationCertificates = entityDescriptor.SPSsoDescriptor.SigningCertificates;
280+
rp.EecryptionCertificates = entityDescriptor.SPSsoDescriptor.EncryptionCertificates;
277281
}
278282
else
279283
{

test/TestIdPCore/Controllers/MetadataController.cs

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,7 @@ public IActionResult Index()
3131
{
3232
config.SigningCertificate
3333
},
34-
//EncryptionCertificates = new X509Certificate2[]
35-
//{
36-
// config.DecryptionCertificate
37-
//},
34+
//EncryptionCertificates = config.DecryptionCertificates,
3835
SingleSignOnServices = new SingleSignOnService[]
3936
{
4037
new SingleSignOnService { Binding = ProtocolBindings.HttpRedirect, Location = config.SingleSignOnDestination }

0 commit comments

Comments
 (0)