Spend

Usage

First use

This page is made for first use of the hardware

You will need to tweak a few things on the SD card you received with the device : You have to tell the device for who it works

Warning

Spend is using symetric and asymetric encryption, using veracrypt containers and PGP-protected messages. If you want use these features, you should learn how it works. Some links about that :

A GPG keyring have been created for you, and Spend is setup to use it as default recipient.

You can find it on the SD card, in the folders : /home/spend/KEYRING-TO-REMOVE. It contains all the keyring file plus one which is the keyring’s password.

Note

You can use the autogenerated keyring if you want, but for more security you are advised to use you own instead. see Importing a new public key

A randomly-generated password used internally by the device is present in `/home/spend/ROOT_PASSWORD_TO_SAVE_AND_DELETE.txt`

If you want to play a bit with the device you are advised to keep it, but that’s not such a matter as OS is not encrypted

Now that Spend knows the public key that it should use, you can insert the Sd card at the rear of the device and start it (but it’s better to continue reading the documentation before)

Workflow

In this chapter, spend working process is succinctly described : how it look for devices, target files on it, how it chooses what to do, and which kind of encryption it uses

Devices

Once Spend is on and running, it will triggers a device research. If no seemingly interesting device is found, it will start monitoring devices plug.

Whenever a device is found, a timer in Configuration is set to let the user the possibility to plug another device.

Warning

Only mounted devices are processed. That means that if the device is somehow in a abnormal state, it will not be considered.

Supported devices

Due to its low electrical power, Spend should not be use with external hard drive that have no external power sources. Actually, it has only be tested with

  • various cheap and wide-spread USB pens
  • SD cards
  • µSD card

Targets

Once a device is detected, it is inspected to see how much space is available, and if interesting files are present.

By default, target files are images and videos only, but this can be changed in the Configuration options

Warning

If you ask Spend to proceed all file types, it can quickly get stuck if the device where you want to output already contains files

If encryption is off, Spend will copy the files in a folder named output

The files present in this folder are then ignored by the scans. This allows Spend to be used many with the same output device

Decision

Once all data about device is gathered, Spend have to choose which device will be used as input and which one as output.

Warning

Spend does not support use of more than two devices at the same time

Note

If only one device is present, the only thing that can be done is encrypting the files and storing them on the same device (other things are meaningless)

Decision is taken according to this heuristic :

  • If device A have interesting file and device B not, copy from A to B
  • If device A and B have interesting files :
    • If one device is recognized as an SD card, copy from the SD to the other device
    • Else fail

Note

It can be annoying that the output device needs to be free of files that can be considered as targets. However you can place them in a folder named output on the root of your removable drive. They will be excluded from scan

Encryption

Spend offers encryption functionality, with veracrypt and GPG.

Warning

Spend is using symetric and asymetric encryption, using veracrypt containers and PGP-protected messages. If you want use these features, you should learn how it works. Some links about that :

Encryption works this way :

  • A random name for the run is chosen. Let’s call it RunA
  • A random password (KeyA) is created. It is stored PGP-encrypted on the output device, under the name RunA.key
  • A veracrypt encrypted volume named RunA.files, which can be open with KeyA, is created on the output device
  • Targets files are stored in the encrypted volume

So, to decrypt the files :

  • Decrypt the file xxx.key with PGP, using your favorite software.
  • Open the veracrypt volume, called xxx.files. The password is inside the file you just opened via PGP
Importing a new public key

Spend have an automatic update system which lets you use your own GPG keyring to receive the files.

You just have to export your public key (cf. documentation of the software you uses), and store it in Spend’s SD card at this place : /home/spend/update/key.gpg

Update

As some software updates will probably be supplied, Spend offer a way to update easily.

You just have to put the supplied file in the SD cards, in the folder /home/spend/update ; and start Spend. Information about update will be displayed on the screen

Note

additional instructions may be given altogether with the update files

Configuration

Some options can be changed in a configuration file, located in /home/spend/.spend/prod.ini

