diff --git a/.travis.yml b/.travis.yml index fd30ea6..7e5b8d0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -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 \ No newline at end of file + - dotnet test --framework=netcoreapp3.0 WebPush.Test/WebPush.Test.csproj diff --git a/WebPush.Test/ECKeyHelperTest.cs b/WebPush.Test/ECKeyHelperTest.cs index ae2ef6d..747d49c 100644 --- a/WebPush.Test/ECKeyHelperTest.cs +++ b/WebPush.Test/ECKeyHelperTest.cs @@ -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 @@ -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; @@ -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)); @@ -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] @@ -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); } diff --git a/WebPush.Test/WebPush.Test.csproj b/WebPush.Test/WebPush.Test.csproj index e49096a..7624825 100755 --- a/WebPush.Test/WebPush.Test.csproj +++ b/WebPush.Test/WebPush.Test.csproj @@ -1,15 +1,15 @@  - netcoreapp1.0;netcoreapp1.1;netcoreapp2.0 + net45;net46;net48;netcoreapp3.0 false - + - - + + @@ -17,4 +17,10 @@ + + + 5.0.0 + + + diff --git a/WebPush/Model/AsymmetricKeyPair.cs b/WebPush/Model/AsymmetricKeyPair.cs new file mode 100644 index 0000000..a9049a8 --- /dev/null +++ b/WebPush/Model/AsymmetricKeyPair.cs @@ -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; + } +} diff --git a/WebPush/Properties/PublishProfiles/FolderProfile.pubxml b/WebPush/Properties/PublishProfiles/FolderProfile.pubxml index 8af1ca3..04b211c 100644 --- a/WebPush/Properties/PublishProfiles/FolderProfile.pubxml +++ b/WebPush/Properties/PublishProfiles/FolderProfile.pubxml @@ -1,13 +1,13 @@  FileSystem Release - netstandard1.1 - bin\Release\PublishOutput + net45 + C:\net\NugetPackage + Any CPU \ No newline at end of file diff --git a/WebPush/Util/ECKeyHelper.cs b/WebPush/Util/ECKeyHelper.cs index 5655908..91218a9 100644 --- a/WebPush/Util/ECKeyHelper.cs +++ b/WebPush/Util/ECKeyHelper.cs @@ -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 } } \ No newline at end of file diff --git a/WebPush/Util/Encryptor.cs b/WebPush/Util/Encryptor.cs index d8b294f..59957ce 100644 --- a/WebPush/Util/Encryptor.cs +++ b/WebPush/Util/Encryptor.cs @@ -1,25 +1,22 @@ using System; using System.Collections.Generic; +using System.IO; using System.Linq; +using System.Security.Cryptography; using System.Text; -using Org.BouncyCastle.Crypto.Digests; -using Org.BouncyCastle.Crypto.Engines; -using Org.BouncyCastle.Crypto.Macs; -using Org.BouncyCastle.Crypto.Modes; -using Org.BouncyCastle.Crypto.Parameters; -using Org.BouncyCastle.Security; +using Security.Cryptography; namespace WebPush.Util { // @LogicSoftware // Originally from https://github.com/LogicSoftware/WebPushEncryption/blob/master/src/Encryptor.cs - internal static class Encryptor + public static class Encryptor { public static EncryptionResult Encrypt(string userKey, string userSecret, string payload) { - var userKeyBytes = UrlBase64.Decode(userKey); - var userSecretBytes = UrlBase64.Decode(userSecret); - var payloadBytes = Encoding.UTF8.GetBytes(payload); + byte[] userKeyBytes = UrlBase64.Decode(userKey); + byte[] userSecretBytes = UrlBase64.Decode(userSecret); + byte[] payloadBytes = Encoding.UTF8.GetBytes(payload); return Encrypt(userKeyBytes, userSecretBytes, payloadBytes); } @@ -27,21 +24,27 @@ public static EncryptionResult Encrypt(string userKey, string userSecret, string public static EncryptionResult Encrypt(byte[] userKey, byte[] userSecret, byte[] payload) { var salt = GenerateSalt(16); - var serverKeyPair = ECKeyHelper.GenerateKeys(); - var ecdhAgreement = AgreementUtilities.GetBasicAgreement("ECDH"); - ecdhAgreement.Init(serverKeyPair.Private); + byte[] serverPublicKey = null; + byte[] key = null; - var userPublicKey = ECKeyHelper.GetPublicKey(userKey); + var cgnKey = ImportCngKeyFromPublicKey(userKey); + using (ECDiffieHellmanCng alice = new ECDiffieHellmanCng(256)) + { + alice.KeyDerivationFunction = ECDiffieHellmanKeyDerivationFunction.Hmac; + alice.HashAlgorithm = CngAlgorithm.Sha256; + alice.HmacKey = userSecret; - var key = ecdhAgreement.CalculateAgreement(userPublicKey).ToByteArrayUnsigned(); - var serverPublicKey = ((ECPublicKeyParameters) serverKeyPair.Public).Q.GetEncoded(false); + serverPublicKey = ImportPublicKeyFromCngKey(alice.PublicKey.ToByteArray()); + key = alice.DeriveKeyMaterial(cgnKey); + } - var prk = HKDF(userSecret, key, Encoding.UTF8.GetBytes("Content-Encoding: auth\0"), 32); - var cek = HKDF(salt, prk, CreateInfoChunk("aesgcm", userKey, serverPublicKey), 16); - var nonce = HKDF(salt, prk, CreateInfoChunk("nonce", userKey, serverPublicKey), 12); + var prk = HKDFSecondStep(key, Encoding.UTF8.GetBytes("Content-Encoding: auth\0"), 32); + byte[] cek = HKDF(salt, prk, CreateInfoChunk("aesgcm", userKey, serverPublicKey), 16); + byte[] nonce = HKDF(salt, prk, CreateInfoChunk("nonce", userKey, serverPublicKey), 12); var input = AddPaddingToInput(payload); + var encryptedMessage = EncryptAes(nonce, cek, input); return new EncryptionResult @@ -52,6 +55,23 @@ public static EncryptionResult Encrypt(byte[] userKey, byte[] userSecret, byte[] }; } + private static CngKey ImportCngKeyFromPublicKey(byte[] userKey) + { + var keyType = new byte[] { 0x45, 0x43, 0x4B, 0x31 }; + var keyLength = new byte[] { 0x20, 0x00, 0x00, 0x00 }; + + var keyImport = keyType.Concat(keyLength).Concat(userKey.Skip(1)).ToArray(); + + return CngKey.Import(keyImport, CngKeyBlobFormat.EccPublicBlob); + } + + private static byte[] ImportPublicKeyFromCngKey(byte[] cngKey) + { + var keyImport = (new byte[] { 0x04 }).Concat(cngKey.Skip(8)).ToArray(); + + return keyImport; + } + private static byte[] GenerateSalt(int length) { var salt = new byte[length]; @@ -68,25 +88,39 @@ private static byte[] AddPaddingToInput(byte[] data) return input; } + private static byte[] EncryptAes(byte[] nonce, byte[] cek, byte[] message) { - var cipher = new GcmBlockCipher(new AesEngine()); - var parameters = new AeadParameters(new KeyParameter(cek), 128, nonce); - cipher.Init(true, parameters); + using (AuthenticatedAesCng aes = new AuthenticatedAesCng()) + { + aes.CngMode = CngChainingMode.Gcm; + + aes.Key = cek; - //Generate Cipher Text With Auth Tag - var cipherText = new byte[cipher.GetOutputSize(message.Length)]; - var len = cipher.ProcessBytes(message, 0, message.Length, cipherText, 0); - cipher.DoFinal(cipherText, len); + aes.IV = nonce; - //byte[] tag = cipher.GetMac(); - return cipherText; + using (MemoryStream ms = new MemoryStream()) + using (var encryptor = aes.CreateAuthenticatedEncryptor()) + using (CryptoStream cs = new CryptoStream(ms, encryptor, CryptoStreamMode.Write)) + { + // Encrypt the secret message + cs.Write(message, 0, message.Length); + + // Finish the encryption and get the output authentication tag and ciphertext + cs.FlushFinalBlock(); + var ciphertext = ms.ToArray(); + + var tag = encryptor.GetTag(); + + return ciphertext.Concat(tag).ToArray(); + } + } } public static byte[] HKDFSecondStep(byte[] key, byte[] info, int length) { - var hmac = new HmacSha256(key); - var infoAndOne = info.Concat(new byte[] {0x01}).ToArray(); + var hmac = new HMACSHA256(key); + var infoAndOne = info.Concat(new byte[] { 0x01 }).ToArray(); var result = hmac.ComputeHash(infoAndOne); if (result.Length > length) @@ -99,7 +133,7 @@ public static byte[] HKDFSecondStep(byte[] key, byte[] info, int length) public static byte[] HKDF(byte[] salt, byte[] prk, byte[] info, int length) { - var hmac = new HmacSha256(salt); + var hmac = new HMACSHA256(salt); var key = hmac.ComputeHash(prk); return HKDFSecondStep(key, info, length); @@ -127,24 +161,4 @@ public static byte[] CreateInfoChunk(string type, byte[] recipientPublicKey, byt return output.ToArray(); } } - - public class HmacSha256 - { - private readonly HMac _hmac; - - public HmacSha256(byte[] key) - { - _hmac = new HMac(new Sha256Digest()); - _hmac.Init(new KeyParameter(key)); - } - - public byte[] ComputeHash(byte[] value) - { - var resBuf = new byte[_hmac.GetMacSize()]; - _hmac.BlockUpdate(value, 0, value.Length); - _hmac.DoFinal(resBuf, 0); - - return resBuf; - } - } } \ No newline at end of file diff --git a/WebPush/Util/JwsSigner.cs b/WebPush/Util/JwsSigner.cs index f000ff2..a165e8c 100644 --- a/WebPush/Util/JwsSigner.cs +++ b/WebPush/Util/JwsSigner.cs @@ -1,19 +1,16 @@ using System; using System.Collections.Generic; -using System.Linq; using System.Text; using Newtonsoft.Json; -using Org.BouncyCastle.Crypto.Digests; -using Org.BouncyCastle.Crypto.Parameters; -using Org.BouncyCastle.Crypto.Signers; +using System.Security.Cryptography; namespace WebPush.Util { internal class JwsSigner { - private readonly ECPrivateKeyParameters _privateKey; + private readonly AsymmetricAlgorithm _privateKey; - public JwsSigner(ECPrivateKeyParameters privateKey) + public JwsSigner(AsymmetricAlgorithm privateKey) { _privateKey = privateKey; } @@ -30,24 +27,17 @@ public string GenerateSignature(Dictionary header, Dictionary GetVapidHeaders(string audience, string ValidatePublicKey(publicKey); ValidatePrivateKey(privateKey); + var decodedPublicKey = UrlBase64.Decode(publicKey); var decodedPrivateKey = UrlBase64.Decode(privateKey); if (expiration == -1) @@ -50,25 +50,27 @@ public static Dictionary GetVapidHeaders(string audience, string } else { - ValidateExpiration(expiration); + ValidateExpiration(expiration); } - var header = new Dictionary {{"typ", "JWT"}, {"alg", "ES256"}}; + var header = new Dictionary { { "typ", "JWT" }, { "alg", "ES256" } }; - var jwtPayload = new Dictionary {{"aud", audience}, {"exp", expiration}, {"sub", subject}}; + var jwtPayload = new Dictionary { { "aud", audience }, { "exp", expiration }, { "sub", subject } }; - var signingKey = ECKeyHelper.GetPrivateKey(decodedPrivateKey); + using (var signingKey = ECKeyHelper.GetPrivateKey(decodedPrivateKey)) + { - var signer = new JwsSigner(signingKey); - var token = signer.GenerateSignature(header, jwtPayload); + var signer = new JwsSigner(signingKey); + var token = signer.GenerateSignature(header, jwtPayload); - var results = new Dictionary - { - {"Authorization", "WebPush " + token}, {"Crypto-Key", "p256ecdsa=" + publicKey} - }; + var results = new Dictionary + { + {"Authorization", "WebPush " + token}, {"Crypto-Key", "p256ecdsa=" + publicKey} + }; - return results; + return results; + } } public static void ValidateAudience(string audience) @@ -152,7 +154,7 @@ private static void ValidateExpiration(long expiration) private static long UnixTimeNow() { var timeSpan = DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0); - return (long) timeSpan.TotalSeconds; + return (long)timeSpan.TotalSeconds; } private static byte[] ByteArrayPadLeft(byte[] src, int size) diff --git a/WebPush/WebPush.csproj b/WebPush/WebPush.csproj index 016d102..6db90d8 100755 --- a/WebPush/WebPush.csproj +++ b/WebPush/WebPush.csproj @@ -1,7 +1,7 @@  - netstandard1.1;netstandard2.0;net45;net46 + net45;net46;net48;netcoreapp3.0; true 1.0.11 Cory Thompson @@ -24,8 +24,15 @@ - + - - + + + + + + 5.0.0 + + + \ No newline at end of file diff --git a/WebPush/WebPushClient.cs b/WebPush/WebPushClient.cs index 547d1e6..c57306e 100755 --- a/WebPush/WebPushClient.cs +++ b/WebPush/WebPushClient.cs @@ -26,14 +26,14 @@ public class WebPushClient : IDisposable public WebPushClient() { - + } public WebPushClient(HttpClient httpClient) { _httpClient = httpClient; } - + public WebPushClient(HttpClientHandler httpClientHandler) { _httpClientHandler = httpClientHandler; @@ -142,7 +142,7 @@ public HttpRequestMessage GenerateRequestDetails(PushSubscription subscription, if (options != null) { - var validOptionsKeys = new List {"headers", "gcmAPIKey", "vapidDetails", "TTL"}; + var validOptionsKeys = new List { "headers", "gcmAPIKey", "vapidDetails", "TTL" }; foreach (var key in options.Keys) { if (!validOptionsKeys.Contains(key)) @@ -181,7 +181,7 @@ public HttpRequestMessage GenerateRequestDetails(PushSubscription subscription, } //at this stage ttl cannot be null. - timeToLive = (int) ttl; + timeToLive = (int)ttl; } } @@ -277,7 +277,7 @@ public void SendNotification(PushSubscription subscription, string payload = nul /// The vapid details for the notification. public void SendNotification(PushSubscription subscription, string payload, VapidDetails vapidDetails) { - var options = new Dictionary {["vapidDetails"] = vapidDetails}; + var options = new Dictionary { ["vapidDetails"] = vapidDetails }; SendNotification(subscription, payload, options); } @@ -290,7 +290,7 @@ public void SendNotification(PushSubscription subscription, string payload, Vapi /// The GCM API key public void SendNotification(PushSubscription subscription, string payload, string gcmApiKey) { - var options = new Dictionary {["gcmAPIKey"] = gcmApiKey}; + var options = new Dictionary { ["gcmAPIKey"] = gcmApiKey }; SendNotification(subscription, payload, options); } @@ -323,7 +323,7 @@ public async Task SendNotificationAsync(PushSubscription subscription, string pa public async Task SendNotificationAsync(PushSubscription subscription, string payload, VapidDetails vapidDetails) { - var options = new Dictionary {["vapidDetails"] = vapidDetails}; + var options = new Dictionary { ["vapidDetails"] = vapidDetails }; await SendNotificationAsync(subscription, payload, options); } @@ -336,7 +336,7 @@ public async Task SendNotificationAsync(PushSubscription subscription, string pa /// The GCM API key public async Task SendNotificationAsync(PushSubscription subscription, string payload, string gcmApiKey) { - var options = new Dictionary {["gcmAPIKey"] = gcmApiKey}; + var options = new Dictionary { ["gcmAPIKey"] = gcmApiKey }; await SendNotificationAsync(subscription, payload, options); } @@ -355,7 +355,7 @@ private static void HandleResponse(HttpResponseMessage response, PushSubscriptio } // Error - var message = @"Received unexpected response code: " + (int) response.StatusCode; + var message = @"Received unexpected response code: " + (int)response.StatusCode; switch (response.StatusCode) { case HttpStatusCode.BadRequest: @@ -366,7 +366,7 @@ private static void HandleResponse(HttpResponseMessage response, PushSubscriptio message = "Payload too large"; break; - case (HttpStatusCode) 429: + case (HttpStatusCode)429: message = "Too many request."; break; @@ -378,7 +378,7 @@ private static void HandleResponse(HttpResponseMessage response, PushSubscriptio throw new WebPushException(message, response.StatusCode, response.Headers, subscription); } - + public void Dispose() { if (_httpClient != null && _isHttpClientInternallyCreated)