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