Search code examples
macossecurityasp.net-core.net-coreasp.net-identity

DotNet Core 'CspParameters' requires Windows Cryptographic API (CAPI) ONLY when running a unit test


I have RSA256 signing code running in an aspnet core application on a mac just fine.

In my unit tests though, I am getting the error.

'CspParameters' requires Windows Cryptographic API (CAPI) ONLY when running a unit test

Is there something aspnet'ish that I need to mock or provide in a unit test to enable this to work?

public class SessionIdSecureDataFormat : ISessionIdSecureDataFormat
{
    private readonly IEnumerable<SecurityKey> _issuerSigningKeys;
    private readonly SecurityKey _privateKey;
    private readonly string _issuer;
    private readonly string _audience;

    public SessionIdSecureDataFormat(IEnumerable<SecurityKey> issuerSigningKeys, SecurityKey privateKey,
        string issuer, string audience)
    {
        _issuerSigningKeys = issuerSigningKeys;
        _privateKey = privateKey;
        _issuer = issuer;
        _audience = audience;
    }

    public string Protect(string data)
    {
        if (data == null)
        {
            return null;
        }

        var securityTokenDescriptor = new SecurityTokenDescriptor
        {
            SigningCredentials = new SigningCredentials(_privateKey, "RS256"),
            Issuer = _issuer,
            Audience = _audience,
        };
        var handler = new JwtSecurityTokenHandler();
        var jwt = handler.CreateJwtSecurityToken(securityTokenDescriptor);
        jwt.Payload.AddClaim(new Claim(JwtRegisteredClaimNames.Jti, data));
        return handler.WriteToken(jwt);
    }

    public string Protect(string data, string purpose)
    {
        return this.Protect(data);
    }

    public string Unprotect(string protectedText)
    {
        if (protectedText == null)
        {
            return null;
        }

        var handler = new JwtSecurityTokenHandler();
        var validationContext = new TokenValidationParameters
        {
            RequireExpirationTime = false,
            ValidateIssuerSigningKey = true,
            ValidateLifetime = true,
            IssuerSigningKeys = _issuerSigningKeys,
            ValidIssuer = _issuer,
            ValidAudience = _audience
        };
        handler.ValidateToken(protectedText, validationContext, out var jwt);
        return jwt.Id;
    }

    public string Unprotect(string protectedText, string purpose)
    {
        //TODO - It's not clear if we are supposed to check if the purpose matches our declared purpose. No documentation.
        return this.Unprotect(protectedText);
    }
}

The setup for the unit test, where Protect is failing.

public class SessionJwtDataProtectorTests
{
        private readonly byte[] _encodedJwt;
        private readonly byte[] _sessionIdOriginal;
        private readonly SessionJwtDataProtector _sessionJwtDataProtector;
        private readonly Mock<IServiceProvider> _provider = new Mock<IServiceProvider>();

        public SessionJwtDataProtectorTests()
        {
            var sessionIdSecureDataFormat = CreateSessionIdSecureDataFormat();
            _provider.Setup(p => p.GetService(typeof(ISessionJwtDataProtector)))
                .Returns(new SessionJwtDataProtector(_provider.Object, sessionIdSecureDataFormat));
            _sessionJwtDataProtector = new SessionJwtDataProtector(_provider.Object, sessionIdSecureDataFormat);
            var sessionId = Guid.NewGuid().ToString();
            _sessionIdOriginal = Encoding.UTF8.GetBytes(sessionId);
            _encodedJwt = _sessionJwtDataProtector.Protect(_sessionIdOriginal);
        }

    private static SessionIdSecureDataFormat CreateSessionIdSecureDataFormat()
    {
        var issuerKeys = new List<RsaSecurityKey>();
        foreach (var pemKey in StaticSessionPemKeys.ISSUER_KEYS)
        {
            RSA rsa = RSAHelper.PublicKeyFromPemString(pemKey.Pem);
            issuerKeys.Add(new RsaSecurityKey(rsa) {KeyId = pemKey.KeyId});
        }

        RSA privateKeyProvider = RSAHelper.PrivateKeyFromPemString(StaticSessionPemKeys.PRIVATE_KEY.Pem);
        var privateKey = new RsaSecurityKey(privateKeyProvider)
            {KeyId = StaticSessionPemKeys.PRIVATE_KEY.KeyId};
        return new SessionIdSecureDataFormat(issuerKeys, privateKey, "ABC", "ABC");
    }
}

Solution

  • The problem appears to be in a dependency on <PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="5.3.0" />

    See https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/issues/1038

    I resolved this issue by removing this reference and instead referencing <PackageReference Include="Microsoft.AspNetCore.App" Version="2.1.1"/>