Skip to content

Commit

Permalink
cherry pick: removing bouncy castle Azure#6019
Browse files Browse the repository at this point in the history
  • Loading branch information
vipeller committed Feb 1, 2022
1 parent ee79c13 commit 38cf0fc
Show file tree
Hide file tree
Showing 11 changed files with 398 additions and 259 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,14 @@ namespace Microsoft.Azure.Devices.Edge.Agent.Core.Planners
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
using Microsoft.Azure.Devices.Edge.Agent.Core.Commands;
using Microsoft.Azure.Devices.Edge.Storage;
using Microsoft.Azure.Devices.Edge.Util;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using Nito.AsyncEx;
using Org.BouncyCastle.Math.EC.Rfc7748;
using DiffState = System.ValueTuple<
// added modules
System.Collections.Generic.IList<Microsoft.Azure.Devices.Edge.Agent.Core.IModule>,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -236,8 +236,8 @@ public async Task AuthenticateAsyncWithDeviceCAX509InScopeCacheSucceeds()
{
var notBefore = DateTime.Now.Subtract(TimeSpan.FromDays(2));
var notAfter = DateTime.Now.AddYears(1);
var (caCert, caKeyPair) = TestCertificateHelper.GenerateSelfSignedCert("MyTestCA", notBefore, notAfter, true);
var (issuedClientCert, issuedClientKeyPair) = TestCertificateHelper.GenerateCertificate("MyIssuedTestClient", notBefore, notAfter, caCert, caKeyPair, false, null, null);
var caCert = TestCertificateHelper.GenerateSelfSignedCert("MyTestCA", notBefore, notAfter, true);
var issuedClientCert = TestCertificateHelper.GenerateCertificate("MyIssuedTestClient", notBefore, notAfter, caCert, false, null, null);
IList<X509Certificate2> issuedClientCertChain = new List<X509Certificate2>() { caCert };
IList<X509Certificate2> trustBundle = new List<X509Certificate2>() { caCert };
string deviceId = "MyIssuedTestClient";
Expand Down Expand Up @@ -266,8 +266,8 @@ public async Task AuthenticateAsyncWithDeviceCAX509NotInScopeCacheFails()
{
var notBefore = DateTime.Now.Subtract(TimeSpan.FromDays(2));
var notAfter = DateTime.Now.AddYears(1);
var (caCert, caKeyPair) = TestCertificateHelper.GenerateSelfSignedCert("MyTestCA", notBefore, notAfter, true);
var (issuedClientCert, issuedClientKeyPair) = TestCertificateHelper.GenerateCertificate("MyIssuedTestClient", notBefore, notAfter, caCert, caKeyPair, false, null, null);
var caCert = TestCertificateHelper.GenerateSelfSignedCert("MyTestCA", notBefore, notAfter, true);
var issuedClientCert = TestCertificateHelper.GenerateCertificate("MyIssuedTestClient", notBefore, notAfter, caCert, false, null, null);
IList<X509Certificate2> issuedClientCertChain = new List<X509Certificate2>() { caCert };
IList<X509Certificate2> trustBundle = new List<X509Certificate2>() { caCert };
string deviceId = "MyIssuedTestClient";
Expand Down Expand Up @@ -297,8 +297,8 @@ public async Task AuthenticateAsyncWithDeviceCAX509ExpiredCertInScopeCacheFails(
{
var notBefore = DateTime.Now.Subtract(TimeSpan.FromDays(2));
var notAfter = DateTime.Now.Subtract(TimeSpan.FromDays(1));
var (caCert, caKeyPair) = TestCertificateHelper.GenerateSelfSignedCert("MyTestCA", notBefore, notAfter, true);
var (issuedClientCert, issuedClientKeyPair) = TestCertificateHelper.GenerateCertificate("MyIssuedTestClient", notBefore, notAfter, caCert, caKeyPair, false, null, null);
var caCert = TestCertificateHelper.GenerateSelfSignedCert("MyTestCA", notBefore, notAfter, true);
var issuedClientCert = TestCertificateHelper.GenerateCertificate("MyIssuedTestClient", notBefore, notAfter, caCert, false, null, null);
IList<X509Certificate2> issuedClientCertChain = new List<X509Certificate2>() { caCert };
IList<X509Certificate2> trustBundle = new List<X509Certificate2>() { caCert };
string deviceId = "MyIssuedTestClient";
Expand Down Expand Up @@ -328,8 +328,8 @@ public async Task AuthenticateAsyncWithDeviceCAX509FutureValidCertInScopeCacheFa
var notBefore = DateTime.Now.AddYears(1);
var notAfter = DateTime.Now.AddYears(2);

var (caCert, caKeyPair) = TestCertificateHelper.GenerateSelfSignedCert("MyTestCA", notBefore, notAfter, true);
var (issuedClientCert, issuedClientKeyPair) = TestCertificateHelper.GenerateCertificate("MyIssuedTestClient", notBefore, notAfter, caCert, caKeyPair, false, null, null);
var caCert = TestCertificateHelper.GenerateSelfSignedCert("MyTestCA", notBefore, notAfter, true);
var issuedClientCert = TestCertificateHelper.GenerateCertificate("MyIssuedTestClient", notBefore, notAfter, caCert, false, null, null);
IList<X509Certificate2> issuedClientCertChain = new List<X509Certificate2>() { caCert };
IList<X509Certificate2> trustBundle = new List<X509Certificate2>() { caCert };
string deviceId = "MyIssuedTestClient";
Expand Down Expand Up @@ -359,8 +359,8 @@ public async Task AuthenticateAsyncWithDeviceCAX509CATrueInScopeCacheFails()
var notBefore = DateTime.Now.Subtract(TimeSpan.FromDays(2));
var notAfter = DateTime.Now.AddYears(1);

var (caCert, caKeyPair) = TestCertificateHelper.GenerateSelfSignedCert("MyTestCA", notBefore, notAfter, true);
var (issuedClientCert, issuedClientKeyPair) = TestCertificateHelper.GenerateCertificate("MyIssuedTestClient", notBefore, notAfter, caCert, caKeyPair, true, null, null);
var caCert = TestCertificateHelper.GenerateSelfSignedCert("MyTestCA", notBefore, notAfter, true);
var issuedClientCert = TestCertificateHelper.GenerateCertificate("MyIssuedTestClient", notBefore, notAfter, caCert, true, null, null);
IList<X509Certificate2> issuedClientCertChain = new List<X509Certificate2>() { caCert };
IList<X509Certificate2> trustBundle = new List<X509Certificate2>() { caCert };
string deviceId = "MyIssuedTestClient";
Expand Down Expand Up @@ -389,8 +389,8 @@ public async Task AuthenticateAsyncWithMismatchDeviceIDCAX509InScopeCacheFails()
{
var notBefore = DateTime.Now.Subtract(TimeSpan.FromDays(2));
var notAfter = DateTime.Now.AddYears(1);
var (caCert, caKeyPair) = TestCertificateHelper.GenerateSelfSignedCert("MyTestCA", notBefore, notAfter, true);
var (issuedClientCert, issuedClientKeyPair) = TestCertificateHelper.GenerateCertificate("MyIssuedTestClient", notBefore, notAfter, caCert, caKeyPair, false, null, null);
var caCert = TestCertificateHelper.GenerateSelfSignedCert("MyTestCA", notBefore, notAfter, true);
var issuedClientCert = TestCertificateHelper.GenerateCertificate("MyIssuedTestClient", notBefore, notAfter, caCert, false, null, null);
IList<X509Certificate2> issuedClientCertChain = new List<X509Certificate2>() { caCert };
IList<X509Certificate2> trustBundle = new List<X509Certificate2>() { caCert };
string deviceId = "different from CN";
Expand Down Expand Up @@ -419,8 +419,8 @@ public async Task AuthenticateAsyncWithEmptyChainDeviceCAX509InScopeCacheFails()
{
var notBefore = DateTime.Now.Subtract(TimeSpan.FromDays(2));
var notAfter = DateTime.Now.AddYears(1);
var (caCert, caKeyPair) = TestCertificateHelper.GenerateSelfSignedCert("MyTestCA", notBefore, notAfter, true);
var (issuedClientCert, issuedClientKeyPair) = TestCertificateHelper.GenerateCertificate("MyIssuedTestClient", notBefore, notAfter, caCert, caKeyPair, false, null, null);
var caCert = TestCertificateHelper.GenerateSelfSignedCert("MyTestCA", notBefore, notAfter, true);
var issuedClientCert = TestCertificateHelper.GenerateCertificate("MyIssuedTestClient", notBefore, notAfter, caCert, false, null, null);
IList<X509Certificate2> issuedClientCertChain = new List<X509Certificate2>() { }; // empty chain supplied
IList<X509Certificate2> trustBundle = new List<X509Certificate2>() { caCert };
string deviceId = "different from CN";
Expand Down Expand Up @@ -449,9 +449,9 @@ public async Task AuthenticateAsyncWithInvalidChainDeviceCAX509InScopeCacheFails
{
var notBefore = DateTime.Now.Subtract(TimeSpan.FromDays(2));
var notAfter = DateTime.Now.AddYears(1);
var (caCert, caKeyPair) = TestCertificateHelper.GenerateSelfSignedCert("MyTestCA", notBefore, notAfter, true);
var (otherCaCert, otherCaKeyPair) = TestCertificateHelper.GenerateSelfSignedCert("MyOtherTestCA", notBefore, notAfter, true);
var (issuedClientCert, issuedClientKeyPair) = TestCertificateHelper.GenerateCertificate("MyIssuedTestClient", notBefore, notAfter, caCert, caKeyPair, false, null, null);
var caCert = TestCertificateHelper.GenerateSelfSignedCert("MyTestCA", notBefore, notAfter, true);
var otherCaCert = TestCertificateHelper.GenerateSelfSignedCert("MyOtherTestCA", notBefore, notAfter, true);
var issuedClientCert = TestCertificateHelper.GenerateCertificate("MyIssuedTestClient", notBefore, notAfter, caCert, false, null, null);
IList<X509Certificate2> issuedClientCertChain = new List<X509Certificate2>() { otherCaCert }; // invalid chain supplied
IList<X509Certificate2> trustBundle = new List<X509Certificate2>() { caCert };
string deviceId = "different from CN";
Expand Down Expand Up @@ -524,8 +524,8 @@ public async Task AuthenticateAsyncWithModuleCAX509InScopeCacheFails()
{
var notBefore = DateTime.Now.Subtract(TimeSpan.FromDays(2));
var notAfter = DateTime.Now.AddYears(1);
var (caCert, caKeyPair) = TestCertificateHelper.GenerateSelfSignedCert("MyTestCA", notBefore, notAfter, true);
var (issuedClientCert, issuedClientKeyPair) = TestCertificateHelper.GenerateCertificate("MyIssuedTestClient", notBefore, notAfter, caCert, caKeyPair, false, null, null);
var caCert = TestCertificateHelper.GenerateSelfSignedCert("MyTestCA", notBefore, notAfter, true);
var issuedClientCert = TestCertificateHelper.GenerateCertificate("MyIssuedTestClient", notBefore, notAfter, caCert, false, null, null);
IList<X509Certificate2> issuedClientCertChain = new List<X509Certificate2>() { caCert };
IList<X509Certificate2> trustBundle = new List<X509Certificate2>() { caCert };
string deviceId = "d1";
Expand Down
145 changes: 93 additions & 52 deletions edge-util/src/Microsoft.Azure.Devices.Edge.Util/CertificateHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,16 @@ namespace Microsoft.Azure.Devices.Edge.Util
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Azure.Devices.Edge.Util.Edged;
using Microsoft.Extensions.Logging;
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.OpenSsl;
using Org.BouncyCastle.Pkcs;
using Org.BouncyCastle.Security;
using X509Certificate = Org.BouncyCastle.X509.X509Certificate;

public static class CertificateHelper
{
static Oid oidRsaEncryption = Oid.FromFriendlyName("RSA", OidGroup.All);
static Oid oidEcPublicKey = Oid.FromFriendlyName("ECC", OidGroup.All);

public static string GetSha256Thumbprint(X509Certificate2 cert)
{
Preconditions.CheckNotNull(cert);
Expand Down Expand Up @@ -354,53 +352,10 @@ internal static (X509Certificate2, IEnumerable<X509Certificate2>) ParseCertifica

IEnumerable<X509Certificate2> certsChain = GetCertificatesFromPem(pemCerts.Skip(1));

Pkcs12Store store = new Pkcs12StoreBuilder().Build();
IList<X509CertificateEntry> chain = new List<X509CertificateEntry>();

// note: the seperator between the certificate and private key is added for safety to delinate the cert and key boundary
var sr = new StringReader(pemCerts.First() + "\r\n" + privateKey);
var pemReader = new PemReader(sr);

AsymmetricKeyParameter keyParams = null;
object certObject = pemReader.ReadObject();
while (certObject != null)
{
if (certObject is X509Certificate x509Cert)
{
chain.Add(new X509CertificateEntry(x509Cert));
}

// when processing certificates generated via openssl certObject type is of AsymmetricCipherKeyPair
if (certObject is AsymmetricCipherKeyPair keyPair)
{
certObject = keyPair.Private;
}

if (certObject is RsaPrivateCrtKeyParameters rsaParameters)
{
keyParams = rsaParameters;
}
else if (certObject is ECPrivateKeyParameters ecParameters)
{
keyParams = ecParameters;
}

certObject = pemReader.ReadObject();
}

if (keyParams == null)
{
throw new InvalidOperationException("Private key is required");
}
var certWithNoKey = new X509Certificate2(Encoding.UTF8.GetBytes(pemCerts.First()));
var certWithPrivateKey = AttachPrivateKey(certWithNoKey, privateKey);

store.SetKeyEntry("Edge", new AsymmetricKeyEntry(keyParams), chain.ToArray());
using (var p12File = new MemoryStream())
{
store.Save(p12File, new char[] { }, new SecureRandom());

var cert = new X509Certificate2(p12File.ToArray());
return (cert, certsChain);
}
return (certWithPrivateKey, certsChain);
}

static string ToHexString(byte[] bytes)
Expand Down Expand Up @@ -429,5 +384,91 @@ static Option<string> GetCommonNameFromSubject(string subject)

return commonName;
}

static X509Certificate2 AttachPrivateKey(X509Certificate2 certificate, string pemEncodedKey)
{
var pkcs8Label = "PRIVATE KEY";
var rsaLabel = "RSA PRIVATE KEY";
var ecLabel = "EC PRIVATE KEY";
var keyAlgorithm = certificate.GetKeyAlgorithm();
var isPkcs8 = pemEncodedKey.IndexOf(Header(pkcs8Label)) >= 0;

X509Certificate2 result = null;

try
{
if (oidRsaEncryption.Value == keyAlgorithm)
{
var decodedKey = UnwrapPrivateKey(pemEncodedKey, isPkcs8 ? pkcs8Label : rsaLabel);
var key = RSA.Create();

if (isPkcs8)
{
key.ImportPkcs8PrivateKey(decodedKey, out _);
}
else
{
key.ImportRSAPrivateKey(decodedKey, out _);
}

result = certificate.CopyWithPrivateKey(key);
}
else if (oidEcPublicKey.Value == keyAlgorithm)
{
var decodedKey = UnwrapPrivateKey(pemEncodedKey, isPkcs8 ? pkcs8Label : ecLabel);
var key = ECDsa.Create();

if (isPkcs8)
{
key.ImportPkcs8PrivateKey(decodedKey, out _);
}
else
{
key.ImportECPrivateKey(decodedKey, out _);
}

result = certificate.CopyWithPrivateKey(key);
}
}
catch (Exception ex)
{
throw new InvalidOperationException("Cannot import private key", ex);
}

if (result == null)
{
throw new InvalidOperationException($"Cannot use certificate, not supported key algorithm: ${keyAlgorithm}");
}

return result;
}

static byte[] UnwrapPrivateKey(string pemEncodedKey, string algoLabel)
{
var headerIndex = pemEncodedKey.IndexOf(Header(algoLabel));
var footerIndex = pemEncodedKey.IndexOf(Footer(algoLabel));

if (headerIndex < 0 || footerIndex < 0)
{
throw new InvalidOperationException($"Certificate key algorithm indicates {algoLabel}, but cannot unwrap key - headers not found");
}

byte[] decodedKey;

try
{
var dataIndex = headerIndex + Header(algoLabel).Length;
decodedKey = Convert.FromBase64String(pemEncodedKey.Substring(dataIndex, footerIndex - dataIndex));
}
catch (Exception ex)
{
throw new InvalidOperationException("Cannot decode private key: base64 decoding failed after removing headers", ex);
}

return decodedKey;
}

static string Header(string label) => $"-----BEGIN {label}-----";
static string Footer(string label) => $"-----END {label}-----";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="5.0.0" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
<PackageReference Include="Nito.AsyncEx" Version="5.0.0" />
<PackageReference Include="Portable.BouncyCastle" Version="1.8.5" />
<PackageReference Include="Serilog.Extensions.Logging" Version="2.0.2" />
<PackageReference Include="Serilog.Sinks.Console" Version="3.1.1" />
<PackageReference Include="System.Runtime.Loader" Version="4.3.0" />
Expand Down
Loading

0 comments on commit 38cf0fc

Please sign in to comment.