Dozownik medyczny - nie ufaj użytkownikowi
Pamiętasz tę scenę w Project Hail Mary [uwaga, spoiler] autorstwa Andy'ego Weira, gdzie dr Ryland Grace przechytrza komputer statku? Cierpi, prosi o środki przeciwbólowe, a komputer odmawia, ponieważ musi odczekać trzy godziny między dawkami. Jego rozwiązanie? Po prostu każe komputerowi przesunąć zegar o trzy godziny do przodu i pyta ponownie. Działa i Grace dostaje swój lek, zadowolony zauważając: "Co za głupi system."
Czytanie tego przywołało wspomnienia z końca lat 90., kiedy cofałem datę w moim PC, aby wycisnąć kilka dodatkowych dni z oprogramowania testowego. Brzmi znajomo? Wszyscy myśleliśmy, że jesteśmy sprytnymi hakerami. Okazuje się, że po prostu wykorzystywaliśmy tę samą fundamentalną wadę, którą Grace odkrył podczas swojej międzygwiezdnej podróży: ufanie czasowi systemowemu w krytycznych decyzjach jest jak ufanie wymówce ucznia "mój pies zjadł mi pracę domową".
Kiedy manipulacja czasem staje się poważna
Podczas gdy oszukiwanie oprogramowania testowego było nieszkodliwym buntem nastolatków, ta podatność spowodowała prawdziwe katastrofy. W 2012 roku główna firma handlowa straciła 440 milionów dolarów w 45 minut [źródło], między innymi dlatego, że logika ich systemu opierała się na założeniach czasowych, które zawiodły w katastrofalny sposób. Co poważniejsze, kilka urządzeń medycznych zostało wycofanych, gdy badacze odkryli, że zmiana czasu systemowego może ominąć limity bezpieczeństwa dotyczące dawek leków lub napromieniowania.
Problem nie polega na tym, że komputery są głupie - chodzi o to, że programiści czasami zapominają, że czas nie jest tak niezmienny, jak byśmy chcieli.
Budujemy nasz kosmiczny dozownik leków
Stwórzmy dozownik leków, który sprawiłby, że dr Ryland Grace musiałby bardziej się postarać o swoje środki przeciwbólowe. Zaczniemy od naiwnego podejścia, którego prawdopodobnie używał komputer Hail Mary, a następnie stopniowo uczynimy go bardziej odpornym na niechciane manipulacje.
Podatne podejście: ufanie czasowi systemowemu
from datetime import datetime, timedelta
import json
class NaiveMedicationDispenser:
"""Oryginalny dozownik z Hail Mary - łatwy do zhakowania"""
def __init__(self):
self.dose_history = []
self.minimum_interval = timedelta(hours=3)
self.max_daily_doses = 6
def request_dose(self, current_time=None):
# Ufamy dowolnemu podanemu czasowi (lub czasowi systemowemu)
if current_time is None:
current_time = datetime.now()
# Sprawdzamy, czy minęło wystarczająco dużo czasu
if self.dose_history:
last_dose = self.dose_history[-1]
time_since_last = current_time - last_dose
if time_since_last < self.minimum_interval:
wait_time = self.minimum_interval - time_since_last
return f"DOSTĘP ZABRONIONY: Poczekaj jeszcze {wait_time.seconds // 60} minut"
# Sprawdzamy dzienny limit (również podatne na hack!)
today_doses = [d for d in self.dose_history
if d.date() == current_time.date()]
if len(today_doses) >= self.max_daily_doses:
return "DOSTĘP ZABRONIONY: Osiągnięto dzienny limit"
# Wydajemy lek
self.dose_history.append(current_time)
return "WYDANO: Jedna dawka kosmicznej aspiryny dostarczona"
# Hack Grace'a w akcji:
dispenser = NaiveMedicationDispenser()
print(dispenser.request_dose(datetime.now())) # Pierwsza dawka działa
print(dispenser.request_dose(datetime.now())) # Odmowa - za wcześnie
# "Sprytny" hack:
future_time = datetime.now() + timedelta(hours=3, minutes=1)
print(dispenser.request_dose(future_time)) # Działa! Komputer jest "głupi"
Widzisz problem? Ufamy zewnętrznemu wejściu w czymś krytycznym dla bezpieczeństwa. To jak mieć drzwi zabezpieczające, które pytają odwiedzających, czy są upoważnieni do wejścia.
Lepsze podejście: czas monotoniczny (tylko w pamięci)
Pythonowy time.monotonic() zapewnia zegar, który nie może się cofnąć i nie jest podatny na zmiany czasu systemowego. Pomyśl o nim jak o stopperze, który uruchamia się, gdy program startuje i po prostu liczy w górę, odporny na sztuczki Grace'a.
Ważne ograniczenie: Czas monotoniczny jest niezawodny tylko w ramach pojedynczej sesji programu. Resetuje się po restarcie programu, więc nie możesz zapisywać tych wartości na dysk. Dla trwałego egzekwowania będziesz potrzebować bezpiecznego podejścia pokazanego później.
import time
from collections import deque
class MonotonicMedicationDispenser:
"""Używa czasu monotonicznego - nie da się oszukać zmianą zegara systemowego
Uwaga: To działa tylko w ramach pojedynczej sesji programu.
Czas monotoniczny resetuje się przy restarcie, więc nie zapisuj tych wartości!
"""
def __init__(self):
self.dose_times = deque()
self.minimum_interval_seconds = 3 * 3600 # 3 godziny
self.daily_window_seconds = 24 * 3600
self.max_daily_doses = 6
def request_dose(self):
current_monotonic = time.monotonic()
# Sprawdzamy odstęp od ostatniej dawki
if self.dose_times:
time_since_last = current_monotonic - self.dose_times[-1]
if time_since_last < self.minimum_interval_seconds:
wait_seconds = self.minimum_interval_seconds - time_since_last
return f"DOSTĘP ZABRONIONY: Poczekaj jeszcze {wait_seconds // 60:.0f} minut"
# Sprawdzamy dawki z ostatnich 24 godzin (przesuwane okno)
cutoff_time = current_monotonic - self.daily_window_seconds
recent_doses = [t for t in self.dose_times if t > cutoff_time]
if len(recent_doses) >= self.max_daily_doses:
oldest_expiry = recent_doses[0] + self.daily_window_seconds
wait_seconds = oldest_expiry - current_monotonic
return f"DOSTĘP ZABRONIONY: Dzienny limit. Kolejna dawka za {wait_seconds // 3600:.1f} godzin"
# Wydajemy i zapisujemy
self.dose_times.append(current_monotonic)
return "WYDANO: Lek wydany bezpiecznie"
# Grace próbuje swojej sztuczki:
dispenser = MonotonicMedicationDispenser()
print(dispenser.request_dose()) # Pierwsza dawka działa
# Nawet jeśli Grace zmieni czas systemowy tutaj, to nie pomoże
print(dispenser.request_dose()) # Nadal odmowa - czas monotoniczny się nie przejmuje
Piękno czasu monotonicznego polega na tym, że jest prosty i niezawodny do sprawdzania odstępów w pamięci podczas pojedynczej sesji. Grace może ustawić zegar systemowy na rok 3000 lub cofnąć do 1970, ale czas monotoniczny po prostu liczy dalej. Jednak jeśli program się zrestartuje, wszystkie zakłady są nieważne - dlatego naprawdę krytyczne systemy potrzebują czegoś bardziej solidnego.
Najlepsze podejście: bezpieczny dziennik zdarzeń z prawidłową kryptografią
Dla naprawdę krytycznych systemów (jak prawdziwe urządzenia medyczne lub statki kosmiczne) potrzebujemy czegoś, co przetrwa restarty i wykrywa manipulacje. Ta wersja używa prawidłowego uwierzytelniania HMAC, przechowuje rzeczywiste znaczniki czasu i utrzymuje dziennik audytu tylko do dołączania.
import hmac
import hashlib
import json
import time
import os
from datetime import datetime, timezone
from pathlib import Path
class SecureMedicationDispenser:
"""Używa kryptograficznego łańcucha zdarzeń z prawidłowym uwierzytelnianiem HMAC
Ta wersja:
- Używa HMAC do prawidłowego uwierzytelniania wiadomości
- Przechowuje znaczniki czasu UTC dla trwałości po restarcie
- Używa czasu monotonicznego do egzekwowania odstępów w sesji
- Utrzymuje dziennik zdarzeń wykrywający ingerencję
- Wczytuje sekret ze środowiska lub generuje bezpiecznie
"""
def __init__(self, log_file="medication_log.json"):
self.log_file = Path(log_file)
self.minimum_interval_seconds = 3 * 3600
self.max_daily_doses = 6
# Wczytujemy lub generujemy bezpieczny sekret urządzenia
self.device_secret = self._get_or_create_secret()
# Śledzimy czas monotoniczny tylko dla bieżącej sesji
self.session_start_monotonic = time.monotonic()
self.session_start_utc = datetime.now(timezone.utc)
# Wczytujemy istniejący dziennik zdarzeń
self.event_log = self._load_log()
def _get_or_create_secret(self):
"""Pobiera sekret ze środowiska lub generuje bezpiecznie"""
secret = os.environ.get('MEDICATION_DISPENSER_SECRET')
if secret:
return secret.encode('utf-8')
# Do celów demonstracyjnych generujemy losowy sekret
# W produkcji: używaj modułu bezpieczeństwa sprzętowego lub bezpiecznego przechowywania kluczy
secret = os.urandom(32)
print("Ostrzeżenie: Używanie losowo wygenerowanego sekretu. Ustaw zmienną środowiskową MEDICATION_DISPENSER_SECRET dla produkcji.")
return secret
def _compute_hmac(self, message):
"""Oblicza HMAC-SHA256 wiadomości używając sekretu urządzenia"""
return hmac.new(
self.device_secret,
message.encode('utf-8'),
hashlib.sha256
).hexdigest()
def _load_log(self):
"""Wczytuje i weryfikuje istniejący dziennik zdarzeń"""
if not self.log_file.exists():
return []
try:
with open(self.log_file, 'r') as f:
events = json.load(f)
# Weryfikujemy integralność łańcucha
for i, event in enumerate(events):
# Rekonstruujemy wiadomość, która została podpisana
event_copy = event.copy()
stored_hmac = event_copy.pop('hmac')
event_string = json.dumps(event_copy, sort_keys=True)
expected_hmac = self._compute_hmac(event_string)
if expected_hmac != stored_hmac:
raise ValueError(f"Weryfikacja HMAC nie powiodła się dla zdarzenia {i}")
return events
except Exception as e:
print(f"Błąd wczytywania dziennika: {e}")
return []
def _save_log(self):
"""Zapisuje dziennik zdarzeń na dysk"""
with open(self.log_file, 'w') as f:
json.dump(self.event_log, f, indent=2)
def _create_event(self, event_type, success, reason=""):
"""Tworzy rekord zdarzenia wykrywający ingerencję z prawidłowym HMAC"""
# Używamy czasu UTC dla trwałości po restarcie
utc_now = datetime.now(timezone.utc)
event = {
'index': len(self.event_log),
'type': event_type,
'success': success,
'reason': reason,
'utc_timestamp': utc_now.isoformat(),
'previous_hmac': self.event_log[-1]['hmac'] if self.event_log else "genesis"
}
# Tworzymy HMAC danych zdarzenia (pomijając sam HMAC)
event_string = json.dumps(event, sort_keys=True)
event['hmac'] = self._compute_hmac(event_string)
return event
def _get_utc_now(self):
"""Pobiera bieżący czas UTC jako datetime ze strefą czasową"""
return datetime.now(timezone.utc)
def request_dose(self):
"""Żąda leku z pełnym śladem audytu"""
utc_now = self._get_utc_now()
# Pobieramy udane zdarzenia wydania dawki
successful_doses = [e for e in self.event_log
if e['type'] == 'DOSE_REQUEST' and e['success']]
# Sprawdzamy ograniczenie odstępu używając najnowszej dawki
if successful_doses:
last_dose_time = datetime.fromisoformat(
successful_doses[-1]['utc_timestamp']
)
elapsed = (utc_now - last_dose_time).total_seconds()
if elapsed < self.minimum_interval_seconds:
event = self._create_event(
'DOSE_REQUEST', False,
f"Naruszenie odstępu: {elapsed:.0f}s < {self.minimum_interval_seconds}s"
)
self.event_log.append(event)
self._save_log()
return f"ODMOWA: Poczekaj {(self.minimum_interval_seconds - elapsed) // 60:.0f} minut"
# Sprawdzamy dzienny limit = ostatnie 24 godziny, nie reset o północy
cutoff = utc_now.timestamp() - (24 * 3600)
recent_doses = [
e for e in successful_doses
if datetime.fromisoformat(e['utc_timestamp']).timestamp() > cutoff
]
if len(recent_doses) >= self.max_daily_doses:
event = self._create_event(
'DOSE_REQUEST', False,
f"Osiągnięto dzienny limit: {len(recent_doses)}/{self.max_daily_doses}"
)
self.event_log.append(event)
self._save_log()
return "ODMOWA: Osiągnięto dzienny limit"
# Sukces - wydajemy lek
event = self._create_event('DOSE_REQUEST', True, "Dawka wydana")
self.event_log.append(event)
self._save_log()
return "WYDANO: Lek dostarczony"
def audit_log(self, num_events=5):
"""Pokazuje ostatnie zdarzenia do debugowania lub przeglądu medycznego"""
for event in self.event_log[-num_events:]:
status = "✓" if event['success'] else "✗"
timestamp = datetime.fromisoformat(event['utc_timestamp'])
local_time = timestamp.astimezone()
print(f"{status} {local_time.strftime('%Y-%m-%d %H:%M:%S %Z')}: {event['reason']}")
def verify_integrity(self):
"""Weryfikuje, czy cały łańcuch zdarzeń nie został zmanipulowany"""
try:
for i, event in enumerate(self.event_log):
event_copy = event.copy()
stored_hmac = event_copy.pop('hmac')
event_string = json.dumps(event_copy, sort_keys=True)
expected_hmac = self._compute_hmac(event_string)
if expected_hmac != stored_hmac:
return False, f"Niezgodność HMAC dla zdarzenia {i}"
return True, "Integralność dziennika zdarzeń zweryfikowana"
except Exception as e:
return False, f"Błąd weryfikacji: {e}"
Testowanie bezpiecznego dozownika
# SKRYPT TESTOWY - Uruchom go, aby zobaczyć działanie!
if __name__ == "__main__":
print("=== TESTOWANIE BEZPIECZNEGO DOZOWNIKA LEKÓW ===\n")
# Tworzymy dozownik z krótszymi odstępami do testów
dispenser = SecureMedicationDispenser("test_medication_log.json")
dispenser.minimum_interval_seconds = 10 # Nadpisujemy dla szybkich testów
print("Test 1: Pierwsza dawka (powinno zadziałać)")
print(f"Wynik: {dispenser.request_dose()}\n")
print("Test 2: Natychmiastowa druga dawka (powinno się nie udać)")
print(f"Wynik: {dispenser.request_dose()}\n")
print("Test 3: Grace próbuje zmienić czas systemowy...")
print("(Uwaga: To nie pomoże - używamy znaczników czasu UTC z HMAC!)")
print(f"Wynik: {dispenser.request_dose()}\n")
print("Czekamy 10 sekund na następną ważną dawkę...")
time.sleep(10)
print("Test 4: Po odczekaniu (powinno zadziałać)")
print(f"Wynik: {dispenser.request_dose()}\n")
print("\n=== DZIENNIK AUDYTU ===")
dispenser.audit_log()
print("\n=== TESTOWANIE DZIENNEGO LIMITU ===")
print("Symulujemy osiągnięcie dziennego limitu...")
# Tworzymy nowy dozownik z 1-sekundowymi odstępami do szybkich testów
quick_dispenser = SecureMedicationDispenser("test_daily_limit.json")
quick_dispenser.minimum_interval_seconds = 1
quick_dispenser.max_daily_doses = 3 # Niższy limit do testów
for i in range(5):
result = quick_dispenser.request_dose()
print(f"Próba dawki {i+1}: {result}")
if i < 4: # Nie czekamy po ostatniej próbie
time.sleep(1.1)
print("\n=== DZIENNIK AUDYTU DLA TESTU DZIENNEGO LIMITU ===")
quick_dispenser.audit_log()
print("\n=== WERYFIKACJA WYKRYWANIA INGERENCJI ===")
is_valid, message = quick_dispenser.verify_integrity()
print(f"Sprawdzenie integralności: {message}")
print("\nŁańcuch zdarzeń (każdy HMAC zależy od poprzedniego):")
for event in quick_dispenser.event_log[-3:]:
print(f"Zdarzenie {event['index']}: HMAC={event['hmac'][:16]}...")
# Sprzątanie plików testowych
import os
for f in ["test_medication_log.json", "test_daily_limit.json"]:
if os.path.exists(f):
os.remove(f)
To podejście łączy kilka najlepszych praktyk bezpieczeństwa:
- Uwierzytelnianie HMAC zamiast prostego hashowania - prawidłowe kryptograficzne uwierzytelnianie wiadomości
- Znaczniki czasu UTC, które przetrwają restarty i mogą być poddane audytowi
- Bezpieczne zarządzanie kluczami używające zmiennych środowiskowych zamiast zakodowanych na stałe sekretów
- Pełnowymiarowe skróty kryptograficzne (256 bitów, nie obcięte do 64 bitów)
- Łańcuch zdarzeń wykrywający ingerencję, gdzie każde zdarzenie zawiera HMAC poprzedniego zdarzenia
- Trwałe przechowywanie, które przetrwa restarty programu
Prawdziwa lekcja
Kiedy Andy Weir pisał tę scenę, prawdopodobnie doskonale wiedział, co robi. Jako programista sam rozumiał, że ta podatność jest wszędzie w oprogramowaniu.
Progresja od naiwnego sprawdzania datetime przez czas monotoniczny po kryptograficznie zabezpieczone sourcing zdarzeń odzwierciedla to, jak przemysł oprogramowania ewoluował w myśleniu o bezpieczeństwie i niezawodności. Nauczyliśmy się (czasami boleśnie), że w krytycznych systemach nie można ufać niczemu, czym można manipulować zewnętrznie.
Dla oprogramowania eksploracji kosmosu, gdzie życie Grace'a dosłownie zależy od prawidłowego działania, te wzorce nie są tylko najlepszymi praktykami - są różnicą między udaną misją a katastrofą. Ale nawet w naszych ziemskich projektach Pythona, stosowanie tych zasad czyni nasz kod bardziej solidnym, bezpiecznym i profesjonalnym.
Kluczowe wnioski
- Nigdy nie ufaj czasowi systemowemu w decyzjach bezpieczeństwa - można nim manipulować
- Używaj czasu monotonicznego do sprawdzania odstępów w pamięci - ale pamiętaj, że resetuje się po restarcie
- Używaj prawidłowego HMAC do uwierzytelniania - nie konkatenacji stringów z SHA256
- Przechowuj znaczniki czasu UTC dla trwałości - wartości czasu monotonicznego nie przetrwają restartów
- Nigdy nie koduj sekretów na stałe - używaj zmiennych środowiskowych lub bezpiecznego przechowywania kluczy
- Nie obcinaj skrótów kryptograficznych - zachowaj pełne 256 bitów bezpieczeństwa
Następnym razem, gdy będziesz budować coś wrażliwego na czas - czy to ograniczanie częstotliwości dla API, obsługę timeout sesji, czy nawet timer cooldownu w grze - przypomnij sobie Grace'a i jego środki przeciwbólowe. Zadaj sobie pytanie: "Czy zdesperowany astronauta mógłby to złamać?" Jeśli odpowiedź brzmi tak, czas wdrożyć odpowiednie środki bezpieczeństwa.
I spójrzmy prawdzie w oczy - gdyby Grace miał fizyczny dostęp do sprzętu komputera i pomoc inżynieryjną Rocky'ego, jest tylko tyle, ile oprogramowanie może zrobić, żeby go powstrzymać (głos Rocky'ego: źle, źle, źle)
import hmac
import hashlib
import json
import time
import os
from datetime import datetime, timezone
from pathlib import Path
class SecureMedicationDispenser:
"""Używa kryptograficznego łańcucha zdarzeń z prawidłowym uwierzytelnianiem HMAC
Ta wersja:
- Używa HMAC do prawidłowego uwierzytelniania wiadomości
- Przechowuje znaczniki czasu UTC dla trwałości po restarcie
- Używa czasu monotonicznego do egzekwowania odstępów w sesji
- Utrzymuje dziennik zdarzeń wykrywający ingerencję
- Wczytuje sekret ze środowiska lub generuje bezpiecznie
"""
def __init__(self, log_file="medication_log.json"):
self.log_file = Path(log_file)
self.minimum_interval_seconds = 3 * 3600
self.max_daily_doses = 6
# Wczytujemy lub generujemy bezpieczny sekret urządzenia
self.device_secret = self._get_or_create_secret()
# Śledzimy czas monotoniczny tylko dla bieżącej sesji
self.session_start_monotonic = time.monotonic()
self.session_start_utc = datetime.now(timezone.utc)
# Wczytujemy istniejący dziennik zdarzeń
self.event_log = self._load_log()
def _get_or_create_secret(self):
"""Pobiera sekret ze środowiska lub generuje bezpiecznie"""
secret = os.environ.get('MEDICATION_DISPENSER_SECRET')
if secret:
return secret.encode('utf-8')
# Do celów demonstracyjnych generujemy losowy sekret
# W produkcji: używaj modułu bezpieczeństwa sprzętowego lub bezpiecznego przechowywania kluczy
secret = os.urandom(32)
print("Ostrzeżenie: Używanie losowo wygenerowanego sekretu. Ustaw zmienną środowiskową MEDICATION_DISPENSER_SECRET dla produkcji.")
return secret
def _compute_hmac(self, message):
"""Oblicza HMAC-SHA256 wiadomości używając sekretu urządzenia"""
return hmac.new(
self.device_secret,
message.encode('utf-8'),
hashlib.sha256
).hexdigest()
def _load_log(self):
"""Wczytuje i weryfikuje istniejący dziennik zdarzeń"""
if not self.log_file.exists():
return []
try:
with open(self.log_file, 'r') as f:
events = json.load(f)
# Weryfikujemy integralność łańcucha
for i, event in enumerate(events):
# Rekonstruujemy wiadomość, która została podpisana
event_copy = event.copy()
stored_hmac = event_copy.pop('hmac')
event_string = json.dumps(event_copy, sort_keys=True)
expected_hmac = self._compute_hmac(event_string)
if expected_hmac != stored_hmac:
raise ValueError(f"Weryfikacja HMAC nie powiodła się dla zdarzenia {i}")
return events
except Exception as e:
print(f"Błąd wczytywania dziennika: {e}")
return []
def _save_log(self):
"""Zapisuje dziennik zdarzeń na dysk"""
with open(self.log_file, 'w') as f:
json.dump(self.event_log, f, indent=2)
def _create_event(self, event_type, success, reason=""):
"""Tworzy rekord zdarzenia wykrywający ingerencję z prawidłowym HMAC"""
# Używamy czasu UTC dla trwałości po restarcie
utc_now = datetime.now(timezone.utc)
event = {
'index': len(self.event_log),
'type': event_type,
'success': success,
'reason': reason,
'utc_timestamp': utc_now.isoformat(),
'previous_hmac': self.event_log[-1]['hmac'] if self.event_log else "genesis"
}
# Tworzymy HMAC danych zdarzenia (pomijając sam HMAC)
event_string = json.dumps(event, sort_keys=True)
event['hmac'] = self._compute_hmac(event_string)
return event
def _get_utc_now(self):
"""Pobiera bieżący czas UTC jako datetime ze strefą czasową"""
return datetime.now(timezone.utc)
def request_dose(self):
"""Żąda leku z pełnym śladem audytu"""
utc_now = self._get_utc_now()
# Pobieramy udane zdarzenia wydania dawki
successful_doses = [e for e in self.event_log
if e['type'] == 'DOSE_REQUEST' and e['success']]
# Sprawdzamy ograniczenie odstępu używając najnowszej dawki
if successful_doses:
last_dose_time = datetime.fromisoformat(
successful_doses[-1]['utc_timestamp']
)
elapsed = (utc_now - last_dose_time).total_seconds()
if elapsed < self.minimum_interval_seconds:
event = self._create_event(
'DOSE_REQUEST', False,
f"Naruszenie odstępu: {elapsed:.0f}s < {self.minimum_interval_seconds}s"
)
self.event_log.append(event)
self._save_log()
return f"ODMOWA: Poczekaj {(self.minimum_interval_seconds - elapsed) // 60:.0f} minut"
# Sprawdzamy dzienny limit = ostatnie 24 godziny, nie reset o północy
cutoff = utc_now.timestamp() - (24 * 3600)
recent_doses = [
e for e in successful_doses
if datetime.fromisoformat(e['utc_timestamp']).timestamp() > cutoff
]
if len(recent_doses) >= self.max_daily_doses:
event = self._create_event(
'DOSE_REQUEST', False,
f"Osiągnięto dzienny limit: {len(recent_doses)}/{self.max_daily_doses}"
)
self.event_log.append(event)
self._save_log()
return "ODMOWA: Osiągnięto dzienny limit"
# Sukces - wydajemy lek
event = self._create_event('DOSE_REQUEST', True, "Dawka wydana")
self.event_log.append(event)
self._save_log()
return "WYDANO: Lek dostarczony"
def audit_log(self, num_events=5):
"""Pokazuje ostatnie zdarzenia do debugowania lub przeglądu medycznego"""
for event in self.event_log[-num_events:]:
status = "✓" if event['success'] else "✗"
timestamp = datetime.fromisoformat(event['utc_timestamp'])
local_time = timestamp.astimezone()
print(f"{status} {local_time.strftime('%Y-%m-%d %H:%M:%S %Z')}: {event['reason']}")
def verify_integrity(self):
"""Weryfikuje, czy cały łańcuch zdarzeń nie został zmanipulowany"""
try:
for i, event in enumerate(self.event_log):
event_copy = event.copy()
stored_hmac = event_copy.pop('hmac')
event_string = json.dumps(event_copy, sort_keys=True)
expected_hmac = self._compute_hmac(event_string)
if expected_hmac != stored_hmac:
return False, f"Niezgodność HMAC dla zdarzenia {i}"
return True, "Integralność dziennika zdarzeń zweryfikowana"
except Exception as e:
return False, f"Błąd weryfikacji: {e}"
# SKRYPT TESTOWY - Uruchom go, aby zobaczyć działanie!
if __name__ == "__main__":
print("=== TESTOWANIE BEZPIECZNEGO DOZOWNIKA LEKÓW ===\n")
# Tworzymy dozownik z krótszymi odstępami do testów
dispenser = SecureMedicationDispenser("test_medication_log.json")
dispenser.minimum_interval_seconds = 10 # Nadpisujemy dla szybkich testów
print("Test 1: Pierwsza dawka (powinno zadziałać)")
print(f"Wynik: {dispenser.request_dose()}\n")
print("Test 2: Natychmiastowa druga dawka (powinno się nie udać)")
print(f"Wynik: {dispenser.request_dose()}\n")
print("Test 3: Grace próbuje zmienić czas systemowy...")
print("(Uwaga: To nie pomoże - używamy znaczników czasu UTC z HMAC!)")
print(f"Wynik: {dispenser.request_dose()}\n")
print("Czekamy 10 sekund na następną ważną dawkę...")
time.sleep(10)
print("Test 4: Po odczekaniu (powinno zadziałać)")
print(f"Wynik: {dispenser.request_dose()}\n")
print("\n=== DZIENNIK AUDYTU ===")
dispenser.audit_log()
print("\n=== TESTOWANIE DZIENNEGO LIMITU ===")
print("Symulujemy osiągnięcie dziennego limitu...")
# Tworzymy nowy dozownik z 1-sekundowymi odstępami do szybkich testów
quick_dispenser = SecureMedicationDispenser("test_daily_limit.json")
quick_dispenser.minimum_interval_seconds = 1
quick_dispenser.max_daily_doses = 3 # Niższy limit do testów
for i in range(5):
result = quick_dispenser.request_dose()
print(f"Próba dawki {i+1}: {result}")
if i < 4: # Nie czekamy po ostatniej próbie
time.sleep(1.1)
print("\n=== DZIENNIK AUDYTU DLA TESTU DZIENNEGO LIMITU ===")
quick_dispenser.audit_log()
print("\n=== WERYFIKACJA WYKRYWANIA INGERENCJI ===")
is_valid, message = quick_dispenser.verify_integrity()
print(f"Sprawdzenie integralności: {message}")
print("\nŁańcuch zdarzeń (każdy HMAC zależy od poprzedniego):")
for event in quick_dispenser.event_log[-3:]:
print(f"Zdarzenie {event['index']}: HMAC={event['hmac'][:16]}...")
# Sprzątanie plików testowych
import os
for f in ["test_medication_log.json", "test_daily_limit.json"]:
if os.path.exists(f):
os.remove(f)