2016-07-02 4 views
4

Мне удастся отправить пустые уведомления о push-адресах на chrome и firefox, используя аналогичные методы, хотя я пытаюсь сделать мои уведомления более подробными, однако я не могу найти пример подробных уведомлений веб-страниц с использованием .net в качестве бэкэнд.Есть ли способ шифрования полезной нагрузки WebPush в .net?

Мой светлячок пример выглядит следующим образом:

Shared Function sendPushFox(ByVal value As String) As String 
    Dim toret As String = "" 
    Dim query As String = "SELECT subscribeid FROM custom_user_data WHERE NOT subscribeid = ' ';" 
    Dim connection As New MySqlConnection(Utils.connectionString) : connection.Open() 
    Dim command As MySqlCommand = New MySqlCommand(query, connection) 
    Dim reader As MySqlDataReader = command.ExecuteReader() 
    Dim regList As New List(Of String) 
    Do While reader.Read 
     regList.Add(reader.GetString(0)) 
    Loop 
    Dim query2 As String = "SELECT p256dh FROM custom_user_data WHERE NOT p256dh = ' ';" 
    Dim connection2 As New MySqlConnection(Utils.connectionString) : connection2.Open() 
    Dim command2 As MySqlCommand = New MySqlCommand(query2, connection2) 
    Dim reader2 As MySqlDataReader = command2.ExecuteReader() 
    Dim regList2 As New List(Of String) 
    Do While reader.Read 
     regList2.Add(reader.GetString(0)) 
    Loop 
    Dim query3 As String = "SELECT authsecret FROM custom_user_data WHERE NOT authsecret = ' ';" 
    Dim connection3 As New MySqlConnection(Utils.connectionString) : connection3.Open() 
    Dim command3 As MySqlCommand = New MySqlCommand(query3, connection3) 
    Dim reader3 As MySqlDataReader = command3.ExecuteReader() 
    Dim regList3 As New List(Of String) 
    Do While reader.Read 
     regList3.Add(reader.GetString(0)) 
    Loop 
    Dim reg1 = regList.ToArray 
    Dim reg2 = regList2.ToArray 
    Dim reg3 = regList3.ToArray 
    For Each Element As String In reg1 
     Try 
      Dim tRequest As WebRequest 
      tRequest = WebRequest.Create("https://updates.push.services.mozilla.com/push/v1/" & Element) 
      tRequest.Method = "post" 
      tRequest.ContentType = " application/json" 
      tRequest.Headers.Add("TTL: 1800") 
      tRequest.Headers.Add("payload: " + value) 
      For Each p25key As String In reg2 
       tRequest.Headers.Add("userPublicKey: " + p25key) 
      Next 
      For Each authkey As String In reg3 
       tRequest.Headers.Add("userAuth: " + authkey) 
      Next 
      Dim dataStream As Stream = tRequest.GetRequestStream() 
      Dim tResponse As WebResponse = tRequest.GetResponse() 
      dataStream = tResponse.GetResponseStream() 
      Dim tReader As New StreamReader(dataStream) 
      Dim sResponseFromServer As [String] = tReader.ReadToEnd() 
      toret = sResponseFromServer 
      tReader.Close() 
      dataStream.Close() 
      tResponse.Close() 
     Catch ex As Exception 
      Console.WriteLine(ex.Message) 
      Continue For 
     End Try 
    Next 
    Return toret 
End Function 

Ни userPublicKey или USERAUTH фактически используются прямо сейчас, и не служат какой-либо цели без шифрования полезной нагрузки, так что я читал, однако, используя vb.net, нет библиотеки .net для отправки push-уведомлений на веб-платформы (хром и FF-браузеры), и нет примеров, которые я могу найти где угодно, поэтому я немного застрял.

Как вы можете видеть, у меня есть конечная точка, p256dh и auth от каждого клиента, сохраненного в DB mysql, но с этого момента я не смог проработать до сих пор.

ответ

4

Кажется, что кто-то находит способ сделать это. Копирование с использованием раствора BouncyCastle из this blog:

/* 
* Built for .NET Core 1.0 on Windows 10 with Portable.BouncyCastle v1.8.1.1 
* 
* Tested on Chrome v53.0.2785.113 m (64-bit) and Firefox 48.0.2 
* 
* Massive thanks to Peter Beverloo for the following: 
* https://docs.google.com/document/d/1_kWRLJHRYN0KH73WipFyfIXI1UzZ5IyOYSs-y_mLxEE/ 
* https://tests.peter.sh/push-encryption-verifier/ 
* 
* Some more useful links: 
* https://developers.google.com/web/updates/2016/03/web-push-encryption?hl=en 
* https://github.com/web-push-libs/web-push/blob/master/src/index.js 
* 
* Copyright (C) 2016 BravoTango86 
* 
* Licensed under the Apache License, Version 2.0 (the "License"); 
* you may not use this file except in compliance with the License. 
* You may obtain a copy of the License at 
* 
*  http://www.apache.org/licenses/LICENSE-2.0 
* 
* Unless required by applicable law or agreed to in writing, software 
* distributed under the License is distributed on an "AS IS" BASIS, 
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
* See the License for the specific language governing permissions and 
* limitations under the License. 
*/ 

