/// <summary> /// Protect and write cache data to file /// </summary> /// <param name="data">Cache data</param> public void WriteData(byte[] data) { if (data == null) { throw new ArgumentNullException(nameof(data)); } try { _logger.TraceEvent(TraceEventType.Information, /*id*/ 0, $"Got '{data?.Length}' bytes to write to storage"); #if NET45 if (SharedUtilities.IsWindowsPlatform() && data.Length != 0) { _logger.TraceEvent(TraceEventType.Information, /*id*/ 0, $"Protecting the data"); data = ProtectedData.Protect(data, optionalEntropy: null, scope: DataProtectionScope.CurrentUser); } #endif WriteDataCore(data); } catch (Exception e) { _logger.TraceEvent(TraceEventType.Error, /*id*/ 0, $"An exception was encountered while writing data from the {nameof(MsalCacheStorage)} : {e}"); } }
/// <summary> /// Read and unprotect cache data /// </summary> /// <returns>Unprotected cache data</returns> public byte[] ReadData() { bool cacheFileExists = File.Exists(CacheFilePath); _logger.TraceEvent(TraceEventType.Information, /*id*/ 0, $"ReadData Cache file exists '{cacheFileExists}'"); byte[] data = null; bool alreadyLoggedException = false; try { _logger.TraceEvent(TraceEventType.Information, /*id*/ 0, $"Reading Data"); byte[] fileData = ReadDataCore(); _logger.TraceEvent(TraceEventType.Information, /*id*/ 0, $"Got '{fileData?.Length}' bytes from file storage"); if (fileData != null && fileData.Length > 0) { _logger.TraceEvent(TraceEventType.Information, /*id*/ 0, $"Unprotecting the data"); if (SharedUtilities.IsWindowsPlatform()) { data = ProtectedData.Unprotect(fileData, optionalEntropy: null, scope: DataProtectionScope.CurrentUser); } else { data = fileData; } } else if (fileData == null || fileData.Length == 0) { data = new byte[0]; _logger.TraceEvent(TraceEventType.Information, /*id*/ 0, $"Empty data does not need to be unprotected"); } else { _logger.TraceEvent(TraceEventType.Information, /*id*/ 0, $"Data does not need to be unprotected"); return(fileData); } } catch (Exception e) { if (!alreadyLoggedException) { _logger.TraceEvent(TraceEventType.Error, /*id*/ 0, $"An exception was encountered while reading data from the {nameof(MsalCacheStorage)} : {e}"); } ClearCore(); } // If the file does not exist this returns a time in the far distant past. _lastWriteTime = File.GetLastWriteTimeUtc(CacheFilePath); return(data ?? new byte[0]); }
/// <summary> /// Initializes a new instance of the <see cref="Storage"/> class. /// The actual cache reading and writing is OS specific: /// <list type="bullet"> /// <item> /// <term>Windows</term> /// <description>DPAPI encrypted file on behalf of the user. </description> /// </item> /// <item> /// <term>Mac</term> /// <description>Cache is stored in KeyChain. </description> /// </item> /// <item> /// <term>Linux</term> /// <description>Cache is stored in Gnome KeyRing - https://developer.gnome.org/libsecret/0.18/ </description> /// </item> /// </list> /// </summary> /// <param name="creationProperties">Properties for creating the cache storage on disk</param> /// <param name="logger">logger</param> /// <returns></returns> public static Storage Create(StorageCreationProperties creationProperties, TraceSource logger = null) { TraceSourceLogger actualLogger = logger == null ? s_staticLogger.Value : new TraceSourceLogger(logger); ICacheAccessor cacheAccessor; if (creationProperties.UseUnencryptedFallback) { cacheAccessor = new FileAccessor(creationProperties.CacheFilePath, setOwnerOnlyPermissions: true, logger: actualLogger); } else { if (SharedUtilities.IsWindowsPlatform()) { cacheAccessor = new DpApiEncryptedFileAccessor(creationProperties.CacheFilePath, logger: actualLogger); } else if (SharedUtilities.IsMacPlatform()) { cacheAccessor = new MacKeychainAccessor( creationProperties.CacheFilePath, creationProperties.MacKeyChainServiceName, creationProperties.MacKeyChainAccountName, actualLogger); } else if (SharedUtilities.IsLinuxPlatform()) { if (creationProperties.UseLinuxUnencryptedFallback) { cacheAccessor = new FileAccessor(creationProperties.CacheFilePath, setOwnerOnlyPermissions: true, actualLogger); } else { cacheAccessor = new LinuxKeyringAccessor( creationProperties.CacheFilePath, creationProperties.KeyringCollection, creationProperties.KeyringSchemaName, creationProperties.KeyringSecretLabel, creationProperties.KeyringAttribute1.Key, creationProperties.KeyringAttribute1.Value, creationProperties.KeyringAttribute2.Key, creationProperties.KeyringAttribute2.Value, actualLogger); } } else { throw new PlatformNotSupportedException(); } } return(new Storage(creationProperties, cacheAccessor, actualLogger)); }
private static string GetUserHomeDirOnUnix() { if (SharedUtilities.IsWindowsPlatform()) { throw new NotSupportedException(); } if (!string.IsNullOrEmpty(SharedUtilities.s_homeEnvVar)) { return(SharedUtilities.s_homeEnvVar); } string username = null; if (!string.IsNullOrEmpty(SharedUtilities.s_lognameEnvVar)) { username = s_lognameEnvVar; } else if (!string.IsNullOrEmpty(SharedUtilities.s_userEnvVar)) { username = s_userEnvVar; } else if (!string.IsNullOrEmpty(SharedUtilities.s_lNameEnvVar)) { username = s_lNameEnvVar; } else if (!string.IsNullOrEmpty(SharedUtilities.s_usernameEnvVar)) { username = s_usernameEnvVar; } if (SharedUtilities.IsMacPlatform()) { return(!string.IsNullOrEmpty(username) ? Path.Combine("/Users", username) : null); } else if (SharedUtilities.IsLinuxPlatform()) { if (LinuxNativeMethods.getuid() == LinuxNativeMethods.RootUserId) { return("/root"); } else { return(!string.IsNullOrEmpty(username) ? Path.Combine("/home", username) : null); } } else { throw new NotSupportedException(); } }
public CrossPlatLock(string lockfilePath, int lockFileRetryDelay = LockfileRetryDelayDefault, int lockFileRetryCount = LockfileRetryCountDefault) { Exception exception = null; FileStream fileStream = null; // Create lock file dir if it doesn't already exist Directory.CreateDirectory(Path.GetDirectoryName(lockfilePath)); for (int tryCount = 0; tryCount < lockFileRetryCount; tryCount++) { try { // We are using the file locking to synchronize the store, do not allow multiple writers or readers for the file. const int defaultBufferSize = 4096; var fileShare = FileShare.None; if (SharedUtilities.IsWindowsPlatform()) { // This is so that Windows can offer read due to the granularity of the locking. Unix will not // lock with FileShare.Read. Read access on Windows is only for debugging purposes and will not // affect the functionality. // // See: https://github.com/dotnet/coreclr/blob/98472784f82cee7326a58e0c4acf77714cdafe03/src/System.Private.CoreLib/shared/System/IO/FileStream.Unix.cs#L74-L89 fileShare = FileShare.Read; } fileStream = new FileStream(lockfilePath, FileMode.OpenOrCreate, FileAccess.ReadWrite, fileShare, defaultBufferSize, FileOptions.DeleteOnClose); using (var writer = new StreamWriter(fileStream, Encoding.UTF8, defaultBufferSize, leaveOpen: true)) { writer.WriteLine($"{Process.GetCurrentProcess().Id} {Process.GetCurrentProcess().ProcessName}"); } break; } catch (IOException ex) { exception = ex; Thread.Sleep(lockFileRetryDelay); } catch (UnauthorizedAccessException ex) { exception = ex; Thread.Sleep(lockFileRetryCount); } } _lockFileStream = fileStream ?? throw new InvalidOperationException("Could not get access to the shared lock file.", exception); }
/// <summary> /// Read and unprotect cache data /// </summary> /// <returns>Unprotected cache data</returns> public byte[] ReadData() { bool cacheFileExists = File.Exists(CacheFilePath); _logger.TraceEvent(TraceEventType.Information, /*id*/ 0, $"ReadData Cache file exists '{cacheFileExists}'"); byte[] data = null; try { _logger.TraceEvent(TraceEventType.Information, /*id*/ 0, $"Reading Data"); byte[] fileData = ReadDataCore(); _logger.TraceEvent(TraceEventType.Information, /*id*/ 0, $"Got '{fileData?.Length}' bytes from file storage"); if (fileData != null && fileData.Length > 0) { _logger.TraceEvent(TraceEventType.Information, /*id*/ 0, $"Unprotecting the data"); data = SharedUtilities.IsWindowsPlatform() ? ProtectedData.Unprotect(fileData, optionalEntropy: null, scope: DataProtectionScope.CurrentUser) : fileData; } else if (fileData == null || fileData.Length == 0) { data = new byte[0]; _logger.TraceEvent(TraceEventType.Information, /*id*/ 0, $"Empty data does not need to be unprotected"); } else { _logger.TraceEvent(TraceEventType.Information, /*id*/ 0, $"Data does not need to be unprotected"); return(fileData); } } catch (Exception e) { _logger.TraceEvent(TraceEventType.Error, /*id*/ 0, $"An exception was encountered while reading data from the {nameof(MsalCacheStorage)} : {e}"); ClearCore(); } return(data ?? new byte[0]); }
private void ClearCore() { _logger.TraceEvent(TraceEventType.Information, /*id*/ 0, "Clearing cache"); bool cacheFileExists = File.Exists(CacheFilePath); _logger.TraceEvent(TraceEventType.Information, /*id*/ 0, $"ReadDataCore Cache file exists '{cacheFileExists}'"); TryProcessFile(() => { _logger.TraceEvent(TraceEventType.Information, /*id*/ 0, "Before deleting the cache file"); try { File.Delete(CacheFilePath); } catch (Exception e) { _logger.TraceEvent(TraceEventType.Error, /*id*/ 0, $"Problem deleting the cache file '{e}'"); } _logger.TraceEvent(TraceEventType.Information, /*id*/ 0, $"After deleting the cache file."); }); if (SharedUtilities.IsMacPlatform()) { _logger.TraceEvent(TraceEventType.Information, /*id*/ 0, "Before delete mac keychain"); MacKeyChain.DeleteKey( _creationProperties.MacKeyChainServiceName, _creationProperties.MacKeyChainAccountName); _logger.TraceEvent(TraceEventType.Information, /*id*/ 0, "After delete mac keychain"); } else if (SharedUtilities.IsLinuxPlatform()) { _logger.TraceEvent(TraceEventType.Information, /*id*/ 0, $"Before deletring secret from linux keyring"); IntPtr error = IntPtr.Zero; Libsecret.secret_password_clear_sync( schema: GetLibsecretSchema(), cancellable: IntPtr.Zero, error: out error, attribute1Type: _creationProperties.KeyringAttribute1.Key, attribute1Value: _creationProperties.KeyringAttribute1.Value, attribute2Type: _creationProperties.KeyringAttribute2.Key, attribute2Value: _creationProperties.KeyringAttribute2.Value, end: IntPtr.Zero); if (error != IntPtr.Zero) { try { GError err = (GError)Marshal.PtrToStructure(error, typeof(GError)); _logger.TraceEvent(TraceEventType.Error, /*id*/ 0, $"An error was encountered while clearing secret from keyring in the {nameof(MsalCacheStorage)} domain:'{err.Domain}' code:'{err.Code}' message:'{err.Message}'"); } catch (Exception e) { _logger.TraceEvent(TraceEventType.Error, /*id*/ 0, $"An exception was encountered while processing libsecret error information during clearing secret in the {nameof(MsalCacheStorage)} ex:'{e}'"); } } _logger.TraceEvent(TraceEventType.Information, /*id*/ 0, "After deleting secret from linux keyring"); } else if (!SharedUtilities.IsWindowsPlatform()) { _logger.TraceEvent(TraceEventType.Information, /*id*/ 0, "Not supported platform"); throw new PlatformNotSupportedException(); } }
private byte[] ReadDataCore() { _logger.TraceEvent(TraceEventType.Information, /*id*/ 0, "ReadDataCore"); byte[] fileData = null; bool cacheFileExists = File.Exists(CacheFilePath); _logger.TraceEvent(TraceEventType.Information, /*id*/ 0, $"ReadDataCore Cache file exists '{cacheFileExists}'"); if (SharedUtilities.IsWindowsPlatform()) { if (cacheFileExists) { TryProcessFile(() => { fileData = File.ReadAllBytes(CacheFilePath); _logger.TraceEvent(TraceEventType.Information, /*id*/ 0, $"ReadDataCore, read '{fileData.Length}' bytes from the file"); }); } } else if (SharedUtilities.IsMacPlatform()) { _logger.TraceEvent(TraceEventType.Information, /*id*/ 0, $"ReadDataCore, Before reading from mac keychain"); fileData = MacKeyChain.RetrieveKey(_creationProperties.MacKeyChainServiceName, _creationProperties.MacKeyChainAccountName, _logger); _logger.TraceEvent(TraceEventType.Information, /*id*/ 0, $"ReadDataCore, read '{fileData?.Length}' bytes from the keychain"); } else if (SharedUtilities.IsLinuxPlatform()) { _logger.TraceEvent(TraceEventType.Information, /*id*/ 0, $"ReadDataCore, Before reading from linux keyring"); IntPtr error = IntPtr.Zero; string secret = Libsecret.secret_password_lookup_sync( schema: GetLibsecretSchema(), cancellable: IntPtr.Zero, error: out error, attribute1Type: _creationProperties.KeyringAttribute1.Key, attribute1Value: _creationProperties.KeyringAttribute1.Value, attribute2Type: _creationProperties.KeyringAttribute2.Key, attribute2Value: _creationProperties.KeyringAttribute2.Value, end: IntPtr.Zero); if (error != IntPtr.Zero) { try { GError err = (GError)Marshal.PtrToStructure(error, typeof(GError)); _logger.TraceEvent(TraceEventType.Error, /*id*/ 0, $"An error was encountered while reading secret from keyring in the {nameof(MsalCacheStorage)} domain:'{err.Domain}' code:'{err.Code}' message:'{err.Message}'"); } catch (Exception e) { _logger.TraceEvent(TraceEventType.Error, /*id*/ 0, $"An exception was encountered while processing libsecret error information during reading in the {nameof(MsalCacheStorage)} ex:'{e}'"); } } else if (string.IsNullOrEmpty(secret)) { _logger.TraceEvent(TraceEventType.Error, /*id*/ 0, "No matching secret found in the keyring"); } else { _logger.TraceEvent(TraceEventType.Information, /*id*/ 0, "Base64 decoding the secret string"); fileData = Convert.FromBase64String(secret); _logger.TraceEvent(TraceEventType.Information, /*id*/ 0, $"ReadDataCore, read '{fileData?.Length}' bytes from the keyring"); } } else { _logger.TraceEvent(TraceEventType.Error, /*id*/ 0, "Platform not supported"); throw new PlatformNotSupportedException(); } return(fileData); }
/// <summary> /// Read and unprotect cache data /// </summary> /// <returns>Unprotected cache data</returns> public byte[] ReadData() { bool cacheFileExists = File.Exists(CacheFilePath); _logger.TraceEvent(TraceEventType.Information, /*id*/ 0, $"ReadData Cache file exists '{cacheFileExists}'"); byte[] data = null; bool alreadyLoggedException = false; try { // Guarantee that the version file exists so that we can know if it changes. if (!File.Exists(VersionFilePath)) { WriteVersionFile(); } else { CacheLastReadVersion(); } _logger.TraceEvent(TraceEventType.Information, /*id*/ 0, $"Reading Data"); byte[] fileData = ReadDataCore(); _logger.TraceEvent(TraceEventType.Information, /*id*/ 0, $"Got '{fileData?.Length}' bytes from file storage"); if (fileData != null && fileData.Length > 0) { _logger.TraceEvent(TraceEventType.Information, /*id*/ 0, $"Unprotecting the data"); if (SharedUtilities.IsWindowsPlatform()) { data = ProtectedData.Unprotect(fileData, optionalEntropy: null, scope: DataProtectionScope.CurrentUser); } else { data = fileData; } } else if (fileData == null || fileData.Length == 0) { data = new byte[0]; _logger.TraceEvent(TraceEventType.Information, /*id*/ 0, $"Empty data does not need to be unprotected"); } else { _logger.TraceEvent(TraceEventType.Information, /*id*/ 0, $"Data does not need to be unprotected"); return(fileData); } } catch (Exception e) { if (!alreadyLoggedException) { _logger.TraceEvent(TraceEventType.Error, /*id*/ 0, $"An exception was encountered while reading data from the {nameof(MsalCacheStorage)} : {e}"); } ClearCore(); } return(data ?? new byte[0]); }