Skip to content

Commit

Permalink
force using AES for cert algo (#1345)
Browse files Browse the repository at this point in the history
* force using 3des for cert algo

* happy build

* use aes

* happy build
  • Loading branch information
tg123 committed Sep 11, 2023
1 parent a3321e0 commit c772d8e
Show file tree
Hide file tree
Showing 3 changed files with 159 additions and 105 deletions.
157 changes: 157 additions & 0 deletions src/KubernetesClient.Classic/CertUtils.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
using k8s.Exceptions;
using Org.BouncyCastle.Asn1.Nist;
using Org.BouncyCastle.Asn1.Pkcs;
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.OpenSsl;
using Org.BouncyCastle.Pkcs;
using Org.BouncyCastle.Security;
using Org.BouncyCastle.X509;
using System.IO;
using System.Security.Cryptography.X509Certificates;

namespace k8s
{
internal static class CertUtils
{
/// <summary>
/// Load pem encoded cert file
/// </summary>
/// <param name="file">Path to pem encoded cert file</param>
/// <returns>List of x509 instances.</returns>
public static X509Certificate2Collection LoadPemFileCert(string file)
{
var certCollection = new X509Certificate2Collection();
using (var stream = FileSystem.Current.OpenRead(file))
{
var certs = new X509CertificateParser().ReadCertificates(stream);

// Convert BouncyCastle X509Certificates to the .NET cryptography implementation and add
// it to the certificate collection
//
foreach (Org.BouncyCastle.X509.X509Certificate cert in certs)
{
// This null password is to change the constructor to fix this KB:
// https://support.microsoft.com/en-us/topic/kb5025823-change-in-how-net-applications-import-x-509-certificates-bf81c936-af2b-446e-9f7a-016f4713b46b
string nullPassword = null;
certCollection.Add(new X509Certificate2(cert.GetEncoded(), nullPassword));
}
}

return certCollection;
}

/// <summary>
/// Generates pfx from client configuration
/// </summary>
/// <param name="config">Kubernetes Client Configuration</param>
/// <returns>Generated Pfx Path</returns>
public static X509Certificate2 GeneratePfx(KubernetesClientConfiguration config)
{
if (config == null)
{
throw new ArgumentNullException(nameof(config));
}

byte[] keyData = null;
byte[] certData = null;

if (!string.IsNullOrWhiteSpace(config.ClientCertificateKeyData))
{
keyData = Convert.FromBase64String(config.ClientCertificateKeyData);
}

if (!string.IsNullOrWhiteSpace(config.ClientKeyFilePath))
{
keyData = File.ReadAllBytes(config.ClientKeyFilePath);
}

if (keyData == null)
{
throw new KubeConfigException("keyData is empty");
}

if (!string.IsNullOrWhiteSpace(config.ClientCertificateData))
{
certData = Convert.FromBase64String(config.ClientCertificateData);
}

if (!string.IsNullOrWhiteSpace(config.ClientCertificateFilePath))
{
certData = File.ReadAllBytes(config.ClientCertificateFilePath);
}

if (certData == null)
{
throw new KubeConfigException("certData is empty");
}

var cert = new X509CertificateParser().ReadCertificate(new MemoryStream(certData));
// key usage is a bit string, zero-th bit is 'digitalSignature'
// See https://www.alvestrand.no/objectid/2.5.29.15.html for more details.
if (cert != null && cert.GetKeyUsage() != null && !cert.GetKeyUsage()[0])
{
throw new Exception(
"Client certificates must be marked for digital signing. " +
"See https://github.com/kubernetes-client/csharp/issues/319");
}

object obj;
using (var reader = new StreamReader(new MemoryStream(keyData)))
{
obj = new PemReader(reader).ReadObject();
if (obj is AsymmetricCipherKeyPair key)
{
var cipherKey = key;
obj = cipherKey.Private;
}
}

var keyParams = (AsymmetricKeyParameter)obj;

var store = new Pkcs12StoreBuilder()
.SetKeyAlgorithm(NistObjectIdentifiers.IdAes128Cbc, PkcsObjectIdentifiers.IdHmacWithSha1)
.Build();
store.SetKeyEntry("K8SKEY", new AsymmetricKeyEntry(keyParams), new[] { new X509CertificateEntry(cert) });

using var pkcs = new MemoryStream();

store.Save(pkcs, new char[0], new SecureRandom());

// This null password is to change the constructor to fix this KB:
// https://support.microsoft.com/en-us/topic/kb5025823-change-in-how-net-applications-import-x-509-certificates-bf81c936-af2b-446e-9f7a-016f4713b46b
string nullPassword = null;

if (config.ClientCertificateKeyStoreFlags.HasValue)
{
return new X509Certificate2(pkcs.ToArray(), nullPassword, config.ClientCertificateKeyStoreFlags.Value);
}
else
{
return new X509Certificate2(pkcs.ToArray(), nullPassword);
}
}

/// <summary>
/// Retrieves Client Certificate PFX from configuration
/// </summary>
/// <param name="config">Kubernetes Client Configuration</param>
/// <returns>Client certificate PFX</returns>
public static X509Certificate2 GetClientCert(KubernetesClientConfiguration config)
{
if (config == null)
{
throw new ArgumentNullException(nameof(config));
}

if ((!string.IsNullOrWhiteSpace(config.ClientCertificateData) ||
!string.IsNullOrWhiteSpace(config.ClientCertificateFilePath)) &&
(!string.IsNullOrWhiteSpace(config.ClientCertificateKeyData) ||
!string.IsNullOrWhiteSpace(config.ClientKeyFilePath)))
{
return GeneratePfx(config);
}

return null;
}
}
}
5 changes: 2 additions & 3 deletions src/KubernetesClient.Classic/KubernetesClient.Classic.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Portable.BouncyCastle" Version="1.9.0" />
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="6.32.3" />
<PackageReference Include="BouncyCastle.Cryptography" Version="2.2.1" />
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="6.32.0" />
<PackageReference Include="IdentityModel.OidcClient" Version="5.2.1" />
<PackageReference Include="Microsoft.Bcl.AsyncInterfaces" Version="7.0.0" />
</ItemGroup>
Expand All @@ -18,7 +18,6 @@
</ItemGroup>

<ItemGroup>
<Compile Include="..\KubernetesClient\CertUtils.cs" />
<Compile Include="..\KubernetesClient\FileSystem.cs" />
<Compile Include="..\KubernetesClient\IKubernetes.cs" />
<Compile Include="..\KubernetesClient\Kubernetes.ConfigInit.cs" />
Expand Down
102 changes: 0 additions & 102 deletions src/KubernetesClient/CertUtils.cs
Original file line number Diff line number Diff line change
@@ -1,14 +1,6 @@
using k8s.Exceptions;
#if !NET5_0_OR_GREATER
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.OpenSsl;
using Org.BouncyCastle.Pkcs;
using Org.BouncyCastle.Security;
using Org.BouncyCastle.X509;
#else
using System.Runtime.InteropServices;
using System.Text;
#endif
using System.IO;
using System.Security.Cryptography.X509Certificates;

Expand All @@ -26,22 +18,7 @@ public static X509Certificate2Collection LoadPemFileCert(string file)
var certCollection = new X509Certificate2Collection();
using (var stream = FileSystem.Current.OpenRead(file))
{
#if NET5_0_OR_GREATER
certCollection.ImportFromPem(new StreamReader(stream).ReadToEnd());
#else
var certs = new X509CertificateParser().ReadCertificates(stream);

// Convert BouncyCastle X509Certificates to the .NET cryptography implementation and add
// it to the certificate collection
//
foreach (Org.BouncyCastle.X509.X509Certificate cert in certs)
{
// This null password is to change the constructor to fix this KB:
// https://support.microsoft.com/en-us/topic/kb5025823-change-in-how-net-applications-import-x-509-certificates-bf81c936-af2b-446e-9f7a-016f4713b46b
string nullPassword = null;
certCollection.Add(new X509Certificate2(cert.GetEncoded(), nullPassword));
}
#endif
}

return certCollection;
Expand All @@ -59,7 +36,6 @@ public static X509Certificate2 GeneratePfx(KubernetesClientConfiguration config)
throw new ArgumentNullException(nameof(config));
}

