Skip to content

Commit 45aa490

Browse files
committed
VAPID tokens cache
1 parent b113a1f commit 45aa490

File tree

2 files changed

+69
-30
lines changed

2 files changed

+69
-30
lines changed
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
using System;
2+
3+
namespace Lib.Net.Http.WebPush.Authentication
4+
{
5+
/// <summary>
6+
/// Represents cache for <see cref="VapidAuthentication"/> tokens.
7+
/// </summary>
8+
public interface IVapidTokenCache
9+
{
10+
/// <summary>
11+
/// Puts token into cache.
12+
/// </summary>
13+
/// <param name="audience">The origin of the push resource (cache key).</param>
14+
/// <param name="expiration">The token expiration.</param>
15+
/// <param name="token">The token.</param>
16+
void Put(string audience, DateTimeOffset expiration, string token);
17+
18+
/// <summary>
19+
/// Gets token from cache.
20+
/// </summary>
21+
/// <param name="audience">The origin of the push resource (cache key).</param>
22+
/// <returns>The cached token or null if token was not present in cache.</returns>
23+
string Get(string audience);
24+
}
25+
}

Lib.Net.Http.WebPush/Authentication/VapidAuthentication.cs

Lines changed: 44 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ internal WebPushSchemeHeadersValues(string authenticationHeaderValueParameter, s
6060
private string _publicKey;
6161
private string _privateKey;
6262
private ECPrivateKeyParameters _privateSigningKey;
63-
private int _expiration;
63+
private int _relativeExpiration;
6464

6565
private static readonly DateTime _unixEpoch = new DateTime(1970, 1, 1, 0, 0, 0);
6666
private static readonly string _jwtHeaderSegment = UrlBase64Converter.ToUrlBase64String(Encoding.UTF8.GetBytes(JWT_HEADER));
@@ -149,7 +149,7 @@ public string PrivateKey
149149
/// </summary>
150150
public int Expiration
151151
{
152-
get { return _expiration; }
152+
get { return _relativeExpiration; }
153153

154154
set
155155
{
@@ -158,9 +158,14 @@ public int Expiration
158158
throw new ArgumentOutOfRangeException(nameof(Expiration), "Expiration must be a number of seconds not longer than 24 hours");
159159
}
160160

161-
_expiration = value;
161+
_relativeExpiration = value;
162162
}
163163
}
164+
165+
/// <summary>
166+
/// Gets or sets the token cache.
167+
/// </summary>
168+
public IVapidTokenCache TokenCache { get; set; }
164169
#endregion
165170

166171
#region Constructor
@@ -174,7 +179,7 @@ public VapidAuthentication(string publicKey, string privateKey)
174179
PublicKey = publicKey;
175180
PrivateKey = privateKey;
176181

177-
_expiration = DEFAULT_EXPIRATION;
182+
_relativeExpiration = DEFAULT_EXPIRATION;
178183
}
179184
#endregion
180185

@@ -211,40 +216,23 @@ private string GetToken(string audience)
211216
throw new ArgumentException(nameof(audience), "Audience should be an absolute URL");
212217
}
213218

214-
string jwtBodySegment = GetJwtBodySegment(audience);
219+
string token = TokenCache?.Get(audience);
215220

216-
return GenerateJwtToken(jwtBodySegment);
217-
}
218-
219-
private string GetJwtBodySegment(string audience)
220-
{
221-
StringBuilder jwtBodyBuilder = new StringBuilder();
222-
223-
jwtBodyBuilder.Append(JWT_BODY_AUDIENCE_PART).Append(audience)
224-
.Append(JWT_BODY_EXPIRATION_PART).Append(GetAbsoluteExpiration(_expiration).ToString(CultureInfo.InvariantCulture));
225-
226-
if (_subject != null)
221+
if (token == null)
227222
{
228-
jwtBodyBuilder.Append(JWT_BODY_SUBJECT_PART).Append(_subject).Append(JWT_BODY_WITH_SUBJECT_CLOSING);
229-
}
230-
else
231-
{
232-
jwtBodyBuilder.Append(JWT_BODY_WITHOUT_SUBJECT_CLOSING);
233-
}
223+
DateTime absoluteExpiration = DateTime.UtcNow.AddSeconds(_relativeExpiration);
234224

235-
return UrlBase64Converter.ToUrlBase64String(Encoding.UTF8.GetBytes(jwtBodyBuilder.ToString()));
236-
}
225+
token = GenerateToken(audience, absoluteExpiration);
237226

238-
private static long GetAbsoluteExpiration(int expirationSeconds)
239-
{
240-
TimeSpan unixEpochOffset = DateTime.UtcNow - _unixEpoch;
227+
TokenCache?.Put(audience, absoluteExpiration, token);
228+
}
241229

242-
return (long)unixEpochOffset.TotalSeconds + expirationSeconds;
230+
return token;
243231
}
244232

245-
private string GenerateJwtToken(string jwtBodySegment)
233+
private string GenerateToken(string audience, DateTime absoluteExpiration)
246234
{
247-
string jwtInput = _jwtHeaderSegment + JWT_SEPARATOR + jwtBodySegment;
235+
string jwtInput = _jwtHeaderSegment + JWT_SEPARATOR + GenerateJwtBodySegment(audience, absoluteExpiration);
248236

249237
byte[] jwtInputHash;
250238
using (var sha256Hasher = SHA256.Create())
@@ -268,6 +256,32 @@ private string GenerateJwtToken(string jwtBodySegment)
268256
return jwtInput + JWT_SEPARATOR + UrlBase64Converter.ToUrlBase64String(combinedJwtSignature);
269257
}
270258

259+
private string GenerateJwtBodySegment(string audience, DateTime absoluteExpiration)
260+
{
261+
StringBuilder jwtBodyBuilder = new StringBuilder();
262+
263+
jwtBodyBuilder.Append(JWT_BODY_AUDIENCE_PART).Append(audience)
264+
.Append(JWT_BODY_EXPIRATION_PART).Append(ToUnixTimeSeconds(absoluteExpiration).ToString(CultureInfo.InvariantCulture));
265+
266+
if (_subject != null)
267+
{
268+
jwtBodyBuilder.Append(JWT_BODY_SUBJECT_PART).Append(_subject).Append(JWT_BODY_WITH_SUBJECT_CLOSING);
269+
}
270+
else
271+
{
272+
jwtBodyBuilder.Append(JWT_BODY_WITHOUT_SUBJECT_CLOSING);
273+
}
274+
275+
return UrlBase64Converter.ToUrlBase64String(Encoding.UTF8.GetBytes(jwtBodyBuilder.ToString()));
276+
}
277+
278+
private static long ToUnixTimeSeconds(DateTime dateTime)
279+
{
280+
TimeSpan unixEpochOffset = dateTime - _unixEpoch;
281+
282+
return (long)unixEpochOffset.TotalSeconds;
283+
}
284+
271285
private static void ByteArrayCopyWithPadLeft(byte[] sourceArray, byte[] destinationArray, int destinationIndex, int destinationLengthToUse)
272286
{
273287
if (sourceArray.Length != destinationLengthToUse)

0 commit comments

Comments
 (0)