using System; using System.Diagnostics; using System.Globalization; using System.IO; using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; using System.Text; using System.Web; using Jose; using Kreta.Resources; namespace Kreta.Core.Logic { public static class UrlLogic { public static string Encrypt(string token, string key) { if (string.IsNullOrWhiteSpace(token)) { throw new ArgumentException(nameof(token)); } byte[] encryptedData; using (SymmetricAlgorithm symmetricAlgorithm = Aes.Create()) { using (var memoryStream = new MemoryStream()) { ICryptoTransform encryptor = symmetricAlgorithm.CreateEncryptor(Encoding.UTF8.GetBytes(key), new byte[16]); using (var cryptoStream = new CryptoStream(memoryStream, encryptor, CryptoStreamMode.Write)) { using (var streamWriter = new StreamWriter(cryptoStream)) { streamWriter.Write(token); } encryptedData = memoryStream.ToArray(); } } } string result = WebEncoders.Base64UrlEncode(encryptedData); return result; } public static string Decrypt(string encryptData, string key) { if (string.IsNullOrWhiteSpace(encryptData)) { throw new ArgumentException(nameof(encryptData)); } string decryptedData; byte[] encryptDataBytes = WebEncoders.Base64UrlDecode(encryptData); using (SymmetricAlgorithm symmetricAlgorithm = Aes.Create()) { using (var memoryStream = new MemoryStream(encryptDataBytes)) { ICryptoTransform encryptor = symmetricAlgorithm.CreateDecryptor(Encoding.UTF8.GetBytes(key), new byte[16]); using (var cryptoStream = new CryptoStream(memoryStream, encryptor, CryptoStreamMode.Read)) { using (var streamReader = new StreamReader(cryptoStream)) { decryptedData = streamReader.ReadToEnd(); } } } } return decryptedData; } /// /// A JweEncrypt egy jose-jwt nevű nuget-et használ és a JSON Web Encryption (JWE) formátumra enrypt-álja a bejövő objektumot. /// A JWE leírása: https://tools.ietf.org/html/rfc7516 /// A jose-jwt leírása: https://github.com/dvsekhvalnov/jose-jwt /// Jelenleg az MKB bankos integrációhoz(tanuló bankszámlaigénylés) szükséges, ők ezt a fajta titkosítást használják. /// /// A publikus kulcs elérési útvonala. /// A publikus kulcshoz tartozó jelszó(nem kötelező). /// Az objektum, amit enkódol a megfelelő formátumra a jose-jwt nuget. /// public static string JweEncrypt(string publicKeyFileName, string publicKeyFilePassword, object payload) { if (!File.Exists(publicKeyFileName)) { throw new FileNotFoundException(publicKeyFileName); } if (payload == null) { throw new ArgumentException(nameof(payload)); } RSACryptoServiceProvider key = string.IsNullOrWhiteSpace(publicKeyFilePassword) ? new X509Certificate2(publicKeyFileName).PublicKey.Key as RSACryptoServiceProvider : new X509Certificate2(publicKeyFileName, publicKeyFilePassword).PublicKey.Key as RSACryptoServiceProvider; string result = JWT.Encode(payload, key, JweAlgorithm.RSA_OAEP, JweEncryption.A256GCM); return result; } /// /// A JweDecrypt egy jose-jwt nevű nuget-et használ és a JSON Web Encryption (JWE) formátumú token-t decrypt-álja string-é, akár json formátumban. /// A JWE leírása: https://tools.ietf.org/html/rfc7516 /// A jose-jwt leírása: https://github.com/dvsekhvalnov/jose-jwt /// Jelenleg az MKB bankos integrációhoz(tanuló bankszámlaigénylés) szükséges, ők ezt a fajta titkosítást használják. /// /// A privát kulcs elérési útvonala. /// A privát kulcshoz tartozó jelszó(nem kötelező). /// A token, amit dekódol string-é a jose-jwt nuget. /// Ha nem json objektumról van szó, akkor a dekódolás benne hagy escape karaktereket(\"), pl. Guid-nál és ezeket kiszedjük, hogy egyszerűbb legyen a típuskoncerzió. /// public static string JweDecrypt(string privateKeyFileName, string privateKeyFilePassword, string token, bool isJsonObject = false) { if (!File.Exists(privateKeyFileName)) { throw new FileNotFoundException(privateKeyFileName); } if (string.IsNullOrWhiteSpace(token)) { throw new ArgumentException(nameof(token)); } RSACryptoServiceProvider key = string.IsNullOrWhiteSpace(privateKeyFilePassword) ? new X509Certificate2(privateKeyFileName).PrivateKey as RSACryptoServiceProvider : new X509Certificate2(privateKeyFileName, privateKeyFilePassword).PrivateKey as RSACryptoServiceProvider; string result = JWT.Decode(token, key); if (!isJsonObject) { result = result.Replace("\"", string.Empty); } return result; } public static string GetEmailKezelesUrl(string intezmenyAzonosito = null, Guid? guid = null) { string url = null; if (!string.IsNullOrWhiteSpace(intezmenyAzonosito) && guid.HasValue) { var data = Encrypt(guid.ToString(), Constants.EncryptionKey); if (!string.IsNullOrWhiteSpace(data)) { var builder = new UriBuilder(string.Format(CommonResource.IntezmenyUrl, intezmenyAzonosito)) { Path = "/Adminisztracio/EmailKezeles", Query = $"data={HttpUtility.UrlEncode(data)}", Scheme = "https" }; url = builder.Uri.ToString(); } } return url; } } // https://github.com/aspnet/Extensions/blob/5c3ee37fb773db04a1aea6b67e49395e84fda223/shared/Microsoft.Extensions.WebEncoders.Sources/Properties/EncoderResources.cs internal static class WebEncoders { private static readonly byte[] EmptyBytes = new byte[0]; /// /// Decodes a base64url-encoded string. /// /// The base64url-encoded input to decode. /// The base64url-decoded form of the input. /// /// The input must not contain any whitespace or padding characters. /// Throws if the input is malformed. /// public static byte[] Base64UrlDecode(string input) { if (input == null) { throw new ArgumentNullException(nameof(input)); } return Base64UrlDecode(input, offset: 0, count: input.Length); } /// /// Decodes a base64url-encoded substring of a given string. /// /// A string containing the base64url-encoded input to decode. /// The position in at which decoding should begin. /// The number of characters in to decode. /// The base64url-decoded form of the input. /// /// The input must not contain any whitespace or padding characters. /// Throws if the input is malformed. /// public static byte[] Base64UrlDecode(string input, int offset, int count) { if (input == null) { throw new ArgumentNullException(nameof(input)); } ValidateParameters(input.Length, nameof(input), offset, count); // Special-case empty input if (count == 0) { return EmptyBytes; } // Create array large enough for the Base64 characters, not just shorter Base64-URL-encoded form. var buffer = new char[GetArraySizeRequiredToDecode(count)]; return Base64UrlDecode(input, offset, buffer, bufferOffset: 0, count: count); } /// /// Decodes a base64url-encoded into a byte[]. /// /// A string containing the base64url-encoded input to decode. /// The position in at which decoding should begin. /// /// Scratch buffer to hold the s to decode. Array must be large enough to hold /// and characters as well as Base64 padding /// characters. Content is not preserved. /// /// /// The offset into at which to begin writing the s to decode. /// /// The number of characters in to decode. /// The base64url-decoded form of the . /// /// The input must not contain any whitespace or padding characters. /// Throws if the input is malformed. /// public static byte[] Base64UrlDecode(string input, int offset, char[] buffer, int bufferOffset, int count) { if (input == null) { throw new ArgumentNullException(nameof(input)); } if (buffer == null) { throw new ArgumentNullException(nameof(buffer)); } ValidateParameters(input.Length, nameof(input), offset, count); if (bufferOffset < 0) { throw new ArgumentOutOfRangeException(nameof(bufferOffset)); } if (count == 0) { return EmptyBytes; } // Assumption: input is base64url encoded without padding and contains no whitespace. var paddingCharsToAdd = GetNumBase64PaddingCharsToAddForDecode(count); var arraySizeRequired = checked(count + paddingCharsToAdd); Debug.Assert(arraySizeRequired % 4 == 0, "Invariant: Array length must be a multiple of 4."); if (buffer.Length - bufferOffset < arraySizeRequired) { throw new ArgumentException( string.Format( CultureInfo.CurrentCulture, EncoderResources.WebEncoders_InvalidCountOffsetOrLength, nameof(count), nameof(bufferOffset), nameof(input)), nameof(count)); } // Copy input into buffer, fixing up '-' -> '+' and '_' -> '/'. var i = bufferOffset; for (var j = offset; i - bufferOffset < count; i++, j++) { var ch = input[j]; if (ch == '-') { buffer[i] = '+'; } else if (ch == '_') { buffer[i] = '/'; } else { buffer[i] = ch; } } // Add the padding characters back. for (; paddingCharsToAdd > 0; i++, paddingCharsToAdd--) { buffer[i] = '='; } // Decode. // If the caller provided invalid base64 chars, they'll be caught here. return Convert.FromBase64CharArray(buffer, bufferOffset, arraySizeRequired); } /// /// Gets the minimum char[] size required for decoding of characters /// with the method. /// /// The number of characters to decode. /// /// The minimum char[] size required for decoding of characters. /// public static int GetArraySizeRequiredToDecode(int count) { if (count < 0) { throw new ArgumentOutOfRangeException(nameof(count)); } if (count == 0) { return 0; } var numPaddingCharsToAdd = GetNumBase64PaddingCharsToAddForDecode(count); return checked(count + numPaddingCharsToAdd); } /// /// Encodes using base64url encoding. /// /// The binary input to encode. /// The base64url-encoded form of . public static string Base64UrlEncode(byte[] input) { if (input == null) { throw new ArgumentNullException(nameof(input)); } return Base64UrlEncode(input, offset: 0, count: input.Length); } /// /// Encodes using base64url encoding. /// /// The binary input to encode. /// The offset into at which to begin encoding. /// The number of bytes from to encode. /// The base64url-encoded form of . public static string Base64UrlEncode(byte[] input, int offset, int count) { if (input == null) { throw new ArgumentNullException(nameof(input)); } ValidateParameters(input.Length, nameof(input), offset, count); // Special-case empty input if (count == 0) { return string.Empty; } var buffer = new char[GetArraySizeRequiredToEncode(count)]; var numBase64Chars = Base64UrlEncode(input, offset, buffer, outputOffset: 0, count: count); return new String(buffer, startIndex: 0, length: numBase64Chars); } /// /// Encodes using base64url encoding. /// /// The binary input to encode. /// The offset into at which to begin encoding. /// /// Buffer to receive the base64url-encoded form of . Array must be large enough to /// hold characters and the full base64-encoded form of /// , including padding characters. /// /// /// The offset into at which to begin writing the base64url-encoded form of /// . /// /// The number of bytes from to encode. /// /// The number of characters written to , less any padding characters. /// public static int Base64UrlEncode(byte[] input, int offset, char[] output, int outputOffset, int count) { if (input == null) { throw new ArgumentNullException(nameof(input)); } if (output == null) { throw new ArgumentNullException(nameof(output)); } ValidateParameters(input.Length, nameof(input), offset, count); if (outputOffset < 0) { throw new ArgumentOutOfRangeException(nameof(outputOffset)); } var arraySizeRequired = GetArraySizeRequiredToEncode(count); if (output.Length - outputOffset < arraySizeRequired) { throw new ArgumentException( string.Format( CultureInfo.CurrentCulture, EncoderResources.WebEncoders_InvalidCountOffsetOrLength, nameof(count), nameof(outputOffset), nameof(output)), nameof(count)); } // Special-case empty input. if (count == 0) { return 0; } // Use base64url encoding with no padding characters. See RFC 4648, Sec. 5. // Start with default Base64 encoding. var numBase64Chars = Convert.ToBase64CharArray(input, offset, count, output, outputOffset); // Fix up '+' -> '-' and '/' -> '_'. Drop padding characters. for (var i = outputOffset; i - outputOffset < numBase64Chars; i++) { var ch = output[i]; if (ch == '+') { output[i] = '-'; } else if (ch == '/') { output[i] = '_'; } else if (ch == '=') { // We've reached a padding character; truncate the remainder. return i - outputOffset; } } return numBase64Chars; } /// /// Get the minimum output char[] size required for encoding /// s with the method. /// /// The number of characters to encode. /// /// The minimum output char[] size required for encoding s. /// public static int GetArraySizeRequiredToEncode(int count) { var numWholeOrPartialInputBlocks = checked(count + 2) / 3; return checked(numWholeOrPartialInputBlocks * 4); } private static int GetNumBase64PaddingCharsToAddForDecode(int inputLength) { switch (inputLength % 4) { case 0: return 0; case 2: return 2; case 3: return 1; default: throw new FormatException( string.Format( CultureInfo.CurrentCulture, EncoderResources.WebEncoders_MalformedInput, inputLength)); } } private static void ValidateParameters(int bufferLength, string inputName, int offset, int count) { if (offset < 0) { throw new ArgumentOutOfRangeException(nameof(offset)); } if (count < 0) { throw new ArgumentOutOfRangeException(nameof(count)); } if (bufferLength - offset < count) { throw new ArgumentException( string.Format( CultureInfo.CurrentCulture, EncoderResources.WebEncoders_InvalidCountOffsetOrLength, nameof(count), nameof(offset), inputName), nameof(count)); } } } internal static class EncoderResources { /// /// Invalid {0}, {1} or {2} length. /// internal static readonly string WebEncoders_InvalidCountOffsetOrLength = "Invalid {0}, {1} or {2} length."; /// /// Malformed input: {0} is an invalid input length. /// internal static readonly string WebEncoders_MalformedInput = "Malformed input: {0} is an invalid input length."; /// /// Invalid {0}, {1} or {2} length. /// internal static string FormatWebEncoders_InvalidCountOffsetOrLength(object p0, object p1, object p2) { return string.Format(CultureInfo.CurrentCulture, WebEncoders_InvalidCountOffsetOrLength, p0, p1, p2); } /// /// Malformed input: {0} is an invalid input length. /// internal static string FormatWebEncoders_MalformedInput(object p0) { return string.Format(CultureInfo.CurrentCulture, WebEncoders_MalformedInput, p0); } } }