从 ASPX/C# 页面发送 APNS,使用 SSL 进行身份验证
Posted
技术标签:
【中文标题】从 ASPX/C# 页面发送 APNS,使用 SSL 进行身份验证【英文标题】:Sending APNS from ASPX/C# Page, Authentication with SSL 【发布时间】:2013-02-27 16:26:34 【问题描述】:我一直在尝试让 Windows 2008 R2 数据中心(在亚马逊上)使用 Apple 推送通知服务 - 我已经看到了很多关于其中一些的问题,但现有的答案都没有解决我的问题。我正在使用带有 TcpClient 的 SslStream 编写此代码,我将它封装在一个名为 SSLClient 的类中。我在 AuthenticateAsClient 行上不断收到“证书链是由不受信任的权威机构颁发的”错误。这是我的 SSLClient(其中大部分是从使用 SslStream 和 TcpClient 的 MSDN 示例开始的):
public class SSLClient
public delegate void OnSSLRequestHandler(SSLRequestEvent e);
private string _machineName;
private string [] _serverCertFilenames;
private string [] _serverCertPasswords;
public event OnSSLRequestHandler OnLoaded;
public SSLClient(
string machineName,
string [] serverCertFilenames,
string [] serverCertPasswords )
_machineName = machineName;
_serverCertFilenames = serverCertFilenames;
_serverCertPasswords = serverCertPasswords;
public bool ValidateServerCertificate(
object sender,
X509Certificate certificate,
X509Chain chain,
SslPolicyErrors sslPolicyErrors)
if (sslPolicyErrors == SslPolicyErrors.None)
return true;
// Do not allow this client to communicate with unauthenticated servers.
return false;
public void Load(byte[] sendData, int port = 443)
// Create a TCP/IP client socket.
// machineName is the host running the server application.
TcpClient client = new TcpClient(_machineName, port);
// Create an SSL stream that will close the client's stream.
SslStream sslStream = new SslStream(
client.GetStream(),
false,
new RemoteCertificateValidationCallback (ValidateServerCertificate),
null
);
// The server name must match the name on the server certificate.
try
X509Certificate2Collection xc = new X509Certificate2Collection();
for (int i = 0; i < _serverCertFilenames.Length; i++)
string certName = _serverCertFilenames[i];
string certPwd = _serverCertPasswords[i];
X509Certificate2 x;
if (certPwd != "")
x = new X509Certificate2(certName, certPwd);
else
x = new X509Certificate2(certName);
xc.Add(x);
sslStream.AuthenticateAsClient(_machineName, xc, SslProtocols.Ssl3, false);
catch (AuthenticationException e)
string errMsg = "Exception: " + e.Message;
if (e.InnerException != null)
errMsg += ("\r\nInner exception: " + e.InnerException.Message);
errMsg += ("\r\nAuthentication failed - closing the connection.");
client.Close();
// Just fires an event, ctor(success, message, response, SSLClient)
if (OnLoaded != null)
OnLoaded(new SSLRequestEvent(false, errMsg, null, this));
return;
sslStream.Write(sendData);
sslStream.Flush();
// Read message from the server.
byte[] response = ReadMessage(sslStream);
// Just fires an event, ctor(success, message, response, SSLClient)
if (OnLoaded != null)
OnLoaded(new SSLRequestEvent(true, "Success", response, this));
// Close the client connection.
client.Close();
private byte[] ReadMessage(SslStream sslStream)
MemoryStream ms = new MemoryStream();
byte [] buffer = new byte[2048];
int bytes = -1;
do
bytes = sslStream.Read(buffer, 0, buffer.Length);
ms.Write(buffer, 0, bytes);
while (bytes != 0);
return ms.ToArray();
使用的证书是我按照从 Apple 到我正在开发的测试移动应用程序的步骤的证书,以及 4 个似乎也需要的委托证书。该类的用法如下:
public void DoAPNSPush()
// Build the binary data to be sent
byte[] bin = BuildAPNSMessage();
// These certs are uploaded to the server next to this ASPX
// and are being found and read correctly.
string[] certs = new string[]
Server.MapPath("certs") + "\\MyPFX.pfx",
Server.MapPath("certs") + "\\my_cert_from_apple.cer",
Server.MapPath("certs") + "\\entrust_2048_ca.cer"
;
string[] pwds = new string[]
"password",
"",
""
;
SSLClient ssl = new SSLClient("gateway.sandbox.push.apple.com", certs, pwds);
ssl.OnLoaded += new SSLClient.OnSSLRequestHandler(onSSLLoaded);
ssl.Load(bin, 2195);
public void onSSLLoaded(SSLRequestEvent e)
// We never get here, but the event has the following members:
// bool e.Success
// string e.Message
// byte[] e.Response
// SSLClient e.Client
希望这不是 TLDR。身份验证仍然失败,我做错了什么?我已经确保在 Amazon 安全组上打开端口 2195。此外,我还尝试了其他 SslProtocols.Ssl3 和 SslProtocols.Tls。苹果的CSR是从这个服务器生成并完成的,委托证书是从他们的网站(https://www.entrust.net/downloads/root_request.cfm#)下载的,我只是在服务器上双击它们进行安装。
任何意见都会有所帮助
提前致谢。
【问题讨论】:
【参考方案1】:我不确定 SSL 代码在 C# 中应该是什么样子(我在 Java 中实现了这个),但你不应该通过证书传递密码吗?
【讨论】:
奇怪的是,它确实接受了这些,因为它们只是没有密码的 .cer 文件。我确实更新了它以接受密码,并在 .pfx 中传递了密码,它也接受了。我还确保我在任何地方都使用 X509Certificate2Collection 和 X509Certificate2(我最初不是),现在我收到了一个不同的错误:“证书链是由不受信任的权威机构颁发的。”我会用这个更新我的帖子,谢谢。 必须使用委托证书吗?在 Java 安全性中,您在两个单独的参数中传递 TrustManager 和 KeyManager 证书。 TrustManager 是可选的。 KeyManager 使用您从 Apple 获得的证书(p12 文件)进行初始化。 Apple 只要求您使用他们颁发的证书才能使连接正常工作。 我尝试了各种有和没有证书的方法,但它从来没有奏效 - 我想稍后再回来,但我现在不得不把它设置下来 - 并决定尝试使用第 3 个派对。我使用 JavaPNS 并通过苹果创建了一个新证书。现在总是握手失败。一旦我有更多,我会带着答案和一些其他人可以使用的代码回到这个问题。 @sidestepper 如果您在使用 JavaPNs 时遇到此错误,则问题很可能是 KeyStore 文件(我使用了 JavaPNs 库,我知道它在 KeyStore 正常时可以工作)。当您将证书导出到 p12 文件(KeyStore)时,您是否同时选择了证书和私钥? 我已按照 Apple 的说明生成 CSR、安装 CER,然后导出 P12 - 但似乎我在某处遗漏了一步。你所说的似乎很奇怪。我不得不对从我的钥匙串导出到 openSSL 的 p12 采取额外的步骤,以使 p12 最终正常工作。请看下面我的回答。感谢您的帮助!【参考方案2】:好的,在使用 JavaPNS 一段时间后我得到了它,我的问题主要出在证书上。直到我发现我需要将 Apple 的 .p12 和 .cer 转换为新的 p12(使用 openSSL),Apple 才接受连接并执行推送。之后我修改了我的 SSLClient 代码,也让它工作了。
public class SSLClient
public delegate void OnSSLRequestHandler(SSLRequestEvent e);
private string _remoteHost;
private string [] _serverCertFilenames;
private string [] _serverCertPasswords;
public event OnSSLRequestHandler OnLoaded;
public SSLClient(string remoteHost, string [] serverCertFilenames, string [] serverCertPasswords)
_remoteHost = remoteHost;
_serverCertFilenames = serverCertFilenames;
_serverCertPasswords = serverCertPasswords;
public bool ValidateServerCertificate(
object sender,
X509Certificate certificate,
X509Chain chain,
SslPolicyErrors sslPolicyErrors)
return true;
public void Load(byte[] sendData, int port = 443)
// Create a TCP/IP client socket.
TcpClient client = new TcpClient(_remoteHost, port);
// Create an SSL stream utilizing the TcpClient
SslStream sslStream = new SslStream(
client.GetStream(),
false,
new RemoteCertificateValidationCallback (ValidateServerCertificate),
null
);
try
X509Certificate2Collection xc = new X509Certificate2Collection();
for (int i = 0; i < _serverCertFilenames.Length; i++)
X509Certificate2 x = new X509Certificate2(
_serverCertFilenames[i],
_serverCertPasswords[i] );
xc.Add(x);
sslStream.AuthenticateAsClient(_remoteHost, xc, SslProtocols.Default, true);
catch (AuthenticationException e)
client.Close();
sslStream.Close();
if (OnLoaded != null)
OnLoaded(new SSLRequestEvent(false, errMsg, null, this));
return;
sslStream.Write(sendData);
sslStream.Flush();
// Read message from the server.
byte[] response = ReadMessage(sslStream);
if (OnLoaded != null)
OnLoaded(new SSLRequestEvent(true, "Success", response, this));
// Close the client connection.
client.Close();
private byte[] ReadMessage(SslStream sslStream)
MemoryStream ms = new MemoryStream();
byte [] buffer = new byte[1024];
int bytes = -1;
do
bytes = sslStream.Read(buffer, 0, buffer.Length);
ms.Write(buffer, 0, bytes);
while (bytes != 0);
return ms.ToArray();
用法和以前一样。这里最大的技巧是我这里需要的 openSSL 命令:
openssl x509 -in "path_to_apple_cert.cer" -inform DER -out "path_to_an_output_Cert.pem" -outform PEM
openssl pkcs12 -nocerts -in "path_to_exported_p12_from_apple_cer.p12" -out "path_to_an_output_Key.pem" -passin pass:your_p12_password -passout pass:your_new_p12_password
openssl pkcs12 -export -inkey "path_to_an_output_Key.pem" -in "path_to_an_output_Cert.pem" -out "path_to_final_p12.p12" -passin pass:your_new_p12_password -passout pass:your_final_p12_password
这可能看起来很讨厌 - 但它真正做的是:第 1 行)获取您从苹果获得的 .CER 文件并从中制作证书 .pem,第 2 行)获取您从钥匙串访问导出的 .P12 和密码保护并从中制作密钥 .pem,然后最后 3) 取出刚刚制作的两个 pem 并从中创建一个新的 p12。这是苹果推送通知服务器最终接受我的 P12。
希望这对你们中的一些人有所帮助 - 我的头脑已经在证书、pfx、p12、pem 等问题上旋转了一周,现在让这些东西正常工作。
【讨论】:
以上是关于从 ASPX/C# 页面发送 APNS,使用 SSL 进行身份验证的主要内容,如果未能解决你的问题,请参考以下文章