/// <summary> /// Implements the Aegis 'create' verb. /// </summary> /// <param name="options">The verb options.</param> /// <returns>Whether or not the operation was handled.</returns> private bool CreateVerb(CreateVerbOptions options) { if (string.IsNullOrWhiteSpace(options.AegisArchivePath)) { throw new AegisUserErrorException("The archive path parameter is required for this operation."); } // Add the canonical "ags" extension to the new archive, if it's not already part of the path. var newArchivePath = options.AegisArchivePath.EndsWith( $".{AegisConstants.CanonicalSecureArchiveFileExtension}", StringComparison.OrdinalIgnoreCase) ? options.AegisArchivePath : $"{options.AegisArchivePath}.{AegisConstants.CanonicalSecureArchiveFileExtension}"; var archiveFileSettings = new SecureArchiveFileSettings(newArchivePath, this.TempDirectory); try { if (File.Exists(archiveFileSettings.Path)) { if (options.Force) { // Get rid of the pre-existing archive file first. File.Delete(archiveFileSettings.Path); } else { throw new AegisUserErrorException( "A file exists at the specified location. Use --force flag to overwrite."); } } using var firstUserKeyAuthorization = this.ArchiveUnlocker.GetNewUserKeyAuthorizationParams(); using var createParameters = new SecureArchiveCreationParameters(firstUserKeyAuthorization); using var archive = AegisArchive.CreateNew(archiveFileSettings, createParameters); var newArchiveFileInfo = new FileInfo(archive.FullFilePath); this.IO.Out.WriteLine($"Created new secure archive file '{newArchiveFileInfo.FullName}'."); } catch (IOException e) { throw new AegisUserErrorException($"Unable to write to {archiveFileSettings.Path}.", innerException: e); } return(true); }
/// <summary> /// Helper that opens an <see cref="AegisArchive"/> from disk. /// </summary> /// <param name="archivePath">The path to the <see cref="AegisArchive"/> on disk.</param> /// <param name="tempDirectory">The temp directory that the archive can use for operations.</param> /// <param name="archiveUnlocker">The archive unlock helper. If null, archive will be opened but left unlocked.</param> /// <returns>The opened <see cref="AegisArchive"/>.</returns> private static AegisArchive OpenArchive(string archivePath, string tempDirectory, ArchiveUnlocker archiveUnlocker) { if (string.IsNullOrWhiteSpace(archivePath)) { throw new AegisUserErrorException( "Specify the path to the Aegis archive to open. Use the '-a' option or check the 'open' command help for details."); } AegisArchive archive = null; var openSuccess = false; try { var archiveFileSettings = new SecureArchiveFileSettings(archivePath, tempDirectory); archive = AegisArchive.Load(archiveFileSettings); if (archiveUnlocker != null) { UnlockArchive(archive, archiveUnlocker); } openSuccess = true; } catch (IOException e) { throw new AegisUserErrorException( $"Unable to read file at {archivePath}.", innerException: e); } catch (ArchiveCorruptedException e) { throw new AegisUserErrorException( $"The archive file at {archivePath} is corrupted: {e.Message}", innerException: e); } finally { if (!openSuccess) { // Make sure to release any holds if we couldn't successfully open the archive. archive?.Dispose(); } } return(archive); }
/// <summary> /// Helper that unlocks the <see cref="AegisArchive"/> using the given <see cref="ArchiveUnlocker"/>. /// </summary> /// <param name="archive">The archive to unlock.</param> /// <param name="unlocker">The unlocking helper.</param> private static void UnlockArchive(AegisArchive archive, ArchiveUnlocker unlocker) { try { unlocker.Unlock(archive); } catch (ArchiveCorruptedException e) { throw new AegisUserErrorException( $"The archive file at {archive.FullFilePath} is corrupted: {e.Message}", innerException: e); } catch (UnauthorizedException e) { throw new AegisUserErrorException( $"The key was not able to unlock the archive.", innerException: e); } }
/// <summary> /// Prompts the user for their user key and uses it to unlock the given archive. /// </summary> /// <param name="archive">The archive to unlock.</param> public void Unlock(AegisArchive archive) { ArgCheck.NotNull(archive, nameof(archive)); if (!archive.IsLocked) { throw new InvalidOperationException("The given archive is already unlocked."); } // First, filter down the user key authorizations to only keep those for which we have a registered interface. var availableUserKeys = archive.GetUserKeyAuthorizations() .Where(key => this.SecretEntryInterfaces.ContainsKey(key.SecretMetadata.SecretKind) && this.SecretEntryInterfaces[key.SecretMetadata.SecretKind].CanProvideSecret(key.SecretMetadata)); if (!availableUserKeys.Any()) { throw new NoKeyAvailableException( "None of the authorized user keys are currently available because this interface does not support them."); } // Of those, ask the user to choose which type of secret they'd like to use to unlock the archive. var availableSecretKinds = availableUserKeys.Select(k => k.SecretMetadata.SecretKind).Distinct().ToImmutableArray(); var selectedSecretKind = availableSecretKinds.Length > 1 ? this.SecretSelector.PromptSelectSecretKind(availableSecretKinds) : availableSecretKinds[0]; // Split out the user keys that are available, and of the selected type. var selectedUserKeys = availableUserKeys .Where(k => k.SecretMetadata.SecretKind == selectedSecretKind) .Select(k => k.SecretMetadata) .ToImmutableArray(); // Finally, prompt to get the user secret and use it to unlock the archive. using var userSecret = this.SecretEntryInterfaces[selectedSecretKind].GetUserSecret(selectedUserKeys); archive.Unlock(userSecret); }