package request import ( "bytes" goerrors "errors" "fmt" "os" "os/exec" "path/filepath" "strings" "github.com/browserpass/browserpass-native/errors" "github.com/browserpass/browserpass-native/response" log "github.com/sirupsen/logrus" ) func fetchDecryptedContents(request *request) { responseData := response.MakeFetchResponse() if !strings.HasSuffix(request.File, ".gpg") { log.Errorf("The requested password file '%v' does not have the expected '.gpg' extension", request.File) response.SendErrorAndExit( errors.CodeInvalidPasswordFileExtension, &map[errors.Field]string{ errors.FieldMessage: "The requested password file does not have the expected '.gpg' extension", errors.FieldAction: "fetch", errors.FieldFile: request.File, }, ) } store, ok := request.Settings.Stores[request.Store] if !ok { log.Errorf( "The password store '%v' is not present in the list of stores '%v'", request.Store, request.Settings.Stores, ) response.SendErrorAndExit( errors.CodeInvalidPasswordStore, &map[errors.Field]string{ errors.FieldMessage: "The password store is not present in the list of stores", errors.FieldAction: "fetch", errors.FieldStoreName: request.Store, }, ) } normalizedStorePath, err := normalizePasswordStorePath(store.Path) if err != nil { log.Errorf( "The password store '%v' is not accessible at the location '%v': %+v", store.Name, store.Path, err, ) response.SendErrorAndExit( errors.CodeInaccessiblePasswordStore, &map[errors.Field]string{ errors.FieldMessage: "The password store is not accessible", errors.FieldAction: "fetch", errors.FieldError: err.Error(), errors.FieldStoreName: store.Name, errors.FieldStorePath: store.Path, }, ) } store.Path = normalizedStorePath gpgPath := request.Settings.GpgPath if gpgPath != "" { err = validateGpgBinary(gpgPath) if err != nil { log.Errorf( "The provided gpg binary path '%v' is invalid: %+v", gpgPath, err, ) response.SendErrorAndExit( errors.CodeInvalidGpgPath, &map[errors.Field]string{ errors.FieldMessage: "The provided gpg binary path is invalid", errors.FieldAction: "fetch", errors.FieldError: err.Error(), errors.FieldGpgPath: gpgPath, }, ) } } else { gpgPath, err = detectGpgBinary() if err != nil { log.Error("Unable to detect the location of the gpg binary: ", err) response.SendErrorAndExit( errors.CodeUnableToDetectGpgPath, &map[errors.Field]string{ errors.FieldMessage: "Unable to detect the location of the gpg binary", errors.FieldAction: "fetch", errors.FieldError: err.Error(), }, ) } } responseData.Contents, err = decryptFile(&store, request.File, gpgPath) if err != nil { log.Errorf( "Unable to decrypt the password file '%v' in the password store '%v' located in '%v': %+v", request.File, store.Name, store.Path, err, ) response.SendErrorAndExit( errors.CodeUnableToDecryptPasswordFile, &map[errors.Field]string{ errors.FieldMessage: "Unable to decrypt the password file", errors.FieldAction: "fetch", errors.FieldError: err.Error(), errors.FieldFile: request.File, errors.FieldStoreName: store.Name, errors.FieldStorePath: store.Path, }, ) } response.SendOk(responseData) } func detectGpgBinary() (string, error) { // Look in $PATH first, then check common locations - the first successful result wins gpgBinaryPriorityList := []string{ "gpg2", "gpg", "/bin/gpg2", "/usr/bin/gpg2", "/usr/local/bin/gpg2", "/bin/gpg", "/usr/bin/gpg", "/usr/local/bin/gpg", } for _, binary := range gpgBinaryPriorityList { err := validateGpgBinary(binary) if err == nil { return binary, nil } } return "", goerrors.New("Unable to detect the location of the gpg binary to use") } func validateGpgBinary(gpgPath string) error { return exec.Command(gpgPath, "--version").Run() } func decryptFile(store *store, file string, gpgPath string) (string, error) { passwordFilePath := filepath.Join(store.Path, file) passwordFile, err := os.Open(passwordFilePath) if err != nil { return "", err } var stdout, stderr bytes.Buffer gpgOptions := []string{"--decrypt", "--yes", "--quiet", "--batch", "-"} cmd := exec.Command(gpgPath, gpgOptions...) cmd.Stdin = passwordFile cmd.Stdout = &stdout cmd.Stderr = &stderr if err := cmd.Run(); err != nil { return "", goerrors.New(fmt.Sprintf("Error: %s, Stderr: %s", err.Error(), stderr.String())) } return stdout.String(), nil }