-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- code splitted and reorganized - bug fixed in mkdir -prepared for publishing with PyPI
- Loading branch information
Showing
8 changed files
with
296 additions
and
241 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
COPYRIGHT = '''Copyright (C)2024, by maxpat78.''' | ||
__version__ = '1.0' | ||
__all__ = ["Vault", "init_vault", "backupDirIds"] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
import locale, sys, argparse, shlex | ||
from os.path import * | ||
from .cryptomator import * | ||
from .cmshell import CMShell | ||
|
||
""" | ||
MIT License | ||
Copyright (c) 2024 maxpat78 | ||
""" | ||
|
||
locale.setlocale(locale.LC_ALL, '') | ||
|
||
parser = argparse.ArgumentParser(prog='pycryptomator', description="Access to a Cryptomator V8 vault") | ||
parser.add_argument('--init', action="store_true", help="Initialize a new vault in an empty directory") | ||
parser.add_argument('--print-keys', help="Print the raw master keys as a list of English words for Cryptomator (default), in ASCII85 (a85) or BASE64 (b64) format", type=str, choices=['a85','b64','words'], const='words', nargs='?') | ||
parser.add_argument('--master-keys', nargs=2, metavar=('PRIMARY_KEY', 'HMAC_KEY'), help="Primary and HMAC master keys in ASCII85 or BASE64 format, or - - to read a words list from standard input") | ||
parser.add_argument('--password', help="Password to unlock master keys stored in config file") | ||
parser.add_argument('--change-password', help="Change the password required to open the vault", action="store_true") | ||
parser.add_argument('vault_name', help="Location of the existing Cryptomator V8 vault to use") | ||
args, extras = parser.parse_known_args() | ||
|
||
if args.init: | ||
init_vault(args.vault_name, args.password) | ||
sys.exit(0) | ||
|
||
if not args.password and not args.master_keys: | ||
args.password = getpass.getpass() | ||
|
||
if args.master_keys: | ||
if args.master_keys[0] == '-': | ||
words = input('Words list: ') | ||
words = words.split() | ||
if len(words) != 44: raise BaseException('Not enough words') | ||
we = Wordsencoder(join(dirname(sys.argv[0]), '4096words_en.txt')) | ||
b = we.words2bytes(words) | ||
we.validate(b) | ||
pk = b[:32] | ||
hk = b[32:64] | ||
print() | ||
else: | ||
def tryDecode(s): | ||
e = 0 | ||
d = b'' | ||
try: d = base64.a85decode(s) | ||
except: pass | ||
if len(d) == 32: return d | ||
try: d = base64.urlsafe_b64decode(s) | ||
except: pass | ||
if len(d) == 32: return d | ||
raise BaseException('Could not decode master key "%s"'%s) | ||
pk = tryDecode(args.master_keys[0]) | ||
hk = tryDecode(args.master_keys[1]) | ||
v = Vault(args.vault_name, pk=pk, hk=hk) | ||
else: | ||
v = Vault(args.vault_name, args.password) | ||
|
||
if args.print_keys: | ||
print('\n * * * WARNING !!! * * *\n') | ||
print('KEEP THESE KEYS TOP SECRET!\nFor recovering purposes only.\n') | ||
|
||
if args.print_keys == 'a85': | ||
encoder = base64.a85encode | ||
elif args.print_keys == 'b64': | ||
encoder = base64.urlsafe_b64encode | ||
else: | ||
# initialize the words encoder with a dictionary in the same directory | ||
# it contains 4096 English words | ||
we = Wordsencoder(join(dirname(sys.argv[0]), '4096words_en.txt')) | ||
words = we.bytes2words(we.blob(v.pk, v.hk)) | ||
print(' '.join(words)) | ||
sys.exit(0) | ||
print('Primary master key :', encoder(v.pk).decode()) | ||
print('HMAC master key :', encoder(v.hk).decode()) | ||
sys.exit(0) | ||
|
||
if args.change_password: | ||
v.change_password() | ||
sys.exit(0) | ||
|
||
if not extras: | ||
CMShell(v).cmdloop() # start a shell with open vault | ||
else: | ||
# We must re-quote args, shlex should suffice | ||
CMShell(v).onecmd(shlex.join(extras)) # execute single command via shell |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,141 @@ | ||
import cmd, sys, shlex | ||
from os.path import * | ||
from .cryptomator import * | ||
|
||
class CMShell(cmd.Cmd): | ||
intro = 'PyCryptomator Shell. Type help or ? to list all available commands.' | ||
prompt = 'PCM:> ' | ||
vault = None | ||
|
||
def __init__ (p, vault): | ||
p.vault = vault | ||
super(CMShell, p).__init__() | ||
|
||
def preloop(p): | ||
p.prompt = '%s:> ' % p.vault.base | ||
|
||
def do_debug(p, arg): | ||
pass | ||
|
||
def do_quit(p, arg): | ||
'Quit the PyCryptomator Shell' | ||
sys.exit(0) | ||
|
||
def do_alias(p, arg): | ||
argl = shlex.split(arg) | ||
if not argl: | ||
print('use: alias <virtual pathname>') | ||
return | ||
i = p.vault.getInfo(argl[0]) | ||
print(i.realPathName) | ||
|
||
def do_backup(p, arg): | ||
'Backup all the dir.c9r with their tree structure in a ZIP archive' | ||
argl = shlex.split(arg) | ||
if not argl: | ||
print('use: backup <ZIP archive>') | ||
return | ||
backupDirIds(p.vault.base, argl[0]) | ||
|
||
def do_decrypt(p, arg): | ||
'Decrypt files or directories from the vault' | ||
argl = shlex.split(arg) | ||
force = '-f' in argl | ||
if force: argl.remove('-f') | ||
if not argl or argl[0] == '-h' or len(argl) != 2: | ||
print('use: decrypt [-f] <virtual_pathname_source> <real_pathname_destination>') | ||
print('use: decrypt <virtual_pathname_source> -') | ||
return | ||
try: | ||
is_dir = p.vault.getInfo(argl[0]).isDir | ||
if is_dir: p.vault.decryptDir(argl[0], argl[1], force) | ||
else: | ||
p.vault.decryptFile(argl[0], argl[1], force) | ||
if argl[1] == '-': print() | ||
except: | ||
print(sys.exception()) | ||
|
||
def do_encrypt(p, arg): | ||
'Encrypt files or directories into the vault' | ||
argl = shlex.split(arg) | ||
if not argl or argl[0] == '-h' or len(argl) != 2: | ||
print('use: encrypt <real_pathname_source> <virtual_pathname_destination>') | ||
return | ||
try: | ||
if isdir(argl[0]): | ||
p.vault.encryptDir(argl[0], argl[1]) | ||
else: | ||
p.vault.encryptFile(argl[0], argl[1]) | ||
except: | ||
print(sys.exception()) | ||
|
||
def do_ls(p, arg): | ||
'List files and directories' | ||
argl = shlex.split(arg) | ||
recursive = '-r' in argl | ||
if recursive: argl.remove('-r') | ||
if not argl: argl += ['/'] # implicit argument | ||
if argl[0] == '-h': | ||
print('use: ls [-r] <virtual_path1> [...<virtual_pathN>]') | ||
return | ||
for it in argl: | ||
try: | ||
p.vault.ls(it, recursive) | ||
except: | ||
pass | ||
|
||
def do_ln(p, arg): | ||
'Make a symbolic link to a file or directory' | ||
argl = shlex.split(arg) | ||
if len(argl) != 2: | ||
print('use: ln <target_virtual_pathname> <symbolic_link_virtual_pathname>') | ||
return | ||
try: | ||
p.vault.ln(argl[0], argl[1]) | ||
except: | ||
print(sys.exception()) | ||
|
||
def do_mkdir(p, arg): | ||
'Make a directory or directory tree' | ||
argl = shlex.split(arg) | ||
if not argl or argl[0] == '-h': | ||
print('use: mkdir <dir1> [...<dirN>]') | ||
return | ||
for it in argl: | ||
try: | ||
p.vault.mkdir(it) | ||
except: | ||
print(sys.exception()) | ||
|
||
def do_mv(p, arg): | ||
'Move or rename files or directories' | ||
argl = shlex.split(arg) | ||
if len(argl) < 2 or argl[0] == '-h': | ||
print('please use: mv <source> [<source2>...<sourceN>] <destination>') | ||
return | ||
for it in argl[:-1]: | ||
p.vault.mv(it, argl[-1]) | ||
|
||
def do_rm(p, arg): | ||
'Remove files and directories' | ||
argl = shlex.split(arg) | ||
force = '-f' in argl | ||
if force: argl.remove('-f') | ||
if not argl or argl[0] == '-h': | ||
print('use: rm <file1|dir1> [...<fileN|dirN>]') | ||
return | ||
for it in argl: | ||
if it == '/': | ||
print("Won't erase root directory.") | ||
return | ||
try: | ||
i = p.vault.getInfo(it) | ||
if not i.isDir: | ||
p.vault.remove(it) # del file | ||
continue | ||
if force: | ||
p.vault.rmtree(it) # del dir, even if nonempty | ||
continue | ||
p.vault.rmdir(it) # del empty dir | ||
except: | ||
print(sys.exception()) |
Oops, something went wrong.