using Microsoft.AspNetCore.WebUtilities; 
using Org.BouncyCastle.Asn1.X9; 
using Org.BouncyCastle.Crypto; 
using Org.BouncyCastle.Crypto.Agreement; 
using Org.BouncyCastle.Crypto.Generators; 
using Org.BouncyCastle.Crypto.Parameters; 
using Org.BouncyCastle.Math; 
using Org.BouncyCastle.Security; 
using System; 
using System.Collections.Generic; 
using System.Net; 
using System.Net.Http; 
using System.Net.Http.Headers; 
using System.Text; 

public class WebPushHelper { 

    private const string FirebaseServerKey = ""; 

    public static bool SendNotification(JsonSubscription sub, byte[] data, int ttl = 0, ushort padding = 0, 
             bool randomisePadding = false) { 
     return SendNotification(endpoint: sub.endpoint, 
           data: data, 
           userKey: WebEncoders.Base64UrlDecode(sub.keys["p256dh"]), 
           userSecret: WebEncoders.Base64UrlDecode(sub.keys["auth"]), 
           ttl: ttl, 
           padding: padding, 
           randomisePadding: randomisePadding); 
    } 

    public static bool SendNotification(string endpoint, string data, string userKey, string userSecret, 
             int ttl = 0, ushort padding = 0, bool randomisePadding = false) { 
     return SendNotification(endpoint: endpoint, 
           data: Encoding.UTF8.GetBytes(data), 
           userKey: WebEncoders.Base64UrlDecode(userKey), 
           userSecret: WebEncoders.Base64UrlDecode(userSecret), 
           ttl: ttl, 
           padding: padding, 
           randomisePadding: randomisePadding); 
    } 

    public static bool SendNotification(string endpoint, byte[] userKey, byte[] userSecret, byte[] data = null, 
            int ttl = 0, ushort padding = 0, bool randomisePadding = false) { 
     HttpRequestMessage Request = new HttpRequestMessage(HttpMethod.Post, endpoint); 
     if (endpoint.StartsWith("https://android.googleapis.com/gcm/send/")) 
      Request.Headers.TryAddWithoutValidation("Authorization", "key=" + FirebaseServerKey); 
     Request.Headers.Add("TTL", ttl.ToString()); 
     if (data != null && userKey != null && userSecret != null) { 
      EncryptionResult Package = EncryptMessage(userKey, userSecret, data, padding, randomisePadding); 
      Request.Content = new ByteArrayContent(Package.Payload); 
      Request.Content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream"); 
      Request.Content.Headers.ContentLength = Package.Payload.Length; 
      Request.Content.Headers.ContentEncoding.Add("aesgcm"); 
      Request.Headers.Add("Crypto-Key", "keyid=p256dh;dh=" + WebEncoders.Base64UrlEncode(Package.PublicKey)); 
      Request.Headers.Add("Encryption", "keyid=p256dh;salt=" + WebEncoders.Base64UrlEncode(Package.Salt)); 
     } 
     using (HttpClient HC = new HttpClient()) { 
      return HC.SendAsync(Request).Result.StatusCode == HttpStatusCode.Created; 
     } 
    } 

