Skip to content

Commit

Permalink
cherrypick: remove bouncy castle dependency from edgeHub #6019 (#6062)
Browse files Browse the repository at this point in the history
  • Loading branch information
vipeller authored Feb 2, 2022
1 parent bc78f1c commit 403ca87
Show file tree
Hide file tree
Showing 12 changed files with 401 additions and 267 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
@@ -1,13 +1,10 @@
// Copyright (c) Microsoft. All rights reserved.
namespace Microsoft.Azure.Devices.Edge.Hub.Http.Controllers
{
using System.Collections.Generic;
using System.Linq;
using Microsoft.Azure.Devices.Edge.Hub.Core;
using System;
using Microsoft.Azure.Devices.Edge.Hub.Core.Identity.Service;
using Microsoft.Azure.Devices.Edge.Util;
using Microsoft.Azure.Devices.Shared;
using Org.BouncyCastle.Security;

public static class EdgeHubScopeResultHelpers
{
Expand All @@ -30,7 +27,7 @@ public static EdgeHubScopeModule ToEdgeHubScopeModule(this ServiceIdentity ident
{
Preconditions.CheckNotNull(identity);
Preconditions.CheckArgument(identity.IsModule);
string moduleId = identity.ModuleId.Expect(() => new InvalidParameterException($"ModuleId shouldn't be empty when ServiceIdentity is a module: {identity.Id}"));
string moduleId = identity.ModuleId.Expect(() => new InvalidOperationException($"ModuleId shouldn't be empty when ServiceIdentity is a module: {identity.Id}"));

return new EdgeHubScopeModule(
Preconditions.CheckNonWhiteSpace(moduleId, nameof(moduleId)),
Expand All @@ -53,13 +50,13 @@ static AuthenticationMechanism GetAuthenticationMechanism(ServiceAuthentication
{
case ServiceAuthenticationType.SymmetricKey:
authentication.Type = AuthenticationType.Sas;
var sasKey = serviceAuth.SymmetricKey.Expect(() => new InvalidParameterException("SAS key shouldn't be empty when auth type is SymmetricKey"));
var sasKey = serviceAuth.SymmetricKey.Expect(() => new InvalidOperationException("SAS key shouldn't be empty when auth type is SymmetricKey"));
authentication.SymmetricKey = new SymmetricKey() { PrimaryKey = sasKey.PrimaryKey, SecondaryKey = sasKey.SecondaryKey };
break;

case ServiceAuthenticationType.CertificateThumbprint:
authentication.Type = AuthenticationType.SelfSigned;
var x509Thumbprint = serviceAuth.X509Thumbprint.Expect(() => new InvalidParameterException("X509 thumbprint shouldn't be empty when auth type is CertificateThumbPrint"));
var x509Thumbprint = serviceAuth.X509Thumbprint.Expect(() => new InvalidOperationException("X509 thumbprint shouldn't be empty when auth type is CertificateThumbPrint"));
authentication.X509Thumbprint = new X509Thumbprint() { PrimaryThumbprint = x509Thumbprint.PrimaryThumbprint, SecondaryThumbprint = x509Thumbprint.SecondaryThumbprint };
break;

Expand All @@ -72,7 +69,7 @@ static AuthenticationMechanism GetAuthenticationMechanism(ServiceAuthentication
break;

default:
throw new InvalidParameterException($"Unexpected ServiceAuthenticationType: {serviceAuth.Type}");
throw new InvalidOperationException($"Unexpected ServiceAuthenticationType: {serviceAuth.Type}");
}

return authentication;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -238,8 +238,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 @@ -269,8 +269,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 @@ -300,8 +300,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 @@ -331,8 +331,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 @@ -362,8 +362,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 @@ -392,8 +392,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 @@ -422,8 +422,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 @@ -452,9 +452,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 @@ -529,8 +529,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 @@ -353,53 +351,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 @@ -428,5 +383,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}-----";
}
}
Loading

0 comments on commit 403ca87

Please sign in to comment.