diff --git a/src/Runner.Listener/Configuration/ConfigurationManager.cs b/src/Runner.Listener/Configuration/ConfigurationManager.cs
index 8e499adc7..796fa2deb 100644
--- a/src/Runner.Listener/Configuration/ConfigurationManager.cs
+++ b/src/Runner.Listener/Configuration/ConfigurationManager.cs
@@ -263,6 +263,7 @@ namespace GitHub.Runner.Listener.Configuration
{
{ "clientId", agent.Authorization.ClientId.ToString("D") },
{ "authorizationUrl", agent.Authorization.AuthorizationUrl.AbsoluteUri },
+ { "requireFipsCryptography", agent.Properties.GetValue("RequireFipsCryptography", false).ToString() }
},
};
diff --git a/src/Runner.Listener/Configuration/IRSAKeyManager.cs b/src/Runner.Listener/Configuration/IRSAKeyManager.cs
index 53fcf8170..65e9b4ea0 100644
--- a/src/Runner.Listener/Configuration/IRSAKeyManager.cs
+++ b/src/Runner.Listener/Configuration/IRSAKeyManager.cs
@@ -20,7 +20,7 @@ namespace GitHub.Runner.Listener.Configuration
/// key is returned to the caller.
///
/// An RSACryptoServiceProvider instance representing the key for the runner
- RSACryptoServiceProvider CreateKey();
+ RSA CreateKey();
///
/// Deletes the RSA key managed by the key manager.
@@ -32,7 +32,7 @@ namespace GitHub.Runner.Listener.Configuration
///
/// An RSACryptoServiceProvider instance representing the key for the runner
/// No key exists in the store
- RSACryptoServiceProvider GetKey();
+ RSA GetKey();
}
// Newtonsoft 10 is not working properly with dotnet RSAParameters class
diff --git a/src/Runner.Listener/Configuration/OAuthCredential.cs b/src/Runner.Listener/Configuration/OAuthCredential.cs
index a7162aafe..a0d2042b9 100644
--- a/src/Runner.Listener/Configuration/OAuthCredential.cs
+++ b/src/Runner.Listener/Configuration/OAuthCredential.cs
@@ -36,7 +36,7 @@ namespace GitHub.Runner.Listener.Configuration
// We expect the key to be in the machine store at this point. Configuration should have set all of
// this up correctly so we can use the key to generate access tokens.
var keyManager = context.GetService();
- var signingCredentials = VssSigningCredentials.Create(() => keyManager.GetKey());
+ var signingCredentials = VssSigningCredentials.Create(() => keyManager.GetKey(), StringUtil.ConvertToBoolean(CredentialData.Data.GetValueOrDefault("requireFipsCryptography"), false));
var clientCredential = new VssOAuthJwtBearerClientCredential(clientId, authorizationUrl, signingCredentials);
var agentCredential = new VssOAuthCredential(new Uri(oauthEndpointUrl, UriKind.Absolute), VssOAuthGrant.ClientCredentials, clientCredential);
diff --git a/src/Runner.Listener/Configuration/RSAEncryptedFileKeyManager.cs b/src/Runner.Listener/Configuration/RSAEncryptedFileKeyManager.cs
index 8401ac09d..239daecdf 100644
--- a/src/Runner.Listener/Configuration/RSAEncryptedFileKeyManager.cs
+++ b/src/Runner.Listener/Configuration/RSAEncryptedFileKeyManager.cs
@@ -13,14 +13,14 @@ namespace GitHub.Runner.Listener.Configuration
private string _keyFile;
private IHostContext _context;
- public RSACryptoServiceProvider CreateKey()
+ public RSA CreateKey()
{
- RSACryptoServiceProvider rsa = null;
+ RSA rsa = null;
if (!File.Exists(_keyFile))
{
Trace.Info("Creating new RSA key using 2048-bit key length");
- rsa = new RSACryptoServiceProvider(2048);
+ rsa = RSA.Create(2048);
// Now write the parameters to disk
SaveParameters(rsa.ExportParameters(true));
@@ -30,7 +30,7 @@ namespace GitHub.Runner.Listener.Configuration
{
Trace.Info("Found existing RSA key parameters file {0}", _keyFile);
- rsa = new RSACryptoServiceProvider();
+ rsa = RSA.Create();
rsa.ImportParameters(LoadParameters());
}
@@ -46,7 +46,7 @@ namespace GitHub.Runner.Listener.Configuration
}
}
- public RSACryptoServiceProvider GetKey()
+ public RSA GetKey()
{
if (!File.Exists(_keyFile))
{
@@ -55,7 +55,7 @@ namespace GitHub.Runner.Listener.Configuration
Trace.Info("Loading RSA key parameters from file {0}", _keyFile);
- var rsa = new RSACryptoServiceProvider();
+ var rsa = RSA.Create();
rsa.ImportParameters(LoadParameters());
return rsa;
}
diff --git a/src/Runner.Listener/Configuration/RSAFileKeyManager.cs b/src/Runner.Listener/Configuration/RSAFileKeyManager.cs
index 37406b1a2..3e5ed40a0 100644
--- a/src/Runner.Listener/Configuration/RSAFileKeyManager.cs
+++ b/src/Runner.Listener/Configuration/RSAFileKeyManager.cs
@@ -14,14 +14,14 @@ namespace GitHub.Runner.Listener.Configuration
private string _keyFile;
private IHostContext _context;
- public RSACryptoServiceProvider CreateKey()
+ public RSA CreateKey()
{
- RSACryptoServiceProvider rsa = null;
+ RSA rsa = null;
if (!File.Exists(_keyFile))
{
Trace.Info("Creating new RSA key using 2048-bit key length");
- rsa = new RSACryptoServiceProvider(2048);
+ rsa = RSA.Create(2048);
// Now write the parameters to disk
IOUtil.SaveObject(new RSAParametersSerializable(rsa.ExportParameters(true)), _keyFile);
@@ -54,7 +54,7 @@ namespace GitHub.Runner.Listener.Configuration
{
Trace.Info("Found existing RSA key parameters file {0}", _keyFile);
- rsa = new RSACryptoServiceProvider();
+ rsa = RSA.Create();
rsa.ImportParameters(IOUtil.LoadObject(_keyFile).RSAParameters);
}
@@ -70,7 +70,7 @@ namespace GitHub.Runner.Listener.Configuration
}
}
- public RSACryptoServiceProvider GetKey()
+ public RSA GetKey()
{
if (!File.Exists(_keyFile))
{
@@ -80,7 +80,7 @@ namespace GitHub.Runner.Listener.Configuration
Trace.Info("Loading RSA key parameters from file {0}", _keyFile);
var parameters = IOUtil.LoadObject(_keyFile).RSAParameters;
- var rsa = new RSACryptoServiceProvider();
+ var rsa = RSA.Create();
rsa.ImportParameters(parameters);
return rsa;
}
diff --git a/src/Runner.Listener/MessageListener.cs b/src/Runner.Listener/MessageListener.cs
index 0ad22e87d..71e5e43f5 100644
--- a/src/Runner.Listener/MessageListener.cs
+++ b/src/Runner.Listener/MessageListener.cs
@@ -319,7 +319,8 @@ namespace GitHub.Runner.Listener
var keyManager = HostContext.GetService();
using (var rsa = keyManager.GetKey())
{
- return aes.CreateDecryptor(rsa.Decrypt(_session.EncryptionKey.Value, RSAEncryptionPadding.OaepSHA1), message.IV);
+ var padding = _session.UseFipsEncryption ? RSAEncryptionPadding.OaepSHA256 : RSAEncryptionPadding.OaepSHA1;
+ return aes.CreateDecryptor(rsa.Decrypt(_session.EncryptionKey.Value, padding), message.IV);
}
}
else
diff --git a/src/Sdk/DTWebApi/WebApi/TaskAgentSession.cs b/src/Sdk/DTWebApi/WebApi/TaskAgentSession.cs
index a0835d2a1..4ddbac8f1 100644
--- a/src/Sdk/DTWebApi/WebApi/TaskAgentSession.cs
+++ b/src/Sdk/DTWebApi/WebApi/TaskAgentSession.cs
@@ -65,5 +65,15 @@ namespace GitHub.DistributedTask.WebApi
get;
set;
}
+
+ ///
+ /// Gets or sets whether to use FIPS compliant encryption scheme for job message key
+ ///
+ [DataMember]
+ public bool UseFipsEncryption
+ {
+ get;
+ set;
+ }
}
}
diff --git a/src/Sdk/WebApi/WebApi/Jwt/JsonWebTokenUtilities.cs b/src/Sdk/WebApi/WebApi/Jwt/JsonWebTokenUtilities.cs
index 5287dbf65..8f1c6c699 100644
--- a/src/Sdk/WebApi/WebApi/Jwt/JsonWebTokenUtilities.cs
+++ b/src/Sdk/WebApi/WebApi/Jwt/JsonWebTokenUtilities.cs
@@ -130,55 +130,6 @@ namespace GitHub.Services.WebApi.Jwt
return credentials.SignatureAlgorithm;
}
- public static ClaimsPrincipal ValidateToken(this JsonWebToken token, JsonWebTokenValidationParameters parameters)
- {
- ArgumentUtility.CheckForNull(token, nameof(token));
- ArgumentUtility.CheckForNull(parameters, nameof(parameters));
-
- ClaimsIdentity actorIdentity = ValidateActor(token, parameters);
- ValidateLifetime(token, parameters);
- ValidateAudience(token, parameters);
- ValidateSignature(token, parameters);
- ValidateIssuer(token, parameters);
-
- ClaimsIdentity identity = new ClaimsIdentity("Federation", parameters.IdentityNameClaimType, ClaimTypes.Role);
-
- if (actorIdentity != null)
- {
- identity.Actor = actorIdentity;
- }
-
- IEnumerable claims = token.ExtractClaims();
-
- foreach (Claim claim in claims)
- {
- identity.AddClaim(new Claim(claim.Type, claim.Value, claim.ValueType, token.Issuer));
- }
-
- return new ClaimsPrincipal(identity);
- }
-
- private static ClaimsIdentity ValidateActor(JsonWebToken token, JsonWebTokenValidationParameters parameters)
- {
- ArgumentUtility.CheckForNull(token, nameof(token));
- ArgumentUtility.CheckForNull(parameters, nameof(parameters));
-
- if (!parameters.ValidateActor)
- {
- return null;
- }
-
- //this recursive call with check the parameters
- ClaimsPrincipal principal = token.Actor.ValidateToken(parameters.ActorValidationParameters);
-
- if (!(principal?.Identity is ClaimsIdentity))
- {
- throw new ActorValidationException();
- }
-
- return (ClaimsIdentity)principal.Identity;
- }
-
private static void ValidateLifetime(JsonWebToken token, JsonWebTokenValidationParameters parameters)
{
ArgumentUtility.CheckForNull(token, nameof(token));
@@ -241,59 +192,6 @@ namespace GitHub.Services.WebApi.Jwt
throw new InvalidAudienceException(); //validation exception;
}
- private static void ValidateSignature(JsonWebToken token, JsonWebTokenValidationParameters parameters)
- {
- ArgumentUtility.CheckForNull(token, nameof(token));
- ArgumentUtility.CheckForNull(parameters, nameof(parameters));
-
- if (!parameters.ValidateSignature)
- {
- return;
- }
-
- string encodedData = token.EncodedToken;
-
- string[] parts = encodedData.Split('.');
-
- if (parts.Length != 3)
- {
- throw new InvalidTokenException(JwtResources.EncodedTokenDataMalformed()); //validation exception
- }
-
- if (string.IsNullOrEmpty(parts[2]))
- {
- throw new InvalidTokenException(JwtResources.SignatureNotFound()); //validation exception
- }
-
- if (token.Algorithm == JWTAlgorithm.None)
- {
- throw new InvalidTokenException(JwtResources.InvalidSignatureAlgorithm()); //validation exception
- }
-
- ArgumentUtility.CheckForNull(parameters.SigningCredentials, nameof(parameters.SigningCredentials));
-
- //ArgumentUtility.CheckEnumerableForNullOrEmpty(parameters.SigningToken.SecurityKeys, nameof(parameters.SigningToken.SecurityKeys));
-
- byte[] sourceInput = Encoding.UTF8.GetBytes(string.Format("{0}.{1}", parts[0], parts[1]));
-
- byte[] sourceSignature = parts[2].FromBase64StringNoPadding();
-
-
- try
- {
- if (parameters.SigningCredentials.VerifySignature(sourceInput, sourceSignature))
- {
- return;
- }
- }
- catch (Exception)
- {
- //swallow exceptions here, we'll throw if nothing works...
- }
-
- throw new SignatureValidationException(); //valiation exception
- }
-
private static void ValidateIssuer(JsonWebToken token, JsonWebTokenValidationParameters parameters)
{
ArgumentUtility.CheckForNull(token, nameof(token));
diff --git a/src/Sdk/WebApi/WebApi/VssSigningCredentials.cs b/src/Sdk/WebApi/WebApi/VssSigningCredentials.cs
index e9b4b054f..e6051019c 100644
--- a/src/Sdk/WebApi/WebApi/VssSigningCredentials.cs
+++ b/src/Sdk/WebApi/WebApi/VssSigningCredentials.cs
@@ -1,7 +1,6 @@
using System;
using System.Collections.Generic;
using System.Security.Cryptography;
-using System.Security.Cryptography.X509Certificates;
using GitHub.Services.Common;
using GitHub.Services.WebApi.Jwt;
@@ -75,7 +74,6 @@ namespace GitHub.Services.WebApi
{
throw new InvalidOperationException();
}
-
return GetSignature(input);
}
@@ -86,48 +84,13 @@ namespace GitHub.Services.WebApi
/// A blob of data representing the signature of the input data
protected abstract Byte[] GetSignature(Byte[] input);
- ///
- /// Verifies the signature of the input data, returning true if the signature is valid.
- ///
- /// The data which should be signed
- /// The signature which should be verified
- /// True if the provided signature matches the current signing token; otherwise, false
- public abstract Boolean VerifySignature(Byte[] input, Byte[] signature);
-
- ///
- /// Creates a new VssSigningCredentials instance using the specified instance
- /// as the signing key.
- ///
- /// The certificate which contains the key used for signing and verification
- /// A new VssSigningCredentials instance which uses the specified certificate for signing
- public static VssSigningCredentials Create(X509Certificate2 certificate)
- {
- ArgumentUtility.CheckForNull(certificate, nameof(certificate));
-
- if (certificate.HasPrivateKey)
- {
- var rsa = certificate.GetRSAPrivateKey();
- if (rsa == null)
- {
- throw new SignatureAlgorithmUnsupportedException(certificate.SignatureAlgorithm.FriendlyName);
- }
-
- if (rsa.KeySize < c_minKeySize)
- {
- throw new InvalidCredentialsException(JwtResources.SigningTokenKeyTooSmall());
- }
- }
-
- return new X509Certificate2SigningToken(certificate);
- }
-
///
/// Creates a new VssSigningCredentials instance using the specified
/// callback function to retrieve the signing key.
///
/// The factory which creates RSA keys used for signing and verification
/// A new VssSigningCredentials instance which uses the specified provider for signing
- public static VssSigningCredentials Create(Func factory)
+ public static VssSigningCredentials Create(Func factory, bool requireFipsCryptography)
{
ArgumentUtility.CheckForNull(factory, nameof(factory));
@@ -143,80 +106,19 @@ namespace GitHub.Services.WebApi
throw new InvalidCredentialsException(JwtResources.SigningTokenKeyTooSmall());
}
- return new RSASigningToken(factory, rsa.KeySize);
+ if (requireFipsCryptography)
+ {
+ return new RSASigningToken(factory, rsa.KeySize, RSASignaturePadding.Pss);
+ }
+ return new RSASigningToken(factory, rsa.KeySize, RSASignaturePadding.Pkcs1);
}
}
- ///
- /// Creates a new VssSigningCredentials instance using the specified as the signing
- /// key. The returned signing token performs symmetric key signing and verification.
- ///
- /// The key used for signing and verification
- /// A new VssSigningCredentials instance which uses the specified key for signing
- public static VssSigningCredentials Create(Byte[] key)
- {
- ArgumentUtility.CheckForNull(key, nameof(key));
-
- // Probably should have validation here, but there was none previously
- return new SymmetricKeySigningToken(key);
- }
-
private const Int32 c_minKeySize = 2048;
private readonly DateTime m_effectiveDate;
#region Concrete Implementations
- private class SymmetricKeySigningToken : VssSigningCredentials
- {
- public SymmetricKeySigningToken(Byte[] key)
- {
- m_key = new Byte[key.Length];
- Buffer.BlockCopy(key, 0, m_key, 0, m_key.Length);
- }
-
- public override Boolean CanSignData
- {
- get
- {
- return true;
- }
- }
-
- public override Int32 KeySize
- {
- get
- {
- return m_key.Length * 8;
- }
- }
-
- public override JWTAlgorithm SignatureAlgorithm
- {
- get
- {
- return JWTAlgorithm.HS256;
- }
- }
-
- protected override Byte[] GetSignature(Byte[] input)
- {
- using (var hash = new HMACSHA256(m_key))
- {
- return hash.ComputeHash(input);
- }
- }
-
- public override Boolean VerifySignature(
- Byte[] input,
- Byte[] signature)
- {
- var computedSignature = SignData(input);
- return SecureCompare.TimeInvariantEquals(computedSignature, signature);
- }
-
- private readonly Byte[] m_key;
- }
-
private abstract class AsymmetricKeySigningToken : VssSigningCredentials
{
protected abstract Boolean HasPrivateKey();
@@ -244,70 +146,14 @@ namespace GitHub.Services.WebApi
private Boolean? m_hasPrivateKey;
}
- private class X509Certificate2SigningToken : AsymmetricKeySigningToken, IJsonWebTokenHeaderProvider
- {
- public X509Certificate2SigningToken(X509Certificate2 certificate)
- {
- m_certificate = certificate;
- }
-
- public override Int32 KeySize
- {
- get
- {
- return m_certificate.GetRSAPublicKey().KeySize;
- }
- }
-
- public override DateTime ValidFrom
- {
- get
- {
- return m_certificate.NotBefore;
- }
- }
-
- public override DateTime ValidTo
- {
- get
- {
- return m_certificate.NotAfter;
- }
- }
-
- public override Boolean VerifySignature(
- Byte[] input,
- Byte[] signature)
- {
- var rsa = m_certificate.GetRSAPublicKey();
- return rsa.VerifyData(input, signature, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
- }
-
- protected override Byte[] GetSignature(Byte[] input)
- {
- var rsa = m_certificate.GetRSAPrivateKey();
- return rsa.SignData(input, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
- }
-
- protected override Boolean HasPrivateKey()
- {
- return m_certificate.HasPrivateKey;
- }
-
- void IJsonWebTokenHeaderProvider.SetHeaders(IDictionary headers)
- {
- headers[JsonWebTokenHeaderParameters.X509CertificateThumbprint] = m_certificate.GetCertHash().ToBase64StringNoPadding();
- }
-
- private readonly X509Certificate2 m_certificate;
- }
-
private class RSASigningToken : AsymmetricKeySigningToken
{
public RSASigningToken(
Func factory,
- Int32 keySize)
+ Int32 keySize,
+ RSASignaturePadding signaturePadding)
{
+ m_signaturePadding = signaturePadding;
m_keySize = keySize;
m_factory = factory;
}
@@ -324,7 +170,7 @@ namespace GitHub.Services.WebApi
{
using (var rsa = m_factory())
{
- return rsa.SignData(input, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
+ return rsa.SignData(input, HashAlgorithmName.SHA256, m_signaturePadding);
}
}
@@ -335,7 +181,7 @@ namespace GitHub.Services.WebApi
// As unfortunate as this is, there is no way to tell from an RSA implementation, based on querying
// properties alone, if it supports signature operations or has a private key. This is a one-time
// hit for the signing credentials implementation, so it shouldn't be a huge deal.
- GetSignature(new Byte[1] { 1 });
+ GetSignature(new Byte[1] { 1 });
return true;
}
catch (CryptographicException)
@@ -344,18 +190,9 @@ namespace GitHub.Services.WebApi
}
}
- public override Boolean VerifySignature(
- Byte[] input,
- Byte[] signature)
- {
- using (var rsa = m_factory())
- {
- return rsa.VerifyData(input, signature, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
- }
- }
-
private readonly Int32 m_keySize;
private readonly Func m_factory;
+ private readonly RSASignaturePadding m_signaturePadding;
}
#endregion