205 lines
8.2 KiB
C#
205 lines
8.2 KiB
C#
// Copyright (c) .NET Foundation. All rights reserved.
|
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
|
|
|
|
|
using System;
|
|
using System.IO;
|
|
using System.Runtime.InteropServices;
|
|
using System.Security.Cryptography.X509Certificates;
|
|
using Microsoft.AspNetCore.Testing.xunit;
|
|
using Xunit;
|
|
|
|
namespace Microsoft.AspNetCore.ApiAuthorization.IdentityServer.Configuration
|
|
{
|
|
public class SigningKeysLoaderTests
|
|
{
|
|
// We need to cast the underlying int value of the EphemeralKeySet to X509KeyStorageFlags
|
|
// due to the fact that is not part of .NET Standard. This value is only used with non-windows
|
|
// platforms (all .NET Core) for which the value is defined on the underlying platform.
|
|
private const X509KeyStorageFlags UnsafeEphemeralKeySet = (X509KeyStorageFlags)32;
|
|
private static readonly X509KeyStorageFlags DefaultFlags = RuntimeInformation.IsOSPlatform(OSPlatform.Linux) ?
|
|
UnsafeEphemeralKeySet : (RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? X509KeyStorageFlags.PersistKeySet :
|
|
X509KeyStorageFlags.DefaultKeySet);
|
|
|
|
[Fact]
|
|
public void LoadFromFile_ThrowsIfFileDoesNotExist()
|
|
{
|
|
// Arrange, Act & Assert
|
|
var exception = Assert.Throws<InvalidOperationException>(() => SigningKeysLoader.LoadFromFile("./nonexisting.pfx", "", DefaultFlags));
|
|
Assert.Equal($"There was an error loading the certificate. The file './nonexisting.pfx' was not found.", exception.Message);
|
|
}
|
|
|
|
[Fact]
|
|
public void LoadFromFile_ThrowsIfPasswordIsNull()
|
|
{
|
|
// Arrange, Act & Assert
|
|
var exception = Assert.Throws<InvalidOperationException>(() => SigningKeysLoader.LoadFromFile("test.pfx", null, DefaultFlags));
|
|
Assert.Equal("There was an error loading the certificate. No password was provided.", exception.Message);
|
|
}
|
|
|
|
[Fact]
|
|
public void LoadFromFile_ThrowsIfPasswordIsIncorrect()
|
|
{
|
|
// Arrange, Act & Assert
|
|
var exception = Assert.Throws<InvalidOperationException>(() => SigningKeysLoader.LoadFromFile("test.pfx", "incorrect", DefaultFlags));
|
|
Assert.Equal(
|
|
$"There was an error loading the certificate. Either the password is incorrect or the process does not have permisions to store the key in the Keyset '{DefaultFlags}'",
|
|
exception.Message);
|
|
}
|
|
|
|
[Fact]
|
|
public static void LoadFromStoreCert_ThrowsIfThereIsNoCertificateAvailable()
|
|
{
|
|
// Arrange
|
|
var time = new DateTimeOffset(2018, 09, 25, 12, 0, 0, TimeSpan.Zero);
|
|
|
|
// Act & Assert
|
|
var exception = Assert.Throws<InvalidOperationException>(() => SigningKeysLoader.LoadFromStoreCert("Invalid", "My", StoreLocation.CurrentUser, time));
|
|
Assert.Equal("Couldn't find a valid certificate with subject 'Invalid' on the 'CurrentUser\\My'", exception.Message);
|
|
}
|
|
|
|
[Fact]
|
|
public static void LoadFromStoreCert_SkipsCertificatesNotYetValid()
|
|
{
|
|
try
|
|
{
|
|
SetupCertificates("./current.pfx", "./future.pfx");
|
|
// Arrange
|
|
var time = new DateTimeOffset(2018, 10, 29, 12, 0, 0, TimeSpan.Zero);
|
|
|
|
// Act
|
|
var certificate = SigningKeysLoader.LoadFromStoreCert("CN=SigningKeysLoaderTest", "My", StoreLocation.CurrentUser, time);
|
|
|
|
// Assert
|
|
Assert.NotNull(certificate);
|
|
Assert.Equal("C54CD513088C23EC2AFD256874CC6C0F81EA9D5E", certificate.Thumbprint);
|
|
}
|
|
finally
|
|
{
|
|
CleanupCertificates();
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public static void LoadFromStoreCert_PrefersCertificatesCloserToExpirationDate()
|
|
{
|
|
try
|
|
{
|
|
SetupCertificates("./current.pfx", "./future.pfx");
|
|
// Arrange
|
|
var time = new DateTimeOffset(2020, 10, 29, 12, 0, 0, TimeSpan.Zero);
|
|
|
|
// Act
|
|
var certificate = SigningKeysLoader.LoadFromStoreCert("CN=SigningKeysLoaderTest", "My", StoreLocation.CurrentUser, time);
|
|
|
|
// Assert
|
|
Assert.NotNull(certificate);
|
|
Assert.Equal("C54CD513088C23EC2AFD256874CC6C0F81EA9D5E", certificate.Thumbprint);
|
|
}
|
|
finally
|
|
{
|
|
CleanupCertificates();
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public static void LoadFromStoreCert_SkipsExpiredCertificates()
|
|
{
|
|
try
|
|
{
|
|
SetupCertificates("./expired.pfx", "./current.pfx", "./future.pfx");
|
|
// Arrange
|
|
var time = new DateTimeOffset(2024, 01, 01, 12, 0, 0, TimeSpan.Zero);
|
|
|
|
// Act
|
|
var certificate = SigningKeysLoader.LoadFromStoreCert("CN=SigningKeysLoaderTest", "My", StoreLocation.CurrentUser, time);
|
|
|
|
// Assert
|
|
Assert.NotNull(certificate);
|
|
Assert.Equal("35840DD366107B89D2885A6B4F42CCBBAE6BA8E3", certificate.Thumbprint);
|
|
}
|
|
finally
|
|
{
|
|
CleanupCertificates();
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public static void LoadDevelopment_ThrowsIfKeyDoesNotExist()
|
|
{
|
|
// Act & Assert
|
|
var exception = Assert.Throws<InvalidOperationException>(() => SigningKeysLoader.LoadDevelopment("c:/inexistent.json", createIfMissing: false));
|
|
Assert.Equal("Couldn't find the file 'c:/inexistent.json' and creation of a development key was not requested.", exception.Message);
|
|
}
|
|
|
|
[ConditionalFact]
|
|
[FrameworkSkipCondition(RuntimeFrameworks.CLR)]
|
|
public static void LoadDevelopment_CreatesKeyIfItDoesNotExist()
|
|
{
|
|
// Arrange
|
|
var path = "./tempkeyfolder/tempkey.json";
|
|
if (File.Exists(path))
|
|
{
|
|
File.Delete(path);
|
|
}
|
|
|
|
// Act
|
|
var key = SigningKeysLoader.LoadDevelopment(path, createIfMissing: true);
|
|
|
|
// Assert
|
|
Assert.NotNull(key);
|
|
Assert.True(File.Exists(path));
|
|
}
|
|
|
|
[ConditionalFact]
|
|
[FrameworkSkipCondition(RuntimeFrameworks.CLR)]
|
|
public static void LoadDevelopment_ReusesKeyIfExists()
|
|
{
|
|
// Arrange
|
|
var path = "./tempkeyfolder/existing.json";
|
|
if (File.Exists(path))
|
|
{
|
|
File.Delete(path);
|
|
}
|
|
var existingKey = SigningKeysLoader.LoadDevelopment(path, createIfMissing: true);
|
|
var existingParameters = existingKey.ExportParameters(includePrivateParameters: true);
|
|
|
|
// Act
|
|
var currentKey = SigningKeysLoader.LoadDevelopment(path, createIfMissing: true);
|
|
var currentParameters = currentKey.ExportParameters(includePrivateParameters: true);
|
|
|
|
// Assert
|
|
Assert.NotNull(currentKey);
|
|
Assert.Equal(existingParameters.P, currentParameters.P);
|
|
Assert.Equal(existingParameters.Q, currentParameters.Q);
|
|
}
|
|
|
|
private static void CleanupCertificates()
|
|
{
|
|
using (var store = new X509Store(StoreName.My, StoreLocation.CurrentUser))
|
|
{
|
|
store.Open(OpenFlags.ReadWrite);
|
|
store.RemoveRange(store.Certificates.Find(X509FindType.FindBySubjectName, "CN=SigningKeysLoaderTest", validOnly: false));
|
|
store.Close();
|
|
}
|
|
}
|
|
|
|
private static void SetupCertificates(params string[] certificateFiles)
|
|
{
|
|
using (var store = new X509Store(StoreName.My, StoreLocation.CurrentUser))
|
|
{
|
|
store.Open(OpenFlags.ReadWrite);
|
|
foreach (var certificate in certificateFiles)
|
|
{
|
|
var cert = new X509Certificate2(certificate, "aspnetcore", DefaultFlags);
|
|
if (!(store.Certificates.Find(X509FindType.FindByThumbprint, cert.Thumbprint, validOnly: false).Count > 0))
|
|
{
|
|
store.Add(cert);
|
|
}
|
|
}
|
|
store.Close();
|
|
}
|
|
}
|
|
}
|
|
}
|