Skip to content

Commit 7df5f17

Browse files
authored
Merge pull request #11 from anusii/dc/10_use_inherit_key
dc/10 use inherit key
2 parents 74f29e5 + 3bc3ebf commit 7df5f17

3 files changed

Lines changed: 155 additions & 65 deletions

File tree

read_encrypted.py

Lines changed: 64 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,22 @@
11
import os
2+
import sys
23
import argparse
34

45
from getpass import getpass
56
from base64 import b64decode
7+
from urllib.parse import urlparse
68

7-
from pod_helper import (
9+
from solidpod_helper import (
810
gen_master_key,
11+
gen_verify_key,
912
decrypt,
1013
parse_ttl,
1114
path_pred,
1215
iv_pred,
13-
session_key_pred,
14-
data_pred,
16+
verify_key_pred,
17+
indi_key_pred,
18+
inherit_key_pred,
19+
enc_data_pred,
1520
server_path,
1621
)
1722

@@ -30,39 +35,75 @@
3035
app_name = items[1]
3136
assert items[2] == 'data'
3237
app_path = f'{server_path}{pod_name}/{app_name}'
38+
relative_file_path = '/'.join(items[1:])
3339

3440
security_key_str = getpass(prompt='Security Key: ')
3541
master_key = gen_master_key(security_key_str)
42+
verify_key = gen_verify_key(security_key_str)
3643

37-
# Parsing ind-keys.ttl file
44+
# Verify security key
3845

39-
key_path = f'{app_path}/encryption/ind-keys.ttl'
40-
result = parse_ttl(key_path)
41-
key_map = {v[path_pred][0]: {iv_pred: v[iv_pred][0], session_key_pred: v[session_key_pred][0]} for k, v in result.items() if iv_pred in v}
42-
file_key = '/'.join(items[1:])
43-
session_key_ct_b64 = key_map[file_key][session_key_pred]
44-
session_key_iv_b64 = key_map[file_key][iv_pred]
46+
enc_key_map = parse_ttl(f'{app_path}/encryption/enc-keys.ttl')
47+
verify_key_stored = list(enc_key_map.items())[0][1][verify_key_pred]
48+
if verify_key.decode('utf-8') != verify_key_stored:
49+
print('ERROR: Incorrect security key (verification failed).')
50+
sys.exit(0)
4551

4652
# Parse data .ttl file
4753

48-
result = parse_ttl(file_path)
49-
data_key = list(result.keys())[0]
50-
data_map = {iv_pred: result[data_key][iv_pred][0], data_pred: result[data_key][data_pred][0]}
51-
data_ct_b64 = data_map[data_pred]
52-
data_iv_b64 = data_map[iv_pred]
54+
with open(file_path) as fd:
55+
file_content = fd.read()
5356

54-
# Decrypt session key
57+
_map = parse_ttl(ttl_str=file_content)
5558

56-
session_key_ct = b64decode(session_key_ct_b64)
57-
session_key_iv = b64decode(session_key_iv_b64)
58-
session_key_b64 = decrypt(session_key_ct, master_key, session_key_iv)
59-
session_key = b64decode(session_key_b64)
59+
def exit():
60+
print(f'WARN: File "{args.filepath}" is not encrypted by solidpod, return raw content\n{"-"*20}')
61+
print(file_content)
62+
sys.exit(0)
63+
64+
if len(_map) != 1:
65+
exit()
66+
67+
file_url, data_map = list(_map.items())[0]
68+
69+
if (path_pred not in data_map) or (data_map[path_pred] != relative_file_path):
70+
exit()
71+
72+
if (iv_pred not in data_map) or (enc_data_pred not in data_map):
73+
exit()
74+
75+
data_ct = b64decode(data_map[enc_data_pred])
76+
data_iv = b64decode(data_map[iv_pred])
77+
78+
# Retrieve encryption key and IV
79+
80+
key_map = parse_ttl(f'{app_path}/encryption/ind-keys.ttl')
81+
indi_key_ct = None
82+
indi_key_iv = None
83+
if file_url in key_map:
84+
indi_key_ct = b64decode(key_map[file_url][indi_key_pred])
85+
indi_key_iv = b64decode(key_map[file_url][iv_pred])
86+
elif inherit_key_pred in data_map:
87+
r = urlparse(file_url)
88+
server_url = f'{r.scheme}://{r.netloc}'
89+
inherit_key_url = '/'.join([server_url, pod_name, data_map[inherit_key_pred]])
90+
if inherit_key_url in key_map:
91+
indi_key_ct = b64decode(key_map[inherit_key_url][indi_key_pred])
92+
indi_key_iv = b64decode(key_map[inherit_key_url][iv_pred])
93+
else:
94+
pass
95+
96+
if indi_key_ct is None or indi_key_iv is None:
97+
exit()
98+
99+
# Decrypt individual key
100+
101+
indi_key = b64decode(decrypt(indi_key_ct, master_key, indi_key_iv))
60102

61103
# Decrypt data
62104

63-
data_ct = b64decode(data_ct_b64)
64-
data_iv = b64decode(data_iv_b64)
65-
plain_text = decrypt(data_ct, session_key, data_iv)
105+
plain_text = decrypt(data_ct, indi_key, data_iv)
66106

107+
print('-'*20)
67108
print(plain_text)
68109

pod_helper.py renamed to solidpod_helper.py

Lines changed: 32 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import sys
12
import hashlib
23
from base64 import (
34
b64decode,
@@ -15,40 +16,55 @@
1516
unpad,
1617
)
1718

18-
1919
separator = '#'
2020
path_pred = 'path'
2121
iv_pred = 'iv'
22-
session_key_pred = 'sessionKey'
23-
data_pred = 'encData'
22+
verify_key_pred = 'encKey'
23+
private_key_pred = 'prvKey'
24+
indi_key_pred = 'sessionKey'
25+
enc_data_pred = 'encData'
26+
inherit_key_pred = 'inheritKeyFrom'
2427
server_path = '/opt/solid/server/'
2528
apps_terms = 'https://solidcommunity.au/predicates/terms#';
2629

27-
def parse_ttl(fname):
28-
# Parse a .ttl file into a dictionary
30+
def parse_ttl(fname=None, ttl_str=None):
31+
# Parse a .ttl file or its content (RDF data string) into a dictionary
32+
if fname is None and ttl_str is None:
33+
print(f'ERROR: either file name or RDF data should be provided')
34+
sys.exit(1)
35+
2936
g = Graph()
30-
g.parse(fname)
37+
if fname is not None:
38+
g.parse(fname)
39+
elif ttl_str is not None:
40+
g.parse(data=ttl_str)
41+
else:
42+
pass
3143

3244
tripleMap = dict()
3345
for sub, pre, obj in g:
34-
s = sub.lower() if isinstance(sub, URIRef) else sub
35-
p = pre.split('#')[-1]
36-
o = obj.split('#')[-1]
46+
s = str(sub) if isinstance(sub, URIRef) else sub
47+
p = pre.split(separator)[-1]
48+
o = obj.split(separator)[-1]
3749
if s in tripleMap:
3850
d = tripleMap[s]
3951
if p in d:
40-
d[p].add(o)
52+
d[p] = d.add(o)
4153
else:
4254
d[p] = [o]
4355
else:
4456
tripleMap[s] = {p: [o]}
45-
return tripleMap
57+
# convert triple (s, p, o) to (s, p, o[0]) if o is a list with only one element
58+
return {s: {p: o[0] if len(o) == 1 else o for p, o in tripleMap[s].items()} for s in tripleMap.keys()}
4659

4760

4861
def gen_master_key(security_key_str):
4962
# Generate master key from security key string as per `solidpod`
5063
return hashlib.sha256(security_key_str.encode('utf-8')).hexdigest()[:32].encode('utf-8')
5164

65+
def gen_verify_key(security_key_str):
66+
# Generate verification key from security key string as per `solidpod`
67+
return hashlib.sha224(security_key_str.encode('utf-8')).hexdigest()[:32].encode('utf-8')
5268

5369
def encrypt(data_str, key, iv):
5470
# Encrypt the input string `data_str` using AES with
@@ -66,6 +82,11 @@ def encrypt(data_str, key, iv):
6682

6783

6884
def decrypt(data_ct, key, iv):
85+
# Decrypt the input string `data_ct` using AES with
86+
# - key (bytes): encryption key
87+
# - iv (bytes): nonce and initial value
88+
# Returns the plaintext string
89+
6990
# CTR mode is also known as segmented integer counter (SIC) mode, as per
7091
# https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Counter_(CTR)
7192
# It seems the first half of `iv' should be used as `nonce', and the latter half for `initial_value'

write_encrypted.py

Lines changed: 59 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import argparse
33

44
from getpass import getpass
5+
from urllib.parse import urlparse
56
from Cryptodome.Random import get_random_bytes
67
from base64 import (
78
b64decode,
@@ -11,65 +12,79 @@
1112
Graph,
1213
URIRef,
1314
)
14-
from pod_helper import (
15+
from solidpod_helper import (
1516
gen_master_key,
17+
gen_verify_key,
1618
encrypt,
19+
parse_ttl,
1720
apps_terms,
1821
path_pred,
1922
iv_pred,
20-
session_key_pred,
21-
data_pred,
23+
verify_key_pred,
24+
indi_key_pred,
25+
enc_data_pred,
2226
server_path,
2327
)
2428

2529

26-
def upload_file(file_content, file_path, server_url, master_key):
30+
def upload_file(file_url, file_content, master_key):
2731
# Encrypt file content
2832

2933
print('Encrypt content ...')
30-
session_key = get_random_bytes(32)
34+
indi_key = get_random_bytes(32)
3135
data_iv = get_random_bytes(16)
32-
enc_data_b64 = encrypt(file_content, session_key, data_iv)
36+
enc_data_b64 = encrypt(file_content, indi_key, data_iv)
3337

3438
# Write encrypted content to file in POD
3539

3640
print('Write encrypted content ...')
41+
42+
r = urlparse(file_url)
43+
items = r.path.split('/')[1:] # r.path: '/pod_name/app_name/data/data_file_path'
44+
assert len(items) >= 4
45+
assert items[2] == 'data'
46+
relative_file_path = '/'.join(items[1:])
3747
g = Graph()
38-
file_url = f'{server_url}{file_path}'
3948
data_iv_b64 = b64encode(data_iv).decode('ascii')
40-
query = f'INSERT DATA {{<{file_url}> <{apps_terms}{path_pred}> "{file_path}"; ' + \
49+
query = f'INSERT DATA {{<{file_url}> <{apps_terms}{path_pred}> "{relative_file_path}"; ' + \
4150
f'<{apps_terms}{iv_pred}> "{data_iv_b64}"; ' + \
42-
f'<{apps_terms}{data_pred}> "{enc_data_b64}".}};'
51+
f'<{apps_terms}{enc_data_pred}> "{enc_data_b64}".}};'
4352
g.update(query)
4453
ttl_str = g.serialize(format='turtle')
45-
abs_path = f'{server_path}{file_path}'
54+
abs_path = f'{server_path}{"/".join(items)}'
4655
with open(abs_path, 'w') as f:
4756
f.write(ttl_str)
4857

4958
# Add session key to ind-keys.ttl
5059

5160
print('Add encrypted session key to ind-keys.ttl ...')
52-
add_session_key(file_path, server_url, session_key, master_key)
61+
add_indi_key(file_url, indi_key, master_key)
62+
63+
def add_indi_key(file_url, indi_key, master_key):
64+
# Add encrypted individual key to ind-keys.ttl
5365

54-
def add_session_key(file_path, server_url, session_key, master_key):
55-
# Add encrypted session key to ind-keys.ttl
66+
# Encrypt the individual key
5667

57-
# Encrypt the session key
58-
session_key_iv = get_random_bytes(16)
59-
session_key_b64 = b64encode(session_key).decode('ascii')
60-
enc_session_key_b64 = encrypt(session_key_b64, master_key, session_key_iv)
68+
indi_key_iv = get_random_bytes(16)
69+
indi_key_b64 = b64encode(indi_key).decode('ascii')
70+
enc_indi_key_b64 = encrypt(indi_key_b64, master_key, indi_key_iv)
6171

6272
# Parse the ind-keys.ttl file
63-
items = file_path.split('/')
73+
74+
r = urlparse(file_url)
75+
items = r.path.split('/')[1:] # r.path: '/pod_name/app_name/data/data_file_path'
6476
assert len(items) >= 4
6577
pod_name = items[0]
6678
app_name = items[1]
79+
assert items[2] == 'data'
80+
server_url = f'{r.scheme}://{r.netloc}'
81+
relative_file_path = '/'.join(items[1:])
6782
ind_key_path = f'{server_path}{pod_name}/{app_name}/encryption/ind-keys.ttl'
6883
g = Graph()
6984
g.parse(ind_key_path)
7085

7186
# Replace file:///POD_DIR prefix with server URL
72-
file_url = f'{server_url}{file_path}'
87+
7388
for s, p, o in g:
7489
prefix = f'file://{server_path}'
7590
if str(s).startswith(prefix):
@@ -80,15 +95,16 @@ def add_session_key(file_path, server_url, session_key, master_key):
8095
if str(s) == file_url:
8196
g.remove((s, p, o))
8297

83-
# Add encrypted session key to ind-keys.ttl
84-
session_key_iv_b64 = b64encode(session_key_iv).decode('ascii')
85-
fpath = '/'.join(items[1:])
86-
query = f'INSERT DATA {{<{file_url}> <{apps_terms}{path_pred}> "{fpath}"; ' + \
87-
f'<{apps_terms}{iv_pred}> "{session_key_iv_b64}"; ' + \
88-
f'<{apps_terms}{session_key_pred}> "{enc_session_key_b64}".}};'
98+
# Add encrypted individual key to ind-keys.ttl
99+
100+
indi_key_iv_b64 = b64encode(indi_key_iv).decode('ascii')
101+
query = f'INSERT DATA {{<{file_url}> <{apps_terms}{path_pred}> "{relative_file_path}"; ' + \
102+
f'<{apps_terms}{iv_pred}> "{indi_key_iv_b64}"; ' + \
103+
f'<{apps_terms}{indi_key_pred}> "{enc_indi_key_b64}".}};'
89104
g.update(query)
90105

91106
# Write back ind-keys.ttl
107+
92108
ttl_str = g.serialize(format='turtle', base=server_url)
93109
with open(ind_key_path, 'w') as f:
94110
f.write(ttl_str)
@@ -97,25 +113,33 @@ def add_session_key(file_path, server_url, session_key, master_key):
97113
parser = argparse.ArgumentParser(description='Process secret and path arguments')
98114
parser.add_argument('srcpath', help='Path of the file to be uploaded')
99115
parser.add_argument('destpath', help='Path of the destination file within data folder')
100-
parser.add_argument('--server', help='Name of the POD server', default='pods.test.solidcommunity.au')
101116
args = parser.parse_args()
102117

103-
server = args.server
104-
server_url = f'https://{args.server}/'
105-
106118
# Destination file path format: server_path/pod_name/app_name/data/data_file
107119

108120
dest_path = os.path.abspath(args.destpath)
109121
assert dest_path.startswith(server_path)
110122
items = dest_path.replace(server_path, '').split('/')
111123
assert len(items) >= 4
124+
pod_name = items[0]
125+
app_name = items[1]
112126
assert items[2] == 'data'
113-
file_path = '/'.join(items) # pod_name/app_name/data/data_file
127+
app_path = f'{server_path}{pod_name}/{app_name}'
114128

115129
# Generate master encryption key from the user-provided security key
116130

117131
security_key_str = getpass(prompt='Security Key: ')
118132
master_key = gen_master_key(security_key_str)
133+
verify_key = gen_verify_key(security_key_str)
134+
135+
# Verify security key
136+
137+
_map = parse_ttl(f'{app_path}/encryption/enc-keys.ttl')
138+
enc_key_url, enc_key_map = list(_map.items())[0]
139+
verify_key_stored = enc_key_map[verify_key_pred]
140+
if verify_key.decode('utf-8') != verify_key_stored:
141+
print('ERROR: Incorrect security key (verification failed).')
142+
sys.exit(0)
119143

120144
# Read content of source file
121145

@@ -124,5 +148,9 @@ def add_session_key(file_path, server_url, session_key, master_key):
124148
with open(src_path, 'r') as f:
125149
file_content = f.read()
126150

127-
upload_file(file_content, file_path, server_url, master_key)
151+
# Write encrypted content to POD
152+
153+
r = urlparse(enc_key_url)
154+
file_url = '/'.join([f'{r.scheme}://{r.netloc}'] + items)
155+
upload_file(file_url, file_content, master_key)
128156

0 commit comments

Comments
 (0)