Key Type Usage
display boolean Toggle screen activation
led boolean Toggle led activation
button boolean Toggle buttons activation
home_dir string Home directory. Used by other options
shred_state_file string File that contains led state bit
crypt_state_file string File that contains crypt state bit
log_stacktrace boolean Log trace in a file on error in the main process
wait_for_another_device_seconds int Time slept between first device detection and actual run
seeked_files string Targets mime types split by |. ex: image|video
recipient string Fingerprint of file recipient’s GPG public key

Troubleshooting

Common problems

Error codes

Errors displayed on the screen contains a code as last line. You can check this code meaning in this table :

Message Code Meaning
Update error UP00 Generic update error
Update error UP10 Error on GPG key update
PGP encryption error ENC50 Error while encrypting volume key with PGP
Encryption error ENC20 Error while creating or encrypting Veracrypt volume
No devices found… SCAN00 Devices scan returned no usable thing
Add other device or choose encryption CHOS10 Only one device found, no encryption ask. Ask for something different
No targets found CHOS20 No targets files have been found in devices
Not enought space INSP60 No device with enought space found
Error in pgp config PGP10 PGP recipient not set or badly set. cf see Importing a new public key
More than 2 devices not supported SCAN2 More than two device detected. This is not supported
Target files on both sides CHOS2 Two devices plugged, with target files on both. Can’t choose what to do.

Technical documentation

spend package

Subpackages

spend.display package
Submodules
spend.display.Message module
class spend.display.Message.Message(content=None, level=30, duration=None, template=2, is_last_one=False)

Bases: object

Displayed message representation. Comparable, Hashable, Human-readable

LEVEL_CRITICAL = 0
LEVEL_DEBUG = 40
LEVEL_ERROR = 10
LEVEL_INFO = 30
LEVEL_WARNING = 20
TEMPLATE_TYPES = [1, 2, 3]
TEMPLATE_TYPE_ERROR = 3
TEMPLATE_TYPE_NORMAL = 2
TEMPLATE_TYPE_SPLASH = 1
can_overtake(message)

Check if message in argument can overtake A message can overtake others with lower level, or with same level and no duration

Parameters:message (Message)
Returns:
Return type:boolean can_overtake
spend.display.MessageHandler module
spend.display.QueueManager module
class spend.display.QueueManager.PreviousItemsList

Bases: list

Old thing ? List that can have only one Message without duration On insert, previous elem is discarded

class spend.display.QueueManager.QueueManager(queue, message_handler)

Bases: threading.Thread

This module handles Message bus management. It allows Message prioritization and Temporary messages.

CHECK_FREQUENCY = 1
current_item = None
display(message: spend.display.Message.Message)

Store argument as currently displayed message and pass it to the Handler

Parameters:message (Message)
external_queue = None
kill()

kills instance without waiting

message_handler = None
next(message: spend.display.Message.Message)

function called by a Timer when it wakes up. Trigger update

Parameters:message (Message)
poll()

Get a new item from the Queue and process it

previous_items = []
process_message(raw_message: Union[spend.display.Message.Message, logging.LogRecord])

Check if message can be displayed considering current item. Start Timer if needed and ask for display

Parameters:raw_message (Union[Message, LogRecord])
Raises:StopListeningException – Used to stop the Manager. Triggered when receiving Message(is_last_one=True)
process_previous_items(new_item=Message: [Lvl.40] )

Check if previous messages can be displayed considering new Message. If last previous item can overtake new one and nothing is displayed, of if it can overtake both ; it can be used

run()

Loop until StopListeningException is received

stop()

Wait for Messages with duration to complete and stop instance

timers = {}
exception spend.display.QueueManager.StopListeningException

Bases: BaseException

spend.display.init module

Functions managing global oled communication environment : PriorityQueue joining QueueManager and a logging.Logger

spend.display.init.kill_display_management(display_props)

Stop display management with kill flag

Parameters:display_props
spend.display.init.start_display_management()
  • Create PriorityQueue
  • Create logging Handler
  • Create QueueManager thread reading from the PriorityQueue
