Skip to content

Commit aa22379

Browse files
authoredFeb 1, 2022
remove bouncy castle dependency from edgeHub (#6019)
A quick solution to see how bouncy castle could be removed. <del>What missing are: <del>- error handling <del>- Import apparently does not like PKCS-8 Now it contains the full solution
1 parent 36054c0 commit aa22379

File tree

12 files changed

+401
-267
lines changed

12 files changed

+401
-267
lines changed
 

‎edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/planners/HealthRestartPlanner.cs

-3
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,14 @@ namespace Microsoft.Azure.Devices.Edge.Agent.Core.Planners
44
using System;
55
using System.Collections.Generic;
66
using System.Collections.Immutable;
7-
using System.Diagnostics;
87
using System.Linq;
9-
using System.Runtime.CompilerServices;
108
using System.Threading.Tasks;
119
using Microsoft.Azure.Devices.Edge.Agent.Core.Commands;
1210
using Microsoft.Azure.Devices.Edge.Storage;
1311
using Microsoft.Azure.Devices.Edge.Util;
1412
using Microsoft.Extensions.Logging;
1513
using Newtonsoft.Json;
1614
using Nito.AsyncEx;
17-
using Org.BouncyCastle.Math.EC.Rfc7748;
1815
using DiffState = System.ValueTuple<
1916
// added modules
2017
System.Collections.Generic.IList<Microsoft.Azure.Devices.Edge.Agent.Core.IModule>,

‎edge-hub/core/src/Microsoft.Azure.Devices.Edge.Hub.Http/EdgeHubScopeResultHelpers.cs

+5-8
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,10 @@
11
// Copyright (c) Microsoft. All rights reserved.
22
namespace Microsoft.Azure.Devices.Edge.Hub.Http.Controllers
33
{
4-
using System.Collections.Generic;
5-
using System.Linq;
6-
using Microsoft.Azure.Devices.Edge.Hub.Core;
4+
using System;
75
using Microsoft.Azure.Devices.Edge.Hub.Core.Identity.Service;
86
using Microsoft.Azure.Devices.Edge.Util;
97
using Microsoft.Azure.Devices.Shared;
10-
using Org.BouncyCastle.Security;
118

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

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

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

@@ -72,7 +69,7 @@ static AuthenticationMechanism GetAuthenticationMechanism(ServiceAuthentication
7269
break;
7370

7471
default:
75-
throw new InvalidParameterException($"Unexpected ServiceAuthenticationType: {serviceAuth.Type}");
72+
throw new InvalidOperationException($"Unexpected ServiceAuthenticationType: {serviceAuth.Type}");
7673
}
7774

7875
return authentication;

‎edge-hub/core/test/Microsoft.Azure.Devices.Edge.Hub.CloudProxy.Test/DeviceScopeCertificateAuthenticatorTest.cs

+19-19
Original file line numberDiff line numberDiff line change
@@ -238,8 +238,8 @@ public async Task AuthenticateAsyncWithDeviceCAX509InScopeCacheSucceeds()
238238
{
239239
var notBefore = DateTime.Now.Subtract(TimeSpan.FromDays(2));
240240
var notAfter = DateTime.Now.AddYears(1);
241-
var (caCert, caKeyPair) = TestCertificateHelper.GenerateSelfSignedCert("MyTestCA", notBefore, notAfter, true);
242-
var (issuedClientCert, issuedClientKeyPair) = TestCertificateHelper.GenerateCertificate("MyIssuedTestClient", notBefore, notAfter, caCert, caKeyPair, false, null, null);
241+
var caCert = TestCertificateHelper.GenerateSelfSignedCert("MyTestCA", notBefore, notAfter, true);
242+
var issuedClientCert = TestCertificateHelper.GenerateCertificate("MyIssuedTestClient", notBefore, notAfter, caCert, false, null, null);
243243
IList<X509Certificate2> issuedClientCertChain = new List<X509Certificate2>() { caCert };
244244
IList<X509Certificate2> trustBundle = new List<X509Certificate2>() { caCert };
245245
string deviceId = "MyIssuedTestClient";
@@ -269,8 +269,8 @@ public async Task AuthenticateAsyncWithDeviceCAX509NotInScopeCacheFails()
269269
{
270270
var notBefore = DateTime.Now.Subtract(TimeSpan.FromDays(2));
271271
var notAfter = DateTime.Now.AddYears(1);
272-
var (caCert, caKeyPair) = TestCertificateHelper.GenerateSelfSignedCert("MyTestCA", notBefore, notAfter, true);
273-
var (issuedClientCert, issuedClientKeyPair) = TestCertificateHelper.GenerateCertificate("MyIssuedTestClient", notBefore, notAfter, caCert, caKeyPair, false, null, null);
272+
var caCert = TestCertificateHelper.GenerateSelfSignedCert("MyTestCA", notBefore, notAfter, true);
273+
var issuedClientCert = TestCertificateHelper.GenerateCertificate("MyIssuedTestClient", notBefore, notAfter, caCert, false, null, null);
274274
IList<X509Certificate2> issuedClientCertChain = new List<X509Certificate2>() { caCert };
275275
IList<X509Certificate2> trustBundle = new List<X509Certificate2>() { caCert };
276276
string deviceId = "MyIssuedTestClient";
@@ -300,8 +300,8 @@ public async Task AuthenticateAsyncWithDeviceCAX509ExpiredCertInScopeCacheFails(
300300
{
301301
var notBefore = DateTime.Now.Subtract(TimeSpan.FromDays(2));
302302
var notAfter = DateTime.Now.Subtract(TimeSpan.FromDays(1));
303-
var (caCert, caKeyPair) = TestCertificateHelper.GenerateSelfSignedCert("MyTestCA", notBefore, notAfter, true);
304-
var (issuedClientCert, issuedClientKeyPair) = TestCertificateHelper.GenerateCertificate("MyIssuedTestClient", notBefore, notAfter, caCert, caKeyPair, false, null, null);
303+
var caCert = TestCertificateHelper.GenerateSelfSignedCert("MyTestCA", notBefore, notAfter, true);
304+
var issuedClientCert = TestCertificateHelper.GenerateCertificate("MyIssuedTestClient", notBefore, notAfter, caCert, false, null, null);
305305
IList<X509Certificate2> issuedClientCertChain = new List<X509Certificate2>() { caCert };
306306
IList<X509Certificate2> trustBundle = new List<X509Certificate2>() { caCert };
307307
string deviceId = "MyIssuedTestClient";
@@ -331,8 +331,8 @@ public async Task AuthenticateAsyncWithDeviceCAX509FutureValidCertInScopeCacheFa
331331
var notBefore = DateTime.Now.AddYears(1);
332332
var notAfter = DateTime.Now.AddYears(2);
333333

334-
var (caCert, caKeyPair) = TestCertificateHelper.GenerateSelfSignedCert("MyTestCA", notBefore, notAfter, true);
335-
var (issuedClientCert, issuedClientKeyPair) = TestCertificateHelper.GenerateCertificate("MyIssuedTestClient", notBefore, notAfter, caCert, caKeyPair, false, null, null);
334+
var caCert = TestCertificateHelper.GenerateSelfSignedCert("MyTestCA", notBefore, notAfter, true);
335+
var issuedClientCert = TestCertificateHelper.GenerateCertificate("MyIssuedTestClient", notBefore, notAfter, caCert, false, null, null);
336336
IList<X509Certificate2> issuedClientCertChain = new List<X509Certificate2>() { caCert };
337337
IList<X509Certificate2> trustBundle = new List<X509Certificate2>() { caCert };
338338
string deviceId = "MyIssuedTestClient";
@@ -362,8 +362,8 @@ public async Task AuthenticateAsyncWithDeviceCAX509CATrueInScopeCacheFails()
362362
var notBefore = DateTime.Now.Subtract(TimeSpan.FromDays(2));
363363
var notAfter = DateTime.Now.AddYears(1);
364364

365-
var (caCert, caKeyPair) = TestCertificateHelper.GenerateSelfSignedCert("MyTestCA", notBefore, notAfter, true);
366-
var (issuedClientCert, issuedClientKeyPair) = TestCertificateHelper.GenerateCertificate("MyIssuedTestClient", notBefore, notAfter, caCert, caKeyPair, true, null, null);
365+
var caCert = TestCertificateHelper.GenerateSelfSignedCert("MyTestCA", notBefore, notAfter, true);
366+
var issuedClientCert = TestCertificateHelper.GenerateCertificate("MyIssuedTestClient", notBefore, notAfter, caCert, true, null, null);
367367
IList<X509Certificate2> issuedClientCertChain = new List<X509Certificate2>() { caCert };
368368
IList<X509Certificate2> trustBundle = new List<X509Certificate2>() { caCert };
369369
string deviceId = "MyIssuedTestClient";
@@ -392,8 +392,8 @@ public async Task AuthenticateAsyncWithMismatchDeviceIDCAX509InScopeCacheFails()
392392
{
393393
var notBefore = DateTime.Now.Subtract(TimeSpan.FromDays(2));
394394
var notAfter = DateTime.Now.AddYears(1);
395-
var (caCert, caKeyPair) = TestCertificateHelper.GenerateSelfSignedCert("MyTestCA", notBefore, notAfter, true);
396-
var (issuedClientCert, issuedClientKeyPair) = TestCertificateHelper.GenerateCertificate("MyIssuedTestClient", notBefore, notAfter, caCert, caKeyPair, false, null, null);
395+
var caCert = TestCertificateHelper.GenerateSelfSignedCert("MyTestCA", notBefore, notAfter, true);
396+
var issuedClientCert = TestCertificateHelper.GenerateCertificate("MyIssuedTestClient", notBefore, notAfter, caCert, false, null, null);
397397
IList<X509Certificate2> issuedClientCertChain = new List<X509Certificate2>() { caCert };
398398
IList<X509Certificate2> trustBundle = new List<X509Certificate2>() { caCert };
399399
string deviceId = "different from CN";
@@ -422,8 +422,8 @@ public async Task AuthenticateAsyncWithEmptyChainDeviceCAX509InScopeCacheFails()
422422
{
423423
var notBefore = DateTime.Now.Subtract(TimeSpan.FromDays(2));
424424
var notAfter = DateTime.Now.AddYears(1);
425-
var (caCert, caKeyPair) = TestCertificateHelper.GenerateSelfSignedCert("MyTestCA", notBefore, notAfter, true);
426-
var (issuedClientCert, issuedClientKeyPair) = TestCertificateHelper.GenerateCertificate("MyIssuedTestClient", notBefore, notAfter, caCert, caKeyPair, false, null, null);
425+
var caCert = TestCertificateHelper.GenerateSelfSignedCert("MyTestCA", notBefore, notAfter, true);
426+
var issuedClientCert = TestCertificateHelper.GenerateCertificate("MyIssuedTestClient", notBefore, notAfter, caCert, false, null, null);
427427
IList<X509Certificate2> issuedClientCertChain = new List<X509Certificate2>() { }; // empty chain supplied
428428
IList<X509Certificate2> trustBundle = new List<X509Certificate2>() { caCert };
429429
string deviceId = "different from CN";
@@ -452,9 +452,9 @@ public async Task AuthenticateAsyncWithInvalidChainDeviceCAX509InScopeCacheFails
452452
{
453453
var notBefore = DateTime.Now.Subtract(TimeSpan.FromDays(2));
454454
var notAfter = DateTime.Now.AddYears(1);
455-
var (caCert, caKeyPair) = TestCertificateHelper.GenerateSelfSignedCert("MyTestCA", notBefore, notAfter, true);
456-
var (otherCaCert, otherCaKeyPair) = TestCertificateHelper.GenerateSelfSignedCert("MyOtherTestCA", notBefore, notAfter, true);
457-
var (issuedClientCert, issuedClientKeyPair) = TestCertificateHelper.GenerateCertificate("MyIssuedTestClient", notBefore, notAfter, caCert, caKeyPair, false, null, null);
455+
var caCert = TestCertificateHelper.GenerateSelfSignedCert("MyTestCA", notBefore, notAfter, true);
456+
var otherCaCert = TestCertificateHelper.GenerateSelfSignedCert("MyOtherTestCA", notBefore, notAfter, true);
457+
var issuedClientCert = TestCertificateHelper.GenerateCertificate("MyIssuedTestClient", notBefore, notAfter, caCert, false, null, null);
458458
IList<X509Certificate2> issuedClientCertChain = new List<X509Certificate2>() { otherCaCert }; // invalid chain supplied
459459
IList<X509Certificate2> trustBundle = new List<X509Certificate2>() { caCert };
460460
string deviceId = "different from CN";
@@ -529,8 +529,8 @@ public async Task AuthenticateAsyncWithModuleCAX509InScopeCacheFails()
529529
{
530530
var notBefore = DateTime.Now.Subtract(TimeSpan.FromDays(2));
531531
var notAfter = DateTime.Now.AddYears(1);
532-
var (caCert, caKeyPair) = TestCertificateHelper.GenerateSelfSignedCert("MyTestCA", notBefore, notAfter, true);
533-
var (issuedClientCert, issuedClientKeyPair) = TestCertificateHelper.GenerateCertificate("MyIssuedTestClient", notBefore, notAfter, caCert, caKeyPair, false, null, null);
532+
var caCert = TestCertificateHelper.GenerateSelfSignedCert("MyTestCA", notBefore, notAfter, true);
533+
var issuedClientCert = TestCertificateHelper.GenerateCertificate("MyIssuedTestClient", notBefore, notAfter, caCert, false, null, null);
534534
IList<X509Certificate2> issuedClientCertChain = new List<X509Certificate2>() { caCert };
535535
IList<X509Certificate2> trustBundle = new List<X509Certificate2>() { caCert };
536536
string deviceId = "d1";

‎edge-util/src/Microsoft.Azure.Devices.Edge.Util/CertificateHelper.cs

+93-52
Original file line numberDiff line numberDiff line change
@@ -9,18 +9,16 @@ namespace Microsoft.Azure.Devices.Edge.Util
99
using System.Security.Cryptography;
1010
using System.Security.Cryptography.X509Certificates;
1111
using System.Text;
12+
using System.Threading;
1213
using System.Threading.Tasks;
1314
using Microsoft.Azure.Devices.Edge.Util.Edged;
1415
using Microsoft.Extensions.Logging;
15-
using Org.BouncyCastle.Crypto;
16-
using Org.BouncyCastle.Crypto.Parameters;
17-
using Org.BouncyCastle.OpenSsl;
18-
using Org.BouncyCastle.Pkcs;
19-
using Org.BouncyCastle.Security;
20-
using X509Certificate = Org.BouncyCastle.X509.X509Certificate;
2116

2217
public static class CertificateHelper
2318
{
19+
static Oid oidRsaEncryption = Oid.FromFriendlyName("RSA", OidGroup.All);
20+
static Oid oidEcPublicKey = Oid.FromFriendlyName("ECC", OidGroup.All);
21+
2422
public static string GetSha256Thumbprint(X509Certificate2 cert)
2523
{
2624
Preconditions.CheckNotNull(cert);
@@ -387,53 +385,10 @@ internal static (X509Certificate2, IEnumerable<X509Certificate2>) ParseCertifica
387385

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

390-
Pkcs12Store store = new Pkcs12StoreBuilder().Build();
391-
IList<X509CertificateEntry> chain = new List<X509CertificateEntry>();
392-
393-
// note: the seperator between the certificate and private key is added for safety to delinate the cert and key boundary
394-
var sr = new StringReader(pemCerts.First() + "\r\n" + privateKey);
395-
var pemReader = new PemReader(sr);
396-
397-
AsymmetricKeyParameter keyParams = null;
398-
object certObject = pemReader.ReadObject();
399-
while (certObject != null)
400-
{
401-
if (certObject is X509Certificate x509Cert)
402-
{
403-
chain.Add(new X509CertificateEntry(x509Cert));
404-
}
405-
406-
// when processing certificates generated via openssl certObject type is of AsymmetricCipherKeyPair
407-
if (certObject is AsymmetricCipherKeyPair keyPair)
408-
{
409-
certObject = keyPair.Private;
410-
}
411-
412-
if (certObject is RsaPrivateCrtKeyParameters rsaParameters)
413-
{
414-
keyParams = rsaParameters;
415-
}
416-
else if (certObject is ECPrivateKeyParameters ecParameters)
417-
{
418-
keyParams = ecParameters;
419-
}
420-
421-
certObject = pemReader.ReadObject();
422-
}
423-
424-
if (keyParams == null)
425-
{
426-
throw new InvalidOperationException("Private key is required");
427-
}
388+
var certWithNoKey = new X509Certificate2(Encoding.UTF8.GetBytes(pemCerts.First()));
389+
var certWithPrivateKey = AttachPrivateKey(certWithNoKey, privateKey);
428390

429-
store.SetKeyEntry("Edge", new AsymmetricKeyEntry(keyParams), chain.ToArray());
430-
using (var p12File = new MemoryStream())
431-
{
432-
store.Save(p12File, new char[] { }, new SecureRandom());
433-
434-
var cert = new X509Certificate2(p12File.ToArray());
435-
return (cert, certsChain);
436-
}
391+
return (certWithPrivateKey, certsChain);
437392
}
438393

439394
static string ToHexString(byte[] bytes)
@@ -462,5 +417,91 @@ static Option<string> GetCommonNameFromSubject(string subject)
462417

463418
return commonName;
464419
}
420+
421+
static X509Certificate2 AttachPrivateKey(X509Certificate2 certificate, string pemEncodedKey)
422+
{
423+
var pkcs8Label = "PRIVATE KEY";
424+
var rsaLabel = "RSA PRIVATE KEY";
425+
var ecLabel = "EC PRIVATE KEY";
426+
var keyAlgorithm = certificate.GetKeyAlgorithm();
427+
var isPkcs8 = pemEncodedKey.IndexOf(Header(pkcs8Label)) >= 0;
428+
429+
X509Certificate2 result = null;
430+
431+
try
432+
{
433+
if (oidRsaEncryption.Value == keyAlgorithm)
434+
{
435+
var decodedKey = UnwrapPrivateKey(pemEncodedKey, isPkcs8 ? pkcs8Label : rsaLabel);
436+
var key = RSA.Create();
437+
438+
if (isPkcs8)
439+
{
440+
key.ImportPkcs8PrivateKey(decodedKey, out _);
441+
}
442+
else
443+
{
444+
key.ImportRSAPrivateKey(decodedKey, out _);
445+
}
446+
447+
result = certificate.CopyWithPrivateKey(key);
448+
}
449+
else if (oidEcPublicKey.Value == keyAlgorithm)
450+
{
451+
var decodedKey = UnwrapPrivateKey(pemEncodedKey, isPkcs8 ? pkcs8Label : ecLabel);
452+
var key = ECDsa.Create();
453+
454+
if (isPkcs8)
455+
{
456+
key.ImportPkcs8PrivateKey(decodedKey, out _);
457+
}
458+
else
459+
{
460+
key.ImportECPrivateKey(decodedKey, out _);
461+
}
462+
463+
result = certificate.CopyWithPrivateKey(key);
464+
}
465+
}
466+
catch (Exception ex)
467+
{
468+
throw new InvalidOperationException("Cannot import private key", ex);
469+
}
470+
471+
if (result == null)
472+
{
473+
throw new InvalidOperationException($"Cannot use certificate, not supported key algorithm: ${keyAlgorithm}");
474+
}
475+
476+
return result;
477+
}
478+
479+
static byte[] UnwrapPrivateKey(string pemEncodedKey, string algoLabel)
480+
{
481+
var headerIndex = pemEncodedKey.IndexOf(Header(algoLabel));
482+
var footerIndex = pemEncodedKey.IndexOf(Footer(algoLabel));
483+
484+
if (headerIndex < 0 || footerIndex < 0)
485+
{
486+
throw new InvalidOperationException($"Certificate key algorithm indicates {algoLabel}, but cannot unwrap key - headers not found");
487+
}
488+
489+
byte[] decodedKey;
490+
491+
try
492+
{
493+
var dataIndex = headerIndex + Header(algoLabel).Length;
494+
decodedKey = Convert.FromBase64String(pemEncodedKey.Substring(dataIndex, footerIndex - dataIndex));
495+
}
496+
catch (Exception ex)
497+
{
498+
throw new InvalidOperationException("Cannot decode private key: base64 decoding failed after removing headers", ex);
499+
}
500+
501+
return decodedKey;
502+
}
503+
504+
static string Header(string label) => $"-----BEGIN {label}-----";
505+
static string Footer(string label) => $"-----END {label}-----";
465506
}
466507
}

0 commit comments

Comments
 (0)