Esempio n. 1
0
        /// <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);
        }
Esempio n. 2
0
        /// <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);
            }
        }
Esempio n. 3
0
        /// <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;
        }
Esempio n. 4
0
        /// <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);
        }
Esempio n. 6
0
        /// <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);
        }