/// <summary> /// Unlocks (i.e. decrypts) the archive with the given raw user secret. /// </summary> /// <param name="userSecret">The user secret to use to unlock the archive.</param> public void Unlock(RawUserSecret userSecret) { ArgCheck.NotNull(userSecret, nameof(userSecret)); using var userKey = UserKey.DeriveFrom( userSecret, this.ArchiveMetadata.KeyDerivationSalt.ToArray(), this.ArchiveMetadata.SecuritySettings); this.Unlock(userKey); }
/// <summary> /// Removes a file from the archive. /// </summary> /// <param name="filePath">The virtual path to the file.</param> /// <remarks>If no file exists at that path, this operation is a silent no-op.</remarks> public void RemoveFile(AegisVirtualFilePath filePath) { ArgCheck.NotNull(filePath, nameof(filePath)); this.ThrowIfLocked(); var fileInfo = this.GetFileInfo(filePath); if (fileInfo != null) { this.RemoveFile(fileInfo.FileId); } }
/// <summary> /// Unlocks (i.e. decrypts) the archive with the given <see cref="UserKey"/>. /// </summary> /// <param name="userKey">The <see cref="UserKey"/> to use to unlock the archive.</param> public void Unlock(UserKey userKey) { ArgCheck.NotNull(userKey, nameof(userKey)); // Setting the ArchiveKey property puts the archive into the "unlocked" state. // Wait to set the property until after everything is properly unlocked. var archiveKey = this.DecryptArchiveKey(userKey); this.FileIndex = this.ArchiveMetadata.EncryptedFileIndex is null || this.ArchiveMetadata.EncryptedFileIndex.IsEmpty() ? new FileIndex() : FileIndex.DecryptFrom(this.ArchiveMetadata.EncryptedFileIndex, archiveKey, this.ArchiveMetadata.SecuritySettings); this.ArchiveKey = archiveKey; }
/// <summary> /// Decrypts and extract a file from the archive. /// </summary> /// <param name="fileInfo">The file to extract.</param> /// <param name="outputStream">The stream to extract the file to.</param> public void ExtractFile(AegisFileInfo fileInfo, Stream outputStream) { ArgCheck.NotNull(fileInfo, nameof(fileInfo)); ArgCheck.NotNull(outputStream, nameof(outputStream)); this.ThrowIfLocked(); var archiveEntry = this.SecureArchive.GetEntry(fileInfo.ArchiveEntryName); if (archiveEntry is null) { throw new ArchiveCorruptedException($"Unable to find archived entry for file ID {fileInfo.FileId}"); } using var archiveStream = archiveEntry.Open(); var encryptedFileData = EncryptedPacketExtensions.ReadFromBinaryStream( archiveStream, this.ArchiveMetadata.SecuritySettings.EncryptionAlgo); var plainTextFileBytes = this.ArchiveKey.Decrypt(this.CryptoStrategy, encryptedFileData); outputStream.Write(plainTextFileBytes); }
public static bool TryDecryptArchiveKey( this UserKeyAuthorization authorization, UserKey userKey, SecuritySettings securitySettings, out ArchiveKey archiveKey) { ArgCheck.NotNull(authorization, nameof(authorization)); ArgCheck.NotNull(userKey, nameof(userKey)); ArgCheck.IsValid(securitySettings, nameof(securitySettings)); archiveKey = null; if (!CryptoHelpers.SecureEquals(userKey.KeyId, authorization.KeyId)) { return(false); } try { // The SecureArchive file format requires that the friendly name and keyId be // checked for tampering when using authenticated cyphers. var additionalData = Encoding.UTF8.GetBytes(authorization.FriendlyName + authorization.KeyId); var cryptoStrategy = CryptoHelpers.GetCryptoStrategy(securitySettings.EncryptionAlgo); var decryptedArchiveKey = userKey.Decrypt(cryptoStrategy, authorization.EncryptedArchiveKey, additionalData); if (!decryptedArchiveKey.IsEmpty) { archiveKey = new ArchiveKey(decryptedArchiveKey.ToArray()); } } catch { return(false); } return(archiveKey != null); }
/// <summary> /// Adds a file to the archive. /// </summary> /// <param name="virtualPath">The virtual path to add the file at.</param> /// <param name="fileStream">The file data stream.</param> /// <param name="overwriteIfExists"> /// Whether or not to overwrite existing files at that path. /// If false, an <see cref="InvalidOperationException"/> is thrown if a file already exists at the path. /// </param> /// <returns>Metadata about the file added to the archive.</returns> public AegisFileInfo PutFile(AegisVirtualFilePath virtualPath, Stream fileStream, bool overwriteIfExists = false) { ArgCheck.NotNull(virtualPath, nameof(virtualPath)); ArgCheck.NotNull(fileStream, nameof(fileStream)); this.ThrowIfLocked(); // Remove any existing files stored at the path, if we're allowed to. var existingFileInfo = this.FileIndex.GetFileInfo(virtualPath); if (existingFileInfo != null) { if (!overwriteIfExists) { throw new InvalidOperationException( $"A file with ID '{existingFileInfo.FileId}' already exists at virtual path '{virtualPath}'!"); } this.RemoveFile(existingFileInfo.FileId); } var currentTime = DateTimeOffset.UtcNow; var fileInfo = existingFileInfo ?? new AegisFileInfo( virtualPath, new FileIndexEntry { FileId = Guid.NewGuid(), FilePath = virtualPath.ToString(), AddedTime = currentTime, LastModifiedTime = currentTime, }); fileInfo.IndexEntry.LastModifiedTime = currentTime; // Encrypt the file. // This mechanism won't work for very large files since it loads the whole thing into memory. // For now, this is an acceptable limit for Aegis file archiving. using var memoryStream = new MemoryStream(); fileStream.CopyTo(memoryStream); var encryptedFile = this.ArchiveKey.Encrypt( this.CryptoStrategy, memoryStream.ToArray()); encryptedFile.WriteToBinaryStream(this.SecureArchive.GetEntryWriteStream(fileInfo.ArchiveEntryName)); try { this.FileIndex.Add(fileInfo); this.PersistMetadata(); } catch { // Creating file metadata failed. // Revert adding file to keep the archive to keep it consistent. this.SecureArchive.DeleteEntryIfExists(fileInfo.ArchiveEntryName); throw; } this.FlushArchive(); return(fileInfo); }