Skip to content
Merged
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
22 changes: 22 additions & 0 deletions Examples/VapidHeaders.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#!/usr/bin/env dotnet
#:package ClosureOSS.WebPush@2.4.1
using WebPush;

var uri = new Uri("https://server.example.com/notify");
var audience = uri.Scheme + Uri.SchemeDelimiter + uri.Host;
var vapidKeys = VapidHelper.GenerateVapidKeys();


var headers = VapidHelper.GetVapidHeaders(
audience,
@"mailto: example@example.com",
vapidKeys.PublicKey,
vapidKeys.PrivateKey,
DateTime.Now.AddDays(2),
ContentEncoding.Aes128gcm
);

foreach (var header in headers)
{
Console.WriteLine($"{header.Key}: {header.Value}");
}
8 changes: 8 additions & 0 deletions Examples/VapidKeys.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#!/usr/bin/env dotnet
#:package ClosureOSS.WebPush@2.4.1
using WebPush;

var vapidKeys = VapidHelper.GenerateVapidKeys();
Console.WriteLine($"Public key: {vapidKeys.PublicKey}");
Console.WriteLine($"Private key: {vapidKeys.PrivateKey}");

30 changes: 30 additions & 0 deletions Examples/WebPushMessage.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
#!/usr/bin/env dotnet
#:package ClosureOSS.WebPush@2.4.1
using WebPush;

var vapidKeys = VapidHelper.GenerateVapidKeys();
vapidKeys.Subject = @"mailto:user@example.net";
// example keys
var p256dh = @"BIwCq8tz028CFq9YFQ56kipZ633EK628l4-u6FcPHTGkS_cqTsV-9MRRAGEu56UmfXQ-8lIg7QXgUTFmzedzMHM";
var auth = @"eSZCfw7d6bXE0erlgnLe_Q";
var webPushClient = new WebPushClient();
var subscription = new PushSubscription("https://server.example.com/notify/Avv3mSO...", p256dh, auth);
var options = new WebPushOptions
{
VapidDetails = vapidKeys,
Topic = "Example",
};


var message = webPushClient.GenerateRequestDetails(subscription, "A payload", options);