Returns:{‘queue_manager’
Return type:QueueManager, ‘message_queue’: PriorityQueue, ‘handler’: logging.Handler}
spend.display.init.teardown_display_management(display_props, kill=False)

Stops disaplys management. Join threads.

Parameters:
  • display_props ({‘queue_manager’: QueueManager, ‘message_queue’: PriorityQueue, ‘handler’: logging.Handler})
  • kill (bool)
spend.display.logging module

This file hold classes for a custom logger, preparing LogRecord containing a Message to be push in a PriorityQueue Overrides in order to

  • filter log record not meant to be displayed
  • Put only Message instances in the queue
class spend.display.logging.DisplayFilter(name='')

Bases: logging.Filter

filter(r)

Filter non-Message LogRecords

class spend.display.logging.DisplayFormatter(fmt=None, datefmt=None, style='%')

Bases: logging.Formatter

format(record)

Does nothing

class spend.display.logging.DisplayQueueHandler(queue)

Bases: logging.handlers.QueueHandler

prepare(record)

Return actual content

spend.display.logging.create_display_handler(queue: queue.PriorityQueue)

Bootstrap handler and registers it

Parameters:queue (PriorityQueue)
Module contents

This module abstracts communication from one or many scripts to an OLED screen connected via GPIO It receive Message from a PriorityQueue and display them using a MessageHandler It can be plugged to the logging module for convenient use

spend.service package
Submodules
spend.service.ButtonManager module
spend.service.DecisionMaker module
spend.service.DeviceInspector module
spend.service.DeviceScanner module
spend.service.LedManager module
Module contents

Submodules

spend.Device module

spend.Spend module

spend.Volume module

spend.exceptions module

exception spend.exceptions.AsymetricEncryptionException(*args, **kwargs)

Bases: spend.exceptions.DisplayableException

PGP file encryption returns something else than OK

exception spend.exceptions.DeviceNotMountedException

Bases: BaseException

exception spend.exceptions.DeviceNotWritableException

Bases: BaseException

exception spend.exceptions.DisplayableException(*args, **kwargs)

Bases: Exception

Base class for Exceptions which could be displayed on the OLED display

as_message() → spend.display.Message.Message

Get exception as Message

Returns:
Return type:Message
get_duration()
exception spend.exceptions.GhostMountpointException

Bases: BaseException

exception spend.exceptions.MoreThanTwoDevicesException(*args, **kwargs)

Bases: spend.exceptions.DisplayableException

More than 2 devices found

exception spend.exceptions.NoDeviceFoundException(*args, **kwargs)

Bases: spend.exceptions.DisplayableException

No (interesting) device was found by DeviceScanner

exception spend.exceptions.NoTargetsFoundException(*args, **kwargs)

Bases: spend.exceptions.DisplayableException

One or more devices have been found, but DeviceInspector did not returned any file to be copied

exception spend.exceptions.NotEnoughtSpaceException(*args, **kwargs)

Bases: spend.exceptions.DisplayableException

No device have enough space to store files selected by DeviceInspector

exception spend.exceptions.SameDeviceAndNoEncryptionException(*args, **kwargs)

Bases: spend.exceptions.DisplayableException

Input and output have been set to the same device, and no encryption have been chosen. It fails as it does not makes sense to duplicate files

exception spend.exceptions.SymetricEncryptionException(*args, **kwargs)

Bases: spend.exceptions.DisplayableException

Error on volume creation

exception spend.exceptions.TargetOnBothSidesException(*args, **kwargs)

Bases: spend.exceptions.DisplayableException

Targets found in both devices

exception spend.exceptions.UnknownRecipientException(*args, **kwargs)

Bases: spend.exceptions.DisplayableException

PGP recipient specified in config is not present in PGP keyring

exception spend.exceptions.UpdateException(*args, **kwargs)

Bases: spend.exceptions.DisplayableException

spend.helpers module

spend.helpers.copy_files(files, to)
spend.helpers.debounce(s)

