Skip to content

Commit 8dd9698

Browse files
author
skelly
committed
Refactored server, minor changes in client
1 parent 83a77ac commit 8dd9698

File tree

5 files changed

+104
-80
lines changed

5 files changed

+104
-80
lines changed

client/src/fraction.c

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ void print_fraction(fraction_t fraction) {
104104
printf("Data size: %lu\n\n", fraction.data_size);
105105
}
106106

107-
uint32_t calc_crc(fraction_t *frac){
107+
int calc_crc(fraction_t *frac){
108108
uint8_t buffer[sizeof(frac->magic) + sizeof(frac->index) + sizeof(frac->iv) + frac->data_size];
109109
size_t offset = 0;
110110

@@ -122,9 +122,7 @@ uint32_t calc_crc(fraction_t *frac){
122122

123123
uint32_t calculated_crc = crc32(buffer, offset);
124124

125-
if (calculated_crc == frac->crc) {
126-
printf("Checksum correct\n");
127-
} else {
125+
if (calculated_crc != frac->crc) {
128126
printf("Checksum incorrect\n");
129127
printf("Checksum generated: %08X\n", calculated_crc);
130128
printf("Checksum from fraction: %08X\n\n", frac->crc);
@@ -134,14 +132,15 @@ uint32_t calc_crc(fraction_t *frac){
134132
}
135133

136134
int check_fractions(fraction_t *fraction, size_t size){
135+
int res = 0;
137136
for(size_t i = 0; i < size; i++){
138137
if (!calc_crc(&fraction[i])) {
139138
fprintf(stderr, "Failed to validate integrity of fraction:\n");
140139
print_fraction(fraction[i]);
141-
return 1;
140+
res += 1;
142141
}
143142
}
144-
return 0;
143+
return res;
145144
}
146145

147146
void fraction_free(fraction_t *fraction) {

client/src/main.c

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -84,11 +84,8 @@ int main(void) {
8484
fprintf(stderr, "Failed to download fraction\n");
8585
}
8686
}
87-
87+
puts("Downloaded fractions");
8888
qsort(fractions, lines_read, sizeof(fraction_t), compare_fractions);
89-
for (int i = 0; i < lines_read; i++) {
90-
print_fraction(fractions[i]);
91-
}
9289

9390
if (check_fractions(fractions, lines_read)) { // if this works, s0s4 and skelly is to blame!
9491
fprintf(stderr, "Fractions check failed\n");
@@ -98,6 +95,7 @@ int main(void) {
9895
http_free(&http_post_res);
9996
goto cleanup;
10097
}
98+
puts("Verified fractions");
10199

102100
if (http_post(sfd, "/deadbeef", "plain/text", "{'downloaded':true}",
103101
&http_post_res) != HTTP_SUCCESS) {

server/fraction.py

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
from dataclasses import dataclass, field
2+
import zlib
3+
import struct
4+
from typing import Literal
5+
import logging
6+
7+
@dataclass
8+
class Fraction:
9+
"""Dataclass to represent a fraction"""
10+
magic: int
11+
index: int
12+
iv: bytes
13+
data: bytes
14+
15+
_crc: int = field(init=False, repr=False)
16+
_generated_crc: bool = field(init=False, repr=False, default=False)
17+
18+
def __post_init__(self) -> None:
19+
"""Post-initialization: calculate CRC after dataclass initialization"""
20+
self.calculate_crc()
21+
22+
def header_to_bytes(
23+
self, endianess: Literal["big", "little"] = "little", include_crc=True
24+
) -> bytes:
25+
"""
26+
Convert the header information of the fraction to bytes.
27+
28+
endianess: Endianness to use (big, little)
29+
include_crc: Include CRC in the returned data (default: True)
30+
"""
31+
end = ">" if endianess == "big" else "<"
32+
fmt = f"{end}II16s{'I' if include_crc else ''}"
33+
34+
# Pack the header, optionally including CRC
35+
args = [self.magic, self.index, self.iv]
36+
if include_crc:
37+
args.append(self.crc)
38+
39+
header_data = struct.pack(fmt, *args)
40+
logging.debug(f"Header data [{self.index}]: {header_data.hex()}")
41+
return header_data
42+
43+
def calculate_crc(self) -> None:
44+
"""Calculate and store the CRC checksum of the fraction"""
45+
crc_data = self.header_to_bytes(include_crc=False) + self.data
46+
self._crc = zlib.crc32(crc_data)
47+
self._generated_crc = True
48+
logging.debug(f"Calculated CRC for fraction [{self.index}]: {self._crc:#x}")
49+
50+
@property
51+
def crc(self) -> int:
52+
"""Get or calculate the CRC if not already generated"""
53+
if not self._generated_crc:
54+
self.calculate_crc()
55+
return self._crc
56+
57+
@property
58+
def data_size(self) -> int:
59+
"""Return the size of the data"""
60+
return len(self.data)

server/fractionator.py

Lines changed: 37 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -1,65 +1,11 @@
11
import os
22
import io
3-
from typing import Callable, Optional
3+
from typing import Optional
44
import logging
55
import secrets
66
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
77
import utils
8-
from dataclasses import dataclass, field
9-
import zlib
10-
import struct
11-
from typing import Literal
12-
13-
14-
@dataclass
15-
class Fraction:
16-
"""Dataclass to represent a fraction"""
17-
18-
magic: int
19-
index: int
20-
iv: bytes
21-
_crc: int = field(init=False, repr=False)
22-
data: bytes
23-
24-
def header_to_bytes(
25-
self, endianess: Literal["big", "little"] = "little", crc=True
26-
) -> bytes:
27-
"""
28-
Convert the header information of the fraction to bytes
29-
30-
endianess: Endianess to use (big, little)
31-
crc: Include CRC in the returned data (default: True)
32-
"""
33-
end = ">" if endianess == "big" else "<"
34-
fmt = f"{end}II16sI" if crc else f"{end}II16s"
35-
36-
args = [fmt, self.magic, self.index, self.iv]
37-
if crc:
38-
args.append(self._crc)
39-
40-
header_data = struct.pack(*args);
41-
logging.info(f"Header data: {header_data.hex()}")
42-
return header_data
43-
44-
def calculate_crc(self) -> None:
45-
"""Calculate the CRC checksum of the fraction"""
46-
crc_data = self.header_to_bytes(crc=False) + self.data
47-
self._crc = zlib.crc32(crc_data)
48-
49-
@property
50-
def crc(self) -> int:
51-
if not self._crc:
52-
self.calculate_crc()
53-
54-
return self._crc
55-
56-
@property
57-
def data_size(self) -> int:
58-
return len(self.data)
59-
60-
def __post_init__(self) -> None:
61-
self.calculate_crc()
62-
8+
from fraction import Fraction
639

6410
class Fractionator:
6511
MAGIC: int = 0xDEADBEEF
@@ -82,16 +28,33 @@ def __init__(
8228
self._key: Optional[bytes] = Fractionator.validate_aes_key(
8329
key
8430
) # AES-256 cryptographic key
31+
self._mode = modes.CFB
32+
self._algorithm = algorithms.AES256
33+
self._cipher = None
34+
35+
@property
36+
def cipher(self) -> Cipher:
37+
if not self._cipher:
38+
if not self._key or not self._iv:
39+
raise ValueError(f"Missing key or IV (_key:{self._key}, _iv:{self._iv})")
40+
41+
self._cipher = Cipher(self._algorithm(self._key), self._mode(self._iv))
42+
43+
return self._cipher
8544

8645
def open_reading_stream(self) -> None:
8746
"""
8847
Opens a reading stream to the file specified in self._path.
8948
If a stream is already open, this function has no effect
9049
"""
91-
if self._buf_reader is None or self._buf_reader.closed:
92-
self._buf_reader = open(self._path, "rb")
93-
logging.debug(f"Opened reading stream to {self._path}.")
94-
return
50+
try:
51+
if self._buf_reader is None or self._buf_reader.closed:
52+
self._buf_reader = open(self._path, "rb")
53+
logging.debug(f"Opened reading stream to {self._path}.")
54+
return
55+
except FileNotFoundError as err:
56+
logging.error(f"File not found: {self._path}")
57+
raise err
9558

9659
def _make_fraction(self, index: int) -> None:
9760
"""Read from the object-file and generate a fraction"""
@@ -103,19 +66,23 @@ def _make_fraction(self, index: int) -> None:
10366
Fractionator.CHUNK_SIZE
10467
) # don't use peek, as it does not advance the position in the file
10568

106-
# Generate an IV and encrypt the chunk
107-
self._iv = secrets.token_bytes(
108-
16
109-
) # initialization vector for AES-256 encryption
110-
encrypted_data = self.do_aes_operation(data, True) # encrypt chunk
111-
69+
# generate iv and encrypt the chunk
70+
encrypted_data = self._encrypt_chunk(data)
71+
11272
# Create a fraction instance and add it to self._fractions
11373
fraction = Fraction(
11474
magic=Fractionator.MAGIC, index=index, iv=self._iv, data=encrypted_data
11575
)
11676
self._fractions.append(fraction)
11777
logging.debug(f"Created fraction #{fraction.index}")
11878

79+
def _encrypt_chunk(self, data: bytes) -> bytes:
80+
self._iv = self._generate_iv()
81+
return self.do_aes_operation(data, True)
82+
83+
def _generate_iv(self) -> bytes:
84+
return secrets.token_bytes(16)
85+
11986
def make_fractions(self) -> None:
12087
"""Iterate through the Fractionator object file specified in self._path and generate Fraction objects"""
12188
size = os.path.getsize(self._path)
@@ -163,11 +130,11 @@ def load_backup(self, backup_path: str):
163130
with open(backup_path, "r") as f:
164131
self.fraction_paths = [line.strip() for line in f]
165132
logging.debug(
166-
f"[debug: _load_backup] Loaded {len(self.fraction_paths)} paths from backup."
133+
f"Loaded {len(self.fraction_paths)} paths from backup."
167134
)
168135

169136
except OSError as e:
170-
logging.error(f"[error: _load_backup] Failed to load backup: {e}")
137+
logging.error(f"Failed to load backup: {e}")
171138
return []
172139

173140
def _clean_fraction(self, path: str):
@@ -190,12 +157,13 @@ def clean_fractions(self) -> None:
190157
self.fraction_paths = []
191158
logging.info("Done.")
192159

160+
193161
def do_aes_operation(self, data: bytes, op: bool) -> bytes:
194162
"""Perform an AES-256 operation on given data (encryption [op=True]/decryption [op=False])"""
195163
if not self._key or not self._iv:
196164
raise ValueError(f"Missing key or IV (_key:{self._key}, _iv:{self._iv})")
197165

198-
cipher = Cipher(algorithms.AES(self._key), modes.OFB(self._iv))
166+
cipher = self.cipher
199167
operator = cipher.encryptor() if op else cipher.decryptor()
200168

201169
return operator.update(data) + operator.finalize()

server/utils.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import random
22
import string
33

4-
54
def random_string(n: int = 16, sample: str = string.ascii_lowercase + string.digits):
65
"""Returns a random string using the characters defined in sample"""
76
return "".join(random.choices(sample, k=n))

0 commit comments

Comments
 (0)