Joe*_*ess 6 apple-push-notifications ecdsa .net-core
我正在尝试创建一个 C# 实现,通过他们的 HTTP/2 APNS 端点和 Docker 中的 .Net 核心向 Apple 发送推送。其中一部分需要将加密的 JWT 授权令牌与有效负载一起发送。使用 .Net 核心,我可以在 Windows 上运行时对令牌进行签名,但是在 Linux Docker 映像中运行时,它会提示加载密钥。
在 .net Core Docker Image 中运行时,我在加载密钥时遇到 platformnotsupported Exception。
public static string SignES256(string privateKey, string header, string payload)
{
// This is the failing Call
CngKey key = CngKey.Import(Convert.FromBase64String(privateKey), CngKeyBlobFormat.Pkcs8PrivateBlob);
using (ECDsaCng dsa = new ECDsaCng(key))
{
var unsignedJwtData =
System.Convert.ToBase64String(Encoding.UTF8.GetBytes(header)) + "." + System.Convert.ToBase64String(Encoding.UTF8.GetBytes(payload));
var unsignedJwtDataBytes = Encoding.UTF8.GetBytes(unsignedJwtData);
var signature =
dsa.SignData(unsignedJwtDataBytes, 0, unsignedJwtDataBytes.Length, HashAlgorithmName.SHA256 );
return unsignedJwtData + "." + System.Convert.ToBase64String(signature);
}
}
Run Code Online (Sandbox Code Playgroud)
如何从 Linux 上的 .Net Core 执行此操作?
谢谢。
private static ECDsa GetEllipticCurveAlgorithm(string privateKey)
{
var result = ECDsa.Create();
result.ImportPkcs8PrivateKey(Convert.FromBase64String(privateKey), out _);
return result;
}
Run Code Online (Sandbox Code Playgroud)
正如其他人指出的那样,BouncyCastle 是 Windows CNG 的跨平台替代品。不过,那里的其他示例都没有对我有用。也许这会帮助某人:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Portable.BouncyCastle" Version="1.8.5" />
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="5.4.0" />
</ItemGroup>
</Project>
Run Code Online (Sandbox Code Playgroud)
using System;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Cryptography;
using Microsoft.IdentityModel.Tokens;
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.Security;
public class ApnsTokenProvider
{
private readonly JwtSecurityTokenHandler _tokenHandler = new JwtSecurityTokenHandler();
// Turn off caching so TokenHandler doesn't try to reuse disposed algorithms.
private readonly CryptoProviderFactory _signingFactory = new CryptoProviderFactory
{
CacheSignatureProviders = false
};
public string GetToken(string teamId, string keyId, string privateKey)
{
using (var algorithm = GetEllipticCurveAlgorithm(privateKey))
{
var credentials = new SigningCredentials(new ECDsaSecurityKey(algorithm)
{
KeyId = keyId,
CryptoProviderFactory = _signingFactory
}, SecurityAlgorithms.EcdsaSha256);
return _tokenHandler.CreateEncodedJwt(new SecurityTokenDescriptor
{
Issuer = teamId,
SigningCredentials = credentials
});
}
}
private static ECDsa GetEllipticCurveAlgorithm(string privateKey)
{
var keyParams = (ECPrivateKeyParameters)PrivateKeyFactory
.CreateKey(Convert.FromBase64String(privateKey));
var q = keyParams.Parameters.G.Multiply(keyParams.D).Normalize();
return ECDsa.Create(new ECParameters
{
Curve = ECCurve.CreateFromValue(keyParams.PublicKeyParamSet.Id),
D = keyParams.D.ToByteArrayUnsigned(),
Q =
{
X = q.XCoord.GetEncoded(),
Y = q.YCoord.GetEncoded()
}
});
}
}
Run Code Online (Sandbox Code Playgroud)
ECDsaCng 是使用 Windows CNG 的 ECDSA 实现。它特定于 Windows,因此在 Linux 上不受支持。
这样做的跨平台方法是
using (ECDsa ecdsa = ECDsa.Create())
{
ecdsa.ImportParameters(Pkcs8ToParameters(privateKey));
// the stuff in your current using
}
Run Code Online (Sandbox Code Playgroud)
当然,从 PKCS#8 到 ECParameters 并不是世界上最简单的事情。但我们可以试一试。在另一个答案中有为 RSA 构建 PKCS#8 的细分。
让我们以这个 blob 为例:
308187020100301306072A8648CE3D020106082A8648CE3D030107046D306B02
0101042070A12C2DB16845ED56FF68CFC21A472B3F04D7D6851BF6349F2D7D5B
3452B38AA144034200048101ECE47464A6EAD70CF69A6E2BD3D88691A3262D22
CBA4F7635EAFF26680A8D8A12BA61D599235F67D9CB4D58F1783D3CA43E78F0A
5ABAA624079936C0C3A9
Run Code Online (Sandbox Code Playgroud)
它崩溃了
30 /* SEQUENCE */
81 87 (payload is 0x87 bytes)
02 /* INTEGER */ 01 (1 byte) 00 // Integer: 0. // validate this
30 /* SEQUENCE */ 13 (0x13 bytes)
06 /* OBJECT IDENTIFIER */ 07 (7 bytes)
2A8648CE3D0201 (1.2.840.10045.2.1 / ecPublicKey) // validate this
06 /* OBJECT IDENTIFIER */ 08 (8 bytes)
2A8648CE3D030107 (1.2.840.10045.3.1.7 / secp256r1) // save this, curveName
04 /* OCTET STREAM (byte[]) */ 6D (0x6D bytes)
// Since the constructed (0x20) bit isn't set in the tag normally we stop here,
// but we know from the ecPublicKey context that this is also DER data.
30 /* SEQUENCE */ 6B (0x6B bytes)
02 /* Integer */ 01 (1 byte) 01 // Integer: 1. // validate this.
04 /* OCTET STREAM (byte[]) */ 20 (0x20 bytes / 256 bits)
70A12C2DB16845ED56FF68CFC21A472B3F04D7D6851BF6349F2D7D5B3452B38A // save this: D
A1 /* CONSTRUCTED CONTEXT SPECIFIC 1 */ 44 (0x44 bytes)
03 /* BIT STRING (byte[] if the first byte is 0x00) */ 66 (0x66 bytes)
00 // Oh, good, it's a normal byte[]. Validate this.
// Formatting will become apparent. Save this.
04
8101ECE47464A6EAD70CF69A6E2BD3D88691A3262D22CBA4F7635EAFF26680A8
D8A12BA61D599235F67D9CB4D58F1783D3CA43E78F0A5ABAA624079936C0C3A9
Run Code Online (Sandbox Code Playgroud)
末尾的 BIT STRING 是“公钥”。因为它以04(通常会这样,除非发件人对你生气)它代表一个“未压缩的点”,这意味着剩下的前半部分是 X 坐标,其余部分是 Y 坐标。所以从这个结构你可能会得到类似的东西
string curveOid;
// You can decode the OID, or special case it.
switch (curveName)
{
case "2A8648CE3D030107":
// secp256r1
curveOid = "1.2.840.10045.3.1.7";
break;
case "2B81040022"
// secp384r1
curveOid = "1.3.132.0.34";
break;
case "2B81040023":
// secp521r1
curveOid = "1.3.132.0.35";
break;
default:
throw new InvalidOperationException();
}
return new ECParameters
{
Curve = ECCurve.CreateFromValid(curveOid),
// We saved this.
D = d,
Q = new ECPoint
{
X = x,
Y = y
},
}
Run Code Online (Sandbox Code Playgroud)
这恰好是Suite B 实施者指南 FIPS 186-3 (ECDSA) 的D.1 节 (NIST P-256 / secp256r1) 中使用的密钥。
由于 EC 密钥格式在 INTEGER 值(可能需要填充字节)方面非常短,因此您可以为要支持的每个密钥大小构建一个手动提取器。或者你可以去现场 DER 阅读路线。或者您可以尝试以更友好的形式为您的应用程序序列化您的私钥。
| 归档时间: |
|
| 查看次数: |
2453 次 |
| 最近记录: |