Beispiel #1
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);
            }
        }
Beispiel #2
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;
        }
Beispiel #3
0
        /// <summary>
        /// Creates a new instance of the <see cref="EncryptedPacket"/> class.
        /// </summary>
        /// <param name="cipherText">The encrypted data.</param>
        /// <param name="iv">The initialization vector for the encryption.</param>
        /// <param name="authTag">The authentication tag, for when authenticated encryption algorithms are used.</param>
        /// <returns></returns>
        public static EncryptedPacket CreateNewEncryptedPacket(
            Span <byte> cipherText,
            Span <byte> iv,
            Span <byte> authTag = default)
        {
            ArgCheck.NotEmpty(cipherText, nameof(cipherText));
            ArgCheck.NotEmpty(iv, nameof(iv));

            return(new EncryptedPacket
            {
                CipherText = new List <byte>(cipherText.ToArray()),
                IV = new List <byte>(iv.ToArray()),
                AuthTag = authTag.IsEmpty
                    ? new List <byte>()
                    : new List <byte>(authTag.ToArray()),
            });
        }
Beispiel #4
0
        /// <summary>
        /// Loads a <see cref="AegisBondArchive"/> from disk. This operation does not unlock the archive.
        /// </summary>
        /// <param name="fileSettings">Settings for where the archive and related files are stored.</param>
        /// <returns>The loaded <see cref="AegisBondArchive"/>.</returns>
        public static AegisArchive Load(SecureArchiveFileSettings fileSettings)
        {
            ArgCheck.IsValid(fileSettings, nameof(fileSettings));

            ZipArchive   tempSecureArchive = null;
            AegisArchive archive           = null;

            try
            {
                // Open the secure archive and read the metadata entry.
                tempSecureArchive = OpenSecureArchiveFile(fileSettings);

                var metadataEntry = tempSecureArchive.GetEntry(AegisConstants.SecureArchiveMetadataEntryName);

                if (metadataEntry is null)
                {
                    throw new ArchiveCorruptedException("The secure archive does not contain any internal metadata!");
                }

                using var metadataReader = new StreamReader(metadataEntry.Open());

                var metadataJson = metadataReader.ReadToEnd();
                var metadata     = JsonSerializer.Deserialize <SecureArchiveMetadata>(metadataJson);

                if (metadata is null)
                {
                    throw new ArchiveCorruptedException("Unable to parse the archive internal metadata!");
                }

                archive = new AegisArchive
                {
                    ArchiveMetadata = metadata,
                    FileSettings    = fileSettings,
                    SecureArchive   = tempSecureArchive,
                };

                // We've transfered dispose ownership of tempSecureArchive to the archive.
                tempSecureArchive = null;
            }
            finally
            {
                tempSecureArchive?.Dispose();
            }

            return(archive);
        }
Beispiel #5
0
        /// <summary>
        /// Creates a new instance of the <see cref="SecuritySettings"/> class.
        /// </summary>
        /// <param name="encryptionAlgo">The algorithm used to encrypt data in the archive.</param>
        /// <param name="keyDerivationFunction">The key derivation function (KDF) used to generate user keys.</param>
        /// <param name="keyDerivationWorkFactor">The work factor parameter (e.g. iteration count) for the KDF.</param>
        /// <param name="KeyIdSizeInBytes">The size (in bytes) of KeyIds generated for user keys.</param>
        public static SecuritySettings Create(
            EncryptionAlgo encryptionAlgo,
            KeyDerivationFunction keyDerivationFunction,
            int keyDerivationWorkFactor,
            int keyIdSizeInBytes)
        {
            ArgCheck.IsNot(EncryptionAlgo.Unknown, encryptionAlgo, nameof(encryptionAlgo));
            ArgCheck.IsNot(KeyDerivationFunction.Unknown, keyDerivationFunction, nameof(keyDerivationFunction));
            ArgCheck.GreaterThanZero(keyDerivationWorkFactor, nameof(keyDerivationWorkFactor));
            ArgCheck.GreaterThanZero(keyIdSizeInBytes, nameof(keyIdSizeInBytes));

            return(new SecuritySettings
            {
                EncryptionAlgo = encryptionAlgo,
                KeyDerivationFunction = keyDerivationFunction,
                KeyDerivationWorkFactor = keyDerivationWorkFactor,
                KeyIdSizeInBytes = keyIdSizeInBytes,
            });
        }
