/// <summary> /// Creates a new <see cref="UserKeyAuthorization"/> entry for a user key and a particular archive. /// </summary> /// <param name="friendlyName">A friendly name to help the user identify the key.</param> /// <param name="userKey">The <see cref="UserKey"/> to authorize.</param> /// <param name="archiveKey">The key used to encrypt the archive that the user key is being authorized for.</param> /// <param name="securitySettings">The archive's <see cref="SecuritySettings"/>.</param> /// <returns>The new <see cref="UserKeyAuthorization"/> entry.</returns> public static UserKeyAuthorization CreateNewAuthorization( UserKeyAuthorizationParameters newKeyParams, ReadOnlySpan <byte> keyDerivationSalt, ArchiveKey archiveKey, SecuritySettings securitySettings) { ArgCheck.IsValid(newKeyParams, nameof(newKeyParams)); ArgCheck.NotEmpty(keyDerivationSalt, nameof(keyDerivationSalt)); ArgCheck.NotNull(archiveKey, nameof(archiveKey)); ArgCheck.IsValid(securitySettings, nameof(securitySettings)); using var userKey = UserKey.DeriveFrom( newKeyParams.UserSecret, keyDerivationSalt, securitySettings); // The SecureArchive file format requires that the friendly name and keyId be // checked for tampering when using authenticated cyphers. var additionalData = Encoding.UTF8.GetBytes(newKeyParams.FriendlyName + userKey.KeyId); var cryptoStrategy = CryptoHelpers.GetCryptoStrategy(securitySettings.EncryptionAlgo); var encryptedArchiveKey = userKey.EncryptSecret(cryptoStrategy, archiveKey, additionalData); return(new UserKeyAuthorization { AuthorizationId = Guid.NewGuid(), FriendlyName = newKeyParams.FriendlyName, KeyId = userKey.KeyId, TimeAdded = DateTime.UtcNow, EncryptedArchiveKey = encryptedArchiveKey, SecretMetadata = newKeyParams.SecretMetadata, }); }
/// <summary> /// Authorizes a new user key to access the archive. /// </summary> /// <param name="authorizationParameters">The new user key authorization parameters.</param> public void AuthorizeNewKey(UserKeyAuthorizationParameters authorizationParameters) { ArgCheck.IsValid(authorizationParameters, nameof(authorizationParameters)); this.ThrowIfLocked(); var newAuthorization = UserKeyAuthorizationExtensions.CreateNewAuthorization( authorizationParameters, this.ArchiveMetadata.KeyDerivationSalt.ToArray(), this.ArchiveKey, this.ArchiveMetadata.SecuritySettings); this.ArchiveMetadata.UserKeyAuthorizations.Add(newAuthorization); try { this.PersistMetadata(); } catch { // Revert the change. Keep in-memory structure consistent. this.ArchiveMetadata.UserKeyAuthorizations.RemoveAt( this.ArchiveMetadata.UserKeyAuthorizations.Count - 1); throw; } }
/// <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); }
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> /// 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; }
/// <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); }