Skip to content

Commit

Permalink
Entirely .NET based ECDH agreement algorithm for .NET 5 (#4)
Browse files Browse the repository at this point in the history
  • Loading branch information
tpeczek committed Dec 9, 2020
1 parent 927dc7f commit d8c715b
Show file tree
Hide file tree
Showing 4 changed files with 135 additions and 29 deletions.
15 changes: 15 additions & 0 deletions src/Lib.Net.Http.WebPush/Internals/ECDHAgreement.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
namespace Lib.Net.Http.WebPush.Internals
{
internal readonly struct ECDHAgreement
{
public byte[] PublicKey { get; }

public byte[] SharedSecretHmac { get; }

public ECDHAgreement(byte[] publicKey, byte[] sharedSecretHmac)
{
PublicKey = publicKey;
SharedSecretHmac = sharedSecretHmac;
}
}
}
Original file line number Diff line number Diff line change
@@ -1,27 +1,53 @@
using System;
#if NET451 || NET461 || NETSTANDARD1_6 || NETSTANDARD2_0
using System;
using System.IO;
using System.Security.Cryptography;
using Org.BouncyCastle.Asn1;
using Org.BouncyCastle.Asn1.Nist;
using Org.BouncyCastle.Asn1.X9;
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.Asn1.X9;
using Org.BouncyCastle.OpenSsl;
using Org.BouncyCastle.Security;
using Org.BouncyCastle.Asn1.Nist;
using Org.BouncyCastle.Crypto.Parameters;

namespace Lib.Net.Http.WebPush.Internals
{
internal static class ECKeyHelper
internal static class ECDHAgreementCalculator
{
private const string PRIVATE_DER_IDENTIFIER = "1.2.840.10045.3.1.7";

private const string PUBLIC_DER_IDENTIFIER = "1.2.840.10045.2.1";

private const string PUBLIC_PEM_KEY_PREFIX = "-----BEGIN PUBLIC KEY-----\n";
private const string PUBLIC_PEM_KEY_SUFFIX = "\n-----END PUBLIC KEY-----";

private const string P256_CURVE_NAME = "P-256";
private const string ECDH_ALGORITHM_NAME = "ECDH";

internal static ECPublicKeyParameters GetECPublicKeyParameters(byte[] publicKey)
public static ECDHAgreement CalculateAgreement(byte[] otherPartyPublicKey, byte[] hmacKey)
{
AsymmetricCipherKeyPair agreementKeyPair = GenerateAsymmetricCipherKeyPair();
byte[] agreementPublicKey = ((ECPublicKeyParameters)agreementKeyPair.Public).Q.GetEncoded(false);

IBasicAgreement agreement = AgreementUtilities.GetBasicAgreement(ECDH_ALGORITHM_NAME);
agreement.Init(agreementKeyPair.Private);

byte[] sharedSecret = agreement.CalculateAgreement(GetECPublicKeyParameters(otherPartyPublicKey)).ToByteArrayUnsigned();

return new ECDHAgreement(agreementPublicKey, HmacSha256(hmacKey, sharedSecret));
}

private static AsymmetricCipherKeyPair GenerateAsymmetricCipherKeyPair()
{
X9ECParameters ecParameters = NistNamedCurves.GetByName(P256_CURVE_NAME);
ECDomainParameters ecDomainParameters = new ECDomainParameters(ecParameters.Curve, ecParameters.G, ecParameters.N, ecParameters.H, ecParameters.GetSeed());

IAsymmetricCipherKeyPairGenerator keyPairGenerator = GeneratorUtilities.GetKeyPairGenerator(ECDH_ALGORITHM_NAME);
keyPairGenerator.Init(new ECKeyGenerationParameters(ecDomainParameters, new SecureRandom()));

return keyPairGenerator.GenerateKeyPair();
}

private static ECPublicKeyParameters GetECPublicKeyParameters(byte[] publicKey)
{
Asn1Object derSequence = new DerSequence(
new DerSequence(new DerObjectIdentifier(PUBLIC_DER_IDENTIFIER), new DerObjectIdentifier(PRIVATE_DER_IDENTIFIER)),
Expand All @@ -36,15 +62,17 @@ internal static ECPublicKeyParameters GetECPublicKeyParameters(byte[] publicKey)
return (ECPublicKeyParameters)pemKeyReader.ReadObject();
}

internal static AsymmetricCipherKeyPair GenerateAsymmetricCipherKeyPair()
private static byte[] HmacSha256(byte[] key, byte[] value)
{
X9ECParameters ecParameters = NistNamedCurves.GetByName(P256_CURVE_NAME);
ECDomainParameters ecDomainParameters = new ECDomainParameters(ecParameters.Curve, ecParameters.G, ecParameters.N, ecParameters.H, ecParameters.GetSeed());
byte[] hash = null;

IAsymmetricCipherKeyPairGenerator keyPairGenerator = GeneratorUtilities.GetKeyPairGenerator(ECDH_ALGORITHM_NAME);
keyPairGenerator.Init(new ECKeyGenerationParameters(ecDomainParameters, new SecureRandom()));
using (HMACSHA256 hasher = new HMACSHA256(key))
{
hash = hasher.ComputeHash(value);
}

return keyPairGenerator.GenerateKeyPair();
return hash;
}
}
}
#endif
68 changes: 68 additions & 0 deletions src/Lib.Net.Http.WebPush/Internals/ECDHAgreementCalculator.NET.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
#if !NET451 && !NET461 && !NETSTANDARD1_6 && !NETSTANDARD2_0
using System;
using System.Formats.Asn1;
using System.Security.Cryptography;

namespace Lib.Net.Http.WebPush.Internals
{
internal static class ECDHAgreementCalculator
{
private const string PRIVATE_DER_IDENTIFIER = "1.2.840.10045.3.1.7";
private const string PUBLIC_DER_IDENTIFIER = "1.2.840.10045.2.1";

private const string PUBLIC_PEM_KEY_PREFIX = "-----BEGIN PUBLIC KEY-----";
private const string PUBLIC_PEM_KEY_SUFFIX = "-----END PUBLIC KEY-----";

public static ECDHAgreement CalculateAgreement(byte[] otherPartyPublicKey, byte[] hmacKey)
{
using (ECDiffieHellman agreement = ECDiffieHellman.Create(ECCurve.NamedCurves.nistP256))
{
byte[] agreementPublicKey = GetAgreementPublicKey(agreement);

byte[] sharedSecretHmac = agreement.DeriveKeyFromHmac(GetECDiffieHellmanPublicKey(otherPartyPublicKey), HashAlgorithmName.SHA256, hmacKey);

return new ECDHAgreement(agreementPublicKey, sharedSecretHmac);
}
}

private static byte[] GetAgreementPublicKey(ECDiffieHellman agreement)
{
ECParameters agreementParameters = agreement.ExportParameters(false);

byte[] agreementPublicKey = new byte[agreementParameters.Q.X.Length + agreementParameters.Q.Y.Length + 1];

agreementPublicKey[0] = 0x04;
Array.Copy(agreementParameters.Q.X, 0, agreementPublicKey, 1, agreementParameters.Q.X.Length);
Array.Copy(agreementParameters.Q.Y, 0, agreementPublicKey, agreementParameters.Q.X.Length + 1, agreementParameters.Q.Y.Length);

return agreementPublicKey;
}

private static ECDiffieHellmanPublicKey GetECDiffieHellmanPublicKey(byte[] publicKey)
{
using (ECDiffieHellman ecdhAgreement = ECDiffieHellman.Create(ECCurve.NamedCurves.nistP256))
{
ecdhAgreement.ImportFromPem(GetPublicKeyPem(publicKey));

return ecdhAgreement.PublicKey;
}
}

private static ReadOnlySpan<char> GetPublicKeyPem(byte[] publicKey)
{
AsnWriter asnWriter = new AsnWriter(AsnEncodingRules.DER);
asnWriter.PushSequence();
asnWriter.PushSequence();
asnWriter.WriteObjectIdentifier(PUBLIC_DER_IDENTIFIER);
asnWriter.WriteObjectIdentifier(PRIVATE_DER_IDENTIFIER);
asnWriter.PopSequence();
asnWriter.WriteBitString(publicKey);
asnWriter.PopSequence();

return PUBLIC_PEM_KEY_PREFIX + Environment.NewLine
+ Convert.ToBase64String(asnWriter.Encode()) + Environment.NewLine
+ PUBLIC_PEM_KEY_SUFFIX;
}
}
}
#endif
27 changes: 11 additions & 16 deletions src/Lib.Net.Http.WebPush/PushServiceClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,6 @@
using System.Threading.Tasks;
using System.Collections.Generic;
using System.Security.Cryptography;
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.Security;
using Lib.Net.Http.EncryptedContentEncoding;
using Lib.Net.Http.WebPush.Internals;
using Lib.Net.Http.WebPush.Authentication;
Expand Down Expand Up @@ -294,31 +291,29 @@ private static HttpRequestMessage SetContent(HttpRequestMessage pushMessageDeliv
}
else
{
AsymmetricCipherKeyPair applicationServerKeys = ECKeyHelper.GenerateAsymmetricCipherKeyPair();
byte[] applicationServerPublicKey = ((ECPublicKeyParameters)applicationServerKeys.Public).Q.GetEncoded(false);
ECDHAgreement keyAgreement = ECDHAgreementCalculator.CalculateAgreement
(
UrlBase64Converter.FromUrlBase64String(subscription.GetKey(PushEncryptionKeyName.P256DH)),
UrlBase64Converter.FromUrlBase64String(subscription.GetKey(PushEncryptionKeyName.Auth))
);

pushMessageDeliveryRequest.Content = new Aes128GcmEncodedContent(
httpContent,
GetKeyingMaterial(subscription, applicationServerKeys.Private, applicationServerPublicKey),
applicationServerPublicKey,
GetKeyingMaterial(subscription, keyAgreement),
keyAgreement.PublicKey,
CONTENT_RECORD_SIZE
);
}

return pushMessageDeliveryRequest;
}

private static byte[] GetKeyingMaterial(PushSubscription subscription, AsymmetricKeyParameter applicationServerPrivateKey, byte[] applicationServerPublicKey)
private static byte[] GetKeyingMaterial(PushSubscription subscription, ECDHAgreement keyAgreement)
{
IBasicAgreement ecdhAgreement = AgreementUtilities.GetBasicAgreement("ECDH");
ecdhAgreement.Init(applicationServerPrivateKey);

byte[] userAgentPublicKey = UrlBase64Converter.FromUrlBase64String(subscription.GetKey(PushEncryptionKeyName.P256DH));
byte[] authenticationSecret = UrlBase64Converter.FromUrlBase64String(subscription.GetKey(PushEncryptionKeyName.Auth));
byte[] sharedSecret = ecdhAgreement.CalculateAgreement(ECKeyHelper.GetECPublicKeyParameters(userAgentPublicKey)).ToByteArrayUnsigned();
byte[] sharedSecretHash = HmacSha256(authenticationSecret, sharedSecret);
byte[] infoParameter = GetKeyingMaterialInfoParameter(userAgentPublicKey, applicationServerPublicKey);
byte[] keyingMaterial = HmacSha256(sharedSecretHash, infoParameter);

byte[] infoParameter = GetKeyingMaterialInfoParameter(userAgentPublicKey, keyAgreement.PublicKey);
byte[] keyingMaterial = HmacSha256(keyAgreement.SharedSecretHmac, infoParameter);

return keyingMaterial;
}
Expand Down

0 comments on commit d8c715b

Please sign in to comment.