diff --git a/build.bat b/build.bat index d4708d7..6ca5605 100644 --- a/build.bat +++ b/build.bat @@ -4,7 +4,7 @@ if "%config%" == "" ( set config=Debug ) -set version=2.0.2 +set version=2.1.1 if not "%PackageVersion%" == "" ( set version=%PackageVersion% diff --git a/src/MpesaLib/Clients/MpesaClient.cs b/src/MpesaLib/Clients/MpesaClient.cs index e9ca973..06f3b97 100644 --- a/src/MpesaLib/Clients/MpesaClient.cs +++ b/src/MpesaLib/Clients/MpesaClient.cs @@ -115,6 +115,11 @@ public async Task GetAuthTokenAsync(string consumerKey, string consumerS /// public async Task MakeB2BPaymentAsync(BusinessToBusiness b2bitem, string accesstoken, string requestUri) { + +#if NET45 + ServicePointManager.SecurityProtocol = + SecurityProtocolType.Tls12 | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls; +#endif _httpclient.DefaultRequestHeaders.Clear(); _httpclient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); _httpclient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accesstoken); @@ -156,6 +161,12 @@ public async Task MakeB2BPaymentAsync(BusinessToBusiness b2bitem, string /// public async Task MakeB2CPaymentAsync(BusinessToCustomer b2citem, string accesstoken, string requestUri) { + +#if NET45 + ServicePointManager.SecurityProtocol = + SecurityProtocolType.Tls12 | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls; +#endif + _httpclient.DefaultRequestHeaders.Clear(); _httpclient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); _httpclient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accesstoken); @@ -197,6 +208,11 @@ public async Task MakeB2CPaymentAsync(BusinessToCustomer b2citem, string /// public async Task MakeC2BPaymentAsync(CustomerToBusinessSimulate c2bsimulate, string accesstoken, string requestUri) { + +#if NET45 + ServicePointManager.SecurityProtocol = + SecurityProtocolType.Tls12 | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls; +#endif _httpclient.DefaultRequestHeaders.Clear(); _httpclient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); _httpclient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accesstoken); @@ -232,6 +248,11 @@ public async Task MakeC2BPaymentAsync(CustomerToBusinessSimulate c2bsimu /// public async Task MakeLipaNaMpesaOnlinePaymentAsync(LipaNaMpesaOnline mpesaItem, string accesstoken, string requestUri) { + +#if NET45 + ServicePointManager.SecurityProtocol = + SecurityProtocolType.Tls12 | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls; +#endif _httpclient.DefaultRequestHeaders.Clear(); _httpclient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); _httpclient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accesstoken); @@ -295,6 +316,11 @@ public async Task MakeLipaNaMpesaOnlinePaymentAsync(LipaNaMpesaOnline mp /// public async Task QueryAccountBalanceAsync(AccountBalance accbalance, string accesstoken, string requestUri) { + +#if NET45 + ServicePointManager.SecurityProtocol = + SecurityProtocolType.Tls12 | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls; +#endif _httpclient.DefaultRequestHeaders.Clear(); _httpclient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); _httpclient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accesstoken); @@ -332,6 +358,11 @@ public async Task QueryAccountBalanceAsync(AccountBalance accbalance, st /// public async Task QueryLipaNaMpesaTransactionAsync(LipaNaMpesaQuery mpesaQuery, string accesstoken, string requestUri) { + +#if NET45 + ServicePointManager.SecurityProtocol = + SecurityProtocolType.Tls12 | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls; +#endif _httpclient.DefaultRequestHeaders.Clear(); _httpclient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); _httpclient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accesstoken); @@ -366,6 +397,11 @@ public async Task QueryLipaNaMpesaTransactionAsync(LipaNaMpesaQuery mpes /// public async Task QueryMpesaTransactionStatusAsync(MpesaTransactionStatus transactionStatus, string accesstoken, string requestUri) { + +#if NET45 + ServicePointManager.SecurityProtocol = + SecurityProtocolType.Tls12 | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls; +#endif _httpclient.DefaultRequestHeaders.Clear(); _httpclient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); _httpclient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accesstoken); @@ -407,6 +443,11 @@ public async Task QueryMpesaTransactionStatusAsync(MpesaTransactionStatu /// public async Task RegisterC2BUrlAsync(CustomerToBusinessRegister c2bregisterItem, string accesstoken, string requestUri) { + +#if NET45 + ServicePointManager.SecurityProtocol = + SecurityProtocolType.Tls12 | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls; +#endif _httpclient.DefaultRequestHeaders.Clear(); _httpclient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); _httpclient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accesstoken); @@ -441,6 +482,11 @@ public async Task RegisterC2BUrlAsync(CustomerToBusinessRegister c2bregi /// public async Task ReverseMpesaTransactionAsync(Reversal reversal, string accesstoken, string requestUri) { + +#if NET45 + ServicePointManager.SecurityProtocol = + SecurityProtocolType.Tls12 | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls; +#endif _httpclient.DefaultRequestHeaders.Clear(); _httpclient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); _httpclient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accesstoken); diff --git a/src/MpesaLib/Helpers/Credentials.cs b/src/MpesaLib/Helpers/Credentials.cs new file mode 100644 index 0000000..a5f15b3 --- /dev/null +++ b/src/MpesaLib/Helpers/Credentials.cs @@ -0,0 +1,89 @@ +using System; +using System.IO; +using System.Security.Cryptography; +using System.Security.Cryptography.X509Certificates; +using System.Text; + +namespace MpesaLib.Helpers +{ + + /* + M-Pesa Core authenticates a transaction by decrypting the security credentials. + Security credentials are generated by encrypting the base64 encoded initiator password with M-Pesa’s public key, + a X509 certificate. + + The algorithm for generating security credentials is as follows: + + Write the unencrypted password into a byte array. + + Encrypt the array with the M-Pesa public key certificate. Use the RSA algorithm, + and use PKCS #1.5 padding (not OAEP), and add the result to the encrypted stream. + + Convert the resulting encrypted byte array into a string using base64 encoding. + The resulting base64 encoded string is the security credential. + + */ + + + /// + /// Encrypt password helper class for MpesaLib + /// + public static class Credentials + { + /// + /// Encrypts Mpesa API Security Credential password + /// + /// + /// + /// + public static string EncryptPassword(string certificatePath, string password) + { + + //get certificate data in bytes + var data = ReadFile(certificatePath); + + //var rsa3 = new RSACryptoServiceProvider(); + + X509Certificate2 x509 = new X509Certificate2(data); + + RSA publicprovider = (RSA)x509.PublicKey.Key; + +#if !NETSTANDARD2_0 + var key1 = publicprovider.ToXmlString(false); +#else + var key1 = publicprovider.ToXmlString2(false); +#endif + + var data2 = Encoding.UTF8.GetBytes(password); + + using (var rsa2 = new RSACryptoServiceProvider()) + { + try + { + rsa2.FromXmlString2(key1); + var encryptedData = rsa2.Encrypt(data2, false); + var base64Encrypted = Convert.ToBase64String(encryptedData); + return base64Encrypted; + } + finally + { + rsa2.PersistKeyInCsp = false; + } + } + } + + //Reads a certificate file file. + private static byte[] ReadFile(string fileName) + { + FileStream f = new FileStream(fileName, FileMode.Open, FileAccess.Read); + int size = (int)f.Length; + byte[] data = new byte[size]; + size = f.Read(data, 0, size); + f.Close(); + return data; + } + + + + } +} diff --git a/src/MpesaLib/Helpers/RSACryptoServiceProviderExtensions.cs b/src/MpesaLib/Helpers/RSACryptoServiceProviderExtensions.cs new file mode 100644 index 0000000..51c36cb --- /dev/null +++ b/src/MpesaLib/Helpers/RSACryptoServiceProviderExtensions.cs @@ -0,0 +1,149 @@ +using System; +using System.Security.Cryptography; +using System.Xml; + +namespace MpesaLib.Helpers +{ + /// + /// RSACryptoServiceProvider Extensions + /// + public static class RSACryptoServiceProviderExtensions + { + /// + /// Imports the specified XML String into the crypto service provider + /// + /// + /// .NET Core 2.0 doesn't provide an implementation of RSACryptoServiceProvider.FromXmlString/ToXmlString, so we have to do it ourselves. + /// Source: https://gist.github.com/Jargon64/5b172c452827e15b21882f1d76a94be4/ + /// + public static void FromXmlString2(this RSACryptoServiceProvider rsa, string xmlString) + { +#if !NETSTANDARD2_0 + rsa.FromXmlString(xmlString); +#else + FromXmlStringImpl(rsa, xmlString); +#endif + } + + internal static void FromXmlStringImpl(RSACryptoServiceProvider rsa, string xmlString) + { + var parameters = new RSAParameters(); + + var xmlDoc = new XmlDocument(); + + xmlDoc.LoadXml(xmlString); + + if (!xmlDoc.DocumentElement.Name.Equals("RSAKeyValue")) + { + throw new InvalidOperationException("Invalid XML RSA key."); + } + + + foreach (XmlNode node in xmlDoc.DocumentElement.ChildNodes) + { + switch (node.Name) + { + case "Modulus": + parameters.Modulus = Convert.FromBase64String(node.InnerText); + break; + case "Exponent": + parameters.Exponent = Convert.FromBase64String(node.InnerText); + break; + case "P": + parameters.P = Convert.FromBase64String(node.InnerText); + break; + case "Q": + parameters.Q = Convert.FromBase64String(node.InnerText); + break; + case "DP": + parameters.DP = Convert.FromBase64String(node.InnerText); + break; + case "DQ": + parameters.DQ = Convert.FromBase64String(node.InnerText); + break; + case "InverseQ": + parameters.InverseQ = Convert.FromBase64String(node.InnerText); + break; + case "D": + parameters.D = Convert.FromBase64String(node.InnerText); + break; + default: + throw new InvalidOperationException("Unknown node name: " + node.Name); + } + } + + rsa.ImportParameters(parameters); + } + + + + /// + /// ToXmlString extention method for .net standard, netcoreapp + /// + /// + /// + /// + public static void ToXmlString3(this RSA rsa, bool includePrivateParameters = false) + { +#if !NETSTANDARD2_0 + rsa.ToXmlString(false); +#else + ToXmlStringImpl(rsa, false); +#endif + } + + + internal static string ToXmlStringImpl(this RSA rsa, bool includePrivateParameters = false) + { + RSAParameters parameters = rsa.ExportParameters(includePrivateParameters); + + if (includePrivateParameters) + { + return string.Format("{0}{1}

{2}

{3}{4}{5}{6}{7}
", + Convert.ToBase64String(parameters.Modulus), + Convert.ToBase64String(parameters.Exponent), + Convert.ToBase64String(parameters.P), + Convert.ToBase64String(parameters.Q), + Convert.ToBase64String(parameters.DP), + Convert.ToBase64String(parameters.DQ), + Convert.ToBase64String(parameters.InverseQ), + Convert.ToBase64String(parameters.D)); + } + + return string.Format("{0}{1}", + Convert.ToBase64String(parameters.Modulus), + Convert.ToBase64String(parameters.Exponent)); + } + + + /// + /// ToXmlString extention method for .net standard, netcoreapp + /// + /// + /// + /// + public static string ToXmlString2(this RSA rsa, bool includePrivateParameters = false) + { + RSAParameters parameters = rsa.ExportParameters(includePrivateParameters); + + if (includePrivateParameters) + { + return string.Format("{0}{1}

{2}

{3}{4}{5}{6}{7}
", + Convert.ToBase64String(parameters.Modulus), + Convert.ToBase64String(parameters.Exponent), + Convert.ToBase64String(parameters.P), + Convert.ToBase64String(parameters.Q), + Convert.ToBase64String(parameters.DP), + Convert.ToBase64String(parameters.DQ), + Convert.ToBase64String(parameters.InverseQ), + Convert.ToBase64String(parameters.D)); + } + + return string.Format("{0}{1}", + Convert.ToBase64String(parameters.Modulus), + Convert.ToBase64String(parameters.Exponent)); + } + + } + +} \ No newline at end of file diff --git a/src/MpesaLib/MpesaLib.csproj b/src/MpesaLib/MpesaLib.csproj index 21ddc19..47a0382 100644 --- a/src/MpesaLib/MpesaLib.csproj +++ b/src/MpesaLib/MpesaLib.csproj @@ -3,7 +3,7 @@ netstandard2.0;net45;net40; - 2.0.5 + 2.1.1 false @@ -15,7 +15,7 @@ Copyright © Geospartan Tech. All rights reserved. https://github.com/ayiemba/MpesaLib/blob/master/LICENSE https://ayiemba.github.io/MpesaLib - MInor Enhancements + Added Security Credential Password Encryption for B2B, B2C, Reversal, Transaction, Account Balance APIs true @@ -25,7 +25,7 @@ - + @@ -37,6 +37,8 @@ + + NETCORE;NETSTANDARD;NETSTANDARD2_0 diff --git a/src/MpesaLib/MpesaLib.xml b/src/MpesaLib/MpesaLib.xml index 3f3de8b..e49cb7d 100644 --- a/src/MpesaLib/MpesaLib.xml +++ b/src/MpesaLib/MpesaLib.xml @@ -347,6 +347,49 @@ Returns a JSON string that you should deserialize + + + Encrypt password helper class for MpesaLib + + + + + Encrypts Mpesa API Security Credential password + + + + + + + + RSACryptoServiceProvider Extensions + + + + + Imports the specified XML String into the crypto service provider + + + .NET Core 2.0 doesn't provide an implementation of RSACryptoServiceProvider.FromXmlString/ToXmlString, so we have to do it ourselves. + Source: https://gist.github.com/Jargon64/5b172c452827e15b21882f1d76a94be4/ + + + + + ToXmlString extention method for .net standard, netcoreapp + + + + + + + + ToXmlString extention method for .net standard, netcoreapp + + + + +