Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove BouncyCastle dependency to use System.Security.Cryptography #75

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
language: csharp
dist: trusty
mono: none
dotnet: 2.0.0
dotnet: 3.1

install:
- dotnet restore

script:
- dotnet test --framework=netcoreapp2.0 WebPush.Test/WebPush.Test.csproj
- dotnet test --framework=netcoreapp3.0 WebPush.Test/WebPush.Test.csproj
25 changes: 13 additions & 12 deletions WebPush.Test/ECKeyHelperTest.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
using System.Linq;
using System.Security.Cryptography;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Org.BouncyCastle.Crypto.Parameters;
using WebPush.Util;

namespace WebPush.Test
Expand All @@ -17,9 +17,8 @@ public class ECKeyHelperTest
public void TestGenerateKeys()
{
var keys = ECKeyHelper.GenerateKeys();

var publicKey = ((ECPublicKeyParameters) keys.Public).Q.GetEncoded(false);
var privateKey = ((ECPrivateKeyParameters) keys.Private).D.ToByteArrayUnsigned();
var publicKey = keys.PublicKey;
var privateKey = keys.PrivateKey;

var publicKeyLength = publicKey.Length;
var privateKeyLength = privateKey.Length;
Expand All @@ -31,14 +30,14 @@ public void TestGenerateKeys()
[TestMethod]
public void TestGenerateKeysNoCache()
{
var keys1 = ECKeyHelper.GenerateKeys();
var keys2 = ECKeyHelper.GenerateKeys();
var keys = ECKeyHelper.GenerateKeys();
var publicKey1 = keys.PublicKey;
var privateKey1 = keys.PrivateKey;

var publicKey1 = ((ECPublicKeyParameters) keys1.Public).Q.GetEncoded(false);
var privateKey1 = ((ECPrivateKeyParameters) keys1.Private).D.ToByteArrayUnsigned();
var keys2 = ECKeyHelper.GenerateKeys();
var publicKey2 = keys2.PublicKey;
var privateKey2 = keys2.PrivateKey;

var publicKey2 = ((ECPublicKeyParameters) keys2.Public).Q.GetEncoded(false);
var privateKey2 = ((ECPrivateKeyParameters) keys2.Private).D.ToByteArrayUnsigned();

Assert.IsFalse(publicKey1.SequenceEqual(publicKey2));
Assert.IsFalse(privateKey1.SequenceEqual(privateKey2));
Expand All @@ -47,12 +46,14 @@ public void TestGenerateKeysNoCache()
[TestMethod]
public void TestGetPrivateKey()
{
#if NET48
var privateKey = UrlBase64.Decode(TestPrivateKey);
var privateKeyParams = ECKeyHelper.GetPrivateKey(privateKey);

var importedPrivateKey = UrlBase64.Encode(privateKeyParams.D.ToByteArrayUnsigned());
var importedPrivateKey = UrlBase64.Encode((privateKeyParams as ECDsaCng).ExportParameters(true).D);

Assert.AreEqual(TestPrivateKey, importedPrivateKey);
#endif
}

[TestMethod]
Expand All @@ -61,7 +62,7 @@ public void TestGetPublicKey()
var publicKey = UrlBase64.Decode(TestPublicKey);
var publicKeyParams = ECKeyHelper.GetPublicKey(publicKey);

var importedPublicKey = UrlBase64.Encode(publicKeyParams.Q.GetEncoded(false));
var importedPublicKey = UrlBase64.Encode(publicKeyParams.GetECPublicKey());

Assert.AreEqual(TestPublicKey, importedPublicKey);
}
Expand Down
14 changes: 10 additions & 4 deletions WebPush.Test/WebPush.Test.csproj
Original file line number Diff line number Diff line change
@@ -1,20 +1,26 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFrameworks>netcoreapp1.0;netcoreapp1.1;netcoreapp2.0</TargetFrameworks>
<TargetFrameworks>net45;net46;net48;netcoreapp3.0</TargetFrameworks>
<IsPackable>false</IsPackable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.5.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.8.3" />
<PackageReference Include="Moq" Version="4.10.1" />
<PackageReference Include="MSTest.TestAdapter" Version="1.2.0" />
<PackageReference Include="MSTest.TestFramework" Version="1.2.0" />
<PackageReference Include="MSTest.TestAdapter" Version="2.1.2" />
<PackageReference Include="MSTest.TestFramework" Version="2.1.2" />
<PackageReference Include="RichardSzalay.MockHttp" Version="5.0.0" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\WebPush\WebPush.csproj" />
</ItemGroup>

<ItemGroup Condition="'$(TargetFramework)' == 'netcoreapp2.0'">
<PackageReference Include="System.Security.Cryptography.Cng">
<Version>5.0.0</Version>
</PackageReference>
</ItemGroup>

</Project>
14 changes: 14 additions & 0 deletions WebPush/Model/AsymmetricKeyPair.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace WebPush
{
public class AsymmetricKeyPair
{
public byte[] PublicKey;
public byte[] PrivateKey;
}
}
8 changes: 4 additions & 4 deletions WebPush/Properties/PublishProfiles/FolderProfile.pubxml
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
This file is used by the publish/package process of your project. You can customize the behavior of this process
by editing this MSBuild file. In order to learn more about this please visit https://go.microsoft.com/fwlink/?LinkID=208121.
https://go.microsoft.com/fwlink/?LinkID=208121.
-->
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<PublishProtocol>FileSystem</PublishProtocol>
<Configuration>Release</Configuration>
<TargetFramework>netstandard1.1</TargetFramework>
<PublishDir>bin\Release\PublishOutput</PublishDir>
<TargetFramework>net45</TargetFramework>
<PublishDir>C:\net\NugetPackage</PublishDir>
<Platform>Any CPU</Platform>
</PropertyGroup>
</Project>
144 changes: 103 additions & 41 deletions WebPush/Util/ECKeyHelper.cs
Original file line number Diff line number Diff line change
@@ -1,64 +1,126 @@
using System;
using System.IO;
using Org.BouncyCastle.Asn1;
using Org.BouncyCastle.Asn1.Nist;
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.OpenSsl;
using Org.BouncyCastle.Security;
using System.Security.Cryptography;
using System.Linq;

namespace WebPush.Util
{
internal static class ECKeyHelper
{
public static ECPrivateKeyParameters GetPrivateKey(byte[] privateKey)
public static byte[] GetECPublicKey(this CngKey key)
{
Asn1Object version = new DerInteger(1);
Asn1Object derEncodedKey = new DerOctetString(privateKey);
Asn1Object keyTypeParameters = new DerTaggedObject(0, new DerObjectIdentifier(@"1.2.840.10045.3.1.7"));
var cngKey = key.Export(CngKeyBlobFormat.EccPublicBlob);
return new byte[] { 0x04 }.Concat(cngKey.Skip(8)).ToArray();
}

Asn1Object derSequence = new DerSequence(version, derEncodedKey, keyTypeParameters);
public static byte[] GetECPrivateKey(this CngKey key)
{
var cngKey = key.Export(CngKeyBlobFormat.EccPrivateBlob);
return cngKey.Skip(8 + 32 + 32).Take(32).ToArray();
}

var base64EncodedDerSequence = Convert.ToBase64String(derSequence.GetDerEncoded());
var pemKey = "-----BEGIN EC PRIVATE KEY-----\n";
pemKey += base64EncodedDerSequence;
pemKey += "\n-----END EC PRIVATE KEY----";
public static CngKey GetPublicKey(byte[] key)
{
var keyType = new byte[] { 0x45, 0x43, 0x4B, 0x31 };
var keyLength = new byte[] { 0x20, 0x00, 0x00, 0x00 };

var reader = new StringReader(pemKey);
var pemReader = new PemReader(reader);
var keyPair = (AsymmetricCipherKeyPair) pemReader.ReadObject();
var keyImport = keyType.Concat(keyLength).Concat(key.Skip(1)).ToArray();

return (ECPrivateKeyParameters) keyPair.Private;
return CngKey.Import(keyImport, CngKeyBlobFormat.EccPublicBlob);
}
#if NET48
public static AsymmetricAlgorithm GetPrivateKey(byte[] privateKey)
{
return ECDsaCng.Create(new ECParameters
{
Curve = ECCurve.NamedCurves.nistP256,
D = privateKey,
Q = new ECPoint(){ X = new byte[32],Y = new byte[32]}
});
}
public static AsymmetricKeyPair GenerateKeys()
{

public static ECPublicKeyParameters GetPublicKey(byte[] publicKey)
using (var cng = new ECDiffieHellmanCng(ECCurve.NamedCurves.nistP256))
{
cng.GenerateKey(ECCurve.NamedCurves.nistP256);
var parameters = cng.ExportParameters(true);
var pr = parameters.D.ToArray();
var pub = new byte[] { 0x04 }.Concat(parameters.Q.X).Concat(parameters.Q.Y).ToArray();
return new AsymmetricKeyPair() { PublicKey = pub,PrivateKey = pr };
}
}
#else
private static CngKey ImportPrivCngKey(byte[] pubKey, byte[] privKey)
{
Asn1Object keyTypeParameters = new DerSequence(new DerObjectIdentifier(@"1.2.840.10045.2.1"),
new DerObjectIdentifier(@"1.2.840.10045.3.1.7"));
Asn1Object derEncodedKey = new DerBitString(publicKey);
// to import keys to CngKey in ECCPublicKeyBlob and ECCPrivateKeyBlob format, keys should be form in specific formats as noted here :
// https://stackoverflow.com/a/24255090
// magic prefixes : https://referencesource.microsoft.com/#system.core/System/Security/Cryptography/BCryptNative.cs,fde0749a0a5f70d8,references
var keyType = new byte[] { 0x45, 0x43, 0x53, 0x32 };
var keyLength = new byte[] { 0x20, 0x00, 0x00, 0x00 };

Asn1Object derSequence = new DerSequence(keyTypeParameters, derEncodedKey);
var key = pubKey.Skip(1);

var base64EncodedDerSequence = Convert.ToBase64String(derSequence.GetDerEncoded());
var pemKey = "-----BEGIN PUBLIC KEY-----\n";
pemKey += base64EncodedDerSequence;
pemKey += "\n-----END PUBLIC KEY-----";
var keyImport = keyType.Concat(keyLength).Concat(key).Concat(privKey).ToArray();

var reader = new StringReader(pemKey);
var pemReader = new PemReader(reader);
var keyPair = pemReader.ReadObject();
return (ECPublicKeyParameters) keyPair;
var cngKey = CngKey.Import(keyImport, CngKeyBlobFormat.EccPrivateBlob);
return cngKey;
}
public static ECDsaCng GetPrivateKey(byte[] privateKey)
{
var fakePubKey = new byte[64];
var publicKey = (new byte[] { 0x04 }).Concat(fakePubKey).ToArray();

public static AsymmetricCipherKeyPair GenerateKeys()
var cngKey = ImportPrivCngKey(publicKey, privateKey);
var ecDsaCng = new ECDsaCng(cngKey);
ecDsaCng.HashAlgorithm = CngAlgorithm.ECDsaP256;
return ecDsaCng;
}

public static bool CngKeyExists(string keyName, CngProvider cp)
{
var ecParameters = NistNamedCurves.GetByName("P-256");
var ecSpec = new ECDomainParameters(ecParameters.Curve, ecParameters.G, ecParameters.N, ecParameters.H,
ecParameters.GetSeed());
var keyPairGenerator = GeneratorUtilities.GetKeyPairGenerator("ECDH");
keyPairGenerator.Init(new ECKeyGenerationParameters(ecSpec, new SecureRandom()));
if (string.IsNullOrEmpty(keyName))
return false;
try
{
return CngKey.Exists(keyName, cp);
}
catch (CryptographicException) { }
return false;
}

return keyPairGenerator.GenerateKeyPair();
public static AsymmetricKeyPair GenerateKeys()
{
//CngProvider cp = CngProvider.MicrosoftSoftwareKeyStorageProvider;
//string keyName = "tempvapidkey";
//if (CngKeyExists(keyName, cp))
//{
// using (CngKey cngKey = CngKey.Open(keyName, cp))
// cngKey.Delete();
//}
CngKeyCreationParameters kcp = new CngKeyCreationParameters
{
//Provider = cp,
ExportPolicy = CngExportPolicies.AllowPlaintextExport
};
try
{
using (CngKey myKey = CngKey.Create(CngAlgorithm.ECDiffieHellmanP256, null, kcp))
{
return new AsymmetricKeyPair()
{
PublicKey = myKey.GetECPublicKey(),
PrivateKey = myKey.GetECPrivateKey()
};
}
}
finally
{
//if (CngKeyExists(keyName, cp))
//{
// using (CngKey cngKey = CngKey.Open(keyName, cp))
// cngKey.Delete();
//}
}
}
#endif
}
}
Loading