Decorator ensures function can only be called once every s seconds.

spend.helpers.get_child_processes(parent_id)
spend.helpers.get_config(filename='prod.ini')

Return ConfigObj :Parameters: filename (str Config filename (e.g test.ini))

Returns:
Return type:ConfigObj
spend.helpers.get_random_str(size)

Return x chars belonging to letters and digits.

Parameters:size (int Len of the returned string)
spend.helpers.get_resource_requirement() → pkg_resources.Requirement
spend.helpers.read_file_as_bool(filepath)
spend.helpers.read_file_content(filepath)

Lock the file and returns its content :Parameters: filepath (str File path to read)

Returns:
Return type:str
Raises:IOError: – Lock cannot be obtained
class spend.helpers.safeutil

Bases: object

This is a script designed to be “safe” drop-in replacements for the shutil move() and copyfile() functions. These functions are safe because they should never overwrite an existing file. In particular, if you try to move/copy to dst and there’s already a file at dst, these functions will attempt to copy to a slightly different (but free) filename, to avoid accidental data loss. More background here: http://alexwlchan.net/2015/06/safer-file-copying/

static _increment_filename(filename, marker='-')

Returns a generator that yields filenames with a counter. This counter is placed before the file extension, and incremented with every iteration. For example: f1 = increment_filename(“myimage.jpeg”) f1.next() # myimage-1.jpeg f1.next() # myimage-2.jpeg f1.next() # myimage-3.jpeg If the filename already contains a counter, then the existing counter is incremented on every iteration, rather than starting from 1. For example: f2 = increment_filename(“myfile-3.doc”) f2.next() # myfile-4.doc f2.next() # myfile-5.doc f2.next() # myfile-6.doc The default marker is an underscore, but you can use any string you like: f3 = increment_filename(“mymovie.mp4”, marker=”_”) f3.next() # mymovie_1.mp4 f3.next() # mymovie_2.mp4 f3.next() # mymovie_3.mp4 Since the generator only increments an integer, it is practically unlimited and will never raise a StopIteration exception.

static copyfile(src, dst)

Copies a file from path src to path dst. If a file already exists at dst, it will not be overwritten, but: * If it is the same as the source file, do nothing * If it is different to the source file, pick a new name for the copy that is distinct and unused, then copy the file there.

Returns the path to the copy.

static move(src, dst)

Moves a file from path src to path dst. If a file already exists at dst, it will not be overwritten, but: * If it is the same as the source file, do nothing * If it is different to the source file, pick a new name for the copy that is distinct and unused, then copy the file there.

Returns the path to the new file.

spend.helpers.shred_file(filename)

Shred designed file

Parameters:filename (str Path of the file to be shreded)
spend.helpers.toggle_state(_file)

Switch a file content from one to zero or inverse Make use of a Lock

Parameters:file (str File path)
Returns:
Return type:bool New value
spend.helpers.umount_devices(devices)

Unmount devices with pumount

Parameters:devices (List[str] List of strings understood by pumount as a device)
spend.helpers.write_file(filepath, data)

Write data to filepath. No exceptions catched. Parameters: ———– filepath: str File path data: str Data to be written in the file

spend.update module

Module contents

Copyright (C) 2019 spend

This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.

Super Portable ENcryption Device

Warning

This project is a prototype. It has some known flaws, and can contain various other bugs. Encryption process have not been audited so DO NOT USE IT IN RISKY SITUATIONS !

Spend is a mobile device which allows you to copy and transfer files among flash storages such as USB key, SD cards… It can also encrypt transfered files, using veracrpyt containers for files and PGP to interact with you

https://i.imgur.com/bxCbJ4U.jpg

This repository contains the main python package, designed to run on an OrangePi Zero with a Oled screen and 3 push buttons connected via GPIO.

Note

v 0.0 has not been released yet, which means that is code is still under development and may not run directly. Updates soon

Project documentation can be found here at readthedocs Repository there at Gitlab

Contact : spend3000 [at] protonmail.com

Indices and tables