|
| 1 | +using System; |
| 2 | +using System.Text; |
| 3 | +using System.Security.Cryptography; |
| 4 | +using System.Runtime.InteropServices; |
| 5 | +using Microsoft.Extensions.DependencyInjection; |
| 6 | +using Microsoft.AspNetCore.DataProtection; |
| 7 | +using System.IO; |
| 8 | +using System.Security.Cryptography.X509Certificates; |
| 9 | + |
| 10 | +namespace CLARiNET |
| 11 | +{ |
| 12 | + class Crypto |
| 13 | + { |
| 14 | + public static string Protect(string stringToEncrypt) |
| 15 | + { |
| 16 | + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) |
| 17 | + { |
| 18 | + return Convert.ToBase64String( |
| 19 | + ProtectedData.Protect( |
| 20 | + Encoding.UTF8.GetBytes(stringToEncrypt) |
| 21 | + , null |
| 22 | + , DataProtectionScope.CurrentUser)); |
| 23 | + } |
| 24 | + else |
| 25 | + { |
| 26 | + return Convert.ToBase64String( |
| 27 | + ProtectMac(stringToEncrypt) |
| 28 | + ); |
| 29 | + } |
| 30 | + } |
| 31 | + |
| 32 | + public static string Unprotect(string encryptedString) |
| 33 | + { |
| 34 | + if (String.IsNullOrEmpty(encryptedString)) |
| 35 | + { |
| 36 | + return ""; |
| 37 | + } |
| 38 | + |
| 39 | + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) |
| 40 | + { |
| 41 | + return Encoding.UTF8.GetString( |
| 42 | + ProtectedData.Unprotect( |
| 43 | + Convert.FromBase64String(encryptedString) |
| 44 | + , null |
| 45 | + , DataProtectionScope.CurrentUser)); |
| 46 | + } |
| 47 | + else |
| 48 | + { |
| 49 | + return Encoding.UTF8.GetString( |
| 50 | + UnProtectMac(Convert.FromBase64String(encryptedString))); |
| 51 | + } |
| 52 | + } |
| 53 | + |
| 54 | + // Credit: Oleg Batashov |
| 55 | + // https://simplecodesoftware.com/articles/how-to-encrypt-data-on-macos-without-dpapi |
| 56 | + private static IDataProtector MacEncryption() |
| 57 | + { |
| 58 | + IServiceCollection serviceCollection = new ServiceCollection(); |
| 59 | + ConfigureServices(serviceCollection); |
| 60 | + IDataProtector dataProtector = serviceCollection |
| 61 | + .BuildServiceProvider() |
| 62 | + .GetDataProtector(purpose: "MacOsEncryption"); |
| 63 | + |
| 64 | + return dataProtector; |
| 65 | + } |
| 66 | + |
| 67 | + private static byte[] ProtectMac(string stringToEncrypt) |
| 68 | + { |
| 69 | + return MacEncryption().Protect(Encoding.UTF8.GetBytes(stringToEncrypt)); |
| 70 | + } |
| 71 | + |
| 72 | + private static byte[] UnProtectMac(byte[] encryptedBytes) |
| 73 | + { |
| 74 | + return MacEncryption().Unprotect(encryptedBytes); |
| 75 | + } |
| 76 | + |
| 77 | + private static void ConfigureServices(IServiceCollection serviceCollection) |
| 78 | + { |
| 79 | + X509Certificate2 cert = SetupDataProtectionCertificate(); |
| 80 | + |
| 81 | + serviceCollection.AddDataProtection() |
| 82 | + .PersistKeysToFileSystem(new DirectoryInfo(AppDomain.CurrentDomain.BaseDirectory)) |
| 83 | + .SetApplicationName("CLARiNET") |
| 84 | + .ProtectKeysWithCertificate(cert); |
| 85 | + } |
| 86 | + |
| 87 | + static X509Certificate2 SetupDataProtectionCertificate() |
| 88 | + { |
| 89 | + string subjectName = "CN=CLARiNET Data Protection Certificate"; |
| 90 | + string subjectNameFind = subjectName.Substring(3); |
| 91 | + |
| 92 | + using (X509Store store = new X509Store(StoreName.My, StoreLocation.CurrentUser, OpenFlags.ReadOnly)) |
| 93 | + { |
| 94 | + X509Certificate2Collection certificateCollection = store.Certificates.Find(X509FindType.FindBySubjectName, |
| 95 | + subjectNameFind, |
| 96 | + // self-signed certificate won't pass X509 chain validation |
| 97 | + validOnly: false); |
| 98 | + if (certificateCollection.Count > 0) |
| 99 | + { |
| 100 | + return certificateCollection[0]; |
| 101 | + } |
| 102 | + |
| 103 | + X509Certificate2 certificate = CreateSelfSignedDataProtectionCertificate(subjectName); |
| 104 | + InstallCertificateAsNonExportable(certificate); |
| 105 | + return certificate; |
| 106 | + } |
| 107 | + } |
| 108 | + |
| 109 | + static X509Certificate2 CreateSelfSignedDataProtectionCertificate(string subjectName) |
| 110 | + { |
| 111 | + using (RSA rsa = RSA.Create(2048)) |
| 112 | + { |
| 113 | + CertificateRequest request = new CertificateRequest(subjectName, rsa, HashAlgorithmName.SHA256, |
| 114 | + RSASignaturePadding.Pkcs1); |
| 115 | + X509Certificate2 cert = request.CreateSelfSigned(DateTimeOffset.UtcNow.AddMinutes(-1), DateTimeOffset.UtcNow.AddYears(50)); |
| 116 | + return cert; |
| 117 | + } |
| 118 | + } |
| 119 | + |
| 120 | + static void InstallCertificateAsNonExportable(X509Certificate2 cert) |
| 121 | + { |
| 122 | + byte[] rawData = cert.Export(X509ContentType.Pkcs12, password: "CLARiNET" ); |
| 123 | + X509Certificate2 certPersistKey = new X509Certificate2(rawData, "CLARiNET", X509KeyStorageFlags.PersistKeySet); |
| 124 | + |
| 125 | + using (X509Store store = new X509Store(StoreName.My, StoreLocation.CurrentUser)) |
| 126 | + { |
| 127 | + store.Open(OpenFlags.MaxAllowed | OpenFlags.ReadWrite); |
| 128 | + store.Add(certPersistKey); |
| 129 | + store.Close(); |
| 130 | + } |
| 131 | + } |
| 132 | + } |
| 133 | +} |
0 commit comments