I need to transfer data between WCF service and UWP app. So I sign and verify data after receive data. I have a problem. The signed data result in WCF is differences in UWP app.(Of course, I can't verify data) This is my source code:
// WCF
private String Sign(string Message)
{
ContentInfo cont = new ContentInfo(Encoding.UTF8.GetBytes(Message));
SignedCms signed = new SignedCms(cont, true);
_SignerCert = new X509Certificate2("Path", "Password");
CmsSigner signer = new CmsSigner(_SignerCert);
signer.IncludeOption = X509IncludeOption.None;
signed.ComputeSignature(signer);
return Convert.ToBase64String(signed.Encode());
}
and
//UWP
public static async Task<String> Sign(String Message)
{
StorageFolder appInstalledFolder = Windows.ApplicationModel.Package.Current.InstalledLocation;
var CerFile = await appInstalledFolder.GetFileAsync(#"Assets\PAYKII_pkcs12.p12");
var CerBuffer = await FileIO.ReadBufferAsync(CerFile);
string CerData = CryptographicBuffer.EncodeToBase64String(CerBuffer);
await CertificateEnrollmentManager.ImportPfxDataAsync
(CerData, "Password",
ExportOption.NotExportable,
KeyProtectionLevel.NoConsent,
InstallOptions.None,
"RASKey2");
var Certificate = (await CertificateStores.FindAllAsync(new CertificateQuery() { FriendlyName = "RASKey2" })).Single();
IInputStream pdfInputstream;
InMemoryRandomAccessStream originalData = new InMemoryRandomAccessStream();
await originalData.WriteAsync(CryptographicBuffer.ConvertStringToBinary(Message,BinaryStringEncoding.Utf8));
await originalData.FlushAsync();
pdfInputstream = originalData.GetInputStreamAt(0);
CmsSignerInfo signer = new CmsSignerInfo();
signer.Certificate = Certificate;
signer.HashAlgorithmName = HashAlgorithmNames.Sha1;
IList<CmsSignerInfo> signers = new List<CmsSignerInfo>();
signers.Add(signer);
IBuffer signature = await CmsDetachedSignature.GenerateSignatureAsync(pdfInputstream, signers, null);
return CryptographicBuffer.EncodeToBase64String(signature);
}
I stumbled over your post, because I wanted to achieve something very similar: sign a message in an UWP app and verifying the signature in my WCF Service. After reading http://www.codeproject.com/Tips/679142/How-to-sign-data-with-SignedCMS-and-signature-chec, I finally managed to make this fly (with a detached signature, ie you need to have the original message for verification):
UWP:
public async static Task<string> Sign(Windows.Security.Cryptography.Certificates.Certificate cert, string messageToSign) {
var messageBytes = Encoding.UTF8.GetBytes(messageToSign);
using (var ms = new MemoryStream(messageBytes)) {
var si = new CmsSignerInfo() {
Certificate = cert,
HashAlgorithmName = HashAlgorithmNames.Sha256
};
var signature = await CmsDetachedSignature.GenerateSignatureAsync(ms.AsInputStream(), new[] { si }, null);
return CryptographicBuffer.EncodeToBase64String(signature);
}
}
WCF:
public static bool Verify(System.Security.Cryptography.X509Certificates.X509Certificate2 cert, string messageToCheck, string signature) {
var retval = false;
var ci = new ContentInfo(Encoding.UTF8.GetBytes(messageToCheck));
var cms = new SignedCms(ci, true);
cms.Decode(Convert.FromBase64String(signature));
// Check whether the expected certificate was used for the signature.
foreach (var s in cms.SignerInfos) {
if (s.Certificate.Equals(cert)) {
retval = true;
break;
}
}
// The following will throw if the signature is invalid.
cms.CheckSignature(true);
return retval;
}
The trick for me was to understand that the desktop SignedCms needs to be constructed with the original content and then decode the signature to perform the verification.
Related
I recently upgraded RestSharp to version 107.3.0. I had to modify my request some, but the Web API gets the request and returns, but it hangs there waiting for the response...
private async Task<bool> AuthenticateUser(string username, string password)
{
var encryption = new Encryption64();
var encrypt = encryption.Encrypt(password, _key);
var client = new RestClient(UserSettings.URL);
var uri = $"users/authenticate/{username}/";
var pass = new PasswordDTO
{
Password = encrypt
};
var request = new RestRequest(uri)
.AddJsonBody(pass);
//var json = JsonConvert.SerializeObject(pass);
//request.AddParameter("application/json; charset=utf-8", json, ParameterType.RequestBody);
var response = await client.PostAsync<bool>(request);
return response;
}
The line await client.PostAsync<bool>(request) never completes. No errors in the Debug window either. This worked before I upgraded. What am I doing wrong?
I am not sure if this is entirely correct... but I fiddled with it until I got it working.
private bool AuthenticateUser(string username, string password)
{
using (var client = new RestClient(UserSettings.URL))
{
var encryption = new Encryption64();
var encrypt = encryption.Encrypt(password, _key);
var uri = $"users/authenticate/{username}/";
var pass = new PasswordDTO
{
Password = encrypt
};
var request = new RestRequest(uri)
.AddJsonBody(pass);
var response = client.PostAsync<bool>(request).Result;
return response;
}
}
UPDATE
private async Task<bool> AuthenticateUser(string username, string password)
{
using (var client = new RestClient(UserSettings.URL))
{
var encryption = new Encryption64();
var encrypt = encryption.Encrypt(password, _key);
var uri = $"users/authenticate/{username}/";
var pass = new PasswordDTO
{
Password = encrypt
};
var request = new RestRequest(uri)
.AddJsonBody(pass);
var response = await client.PostAsync<bool>(request);
return response;
}
}
public async Task<bool> AuthenticateUserAsync([FromUri] string username, [FromBody] PasswordDTO pass)
{
Log.Logger.ForContext<UserController>().Information("{User} is Logging In", username);
using (var context = new DatabaseContext())
{
var user = await context.bma_users
.AsNoTracking()
.FirstOrDefaultAsync(p => p.username == username);
if (user is null)
return false;
return (user.password == pass.Password);
}
}
I still welcome any ideas for improvement.
I'm trying to establish mtls connection using https://github.com/chkr1011/MQTTnet library in my xamarin.android project. During this process I encountered with such an exception in Init method on calling conResult.Wait(TimeSpan.FromSeconds(60));
{MQTTnet.Exceptions.MqttCommunicationException: Authentication failed, see inner exception. ---> System.Security.Authentication.AuthenticationException: Authentication failed, see inner exception. ---> System.Security.Cryptography.CryptographicException: Caught…}
{System.Security.Cryptography.CryptographicException: Caught unhandled exception in MonoBtlsSslCtx.ProcessHandshake. ---> System.NullReferenceException: Object reference not set to an instance of an object.
at Mono.Btls.MonoBtlsSsl.SetPrivateKey (Mono.Btls.…}
This is my code:
public void Init()
{
ILoadCertificate certificateService = DependencyService.Get<ILoadCertificate>();
var cert = certificateService.LoadPemCertificate("certificate", "private_key");
Console.WriteLine(cert.GetRSAPrivateKey());
string clientId = Guid.NewGuid().ToString();
string mqttURI = "";
int mqttPort = 8883;
var factory = new MqttFactory();
var mqttClient = factory.CreateMqttClient();
bool disableServerValidation = true;
var tlsParameters = new MqttClientOptionsBuilderTlsParameters
{
UseTls = true,
Certificates = new[] { new X509Certificate(cert.Export(X509ContentType.Cert)) },
IgnoreCertificateChainErrors = disableServerValidation,
AllowUntrustedCertificates = disableServerValidation,
SslProtocol = System.Security.Authentication.SslProtocols.Tls12,
CertificateValidationHandler = (o) =>
{
return true;
},
};
var connectOptions = new MqttClientOptionsBuilder()
.WithProtocolVersion(MQTTnet.Formatter.MqttProtocolVersion.V311)
.WithClientId(clientId)
.WithTcpServer(mqttURI, mqttPort)
.WithCommunicationTimeout(new TimeSpan(0, 2, 30))
.WithCleanSession()
.WithTls(tlsParameters)
.Build();
var conResult = mqttClient.ConnectAsync(connectOptions);
conResult.ContinueWith(r =>
{
Console.WriteLine(r.Result.ResultCode);
Console.WriteLine(r.Exception.StackTrace);
});
conResult.Wait(TimeSpan.FromSeconds(60));
var t = mqttClient.PublishAsync("events/test", "test");
t.ContinueWith(r =>
{
Console.WriteLine(r.Result.PacketIdentifier);
Console.WriteLine(r.Exception.StackTrace);
});
t.Wait();
}
//This methods is used to construct certificate:
public X509Certificate2 GetCertificate(string pemCert, string pemKey)
{
string fileNameCert = Path.Combine(Environment
.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), pemCert);
var pem = File.ReadAllText(fileNameCert);
string fileNameKey = Path.Combine(Environment
.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), pemKey);
var key = File.ReadAllText(fileNameKey);
var keyPair = (AsymmetricCipherKeyPair)new PemReader(new StringReader(key))
.ReadObject();
var cert = (Org.BouncyCastle.X509.X509Certificate)new PemReader(new
StringReader(pem)).ReadObject();
var builder = new Pkcs12StoreBuilder();
builder.SetUseDerEncoding(true);
var store = builder.Build();
var certEntry = new X509CertificateEntry(cert);
store.SetCertificateEntry("", certEntry);
store.SetKeyEntry("", new AsymmetricKeyEntry(keyPair.Private), new[] { certEntry });
byte[] data;
using (var ms = new MemoryStream())
{
store.Save(ms, Array.Empty<char>(), new SecureRandom());
data = ms.ToArray();
}
return new X509Certificate2(data);
}
public byte[] GetBytesFromPEM(string pemString, string type)
{
string header; string footer;
switch (type)
{
case "cert":
header = "-----BEGIN CERTIFICATE-----";
footer = "-----END CERTIFICATE-----";
break;
case "key":
header = "-----BEGIN RSA PRIVATE KEY-----";
footer = "-----END RSA PRIVATE KEY-----";
break;
default:
return null;
}
int start = pemString.IndexOf(header) + header.Length;
int end = pemString.IndexOf(footer, start) - start;
return Convert.FromBase64String(pemString.Substring(start, end));
}
I have several options:
Maybe there is some issue with constructing certificate in GetCertificate method;
There is issue with MqttNet library itself. I suspect that the library does not work with certificates in xamarin.android, because i found such topics: https://github.com/xamarin/xamarin-android/issues/4481, https://github.com/chkr1011/MQTTnet/issues/883.
I tried to construct certificate this way, but xamarin does not support rsa.ImportRSAPrivateKey. I have got: System.PlatformNotSupportedException: Operation is not supported on this platform.
string fileNameCert = Path.Combine(Environment
.GetFolderPath(Environment
.SpecialFolder.LocalApplicationData), certificatePath);
using var publicKey = new X509Certificate2(fileNameCert);
string fileNameKey = Path.Combine(Environment
.GetFolderPath(Environment
.SpecialFolder.LocalApplicationData), privateKeyPath);
using var rsa = RSA.Create();
byte[] keyBuffer =
GetBytesFromPEM(File.ReadAllText(fileNameKey), "key");
int o;
rsa.ImportRSAPrivateKey(keyBuffer, out o);
var keyPair = publicKey.CopyWithPrivateKey(rsa);
return new X509Certificate2(keyPair.Export(X509ContentType.Pkcs12));
I have the followinf code for verification of GKLocalPlayer:
var cert = await GetCertificate(gameCenter.PublicKeyURL);
if (cert.Verify())
{
var rsa = cert.GetRSAPublicKey();
if (rsa != null)
{
var sha256 = new SHA256Managed();
var sig = ConcatSignature(gameCenter.PlayerID, gameCenter.BundleID, gameCenter.TimeStamp, gameCenter.Salt);
var hash = sha256.ComputeHash(sig);
if (rsa.VerifyHash(hash, Convert.FromBase64String(gameCenter.Signature), HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1))
{
return true;
}
return false;
}
}
private async Task<X509Certificate2> GetCertificate(string url)
{
var client = new HttpClient();
var response = await client.GetAsync(url);
var rawData = await response.Content.ReadAsByteArrayAsync();
return new X509Certificate2(rawData);
}
private byte[] ConcatSignature(string playerId, string bundleId, string timestamp, string salt)
{
var b = Convert.FromBase64String(salt);
var data = new List<byte>();
data.AddRange(Encoding.UTF8.GetBytes(playerId));
data.AddRange(Encoding.UTF8.GetBytes(bundleId));
data.AddRange(ToBigEndian(Convert.ToUInt64(timestamp)));
data.AddRange(Convert.FromBase64String(salt));
return data.ToArray();
}
private static byte[] ToBigEndian(ulong value)
{
var buffer = new byte[8];
for (int i = 0; i < 8; i++)
{
buffer[7 - i] = unchecked((byte)(value & 0xff));
value = value >> 8;
}
return buffer;
}
but this always returns false, when I am trying to verify the accurate GameCenter. I browsed through all the comments, but I cannot find anything posted specifically for .net Core and GKLocalPlayer verification.
Your code worked for me.
Note it will fail if you not testing it on a device (as the data is time sensitive)
For an app with some kind of chat based features I want to add push notification support for receiving new messages.
What I want to do is use the new token based authentication (.p8 file) from Apple, but I can't find much info about the server part.
I came across the following post:
How to use APNs Auth Key (.p8 file) in C#?
However the answer was not satisfying as there was not much detail about how to:
establish a connection with APNs
use the p8 file (except for some kind of encoding)
send data to the Apple Push Notification Service
You can't really do this on raw .NET Framework at the moment. The new JWT-based APNS server uses HTTP/2 only, which .NET Framework does not yet support.
.NET Core's version of System.Net.Http, however, does, provided you meet the following prerequisites:
On Windows, you must be running Windows 10 Anniversary Edition (v1607) or higher, or the equivalent build of Windows Server 2016 (I think).
On Linux, you must have a version of libcurl that supports HTTP/2.
On macOS, you have to compile libcurl with support for HTTP/2, then use the DYLD_INSERT_LIBRARIES environment variable in order to load your custom build of libcurl.
You should be able to use .NET Core's version of System.Net.Http in the .NET Framework if you really want.
I have no idea what happens on Mono, Xamarin or UWP.
There are then three things you have to do:
Parse the private key that you have been given. This is currently an ECDSA key, and you can load this into a System.Security.Cryptography.ECDsa object.
On Windows, you can use the CNG APIs. After parsing the base64-encoded DER part of the key file, you can then create a key with new ECDsaCng(CngKey.Import(data, CngKeyBlobFormat.Pkcs8PrivateBlob)).
On macOS or Linux there is no supported API and you have to parse the DER structure yourself, or use a third-party library.
Create a JSON Web Token / Bearer Token. If you use the System.IdentityModel.Tokens.Jwt package from NuGet, this is fairly simple. You will need the Key ID and Team ID from Apple.
public static string CreateToken(ECDsa key, string keyID, string teamID)
{
var securityKey = new ECDsaSecurityKey(key) { KeyId = keyID };
var credentials = new SigningCredentials(securityKey, SecurityAlgorithms.EcdsaSha256);
var descriptor = new SecurityTokenDescriptor
{
IssuedAt = DateTime.Now,
Issuer = teamID,
SigningCredentials = credentials
};
var handler = new JwtSecurityTokenHandler();
var encodedToken = handler.CreateEncodedJwt(descriptor);
return encodedToken;
}
Send an HTTP/2 request. This is as normal, but you need to do two extra things:
Set yourRequestMessage.Version to new Version(2, 0) in order to make the request using HTTP/2.
Set yourRequestMessage.Headers.Authorization to new AuthenticationHeaderValue("bearer", token) in order to provide the bearer authentication token / JWT with your request.
Then just put your JSON into the HTTP request and POST it to the correct URL.
Because Token (.p8) APNs only works in HTTP/2, thus most of the solutions only work in .net Core. Since my project is using .net Framework, some tweak is needed. If you're using .net Framework like me, please read on.
I search here and there and encountered several issues, which I managed to fix and pieced them together.
Below is the APNs class that actually works. I created a new class library for it, and placed the .P8 files within the AuthKeys folder of the class library. REMEMBER to right click on the .P8 files and set it to "Always Copy". Refer Get relative file path in a class library project that is being referenced by a web project.
After that, to get the location of the P8 files, please use AppDomain.CurrentDomain.RelativeSearchPath for web project or AppDomain.CurrentDomain.BaseDirectory for win application. Refer Why AppDomain.CurrentDomain.BaseDirectory not contains "bin" in asp.net app?
To get the token from the P8, you'll need to use the BouncyCastle class, please download it from Nuget.
using Jose;
using Newtonsoft.Json;
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.OpenSsl;
using Security.Cryptography;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
namespace PushLibrary
{
public class ApplePushNotificationPush
{
//private const string WEB_ADDRESS = "https://api.sandbox.push.apple.com:443/3/device/{0}";
private const string WEB_ADDRESS = "https://api.push.apple.com:443/3/device/{0}";
private string P8_PATH = AppDomain.CurrentDomain.RelativeSearchPath + #"\AuthKeys\APNs_AuthKey.p8";
public ApplePushNotificationPush()
{
}
public async Task<bool> SendNotification(string deviceToken, string title, string content, int badge = 0, List<Tuple<string, string>> parameters = null)
{
bool success = true;
try
{
string data = System.IO.File.ReadAllText(P8_PATH);
List<string> list = data.Split('\n').ToList();
parameters = parameters ?? new List<Tuple<string, string>>();
string prk = list.Where((s, i) => i != 0 && i != list.Count - 1).Aggregate((agg, s) => agg + s);
ECDsaCng key = new ECDsaCng(CngKey.Import(Convert.FromBase64String(prk), CngKeyBlobFormat.Pkcs8PrivateBlob));
string token = GetProviderToken();
string url = string.Format(WEB_ADDRESS, deviceToken);
HttpRequestMessage httpRequestMessage = new HttpRequestMessage(HttpMethod.Post, url);
httpRequestMessage.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token);
httpRequestMessage.Headers.TryAddWithoutValidation("apns-push-type", "alert"); // or background
httpRequestMessage.Headers.TryAddWithoutValidation("apns-id", Guid.NewGuid().ToString("D"));
//Expiry
//
httpRequestMessage.Headers.TryAddWithoutValidation("apns-expiration", Convert.ToString(0));
//Send imediately
httpRequestMessage.Headers.TryAddWithoutValidation("apns-priority", Convert.ToString(10));
//App Bundle
httpRequestMessage.Headers.TryAddWithoutValidation("apns-topic", "com.xxx.yyy");
//Category
httpRequestMessage.Headers.TryAddWithoutValidation("apns-collapse-id", "test");
//
var body = JsonConvert.SerializeObject(new
{
aps = new
{
alert = new
{
title = title,
body = content,
time = DateTime.Now.ToString()
},
badge = 1,
sound = "default"
},
acme2 = new string[] { "bang", "whiz" }
});
httpRequestMessage.Version = new Version(2, 0);
using (var stringContent = new StringContent(body, Encoding.UTF8, "application/json"))
{
//Set Body
httpRequestMessage.Content = stringContent;
Http2Handler.Http2CustomHandler handler = new Http2Handler.Http2CustomHandler();
handler.SslProtocols = System.Security.Authentication.SslProtocols.Tls12 | System.Security.Authentication.SslProtocols.Tls11 | System.Security.Authentication.SslProtocols.Tls;
//handler.ServerCertificateCustomValidationCallback = (message, cert, chain, errors) => true;
//Continue
using (HttpClient client = new HttpClient(handler))
{
HttpResponseMessage resp = await client.SendAsync(httpRequestMessage).ContinueWith(responseTask =>
{
return responseTask.Result;
});
if (resp != null)
{
string apnsResponseString = await resp.Content.ReadAsStringAsync();
handler.Dispose();
}
handler.Dispose();
}
}
}
catch (Exception ex)
{
success = false;
}
return success;
}
private string GetProviderToken()
{
double epochNow = (int)DateTime.UtcNow.Subtract(new DateTime(1970, 1, 1)).TotalSeconds;
Dictionary<string, object> payload = new Dictionary<string, object>()
{
{ "iss", "YOUR APPLE TEAM ID" },
{ "iat", epochNow }
};
var extraHeaders = new Dictionary<string, object>()
{
{ "kid", "YOUR AUTH KEY ID" },
{ "alg", "ES256" }
};
CngKey privateKey = GetPrivateKey();
return JWT.Encode(payload, privateKey, JwsAlgorithm.ES256, extraHeaders);
}
private CngKey GetPrivateKey()
{
using (var reader = File.OpenText(P8_PATH))
{
ECPrivateKeyParameters 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();
return EccKey.New(x, y, d);
}
}
}
}
Secondly, if you noticed, I am using the custom WinHTTPHandler to make the code to support HTTP/2 based on How to make the .net HttpClient use http 2.0?. I am creating this using another class library, remember to download WinHTTPHandler from Nuget.
public class Http2CustomHandler : WinHttpHandler
{
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, System.Threading.CancellationToken cancellationToken)
{
request.Version = new Version("2.0");
return base.SendAsync(request, cancellationToken);
}
}
After that, just call the "SendNotification" on the ApplePushNotificationPush class and you should get the message on your iPhone.
private string GetToken()
{
var dsa = GetECDsa();
return CreateJwt(dsa, "keyId", "teamId");
}
private ECDsa GetECDsa()
{
using (TextReader reader = System.IO.File.OpenText("AuthKey_xxxxxxx.p8"))
{
var ecPrivateKeyParameters =
(ECPrivateKeyParameters)new Org.BouncyCastle.OpenSsl.PemReader(reader).ReadObject();
var q = ecPrivateKeyParameters.Parameters.G.Multiply(ecPrivateKeyParameters.D).Normalize();
var qx = q.AffineXCoord.GetEncoded();
var qy = q.AffineYCoord.GetEncoded();
var d = ecPrivateKeyParameters.D.ToByteArrayUnsigned();
// Convert the BouncyCastle key to a Native Key.
var msEcp = new ECParameters {Curve = ECCurve.NamedCurves.nistP256, Q = {X = qx, Y = qy}, D = d};
return ECDsa.Create(msEcp);
}
}
private string CreateJwt(ECDsa key, string keyId, string teamId)
{
var securityKey = new ECDsaSecurityKey(key) { KeyId = keyId };
var credentials = new SigningCredentials(securityKey, SecurityAlgorithms.EcdsaSha256);
var descriptor = new SecurityTokenDescriptor
{
IssuedAt = DateTime.Now,
Issuer = teamId,
SigningCredentials = credentials,
};
var handler = new JwtSecurityTokenHandler();
var encodedToken = handler.CreateEncodedJwt(descriptor);
return encodedToken;
}
It have tried the above on ASP.NET CORE 2.1 and 2.2 to no avail. The response I always got was "The message received was unexpected or badly formatted" with HttpVersion20 enabled, which made me doubt whether http2 implementation is concrete.
Below is what worked on ASP.NET CORE 3.0;
var teamId = "YOURTEAMID";
var keyId = "YOURKEYID";
try
{
//
var data = await System.IO.File.ReadAllTextAsync(Path.Combine(_environment.ContentRootPath, "apns/"+config.P8FileName));
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));
//
var token = CreateToken(key, keyId, teamId);
//
var deviceToken = "XXXXXXXXXXXXXXXXXXXXXXXXXXXX";
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"));
//Expiry
//
request.Headers.TryAddWithoutValidation("apns-expiration", Convert.ToString(0));
//Send imediately
request.Headers.TryAddWithoutValidation("apns-priority", Convert.ToString(10));
//App Bundle
request.Headers.TryAddWithoutValidation("apns-topic", "com.xx.yy");
//Category
request.Headers.TryAddWithoutValidation("apns-collapse-id", "test");
//
var body = JsonConvert.SerializeObject(new
{
aps = new
{
alert = new
{
title = "Test",
body = "Sample Test APNS",
time = DateTime.Now.ToString()
},
badge = 1,
sound = "default"
},
acme2 = new string[] { "bang", "whiz" }
})
//
request.Version = HttpVersion.Version20;
//
using (var stringContent = new StringContent(body, Encoding.UTF8, "application/json"))
{
//Set Body
request.Content = stringContent;
_logger.LogInformation(request.ToString());
//
var handler = new HttpClientHandler();
//
handler.SslProtocols = SslProtocols.Tls12 | SslProtocols.Tls11 | SslProtocols.Tls;
//
handler.ServerCertificateCustomValidationCallback = (message, cert, chain, errors) => true;
//Continue
using (HttpClient client = new HttpClient(handler))
{
//
HttpResponseMessage resp = await client.SendAsync(request).ContinueWith(responseTask =>
{
return responseTask.Result;
//
});
//
_logger.LogInformation(resp.ToString());
//
if (resp != null)
{
string apnsResponseString = await resp.Content.ReadAsStringAsync();
//
handler.Dispose();
//ALL GOOD ....
return;
}
//
handler.Dispose();
}
}
}
catch (HttpRequestException e)
{
_logger.LogError(5, e.StackTrace, e);
}
For CreateToken() Refer Above Recommended solution by yaakov,
I has a problem like you. And i seen #gorniv answer. So it's work with me!
May be you can use: https://www.nuget.org/packages/Apple.Auth.Signin for it!
Goodluck!
I am trying to build a SCEP server to support Apple MDM Device Enrollment. This needs to be implemented into our current MDM Service, written in C#.
I have looked into the following for inspiration:
JSCEP, a java library for scep server implementation https://github.com/jscep/jscep
Bouncy Castle (complicated, and not a lot of documentation on the C# side)
Cisco SCEP documentation http://www.cisco.com/c/en/us/support/docs/security-vpn/public-key-infrastructure-pki/116167-technote-scep-00.html
Does anyone know of any solid examples, on creating a C# SCEP server? I haven't been able to find any good documentation for this.
UPDATE
This is what i have by now, it is still not working, but i think the issue lies with the signed pkcs7, but I am not sure what i am missing?
public class ScepModule: NancyModule
{
/// <summary>
/// The _log.
/// </summary>
private readonly ILog log = LogManager.GetLogger(typeof(ScepModule));
/// <summary>
/// Initializes a new instance of the <see cref="ScepModule"/> class.
/// </summary>
/// <param name="cp">
/// The certificate provider.
/// </param>
/// <param name="config">
/// The config.
/// </param>
public ScepModule(MdmConfigDTO config)
: base("/cimdm/scep")
{
this.log.Debug(m => m("Instanciating scep Module."));
this.Get["/"] = result =>
{
var message = Request.Query["message"];
var operation = Request.Query["operation"];
if (operation == "GetCACert")
{
return RespondWithCACert();
}
else if (operation == "GetCACaps")
{
return RespondWithCACaps();
}
return "";
};
this.Post["/"] = result =>
{
var message = Request.Query["message"];
var operation = Request.Query["operation"];
byte[] requestData = null;
using (var binaryReader = new BinaryReader(Request.Body))
{
requestData = binaryReader.ReadBytes((int)Request.Body.Length);
}
var headers = Request.Headers;
foreach (var header in headers)
{
this.log.Debug(m => m("Header: {0}, Value: {1}", header.Key, String.Join(",", header.Value)));
}
var caCert = getSignerCert();
var signingCert = createSigningCert(caCert, requestData, config);
return signingCert;
};
this.log.Debug(m => m("Finished Instanciating scep Module."));
}
private Response RespondWithCACert()
{
var caCert = getSignerCert();
var response = new Response();
response.ContentType = "application/x-x509-ca-cert";
response.Contents = stream =>
{
byte[] data = caCert.Export(X509ContentType.Cert);
stream.Write(data, 0, data.Length);
stream.Flush();
stream.Close();
};
return response;
}
private Response RespondWithCACaps()
{
var response = new Response();
response.ContentType = "text/plain; charset=ISO-8859-1";
//byte[] data = Encoding.UTF8.GetBytes("POSTPKIOperation\nSHA-512\nSHA-256\nSHA-1");
byte[] data = Encoding.UTF8.GetBytes("POSTPKIOperation\nSHA-1");
response.Contents = stream =>
{
stream.Write(data, 0, data.Length);
stream.Flush();
stream.Close();
};
return response;
}
private Response createSigningCert(X509Certificate2 caCert, byte[] data, MdmConfigDTO config)
{
var signedResponse = new SignedCms();
signedResponse.Decode(data);
var caChain = new X509Certificate2Collection(caCert);
signedResponse.CheckSignature(caChain, true);
var attributes = signedResponse
.SignerInfos
.Cast<System.Security.Cryptography.Pkcs.SignerInfo>()
.SelectMany(si => si.SignedAttributes.Cast<CryptographicAttributeObject>());
// Any errors then return null
if (attributes.Any(att => att.Oid.Value.Equals(Oids.Scep.FailInfo)))
{
return null;
}
byte[] msg = DecryptMsg(signedResponse.ContentInfo.Content, caChain);
byte[] certResult = GenerateSelfSignedClientCertificate(msg, caCert, config);
X509Certificate2Collection reqCerts = signedResponse.Certificates;
//Create Enveloped PKCS#7 data
var envelpeDataPkcs7 = createEnvelopedDataPkcs7(certResult);
//Create Signed PKCS#7 data
var signedDataPkcs7 = createSignedDataPkcs7(envelpeDataPkcs7, caCert, attributes);
var response = new Response();
response.ContentType = "application/x-pki-message";
response.WithHeader("Cache-Control", "no-store, no-cache, must-revalidate");
response.WithHeader("Pragma", "no-cache");
var execPath = Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location);
File.WriteAllBytes(execPath + "\\signedDataPkcs7.p7b", signedDataPkcs7);
File.WriteAllBytes(execPath + "\\envelpeDataPkcs7.p7b", envelpeDataPkcs7);
response.Contents = stream =>
{
stream.Write(signedDataPkcs7, 0, signedDataPkcs7.Length);
stream.Flush();
stream.Close();
};
return response;
}
private byte[] GenerateSelfSignedClientCertificate(byte[] encodedPkcs10, X509Certificate2 caCert, MdmConfigDTO config)
{
var execPath = Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location);
File.WriteAllText(execPath + "\\scepPkcs10.csr", "-----BEGIN CERTIFICATE REQUEST-----\n" + Convert.ToBase64String(encodedPkcs10) + "\n-----END CERTIFICATE REQUEST-----");
Pkcs10CertificationRequest csr = new Pkcs10CertificationRequest(encodedPkcs10);
CertificationRequestInfo csrInfo = csr.GetCertificationRequestInfo();
SubjectPublicKeyInfo pki = csrInfo.SubjectPublicKeyInfo;
Asn1Set attributes = csrInfo.Attributes;
//pub key for the signed cert
var publicKey = pki.GetPublicKey();
// Build a Version3 Certificate
DateTime startDate = DateTime.UtcNow.AddMonths(-1);
DateTime expiryDate = startDate.AddYears(10);
BigInteger serialNumber = new BigInteger(32, new Random());
this.log.Debug(m => m("Certificate Signing Request Subject is: {0}", csrInfo.Subject));
CmsSignedDataGenerator cmsGen = new CmsSignedDataGenerator();
X509V3CertificateGenerator certGen = new X509V3CertificateGenerator();
string signerCN = caCert.GetNameInfo(X509NameType.SimpleName, true);
certGen.SetSubjectDN(new X509Name(String.Format("CN={0}", config.HostName)));
certGen.SetSerialNumber(serialNumber);
certGen.SetIssuerDN(new X509Name(String.Format("CN={0}", signerCN)));
certGen.SetNotBefore(startDate);
certGen.SetNotAfter(expiryDate);
certGen.SetSignatureAlgorithm("SHA1withRSA");
certGen.SetPublicKey(PublicKeyFactory.CreateKey(pki));
AsymmetricCipherKeyPair caPair = DotNetUtilities.GetKeyPair(caCert.PrivateKey);
for(int i=0; i!=attributes.Count; i++)
{
AttributeX509 attr = AttributeX509.GetInstance(attributes[i]);
//process extension request
if (attr.AttrType.Id.Equals(PkcsObjectIdentifiers.Pkcs9AtExtensionRequest.Id))
{
X509Extensions extensions = X509Extensions.GetInstance(attr.AttrValues[0]);
var e = extensions.ExtensionOids.GetEnumerator();
while (e.MoveNext())
{
DerObjectIdentifier oid = (DerObjectIdentifier)e.Current;
Org.BouncyCastle.Asn1.X509.X509Extension ext = extensions.GetExtension(oid);
certGen.AddExtension(oid, ext.IsCritical, ext.GetParsedValue());
}
}
}
//certGen.AddExtension(X509Extensions.KeyUsage, true, new KeyUsage(KeyUsage.DigitalSignature | KeyUsage.KeyEncipherment));
certGen.AddExtension(X509Extensions.ExtendedKeyUsage, true, new ExtendedKeyUsage(KeyPurposeID.AnyExtendedKeyUsage));
certGen.AddExtension(X509Extensions.BasicConstraints, true, new BasicConstraints(0));
Org.BouncyCastle.X509.X509Certificate selfSignedCert = certGen.Generate(caPair.Private);
//Check if the certificate can be verified
selfSignedCert.Verify(caPair.Public);
if (log.IsDebugEnabled)
{
//var execPath = Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location);
File.WriteAllBytes(execPath + "\\scepCertificate.pfx", selfSignedCert.GetEncoded());
}
return selfSignedCert.GetEncoded();
}
private byte[] createEnvelopedDataPkcs7(byte[] pkcs7RequestData)
{
var recipient = new CmsRecipient(new X509Certificate2(pkcs7RequestData));
var envelopedContent = new System.Security.Cryptography.Pkcs.ContentInfo(new Oid(Oids.Pkcs7.EncryptedData, "envelopedData"), pkcs7RequestData);
//var envelopedContent = new System.Security.Cryptography.Pkcs.ContentInfo(pkcs7RequestData);
var envelopedMessage = new EnvelopedCms(envelopedContent);
//var envelopedMessage = new EnvelopedCms(Con);
envelopedMessage.Encrypt(recipient);
var encryptedMessageData = envelopedMessage.Encode();
return encryptedMessageData;
}
private byte[] createSignedDataPkcs7(byte[] encryptedMessageData, X509Certificate2 localPrivateKey, IEnumerable<CryptographicAttributeObject> attributes)
{
var senderNonce = attributes
.Single(att => att.Oid.Value.Equals(Oids.Scep.SenderNonce))
.Values[0];
var transactionId = attributes
.Single(att => att.Oid.Value.Equals(Oids.Scep.TransactionId))
.Values[0];
// Create the outer envelope, signed with the local private key
var signer = new CmsSigner(SubjectIdentifierType.IssuerAndSerialNumber, localPrivateKey);
//{
// DigestAlgorithm = new Oid(Oids.Pkcs.SHA1, "digestAlorithm")
//};
//signer.SignedAttributes.Add(signingTime);
var messageType = new AsnEncodedData(Oids.Scep.MessageType, DerEncoding.EncodePrintableString("3"));
signer.SignedAttributes.Add(messageType);
signer.SignedAttributes.Add(transactionId);
var recipientNonce = new Pkcs9AttributeObject(Oids.Scep.RecipientNonce, DerEncoding.EncodeOctet(senderNonce.RawData));
signer.SignedAttributes.Add(recipientNonce);
var pkiStatus = new AsnEncodedData(Oids.Scep.PkiStatus, DerEncoding.EncodePrintableString("0"));
signer.SignedAttributes.Add(pkiStatus);
var nonceBytes = new byte[16];
RNGCryptoServiceProvider.Create().GetBytes(nonceBytes);
senderNonce = new Pkcs9AttributeObject(Oids.Scep.SenderNonce, DerEncoding.EncodeOctet(nonceBytes));
signer.SignedAttributes.Add(senderNonce);
//var failInfo = new Pkcs9AttributeObject(Oids.Scep.FailInfo, DerEncoding.EncodePrintableString("2"));
//signer.SignedAttributes.Add(failInfo);
// Seems that the oid is not needed for this envelope
var contentInfo = new System.Security.Cryptography.Pkcs.ContentInfo(encryptedMessageData); //new Oid("1.2.840.113549.1.7.1", "data"), encryptedMessageData);
var signedCms = new SignedCms(contentInfo, false);
signedCms.ComputeSignature(signer);
return signedCms.Encode();
}
private X509Certificate2 getSignerCert()
{
string thumbprint = "CACertThumbprint";
thumbprint = Regex.Replace(thumbprint, #"[^\da-zA-z]", string.Empty).ToUpper();
// Get a local private key for sigining the outer envelope
var certStore = new X509Store(StoreName.Root, StoreLocation.LocalMachine);
certStore.Open(OpenFlags.ReadOnly);
var signerCert = certStore
.Certificates
.Find(X509FindType.FindByThumbprint, thumbprint, false)
.Cast<X509Certificate2>()
.Single();
certStore.Close();
return signerCert;
}
// Decrypt the encoded EnvelopedCms message for one of the
// recipients.
public Byte[] DecryptMsg(byte[] encodedEnvelopedCms, X509Certificate2Collection caChain)
{
// Prepare object in which to decode and decrypt.
EnvelopedCms envelopedCms = new EnvelopedCms();
// Decode the message.
envelopedCms.Decode(encodedEnvelopedCms);
// Display the number of recipients
DisplayEnvelopedCms(envelopedCms, false);
// Decrypt the message.
this.log.Debug("Decrypting Data for one recipient ... ");
envelopedCms.Decrypt(envelopedCms.RecipientInfos[0], caChain);
this.log.Debug("Done.");
// The decrypted message occupies the ContentInfo property
// after the Decrypt method is invoked.
return envelopedCms.ContentInfo.Content;
}
// Display the ContentInfo property of an EnvelopedCms object.
private void DisplayEnvelopedCmsContent(String desc,
EnvelopedCms envelopedCms)
{
this.log.Debug(string.Format(desc + " (length {0}): ",
envelopedCms.ContentInfo.Content.Length));
foreach (byte b in envelopedCms.ContentInfo.Content)
{
this.log.Debug(b.ToString() + " ");
}
}
// Display some properties of an EnvelopedCms object.
private void DisplayEnvelopedCms(EnvelopedCms e,
Boolean displayContent)
{
this.log.Debug("\nEnveloped PKCS #7 Message Information:");
this.log.Debug(string.Format(
"\tThe number of recipients for the Enveloped PKCS #7 " +
"is: {0}", e.RecipientInfos.Count));
for (int i = 0; i < e.RecipientInfos.Count; i++)
{
this.log.Debug(string.Format(
"\tRecipient #{0} has type {1}.",
i + 1,
e.RecipientInfos[i].RecipientIdentifier.Type));
}
if (displayContent)
{
DisplayEnvelopedCmsContent("Enveloped PKCS #7 Content", e);
}
}
}