Beispiel #6
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);
        }
Beispiel #8
0
        /// <summary>
        /// Initializes a new instance of the <see cref="SecureArchiveCreationParameters"/> class.
        /// </summary>
        /// <param name="firstKeyAuthorizationParam">The parameters to authorize the first user key.</param>
        public SecureArchiveCreationParameters(UserKeyAuthorizationParameters firstKeyAuthorizationParam)
        {
            ArgCheck.IsValid(firstKeyAuthorizationParam, nameof(firstKeyAuthorizationParam));

            this.FirstKeyAuthorizationParams = firstKeyAuthorizationParam;
        }
Beispiel #9
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);
        }
Beispiel #10
0
        /// <summary>
        /// Creates a new <see cref="AegisArchive"/> that contains no files.
        /// </summary>
        /// <param name="fileSettings">Settings for where the archive and related files are stored.</param>
        /// <param name="creationParameters">The <see cref="SecureArchiveCreationParameters"/> to use when creating the archive.</param>
        /// <returns>A new <see cref="AegisArchive"/> that is opened but not yet persisted to disk.</returns>
        public static AegisArchive CreateNew(
            SecureArchiveFileSettings fileSettings,
            SecureArchiveCreationParameters creationParameters)
        {
            ArgCheck.IsValid(fileSettings, nameof(fileSettings));
            ArgCheck.IsValid(creationParameters, nameof(creationParameters));

            var currentTime = DateTimeOffset.UtcNow;
            var archiveId   = Guid.NewGuid();

            ArchiveKey tempArchiveKey = null;

            AegisArchive tempArchive = null;
            AegisArchive archive     = null;

            try
            {
                tempArchiveKey = ArchiveKey.CreateNew(creationParameters.SecuritySettings);

                // Derive and authorize the first user key.
                var keyDerivationSalt = CryptoHelpers.GetRandomBytes(creationParameters.KeyDerivationSaltSizeInBytes);

                var firstUserKeyAuthorization = UserKeyAuthorizationExtensions.CreateNewAuthorization(
                    creationParameters.FirstKeyAuthorizationParams,
                    keyDerivationSalt,
                    tempArchiveKey,
                    creationParameters.SecuritySettings);

                var authCanary = tempArchiveKey.Encrypt(
                    CryptoHelpers.GetCryptoStrategy(creationParameters.SecuritySettings.EncryptionAlgo),
                    archiveId.ToByteArray());

                var archiveMetadata = new SecureArchiveMetadata
                {
                    Id = archiveId,
                    SecuritySettings      = creationParameters.SecuritySettings,
                    CreateTime            = currentTime,
                    LastModifiedTime      = currentTime,
                    KeyDerivationSalt     = new List <byte>(keyDerivationSalt),
                    AuthCanary            = authCanary,
                    UserKeyAuthorizations = new List <UserKeyAuthorization> {
                        firstUserKeyAuthorization
                    },
                };

                tempArchive = new AegisArchive
                {
                    ArchiveMetadata = archiveMetadata,
                    ArchiveKey      = tempArchiveKey,
                    FileSettings    = fileSettings,
                    FileIndex       = new FileIndex(),
                    SecureArchive   = OpenSecureArchiveFile(fileSettings, createNewArchive: true),
                };

                tempArchive.PersistMetadata();

                // Transfer the archive reference to the return variable.
                archive     = tempArchive;
                tempArchive = null;

                // Dispose ownership of the archive key now belongs to the archive.
                tempArchiveKey = null;
            }
            finally
            {
                tempArchive?.Dispose();
                tempArchiveKey?.Dispose();
            }

            return(archive);
        }