    public static EncryptionResult EncryptMessage(byte[] userKey, byte[] userSecret, byte[] data, 
                ushort padding = 0, bool randomisePadding = false) { 
     SecureRandom Random = new SecureRandom(); 
     byte[] Salt = new byte[16]; 
     Random.NextBytes(Salt); 
     X9ECParameters Curve = ECNamedCurveTable.GetByName("prime256v1"); 
     ECDomainParameters Spec = new ECDomainParameters(Curve.Curve, Curve.G, Curve.N, Curve.H, Curve.GetSeed()); 
     ECKeyPairGenerator Generator = new ECKeyPairGenerator(); 
     Generator.Init(new ECKeyGenerationParameters(Spec, new SecureRandom())); 
     AsymmetricCipherKeyPair KeyPair = Generator.GenerateKeyPair(); 
     ECDHBasicAgreement AgreementGenerator = new ECDHBasicAgreement(); 
     AgreementGenerator.Init(KeyPair.Private); 
     BigInteger IKM = AgreementGenerator.CalculateAgreement(new ECPublicKeyParameters(Spec.Curve.DecodePoint(userKey), Spec)); 
     byte[] PRK = GenerateHKDF(userSecret, IKM.ToByteArrayUnsigned(), Encoding.UTF8.GetBytes("Content-Encoding: auth\0"), 32); 
     byte[] PublicKey = ((ECPublicKeyParameters)KeyPair.Public).Q.GetEncoded(false); 
     byte[] CEK = GenerateHKDF(Salt, PRK, CreateInfoChunk("aesgcm", userKey, PublicKey), 16); 
     byte[] Nonce = GenerateHKDF(Salt, PRK, CreateInfoChunk("nonce", userKey, PublicKey), 12); 
     if (randomisePadding && padding > 0) padding = Convert.ToUInt16(Math.Abs(Random.NextInt()) % (padding + 1)); 
     byte[] Input = new byte[padding + 2 + data.Length]; 
     Buffer.BlockCopy(ConvertInt(padding), 0, Input, 0, 2); 
     Buffer.BlockCopy(data, 0, Input, padding + 2, data.Length); 
     IBufferedCipher Cipher = CipherUtilities.GetCipher("AES/GCM/NoPadding"); 
     Cipher.Init(true, new AeadParameters(new KeyParameter(CEK), 128, Nonce)); 
     byte[] Message = new byte[Cipher.GetOutputSize(Input.Length)]; 
     Cipher.DoFinal(Input, 0, Input.Length, Message, 0); 
     return new EncryptionResult() { Salt = Salt, Payload = Message, PublicKey = PublicKey }; 
    } 

    public class EncryptionResult { 
     public byte[] PublicKey { get; set; } 
     public byte[] Payload { get; set; } 
     public byte[] Salt { get; set; } 
    } 

    public class JsonSubscription { 
     public string endpoint { get; set; } 
     public Dictionary<string, string> keys { get; set; } 
    } 

    public static byte[] ConvertInt(int number) { 
     byte[] Output = BitConverter.GetBytes(Convert.ToUInt16(number)); 
     if (BitConverter.IsLittleEndian) Array.Reverse(Output); 
     return Output; 
    } 

    public static byte[] CreateInfoChunk(string type, byte[] recipientPublicKey, byte[] senderPublicKey) { 
     List<byte> Output = new List<byte>(); 
     Output.AddRange(Encoding.UTF8.GetBytes($"Content-Encoding: {type}\0P-256\0")); 
     Output.AddRange(ConvertInt(recipientPublicKey.Length)); 
     Output.AddRange(recipientPublicKey); 
     Output.AddRange(ConvertInt(senderPublicKey.Length)); 
     Output.AddRange(senderPublicKey); 
     return Output.ToArray(); 
    } 

    public static byte[] GenerateHKDF(byte[] salt, byte[] ikm, byte[] info, int len) { 
     IMac PRKGen = MacUtilities.GetMac("HmacSHA256"); 
     PRKGen.Init(new KeyParameter(MacUtilities.CalculateMac("HmacSHA256", new KeyParameter(salt), ikm))); 
     PRKGen.BlockUpdate(info, 0, info.Length); 
     PRKGen.Update((byte)1); 
     byte[] Result = MacUtilities.DoFinal(PRKGen); 
     if (Result.Length > len) Array.Resize(ref Result, len); 
     return Result; 
    } 

} 

Чтобы сделать этот код работать с asp.net заменить методы декодирования/кодирования из aspcore имен с:

///<summary> 
/// Base 64 Encoding with URL and Filename Safe Alphabet using UTF-8 character set. 
///</summary> 
///<param name="str">The origianl string</param> 
///<returns>The Base64 encoded string</returns> 
public static string Base64ForUrlEncode(string str) 
{ 
    byte[] encbuff = Encoding.UTF8.GetBytes(str); 
    return HttpServerUtility.UrlTokenEncode(encbuff); 
} 
///<summary> 
/// Decode Base64 encoded string with URL and Filename Safe Alphabet using UTF-8. 
///</summary> 
///<param name="str">Base64 code</param> 
///<returns>The decoded string.</returns> 
public static string Base64ForUrlDecode(string str) 
{ 
    byte[] decbuff = HttpServerUtility.UrlTokenDecode(str); 
    return Encoding.UTF8.GetString(decbuff); 
} 
+0

Это фантастическая, спасибо большое. Я только что продолжал использовать indescript push-уведомления, так как у меня не было времени понять это. Также хорошая картина легионера, я играл в HN много ха-ха. –

+0

Когда я столкнулся с этой проблемой, я нашел решения на многих языках ... кроме C#. Меня так беспокоило, что я просто не мог остановиться, пока не нашел решение. – LbISS

+0

Я использую asp.net, но я не понимаю последнюю часть. Что вы имеете в виду при замене этих методов? Должны ли они выполняться в определенном классе? Должен ли я заменить вызовы на первый фрагмент кода «WebEncoders.Base64UrlDecode» и «WebEncoders.Base64UrlEncode» на эти методы? Благодарю. –