#if NET5_0_OR_GREATER
string keyData = null;
string certData = null;

Expand Down Expand Up @@ -114,84 +90,6 @@ public static X509Certificate2 GeneratePfx(KubernetesClientConfiguration config)
}

return cert;
#else

byte[] keyData = null;
byte[] certData = null;

if (!string.IsNullOrWhiteSpace(config.ClientCertificateKeyData))
{
keyData = Convert.FromBase64String(config.ClientCertificateKeyData);
}

if (!string.IsNullOrWhiteSpace(config.ClientKeyFilePath))
{
keyData = File.ReadAllBytes(config.ClientKeyFilePath);
}

if (keyData == null)
{
throw new KubeConfigException("keyData is empty");
}

if (!string.IsNullOrWhiteSpace(config.ClientCertificateData))
{
certData = Convert.FromBase64String(config.ClientCertificateData);
}

if (!string.IsNullOrWhiteSpace(config.ClientCertificateFilePath))
{
certData = File.ReadAllBytes(config.ClientCertificateFilePath);
}

if (certData == null)
{
throw new KubeConfigException("certData is empty");
}

var cert = new X509CertificateParser().ReadCertificate(new MemoryStream(certData));
// key usage is a bit string, zero-th bit is 'digitalSignature'
// See https://www.alvestrand.no/objectid/2.5.29.15.html for more details.
if (cert != null && cert.GetKeyUsage() != null && !cert.GetKeyUsage()[0])
{
throw new Exception(
"Client certificates must be marked for digital signing. " +
"See https://github.com/kubernetes-client/csharp/issues/319");
}

object obj;
using (var reader = new StreamReader(new MemoryStream(keyData)))
{
obj = new PemReader(reader).ReadObject();
if (obj is AsymmetricCipherKeyPair key)
{
var cipherKey = key;
obj = cipherKey.Private;
}
}

var keyParams = (AsymmetricKeyParameter)obj;

var store = new Pkcs12StoreBuilder().Build();
store.SetKeyEntry("K8SKEY", new AsymmetricKeyEntry(keyParams), new[] { new X509CertificateEntry(cert) });

using var pkcs = new MemoryStream();

store.Save(pkcs, new char[0], new SecureRandom());

// This null password is to change the constructor to fix this KB:
// https://support.microsoft.com/en-us/topic/kb5025823-change-in-how-net-applications-import-x-509-certificates-bf81c936-af2b-446e-9f7a-016f4713b46b
string nullPassword = null;

if (config.ClientCertificateKeyStoreFlags.HasValue)
{
return new X509Certificate2(pkcs.ToArray(), nullPassword, config.ClientCertificateKeyStoreFlags.Value);
}
else
{
return new X509Certificate2(pkcs.ToArray(), nullPassword);
}
#endif
}

/// <summary>
Expand Down

0 comments on commit c772d8e

Please sign in to comment.