diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..9f27cdb --- /dev/null +++ b/.editorconfig @@ -0,0 +1,10 @@ +[*.cs] + +# IDE0090: Use 'new(...)' +csharp_style_implicit_object_creation_when_type_is_apparent = true + +# IDE0090: Use 'new(...)' +dotnet_diagnostic.IDE0090.severity = none + +# IDE0054: Use compound assignment +dotnet_diagnostic.IDE0054.severity = none diff --git a/.gitignore b/.gitignore index 29e7dc5..8997407 100644 --- a/.gitignore +++ b/.gitignore @@ -52,3 +52,5 @@ NUnit3TestAdapter-3.8.0.vsix # dotCover *.dotCover + +TestHarness/ \ No newline at end of file diff --git a/.vs/ApiSecuritySolution/xs/UserPrefs.xml b/.vs/ApiSecuritySolution/xs/UserPrefs.xml deleted file mode 100644 index a0fb447..0000000 --- a/.vs/ApiSecuritySolution/xs/UserPrefs.xml +++ /dev/null @@ -1,32 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/.vs/ApiSecuritySolution/xs/sqlite3/db.lock b/.vs/ApiSecuritySolution/xs/sqlite3/db.lock deleted file mode 100644 index e69de29..0000000 diff --git a/.vs/ApiSecuritySolution/xs/sqlite3/storage.ide b/.vs/ApiSecuritySolution/xs/sqlite3/storage.ide deleted file mode 100644 index d15f853..0000000 Binary files a/.vs/ApiSecuritySolution/xs/sqlite3/storage.ide and /dev/null differ diff --git a/.vs/ApiSecuritySolution/xs/sqlite3/storage.ide-shm b/.vs/ApiSecuritySolution/xs/sqlite3/storage.ide-shm deleted file mode 100644 index e7a9a4e..0000000 Binary files a/.vs/ApiSecuritySolution/xs/sqlite3/storage.ide-shm and /dev/null differ diff --git a/.vs/ApiSecuritySolution/xs/sqlite3/storage.ide-wal b/.vs/ApiSecuritySolution/xs/sqlite3/storage.ide-wal deleted file mode 100644 index ce6292b..0000000 Binary files a/.vs/ApiSecuritySolution/xs/sqlite3/storage.ide-wal and /dev/null differ diff --git a/ApiSecuritySolution.sln b/ApiSecuritySolution.sln index d917907..1b3b41c 100644 --- a/ApiSecuritySolution.sln +++ b/ApiSecuritySolution.sln @@ -10,6 +10,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution LICENSE = LICENSE README.md = README.md CHANGELOG.md = CHANGELOG.md + .editorconfig = .editorconfig EndProjectSection EndProject Global @@ -29,7 +30,7 @@ Global EndGlobalSection GlobalSection(MonoDevelopProperties) = preSolution description = Internal Update - version = 0.1.2 + version = 2.0.1 Policies = $0 $0.DotNetNamingPolicy = $1 $1.DirectoryNamespaceAssociation = PrefixedHierarchical diff --git a/ApiUtilLib/ApiAuthorization.cs b/ApiUtilLib/ApiAuthorization.cs index 7fe3519..27eae54 100644 --- a/ApiUtilLib/ApiAuthorization.cs +++ b/ApiUtilLib/ApiAuthorization.cs @@ -4,16 +4,10 @@ using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; using System.Text; -using ApexUtilLib; using System.Collections.Generic; -using Newtonsoft.Json.Converters; -using Newtonsoft.Json; using System.Linq; -using Org.BouncyCastle; using Org.BouncyCastle.Crypto; using Org.BouncyCastle.OpenSsl; -using Org.BouncyCastle.Crypto.Encodings; -using Org.BouncyCastle.Crypto.Engines; using Org.BouncyCastle.Crypto.Parameters; using Org.BouncyCastle.Security; @@ -24,26 +18,20 @@ public static class ApiAuthorization // PKI - https://www.lyquidity.com/devblog/?p=70 // hmac - https://www.jokecamp.com/blog/examples-of-creating-base64-hashes-using-hmac-sha256-in-different-languages/#csharp - public static LoggerBase Logger - { - get - { - return LoggerManager.Logger; - } - } + public static LoggerBase Logger => LoggerManager.Logger; public static string L1Signature(this string message, string secret) { Logger.LogEnter(LoggerBase.Args(message, "***secret***")); - if (String.IsNullOrEmpty(message)) + if (string.IsNullOrEmpty(message)) { Logger.LogError("{0} must not be null or empty.", nameof(message)); throw new ArgumentNullException(nameof(message)); } - if (String.IsNullOrEmpty(secret)) + if (string.IsNullOrEmpty(secret)) { Logger.LogError("{0} must not be null or empty.", nameof(secret)); @@ -54,7 +42,7 @@ public static string L1Signature(this string message, string secret) byte[] secretBytes = Encoding.UTF8.GetBytes(secret); string base64Token = null; - using (var hmacsha256 = new HMACSHA256(secretBytes)) + using (HMACSHA256 hmacsha256 = new HMACSHA256(secretBytes)) { byte[] messageHash = hmacsha256.ComputeHash(messageBytes); @@ -71,7 +59,7 @@ public static bool VerifyL1Signature(this string signature, string secret, strin { Logger.LogEnter(LoggerBase.Args(signature, message, "***secret***")); - bool signatureValid = ApiAuthorization.L1Signature(message, secret) == signature; + bool signatureValid = L1Signature(message, secret) == signature; Logger.LogExit(LoggerBase.Args(signatureValid)); return signatureValid; @@ -81,7 +69,7 @@ public static string L2Signature(this string message, RSACryptoServiceProvider p { Logger.LogEnter(LoggerBase.Args(message, privateKey)); - if (String.IsNullOrEmpty(message)) + if (string.IsNullOrEmpty(message)) { Logger.LogError("{0} must not be null or empty.", nameof(message)); @@ -109,255 +97,209 @@ public static string L2Signature(this string message, RSACryptoServiceProvider p return base64Token; } - public static RSACryptoServiceProvider PrivateKeyFromP12(string certificateFileName, string password) + public static bool VerifyL2Signature(this string signature, RSACryptoServiceProvider publicKey, string message) { - Logger.LogEnterExit(LoggerBase.Args(certificateFileName, "***password***")); - - - var privateCert = new X509Certificate2(System.IO.File.ReadAllBytes(certificateFileName), password, X509KeyStorageFlags.Exportable); + Logger.LogEnter(LoggerBase.Args(signature, message, publicKey)); - var OriginalPrivateKey = (RSACryptoServiceProvider)privateCert.PrivateKey; + byte[] messageBytes = Encoding.UTF8.GetBytes(message); + byte[] signatureBytes = Convert.FromBase64String(signature); - if (Environment.OSVersion.Platform == PlatformID.MacOSX || Environment.OSVersion.Platform == PlatformID.Unix) - { - return OriginalPrivateKey; - } - else + bool signatureValid = false; + using (SHA256Managed sha256 = new SHA256Managed()) { - var privateKey = new RSACryptoServiceProvider(); - privateKey.ImportParameters(OriginalPrivateKey.ExportParameters(true)); + byte[] messageHash = sha256.ComputeHash(messageBytes); - return privateKey; + signatureValid = publicKey.VerifyHash(messageHash, CryptoConfig.MapNameToOID("SHA256"), signatureBytes); } + + Logger.LogExit(LoggerBase.Args(signatureValid)); + return signatureValid; } - public static string GetL2SignatureFromPEM(string filename, string message, string passPhrase) + public static RSACryptoServiceProvider GetPrivateKey(string keyFileName, string passPhrase = null) { - Logger.LogEnterExit(LoggerBase.Args(filename, "***password***")); - if (String.IsNullOrEmpty(message)) - { - Logger.LogError("{0} must not be null or empty.", nameof(message)); - - throw new ArgumentNullException(nameof(message)); - } + Logger.LogEnterExit(LoggerBase.Args(keyFileName, "***password***")); - if (String.IsNullOrEmpty(filename)) - { - Logger.LogError("{0} must not be null or empty.", nameof(filename)); - - throw new ArgumentNullException(nameof(filename)); - } - string result = null; - try - { - using (FileStream fs = File.OpenRead(filename)) - { - AsymmetricCipherKeyPair keyPair; - var obj = GetRSAProviderFromPem(File.ReadAllText(filename).Trim(), passPhrase); - byte[] bytes = Encoding.UTF8.GetBytes(message); + RSACryptoServiceProvider privateKey = null; - using (var reader = File.OpenText(filename)) - keyPair = (AsymmetricCipherKeyPair)new PemReader(reader, new PasswordFinder(passPhrase)).ReadObject(); - var decryptEngine = new Pkcs1Encoding(new RsaEngine()); + string fileExtension = Path.GetExtension(keyFileName).ToLower(); - decryptEngine.Init(false, keyPair.Private); - var str = obj.SignData(bytes, CryptoConfig.MapNameToOID("SHA256")); - - result = System.Convert.ToBase64String(str); - } - } - catch (Exception ex) + //if (fileType == PrivateKeyFileType.P12_OR_PFX) + switch (fileExtension) { - throw ex; + case ".p12": + case ".pfx": + { + X509Certificate2 privateCert = new X509Certificate2(File.ReadAllBytes(keyFileName), passPhrase, X509KeyStorageFlags.Exportable); + RSACryptoServiceProvider OriginalPrivateKey = (RSACryptoServiceProvider)privateCert.PrivateKey; + + if (Environment.OSVersion.Platform == PlatformID.MacOSX || Environment.OSVersion.Platform == PlatformID.Unix) + { + privateKey = OriginalPrivateKey; + } + else + { + privateKey = new RSACryptoServiceProvider(); + privateKey.ImportParameters(OriginalPrivateKey.ExportParameters(true)); + } + + break; + } + //if (fileType == PrivateKeyFileType.PEM_PKCS1 || fileType == PrivateKeyFileType.PEM_PKCS8) + case ".pem": + case ".key": + { + RSAParameters rsaParams; + + bool isPkcs1 = false; + string pemString = File.ReadAllText(keyFileName); + if (pemString.StartsWith("-----BEGIN RSA PRIVATE KEY-----")) + { + isPkcs1 = true; + } + + using (StreamReader reader = File.OpenText(keyFileName)) // file containing RSA PKCS8 private key + { + if (isPkcs1) + { + AsymmetricCipherKeyPair keyPair_pkcs1; + + keyPair_pkcs1 = (AsymmetricCipherKeyPair)new PemReader(reader, new PasswordFinder(passPhrase)).ReadObject(); + rsaParams = DotNetUtilities.ToRSAParameters((RsaPrivateCrtKeyParameters)keyPair_pkcs1.Private); + } + else + { + RsaPrivateCrtKeyParameters keyPair_pkcs8; + + keyPair_pkcs8 = (RsaPrivateCrtKeyParameters)new PemReader(reader, new PasswordFinder(passPhrase)).ReadObject(); + rsaParams = DotNetUtilities.ToRSAParameters(keyPair_pkcs8); + } + } + + privateKey = new RSACryptoServiceProvider(); + privateKey.ImportParameters(rsaParams); + + break; + } + default: + throw new CryptographicException("No supported key formats were found. Check that the file format are supported."); } - return result; + + return privateKey; } - public static RSACryptoServiceProvider ImportPrivateKey(string pem) + public static RSACryptoServiceProvider GetPublicKey(string keyFileName, string passPhrase = null) { - PemReader pr = new PemReader(new StringReader(pem)); - AsymmetricCipherKeyPair KeyPair = (AsymmetricCipherKeyPair)pr.ReadObject(); - RSAParameters rsaParams = DotNetUtilities.ToRSAParameters((RsaPrivateCrtKeyParameters)KeyPair.Private); + Logger.LogEnterExit(LoggerBase.Args(keyFileName)); - RSACryptoServiceProvider csp = new RSACryptoServiceProvider(); - csp.ImportParameters(rsaParams); - return csp; - } + RSACryptoServiceProvider key = null; + string fileExtension = Path.GetExtension(keyFileName).ToLower(); - public static X509Certificate2 LoadCertificateFile(string filename, string passPhrase) - { - X509Certificate2 x509 = null; - try + // check if the file contain certificate? + // by default .pem file conatain key + if (fileExtension == ".pem") { - using (FileStream fs = File.OpenRead(filename)) + string pemString = File.ReadAllText(keyFileName); + if (pemString.StartsWith("-----BEGIN CERTIFICATE-----")) { - AsymmetricCipherKeyPair keyPair; - var obj = GetRSAProviderFromPem(File.ReadAllText(filename).Trim(), passPhrase); - byte[] bytes = Encoding.UTF8.GetBytes("message"); - - using (var reader = File.OpenText(filename)) - keyPair = (AsymmetricCipherKeyPair)new PemReader(reader, new PasswordFinder(passPhrase)).ReadObject(); - var decryptEngine = new Pkcs1Encoding(new RsaEngine()); - - decryptEngine.Init(false, keyPair.Private); - var str = obj.SignData(bytes, CryptoConfig.MapNameToOID("SHA256")); - - var base64 = System.Convert.ToBase64String(str); - - var privateCert = new X509Certificate2(base64, passPhrase, X509KeyStorageFlags.Exportable); + // assume .pem as .cer + fileExtension = ".cer"; } } - catch (Exception ex) - { - throw ex; - } - return x509; - } - - - public static RSACryptoServiceProvider GetRSAProviderFromPem(String pemstr, string password) - { - CspParameters cspParameters = new CspParameters(); - cspParameters.KeyContainerName = "MyKeyContainer"; - RSACryptoServiceProvider rsaKey = new RSACryptoServiceProvider(cspParameters); - - Func MakePublicRCSP = (RSACryptoServiceProvider rcsp, RsaKeyParameters rkp) => - { - RSAParameters rsaParameters = DotNetUtilities.ToRSAParameters(rkp); - rcsp.ImportParameters(rsaParameters); - return rsaKey; - }; - Func MakePrivateRCSP = (RSACryptoServiceProvider rcsp, RsaPrivateCrtKeyParameters rkp) => - { - RSAParameters rsaParameters = DotNetUtilities.ToRSAParameters(rkp); - rcsp.ImportParameters(rsaParameters); - return rsaKey; - }; - IPasswordFinder pwd; - PemReader reader; - reader = new PemReader(new StringReader(pemstr), new PasswordFinder(password)); - object kp = reader.ReadObject(); - - if (kp.GetType().GetProperty("Private") != null) - { - return MakePrivateRCSP(rsaKey, (RsaPrivateCrtKeyParameters)(((AsymmetricCipherKeyPair)kp).Private)); - } - else + switch (fileExtension) { - return MakePublicRCSP(rsaKey, (RsaKeyParameters)kp); - } - - - } - - - - public static byte[] PEM(string type, byte[] data) - { - string pem = Encoding.ASCII.GetString(data); - string header = String.Format("-----BEGIN {0}-----", type); - string footer = String.Format("-----END {0}-----", type); - int start = pem.IndexOf(header) + header.Length; - int end = pem.IndexOf(footer, start); - string base64 = pem.Substring(start, (end - start)); - return Convert.FromBase64String(base64); - } - - public static RSACryptoServiceProvider PublicKeyFromCer(string certificateFileName) - { - Logger.LogEnterExit(LoggerBase.Args(certificateFileName)); - - var privateCert = new X509Certificate2(System.IO.File.ReadAllBytes(certificateFileName)); - - return (RSACryptoServiceProvider)privateCert.PublicKey.Key; - } - - public static bool VerifyL2Signature(this string signature, RSACryptoServiceProvider publicKey, string message) - { - Logger.LogEnter(LoggerBase.Args(signature, message, publicKey)); - - byte[] messageBytes = Encoding.UTF8.GetBytes(message); - byte[] signatureBytes = Convert.FromBase64String(signature); + case ".p12": + case ".pfx": + //if (fileType == PublicKeyFileType.P12_OR_PFX) + { + X509Certificate2 cert = new X509Certificate2(File.ReadAllBytes(keyFileName), passPhrase, X509KeyStorageFlags.Exportable); + key = (RSACryptoServiceProvider)cert.PublicKey.Key; + break; + } - bool signatureValid = false; - using (SHA256Managed sha256 = new SHA256Managed()) - { - byte[] messageHash = sha256.ComputeHash(messageBytes); + //if (fileType == PublicKeyFileType.CERTIFICATE) + case ".cer": + { + X509Certificate2 cert = new X509Certificate2(File.ReadAllBytes(keyFileName)); + key = (RSACryptoServiceProvider)cert.PublicKey.Key; + break; + } - signatureValid = publicKey.VerifyHash(messageHash, CryptoConfig.MapNameToOID("SHA256"), signatureBytes); + //if (fileType == PublicKeyFileType.PUBLIC_KEY) + case ".pem": + case ".key": + { + using (StreamReader reader = File.OpenText(keyFileName)) + { + PemReader pr = new PemReader(reader); + AsymmetricKeyParameter publicKey = (AsymmetricKeyParameter)pr.ReadObject(); + + RSAParameters rsaParams = DotNetUtilities.ToRSAParameters((RsaKeyParameters)publicKey); + key = new RSACryptoServiceProvider(); + key.ImportParameters(rsaParams); + } + break; + } + default: + throw new CryptographicException("No supported key formats were found. Check that the file format are supported."); } - Logger.LogExit(LoggerBase.Args(signatureValid)); - return signatureValid; + return key; } - public static string BaseString( - string authPrefix - , SignatureMethod signatureMethod - , string appId - , Uri siteUri - , HttpMethod httpMethod - , ApiList formList - , string nonce - , string timestamp - , string version) - { + public static string BaseString(string authPrefix, SignatureMethod signatureMethod, string appId, Uri siteUri, FormData formData, HttpMethod httpMethod, string nonce, string timestamp, string version) + { try { - Logger.LogEnter(LoggerBase.Args(authPrefix, signatureMethod, appId, siteUri, httpMethod, formList, nonce, timestamp)); + Logger.LogEnter(LoggerBase.Args(authPrefix, signatureMethod, appId, siteUri, httpMethod, formData, nonce, timestamp)); authPrefix = authPrefix.ToLower(); // make sure that the url are valid if (siteUri.Scheme != "http" && siteUri.Scheme != "https") { - throw new System.NotSupportedException("Support http and https protocol only."); + throw new NotSupportedException("Support http and https protocol only."); } // make sure that the port no and querystring are remove from url - string url; - - if(siteUri.Port==-1 || siteUri.Port==80 || siteUri.Port == 443) - { - url = string.Format("{0}://{1}{2}", siteUri.Scheme, siteUri.Host, siteUri.AbsolutePath); - } - else - { - url = string.Format("{0}://{1}:{2}{3}", siteUri.Scheme, siteUri.Host, siteUri.Port, siteUri.AbsolutePath); - } - + string url = siteUri.Port == (-1) || siteUri.Port == 80 || siteUri.Port == 443 + ? string.Format("{0}://{1}{2}", siteUri.Scheme, siteUri.Host, siteUri.AbsolutePath) + : string.Format("{0}://{1}:{2}{3}", siteUri.Scheme, siteUri.Host, siteUri.Port, siteUri.AbsolutePath); Logger.LogInformation("url:: {0}", url); // helper calss that handle parameters and form fields - ApiList paramList = new ApiList(); + ApiList paramList = new ApiList(); // process QueryString from url by transfering it to paramList if (siteUri.Query.Length > 1) { - var queryString = siteUri.Query.Substring(1); // remove the ? from first character + string queryString = siteUri.Query.Substring(1); // remove the ? from first character Logger.LogInformation("queryString:: {0}", queryString); - var paramArr = queryString.Split('&'); + string[] paramArr = queryString.Split('&'); foreach (string item in paramArr) { string key = null; string val = null; - var itemArr = item.Split('='); + string[] itemArr = item.Split('='); key = itemArr[0]; - if(itemArr.Length>1) + if (itemArr.Length > 1) + { val = itemArr[1]; - paramList.Add(key, System.Net.WebUtility.UrlDecode(val)); + } + + paramList.Add(WebUtility.UrlDecode(key), WebUtility.UrlDecode(val)); } Logger.LogInformation("paramList:: {0}", paramList); } // add the form fields to paramList - if (formList != null && formList.Count > 0) + if (formData != null && formData.Count > 0) { - paramList.AddRange(formList); + paramList.AddRange(formData.GetBaseStringList()); } paramList.Add(authPrefix + "_timestamp", timestamp); @@ -373,7 +315,7 @@ string authPrefix Logger.LogExit(LoggerBase.Args(baseString)); return baseString; } - catch (Exception ex) + catch (Exception ex) { throw ex; } @@ -394,98 +336,25 @@ public static string NewNonce() using (RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider()) { // Buffer storage. - data = new byte[8]; + data = new byte[32]; // Fill buffer. rng.GetBytes(data); } Logger.LogEnterExit(LoggerBase.Args(nonce.ToString())); - - return System.Convert.ToBase64String(data); + return Convert.ToBase64String(data); } - public static String Token( - string realm - , string authPrefix - , HttpMethod httpMethod - , Uri urlPath - , string appId - , string secret = null - , ApiList formList = null - , RSACryptoServiceProvider privateKey = null - , string nonce = null - , string timestamp = null - , string version = "1.0") + public static int HttpRequest(Uri url, string token, FormData formData, HttpMethod httpMethod = HttpMethod.GET, bool ignoreServerCert = false) { - String nullValueErrMsg = "One or more required parameters are missing!"; - try - { - Logger.LogEnter(LoggerBase.Args(realm, authPrefix, httpMethod, urlPath, appId, secret, formList == null ? null : formList.ToFormData(), privateKey, nonce, timestamp, version)); - - Logger.LogDebug("URL:: {0}", urlPath); - if (String.IsNullOrEmpty(authPrefix)) - { - Logger.LogError(nullValueErrMsg); - throw new ArgumentNullException(nameof(authPrefix)); - } - - - authPrefix = authPrefix.ToLower(); - - // Generate the nonce value - nonce = nonce ?? ApiAuthorization.NewNonce().ToString(); - timestamp = timestamp ?? ApiAuthorization.NewTimestamp().ToString(); - - SignatureMethod signatureMethod = SignatureMethod.HMACSHA256; - if (secret == null) - { - signatureMethod = SignatureMethod.SHA256withRSA; - } - - String baseString = BaseString(authPrefix, signatureMethod - , appId, urlPath, httpMethod - , formList, nonce, timestamp, version); - - String base64Token = ""; - if (secret != null) - { - base64Token = baseString.L1Signature(secret); - } - else - { - base64Token = baseString.L2Signature(privateKey); - } - - var tokenList = new ApiList(); - - tokenList.Add("realm", realm); - tokenList.Add(authPrefix + "_app_id", appId); - tokenList.Add(authPrefix + "_nonce", nonce); - tokenList.Add(authPrefix + "_signature_method", signatureMethod.ToString()); - tokenList.Add(authPrefix + "_timestamp", timestamp); - tokenList.Add(authPrefix + "_version", version); - tokenList.Add(authPrefix + "_signature", base64Token); + Logger.LogEnter(LoggerBase.Args(url, token, formData == null ? "null" : formData.ToString(), httpMethod)); - string authorizationToken = string.Format("{0} {1}", authPrefix.Substring(0, 1).ToUpperInvariant() + authPrefix.Substring(1), tokenList.ToString(", ", false, true)); - - Logger.LogDebug("Token :: {0}", authorizationToken); + int returnCode = 0; - Logger.LogExit(LoggerBase.Args(authorizationToken)); - return authorizationToken; - } - catch (Exception ex) + if (ignoreServerCert) { - throw ex; + InitiateSSLTrust(); } - } - - public static int HttpRequest(Uri url, string token = null, ApiList postData = null, HttpMethod httpMethod = HttpMethod.GET, bool ignoreServerCert = false) - { - Logger.LogEnter(LoggerBase.Args(url, token, postData == null ? "null" : postData.ToFormData(), httpMethod)); - - int returnCode = 0; - - if (ignoreServerCert) InitiateSSLTrust(); WebResponse response = null; StreamReader reader = null; @@ -498,16 +367,19 @@ public static int HttpRequest(Uri url, string token = null, ApiList postData = n request.Method = httpMethod.ToString(); - if (!string.IsNullOrEmpty(token)) request.Headers.Add("Authorization", token); + if (!string.IsNullOrEmpty(token)) + { + request.Headers.Add("Authorization", token); + } - if (postData != null) + if (formData != null) { // Set the Method property of the request to POST. request.Method = HttpMethod.POST.ToString(); // Create POST data and convert it to a byte array. //string postData = "This is a test that posts this string to a Web server."; - byte[] byteArray = Encoding.UTF8.GetBytes(postData.ToFormData()); + byte[] byteArray = Encoding.UTF8.GetBytes(formData.ToString()); // Set the ContentType property of the WebRequest. request.ContentType = "application/x-www-form-urlencoded"; @@ -529,9 +401,9 @@ public static int HttpRequest(Uri url, string token = null, ApiList postData = n response = request.GetResponse(); // Display the status. - Logger.LogDebug("Response Code:: {0} - {1}", ((HttpWebResponse)response).StatusDescription, (int)(((HttpWebResponse)response).StatusCode)); + Logger.LogDebug("Response Code:: {0} - {1}", ((HttpWebResponse)response).StatusDescription, (int)((HttpWebResponse)response).StatusCode); - returnCode = (int)(((HttpWebResponse)response).StatusCode); + returnCode = (int)((HttpWebResponse)response).StatusCode; // Get the stream containing content returned by the server. dataStream = response.GetResponseStream(); @@ -551,9 +423,9 @@ public static int HttpRequest(Uri url, string token = null, ApiList postData = n { response = (HttpWebResponse)e.Response; - returnCode = (int)(((HttpWebResponse)response).StatusCode); + returnCode = (int)((HttpWebResponse)response).StatusCode; - Logger.LogWarning("Error Code: {0} - {1}", returnCode, (((HttpWebResponse)response).StatusDescription)); + Logger.LogWarning("Error Code: {0} - {1}", returnCode, ((HttpWebResponse)response).StatusDescription); // Get the stream containing content returned by the server. dataStream = response.GetResponseStream(); @@ -576,20 +448,34 @@ public static int HttpRequest(Uri url, string token = null, ApiList postData = n finally { // Clean up the streams and the response. - if (response != null) response.Close(); - if (reader != null) reader.Close(); - if (dataStream != null) dataStream.Close(); + if (response != null) + { + response.Close(); + } + + if (reader != null) + { + reader.Close(); + } + + if (dataStream != null) + { + dataStream.Close(); + } } Logger.LogExit(LoggerBase.Args(returnCode)); return returnCode; } - static bool iniSslTrust; + private static bool iniSslTrust; - public static void InitiateSSLTrust() + private static void InitiateSSLTrust() { - if (iniSslTrust) return; + if (iniSslTrust) + { + return; + } try { @@ -610,7 +496,7 @@ public static void InitiateSSLTrust() private class PasswordFinder : IPasswordFinder { - private string password; + private readonly string password; public PasswordFinder(string password) { @@ -624,7 +510,130 @@ public char[] GetPassword() } } - } + private const string APEX1_DOMAIN = "api.gov.sg"; + + public static AuthToken TokenV2(AuthParam authParam) { + Logger.LogEnter(LoggerBase.Args(authParam)); + + string nullValueErrMsg = "One or more required parameters are missing!"; + + if (authParam.url == null) + { + Logger.LogError(nullValueErrMsg); + throw new ArgumentNullException(nameof(authParam.url)); + } + // split url into hostname and domain + string gatewayName = authParam.url.Host.Split('.')[0]; + string originalDomain = string.Join(".", authParam.url.Host.Split('.').Skip(1).ToArray()); -} + // default api domain to external zone + string apiDomain = string.Format(".{0}", originalDomain); + string authSuffix = "eg"; + + // for backward compatible with apex1 + if (authParam.url.Host.EndsWith(APEX1_DOMAIN)) + { + apiDomain = string.Format(".e.{0}", originalDomain); + } + + // switch to internal zone based on hostname suffix + if (gatewayName.EndsWith("-pvt")) + { + authSuffix = "ig"; + + // for backward compatible with apex1 + if (authParam.url.Host.EndsWith(APEX1_DOMAIN)) + { + apiDomain = string.Format(".i.{0}", originalDomain); + } + } + + // default auth level to l2, switch to l1 if appSecret provided + string authLevel = "l2"; + if (authParam.appSecret != null) + { + authLevel = "l1"; + } + + // for backward compatible with apex1, update the signature url + string signatureUrl = authParam.url.ToString().Replace(string.Format(".{0}", originalDomain), apiDomain); + + // Generate the nonce value + authParam.nonce = authParam.nonce ?? NewNonce().ToString(); + authParam.timestamp = authParam.timestamp ?? NewTimestamp().ToString(); + + authParam.signatureMethod = SignatureMethod.HMACSHA256; + if (authParam.appSecret == null) + { + authParam.signatureMethod = SignatureMethod.SHA256withRSA; + } + + string apexToken = ""; + List baseStringList = new List(); + + if (authParam.appName != null) + { + string realm = string.Format("https://{0}", authParam.url.Host); + string authPrefix = string.Format("apex_{0}_{1}", authLevel, authSuffix); + if (authParam.version == null) + { + authParam.version = "1.0"; + } + + string baseString = BaseString( + authPrefix, + authParam.signatureMethod, + authParam.appName, + new Uri(signatureUrl), + authParam.formData, + authParam.httpMethod, + authParam.nonce, + authParam.timestamp, + authParam.version + ); + baseStringList.Add(baseString); + + string base64Token = authLevel == "l1" ? baseString.L1Signature(authParam.appSecret) : baseString.L2Signature(authParam.privateKey); + ApiList tokenList = new ApiList + { + { "realm", realm }, + { authPrefix + "_app_id", authParam.appName }, + { authPrefix + "_nonce", authParam.nonce }, + { authPrefix + "_signature_method", authParam.signatureMethod.ToString() }, + { authPrefix + "_timestamp", authParam.timestamp }, + { authPrefix + "_version", authParam.version }, + { authPrefix + "_signature", base64Token } + }; + + apexToken = string.Format("{0} {1}", authPrefix.Substring(0, 1).ToUpperInvariant() + authPrefix.Substring(1), tokenList.ToString(", ", false, true)); + } + string authToken = string.Format("{0}", apexToken); + + if (authParam.nextHop != null) { + // propagate the information from root param to nextHop + authParam.nextHop.httpMethod = authParam.httpMethod; + //authParam.nextHop.queryString = authParam.queryString; + authParam.nextHop.formData = authParam.formData; + + // get the apexToken for nextHop + AuthToken nextHopResult = TokenV2(authParam.nextHop); + + // save the baseString + baseStringList.Add(nextHopResult.BaseString); + + string netxHopToken = nextHopResult.Token; + + // combine the apexToken if required + if (authToken == "") { + authToken = string.Format("{0}", netxHopToken); + } else { + authToken += string.Format(", {0}", netxHopToken); + } + } + + Logger.LogExit(LoggerBase.Args(authToken, baseStringList)); + return new AuthToken(authToken, baseStringList); + } + } +} \ No newline at end of file diff --git a/ApiUtilLib/ApiList.cs b/ApiUtilLib/ApiList.cs index 9b5e86d..6f2f585 100644 --- a/ApiUtilLib/ApiList.cs +++ b/ApiUtilLib/ApiList.cs @@ -1,38 +1,42 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Collections; -using ApexUtilLib; namespace ApiUtilLib { - public class ApiList : List> + internal class ApiList : List> { public void Add(string key, string value) { - KeyValuePair item = new KeyValuePair(key, value); + KeyValuePair item = new KeyValuePair(key, value); - this.Add(item); + Add(item); } // for BaseString public string ToString(string delimiter = "&", bool sort = true, bool quote = false) { - var list = new List(); + List list = new List(); string format = "{0}={1}"; - if (quote) format = "{0}=\"{1}\""; + if (quote) + { + format = "{0}=\"{1}\""; + } if (sort) { // sort by key, than by value - var sortedList = this.OrderBy(k => k.Key,StringComparer.Ordinal).ThenBy(v => v.Value,StringComparer.Ordinal); //Fixed issue to sort by capital letter. + IOrderedEnumerable> sortedList = this.OrderBy(k => k.Key,StringComparer.Ordinal).ThenBy(v => v.Value,StringComparer.Ordinal); //Fixed issue to sort by capital letter. - foreach (var item in sortedList) + foreach (KeyValuePair item in sortedList) { format = "{0}={1}"; - if (quote) format = "{0}=\"{1}\""; + if (quote) + { + format = "{0}=\"{1}\""; + } if (item.Value.IsNullOrEmpty() && !quote) { @@ -46,34 +50,13 @@ public string ToString(string delimiter = "&", bool sort = true, bool quote = fa } else { - foreach (var item in this) + foreach (KeyValuePair item in this) { list.Add(string.Format(format, item.Key, item.Value)); } } - return String.Join(delimiter, list.ToArray()); - } - - public string ToFormData() - { - string delimiter = "&"; - - var list = new List(); - - string format = "{0}={1}"; - - foreach (var item in this) - { - list.Add(string.Format(format, System.Net.WebUtility.UrlEncode(item.Key), System.Net.WebUtility.UrlEncode(item.Value))); - } - - return String.Join(delimiter, list.ToArray()); - } - - public string ToQueryString() - { - return ToFormData(); + return string.Join(delimiter, list.ToArray()); } } } diff --git a/ApiUtilLib/ApiUtilLib.csproj b/ApiUtilLib/ApiUtilLib.csproj index c821bfc..17abe13 100644 --- a/ApiUtilLib/ApiUtilLib.csproj +++ b/ApiUtilLib/ApiUtilLib.csproj @@ -8,7 +8,7 @@ ApexUtilLib ApexUtilLib v4.6.1 - 0.1.2 + 2.0.1 true @@ -30,10 +30,10 @@ - ..\packages\Newtonsoft.Json.11.0.2\lib\net45\Newtonsoft.Json.dll + ..\packages\Newtonsoft.Json.13.0.1\lib\net45\Newtonsoft.Json.dll - ..\packages\BouncyCastle.1.8.3\lib\BouncyCastle.Crypto.dll + ..\packages\BouncyCastle.1.8.9\lib\BouncyCastle.Crypto.dll @@ -49,6 +49,10 @@ + + + + diff --git a/ApiUtilLib/AuthParam.cs b/ApiUtilLib/AuthParam.cs new file mode 100644 index 0000000..2011356 --- /dev/null +++ b/ApiUtilLib/AuthParam.cs @@ -0,0 +1,25 @@ +using System; +using System.Security.Cryptography; + +namespace ApiUtilLib +{ + public class AuthParam + { + public Uri url; + public HttpMethod httpMethod; + + public string appName; + public string appSecret; + public RSACryptoServiceProvider privateKey = null; + + public FormData formData; + + public string nonce; + public string timestamp; + + internal SignatureMethod signatureMethod; + public string version = "1.0"; + + public AuthParam nextHop; + } +} diff --git a/ApiUtilLib/AuthToken.cs b/ApiUtilLib/AuthToken.cs new file mode 100644 index 0000000..f4e9d85 --- /dev/null +++ b/ApiUtilLib/AuthToken.cs @@ -0,0 +1,19 @@ +using System.Collections.Generic; + +namespace ApiUtilLib +{ + public class AuthToken + { + internal AuthToken(string token, List baseStringList) + { + Token = token; + BaseStringList = baseStringList; + } + + public string Token { get; } + + public List BaseStringList { get; } + + public string BaseString => string.Join(", ", BaseStringList.ToArray()); + } +} diff --git a/ApiUtilLib/CommonExtensions.cs b/ApiUtilLib/CommonExtensions.cs index d1af409..342b5eb 100644 --- a/ApiUtilLib/CommonExtensions.cs +++ b/ApiUtilLib/CommonExtensions.cs @@ -1,19 +1,12 @@ -using System; -using System.Text; +using System.Text; -namespace ApexUtilLib +namespace ApiUtilLib { public static class CommonExtensions { public static bool IsNullOrEmpty(this string value) { - if (value == null) - return true; - - if (value == String.Empty) - return true; - - return false; + return value == null || value == string.Empty; } public static string Unescape(this string txt) @@ -23,10 +16,18 @@ public static string Unescape(this string txt) for (int ix = 0; ix < txt.Length;) { int jx = txt.IndexOf('\n', ix); - if (jx < 0 || jx == txt.Length - 1) jx = txt.Length; + if (jx < 0 || jx == txt.Length - 1) + { + jx = txt.Length; + } + retval.Append(txt, ix, jx - ix); - if (jx >= txt.Length) break; - var str = txt[jx + 1]; + if (jx >= txt.Length) + { + break; + } + + //char str = txt[jx + 1]; switch (txt[jx + 1]) { case 'n': retval.Append('\n'); break; // Line feed @@ -43,7 +44,7 @@ public static string Unescape(this string txt) public static string RemoveString(this string value, string[] array) { - foreach (var item in array) + foreach (string item in array) { value = value.Replace(item, ""); } diff --git a/ApiUtilLib/ConsoleLogger.cs b/ApiUtilLib/ConsoleLogger.cs index 1d2ed8b..ebcdb2d 100644 --- a/ApiUtilLib/ConsoleLogger.cs +++ b/ApiUtilLib/ConsoleLogger.cs @@ -4,17 +4,17 @@ namespace ApiUtilLib { public class ConsoleLogger : LoggerBase { - readonly string _messageFormat = "{0:yyyy-MM-dd HH:mm:ss.fff %K} : {1} : {2}"; + private readonly string _messageFormat = "{0:yyyy-MM-dd HH:mm:ss.fff %K} : {1} : {2}"; - public ConsoleLogger() - { - this.LogLevel = LogLevel.None; - } + public ConsoleLogger() + { + LogLevel = LogLevel.None; + } public ConsoleLogger(LogLevel logLevel) - { - this.LogLevel = logLevel; - } + { + LogLevel = logLevel; + } public override void LogMessage(LogLevel messageLogLevel, string message) { diff --git a/ApiUtilLib/FileLogger.cs b/ApiUtilLib/FileLogger.cs index 5078848..849ca94 100644 --- a/ApiUtilLib/FileLogger.cs +++ b/ApiUtilLib/FileLogger.cs @@ -5,16 +5,16 @@ namespace ApiUtilLib { public class FileLogger : LoggerBase { - readonly string _messageFormat = "{0:yyyy-MM-dd HH:mm:ss.fff %K} : {1} : {2}"; + private readonly string _messageFormat = "{0:yyyy-MM-dd HH:mm:ss.fff %K} : {1} : {2}"; public FileLogger() { - this.LogLevel = LogLevel.None; + LogLevel = LogLevel.None; } public FileLogger(LogLevel logLevel) { - this.LogLevel = logLevel; + LogLevel = logLevel; } ~FileLogger() @@ -24,31 +24,30 @@ public FileLogger(LogLevel logLevel) public override void LogMessage(LogLevel messageLogLevel, string message) { - //Console.WriteLine(_messageFormat, DateTime.Now, messageLogLevel.ToString(), message); WriteLog(string.Format(_messageFormat, DateTime.Now, messageLogLevel.ToString(), message)); } - static bool _firstTime = true; - static string _logFilePath = "Logs"; + private static bool _firstTime = true; + private static string _logFilePath = "Logs"; private static void SetupLog() { _firstTime = false; + FileInfo logFileInfo; - FileStream fileStream = null; - DirectoryInfo logDirInfo = null; - FileInfo logFileInfo; - - _logFilePath = Path.Combine(_logFilePath, System.DateTime.Today.ToString("yyyy-MM-dd") + "." + "log"); + _logFilePath = Path.Combine(_logFilePath, DateTime.Today.ToString("yyyy-MM-dd") + "." + "log"); logFileInfo = new FileInfo(_logFilePath); - logDirInfo = new DirectoryInfo(logFileInfo.DirectoryName); - if (!logDirInfo.Exists) logDirInfo.Create(); + DirectoryInfo logDirInfo = new DirectoryInfo(logFileInfo.DirectoryName); + if (!logDirInfo.Exists) + { + logDirInfo.Create(); + } - if (!logFileInfo.Exists) + if (!logFileInfo.Exists) { - fileStream = logFileInfo.Create(); - fileStream.Close(); + FileStream fileStream = logFileInfo.Create(); + fileStream.Close(); } WriteLog(string.Format("\n\n{0:yyyy-MM-dd HH:mm:ss.fff %K} : {1}", DateTime.Now, new string('-', 128))); @@ -56,17 +55,18 @@ private static void SetupLog() private static void WriteLog(string strLog) { - if (_firstTime) SetupLog(); - - StreamWriter log; - FileStream fileStream = null; + if (_firstTime) + { + SetupLog(); + } - fileStream = new FileStream(_logFilePath, FileMode.Append); + StreamWriter log; + FileStream fileStream = new FileStream(_logFilePath, FileMode.Append); - log = new StreamWriter(fileStream); + log = new StreamWriter(fileStream); log.WriteLine(strLog); log.Close(); } } -} +} \ No newline at end of file diff --git a/ApiUtilLib/FormList.cs b/ApiUtilLib/FormList.cs new file mode 100644 index 0000000..0acb9fb --- /dev/null +++ b/ApiUtilLib/FormList.cs @@ -0,0 +1,305 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace ApiUtilLib +{ + public abstract class BaseList : List> + { + public void Add(string key, string[] value) + { + FormField newValue = new FormField(value); + Add(key, newValue); + } + + public void Add(string key, string value) + { + FormField newValue = new FormField(value); + Add(key, newValue); + } + + private void Add(string key, FormField value) + { + KeyValuePair formField = this.FirstOrDefault(x => x.Key == key); + + if (formField.Key == key) + { + formField.Value.Add(value.RawValue); + } + else + { + KeyValuePair item = new KeyValuePair(key, value); + + value.Key = key; + + Add(item); + } + } + + public virtual string ToString(bool urlSafeString = true) + { + string delimiter = "&"; + + List list = new List(); + + string format = "{0}={1}"; + + foreach (KeyValuePair item in this) + { + if (urlSafeString) + { + list.Add(string.Format(format, System.Net.WebUtility.UrlEncode(item.Key), item.Value.FormValue)); + } + else + { + list.Add(string.Format(format, item.Key, item.Value.Value)); + } + } + + return string.Join(delimiter, list.ToArray()); + } + + internal static T SetupList(Dictionary data = null) where T : BaseList, new() + { + try + { + T dataList = new T(); + + if (data != null) + { + foreach (KeyValuePair item in data) + { + object key = item.Key ?? ""; + object value = item.Value ?? ""; + + string value_s = value.ToString().Trim(); + + if (!key.ToString().IsNullOrEmpty()) + { + string[] _queryParams = { "" }; + string val = null; + + if (!value_s.IsNullOrEmpty() && !(value_s.StartsWith("{", StringComparison.InvariantCulture) && value_s.EndsWith("}", StringComparison.InvariantCulture))) + { + + val = value_s.RemoveString(new string[] { "\\", "\\ ", " \\", "\"", "\\ ", "\n" }).Unescape(); + + if (val == "True") + { + val = "true"; + } + + if (val == "False") + { + val = "false"; + } + + if (val.StartsWith("[", StringComparison.InvariantCulture) && val.EndsWith("]", StringComparison.InvariantCulture)) + { + + string[] _paramValues = { "" }; + val = val.RemoveString(new string[] { "[", "]", " " }); + _paramValues = val.Split(','); + foreach (string paramvalue in _paramValues) + { + string _paramvalue = paramvalue; + dataList.Add(key.ToString(), _paramvalue.Unescape()); + } + + } + else + { + dataList.Add(key.ToString(), val); + } + } + else + { + dataList.Add(key.ToString(), val); + } + + } + } + } + + return dataList; + } + catch (Exception ex) + { + throw ex; + } + } + } + + public class FormData : BaseList + { + public override string ToString() + { + return base.ToString(); + } + + internal List> GetBaseStringList() + { + List> list = new List>(); + + foreach (KeyValuePair item in this) + { + KeyValuePair newItem = new KeyValuePair(item.Key, item.Value.Value); + + list.Add(newItem); + } + + return list; + } + + + public static FormData SetupList(Dictionary data = null) + { + return SetupList(data); + } + } + + public class QueryData : BaseList + { + public override string ToString() + { + string queryString = base.ToString(); + + if (queryString.Length > 0) + { + queryString = string.Format("?{0}", queryString); + } + + return queryString; + } + + public override string ToString(bool urlSafeString = true) + { + string queryString = base.ToString(urlSafeString); + + if (queryString.Length > 0) + { + queryString = string.Format("?{0}", queryString); + } + + return queryString; + } + + public static QueryData SetupList(Dictionary data = null) + { + return SetupList(data); + } + } + + public class FormField + { + private string _key = null; + + private string _value = null; + private string[] _arrayValue = null; + + internal string Key + { + set => _key = value; + } + + internal void Add(string[] newValue) + { + List tempList = new List(); + + if (_arrayValue != null) + { + foreach (string item in _arrayValue) + { + tempList.Add(item); + } + } + else + { + tempList.Add(_value); + _value = null; + } + tempList.AddRange(newValue); + + // You can convert it back to an array if you would like to + _arrayValue = tempList.ToArray(); + } + + internal FormField(string value) + { + _value = value; + } + + internal FormField(string[] value) + { + _arrayValue = value; + } + + internal string[] RawValue => _value != null ? (new string[] { _value }) : _arrayValue; + + public string FormValue + { + get + { + string delimiter = "&"; + string value = ""; + + if (_arrayValue == null) + { + value = System.Net.WebUtility.UrlEncode(_value); + } + else + { + int index = 0; + + foreach (string item in _arrayValue) + { + if (index == 0) + { + value = System.Net.WebUtility.UrlEncode(item); + } + else + { + value += string.Format("{0}{1}={2}", delimiter, System.Net.WebUtility.UrlEncode(_key), System.Net.WebUtility.UrlEncode(item)); + } + index++; + } + } + + return value; + } + } + + public string Value + { + get + { + string delimiter = "&"; + string value = ""; + + if (_arrayValue == null) + { + value = _value; + } + else + { + int index = 0; + + foreach (string item in _arrayValue) + { + if (index == 0) + { + value = item; + } + else + { + value += string.Format("{0}{1}={2}", delimiter, _key, item); + } + index++; + } + } + + return value; + } + } + } +} \ No newline at end of file diff --git a/ApiUtilLib/HttpMethod.cs b/ApiUtilLib/HttpMethod.cs index 12005bb..b9ddf6b 100644 --- a/ApiUtilLib/HttpMethod.cs +++ b/ApiUtilLib/HttpMethod.cs @@ -1,5 +1,4 @@ -using System; -namespace ApiUtilLib +namespace ApiUtilLib { public enum HttpMethod { diff --git a/ApiUtilLib/ILogger.cs b/ApiUtilLib/ILogger.cs index 287c77b..8345782 100644 --- a/ApiUtilLib/ILogger.cs +++ b/ApiUtilLib/ILogger.cs @@ -1,5 +1,4 @@ -using System; -using System.Runtime.CompilerServices; +using System.Runtime.CompilerServices; namespace ApiUtilLib { diff --git a/ApiUtilLib/KeyFileType.cs b/ApiUtilLib/KeyFileType.cs new file mode 100644 index 0000000..c8f5c68 --- /dev/null +++ b/ApiUtilLib/KeyFileType.cs @@ -0,0 +1,16 @@ +namespace ApiUtilLib +{ + public enum PrivateKeyFileType + { + P12_OR_PFX, + PEM_PKCS1, + PEM_PKCS8 + } + + public enum PublicKeyFileType + { + CERTIFICATE, + PUBLIC_KEY, + P12_OR_PFX + } +} diff --git a/ApiUtilLib/LogLevel.cs b/ApiUtilLib/LogLevel.cs index b664f9c..f35b2de 100644 --- a/ApiUtilLib/LogLevel.cs +++ b/ApiUtilLib/LogLevel.cs @@ -1,5 +1,4 @@ -using System; -namespace ApiUtilLib +namespace ApiUtilLib { // https://docs.microsoft.com/en-us/aspnet/core/fundamentals/logging?tabs=aspnetcore2x // Trace = 0 diff --git a/ApiUtilLib/LoggerBase.cs b/ApiUtilLib/LoggerBase.cs index 39d36bf..9494b0e 100644 --- a/ApiUtilLib/LoggerBase.cs +++ b/ApiUtilLib/LoggerBase.cs @@ -1,52 +1,56 @@ using System; -using System.Collections.Generic; using System.Runtime.CompilerServices; namespace ApiUtilLib { public abstract class LoggerBase : ILogger { - protected LogLevel _logLevel = ApiUtilLib.LogLevel.None; + protected LogLevel _logLevel = LogLevel.None; protected LoggerBase __child; public void SetChainLogger(LoggerBase childLogger) { - if (__child != null) throw new ArgumentException("Chain logger already been set."); + if (__child != null) + { + throw new ArgumentException("Chain logger already been set."); + } __child = childLogger; } public LogLevel LogLevel - { - get { return _logLevel; } - set { _logLevel = value; } - } + { + get => _logLevel; + set => _logLevel = value; + } void ILogger.Log(LogLevel logLevel, string message) { switch (logLevel) { - case ApiUtilLib.LogLevel.None: + case LogLevel.None: break; - case ApiUtilLib.LogLevel.Trace: + case LogLevel.Trace: LogTrace(message); break; - case ApiUtilLib.LogLevel.Debug: + case LogLevel.Debug: LogDebug(message); break; - case ApiUtilLib.LogLevel.Information: + case LogLevel.Information: LogInformation(message); break; - case ApiUtilLib.LogLevel.Warning: + case LogLevel.Warning: LogWarning(message); break; - case ApiUtilLib.LogLevel.Error: + case LogLevel.Error: LogError(message); break; - case ApiUtilLib.LogLevel.Critical: + case LogLevel.Critical: LogCritical(message); break; + default: + break; } } @@ -70,7 +74,7 @@ public void LogExit(object[] args = null, [CallerMemberName] string caller = nul LogMethodCall("{0} Exit :: Return :: {1}", args, caller); } - void LogMethodCall(string format, object[] args = null, string caller = null) + private void LogMethodCall(string format, object[] args = null, string caller = null) { string argsList = ""; string delimiter = ""; @@ -89,9 +93,12 @@ void LogMethodCall(string format, object[] args = null, string caller = null) delimiter = ", "; } - if (string.IsNullOrEmpty(argsList)) argsList = "none"; + if (string.IsNullOrEmpty(argsList)) + { + argsList = "none"; + } - LogTrace(String.Format(format, caller, argsList)); + LogTrace(string.Format(format, caller, argsList)); } public void LogCritical(string message) @@ -154,15 +161,18 @@ public void LogWarning(string format, params object[] args) LogWarning(string.Format(format, args)); } - void InternalLogMessage(LogLevel messageLogLevel, string message) + private void InternalLogMessage(LogLevel messageLogLevel, string message) { if (_logLevel <= messageLogLevel) { - this.LogMessage(messageLogLevel, message); + LogMessage(messageLogLevel, message); } - if (__child != null) __child.InternalLogMessage(messageLogLevel, message); - } + if (__child != null) + { + __child.InternalLogMessage(messageLogLevel, message); + } + } public abstract void LogMessage(LogLevel messageLogLevel, string message); } diff --git a/ApiUtilLib/LoggerManager.cs b/ApiUtilLib/LoggerManager.cs index 6b66372..9e44a94 100644 --- a/ApiUtilLib/LoggerManager.cs +++ b/ApiUtilLib/LoggerManager.cs @@ -1,25 +1,21 @@ -using System; -namespace ApiUtilLib +namespace ApiUtilLib { public static class LoggerManager { - static LoggerBase _logger; + private static LoggerBase _logger; public static LoggerBase Logger - { - get - { - if (_logger == null) - { - _logger = new ConsoleLogger(); - } - return _logger; - } + { + get + { + if (_logger == null) + { + _logger = new ConsoleLogger(); + } + return _logger; + } - set - { - _logger = value; - } - } - } + set => _logger = value; + } + } } diff --git a/ApiUtilLib/Properties/AssemblyInfo.cs b/ApiUtilLib/Properties/AssemblyInfo.cs index e45365e..eafa919 100644 --- a/ApiUtilLib/Properties/AssemblyInfo.cs +++ b/ApiUtilLib/Properties/AssemblyInfo.cs @@ -1,5 +1,4 @@ using System.Reflection; -using System.Runtime.CompilerServices; // Information about this assembly is defined by the following attributes. // Change them to the values specific to your project. @@ -17,7 +16,7 @@ // The form "{Major}.{Minor}.*" will automatically update the build and revision, // and "{Major}.{Minor}.{Build}.*" will update just the revision. -[assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("2.0.*")] // The following attributes are used to specify the signing key for the assembly, // if desired. See the Mono documentation for more information about signing. diff --git a/ApiUtilLib/SignatureMethod.cs b/ApiUtilLib/SignatureMethod.cs index 03eda3e..28c49f9 100644 --- a/ApiUtilLib/SignatureMethod.cs +++ b/ApiUtilLib/SignatureMethod.cs @@ -1,5 +1,4 @@ -using System; -namespace ApiUtilLib +namespace ApiUtilLib { public enum SignatureMethod { diff --git a/ApiUtilLib/packages.config b/ApiUtilLib/packages.config index 3fe4864..6fca89a 100644 --- a/ApiUtilLib/packages.config +++ b/ApiUtilLib/packages.config @@ -1,5 +1,5 @@  - - + + \ No newline at end of file diff --git a/ApiUtilLibTest/ApiUtilLibTest.csproj b/ApiUtilLibTest/ApiUtilLibTest.csproj index 9453fe4..cff2124 100644 --- a/ApiUtilLibTest/ApiUtilLibTest.csproj +++ b/ApiUtilLibTest/ApiUtilLibTest.csproj @@ -1,5 +1,6 @@ + Debug @@ -9,7 +10,7 @@ ApexUtilLibTest ApexUtilLibTest v4.6.1 - 0.1.2 + 2.0.1 true @@ -32,20 +33,18 @@ - ..\packages\Newtonsoft.Json.11.0.2\lib\net45\Newtonsoft.Json.dll + ..\packages\Newtonsoft.Json.13.0.1\lib\net45\Newtonsoft.Json.dll - ..\packages\NUnit.3.11.0\lib\net45\nunit.framework.dll + ..\packages\NUnit.3.13.2\lib\net45\nunit.framework.dll + + + ..\packages\BouncyCastle.1.8.9\lib\BouncyCastle.Crypto.dll - - - - - @@ -66,6 +65,7 @@ PreserveNewest + diff --git a/ApiUtilLibTest/AuthorizationTokenTest.cs b/ApiUtilLibTest/AuthorizationTokenTest.cs deleted file mode 100644 index 8b35266..0000000 --- a/ApiUtilLibTest/AuthorizationTokenTest.cs +++ /dev/null @@ -1,141 +0,0 @@ -using NUnit.Framework; -using System; - -using ApiUtilLib; -using System.IO; -using System.Reflection; -using System.Security.Cryptography; - -namespace ApiUtilLibTest -{ - [TestFixture] - public class AuthorizationToken - { - - static string GetLocalPath(string relativeFileName) - { - var localPath = Path.Combine(Path.GetDirectoryName(new Uri(Assembly.GetExecutingAssembly().CodeBase).LocalPath), relativeFileName.Replace('/', Path.DirectorySeparatorChar)); - - return localPath; - } - - // file name follow unix convention... - static readonly string privateCertNameP12 = GetLocalPath("Certificates/ssc.alpha.example.com.p12"); - - const string passphrase = "passwordp12"; - - static RSACryptoServiceProvider privateKey = ApiAuthorization.PrivateKeyFromP12(privateCertNameP12, passphrase); - - const string realm = "http://example.api.test/token"; - const string authPrefixL1 = "api_prefix_l1"; - const string authPrefixL2 = "api_prefix_l2"; - - const HttpMethod httpMethod = HttpMethod.GET; - private Uri url = new Uri("https://test.example.com:443/api/v1/rest/level1/in-in/?ap=裕廊坊%20心邻坊"); - - const string appId = "app-id-lpX54CVNltS0ye03v2mQc0b"; - const string secret = "5aes9wG4mQgWJBfKMuYLtrEtNslm1enWG2XpGaMk"; - - const string nonce = "-5816789581922453013"; - const string timestamp = "1502199514462"; - - [Test] - public void Test_L1_Basic_Test() - { - var expectedTokenL1 = "Api_prefix_l1 realm=\"http://example.api.test/token\", api_prefix_l1_app_id=\"app-id-lpX54CVNltS0ye03v2mQc0b\", api_prefix_l1_nonce=\"-5816789581922453013\", api_prefix_l1_signature_method=\"HMACSHA256\", api_prefix_l1_timestamp=\"1502199514462\", api_prefix_l1_version=\"1.0\", api_prefix_l1_signature=\"loz2Hp2wqiK8RxWjkI6Y6Y4OzmOS/QVPevT8Z43TRM4=\""; - - var authorizationToken = ApiAuthorization.Token( - realm - , authPrefixL1 - , httpMethod - , url - , appId - , secret - , timestamp: timestamp - , nonce: nonce - ); - - Assert.AreEqual(expectedTokenL1, authorizationToken); - } - - [Test] - public void Test_L2_Basic_Test() - { - var expectedTokenL2 = "Api_prefix_l2 realm=\"http://example.api.test/token\", api_prefix_l2_app_id=\"app-id-lpX54CVNltS0ye03v2mQc0b\", api_prefix_l2_nonce=\"-5816789581922453013\", api_prefix_l2_signature_method=\"SHA256withRSA\", api_prefix_l2_timestamp=\"1502199514462\", api_prefix_l2_version=\"1.0\", api_prefix_l2_signature=\"EZuFn/n3dxJ4OA9nkdM3yvw76azvyx/HKptQoWzTNWHxMB/2FyurbbpsSb16yNU4bOzRgHlFTZZzbJeZd211M7tLfRC/YQ1Mc2aIxufG7c7H3/3IZ0WdfHIJlF+XwHOR4U5sjRhbCBwSOZzHp6V2a/nmm+CYTjW2LBHxG7aB1wNI6V1PGDp+ePVr8uoyd4MD9nJj5IqLlljtpWCBUJsa7ZZdXgwbStxAdVA3j2lk3FAH9BzaKTQV0msB50Ou/itAw95pqH4RGrWjcuUETUN82JG154SrT/+hqXlmgsgl+6vui7kyCIGnQjhH+3ZSIp/91nJKW8/1hDcNKWQzuoIS9G23rJzPIuStc1f8y/YvXjUSxNTItb4DcSGwqOs1W8+ejLofW/HDBENhhL66ZZaO0EbJmMWJDp+r7w+RtrlRa2QLsuocuAYAsc8FbhW8SBowIHt/BpuIE21SCfXhbbqYmi0WY+YjJxJ79bNsf7OzH57wQln2Ri6jUtRsCez3rP+714aSAJMLKzJPrsUsiefQDuDjl+g7Fs+Ge5eCv3EOu36qmBEAwvS8oNU8eKa0ZnuXTZrvVEyAAgqQXjv7V4tklKImHMhBv3CqWHGtmxCIqFJuJ71ss81kOJ9pc1otyMzKvSZtVyxaOFgE1hTPfsA6Y5pQayhVikeCMfX8u/uFSmM=\""; - - var authorizationToken = ApiAuthorization.Token( - realm - , authPrefixL2 - , httpMethod - , url - , appId - , privateKey: privateKey - , timestamp: timestamp - , nonce: nonce - ); - - Assert.AreEqual(expectedTokenL2, authorizationToken); - } - - [Test] - public void Test_L2_Wrong_Password_Test() - { - - Assert.Throws(() => - { - var myPrivateKey = ApiAuthorization.PrivateKeyFromP12(privateCertNameP12, passphrase + "x"); - - ApiAuthorization.Token( - realm - , authPrefixL2 - , httpMethod - , url - , appId - , privateKey: myPrivateKey - ); - }); - } - - [Test] - public void Test_L2_Not_Supported_Cert_Test() - { - var fileName = GetLocalPath("Certificates/ssc.alpha.example.com.pem"); - - Assert.Throws(() => - { - var myPrivateKey = ApiAuthorization.PrivateKeyFromP12(fileName, passphrase); - - ApiAuthorization.Token( - realm - , authPrefixL2 - , httpMethod - , url - , appId - , privateKey: myPrivateKey - ); - }); - } - - [Test] - public void Test_L2_Invalid_FileName_Test() - { - var fileName = "Xssc.alpha.example.com.p12"; - - Assert.Throws(() => - { - var myPrivateKey = ApiAuthorization.PrivateKeyFromP12(fileName, passphrase); - - ApiAuthorization.Token( - realm - , authPrefixL2 - , httpMethod - , url - , appId - , privateKey: myPrivateKey - ); - }); - } - - - } -} diff --git a/ApiUtilLibTest/BaseService.cs b/ApiUtilLibTest/BaseService.cs index 6836455..0f720f2 100644 --- a/ApiUtilLibTest/BaseService.cs +++ b/ApiUtilLibTest/BaseService.cs @@ -1,52 +1,64 @@ -using ApexUtilLib; -using ApiUtilLib; +using ApiUtilLib; using System; using System.Collections.Generic; using System.IO; -using System.Security.Cryptography; -using System.Text; using System.IO.Compression; using System.Reflection; +using System.Linq; +using Newtonsoft.Json; +using NUnit.Framework; namespace ApexUtilLibTest { public class BaseService { - internal string apexTestSuitePath = "https://github.com/GovTechSG/test-suites-apex-api-security/archive/master.zip"; - internal string testDataPath = GetLocalPath("temp/test-suites-apex-api-security-master/testData/"); - internal string testCertPath = GetLocalPath("temp/test-suites-apex-api-security-master/"); - - internal ApiUtilLib.SignatureMethod signatureMethod { get; set; } - internal ApiUtilLib.HttpMethod httpMethod { get; set; } - internal ApiList apiList { get; set; } - internal string timeStamp { get; set; } - internal string version { get; set; } - internal string nonce { get; set; } - internal string authPrefix { get; set; } - internal string testId { get; set; } - internal string appId { get; set; } - internal Uri signatureURL { get; set; } - internal string expectedResult { get; set; } - internal bool errorTest { get; set; } - internal string[] skipTest { get; set; } - internal string realm { get; set; } - internal Uri invokeUrl { get; set; } - internal string secret { get; set; } - internal string passphrase { get; set; } + // APEX 1 + //internal static string apexTestSuitePath = "https://github.com/GovTechSG/test-suites-apex-api-security/archive/master.zip"; + + // for APEX2 + //internal static string apexTestSuitePath = "https://github.com/GovTechSG/test-suites-apex-api-security/zipball/master/"; + internal static string apexTestSuitePath = "https://github.com/GovTechSG/test-suites-apex-api-security/zipball/development/"; + + internal static bool IsDebug = false; + + internal static bool IsDataFileDownloaded = false; + internal static string testDataPath = GetLocalPath("temp/GovTechSG-test-suites-apex-api-security-2b397cc/testData/"); + internal static string testCertPath = GetLocalPath("temp/GovTechSG-test-suites-apex-api-security-2b397cc/"); + + internal SignatureMethod SignatureMethod { get; set; } + internal HttpMethod HttpMethod { get; set; } + + internal FormData FormData { get; set; } + internal string TimeStamp { get; set; } + internal string Version { get; set; } + internal string Nonce { get; set; } + internal string AuthPrefix { get; set; } + internal string TestId { get; set; } + internal string AppId { get; set; } + internal Uri SignatureURL { get; set; } + + internal string ExpectedResult { get; set; } + public bool ResultBool => ExpectedResult == "true"; + + internal bool ErrorTest { get; set; } + + internal bool SkipTest { get; set; } + + internal string Realm { get; set; } + internal Uri InvokeUrl { get; set; } + internal string Secret { get; set; } + internal string Passphrase { get; set; } public BaseService() { - downloadFile(apexTestSuitePath, GetLocalPath("testSuite.zip")); } - - internal static string GetLocalPath(string relativeFileName) { - var localPath = Path.Combine(Path.GetDirectoryName(new Uri(Assembly.GetExecutingAssembly().CodeBase).LocalPath), relativeFileName.Replace('/', Path.DirectorySeparatorChar)); - return localPath; + return Path.Combine(Path.GetDirectoryName(new Uri(Assembly.GetExecutingAssembly().CodeBase).LocalPath), relativeFileName.Replace('/', Path.DirectorySeparatorChar)); } - internal void downloadFile(string sourceURL, string downloadPath) + + internal static string DownloadFile(string sourceURL, string downloadPath) { try { @@ -54,17 +66,17 @@ internal void downloadFile(string sourceURL, string downloadPath) int bufferSize = 1024; bufferSize *= 1000; long existLen = 0; - System.IO.FileStream saveFileStream; - saveFileStream = new System.IO.FileStream(downloadPath, - System.IO.FileMode.Create, - System.IO.FileAccess.Write, - System.IO.FileShare.ReadWrite); + FileStream saveFileStream; + saveFileStream = new FileStream(downloadPath, + FileMode.Create, + FileAccess.Write, + FileShare.ReadWrite); System.Net.HttpWebRequest httpReq; System.Net.HttpWebResponse httpRes; httpReq = (System.Net.HttpWebRequest)System.Net.WebRequest.Create(sourceURL); httpReq.AddRange((int)existLen); - System.IO.Stream resStream; + Stream resStream; httpRes = (System.Net.HttpWebResponse)httpReq.GetResponse(); resStream = httpRes.GetResponseStream(); @@ -78,11 +90,19 @@ internal void downloadFile(string sourceURL, string downloadPath) } saveFileStream.Close(); - if (System.IO.Directory.Exists(GetLocalPath("temp/"))) + if (Directory.Exists(GetLocalPath("temp/"))) { Directory.Delete(GetLocalPath("temp/"), true); } ZipFile.ExtractToDirectory(downloadPath, GetLocalPath("temp/")); + + // determine the folder for the json files + string path = GetLocalPath("temp/"); + DirectoryInfo dictiontory = new DirectoryInfo(path); + DirectoryInfo[] dir = dictiontory.GetDirectories();// this get all subfolder //name in folder NetOffice. + string dirName = dir[0].Name; //var dirName get name from array Dir; + + return dirName; } catch (Exception ex) { @@ -94,25 +114,54 @@ internal void SetDetaultParams(TestParam paramFile) { try { - signatureMethod = paramFile.apiParam.signatureMethod.ParseSignatureMethod(paramFile.apiParam.secret); - httpMethod = paramFile.apiParam.httpMethod.ToEnum(); - apiList = new ApiList(); - SetApiList(paramFile.apiParam.formData); - SetApiList(paramFile.apiParam.queryString); - timeStamp = paramFile.apiParam.timestamp ?? "%s"; - version = paramFile.apiParam.version ?? "1.0"; - nonce = paramFile.apiParam.nonce ?? "%s"; - authPrefix = paramFile.apiParam.authPrefix; - appId = paramFile.apiParam.appID; - testId = paramFile.id; - signatureURL = paramFile.apiParam.signatureURL.IsNullOrEmpty() == true ? null : new System.Uri(paramFile.apiParam.signatureURL); - expectedResult = CommonExtensions.GetCharp(paramFile.expectedResult); - errorTest = paramFile.errorTest; - skipTest = paramFile.skipTest; - invokeUrl = paramFile.apiParam.invokeURL.IsNullOrEmpty() == true ? null : new System.Uri(paramFile.apiParam.invokeURL); - secret = paramFile.apiParam.secret ?? null; - realm = paramFile.apiParam.realm ?? null; - passphrase = paramFile.apiParam.passphrase;// ?? "passwordp12"; + if (paramFile.ApiParam != null) + { + SignatureMethod = paramFile.ApiParam.SignatureMethod.ParseSignatureMethod(paramFile.ApiParam.Secret); + HttpMethod = paramFile.ApiParam.HttpMethod.ToEnum(); + + // queryString and formData must be saperated + FormData = FormData.SetupList(paramFile.ApiParam.FormData); + QueryData queryData = QueryData.SetupList(paramFile.ApiParam.QueryString); + + //TimeStamp = paramFile.ApiParam.Timestamp ?? "%s"; + TimeStamp = paramFile.ApiParam.Timestamp; + + Version = paramFile.ApiParam.Version ?? "1.0"; + + //Nonce = paramFile.ApiParam.Nonce ?? "%s"; + Nonce = paramFile.ApiParam.Nonce; + + AuthPrefix = paramFile.ApiParam.AuthPrefix; + AppId = paramFile.ApiParam.AppID; + + // combine queryString with URL + string queryString = ""; + if (!paramFile.ApiParam.SignatureURL.IsNullOrEmpty()) + { + queryString = queryData.ToString(); + if (!queryString.IsNullOrEmpty()) + { + // query start with ?, replace ? with & when url already contain queryString + if (paramFile.ApiParam.SignatureURL.IndexOf('?') > -1) + { + queryString = queryString.Replace("?", "&"); + } + } + } + SignatureURL = paramFile.ApiParam.SignatureURL.IsNullOrEmpty() ? null : new Uri(string.Format("{0}{1}", paramFile.ApiParam.SignatureURL, queryString)); + + InvokeUrl = paramFile.ApiParam.InvokeURL.IsNullOrEmpty() ? null : new Uri(paramFile.ApiParam.InvokeURL); + Secret = paramFile.ApiParam.Secret ?? null; + Realm = paramFile.ApiParam.Realm ?? null; + Passphrase = paramFile.ApiParam.Passphrase ?? paramFile.Passphrase;// ?? "passwordp12"; + } + + TestId = paramFile.Id; + ExpectedResult = CommonExtensions.GetCharp(paramFile.ExpectedResult); + ErrorTest = paramFile.ErrorTest; + + SkipTest = paramFile.SkipTest == null ? false : paramFile.SkipTest.Contains("c#"); + } catch (Exception ex) { @@ -120,86 +169,54 @@ internal void SetDetaultParams(TestParam paramFile) } } - internal void SetApiList(Dictionary data = null) + internal static IEnumerable GetJsonFile(string fileName) { - try + if (!IsDataFileDownloaded) { - if (data != null) + if (IsDebug) { - foreach (var item in data) - { - var key = item.Key ?? ""; - var value = item.Value ?? ""; + var folderName = "linkFolder"; - String value_s = value.ToString().Trim(); - - if (!key.ToString().IsNullOrEmpty()) - { - string[] _queryParams = { "" }; - string val = null; - - if (!value_s.IsNullOrEmpty() && !(value_s.StartsWith("{", StringComparison.InvariantCulture) && value_s.EndsWith("}", StringComparison.InvariantCulture))) - { - - val = value_s.RemoveString(new string[] { "\\", "\\ ", " \\", "\"", "\\ ", "\n" }).Unescape(); - - if (val == "True") - val = "true"; - if (val == "False") - val = "false"; - if (val.StartsWith("[", StringComparison.InvariantCulture) && val.EndsWith("]", StringComparison.InvariantCulture)) - { - - string[] _paramValues = { "" }; - val = val.RemoveString(new string[] { "[", "]", " " }); - _paramValues = val.Split(','); - foreach (var paramvalue in _paramValues) - { - var _paramvalue = paramvalue; - apiList.Add(key.ToString(), _paramvalue.Unescape()); - } - - } - else - { - apiList.Add(key.ToString(), val); - } - } - else - { - apiList.Add(key.ToString(), val); - } + // set the path to test data files + testDataPath = GetLocalPath($"{folderName}/testData/"); + testCertPath = GetLocalPath($"{folderName}/"); + } + else + { + var folderName = DownloadFile(apexTestSuitePath, GetLocalPath("testSuite.zip")); - } - } + // set the path to test data files + testDataPath = GetLocalPath($"temp/{folderName}/testData/"); + testCertPath = GetLocalPath($"temp/{folderName}/"); } - }catch (Exception ex) - { - throw ex; + IsDataFileDownloaded = true; } - } - internal IEnumerable - GetJsonFile(string fileName) - { string path = testDataPath + fileName; - TestDataService service = new TestDataService(); - var jsonData = service.LoadTestFile(path); + try + { + using (StreamReader reader = new StreamReader(path)) + { + string _result = reader.ReadToEnd(); - return jsonData; - } + IEnumerable result = JsonConvert.DeserializeObject>(_result); + return result; + } + } + catch (Exception ex) + { + throw ex; + } + } - public static byte[] PEM(string type, byte[] data) + internal static void ValidateErrorMessage(TestParam testCase, Exception ex) { - string pem = Encoding.ASCII.GetString(data); - string header = String.Format("-----BEGIN {0}-----", type); - string footer = String.Format("-----END {0}-----", type); - int start = pem.IndexOf(header) + header.Length; - int end = pem.IndexOf(footer, start); - string base64 = pem.Substring(start, (end - start)); - return Convert.FromBase64String(base64); + // remove the file path that is machine dependent... + string err = ex.Message.Replace(testCertPath, ""); + + Assert.AreEqual(testCase.Result, err, "{0} - {1}", testCase.Id, testCase.Description); } } } diff --git a/ApiUtilLibTest/BaseStringTest.cs b/ApiUtilLibTest/BaseStringTest.cs deleted file mode 100644 index 8d60b81..0000000 --- a/ApiUtilLibTest/BaseStringTest.cs +++ /dev/null @@ -1,126 +0,0 @@ -using NUnit.Framework; -using System.Linq; -using ApiUtilLib; -using ApexUtilLibTest; -using System.Collections.Generic; -using System.Reflection; -using System; - -namespace ApiUtilLibTest -{ - [TestFixture] - public class BaseStringTest - { - - [Test] - public void BaseString_Basic_Test() - { - var url = "https://test.example.com:443/api/v1/rest/level1/in-in/?ap=裕廊坊%20心邻坊"; - var expectedBaseString = "GET&https://test.example.com/api/v1/rest/level1/in-in/&ap=裕廊坊 心邻坊&auth_prefix_app_id=app-id-lpX54CVNltS0ye03v2mQc0b&auth_prefix_nonce=1355584618267440511&auth_prefix_signature_method=HMACSHA256&auth_prefix_timestamp=1502175057654&auth_prefix_version=1.0"; - - var baseString = ApiAuthorization.BaseString( - "auth_prefix", - SignatureMethod.HMACSHA256, - "app-id-lpX54CVNltS0ye03v2mQc0b", - new System.Uri(url), - HttpMethod.GET, - null, - "1355584618267440511", - "1502175057654", - "1.0" - ); - - Assert.AreEqual(expectedBaseString, baseString); - } - - [Test] - public void BaseString_BugTest() - { - - var formData = new ApiUtilLib.ApiList(); - - formData.Add("Action", "SendMessage"); - formData.Add("MessageBody", "[{}]"); - - var url = "https://test.example.com:443/api/v1/rest/level1/in-in/?ap=裕廊坊%20心邻坊"; - var expectedBaseString = "GET&https://test.example.com/api/v1/rest/level1/in-in/&Action=SendMessage&MessageBody=[{}]&ap=裕廊坊 心邻坊&auth_prefix_app_id=app-id-lpX54CVNltS0ye03v2mQc0b&auth_prefix_nonce=1355584618267440511&auth_prefix_signature_method=HMACSHA256&auth_prefix_timestamp=1502175057654&auth_prefix_version=1.0"; - - var baseString = ApiAuthorization.BaseString( - "auth_prefix", - SignatureMethod.HMACSHA256, - "app-id-lpX54CVNltS0ye03v2mQc0b", - new System.Uri(url), - HttpMethod.GET, - formData, - "1355584618267440511", - "1502175057654", - "1.0" - ); - - Assert.AreEqual(expectedBaseString, baseString); - } - - [Test] - public void BaseString_FormData_Test() - { - var url = "https://test.example.com:443/api/v1/rest/level1/in-in/?ap=裕廊坊%20心邻坊"; - var expectedBaseString = "POST&https://test.example.com/api/v1/rest/level1/in-in/&ap=裕廊坊 心邻坊&auth_prefix_app_id=app-id-lpX54CVNltS0ye03v2mQc0b&auth_prefix_nonce=6584351262900708156&auth_prefix_signature_method=HMACSHA256&auth_prefix_timestamp=1502184161702&auth_prefix_version=1.0¶m1=data1"; - - var formList = new ApiList(); - formList.Add("param1", "data1"); - - var baseString = ApiAuthorization.BaseString( - "auth_prefix", - SignatureMethod.HMACSHA256, - "app-id-lpX54CVNltS0ye03v2mQc0b", - new System.Uri(url), - HttpMethod.POST, - formList, - "6584351262900708156", - "1502184161702", - "1.0" - ); - - Assert.AreEqual(expectedBaseString, baseString); - } - - [Test] - public void BaseString_Invalid_Url_01_Test() - { - var url = "ftp://test.example.com:443/api/v1/rest/level1/in-in/?ap=裕廊坊%20心邻坊"; - - Assert.Throws(() => ApiAuthorization.BaseString( - "auth_prefix", - SignatureMethod.HMACSHA256, - "app-id-lpX54CVNltS0ye03v2mQc0b", - new System.Uri(url), - HttpMethod.POST, - null, - "6584351262900708156", - "1502184161702", - "1.0" - )); - } - - [Test] - public void BaseString_Invalid_Url_02_Test() - { - var url = "://test.example.com:443/api/v1/rest/level1/in-in/?ap=裕廊坊%20心邻坊"; - - Assert.Throws(() => ApiAuthorization.BaseString( - "auth_prefix", - SignatureMethod.HMACSHA256, - "app-id-lpX54CVNltS0ye03v2mQc0b", - new System.Uri(url), - HttpMethod.POST, - null, - "6584351262900708156", - "1502184161702", - "1.0" - )); - } - - - - } -} diff --git a/ApiUtilLibTest/CommonExtensions.cs b/ApiUtilLibTest/CommonExtensions.cs index 97cef13..92ec10c 100644 --- a/ApiUtilLibTest/CommonExtensions.cs +++ b/ApiUtilLibTest/CommonExtensions.cs @@ -1,5 +1,4 @@ using System; -using System.Text; using System.Collections.Generic; using System.Linq; @@ -15,31 +14,18 @@ public static class CommonExtensions /// public static T ToEnum(this string value) { - if (value.IsNullOrEmpty()) - return default(T); - - return (T)Enum.Parse(typeof(T), value, true); + return value.IsNullOrEmpty() ? default : (T)Enum.Parse(typeof(T), value, true); } - public static string GetCharp(dynamic value) + public static string GetCharp(Dictionary value) { try { - return value.charp; - } - catch (Exception) - { - return Convert.ToString(value); - } - } - - public static string GetCharp(Dictionary value) - { - try - { - var result = value.Where(c => c.Key == "c#").FirstOrDefault().Value; - if (result==null) - result = value.Where(c => c.Key == "default").FirstOrDefault().Value; + string result = value.FirstOrDefault(c => c.Key == "c#").Value; + if (result == null) + { + result = value.FirstOrDefault(c => c.Key == "default").Value; + } return result; } @@ -51,71 +37,27 @@ public static string GetCharp(Dictionary value) public static ApiUtilLib.SignatureMethod ParseSignatureMethod(this string value, string secret) { - if(value==null) - { - if(secret==null) - { - return ApiUtilLib.SignatureMethod.SHA256withRSA; - }else{ - return ApiUtilLib.SignatureMethod.HMACSHA256; - } - } - - return value.ToEnum(); + return value == null + ? secret == null ? ApiUtilLib.SignatureMethod.SHA256withRSA : ApiUtilLib.SignatureMethod.HMACSHA256 + : value.ToEnum(); } public static bool IsNullOrEmpty(this string value){ - if (value == null) - return true; - - if (value == String.Empty) - return true; - - return false; - } - - public static string Unescape(this string txt) - { - if (string.IsNullOrEmpty(txt)) { return txt; } - StringBuilder retval = new StringBuilder(txt.Length); - for (int ix = 0; ix < txt.Length;) - { - int jx = txt.IndexOf('\\', ix); - if (jx < 0 || jx == txt.Length - 1) jx = txt.Length; - retval.Append(txt, ix, jx - ix); - if (jx >= txt.Length) break; - switch (txt[jx + 1]) - { - case 'n': retval.Append('\n'); break; // Line feed - case 'r': retval.Append('\r'); break; // Carriage return - case 't': retval.Append('\t'); break; // Tab - case '\\': retval.Append('\\'); break; // Don't escape - default: // Unrecognized, copy as-is - retval.Append('\\').Append(txt[jx + 1]); break; - } - ix = jx + 2; - } - return retval.ToString(); - } - - public static string RemoveString(this string value, string[] array) - { - foreach (var item in array) - { - value = value.Replace(item, ""); - } - - return value; + return value == null || value == string.Empty; } public static bool ToBool(this string value) { if(!value.IsNullOrEmpty()){ if (value.ToLower() == "true") + { return true; + } if (value.ToLower() == "false") + { return false; + } } return false; diff --git a/ApiUtilLibTest/L1SignatureTest.cs b/ApiUtilLibTest/L1SignatureTest.cs deleted file mode 100644 index 63f8e0a..0000000 --- a/ApiUtilLibTest/L1SignatureTest.cs +++ /dev/null @@ -1,143 +0,0 @@ -using System; -using System.Collections.Generic; -using ApiUtilLib; -using NUnit.Framework; - -namespace ApiUtilLibTest -{ - [TestFixture] - public class L1SignatureTest - { - const string baseString = "message"; - - const string secret = "secret"; - const string secret2 = "5aes9wG4mQgWJBfKMuYLtrEtNslm1enWG2XpGaMk"; - - const string expectedResult = "i19IcCmVwVmMVz2x4hhmqbgl1KeU0WnXBgoDYFeWNgs="; - - [Test] - public void L1_BaseString_IsNullOrEmpty_Test() - { - string testBaseString = null; - - Assert.Throws(() => testBaseString.L1Signature(secret)); - Assert.Throws(() => "".L1Signature(secret)); - - Assert.Throws(() => ApiAuthorization.L1Signature(null, secret)); - Assert.Throws(() => ApiAuthorization.L1Signature("", secret)); - } - - [Test] - public void L1_Secret_IsNullOrEmpty_Test() - { - Assert.Throws(() => baseString.L1Signature(null)); - Assert.Throws(() => baseString.L1Signature("")); - - Assert.Throws(() => ApiAuthorization.L1Signature(baseString, null)); - Assert.Throws(() => ApiAuthorization.L1Signature(baseString, "")); - } - - [Test] - public void L1_Verify_Signature_Test() - { - Assert.IsTrue(expectedResult.VerifyL1Signature(secret, baseString)); - - Assert.IsTrue(ApiAuthorization.VerifyL1Signature(expectedResult, secret, baseString)); - } - - [Test] - public void L1_Verify_Signature_With_Wrong_Secret_Test() - { - Assert.IsFalse(expectedResult.VerifyL1Signature(secret + 'x', baseString)); - - Assert.IsFalse(ApiAuthorization.VerifyL1Signature(expectedResult, secret + 'x', baseString)); - } - - [Test] - public void L1_Verify_Signature_With_Wrong_BaseString_Test() - { - Assert.IsFalse(expectedResult.VerifyL1Signature(secret, baseString + 'x')); - - Assert.IsFalse(ApiAuthorization.VerifyL1Signature(expectedResult, secret, baseString + 'x')); - } - - [Test] - public void L1_BaseString_Encoding_Test() - { - var dataList = new List(); - - dataList.Add(new string[] - { - "Lorem ipsum dolor sit amet, vel nihil senserit ei. Ne quo erat feugait disputationi.", - secret, - "cL3lY5/rhmkxMw/dCHCa4b9Lpp/soPPACnIxtQwRQI8=", - "Basic Test" - }); - - // Chinese Traditional - dataList.Add(new string[] - { - "道続万汁国圭絶題手事足物目族月会済。", - secret, - "wOHv68zuoiIjfJHW0hZcOk4lORyiAL/IGK8WSkBUnuk=", - "UTF8 (Chinese Traditional) Test" - }); - - // Japanese - dataList.Add(new string[] - { - "員ちぞど移点お告周ひょ球独狙チウソノ法保断フヒシハ東5広みぶめい質創ごぴ採8踊表述因仁らトつ。", - secret, - "L0ft4O8R2hxpupJVkLbgQpW0+HRw3KDgNUNf9DAEY7Y=", - "UTF8 (Japanese) Test" - }); - - // Korean - dataList.Add(new string[] - { - "대통령은 즉시 이를 공포하여야 한다, 그 자율적 활동과 발전을 보장한다.", - secret, - "a6qt0t/nQ3GQFAEVTH+LMvEi0D41ZaKqC7LWJcVmHlE=", - "UTF8 (Korean) Test" - }); - - // Greek - dataList.Add(new string[] - { - "Λορεμ ιπσθμ δολορ σιτ αμετ, τατιον ινιμιcθσ τε ηασ, ιν εαμ μοδο ποσσιμ ινvιδθντ.", - secret, - "WUGjbeO8Jy8Rvs5tD2biLHPR0+qtAmXeZKqX6acYL/4=", - "UTF8 (Greek) Test" - }); - - dataList.Add(new string[] - { - "GET&https://test.example.com/api/v1/rest/level1/in-in/&auth_prefix_app_id=app-id-lpX54CVNltS0ye03v2mQc0b&auth_prefix_nonce=-4985265956715077053&auth_prefix_signature_method=HMACSHA256&auth_prefix_timestamp=1502159855341&auth_prefix_version=1.0¶m1=def+123", - secret2, - "8NxfLG0pFWEq1gZEttCW4lgrb92MFaqQpeUPRDx4CAE=", - "L1 BaseString Happy Path Test" - }); - - dataList.Add(new string[] - { - "GET&https://test.example.com/api/v1/rest/level1/in-in/&ap=裕廊坊 心邻坊&auth_prefix_app_id=app-id-lpX54CVNltS0ye03v2mQc0b&auth_prefix_nonce=2851111144329605674&auth_prefix_signature_method=HMACSHA256&auth_prefix_timestamp=1502163903712&auth_prefix_version=1.0", - secret2, - "0fE74Vf/Q7nktxeezzrYcvOeq36Pd4CJ7Ez9I00cdJk=", - "L1 BaseString with UTF8 Parameters Test" - }); - - // excute test - foreach (var item in dataList) - { - L1Test(item[0], item[1], item[2], item[3]); - } - } - - void L1Test(string testBaseString, string testSecret, string expectedSignature, string messageTag) - { - var signature = testBaseString.L1Signature(testSecret); - - Assert.AreEqual(expectedSignature, signature, messageTag); - } - } -} diff --git a/ApiUtilLibTest/L2SignatureTest.cs b/ApiUtilLibTest/L2SignatureTest.cs deleted file mode 100644 index b200933..0000000 --- a/ApiUtilLibTest/L2SignatureTest.cs +++ /dev/null @@ -1,134 +0,0 @@ -using NUnit.Framework; -using System; -using System.Security.Cryptography.X509Certificates; -using ApiUtilLib; -using System.Collections.Generic; -using System.Security.Cryptography; -using System.IO; -using System.Reflection; - -namespace ApiUtilLibTest -{ - [TestFixture] - public class L2SignatureTest - { - // file name follow unix convention... - static readonly string privateCertName = GetLocalPath("Certificates/ssc.alpha.example.com.p12"); - static readonly string publicCertName = GetLocalPath("Certificates/ssc.alpha.example.com.cer"); - - const string baseString = "message"; - const string password = "passwordp12"; - - static readonly RSACryptoServiceProvider privateKey = ApiAuthorization.PrivateKeyFromP12(privateCertName, password); - static readonly RSACryptoServiceProvider publicKey = ApiAuthorization.PublicKeyFromCer(publicCertName); - - static string GetLocalPath(string relativeFileName) - { - var localPath = Path.Combine(Path.GetDirectoryName(new Uri(Assembly.GetExecutingAssembly().CodeBase).LocalPath), relativeFileName.Replace('/', Path.DirectorySeparatorChar)); - - return localPath; - } - - [Test] - public void L2_BaseString_IsNullOrEmpty_Test() - { - const string testBaseString = null; - - Assert.Throws(() => testBaseString.L2Signature(privateKey)); - Assert.Throws(() => "".L2Signature(privateKey)); - - Assert.Throws(() => ApiAuthorization.L2Signature(null, privateKey)); - Assert.Throws(() => ApiAuthorization.L2Signature("", privateKey)); - } - - [Test] - public void L2_PrivateKey_IsNull_Test() - { - Assert.Throws(() => baseString.L2Signature(null)); - - Assert.Throws(() => ApiAuthorization.L2Signature(baseString, null)); - } - - const string message = "Lorem ipsum dolor sit amet, vel nihil senserit ei. Ne quo erat feugait disputationi."; - - const string expectedSignature = "OsOqG/6hJfGmpCDkqBSZ4netNJDex1lzBYTzGjvjShSFEhJEzAD1zNHKg8Zf9Dve7o9lx3+Yrhrn68nMocgUSOvinhUNF3ttLWw36GzXG7BFJRSIbeUfY3C1vAhkjxmE8oiYoIWctT9qBOL/3GY5QD1H3DiWrb3OLUjy52dsAPmK2P5ofdo8Erd5/0mTxgX+OLMADLJUXq/Aajp1ZIF+djQipPHg0Ms1sNkSHCURxyCjRMKOHNe8DH15lKcApBBjd3XPlb+PGlFl/ffc5Q1ALnAOmsqN6hi8mW+R6Eb0QZsvoRMFSA7kQdWvkCrlWtP5ux+A2Ji/b48SWFSJurVz7yRBhJFDYlvTTCGcgLfwn3TJXa/YbCK05qy307i6X9jnfYaqSYhKC61ExTZYE2SyfagAcWVlSlq3bEovZXllKAwq8Yqyez2EqkOoSzJdj5gmJ1Pb4wN/ss7yYybRSvFShQunj/t6TiQDCJuhghXOfV5Scs/wqjDMWViqrA65YOQHROqAku81NiWFmciVHjk6bNAGsp7iE0p5XnA4z9B41ZVPsxsSXUg4tZvpUrZSpNzlGFBi/uEa1UYcrUd8APzBCvUa75RhZsfxRsCOkpyOEmqoFzg4ngCfegJzBpU5La9e0SOlRvW29p9CK7fS/FZC5YJtP1kucaBN5pX/mxaYeUQ="; - - [Test] - public void L2_Verify_Signature_Test() - { - Assert.IsTrue(expectedSignature.VerifyL2Signature(publicKey, message)); - } - - [Test] - public static void L2_Basic_Test() - { - var dataList = new List(); - - dataList.Add(new string[] - { - message, - expectedSignature, - "Basic Test" - }); - - // Chinese Traditional - dataList.Add(new string[] - { - "道続万汁国圭絶題手事足物目族月会済。", - "BcgiwVRV5NPf2D15NMA7PjfheHY+jYeODlODuaAahd5dU/fuGanMcFpFuKJtxuCQLOE3veZMCC7V+hb/LEaBfkvXw+7gl8WtLu+T927Xs+3517AZm9vZ3nU34FIMAQpTJ8QbciFcd5FAybDiMuCfzvVE59yTSL/JmzSH4188/K6Z1uZ29VizrC2BwtVA/SHaWN1SMUGX6u0tQN5nE4dGZ9lRKm1Jd2rsUNDmqsmUZDJTbgoZbTJjNQklRv48GunXYBt/cfi9T5bryIVilqUphTIe6GrjLXZ1NVVCcMCJaCzAesX2dWUwLCEULcM4Vqw+7SWN20k4zcori5+QkwNH/eyViHwKiYY+neIusUU4HcafIXNHlYQjj1OVEXqPn2P7TzH9y+7TXheNrQ03P6NnRBjEW/bAgoCplbhYWnlNtu+BBNLn9+6rN/ePJz265Wetb16ZjG+ZwbV72PUkGxeFoT7cGBNvcC5zK4bFZV4AOr7TqE9Nt/xm9Xi7/gM0oU7zgYm+32LJaAxG2vax9EFdi3yBKrGRBYLaMH/6KEreZV+iZgLsqK/7tWEQom843iTmeRaxA4/Xeg3MLPyyxrWtQBqu2O/lv6pEf+scnc2Mg6gyc5uRm0luxJUBkqI6i/BAHGZRN1cDkMhWywAcWs3yxxV6qptFYxl6ubLCbCXtiw0=", - "UTF8 (Chinese Traditional) Test" - }); - - // Japanese - dataList.Add(new string[] - { - "員ちぞど移点お告周ひょ球独狙チウソノ法保断フヒシハ東5広みぶめい質創ごぴ採8踊表述因仁らトつ。", - "RtNtUoRXhNFrFPMy5aJjPTB8yI9AyvLqIKmgjmarxZhB/aOLXSJtHHJMgufOLDsUzEyDenlPuRp4ju2Dp870P19H/IxLktTqkU3DZU35tqk21TWNQDmdl/P9YjY3BNJqU4YBV3A83KRDRhJh235Hjy20dbJqZAe/oL/8GboRd0W941Oj2VfC53SmVAYWQV1aJb4qV3cvoQG2OtcBMNA+ayG+0oTB9AtGZ3CqCUPqbfbb36oc81jYQj0nElHRew7QdclfpAUQaDgCF6svduji2rdXrU+fRYaiRPtm4F1zv9JVuIjKOZRqVQeQ3Nb/X8zUMEBNeWToQPmzoHz6hAEfzYUif2IJ1KqYooV29AwOvwu1itAeUwLtqlHK3QGJYaJVrw05EyAg1IsicAQ+szP+6t6Er3GjhRSXwIcpKdxLUHVtwFoK7E1L4FqxCW+Pokm97h0/rqWREt7DJvoIofQ8rtfEfao5CTaJOQyMRUx+Ds1Kytzpzd1T7aWFvdzFxo9YLfsZ/DzIy2F7iMi9c1b8WYfStlBvfUeEEeByZj+7FrvLMo9Ys5K/UweBfTcBHdPfCmW5RTJhmfK0p+EVsntLqkCbWMoQ6JdNZoASSB7E+NPGJuk3kuVo4sPnPy9vQlHsYJWktXjwTmBp4EZzfcia6U5TSWG0Wdn4ohCYQU2Y/sg=", - "UTF8 (Japanese) Test" - }); - - // Korean - dataList.Add(new string[] - { - "대통령은 즉시 이를 공포하여야 한다, 그 자율적 활동과 발전을 보장한다.", - "GW0UWsS/bdP22Zd8D+WCZtz4LhyHF/8QemS7xTDPzhSlN+yjPtu7O0f/GGl3s+U1Cm3gUjMIRKbSKyi441Z57MD/9Ju8swtAJkHh9K/LPf/fFfm3UMN0EU7jeoEUkFG3AM8rR24ih16HFpK8RcDHDRL5+tAoU6au/JRLAnuRnhcOjunSC91OhTZJqSGYukoarLYVFxnLFyZPviZPe+aaFW4ZUrD+Kc6K2C/htHS1S/7NJedDsD8If31+dh/wdkIbvhQRDgWBJlSAoqOqmeFSRIIXW/VeufOjXZ9fxa/pmsBDN5BB5Fb3MguxebD61c0MN4F+gnRQ/5arKQL5oIn/QAGan6Ll7s7nUGpa88sdVKRqw/TVcqmYeIFgWBUhnk2p54tvWbCXski63z4QRC+4TZ/ITPgn1sDqsD5Qf9/Ly1RPpJPODNgIYb5i6vh94gchqrF1g3EphbJ3riWCqREoBuCD+yqS2DSE7QWg1gjaHtT8kzcxkt3KpJoLPlZPKt92y03/av8a0AXpc2H7pw2mJ4i13xDsiRKavE4R7pwrfUJxSxYD2jBPZgNTo3XxaboHZgFbvyyw3xHreSo9CmM0mL94qha4jv2TqGuURooiBfizxzuHeMub1t8VIAXOiTk/iQtBPvGLtsQzFW3TeAeZtiYSGBeKOmb6O1vtetBurQk=", - "UTF8 (Korean) Test" - }); - - // Greek - dataList.Add(new string[] - { - "Λορεμ ιπσθμ δολορ σιτ αμετ, τατιον ινιμιcθσ τε ηασ, ιν εαμ μοδο ποσσιμ ινvιδθντ.", - "G6FezmgEqrnZNxqWfIE8Rcb49L3WQRcAQxQ0xX2sibejHHiOXPXU811OIsL7hsYmyLSSoY3IXTtu271MwfR1TTiODBnIqpgZ0jwmyKK7YoHUDqRgKmVscBnwotw2ntDn1eA2BAU2yKi+UOeUbDcY8dCK/qxdoKdvQg99zjmm1P4EG0dFlmh07oa2ByH4pgioaxI0sKQdDL14qbjrKOiFtfgdv5NEd1Q3kP240p9vLOoScPsRvRZlpWGPCUa0R9wQMtXZAKB3TVs+p8hu5ZHmG9JP2Jo5FRt8EkCG6V3Fg8qlbDO5m9B49atynVBsNSQkYKpCylokJI/mcESNciliQmOwkLmqh6YeELX82PSvnErIPRSAzrqkKYed/HI5gL2Z8pCOwohSfuMeoOrba3JeD98kMQHGwhw+pxSP6lnTCxLwLREhqgSrcXfymhc2TCbA/w/1gT3MjTIDjIF1HgtT2bPpjco62iuKPyrjejb4ARGcty5mlUjbPNUCD/DB4qgghnhbtvWJFJxF7Egs/BeDk5swyyvFBrlXPd/yhCpMJRAOZ0bK3Adj1ij0tVH/kHtDzRYZnF0ZQXZBlHyP2DMvlnJQbIDrTBuojRYFb8W7CPWc/P4RQIGwRv6ZvT+LLl+uuNpvNoVFc/EB0gKII819nINmCjcmuYhsboBLkJ9XHyE=", - "UTF8 (Greek) Test" - }); - - dataList.Add(new string[] - { - "GET&https://test.example.com/api/v1/rest/level2/in-in/&auth_prefix_app_id=app-id-lpX54CVNltS0ye03v2mQc0b&auth_prefix_nonce=7798278298637796436&auth_prefix_signature_method=SHA256withRSA&auth_prefix_timestamp=1502163142423&auth_prefix_version=1.0", - "hmAlq47s1yfjVjmgdMFK4OXk1Gm380Gy+cfhWrhuD2Dux523D8ywA4dn1UVsxukGYT6HnXm83tHwFiD0WWyL1nyHfViD7HDNo95pZrHFOEgrkmXzUUJ5g8aVO1EPxbMuJSlRECeLjbHSpkfyMljSVX/8KH9wt4S50Zs36f9kMcMpYZvK8Z1C7SUleoM+yIvCJPGj5mRIbZyieFXGJ0Zt3aJBsWwZeV4BcrSL/T1GQQLigOY5M7lmkmDmc/Wz7Ky82rTImbJB8HfclNkD0l4wy49vSLXh9IbIvUpErbzipjG1MmA4t1cPMA3PoyAXWAcrHHSSBBwcpD6yOr5HOXyYAKDHz5IPZLRtAOft4BPwKygzOhDR881rfFbOT8IOOSI20uXtlTa6KTIUq9Opvc0g2gnUgai0qfm6uCOi8vmEBKj5z5mOrG+39FhXbB5FEpv6ZuFPP3ATtkIg9nSWStifbkq4fJCTSAsXRF4ragNWQRgc46B4zUwWpnRZ7s83oi808A2vQXLFExPTjW4DBUTtBt+O9Z1Gp7Z5yRHekut4vLaNqrIvRcLqmn92G9QqfkFJmQGrOH7SpAtgDhGHzaNc5C+OTmCkxhFKtoblO4ItwIh5X4i3gePDcHrBDEDTwjHPjXEFbDOL0JKrA6qlgA8evXVZXLugHAs6bO5zJhacXTU=", - "L2 BaseString Happy Path Test" - }); - - dataList.Add(new string[] - { - "GET&https://test.example.com/api/v1/rest/level2/in-in/&ap=裕廊坊 心邻坊&auth_prefix_app_id=app-id-lpX54CVNltS0ye03v2mQc0b&auth_prefix_nonce=7231415196459608363&auth_prefix_signature_method=SHA256withRSA&auth_prefix_timestamp=1502164219425&auth_prefix_version=1.0&oq=c# nunit mac&q=c# nunit mac", - "q7gaCiA1/oTYtCwrj3ZMumSwuIsF++NnjDzoexpU1OunABGrVxmQrJIcDXnVkKA1gaDZf0JXJMCfyfu9r/F2S8raQUyyxt0edjEjzCPFnF+1N6Hhj5KYMxT2lggv6mSblqi0tWUXD/tSgw0ssRfNM0+v0xo4G2kzbOlZe/JzIGUX/FtkanW5xJGS0wrZIfoAO3p1OVwghN6FZkKZ3o9gH3VGZItJKEa6agPqlaH2XVbF3636nKlGRKbcc+fNDd2/EFqqxDxS3gx21a1cRJT6SfOZ7lqbnAkxvC9QW/mw42hObUx/RjyGI1QqMdJ6aRkLHQkHZ9LT41kJKg+AKsLlo/ogTdwxyekok9jt2UgVm7EFA9lkAB2T8T8q1eDalpby6BHCSBiQn9j7FhcVw6EFgWPLzlXCZl/sDzbrqX90AR38VAntTS+2f+Uc5vKxrJ41m2zD2HIYLw0oiXEjODPfvRPtCt478CCz1Y6xnMgD4hJwhTN8pyuMvdoxwWKWcCsdZhXPZrzNm/GZljT3jdRekUgAt3fBOwbji+OVt7TUqUKpYMadExrhiTRKV+dh8EUtdnJoM056YC5Ta2w/Iuxai6dT1QRW7bZOrUfOljD3Miq6Xyx3nnUW2sVQbeJkxFBJVbFAsxtpMRICcTg63VRrcGmhqxTX0QyqsxFqSrbj+eQ=", - "L2 BaseString with UTF8 Parameters Test" - }); - - // excute test - foreach (var item in dataList) - { - L2Test(item[0], privateKey, item[1], item[2]); - } - } - - public static void L2Test(string baseString, RSACryptoServiceProvider privateKey, string expectedSignature, string message) - { - var signature = baseString.L2Signature(privateKey); - - Assert.AreEqual(expectedSignature, signature, message); - } - } -} diff --git a/ApiUtilLibTest/TestDataService.cs b/ApiUtilLibTest/TestDataService.cs deleted file mode 100644 index dbf15d2..0000000 --- a/ApiUtilLibTest/TestDataService.cs +++ /dev/null @@ -1,48 +0,0 @@ -using System; -using System.IO; -using System.Reflection; -using Newtonsoft.Json.Converters; -using Newtonsoft.Json; -using System.Collections.Generic; -using System.Collections; - -namespace ApexUtilLibTest -{ - public class TestDataService - { - - public TestDataService() - { - - } - - /// - /// Loads the test file. - /// - /// The test file. - /// Path. - public IEnumerable LoadTestFile(string path) - { - try - { - ///Use below code for relative path - ///var projectPath = AppDomain.CurrentDomain.BaseDirectory.Replace("bin/Debug/", ""); - ///path = path.Replace(".json", ""); - ///path = Path.Combine(Path.GetDirectoryName(projectPath), jsonFileName + ".json"); - - using (StreamReader reader = new StreamReader(path)) - { - string _result = reader.ReadToEnd(); - - var result = JsonConvert.DeserializeObject>(_result); - - return result; - } - } - catch (Exception ex) - { - throw ex; - } - } - } -} diff --git a/ApiUtilLibTest/TestDataTest.cs b/ApiUtilLibTest/TestDataTest.cs index bd5cbdf..91f9c58 100644 --- a/ApiUtilLibTest/TestDataTest.cs +++ b/ApiUtilLibTest/TestDataTest.cs @@ -1,296 +1,378 @@ using ApiUtilLib; -using NUnit.Framework; -using System; -using System.Collections.Generic; -using System.Linq; -using Newtonsoft.Json.Serialization; +using NUnit.Framework; +using System; +using System.IO; using System.Security.Cryptography; -using System.Text.RegularExpressions; - -namespace ApexUtilLibTest -{ - [TestFixture()] - public class TestDataTest : BaseService - { - [Test()] - public void TestBaseString() - { - var jsonData = GetJsonFile("getSignatureBaseString.json"); - int expectedPass = jsonData.Count(); - int actualPass = 0; - - try - { - foreach (var test in jsonData) - { - SetDetaultParams(test); +using System.Text.RegularExpressions; - if (skipTest == null || !skipTest.Contains("c#")) - { +namespace ApexUtilLibTest +{ + [TestFixture()] + public class TestDataTest : BaseService + { + [Test(), TestCaseSource(nameof(GetJsonFile), new object[] { "getSignatureBaseString.json" })] + public void TestBaseString(TestParam testCase) + { + SetDetaultParams(testCase); - try - { - var baseString = ApiAuthorization.BaseString(authPrefix, signatureMethod, appId, signatureURL, httpMethod, apiList, nonce, timeStamp, version); - Assert.AreEqual(expectedResult, baseString); - actualPass++; - } - catch (Exception ex) - { - ex = ex; - } - } - else - { - actualPass++; - } - } + if (SkipTest) + { + Assert.Ignore(); } - catch (Exception ex) + else { - throw ex; - } - - Assert.AreEqual(expectedPass, actualPass); - + if (testCase.ErrorTest) + { + Assert.Fail("No exception defined for test case..."); + } + else + { + string baseString = ApiAuthorization.BaseString(AuthPrefix, SignatureMethod, AppId, SignatureURL, FormData, HttpMethod, Nonce, TimeStamp, Version); + Assert.AreEqual(ExpectedResult, baseString, testCase.ToString()); + } + } } - [Test()] - public void VerifyL1Signature() - { - var jsonData = GetJsonFile("verifyL1Signature.json"); - int expectedPass = jsonData.Count(); - int actualPass = 0; - - try - { - foreach (var test in jsonData) - { - SetDetaultParams(test); - - if (skipTest == null || !skipTest.Contains("c#")) - { - var message = test.message; - var signature = test.apiParam.signature; - var result = signature.VerifyL1Signature(secret,message); - try - { - Assert.AreEqual(expectedResult.ToBool(),result); - actualPass++; - } - catch (Exception) - { - if (expectedResult == "false") - actualPass++; - } - } - else - { - actualPass++; - } - } + [Test(), TestCaseSource(nameof(GetJsonFile), new object[] { "verifyL1Signature.json" })] + public void VerifyL1Signature(TestParam testCase) + { + SetDetaultParams(testCase); + if (SkipTest) + { + Assert.Ignore(); } - catch (Exception) + else { - throw; - } - - Assert.AreEqual(expectedPass, actualPass); - } - - [Test()] - public void VerifyL2Signature() - { - var jsonData = GetJsonFile("verifyL2Signature.json"); - int expectedPass = jsonData.Count(); - int actualPass = 0; - - try - { - foreach (var test in jsonData) - { - SetDetaultParams(test); - - if (skipTest == null || !skipTest.Contains("c#")) - { - - var message = test.message; - var signature = test.apiParam.signature; - string certPath = testCertPath + test.publicCertFileName; - - RSACryptoServiceProvider publicKey = ApiAuthorization.PublicKeyFromCer(certPath); - - var result = signature.VerifyL2Signature(publicKey, message); - - try - { - Assert.IsTrue(result); - actualPass++; - } - catch (Exception) - { - if (expectedResult == "false") - actualPass++; - } - }else{ - actualPass++; - } - } - } - catch (Exception ex) - { - throw ex; - } - - Assert.AreEqual(expectedPass, actualPass); + if (testCase.ErrorTest) + { + Assert.Fail("No exception defined for test case..."); + } + else + { + string message = testCase.Message; + string signature = testCase.ApiParam.Signature; + bool result = signature.VerifyL1Signature(Secret, message); + + Assert.AreEqual(ExpectedResult.ToBool(), result, testCase.ToString()); + } + } + } + + [Test(), TestCaseSource(nameof(GetJsonFile), new object[] { "verifyL2Signature.json" })] + public void VerifyL2Signature(TestParam testCase) + { + SetDetaultParams(testCase); + + if (SkipTest) + { + Assert.Ignore(); + } + else + { + if (testCase.ErrorTest) + { + Assert.Fail("No exception defined for test case..."); + } + else + { + string message = testCase.Message; + string signature = testCase.ApiParam.Signature; + string certPath = testCertPath + testCase.PublicKeyFileName; + + RSACryptoServiceProvider publicKey = ApiAuthorization.GetPublicKey(certPath, Passphrase); + bool result = signature.VerifyL2Signature(publicKey, message); + + Assert.AreEqual(ExpectedResult.ToBool(), result, testCase.ToString()); + } + } } + + private Exception GetTokenException(AuthParam authParam) where T : Exception + { + return Assert.Throws(() => { string result = ApiAuthorization.TokenV2(authParam).Token; }); + } - [Test()] - public void TestTokenSignature() - { - var jsonData = GetJsonFile("getSignatureToken.json"); - int expectedPass = jsonData.Count(); - int actualPass = 0; - try - { - foreach (var test in jsonData) - { - SetDetaultParams(test); - - if (skipTest == null || !skipTest.Contains("c#")) - { - try - { - string certName = test.apiParam.privateCertFileNameP12; - string privateCertPath = testCertPath + certName; - RSACryptoServiceProvider privateKey = null; - if (!certName.IsNullOrEmpty()) - privateKey = ApiAuthorization.PrivateKeyFromP12(GetLocalPath(privateCertPath), passphrase); - var result = ApiAuthorization.Token(realm, authPrefix, httpMethod, signatureURL, appId, secret, apiList, privateKey, nonce, timeStamp, version); - if(timeStamp.Equals("%s") || nonce.Equals("%s")) + [Test(), TestCaseSource(nameof(GetJsonFile), new object[] { "getSignatureToken.json" })] + public void GetToken(TestParam testCase) + { + SetDetaultParams(testCase); + + if (SkipTest) + { + Assert.Ignore(); + } + else + { + RSACryptoServiceProvider privateKey = null; + if (!testCase.ApiParam.PrivateKeyFileName.IsNullOrEmpty()) + { + privateKey = ApiAuthorization.GetPrivateKey(Path.Combine(testCertPath, testCase.ApiParam.PrivateKeyFileName), Passphrase); + } + + AuthParam authParam = new AuthParam + { + url = SignatureURL, + httpMethod = HttpMethod, + appName = AppId, + appSecret = Secret, + formData = FormData, + privateKey = privateKey, + timestamp = TimeStamp, + nonce = Nonce + }; + + if (testCase.ErrorTest) + { + //Assert.Fail("No exception defined for test case..."); + + Exception ex; + switch (testCase.Exception) + { + case "ArgumentNullException": + { + ex = GetTokenException(authParam); + break; + } + default: { - Match m = Regex.Match(result, "apex_(l[12])_([ei]g)_signature=(\\S+)", System.Text.RegularExpressions.RegexOptions.IgnoreCase); - String signature_value = ""; - if (m.Success) - signature_value = m.Groups[3].Value; - - expectedResult = expectedResult.Replace("signature=\"%s\"", "signature=" + signature_value); - } - Assert.AreEqual(expectedResult, result); - actualPass++; - } - catch (Exception ex) - { - Assert.AreEqual(expectedResult, ex.Message); - if (errorTest) - actualPass++; - } - } - else - { - actualPass++; - } - } + throw new Exception($"Exception ({testCase.Exception}) not defined in json, please update the test case."); + } + }; + ValidateErrorMessage(testCase, ex); + } + else + { + string result = ApiAuthorization.TokenV2(authParam).Token; + + var expectedResult = ExpectedResult; + if (string.IsNullOrEmpty(TimeStamp) || string.IsNullOrEmpty(Nonce)) + { + if (string.IsNullOrEmpty(TimeStamp)) + { + expectedResult = expectedResult.Replace("timestamp=\"%s\"", "timestamp=\"" + authParam.timestamp + "\""); + } + + if (string.IsNullOrEmpty(Nonce)) + { + expectedResult = expectedResult.Replace("nonce=\"%s\"", "nonce=\"" + authParam.nonce + "\""); + } + + Match m = Regex.Match(result, "apex_(l[12])_([ei]g)_signature=(\\S+)", RegexOptions.IgnoreCase); + string signature_value = ""; + if (m.Success) + { + signature_value = m.Groups[3].Value; + } + + expectedResult = expectedResult.Replace("signature=\"%s\"", "signature=" + signature_value); + } + + Assert.AreEqual(expectedResult, result, testCase.ToString()); + } } - catch (Exception ex) - { - throw ex; - } - - Assert.AreEqual(expectedPass, actualPass); - } - - [Test()] - public void GetL1Signature() - { - var jsonData = GetJsonFile("getL1Signature.json"); - int expectedPass = jsonData.Count(); - int actualPass = 0; + } + + private Exception GetL1SignatureException(TestParam testCase) where T : Exception + { + return Assert.Throws(() => testCase.Message.L1Signature(testCase.ApiParam.Secret)); + } - try - { - foreach (var test in jsonData) - { - SetDetaultParams(test); + [Test(), TestCaseSource(nameof(GetJsonFile), new object[] { "getL1Signature.json" })] + public void GetL1Signature(TestParam testCase) + { + SetDetaultParams(testCase); + if (SkipTest) + { + Assert.Ignore(); + } + else + { + if (testCase.ErrorTest) + { + //Assert.Fail("No exception defined for test case..."); + + Exception ex; + switch(testCase.Exception) + { + case "ArgumentException": + { + ex = GetL1SignatureException(testCase); + break; + } + case "ArgumentNullException": + { + ex = GetL1SignatureException(testCase); + break; + } + default: + { + throw new Exception($"Exception ({testCase.Exception}) not defined in json, please update the test case."); + } + }; + ValidateErrorMessage(testCase, ex); + } + else + { + string message = testCase.Message; + string result = message.L1Signature(Secret); + Assert.AreEqual(ExpectedResult, result, testCase.ToString()); + } + } + } + + private Exception GetL2SignatureException(TestParam testCase, RSACryptoServiceProvider privateKey) where T : Exception + { + return Assert.Throws(() => testCase.Message.L2Signature(privateKey)); + } - if (skipTest == null || !skipTest.Contains("c#")) - { - var message = test.message; - try - { - var result = message.L1Signature(secret); - Assert.AreEqual(expectedResult, result); - actualPass++; - } - catch (Exception ex) - { - Assert.AreEqual(expectedResult, ex.Message); - if (errorTest) - actualPass++; - } + [Test(), TestCaseSource(nameof(GetJsonFile), new object[] { "getL2Signature.json" })] + public void GetL2Signature(TestParam testCase) + { + SetDetaultParams(testCase); + + if (SkipTest) + { + Assert.Ignore(); + } + else + { + RSACryptoServiceProvider privateKey = null; + if (!testCase.ApiParam.PrivateKeyFileName.IsNullOrEmpty()) + { + privateKey = ApiAuthorization.GetPrivateKey(Path.Combine(testCertPath, testCase.ApiParam.PrivateKeyFileName), Passphrase); + } + + if (testCase.ErrorTest) + { + //Assert.Fail("No exception defined for test case..."); + + Exception ex; + switch (testCase.Exception) + { + case "ArgumentException": + { + ex = GetL2SignatureException(testCase, privateKey); + break; + } + case "ArgumentNullException": + { + ex = GetL2SignatureException(testCase, privateKey); + break; + } + default: + { + throw new Exception($"Exception ({testCase.Exception}) not defined in json, please update the test case."); + } + }; + ValidateErrorMessage(testCase, ex); + } + else + { + string result = testCase.Message.L2Signature(privateKey); + + Assert.AreEqual(ExpectedResult, result, testCase.ToString()); + } + } + } + + private Exception GetPrivateKeyException(string keyFileName, string passphrase) where T : Exception + { + return Assert.Throws(() => ApiAuthorization.GetPrivateKey(keyFileName, passphrase)); + } + + private Exception GetPublicKeyException(string keyFileName, string passphrase) where T : Exception + { + return Assert.Throws(() => ApiAuthorization.GetPublicKey(keyFileName, passphrase)); + } + + [Test, TestCaseSource(nameof(GetJsonFile), new object[] { "verifySupportedKeyFileType.json" })] + public void VerifySupportedKeyFileType(TestParam testCase) + { + SetDetaultParams(testCase); + + if (SkipTest) + { + Assert.Ignore(); + } + else + { + if (testCase.ErrorTest) + { + Exception ex = null; + if (testCase.ApiParam != null && testCase.ApiParam.PrivateKeyFileName != null) + { + string keyFileName = $"{testCertPath}/{testCase.ApiParam.PrivateKeyFileName}"; + string passphrase = testCase.ApiParam.Passphrase; + + switch (testCase.Exception) + { + case "ArgumentException": + { + ex = GetPrivateKeyException(keyFileName, passphrase); + break; + } + case "CryptographicException": + { + ex = GetPrivateKeyException(keyFileName, passphrase); + break; + } + case "FileNotFoundException": + { + ex = GetPrivateKeyException(keyFileName, passphrase); + break; + } + case "PemException": + { + ex = GetPrivateKeyException(keyFileName, passphrase); + break; + } + case "InvalidCastException": + { + ex = GetPrivateKeyException(keyFileName, passphrase); + break; + } + default: + throw new Exception($"Exception ({testCase.Exception}) not defined in json, please update the test case."); + } + ValidateErrorMessage(testCase, ex); } - else - { - actualPass++; - } - } - } - catch (Exception ex) - { - throw ex; - } - - Assert.AreEqual(expectedPass, actualPass); - + + if (testCase.PublicKeyFileName != null) + { + string keyFileName = $"{testCertPath}/{testCase.PublicKeyFileName}"; + string passphrase = testCase.Passphrase; + + switch (testCase.Exception) + { + case "FileNotFoundException": + { + ex = GetPublicKeyException(keyFileName, passphrase); + break; + } + case "CryptographicException": + { + ex = GetPublicKeyException(keyFileName, passphrase); + break; + } + default: + throw new Exception($"Exception ({testCase.Exception}) not defined in json, please update the test case."); + }; + } + + ValidateErrorMessage(testCase, ex); + } + else + { + RSACryptoServiceProvider privateKey = ApiAuthorization.GetPrivateKey($"{testCertPath}/{testCase.ApiParam.PrivateKeyFileName}", testCase.ApiParam.Passphrase); + string signature = testCase.Message.L2Signature(privateKey); + + RSACryptoServiceProvider publicKey = ApiAuthorization.GetPublicKey($"{testCertPath}/{testCase.PublicKeyFileName}", testCase.Passphrase); + bool result = signature.VerifyL2Signature(publicKey, testCase.Message); + + Assert.AreEqual(testCase.ApiParam.Signature, signature, "Signature - {0} - {1}", testCase.Id, testCase.Description); + Assert.IsTrue(result, "{0} - {1}", testCase.Id, testCase.Description); + } + } } - - [Test()] - public void GetL2Signature() - { - var jsonData = GetJsonFile("getL2Signature.json"); - int expectedPass = jsonData.Count(); - int actualPass = 0; - - try - { - foreach (var test in jsonData) - { - SetDetaultParams(test); - - if (skipTest == null || !skipTest.Contains("c#")) - { - try - { - var message = test.message; - string certName = test.apiParam.privateCertFileName; - string privateCertPath = testCertPath + certName; - - string result = null; - if (!certName.IsNullOrEmpty()) - result = ApiAuthorization.GetL2SignatureFromPEM(privateCertPath,message, passphrase); - - Assert.AreEqual(expectedResult, result); - actualPass++; - } - catch (Exception ex) - { - Assert.AreEqual(expectedResult, ex.Message); - if (errorTest) - actualPass++; - } - } - else - { - actualPass++; - } - } - } - catch (Exception ex) - { - throw ex; - } - - Assert.AreEqual(expectedPass, actualPass); - - } - } + } } diff --git a/ApiUtilLibTest/TestParam.cs b/ApiUtilLibTest/TestParam.cs index 4b3ec19..75d32c4 100644 --- a/ApiUtilLibTest/TestParam.cs +++ b/ApiUtilLibTest/TestParam.cs @@ -1,73 +1,87 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; +using System.Linq; + namespace ApexUtilLibTest { public class TestParam { - public string id { get; set; } - - public string description { get; set; } + public string Id { get; set; } - public APIParam apiParam { get; set; } + public string Description { get; set; } - public string publicCertFileName { get; set; } + public APIParam ApiParam { get; set; } - public string[] skipTest { get; set; } + public string PublicKeyFileName { get; set; } + public string Passphrase { get; set; } - public string message { get; set; } + public string[] SkipTest { get; set; } + internal bool Skip => SkipTest != null && SkipTest.Contains("c#"); - public string debug { get; set; } - public string testTag { get; set; } + public string Message { get; set; } - public bool errorTest { get; set; } + public string Debug { get; set; } - public Dictionary expectedResult { get; set; } - } + public string TestTag { get; set; } - public class APIParam - { - public string realm{ get; set; } + public bool ErrorTest { get; set; } - public string appID { get; set; } + public Dictionary ExceptionType { get; set; } + public string Exception => ExceptionType != null ? ExceptionType.FirstOrDefault(c => c.Key == "c#").Value ?? ExceptionType.FirstOrDefault(c => c.Key == "default").Value : ""; - public string authPrefix { get; set; } + public Dictionary ExpectedResult { get; set; } + public string Result + { + get + { + string result = ExpectedResult.FirstOrDefault(c => c.Key == "c#").Value; + if (result == null) + { + result = ExpectedResult.FirstOrDefault(c => c.Key == "default").Value; + } - public string secret { get; set; } + return result; + } + } - public string invokeURL { get; set; } + public override string ToString() + { + return Skip ? $"{Id} - {Description} >>> {Result} <<<" : $"{Id} - {Description}"; + } + } - public string signatureURL { get; set; } + public class APIParam + { + public string Realm{ get; set; } - public string httpMethod { get; set; } + public string AppID { get; set; } - public string signature { get; set; } + public string AuthPrefix { get; set; } - public string privateCertFileName { get; set; } + public string Secret { get; set; } - public string privateCertFileNameP12 { get; set; } + public string InvokeURL { get; set; } - public string passphrase { get; set; } + public string SignatureURL { get; set; } - public string signatureMethod { get; set; } + public string HttpMethod { get; set; } - public string nonce { get; set; } + public string Signature { get; set; } - public string timestamp { get; set; } + public string PrivateKeyFileName { get; set; } + public string Passphrase { get; set; } - public string version { get; set; } + public string SignatureMethod { get; set; } - public Dictionary queryString { get; set; } + public string Nonce { get; set; } - public Dictionary formData { get; set; } + public string Timestamp { get; set; } - } + public string Version { get; set; } - public class ExpectedResult - { - public string golang { get; set; } - public string nodejs { get; set; } - } + public Dictionary QueryString { get; set; } + public Dictionary FormData { get; set; } + } } diff --git a/ApiUtilLibTest/addLinks.sh b/ApiUtilLibTest/addLinks.sh new file mode 100644 index 0000000..186fbb0 --- /dev/null +++ b/ApiUtilLibTest/addLinks.sh @@ -0,0 +1,23 @@ +#!/bin/bash +# Proper header for a Bash script. + +# dependency: must clone test-suites-apex-api-security +# at the save level as csharp-apex2-net5-skd + +CREATE_LINK=false + +cd bin/Debug +mkdir -p linkFolder +cd linkFolder + +FOLDER=../../../../../test-suites-apex-api-security + +FOLDER_NAME=testData +[ -L "${FOLDER_NAME}" ] && unlink ${FOLDER_NAME} && echo "Unlink folder - ${FOLDER_NAME}" +if [ ${CREATE_LINK} = "true" ]; then ln -s ${FOLDER}/${FOLDER_NAME} ${FOLDER_NAME} && echo ">>>Add link folder - ${FOLDER_NAME}"; fi + +FOLDER_NAME=cert +[ -L "${FOLDER_NAME}" ] && unlink ${FOLDER_NAME} && echo "Unlink folder - ${FOLDER_NAME}" +if [ ${CREATE_LINK} = "true" ]; then ln -s ${FOLDER}/${FOLDER_NAME} ${FOLDER_NAME} && echo ">>>Add link folder - ${FOLDER_NAME}"; fi + +cd ../../../ \ No newline at end of file diff --git a/ApiUtilLibTest/packages.config b/ApiUtilLibTest/packages.config index 00c9ff6..8ff1025 100644 --- a/ApiUtilLibTest/packages.config +++ b/ApiUtilLibTest/packages.config @@ -1,5 +1,6 @@  - - + + + \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 96c05df..5bc7ed1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,18 @@ All notable changes to this project will be documented in this file. This project adheres to Semantic Versioning. +### V2.0.1-SNAPSHOT ++ 2021-11-22 - Remove Test Harness + + +### V2.0.1-SNAPSHOT ++ 2021-08-08 - Restructure PrivateKey and PublicKey API. + - Update Test Cases + +### V2.0-SNAPSHOT ++ 2021-07-16 - Fix bugs related to form post with array. + - Add APEX2 supports and code refactors + ### V1.6-SNAPSHOT + 2019-03-22 - Update ApiAuthorization class to support non-standard http port diff --git a/README.md b/README.md index 063dab4..83e4d68 100644 --- a/README.md +++ b/README.md @@ -3,16 +3,19 @@ [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)](http://makeapullrequest.com) [![MIT License](https://img.shields.io/badge/License-MIT-blue.svg)](https://github.com/GovTechSG/csharp-apex-api-security/blob/master/LICENSE) -A C# helper utility that construct and sign HTTP Authorization header scheme for API authentication and verification +A C# helper utility that construct and sign HTTP Authorization header scheme for API authentication and verification. -## Table of Contents +## Table of Contents (version 2.0 - beta (2021-08-08 v2.0.1)) - [Getting Started](#getting-started) * [Prerequisites](#prerequisites) - * [APIList Interface](#using-the-apilist-class) + * [Query String and FormData Class](#using-the-querydata-and-formdata-class) + [Generate QueryString](#generate-querystring) + [Generate FormData](#generate-formdata) - * [Constructing HMAC256 L1 Authorization Header](#how-to-generate-hmac256-l1-authorization-header) - * [Constructing RSA256 L2 Authorization Header](#how-to-generate-rsa256-l2-authorization-header) + * [Constructing L1 Authorization Header](#how-to-generate-l1-authorization-header) + * [Supported Private Key File Type](#supported-private-key-file-type) + * [Constructing L2 Authorization Header](#how-to-generate-l2-authorization-header) + * [Cross Zone API from Internet to Intranet](#how-to-generate-l21-authorization-header) + * [Cross Zone API from Intranet to Internet](#how-to-generate-l12-authorization-header) - [Release](#release) - [Contributing](#contributing) - [License](#license) @@ -22,8 +25,8 @@ A C# helper utility that construct and sign HTTP Authorization header scheme for ### Prerequisites + .NET Framework 4.6.1 -+ Visual Studio IDE 2015/2017 Community+ -+ NUnit Framework 3.8+ (for Windows Platform) ++ Visual Studio 2019 Community ++ NUnit Framework 3.13+ Make sure that all unit test cases are passed before using the library. @@ -40,32 +43,32 @@ For windows users , NUnitTestAdapter have to be installed before you can run the 4. Click install, and select existing project ApiSecuritySolution to add the adapter. -### Using the ApiList Class -The ApiUtilLib Library provide the utility class ApiList to construct request Query String and Form Data. +### Using the QueryData and FormData Class +The ApiUtilLib Library provide the utility class QueryData to construct request Query String and Form Data. #### Generate QueryString ``` - var queryParam = new ApiUtilLib.ApiList(); + var queryData = new QueryData(); - queryParam.Add("clientId", "1256-1231-4598"); - queryParam.Add("accountStatus", "active"); - queryParam.Add("txnDate", "2017-09-29"); + queryData.Add("clientId", "1256-1231-4598"); + queryData.Add("accountStatus", "active"); + queryData.Add("txnDate", "2017-09-29"); - string queryString = queryParam.ToQueryString(); + string queryString = queryData.ToString(); - string baseUrl = string.Format("https://example.com/resource/?{0}", queryString); - // https://example.com/resource/?clientId=1256-1231-4598&accountStatus=active&txnDate=2017-09-29 + string baseUrl = string.Format("https://example.com/resource{0}", queryString); + // https://example.com/resource?clientId=1256-1231-4598&accountStatus=active&txnDate=2017-09-29 ``` #### Generate FormData ``` - var formData = new ApiUtilLib.ApiList(); + var formData = new FormData(); formData.Add("phoneNo", "+1 1234 4567 890"); formData.Add("street", "Hellowood Street"); formData.Add("state", "AP"); - string formData = formData.ToFormData(); + string formData = formData.ToString(); // phoneNo=%2B1+1234+4567+890&street=Hellowood+Street&state=AP ``` @@ -74,162 +77,203 @@ The ApiUtilLib Library provide the utility class ApiList to construct request Qu For **formData** parameter used for Signature generation, the key value parameters **do not** need to be URL encoded, When you use this client library method **ApiAuthorization.HttpRequest**, it will do the url-encoding during the HTTP call -### How to Generate HMAC256 L1 Authorization Header +### How to Generate L1 Authorization Header ``` -public static void L1Sample() +public void L1Sample() { - // application realm - string realm = "<>"; + var URL = "https://{gatewayName}.api.gov.sg/api/v1/resource"; + var APP_NAME = "{appName}"; + var APP_SECRET = "{appSecret}"; - // authorization prefix (i.e 'Apex_l1_eg' ) - string authPrefix = "<>"; + // prepare form data + var formData = new FormData(); + formData.Add("q", "how to validate signature in pdf"); + formData.Add("ei", "yAr8YLmwCM_Fz7sPsKmLoAU"); - // app id and app secret assign to the application - string appId = "<>"; - string appSecret = "<>"; + var authParam = new AuthParam() + { + url = new Uri($"{URL}"), + httpMethod = HttpMethod.POST, - // api signing Internet gateway name and path (for Intranet i.e -pvt.i.api.gov.sg) - string signingGateway = ".e.api.gov.sg"; - string apiPath = "api/v1/l1/"; + appName = APP_NAME, + appSecret = APP_SECRET, - // query string (optional) - var queryParam = new ApiUtilLib.ApiList(); + formData = formData + }; - queryParam.Add("clientId", "1256-1231-4598"); - queryParam.Add("accountStatus", "active"); - queryParam.Add("txnDate", "2017-09-29"); + // get the authorization token for L1 + var authToken = ApiAuthorization.TokenV2(authParam); - string queryString = queryParam.ToQueryString(); + Console.WriteLine($"\n>>> BaseString :: '{authToken.BaseString}'<<<"); + Console.WriteLine($"\n>>> Authorization Token :: '{authToken.Token}'<<<"); - string baseUrl = string.Format("https://{0}/{1}?{2}", signingGateway, apiPath, queryString); + // make api call with authToken.Token +} +``` - // form data (optional) - var formData = new ApiUtilLib.ApiList(); +### Supported Private Key File Type +1. .pem/.key - pkcs#1 base64 encoded text file +2. .pem/.key - pkcs#8 base64 encoded text file +3. .p12/.pfx - pkcs#12 key store - formData.Add("phoneNo", "+1 1234 4567 890"); - formData.Add("street", "Hellowood Street"); - formData.Add("state", "AP"); +### How to Generate L2 Authorization Header +``` +public void L2Sample() +{ + var URL = "https://{gatewayName}.api.gov.sg/api/v1/resource"; + var APP_NAME = "{appName}"; + var PRIVATE_KEY_FILE_NAME = "privateKey.key"; + var PRIVATE_KEY_PASSPHRASE = "{passphrase}"; + + // get the private key from pem file (in pkcs1 format) + var privateKey = ApiAuthorization.GetPrivateKey(PRIVATE_KEY_FILE_NAME, PRIVATE_KEY_PASSPHRASE); + + // prepare queryString + var queryData = new QueryData(); + queryData.Add("view", "net-5.0"); + queryData.Add("system", "C# sample code"); + + // get url safe querystring from ToString() + Console.WriteLine($">>> Query String >>>{queryData.ToString()}<<<"); + + // prepare form data + var formData = new FormData(); + formData.Add("name", "peter pan"); + formData.Add("age", "12"); + + var authParam = new AuthParam() + { + url = new Uri($"{URL}{queryData.ToString()}"), + httpMethod = HttpMethod.POST, - // authorization header - var authorizationHeader = ApiAuthorization.Token(realm, authPrefix, HttpMethod.POST, new Uri(baseUrl), appId, appSecret, formData); + appName = APP_NAME, + privateKey = privateKey, - Console.WriteLine("\n>>>Authorization Header :: '{0}'<<<", authorizationHeader); + formData = formData + }; - // no need append .e on the target Internet gateway name (for Intranet i.e -pvt.api.gov.sg) - string targetGatewayName = ".api.gov.sg"; - string targetBaseUrl = string.Format("https://{0}/{1}?{2}", targetGatewayName, apiPath, queryString); + // get the authorization token for L1 + var authToken = ApiAuthorization.TokenV2(authParam); - // this method only for verification only - // expecting result to be 200 - var result = ApiAuthorization.HttpRequest(new Uri(targetBaseUrl), authorizationHeader, formData, HttpMethod.POST, ignoreServerCert: true); + Console.WriteLine($"\n>>> BaseString :: '{authToken.BaseString}'<<<"); + Console.WriteLine($"\n>>> Authorization Token :: '{authToken.Token}'<<<"); + + // make api call with authToken.Token } ``` -### How to Generate RSA256 L2 Authorization Header +### How to Generate L21 Authorization Header +(for cross zone api from internet to intranet) ``` -public static void L2Sample() +public void L21Sample() { - // application realm - string realm = "<>"; - - // authorization prefix (i.e 'Apex_l2_eg' ) - string authPrefix = "<>"; + var URL_WWW = "https://{www_gatewayName}.api.gov.sg/api/v1/resource"; + var APP_NAME_WWW = "www_appName"; + var PRIVATE_KEY_FILE_NAME = "www_privateKey.key"); + var PRIVATE_KEY_PASSPHRASE = "{password}"; - // app id i.e 'Apex_l2_eg' assign to the application - string appId = "<>"; + var URL_WOG = "https://{wog_gatewayName}.api.gov.sg/api/v1/resource"; + var APP_NAME_WOG = "{wog_AppName}"; + var APP_SECRET_WOG = "{wog_appSecret}"; - // api signing gateway name and path (for Intranet i.e -pvt.i.api.gov.sg) - string signingGateway = ".e.api.gov.sg"; - string apiPath = "api/v1/l2/"; + // get the private key from pem file (in pkcs1 format) + var privateKey = ApiAuthorization.GetPrivateKey(PRIVATE_KEY_FILE_NAME, PRIVATE_KEY_PASSPHRASE); - // query string (optional) - var queryParam = new ApiUtilLib.ApiList(); + // prepare queryString + var queryData = new QueryData(); + queryData.Add("view", "net-5.0"); + queryData.Add("system", "C# sample code"); - queryParam.Add("clientId", "1256-1231-4598"); - queryParam.Add("accountStatus", "active"); - queryParam.Add("txnDate", "2017-09-29"); + // prepare form data + var formData = new FormData(); + formData.Add("name", "peter pan"); + formData.Add("age", "12"); - string queryString = queryParam.ToQueryString(); + // prepare the parameters + var authParam = new AuthParam() + { + url = new Uri($"{URL_WWW}{queryData.ToString()}"), + httpMethod = HttpMethod.POST, - string baseUrl = string.Format("https://{0}/{1}?{2}", signingGateway, apiPath, queryString); + appName = APP_NAME_WWW, + privateKey = privateKey, - // form data (optional) - var formData = new ApiUtilLib.ApiList(); + formData = formData, - formData.Add("phoneNo", "+1 1234 4567 890"); - formData.Add("street", "Hellowood Street"); - formData.Add("state", "AP"); + nextHop = new AuthParam() + { + url = new Uri($"{URL_WOG}{queryData.ToString()}"), - // private cert file and password - string privateCertName = GetLocalPath("Certificates/alpha.example.api.com.p12"); - string password = "password"; + appName = APP_NAME_WOG, + appSecret = APP_SECRET_WOG + } + }; - // get the private key from cert - var privateKey = ApiAuthorization.PrivateKeyFromP12(privateCertName, password); - - // authorization header - var authorizationHeader = ApiAuthorization.Token(realm, authPrefix, HttpMethod.POST, new Uri(baseUrl), appId, null, formData, privateKey); + // get the authorization token for L21 + var authToken = ApiAuthorization.TokenV2(authParam); - // no need append .e on the target gateway name (for Intranet i.e -pvt.api.gov.sg) - string targetGatewayName = ".api.gov.sg"; - string targetBaseUrl = string.Format("https://{0}/{1}?{2}", targetGatewayName, apiPath, queryString); + Console.WriteLine($"\n>>>{tag}<<< BaseString :: '{authToken.BaseString}'<<<"); + Console.WriteLine($"\n>>>{tag}<<< Authorization Token :: '{authToken.Token}'<<<"); - // this method only for verification only - // expecting result to be 200 - var result = ApiAuthorization.HttpRequest(new Uri(targetBaseUrl), authorizationHeader, formData, HttpMethod.POST, ignoreServerCert: true); + // make api call with authToken.Token } -static string GetLocalPath(string relativeFileName) -{ - var localPath = Path.Combine(Path.GetDirectoryName(new Uri(Assembly.GetExecutingAssembly().CodeBase).LocalPath), relativeFileName.Replace('/', Path.DirectorySeparatorChar)); - - return localPath; -} ``` -#### Sample HTTP POST Call for x-www-form-urlencoded with APEX L1 Security (for reference only) +### How to Generate L12 Authorization Header +(for cross zone api from intranet to internet) ``` -[Test] -public static void Http_Call_Test() +public void L12Sample() { - // application realm - string realm = "http://example.api.test/token"; + var URL_WOG = "https://{wog_gatewayName}.api.gov.sg/api/v1/reslource"; + var APP_NAME_WOG = "{wog_appName}"; + var APP_SECRET_WOG = "{wog_AppSecret}"; + + var URL_WWW = "https://{www_appName}.api.gov.sg/api/v1/resource"; + var APP_NAME_WWW = "{www_AppName}"; + var PRIVATE_KEY_FILE_NAME = "Certificates/www_privateKey.pkcs8"); + var PRIVATE_KEY_PASSPHRASE = "{passphrase}"; + + // get the private key from pem file (in pkcs8 format) + var privateKey = ApiAuthorization.GetPrivateKey(PRIVATE_KEY_FILE_NAME, PRIVATE_KEY_PASSPHRASE); + + // prepare queryString + var queryData = new QueryData(); + queryData.Add("view", "net-5.0"); + queryData.Add("system", "C# sample code"); - // authorization prefix - string authPrefix = "Apex_l1_eg"; + // prepare form data + var formData = new FormData(); + formData.Add("name", "peter pan"); + formData.Add("age", "12"); - // app id and app secret assign to the application - string appId = "tenant-1X2w7NQPzjO2azDu904XI5AE"; - string appSecret = "s0m3s3cr3t"; - var formData = new ApiUtilLib.ApiList(); + // prepare the token parameters + var authParam = new AuthParam() + { + url = new Uri($"{URL_WOG}{queryData.ToString()}"), + httpMethod = HttpMethod.POST, - formData.Add("key1", "value1); - formData.Add("key2", "value2"); - - // api signing gateway name and path - string gatewayName = "https://tenant.e.api.gov.sg"; - string apiPath = "api14021live/resource"; - string baseUrl = string.Format("{0}/{1}", gatewayName, apiPath); - Console.WriteLine("\n>>>baseUrl :: '{0}'<<<", baseUrl); - Console.WriteLine("\n>>>appId :: '{0}'<<<", appId); - Console.WriteLine("\n>>>appSecret :: '{0}'<<<", appSecret); - // authorization header - var authorizationHeader = ApiAuthorization.Token(realm, authPrefix, HttpMethod.POST, new Uri(baseUrl), appId, appSecret, formData); + appName = APP_NAME_WOG, + appSecret = APP_SECRET_WOG, - Console.WriteLine("\n>>>Authorization Header :: '{0}'<<<", authorizationHeader); + formData = formData, - // if the target gateway name is different from signing gateway name - string targetBaseUrl = "https://tenant.api.gov.sg/api14021live/resource"; + nextHop = new AuthParam() + { + url = new Uri($"{URL_WWW}{queryData.ToString()}"), + appName = APP_NAME_WWW, + privateKey = privateKey, + } + }; - // this method only for verification only - // expecting result to be 200 + // get the authorization token + var authToken = ApiAuthorization.TokenV2(authParam); - var result = ApiAuthorization.HttpRequest(new Uri(targetBaseUrl), authorizationHeader, formData, HttpMethod.POST, ignoreServerCert: true); - Console.WriteLine(result); - Console.ReadLine(); + Console.WriteLine($"\n>>> BaseString :: '{authToken.BaseString}'<<<"); + Console.WriteLine($"\n>>> Authorization Token :: '{authToken.Token}'<<<"); - Assert.True(true); + // make api call with authToken.Token } ```