Я пытаюсь отправить сообщение PushNotification в список iOS device
. Мои ограничения:
- код находится внутри контроллера webapi
- веб-сервер - Windows 2012 R2
- с использованием ядра asp.net/.net (на самом деле 3.1)
Во-первых, я решил использовать файл p8 вместо p12 (кажется, Apple предпочитает его).
Я просканировал много вопросов SO, пробуя около 5-6 решений, но все равно получаю те же результаты:
При обработке запроса произошло необработанное исключение. Win32Exception: Сообщение получено без внимания или неправильно отформатировано. Неизвестное местоположение
AuthenticationException: Ошибка аутентификации, см. внутреннее исключение. System.Net.Security.SslStream.StartSendAuthResetSignal (сообщение ProtocolToken, AsyncProtocolRequest asyncRequest, исключение ExceptionDispatchInfo)
HttpRequestException: SSL-соединение не может быть установлено, см. внутреннее исключение. System.Net.Http.ConnectHelper.EstablishSslConnectionAsyncCore (поток потока, SslClientAuthenticationOptions sslOptions, CancellationToken CancellationToken)
С французского «Win32Exception: Le message reçu était inattendu ou formaté de façon correcte» на английском языке будет «Win32Exception: полученное сообщение было неожиданным или плохо отформатировано».
Для форматирования JWT я использовал решение здесь от Bourne Koloh.
Я также пробовал PushSharp и CoreSharp с той же ошибкой.
Когда я извлекаю данные из файла p8, у меня есть весь файл без первой и последней строки и без разрыва строки.
Когда я использую приложение Push Notifications Tester, оно работает, сообщение доставляется на устройство.
Кстати, я добавил оба сертификата p12 (dev и prod) на сервер Windows 2012 R2. Я, вероятно, пропустил что-то важное, но до сих пор не знаю, что.
Поскольку в сообщении говорится, что это проблема аутентификации, я думаю, это связано с JWT и файлом p8.
Как я извлекаю данные файла p8:
var data = System.IO.File.ReadAllText("AuthKey.p8");
var list = data.Split('\n').ToList();
var prk = list.Where((s, i) => i != 0 && i != list.Count - 1).Aggregate((agg, s) => agg + s);
var key = new ECDsaCng(CngKey.Import(Convert.FromBase64String(prk), CngKeyBlobFormat.Pkcs8PrivateBlob));
Il также пробовал этот способ (с BouncyCastle):
using (var reader = System.IO.File.OpenText("AuthKey.p8"))
{
var ecPrivateKeyParameters = (ECPrivateKeyParameters)new PemReader(reader).ReadObject();
var x = ecPrivateKeyParameters.Parameters.G.AffineXCoord.GetEncoded();
var y = ecPrivateKeyParameters.Parameters.G.AffineYCoord.GetEncoded();
var d = ecPrivateKeyParameters.D.ToByteArrayUnsigned();
var key = ECDsaCng(EccKey.New(x, y, d));
}
А теперь, как я создаю JWT:
попытка №1: использование пакета Jose.
private string GetProviderToken(ECDsaCng key)
{
var epochNow = (int)DateTime.UtcNow.Subtract(new DateTime(1970,1,1)).TotalSeconds;
var payload = new Dictionary<string, object>()
{
{ "iss", "THETEAMID" },
{ "iat", epochNow }
};
var extraHeaders = new Dictionary<string, object>()
{
{ "kid", "THEKEYID"}
};
return JWT.Encode(payload, key, JwsAlgorithm.ES256, extraHeaders);
}
var token = GetProviderToken(key);
попытка №2:
private string CreateToken(ECDsa key, string keyID, string teamID)
{
var securityKey = new ECDsaSecurityKey(key) { KeyId = keyID };
var credentials = new SigningCredentials(securityKey, "ES256");
var descriptor = new SecurityTokenDescriptor
{
IssuedAt = DateTime.Now,
Issuer = teamID,
SigningCredentials = credentials
};
descriptor.Expires = null;
descriptor.NotBefore = null;
var handler = new JwtSecurityTokenHandler();
var encodedToken = handler.CreateEncodedJwt(descriptor);
return encodedToken;
}
var token = CreateToken(key, "THEKEYID", "THETEAMID");
попробуй №3
private string SignES256(string privateKey, string header, string payload)
{
CngKey key = CngKey.Import(Convert.FromBase64String(privateKey), CngKeyBlobFormat.Pkcs8PrivateBlob);
using (ECDsaCng dsa = new ECDsaCng(key))
{
dsa.HashAlgorithm = CngAlgorithm.Sha256;
var unsignedJwtData = Microsoft.AspNetCore.WebUtilities.WebEncoders.Base64UrlEncode(System.Text.Encoding.UTF8.GetBytes(header)) + "." + Microsoft.AspNetCore.WebUtilities.WebEncoders.Base64UrlEncode(System.Text.Encoding.UTF8.GetBytes(payload));
var signature = dsa.SignData(System.Text.Encoding.UTF8.GetBytes(unsignedJwtData));
return unsignedJwtData + "." + Microsoft.AspNetCore.WebUtilities.WebEncoders.Base64UrlEncode(signature);
}
}
var token = SignES256(prk, "{\"alg\":\"ES256\" ,\"kid\":\"THEKEYID\"}", "{ \"iss\": \"THETEAMID\",\"iat\":" + (int)DateTime.UtcNow.Subtract(new DateTime(1970, 1, 1)).TotalSeconds + "\" }");
Если я не использую библиотеку (CorePush и т. д.) для отправки http-сообщения, я делаю так:
var url = string.Format("https://api.sandbox.push.apple.com/3/device/{0}", deviceToken);
var request = new HttpRequestMessage(HttpMethod.Post, url);
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token);
request.Headers.TryAddWithoutValidation("apns-push-type", "alert"); // or background
request.Headers.TryAddWithoutValidation("apns-id", Guid.NewGuid().ToString("D"));
request.Headers.TryAddWithoutValidation("apns-expiration", Convert.ToString(0));
request.Headers.TryAddWithoutValidation("apns-priority", Convert.ToString(10));
request.Headers.TryAddWithoutValidation("apns-topic", "com.company.project");
request.Content = new StringContent("{\"aps\":{\"alert\":\"Hello\"},\"yourCustomKey\":\"1\"}");
// also tried without yourcustomkey
request.Version = new Version(2, 0); // tried directly with System.Net.HttpVersion.Version20;
var handler = new HttpClientHandler();
handler.SslProtocols = SslProtocols.Tls12 | SslProtocols.Tls11 | SslProtocols.Tls; // Tried with only Tls12
handler.ServerCertificateCustomValidationCallback = (message, cert, chain, errors) => true;
using (HttpClient client = new HttpClient(handler))
{
HttpResponseMessage resp = await client.SendAsync(request).ContinueWith(responseTask =>
{
return responseTask.Result; // line of error
});
if (resp != null)
{
string apnsResponseString = await resp.Content.ReadAsStringAsync();
handler.Dispose();
//ALL GOOD ....
return Ok(apnsResponseString);
}
handler.Dispose();
}