View on GitHub

pps-2021-authsim

Design di dettaglio

Library

AbstractBruteForceAttackBuilder

metodi protected

Nella classe AbstractBruteForceBuilder sono stati inseriti i metodi di configurazione relativi agli attacchi bruteforce con visibilità protected per permettere alle classi che la estendono di esporre metodi con nomi più semantici: ad esempio, un attacco bruteforce basato su dizionario (generato dal builder DictionaryAttackBuilder) richiede un alfabeto in senso generico, quindi un insieme di parole con cui generare le stringhe (non obbligatoriamente le parole sono lunghe 1 solo carattere), perciò è semanticamente più corretto esporre un metodo nominato withDictionary rispetto a withAlphabet, infatti rispecchia il tipo richiesto dal metodo.

Alphabet

extends Set[String]

Il trait Alphabet estende il trait dei set, in particolare specificando che si tratta di un insieme di stringhe. Questa estensione è stata fatta per poter utilizzare (e raggiungere) il Set all’interno dell’alfabeto stesso in modo trasparente e rendere il codice meno verboso.

generico rispetto a sè stesso

Alphabet è generico rispetto a sè stesso, infatti la sua signature è trait Alphabet[T <: Alphabet[T]. Questa definizione particolare è utile per aiutare il compilatore a conoscere il tipo del valore di ritorno del metodo and (che unisce due alfabeti in una nuova istanza), il quale generalmente ritorna un oggetto dello stesso tipo dell’alfabeto sul quale si chiama il metodo stesso: ad esempio, se si chiama and su un alfabeto di tipo Dictionary, il nuovo alfabeto prodotto sarà anch’esso di tipo Dictionary.

Builder

builderMethod

Il metodo builderMethod all’interno del trait Builder è stato inserito per rendere meno verboso il codice dei vari builder presenti, in quanto incapsula la restituzione del builder stesso. Ciò è stato possibile grazie alla possibilità di Scala di accettare funzioni come argomenti per altre funzioni. builderMethod richiede una funzione che accetta un solo valore del tipo specificato e restituisce il tipo Unit, che normalmente si associa a una funzione di assegnamento.

ConcurrentStringCombinator

restituisce un solo Option[String] per chiamata

Questa classe funge da monitor per le combinazioni di stringhe richieste. In questo modo, procedure in esecuzione in parallelo non interferiscono tra di loro. Inoltre, fornisce anche una condizione di terminazione, in quanto quando restituisce un Option vuoto significa che le combinazioni di stringhe richieste sono terminate.

Security Policy

Policy Model Package UML

Le interfacce Policy, UserIDPolicy,PasswordPolicy,OTPPolicy,Saltpolicy definiscono un tipo immutabile di politica di sicurezza.

Le credenziali e i valori di sale sono di tipo stringa, quindi le policy corrispondenti sono state progettare in modo da essere un estensione dell’ interfaccia StringPolicy che richiede un alfabeto (PolicyAlphabet), grazie al quale possono essere generate automaticamente le stringhe, attraverso il metodo generate(implicit policyAutoBuilder: StringPolicy => PolicyAutoBuilder[String]): String, senza che l’ utilizzatore debba generarle manualmente.

Il metodo utilizzato per generare automaticamente le stringhe è una Higher-Order Function, ovvero una funzione che accetta altre funzioni come parametri, quindi il metodo delega alla funzione passatagli ( implict StringPolicy => PolicyAutoBuilder[String] ) la generazione della stringa basata sulla StringPolicy.

L’alfabeto PolicyAlphabet è stato progettato utilizzando il pattern template method, in modo tale che l’utilizzatore debba definire solamente una parte o tutti i seguenti metodi:

Questi metodi restituiscono un alfabeto del tipo SymbolicAlphabet (extends Alphabet).

L’alfabeto è stato arricchito con il mixin RandomAlphabet che definisce metodi che restituiscono una lazy list di caratteri e con il mixin RegexAlphabet che definisce metodi che restituiscono una espressione regolare.

Ogni StringPolicy può essere arricchita da una o più interfacce (mixins) che definiscono dei metodi utili per la generazione e la validazione delle stesse.

La creazione delle security policy è delegata alle classi PolicyBuilder, UserIDPolicyBuilder, PasswordPolicyBuilder, OTPPolicyBuilder, SaltPolicyBuilder, progettate utilizzando il pattern Builder. La struttura dei vari builder rispecchia il modello descritto precedentemente.

In più è stata inserita una interfaccia (mixin) SettableAlphabetBuilder, utile al settaggio di un nuovo alfabeto. Questa interfaccia è stata creata per necessita visto che non tutte le StringPolicy devono cambiare alfabeto, infatti le One Time Password (OTP), per definizione, hanno un alfabeto fisso di soli caratteri numerici.

Policy Builders Package UML

Essendo le security policy immutabili, esiste la possibilità di modificare delle policy già definite tramite l’ oggetto PolicyChanger, il quale definisce dei metodi factory per ogni policy progetta.

Tutti i PolicyChanger estendono il builder corrispondente e l’interfaccia mixin PolicyChanger[T], nella quale è definito un metodo rebuild: T che ricostruisce la security policy con le eventuali modifiche.

Policy Changers Package UML

Una prerogativa delle security policy relative alle stringhe è quella di essere validate/verificate, ciò è possibile utilizzando l’ oggetto StringPolicyChecker che possiede il metodo factory apply(policy: Stringpolicy): PolicyChecker[String] che crea un istanza di PolicyChecker[String] la quale possiede un metodo check(value: String): Boolean che verifica che il valore passatogli sia conforme alla policy precedentemente inserita.

Policy Checkers Package UML

Per facilitare l’assegnamento delle credenziali generate in base al tipo di credenziale (userID, password, otp) sono stati progettati degli extractor object (ovvero oggetti che definiscono un metodo unapply(credentialPolicy: CredentialPolicy): Option[String]).

Policy Extractors Package UML

Sono state progettate delle Security Policy di Default che sfruttano le varie funzionalità descritte precedentemente.

Policy Defaults Package UML

Generatore di One-Time Password (OTP)

La One Time Password è una password che è valida solo per una singola sessione di accesso o una transazione. Per questo suo scopo l’OTP è anche detta password usa e getta. Le OTP possono essere utilizzate come unico fattore di autenticazione, o in aggiunta ad un altro fattore, come può essere la password dell’utente, in modo da realizzare una autenticazione a due fattori.

OTP Model Package UML

Le interfacce OTP, HOTP, TOTP definiscono un tipo immutabile di One-Time Password.

OTP è l’ interfaccia che definisce un tipo generico di one time password.

Le sue estensioni, ovvero le effettive interfacce, sono:

La creazione delle one time password è delegata alle classi HOTPBuilder, TOTPBuilder, progettate utilizzando il pattern Builder. La struttura dei vari builder rispecchia il modello descritto precedentemente.

L’interfaccia OTPBuilder può essere arrichita con le interfacce (mixins) SecretBuilder, HmacOTPBuilder e TimeOTPBuilder a seconda di quale tipo di OTP si vuole implementare. OTP Builders Package UML

L’algoritmo dell’HOTP è stato incapsulato nell’oggetto OTPGenerator, in modo tale da poterlo utilizzare anche standalone.

La generazione della lunghezza della OTP è delegata alla interfaccia LengthGenerator, la quale possiede un metodo che prende in input una OTP policy e genera la lunghezza effettiva (length(optPolicy: OTPPolicy): Int). Questa interfaccia viene utilizza nel OTPBuilder attraverso il metodo withPolicy(policy: OTPPolicy)(implicit generateLength: LengthGenerator).

OTP Builders Package UML

Cryptography

Il modulo di crittografia è la parte del sistema adibita a tutte le operazioni crittografiche. Quest’ultima incapsula gli algoritmi crittografici così come i cifrari che ne permettono le operazioni principali e le utilities ad essi collegati.

Algorithm

Gli algoritmi crittografici sono stati modellati tramite una gerarchia che trova la sua root nell’interfaccia CryptographicAlgorithm.

Nel framework si è deciso di supportare tre macro tipologie di algoritmi crittografici, in modo da lasciare all’utilizzatore decidere se garantire l’integrità, l’autenticità, o la confidenzialità dei dati. Di seguito vengono quindi descritte le tre famiglie supportate partendo dalle funzioni hash, per poi arrivare agli algoritmi a chiave simmetrica ed asimmetrica.

Questi ultimi infatti permettono di convertire input di una lunghezza arbitraria, in stringhe di lunghezza fissa, questo mapping deve essere infeasible da invertire e resistente alle collisioni per essere considerato sicuro.

Entrambe le famiglie di algoritmi di encryption estendono da un trait condiviso che ne modella operazioni comuni; questi prende il nome di EncryptionAlgoritm .

Per quanto riguarda questa tipologia di algoritmi si è scelto di mettere a disposizione tre algoritmi piuttosto differenti, il Cifrario di Cesare, DES, AES, anche in questo caso si è scelto di lasciare gli utilizzatori liberi di disporre anche di algoritmi la cui insicurezza è nota da lungo tempo 4 ricordando che lo scopo del framework è quello di permettere all’utente di fare di confronti.

Il framework in questo caso mette a disposizione un solo algoritmo RSA. Quest’ultimo viene utilizzato sia per quanto riguarda la generazione delle chiavi stesse da utilizzare durante le operazioni di crittografia, sia per la generazioni delle chiavi stesse.

La libreria separa quelle che sono le caratteristiche statiche degli algoritmi, da quelle che sono le modalità in cui queste vengono sfruttate per l’implementazione delle operazioni crittografiche. Gli algoritmi appena descritti modellano le prime e di seguito verranno esposti i cifrari, che implementano le seconde.

Algorithm

Infatti, ad ogni algoritmo di encryption 6 identificato (algoritmi a chiave simmetrica, o asimmetrica) è stato associato un cifrario che incapsula la logica con cui l’algoritmo viene utilizzato per criptare e successivamente decriptare una segreto.

I cifrari sono stati implementati cercando di incapsulare le operazioni comuni in un abstract class applicando il pattern Template Method per seguire il principio DRY, tuttavia, sebbene il paradigma funzionale si appoggi sull’idea che ogni cosa dovrebbe essere una funzione, in questo caso si è preferito non implementare factories per la creazione di oggetti, in quanto avrebbe potuto impendire l’estensibilità del framework o, minarne la consistenza.

L’entità adibita a tale compito è il BasicCipher, classe astratta che fornisce le implementazioni dei metodi di encryption e decryption oltre che un metodo, crypto, per l’implementazione dell’operazione di cifratura.

Di seguito vengono descritti i cifrari implementati, nell’ordine prima quelli simmetrici e poi quelli asimettrici.

Cipher

User

Per garantire la consistenza delle informazioni e il rispetto delle policy sono stati implementati due macro tipologie di builder in grado di implementare la costruzione da un lato degli utenti e dall’altro delle informazioni ad esse relative. I builder afferenti alle due categorie builder, così come tutti i builder del framework, estendendono l’interfaccia Builder. Nello specifico per l’implementazione dei builder degli utenti si è scelto nuovamente di applicare il principio Dry mettendo a disposizione una classe astratta UserBuilderche implementi i metodi di base comuni a tutti i builder degli utenti. Vengono quindi creati due builder specifici per gli utenti: il primo UserCostumBuilder in grado di permettere la costruizione di utenti a partire da credenziali e opzionalmente policy scelte dall’utente. Il secondo UserAutoBuilder che al contrario permette la definzioni di un numero prestabilito di utenti a partire da un set di policy definite dagli utenti. Quest’ultimo è infatti in grado di generare credenziali randomiche che rispettino le regole scelte dall’utilizzatore per il numero richiesto di utenti.

Come per gli User anche per le UserInformation è stato implementato un builder in grado assicurarne una corretta istanziazione, questi prende il nome di: UserInformationBuilder. User Builder e UserInformation

Pattern di progettazione

Creazionali

Strutturali

Comportamentali

Client

ScalaFx Task

La simulazione di un attacco potrebbe durare per molto tempo, quindi non sarebbe accettabile bloccare il thread della gui con una computazione impegnativa.

La libreria ScalaFx offre Task[T] una classe utility analoga a SwingWorker che permette di eseguire una computazione non bloccante su un thread diverso da quello di EDT.

Permette inoltre di comunicare con la GUI tramite il metodo updateMessage che permette di aggiornare un StringProperty, messageProperty, disponibile alla GUI.

In questo modo si riesce a eseguire la simulazione e stampare su un elemento della gui i log della sua esecuzione.

L’esecuzione del task viene effettuata da un component SimulationRunnerComponent responsabile di avviare e di fermare il task in esecuzione. Questa classe utilizza ExecutorService istanziato con thread singolo (newSingleThreadExecutor) per gestire il task.

Simulation Runner

Repository

Si possono persistere gli utenti in due modi: Database SQL e Database Mongo. Per facilitare il deploy dell’applicazione, è stato deciso di utilizzare i database in-memory:

Entrambi sono delle dipendenze Java e quindi sono state integrate con il codice Scala.

Queste dipendenze sono state pensate per gli ambienti di testing, tuttavia con qualche accorgimento sono state adattate ad essere utilizzare nell’applicativo.

L’accesso alla persistenza è stato gestito nelle classi Repository ispirate a Domain Driven Design, forniscono un’interfaccia che astrae l’accesso ai dati e rende trasparente l’implementazione.

Repositories

Service

Per gestire la business logic dell’applicativo sono state create delle classi Service ispirate a Domain Driven Design.

Error Handling Funzionale

Nel client si cercava di preferire error handling funzionale. In particolare sono stati usati i costrutti Try e Using.

Promise

Il driver di MongoDB per Scala 2 (portato nell’ambiente del progetto con cross building) esponse un API asincrona basata sul pattern Observer. Per le esigenze della simulazione è stato deciso di rendere sequenziali le operazioni con il database. Per questo motivo si è ricorso al costrutto Promise. Semplicemente ogni volta che si sesegue un’operazione su MongoDB viene restituito un Observable e viene creata una Promise che viene risolta in certe condizioni (che dipendono dall’operazioni in questione), oppure rigettata nel caso d’errore.

In questo modo eseguendo Await.result sulla promise, il flusso diventa sequenziale.

Factory

Per costruire gli attacchi preconfigurati è stato utilizzato il pattern Factory. La classe AttacksFactory viene istanziata con UserProvider e StatisticsConsumer necessari per costruire AttackBuilder richiesto.

Observer

Per gestire le notifiche dinamiche tra ViewModel e Model, è stata creata la classe ObservableListBuffer[A] che permette di eseguire delle operazioni semplici con una collection (mutable) di elementi di tipo A notificando i listener su quel evento.

In particolare ObservableListBuffer[A] supporta i listener per gli eventi

I listener si possono passare in costruzione utilizzando uno degli apply nel suo Companion Object oppure anche post-costruzione con i metodi appositi.

ObservableListBuffer

Organizzazione del codice

Il codice del progetto è diviso in due marco-package: client e library

Library

L’organizzazione del codice della libreria può essere riassunta con il seguente diagramma:

Client Packages

Client

L’organizzazione del codice del client può essere riassunta con il seguente diagramma:

Client Packages

  1. Gli algoritmi hash sono particolari tipi di funzioni utilizzati per garantire la confidenzialità dei dati. 

  2. Le proprietà di sicurezza di riferimento delle funzioni hash sono tre: resistenza alla preimmagine, resistenza alla seconda preimmagine e resistenza alla collisione. Per maggiori informazioni consultare [link to Funzioni Crittofiche di hash!](https://it.wikipedia.org/wiki/Funzione_crittografica_di_hash). 

  3. Gli algoritmi di crittografia simmetrica sono gli algoritmi crittografici in grado di garantire la confidenzialità in un sistema. 

  4. L’insicurezza del Cifrario di Cesare è stata evidente fin dal XI a seguito degli studi sulle tecniche di crittoanalisi del arabo Al-Kindi. La sicurezza di DES invece è stata messa in questione dal 1997 quando per la prima volta sono stati violati messaggi criptati col suddetto algoritmo. Attualmente AES è l’unico algoritmo proposto ad essere approvato dal NSA per il passaggio di informazioni top secret

  5. Il modulo di crittografia a chiave asimmetrica, concerne quella categoria di algoritmi in grado di garantire l’autenticità. 

  6. Nel caso del framework le funzioni hash vengono utilizzate per evitare all’utente di salvare le password in chiaro nel database, tuttavia essendo queste non invertibili, perchè l’utente possa controllare l’integrità della password non potrà decriptare il valore hash ma fare un confronto tra il valore hash della password passata in input e quello salvata sul database. 

  7. L’unica eccezione è rappresentata dal CaesarCipher in quanto unico cifrario a non estendere dalla classe astratta per la natura intrinseca dell’algoritmo.