Files
runner/src/Sdk/WebApi/WebApi/VssSigningCredentials.cs
JoannaaKL efffbaeabc Add utf8 with bom (#2641)
* Change default file encoding
2023-06-02 21:47:59 +02:00

201 lines
6.4 KiB
C#

using System;
using System.Collections.Generic;
using System.Security.Cryptography;
using GitHub.Services.Common;
using GitHub.Services.WebApi.Jwt;
namespace GitHub.Services.WebApi
{
/// <summary>
/// Provides a contract for signing, and verifying signatures of, blobs of data.
/// </summary>
public abstract class VssSigningCredentials
{
protected VssSigningCredentials()
{
m_effectiveDate = DateTime.UtcNow;
}
/// <summary>
/// Gets a value indicating whether or not this token may be used to sign data.
/// </summary>
public abstract Boolean CanSignData
{
get;
}
/// <summary>
/// Gets the size of the key, in bits, used for signing and verification.
/// </summary>
public abstract Int32 KeySize
{
get;
}
/// <summary>
/// Gets the date from which this signing token is valid.
/// </summary>
public virtual DateTime ValidFrom
{
get
{
return m_effectiveDate;
}
}
/// <summary>
/// Gets the datetime at which this signing token expires.
/// </summary>
public virtual DateTime ValidTo
{
get
{
return DateTime.MaxValue;
}
}
/// <summary>
/// Gets the signature algorithm used by this signing token.
/// </summary>
public abstract JWTAlgorithm SignatureAlgorithm
{
get;
}
/// <summary>
/// Signs the <paramref name="input"/> array with the signing key associated with the token.
/// </summary>
/// <param name="input">The data which should be signed</param>
/// <returns>A blob of data representing the signature of the input data</returns>
/// <exception cref="InvalidOperationException">Thrown when the current instance cannot be used for signing</exception>
public virtual Byte[] SignData(Byte[] input)
{
if (!CanSignData)
{
throw new InvalidOperationException();
}
return GetSignature(input);
}
/// <summary>
/// Signs the <paramref name="input"/> array with the signing key associated with the token.
/// </summary>
/// <param name="input">The data which should be signed</param>
/// <returns>A blob of data representing the signature of the input data</returns>
protected abstract Byte[] GetSignature(Byte[] input);
/// <summary>
/// Creates a new <c>VssSigningCredentials</c> instance using the specified <paramref name="factory"/>
/// callback function to retrieve the signing key.
/// </summary>
/// <param name="factory">The factory which creates <c>RSA</c> keys used for signing and verification</param>
/// <returns>A new <c>VssSigningCredentials</c> instance which uses the specified provider for signing</returns>
public static VssSigningCredentials Create(Func<RSA> factory, bool requireFipsCryptography)
{
ArgumentUtility.CheckForNull(factory, nameof(factory));
using (var rsa = factory())
{
if (rsa == null)
{
throw new InvalidCredentialsException(JwtResources.SignatureAlgorithmUnsupportedException("None"));
}
if (rsa.KeySize < c_minKeySize)
{
throw new InvalidCredentialsException(JwtResources.SigningTokenKeyTooSmall());
}
if (requireFipsCryptography)
{
return new RSASigningToken(factory, rsa.KeySize, RSASignaturePadding.Pss);
}
return new RSASigningToken(factory, rsa.KeySize, RSASignaturePadding.Pkcs1);
}
}
private const Int32 c_minKeySize = 2048;
private readonly DateTime m_effectiveDate;
#region Concrete Implementations
private abstract class AsymmetricKeySigningToken : VssSigningCredentials
{
protected abstract Boolean HasPrivateKey();
public override JWTAlgorithm SignatureAlgorithm
{
get
{
return JWTAlgorithm.RS256;
}
}
public override Boolean CanSignData
{
get
{
if (m_hasPrivateKey == null)
{
m_hasPrivateKey = HasPrivateKey();
}
return m_hasPrivateKey.Value;
}
}
private Boolean? m_hasPrivateKey;
}
private class RSASigningToken : AsymmetricKeySigningToken
{
public RSASigningToken(
Func<RSA> factory,
Int32 keySize,
RSASignaturePadding signaturePadding)
{
m_signaturePadding = signaturePadding;
m_keySize = keySize;
m_factory = factory;
}
public override Int32 KeySize
{
get
{
return m_keySize;
}
}
protected override Byte[] GetSignature(Byte[] input)
{
using (var rsa = m_factory())
{
return rsa.SignData(input, HashAlgorithmName.SHA256, m_signaturePadding);
}
}
protected override Boolean HasPrivateKey()
{
try
{
// 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 });
return true;
}
catch (CryptographicException)
{
return false;
}
}
private readonly Int32 m_keySize;
private readonly Func<RSA> m_factory;
private readonly RSASignaturePadding m_signaturePadding;
}
#endregion
}
}