Console.WriteLine($"{message.Method} {message.RequestUri}");
Console.WriteLine();
foreach (var header in message.Headers){
foreach (var value in header.Value)
{
Console.WriteLine($"{header.Key} : {value}");
}
}
Console.WriteLine();
Console.WriteLine($"{Convert.ToBase64String(await message.Content!.ReadAsByteArrayAsync())}");
95 changes: 95 additions & 0 deletions Examples/packages.lock.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
{
"version": 1,
"dependencies": {
"net10.0": {
"ClosureOSS.WebPush": {
"type": "Direct",
"requested": "[2.4.1, )",
"resolved": "2.4.1",
"contentHash": "iraAOpOOyz+MafRKEBYPQuq7uCKAS3du4o+bK8SUFCi5C6pm1UCh/Yl4N2vDmZojM2875/QQ18oUWARBxaopWQ==",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "10.0.4",
"Microsoft.IdentityModel.JsonWebTokens": "8.16.0",
"Microsoft.IdentityModel.Tokens": "8.16.0"
}
},
"Microsoft.DotNet.ILCompiler": {
"type": "Direct",
"requested": "[10.0.7, )",
"resolved": "10.0.7",
"contentHash": "2H7j1NltkQx04sPWBkUtFrZNBtro7vwsxRtdThP0oDj6Sn3ouGHCQlxATZ4Me2aJE67+KiXMX2V1IHDjt1uIpw=="
},
"Microsoft.NET.ILLink.Tasks": {
"type": "Direct",
"requested": "[10.0.7, )",
"resolved": "10.0.7",
"contentHash": "AA/yhzFHNtQZXLdqjzujPy25G8EWwGWsAnxOE2zYSBoT/8QHP6ketN3CToD3DFreO653ipUwnKHo22B8AlBMCw=="
},
"Nerdbank.GitVersioning": {
"type": "Direct",
"requested": "[3.9.50, )",
"resolved": "3.9.50",
"contentHash": "HtOgGF6jZ+WYbXnCUCYPT8Y2d6mIJo9ozjK/FINTRsXdm4Zgv9GehUMa7EFoGQkqrMcDJNOIDwCmENnvXg4UbA=="
},
"Microsoft.Extensions.DependencyInjection.Abstractions": {
"type": "Transitive",
"resolved": "10.0.4",
"contentHash": "SIe9zlVQJecnk/DTmevIcl6+aEDYhoVLc2eG2AKwVeNEC8CSyxHAbh4lf0xtHq9JUum0vVTEByGNTK+b6oihTQ=="
},
"Microsoft.Extensions.Logging.Abstractions": {
"type": "Transitive",
"resolved": "10.0.4",
"contentHash": "PDMMt7fvBatv6hcxxyJtXIzSwn7Dy00W6I2vDAOTYrQqNM2dF5A2L9n0uMzdPz2IPoNZWkAmYjoOCEdDLq0i4w==",
"dependencies": {
"Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.4"
}
},
"Microsoft.IdentityModel.Abstractions": {
"type": "Transitive",
"resolved": "8.16.0",
"contentHash": "gSxKLWRZzBpIsEoeUPkxfywNCCvRvl7hkq146XHPk5vOQc9izSf1I+uL1vh4y2U19QPxd9Z8K/8AdWyxYz2lSg=="
},
"Microsoft.IdentityModel.JsonWebTokens": {
"type": "Transitive",
"resolved": "8.16.0",
"contentHash": "prBU72cIP4V8E9fhN+o/YdskTsLeIcnKPbhZf0X6mD7fdxoZqnS/NdEkSr+9Zp+2q7OZBOMfNBKGbTbhXODO4w==",
"dependencies": {
"Microsoft.IdentityModel.Tokens": "8.16.0"
}
},
"Microsoft.IdentityModel.Logging": {
"type": "Transitive",
"resolved": "8.16.0",
"contentHash": "MTzXmETkNQPACR7/XCXM1OGM6oU9RkyibqeJRtO9Ndew2LnGjMf9Atqj2VSf4XC27X0FQycUAlzxxEgQMWn2xQ==",
"dependencies": {
"Microsoft.IdentityModel.Abstractions": "8.16.0"
}
},
"Microsoft.IdentityModel.Tokens": {
"type": "Transitive",
"resolved": "8.16.0",
"contentHash": "rtViGJcGsN7WcfUNErwNeQgjuU5cJNl6FDQsfi9TncwO+Epzn0FTfBsg3YuFW1Q0Ch/KPxaVdjLw3/+5Z5ceFQ==",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "10.0.0",
"Microsoft.IdentityModel.Logging": "8.16.0"
}
}
},
"net10.0/win-x64": {
"Microsoft.DotNet.ILCompiler": {
"type": "Direct",
"requested": "[10.0.7, )",
"resolved": "10.0.7",
"contentHash": "2H7j1NltkQx04sPWBkUtFrZNBtro7vwsxRtdThP0oDj6Sn3ouGHCQlxATZ4Me2aJE67+KiXMX2V1IHDjt1uIpw==",
"dependencies": {
"runtime.win-x64.Microsoft.DotNet.ILCompiler": "10.0.7"
}
},
"runtime.win-x64.Microsoft.DotNet.ILCompiler": {
"type": "Transitive",
"resolved": "10.0.7",
"contentHash": "pdlgAPDgcAMCi1XMrgKTPcovBzM0IG9j8LbsIyXS8XXXIrPRyygByqJPJnPtTPDIHxXsLmd3tlMEDULglbBdKA=="
}
}
}
}
62 changes: 52 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
[![Build](https://github.com/closureOSS/webpush-csharp/actions/workflows/dotnet.yml/badge.svg)](https://github.com/closureOSS/webpush-csharp/actions/workflows/dotnet.yml)
![NuGet Version](https://img.shields.io/nuget/v/ClosureOSS.WebPush)


# Why

To deliver generic events using HTTP Push as outlined in [Generic Event Delivery Using HTTP](https://datatracker.ietf.org/doc/html/rfc8030), backend-triggered push messages must be encrypted. This is accomplished using the [Message Encryption for Web Push](https://datatracker.ietf.org/doc/html/rfc8291) standard, which relies on [Voluntary Application Server Identification (VAPID) for Web Push (RFC8292)](https://datatracker.ietf.org/doc/html/rfc8292) for authentication. Furthermore, any data included with the push message must be separately encrypted following the rules of [Encrypted Content-Encoding for HTTP (RFC8188)](https://datatracker.ietf.org/doc/html/rfc8188).
Expand All @@ -12,6 +11,8 @@ This package makes it easy to send push notifications from an application server

## Purpose of fork

Support for message topic and message urgency flag.

Support for the "aes128gcm" HTTP Content Coding.

Rewrite using System.Security.Crytography, Microsoft.IdentityModel and other first party interfaces.
Expand Down Expand Up @@ -43,12 +44,17 @@ var publicKey = @"BDjASz8kkVBQJgWcD05uX3VxIs_gSHyuS023jnBoHBgUbg8zIJvTSQytR8MP4Z
var privateKey = @"mryM-krWj_6IsIMGsd8wNFXGBxnx...............";

var subscription = new PushSubscription(pushEndpoint, p256dh, auth);
var vapidDetails = new VapidDetails(subject, publicKey, privateKey);
var options = new WebPushOptions
{
VapidDetails = new VapidDetails(subject, publicKey, privateKey),
ContentEncoding = ContentEncoding.Aes128gcm,
Urgency = Urgency.High,
};

var webPushClient = new WebPushClient();
try
{
await webPushClient.SendNotificationAsync(subscription, "payload", vapidDetails);
await webPushClient.SendNotificationAsync(subscription, "payload", options);
}
catch (WebPushException exception)
{
Expand Down Expand Up @@ -114,14 +120,43 @@ Options is an optional argument that if defined should be an Dictionary<string,o

<hr />

## GenerateRequestDetails(pushSubscription, payload, options)

Generates a Http message without sending. Parameters have the same meaning as with `SendNotificationAsync`.

See [standalone example](Examples/WebPushMessage.cs).

```csharp
var vapidKeys = VapidHelper.GenerateVapidKeys();
vapidKeys.Subject = @"mailto:user@example.net";
var p256dh = @"BI...MHM";
var auth = @"eSZ...Q";
var webPushClient = new WebPushClient();
var subscription = new PushSubscription("https://server.example.com/notify/Avv3mSO...", p256dh, auth);
var options = new WebPushOptions
{
VapidDetails = vapidKeys,
Topic = "Example",
};
```

<hr />

## GenerateVapidKeys()

See [standalone example](Examples/VapidKeys.cs).

```csharp
var vapidKeys = VapidHelper.GenerateVapidKeys();
Console.WriteLine($"Public key: {vapidKeys.PublicKey}");
Console.WriteLine($"Private key: {vapidKeys.PrivateKey}");
```

outputs for example:

// Prints 2 URL Safe Base64 Encoded Strings
Console.WriteLine("Public {0}", vapidKeys.PublicKey);
Console.WriteLine("Private {0}", vapidKeys.PrivateKey);
```text
Public key: BFu5Jx7eA285mMZRx7a-SuFH8Cc2mAMZ5RhbqvGJKAIqRT6VzRc4Y5x7uuBD2AVkeLn13MrQZKHHUV6QDL8arGM
Private key: 5aRRAbKkELlCDlhEO68GItWm9ux2hS7ORP2KmQVHxAI
```

### Input
Expand All @@ -139,6 +174,8 @@ Returns a VapidDetails object with **PublicKey** and **PrivateKey** values popul

## GetVapidHeaders(audience, subject, publicKey, privateKey, expiration)

See [standalone example](Examples/VapidHeaders.cs).

```csharp
Uri uri = new Uri(subscription.Endpoint);
string audience = uri.Scheme + Uri.SchemeDelimiter + uri.Host;
Expand All @@ -147,7 +184,9 @@ Dictionary<string, string> vapidHeaders = VapidHelper.GetVapidHeaders(
audience,
@"mailto: example@example.com",
publicKey,
privateKey
privateKey,
DateTime.Now.AddDays(2),
ContentEncoding.Aes128gcm
);
```

Expand All @@ -162,11 +201,14 @@ The `GetVapidHeaders()` method expects the following input:
- _publicKey_: the VAPID public key.
- _privateKey_: the VAPID private key.

### Returns
and optionally

- _expiration_: Expiration date (defaults to 12 hours from now)
- _contentEncoding_: Either Aes128gcm (default) or Aesgcm

This method returns a Dictionary<string, string> intented to be headers of a web request. It will contain the following key(s):
### Returns

- _Authorization_
This method returns a Dictionary<string, string> intented to be headers of a web request. It will contain the `Authorization` header and for Aes128 additionally a `Crypto-Key` header.

---

Expand Down
31 changes: 0 additions & 31 deletions WebPush.sln

This file was deleted.

4 changes: 4 additions & 0 deletions WebPush.slnx
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<Solution>
<Project Path="WebPush.Test/WebPush.Test.csproj" />
<Project Path="WebPush/WebPush.csproj" />
</Solution>
Loading