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));
Related
Can someone tell me how I can recreate the hash from WooCommerce webhook to compare with the "X-WC-Webhook-Signature" header hash from the request?
The documentation specifies the hash is generated from the 'payload', but I am unable to generate the same hash.
My API is .NET Core 3.1
First thing i tried:
var secret = "XXX";
var requestHash = Request.Headers["X-WC-Webhook-Signature"];
var generatedHash = "";
Stream byteContent = Request.Body;
byte[] keyByte = encoding.GetBytes(secret);
using(var hmacsha256 = new HMACSHA256(keyByte))
{
byte[] hashmessage = hmacsha256.ComputeHash(byteContent);
generatedHash = Convert.ToBase64String(hashmessage);
}
if(requestHash == generatedHash)
{
// Succes
}
Second:
using(StreamReader reader = new StreamReader(Request.Body, Encoding.UTF8))
{
String json = await reader.ReadToEndAsync();
var generatedHash = "";
byte[] messageBytes = encoding.GetBytes(json);
keyByte = encoding.GetBytes(secret);
using(var hmacsha256 = new HMACSHA256(keyByte))
{
byte[] hashmessage = hmacsha256.ComputeHash(messageBytes);
generatedHash = Convert.ToBase64String(hashmessage);
}
if(requestHash == generatedHash)
{
// Succes
}
}
I had the same problem and here's what I did:
using System.Security.Cryptography;
if (Request.Headers.TryGetValue("X-WC-Webhook-Signature", out var headerValues))
{
XWCWebhookSignature = headerValues.FirstOrDefault();
}
var encoding = new UTF8Encoding();
var key = "yourKeyValue";
var keyBytes = encoding.GetBytes(key);
var hash = new HMACSHA256(keyBytes);
var computedHash = hash.ComputeHash(Request.Body);
var computedHashString = System.Convert.ToBase64String(computedHash);
if (XWCWebhookSignature != computedHashString)
{
return Unauthorized();
}
Update: For this to work you will need to go to Startup.cs file and find "services.Configure" section.
Add options.AllowSynchronousIO = true;
It should look like this:
services.Configure<IISServerOptions>(options =>
{
options.AllowSynchronousIO = true;
});
I'm trying to send push notifications to iOS devices, using token-based authentication.
As required, I generated an APNs Auth Key in Apple's Dev Portal, and downloaded it (it's a file with p8 extension).
To send push notifications from my C# server, I need to somehow use this p8 file to sign my JWT tokens. How do I do that?
I tried to load the file to X509Certificate2, but X509Certificate2 doesn't seem to accept p8 files, so then I tried to convert the file to pfx/p12, but couldn't find a way to do that that actually works.
I found a way to do that, using BouncyCastle:
private static CngKey GetPrivateKey()
{
using (var reader = File.OpenText("path/to/apns/auth/key/file.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();
return EccKey.New(x, y, d);
}
}
And now creating and signing the token (using jose-jwt):
private static string GetProviderToken()
{
var epochNow = (int) DateTime.UtcNow.Subtract(new DateTime(1970, 1, 1)).TotalSeconds;
var payload = new Dictionary<string, object>()
{
{"iss", "your team id"},
{"iat", epochNow}
};
var extraHeaders = new Dictionary<string, object>()
{
{"kid", "your key id"}
};
var privateKey = GetPrivateKey();
return JWT.Encode(payload, privateKey, JwsAlgorithm.ES256, extraHeaders);
}
I hope this will be a solution;
private static string GetToken(string fileName)
{
var fileContent = File.ReadAllText(fileName).Replace("-----BEGIN PRIVATE KEY-----", "").Replace
("-----END PRIVATE KEY-----", "").Replace("\r", "");
var signatureAlgorithm = GetEllipticCurveAlgorithm(fileContent);
ECDsaSecurityKey eCDsaSecurityKey = new ECDsaSecurityKey(signatureAlgorithm)
{
KeyId = "S********2"
};
var handler = new JwtSecurityTokenHandler();
JwtSecurityToken token = handler.CreateJwtSecurityToken(
issuer: "********-****-****-****-************",
audience: "appstoreconnect-v1",
expires: DateTime.UtcNow.AddMinutes(5),
issuedAt: DateTime.UtcNow,
notBefore: DateTime.UtcNow,
signingCredentials: new SigningCredentials(eCDsaSecurityKey, SecurityAlgorithms.EcdsaSha256));
return token.RawData;
}
private static ECDsa GetEllipticCurveAlgorithm(string privateKey)
{
var keyParams = (ECPrivateKeyParameters)PrivateKeyFactory.CreateKey(Convert.FromBase64String(privateKey));
var normalizedEcPoint = keyParams.Parameters.G.Multiply(keyParams.D).Normalize();
return ECDsa.Create(new ECParameters
{
Curve = ECCurve.CreateFromValue(keyParams.PublicKeyParamSet.Id),
D = keyParams.D.ToByteArrayUnsigned(),
Q =
{
X = normalizedEcPoint.XCoord.GetEncoded(),
Y = normalizedEcPoint.YCoord.GetEncoded()
}
});
}
A way to create the key without BouncyCastle:
var privateKeyString = (await File.ReadAllTextAsync("<PATH_TO_KEY_FILE>"))
.Replace("-----BEGIN PRIVATE KEY-----", "")
.Replace("-----END PRIVATE KEY-----", "")
.Replace("\n", "");
using var algorithm = ECDsa.Create();
algorithm.ImportPkcs8PrivateKey(Convert.FromBase64String(privateKeyText), out var _);
var securityKey = new ECDsaSecurityKey(algorithm) { KeyId = "<KEY_ID>" };
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);
}
}
}
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.
I have a token, a file containing public key and I want to verify the signature.
I tried to verify signature based on this.
However, decodedCrypto and decodedSignature don't match.
Here is my code:
public static string Decode(string token, string key, bool verify)
{
var parts = token.Split('.');
var header = parts[0];
var payload = parts[1];
byte[] crypto = Base64UrlDecode(parts[2]);
var headerJson = Encoding.UTF8.GetString(Base64UrlDecode(header));
var headerData = JObject.Parse(headerJson);
var payloadJson = Encoding.UTF8.GetString(Base64UrlDecode(payload));
var payloadData = JObject.Parse(payloadJson);
if (verify)
{
var bytesToSign = Encoding.UTF8.GetBytes(string.Concat(header, ".", payload));
var keyBytes = Encoding.UTF8.GetBytes(key);
var algorithm = (string)headerData["alg"];
var signature = HashAlgorithms[GetHashAlgorithm(algorithm)](keyBytes, bytesToSign);
var decodedCrypto = Convert.ToBase64String(crypto);
var decodedSignature = Convert.ToBase64String(signature);
if (decodedCrypto != decodedSignature)
{
throw new ApplicationException(string.Format("Invalid signature. Expected {0} got {1}", decodedCrypto, decodedSignature));
}
}
return payloadData.ToString();
}
I'm sure that the signature of token is valid. I try to verify on https://jwt.io/ and it showed that Signature verified.
So the problem is the algorithm to encode, decode.
Is there anyone can solve this problem? The algorithm is RS256
How about using JwtSecurityTokenHandler?
it could look something like this:
public bool ValidateToken(string token, byte[] secret)
{
var tokenHandler = new JwtSecurityTokenHandler();
var validationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningToken = new BinarySecretSecurityToken(secret)
};
SecurityToken validatedToken;
try
{
tokenHandler.ValidateToken(token, validationParameters, out validatedToken);
}
catch (Exception)
{
return false;
}
return validatedToken != null;
}
Be aware I haven't tested it but we used a similar implementation in one of the projects
I finally got a solution from my colleague.
For those who have the same problem, try my code:
public static string Decode(string token, string key, bool verify = true)
{
string[] parts = token.Split('.');
string header = parts[0];
string payload = parts[1];
byte[] crypto = Base64UrlDecode(parts[2]);
string headerJson = Encoding.UTF8.GetString(Base64UrlDecode(header));
JObject headerData = JObject.Parse(headerJson);
string payloadJson = Encoding.UTF8.GetString(Base64UrlDecode(payload));
JObject payloadData = JObject.Parse(payloadJson);
if (verify)
{
var keyBytes = Convert.FromBase64String(key); // your key here
AsymmetricKeyParameter asymmetricKeyParameter = PublicKeyFactory.CreateKey(keyBytes);
RsaKeyParameters rsaKeyParameters = (RsaKeyParameters)asymmetricKeyParameter;
RSAParameters rsaParameters = new RSAParameters();
rsaParameters.Modulus = rsaKeyParameters.Modulus.ToByteArrayUnsigned();
rsaParameters.Exponent = rsaKeyParameters.Exponent.ToByteArrayUnsigned();
RSACryptoServiceProvider rsa = new RSACryptoServiceProvider();
rsa.ImportParameters(rsaParameters);
SHA256 sha256 = SHA256.Create();
byte[] hash = sha256.ComputeHash(Encoding.UTF8.GetBytes(parts[0] + '.' + parts[1]));
RSAPKCS1SignatureDeformatter rsaDeformatter = new RSAPKCS1SignatureDeformatter(rsa);
rsaDeformatter.SetHashAlgorithm("SHA256");
if (!rsaDeformatter.VerifySignature(hash, FromBase64Url(parts[2])))
throw new ApplicationException(string.Format("Invalid signature"));
}
return payloadData.ToString();
}
It works for me. The algorithm is RS256.
I know this is an old thread but I could have recommended you to use this library instead of writing on your own. It has got some good documentation to get started. Am using it without any issues.
byte[] crypto = Base64UrlDecode(parts[2]);
In this line you are base64 decoding signature part of JWT token, but as I know that part isn't base64 encoded. Please try this code. ( I have commented out unnecessary lines )
public static string Decode(string token, string key, bool verify)
{
var parts = token.Split('.');
var header = parts[0];
var payload = parts[1];
// byte[] crypto = Base64UrlDecode(parts[2]);
var jwtSignature = parts[2];
var headerJson = Encoding.UTF8.GetString(Base64UrlDecode(header));
var headerData = JObject.Parse(headerJson);
var payloadJson = Encoding.UTF8.GetString(Base64UrlDecode(payload));
var payloadData = JObject.Parse(payloadJson);
if (verify)
{
var bytesToSign = Encoding.UTF8.GetBytes(string.Concat(header, ".", payload));
var keyBytes = Encoding.UTF8.GetBytes(key);
var algorithm = (string)headerData["alg"];
var computedJwtSignature = Encoding.UTF8.GetString(HashAlgorithms[GetHashAlgorithm(algorithm)](keyBytes, bytesToSign));
// var decodedCrypto = Convert.ToBase64String(crypto);
// var decodedSignature = Convert.ToBase64String(signature);
if (jwtSignature != computedJwtSignature)
{
throw new ApplicationException(string.Format("Invalid signature. Expected {0} got {1}", decodedCrypto, decodedSignature));
}
}
return payloadData.ToString();
}