1- using System ;
1+ using System ;
22using System . Collections . Concurrent ;
33using System . Collections . Generic ;
44using System . Net . Http ;
77using System . Text ;
88using System . Threading ;
99using System . Threading . Tasks ;
10- using Org . BouncyCastle . Crypto . Parameters ;
11- using Org . BouncyCastle . Security ;
1210
1311using CorePush . Interfaces ;
1412using CorePush . Models ;
@@ -43,7 +41,7 @@ public class ApnSender : IApnSender
4341 public ApnSender ( ApnSettings settings , HttpClient http ) : this ( settings , http , new DefaultCorePushJsonSerializer ( ) )
4442 {
4543 }
46-
44+
4745 /// <summary>
4846 /// Apple push notification sender constructor
4947 /// </summary>
@@ -55,7 +53,7 @@ public ApnSender(ApnSettings settings, HttpClient http, IJsonSerializer serializ
5553 this . settings = settings ?? throw new ArgumentNullException ( nameof ( settings ) ) ;
5654 this . http = http ?? throw new ArgumentNullException ( nameof ( http ) ) ;
5755 this . serializer = serializer ?? throw new ArgumentNullException ( nameof ( serializer ) ) ;
58-
56+
5957 if ( http . BaseAddress == null )
6058 {
6159 http . BaseAddress = new Uri ( servers [ settings . ServerType ] ) ;
@@ -65,7 +63,7 @@ public ApnSender(ApnSettings settings, HttpClient http, IJsonSerializer serializ
6563 /// <summary>
6664 /// Serialize and send notification to APN. Please see how your message should be formatted here:
6765 /// https://developer.apple.com/library/archive/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/CreatingtheNotificationPayload.html#//apple_ref/doc/uid/TP40008194-CH10-SW1
68- /// !IMPORTANT: If you send many messages at once, make sure to retry those calls. Apple typically doesn't like
66+ /// !IMPORTANT: If you send many messages at once, make sure to retry those calls. Apple typically doesn't like
6967 /// to receive too many requests and may occasionally respond with HTTP 429. Just try/catch this call and retry as needed.
7068 /// </summary>
7169 /// <exception cref="HttpRequestException">Throws exception when not successful</exception>
@@ -78,14 +76,16 @@ public async Task<PushResult> SendAsync(
7876 ApnPushType apnPushType = ApnPushType . Alert ,
7977 CancellationToken cancellationToken = default )
8078 {
79+ ArgumentException . ThrowIfNullOrWhiteSpace ( deviceToken ) ;
80+
8181 var path = $ "/3/device/{ deviceToken } ";
8282 var json = serializer . Serialize ( notification ) ;
8383
8484 using var message = new HttpRequestMessage ( HttpMethod . Post , path ) ;
85-
85+
8686 message . Version = new Version ( 2 , 0 ) ;
8787 message . Content = new StringContent ( json ) ;
88-
88+
8989 message . Headers . Authorization = new AuthenticationHeaderValue ( "bearer" , GetJwtToken ( ) ) ;
9090 message . Headers . TryAddWithoutValidation ( ":method" , "POST" ) ;
9191 message . Headers . TryAddWithoutValidation ( ":path" , path ) ;
@@ -100,21 +100,22 @@ public async Task<PushResult> SendAsync(
100100 }
101101
102102 using var response = await http . SendAsync ( message , cancellationToken ) ;
103-
103+
104104 var content = await response . Content . ReadAsStringAsync ( cancellationToken ) ;
105- var error = response . IsSuccessStatusCode
106- ? null
107- : serializer . Deserialize < ApnsError > ( content ) . Reason ;
105+ var error = response . IsSuccessStatusCode
106+ ? null
107+ : serializer . Deserialize < ApnsError > ( content ) ? . Reason ;
108108
109109 return new PushResult ( ( int ) response . StatusCode , response . IsSuccessStatusCode , content , error ) ;
110110 }
111111
112112 private string GetJwtToken ( )
113113 {
114- var ( token , date ) = tokens . GetOrAdd ( settings . AppBundleIdentifier , _ => new Tuple < string , DateTime > ( CreateJwtToken ( ) , DateTime . UtcNow ) ) ;
114+ var cacheKey = $ "{ settings . AppBundleIdentifier } :{ settings . P8PrivateKeyId } ";
115+ var ( token , date ) = tokens . GetOrAdd ( cacheKey , _ => new Tuple < string , DateTime > ( CreateJwtToken ( ) , DateTime . UtcNow ) ) ;
115116 if ( date < DateTime . UtcNow . AddMinutes ( - tokenExpiresMinutes ) )
116117 {
117- tokens . TryRemove ( settings . AppBundleIdentifier , out _ ) ;
118+ tokens . TryRemove ( cacheKey , out _ ) ;
118119 return GetJwtToken ( ) ;
119120 }
120121
@@ -129,32 +130,26 @@ private string CreateJwtToken()
129130 var payloadBase64 = Base64UrlEncode ( payload ) ;
130131 var unsignedJwtData = $ "{ headerBase64 } .{ payloadBase64 } ";
131132 var unsignedJwtBytes = Encoding . UTF8 . GetBytes ( unsignedJwtData ) ;
132-
133+
133134 var privateKeyBytes = Convert . FromBase64String ( CryptoHelper . CleanP8Key ( settings . P8PrivateKey ) ) ;
134- var keyParams = ( ECPrivateKeyParameters ) PrivateKeyFactory . CreateKey ( privateKeyBytes ) ;
135- var q = keyParams . Parameters . G . Multiply ( keyParams . D ) . Normalize ( ) ;
136-
137- using var dsa = ECDsa . Create ( new ECParameters
138- {
139- Curve = ECCurve . CreateFromValue ( keyParams . PublicKeyParamSet . Id ) ,
140- D = keyParams . D . ToByteArrayUnsigned ( ) ,
141- Q =
142- {
143- X = q . XCoord . GetEncoded ( ) ,
144- Y = q . YCoord . GetEncoded ( )
145- }
146- } ) ;
147-
148- var signature = dsa . SignData ( unsignedJwtBytes , 0 , unsignedJwtBytes . Length , HashAlgorithmName . SHA256 ) ;
135+
136+ using var dsa = ECDsa . Create ( ) ;
137+ dsa . ImportPkcs8PrivateKey ( privateKeyBytes , out _ ) ;
138+
139+ var signature = dsa . SignData ( unsignedJwtBytes , HashAlgorithmName . SHA256 ) ;
149140 var signatureBase64 = Base64UrlEncode ( signature ) ;
150141 return $ "{ unsignedJwtData } .{ signatureBase64 } ";
151142 }
152-
143+
153144 private static string Base64UrlEncode ( string str )
154145 {
155146 var bytes = Encoding . UTF8 . GetBytes ( str ) ;
156147 return Base64UrlEncode ( bytes ) ;
157148 }
158149
159- private static string Base64UrlEncode ( byte [ ] bytes ) => Convert . ToBase64String ( bytes ) ;
150+ private static string Base64UrlEncode ( byte [ ] bytes ) =>
151+ Convert . ToBase64String ( bytes )
152+ . Replace ( '+' , '-' )
153+ . Replace ( '/' , '_' )
154+ . TrimEnd ( '=' ) ;
160155}
0 commit comments