This shows you the differences between two versions of the page.
|
ss:laboratoare:09 [2025/02/20 16:30] 127.0.0.1 external edit |
ss:laboratoare:09 [2026/05/04 19:40] (current) ciprian.popescu0411 [Laborator 9: Implementarea mTLS în aplicația mobilă Android] |
||
|---|---|---|---|
| Line 1: | Line 1: | ||
| - | ===== Laboratorul 09. ===== | + | ====== Laborator 9: Implementarea mTLS în aplicația mobilă ====== |
| + | În cadrul acestui laborator vom implementa **Mutual TLS (mTLS)** pentru aplicația Android, asigurând autentificarea bidirecțională între clientul mobil și brokerul MQTT. | ||
| + | ===== Ce este mTLS? ===== | ||
| + | |||
| + | **Mutual TLS** (mTLS) este un mecanism de autentificare bidirecțională în care: | ||
| + | * **Serverul se autentifică** față de client (TLS standard) | ||
| + | * **Clientul se autentifică** față de server (autentificare mutuală) | ||
| + | |||
| + | Acest lucru asigură că: | ||
| + | * Datele sunt criptate în tranzit | ||
| + | * Doar dispozitivele autorizate se pot conecta la broker | ||
| + | * Atacurile de tip man-in-the-middle sunt prevenite | ||
| + | |||
| + | ==== Diferența față de TLS simplu ==== | ||
| + | |||
| + | ^ Aspect ^ TLS simplu ^ mTLS ^ | ||
| + | | Autentificare server | DA | DA | | ||
| + | | Autentificare client | NU | DA | | ||
| + | | Certificat client necesar | NU | DA | | ||
| + | | Protecție împotriva clienților neautorizați | NU | DA | | ||
| + | |||
| + | ===== Fișiere de modificat ===== | ||
| + | |||
| + | ^ Fișier ^ Scop ^ Modificări necesare ^ | ||
| + | | ''broker/mosquitto.conf'' | Configurare broker | Activarea mTLS pe portul 8883 | | ||
| + | | ''MqttHandler.kt'' | Client MQTT Android | Configurarea ''SSLSocketFactory'' cu certificate client | | ||
| + | | ''SetupActivity.kt'' | Ecran de setup | Adăugarea opțiunii de selectare a protocolului (TCP/SSL) | | ||
| + | | ''MqttConstants.kt'' | Constante | Adăugarea constantelor pentru TLS | | ||
| + | | ''app/src/main/res/raw/'' | Resurse | Stocarea certificatelor CA și client | | ||
| + | |||
| + | ===== Cerințe preliminare ===== | ||
| + | |||
| + | * OpenSSL instalat (''sudo apt install openssl'' pe Linux) | ||
| + | * Un broker MQTT Mosquitto funcțional | ||
| + | * Proiectul Android funcțional cu conexiune MQTT simplă (laboratoarele anterioare) | ||
| + | |||
| + | ===== Pasul 1: Generarea certificatelor ===== | ||
| + | |||
| + | ==== 1.1 Crearea Autorității de Certificare (CA) ==== | ||
| + | |||
| + | **Într-un terminal:** | ||
| + | <code bash> | ||
| + | mkdir -p certs && cd certs | ||
| + | |||
| + | # Generarea cheii private a CA (4096 biți) | ||
| + | openssl genrsa -out ca.key 4096 | ||
| + | |||
| + | # Generarea certificatului CA (auto-semnat, valabil 10 ani) | ||
| + | openssl req -new -x509 -days 3650 -key ca.key -out ca.crt \ | ||
| + | -subj "/C=RO/ST=Romania/L=Bucharest/O=SSProject/OU=Security/CN=SSProject-CA" | ||
| + | </code> | ||
| + | |||
| + | ==== 1.2 Crearea certificatului de server (pentru brokerul Mosquitto) ==== | ||
| + | |||
| + | Înlocuiți ''ADRESA_IP_BROKER'' cu IP-ul real al mașinii pe care rulează Mosquitto: | ||
| + | |||
| + | **Într-un terminal:** | ||
| + | <code bash> | ||
| + | # Generarea cheii private a serverului | ||
| + | openssl genrsa -out server.key 2048 | ||
| + | |||
| + | # Generarea cererii de semnare (CSR) cu Subject Alternative Name | ||
| + | openssl req -new -key server.key -out server.csr \ | ||
| + | -subj "/C=RO/ST=Romania/L=Bucharest/O=SSProject/OU=Broker/CN=ADRESA_IP_BROKER" \ | ||
| + | -addext "subjectAltName=IP:ADRESA_IP_BROKER" | ||
| + | |||
| + | # Semnarea certificatului de server cu CA (valabil 1 an) | ||
| + | openssl x509 -req -days 365 -in server.csr -CA ca.crt -CAkey ca.key \ | ||
| + | -CAcreateserial -out server.crt -sha256 \ | ||
| + | -copy_extensions copyall | ||
| + | |||
| + | # Curățare CSR | ||
| + | rm server.csr | ||
| + | </code> | ||
| + | |||
| + | ==== 1.3 Crearea certificatului de client (pentru aplicația Android) ==== | ||
| + | |||
| + | **Într-un terminal:** | ||
| + | <code bash> | ||
| + | # Generarea cheii private a clientului | ||
| + | openssl genrsa -out android.key 2048 | ||
| + | |||
| + | # Generarea cererii de semnare a certificatului de client (CSR) | ||
| + | openssl req -new -key android.key -out android.csr \ | ||
| + | -subj "/C=RO/ST=Romania/L=Bucharest/O=SSProject/OU=MobileClient/CN=android" | ||
| + | |||
| + | # Semnarea certificatului de client cu CA (valabil 1 an) | ||
| + | openssl x509 -req -days 365 -in android.csr -CA ca.crt -CAkey ca.key \ | ||
| + | -CAcreateserial -out android.crt -sha256 | ||
| + | |||
| + | # Curățare CSR | ||
| + | rm android.csr | ||
| + | </code> | ||
| + | |||
| + | ==== 1.4 Crearea fișierului BKS (BouncyCastle KeyStore) pentru Android ==== | ||
| + | |||
| + | Android nu suportă nativ fișiere PEM. Trebuie să convertim certificatele într-un format compatibil. Vom crea două keystore-uri: | ||
| + | * **''truststore.bks''** — conține certificatul CA (pentru a verifica identitatea serverului) | ||
| + | * **''keystore.bks''** — conține certificatul și cheia clientului (pentru a ne autentifica față de server) | ||
| + | |||
| + | **Într-un terminal:** | ||
| + | <code bash> | ||
| + | # Crearea unui fișier PKCS12 cu certificatul și cheia clientului | ||
| + | openssl pkcs12 -export -in android.crt -inkey android.key \ | ||
| + | -out android.p12 -name "android-client" \ | ||
| + | -password pass:changeit | ||
| + | |||
| + | # Descărcarea BouncyCastle Provider (necesar pentru format BKS) | ||
| + | wget https://repo1.maven.org/maven2/org/bouncycastle/bcprov-jdk18on/1.80/bcprov-jdk18on-1.80.jar | ||
| + | |||
| + | # Crearea truststore-ului BKS (conține CA) | ||
| + | keytool -importcert -v -trustcacerts \ | ||
| + | -file ca.crt -alias ca \ | ||
| + | -keystore truststore.bks \ | ||
| + | -storetype BKS \ | ||
| + | -providerclass org.bouncycastle.jce.provider.BouncyCastleProvider \ | ||
| + | -providerpath ./bcprov-jdk18on-1.80.jar \ | ||
| + | -storepass changeit -noprompt | ||
| + | |||
| + | # Crearea keystore-ului BKS (conține certificatul client) | ||
| + | keytool -importkeystore \ | ||
| + | -srckeystore android.p12 -srcstoretype PKCS12 -srcstorepass changeit \ | ||
| + | -destkeystore keystore.bks -deststoretype BKS -deststorepass changeit \ | ||
| + | -providerclass org.bouncycastle.jce.provider.BouncyCastleProvider \ | ||
| + | -providerpath ./bcprov-jdk18on-1.80.jar | ||
| + | </code> | ||
| + | |||
| + | ==== 1.5 Verificarea certificatelor ==== | ||
| + | |||
| + | **Într-un terminal:** | ||
| + | <code bash> | ||
| + | # Verificarea certificatului CA | ||
| + | openssl x509 -in ca.crt -text -noout | head -15 | ||
| + | |||
| + | # Verificarea certificatului de server | ||
| + | openssl verify -CAfile ca.crt server.crt | ||
| + | |||
| + | # Verificarea certificatului de client | ||
| + | openssl verify -CAfile ca.crt android.crt | ||
| + | |||
| + | # Verificarea conținutului truststore-ului BKS | ||
| + | keytool -list -keystore truststore.bks -storetype BKS \ | ||
| + | -providerclass org.bouncycastle.jce.provider.BouncyCastleProvider \ | ||
| + | -providerpath ./bcprov-jdk18on-1.80.jar \ | ||
| + | -storepass changeit | ||
| + | </code> | ||
| + | |||
| + | ==== 1.6 Lista fișierelor generate ==== | ||
| + | |||
| + | După generare, directorul ''certs/'' ar trebui să conțină: | ||
| + | |||
| + | <code text> | ||
| + | certs/ | ||
| + | ├── ca.crt # Certificatul CA | ||
| + | ├── ca.key # Cheia privată CA (NU se distribuie!) | ||
| + | ├── server.crt # Certificatul serverului | ||
| + | ├── server.key # Cheia privată a serverului | ||
| + | ├── android.crt # Certificatul clientului Android | ||
| + | ├── android.key # Cheia privată a clientului Android | ||
| + | ├── android.p12 # Fișier PKCS12 intermediar | ||
| + | ├── truststore.bks # Truststore BKS → va fi copiat în proiectul Android | ||
| + | └── keystore.bks # Keystore BKS → va fi copiat în proiectul Android | ||
| + | </code> | ||
| + | |||
| + | ===== Pasul 2: Configurarea brokerului Mosquitto cu mTLS ===== | ||
| + | |||
| + | Creați sau editați fișierul ''mosquitto_mtls.conf'': | ||
| + | |||
| + | <file bash mosquitto_mtls.conf> | ||
| + | # MQTT securizat cu mTLS | ||
| + | listener 8883 | ||
| + | cafile ./certs/ca.crt | ||
| + | certfile ./certs/server.crt | ||
| + | keyfile ./certs/server.key | ||
| + | require_certificate true | ||
| + | use_subject_as_username true | ||
| + | allow_anonymous true | ||
| + | |||
| + | # Logare | ||
| + | log_type all | ||
| + | connection_messages true | ||
| + | log_timestamp true | ||
| + | </file> | ||
| + | |||
| + | Porniți brokerul securizat: | ||
| + | |||
| + | **Într-un terminal:** | ||
| + | <code bash> | ||
| + | sudo systemctl stop mosquitto.service | ||
| + | mosquitto -c mosquitto_mtls.conf -v | ||
| + | </code> | ||
| + | |||
| + | **Setări cheie:** | ||
| + | * ''cafile'' — certificatul CA pentru verificarea certificatelor clienților | ||
| + | * ''certfile/keyfile'' — certificatul propriu al serverului și cheia privată | ||
| + | * ''require_certificate true'' — **activează mTLS**: clienții trebuie să prezinte un certificat valid | ||
| + | * ''use_subject_as_username true'' — folosește CN-ul certificatului ca nume de utilizator MQTT | ||
| + | |||
| + | ===== Pasul 3: Copierea certificatelor în proiectul Android ===== | ||
| + | |||
| + | Copiați fișierele ''truststore.bks'' și ''keystore.bks'' în directorul de resurse raw al aplicației: | ||
| + | |||
| + | **Într-un terminal:** | ||
| + | <code bash> | ||
| + | mkdir -p app/src/main/res/raw | ||
| + | cp certs/truststore.bks app/src/main/res/raw/truststore.bks | ||
| + | cp certs/keystore.bks app/src/main/res/raw/keystore.bks | ||
| + | </code> | ||
| + | |||
| + | <note tip> | ||
| + | Directorul ''res/raw/'' este accesibil din cod prin ''R.raw.truststore'' și ''R.raw.keystore''. | ||
| + | </note> | ||
| + | <note> | ||
| + | Fișierele BKS sunt incluse în APK, deci nu trebuie să le distribuiți separat. | ||
| + | </note> | ||
| + | |||
| + | ===== Pasul 4: Actualizarea constantelor MQTT ===== | ||
| + | |||
| + | Adăugați constantele necesare pentru TLS în ''MqttConstants.kt'': | ||
| + | |||
| + | <file kotlin MqttConstants.kt> | ||
| + | package ro.pub.cs.systems.ssproject.mqtt | ||
| + | |||
| + | object MqttConstants { | ||
| + | const val TAG = "MqttHandler" | ||
| + | const val TOPIC_IMAGES = "ssproject/images" | ||
| + | const val TOPIC_COMMANDS = "ssproject/commands" | ||
| + | |||
| + | const val CMD_CAPTURE = "CAPTURE" | ||
| + | const val CMD_START_LIVE = "START-LIVE" | ||
| + | const val CMD_STOP_LIVE = "STOP-LIVE" | ||
| + | |||
| + | // TLS | ||
| + | const val BKS_STORE_TYPE = "BKS" | ||
| + | const val TLS_PROTOCOL = "TLSv1.2" | ||
| + | const val KEYSTORE_PASSWORD = "changeit" | ||
| + | const val TRUSTSTORE_PASSWORD = "changeit" | ||
| + | const val DEFAULT_TLS_PORT = "8883" | ||
| + | } | ||
| + | </file> | ||
| + | |||
| + | ===== Pasul 5: Crearea clasei ''TlsHelper'' ===== | ||
| + | |||
| + | Creați o clasă nouă care gestionează crearea contextului SSL: | ||
| + | |||
| + | <file kotlin TlsHelper.kt> | ||
| + | package ro.pub.cs.systems.ssproject.mqtt | ||
| + | |||
| + | import android.content.Context | ||
| + | import java.security.KeyStore | ||
| + | import javax.net.ssl.KeyManagerFactory | ||
| + | import javax.net.ssl.SSLContext | ||
| + | import javax.net.ssl.SSLSocketFactory | ||
| + | import javax.net.ssl.TrustManagerFactory | ||
| + | |||
| + | object TlsHelper { | ||
| + | |||
| + | /** | ||
| + | * Creează un SSLSocketFactory configurat pentru mTLS. | ||
| + | * | ||
| + | * @param context Contextul Android (necesar pentru accesarea resurselor raw) | ||
| + | * @param trustStoreResId ID-ul resursei raw pentru truststore (R.raw.truststore) | ||
| + | * @param keyStoreResId ID-ul resursei raw pentru keystore (R.raw.keystore) | ||
| + | * @return SSLSocketFactory configurat cu certificatele client și CA | ||
| + | */ | ||
| + | fun createMtlsSocketFactory( | ||
| + | context: Context, | ||
| + | trustStoreResId: Int, | ||
| + | keyStoreResId: Int | ||
| + | ): SSLSocketFactory { | ||
| + | // 1. Încărcarea TrustStore-ului (conține certificatul CA) | ||
| + | val trustStore = KeyStore.getInstance(MqttConstants.BKS_STORE_TYPE) | ||
| + | context.resources.openRawResource(trustStoreResId).use { input -> | ||
| + | trustStore.load(input, MqttConstants.TRUSTSTORE_PASSWORD.toCharArray()) | ||
| + | } | ||
| + | |||
| + | val trustManagerFactory = TrustManagerFactory.getInstance( | ||
| + | TrustManagerFactory.getDefaultAlgorithm() | ||
| + | ) | ||
| + | trustManagerFactory.init(trustStore) | ||
| + | |||
| + | // 2. Încărcarea KeyStore-ului (conține certificatul și cheia clientului) | ||
| + | val keyStore = KeyStore.getInstance(MqttConstants.BKS_STORE_TYPE) | ||
| + | context.resources.openRawResource(keyStoreResId).use { input -> | ||
| + | keyStore.load(input, MqttConstants.KEYSTORE_PASSWORD.toCharArray()) | ||
| + | } | ||
| + | |||
| + | val keyManagerFactory = KeyManagerFactory.getInstance( | ||
| + | KeyManagerFactory.getDefaultAlgorithm() | ||
| + | ) | ||
| + | keyManagerFactory.init(keyStore, MqttConstants.KEYSTORE_PASSWORD.toCharArray()) | ||
| + | |||
| + | // 3. Crearea contextului SSL cu ambele componente (mTLS) | ||
| + | val sslContext = SSLContext.getInstance(MqttConstants.TLS_PROTOCOL) | ||
| + | sslContext.init( | ||
| + | keyManagerFactory.keyManagers, // Autentificarea clientului | ||
| + | trustManagerFactory.trustManagers, // Verificarea serverului | ||
| + | null | ||
| + | ) | ||
| + | |||
| + | return sslContext.socketFactory | ||
| + | } | ||
| + | } | ||
| + | </file> | ||
| + | |||
| + | **Explicarea codului:** | ||
| + | |||
| + | - **TrustStore** — conține certificatul CA. Este folosit pentru a **verifica identitatea serverului** (echivalentul ''ca_certs'' din Python). | ||
| + | - **KeyStore** — conține certificatul și cheia privată a clientului. Este folosit pentru **autentificarea clientului** față de server (echivalentul ''certfile'' + ''keyfile'' din Python). | ||
| + | - **SSLContext** — combină cele două componente într-un context SSL complet pentru mTLS. | ||
| + | |||
| + | ===== Pasul 6: Actualizarea ''MqttHandler'' pentru suportul TLS ===== | ||
| + | |||
| + | Modificați constructorul și metoda ''connect()'' din ''MqttHandler.kt'' pentru a suporta atât conexiuni simple (TCP), cât și conexiuni securizate (SSL/TLS): | ||
| + | |||
| + | <file kotlin MqttHandler.kt> | ||
| + | package ro.pub.cs.systems.ssproject.mqtt | ||
| + | |||
| + | import android.os.Build | ||
| + | import android.util.Log | ||
| + | import kotlinx.coroutines.Dispatchers | ||
| + | import kotlinx.coroutines.withContext | ||
| + | import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken | ||
| + | import org.eclipse.paho.client.mqttv3.MqttCallback | ||
| + | import org.eclipse.paho.client.mqttv3.MqttClient | ||
| + | import org.eclipse.paho.client.mqttv3.MqttConnectOptions | ||
| + | import org.eclipse.paho.client.mqttv3.MqttMessage | ||
| + | import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence | ||
| + | import javax.net.ssl.SSLSocketFactory | ||
| + | |||
| + | class MqttHandler( | ||
| + | brokerIp: String, | ||
| + | brokerPort: String, | ||
| + | private val sslSocketFactory: SSLSocketFactory? = null, | ||
| + | private val isConnectedCallback: (Boolean) -> Unit, | ||
| + | private val onCommandReceived: (String) -> Unit | ||
| + | ) : MqttCallback { | ||
| + | // Determinăm protocolul pe baza prezenței SSLSocketFactory | ||
| + | private val protocol = if (sslSocketFactory != null) "ssl" else "tcp" | ||
| + | private val brokerUrl = "$protocol://$brokerIp:$brokerPort" | ||
| + | private val clientId = "${Build.MANUFACTURER}_${Build.MODEL}_${MqttClient.generateClientId()}" | ||
| + | private var client: MqttClient? = null | ||
| + | |||
| + | suspend fun connect() { | ||
| + | withContext(Dispatchers.IO) { | ||
| + | if (client?.isConnected == true) { | ||
| + | return@withContext | ||
| + | } | ||
| + | |||
| + | try { | ||
| + | client = MqttClient(brokerUrl, clientId, MemoryPersistence()) | ||
| + | client?.setCallback(this@MqttHandler) | ||
| + | |||
| + | val options = MqttConnectOptions().apply { | ||
| + | isCleanSession = true | ||
| + | connectionTimeout = 10 | ||
| + | keepAliveInterval = 60 | ||
| + | |||
| + | // Configurarea TLS dacă SSLSocketFactory este disponibil | ||
| + | if (sslSocketFactory != null) { | ||
| + | socketFactory = sslSocketFactory | ||
| + | } | ||
| + | } | ||
| + | |||
| + | client?.connect(options) | ||
| + | Log.i(MqttConstants.TAG, "Connected to $brokerUrl") | ||
| + | |||
| + | client?.subscribe(MqttConstants.TOPIC_COMMANDS, 1) | ||
| + | Log.i(MqttConstants.TAG, "Subscribed to ${MqttConstants.TOPIC_COMMANDS}") | ||
| + | } catch (e: Exception) { | ||
| + | Log.e(MqttConstants.TAG, "Connection error: ${e.message}") | ||
| + | e.printStackTrace() | ||
| + | } finally { | ||
| + | isConnectedCallback(isConnected()) | ||
| + | } | ||
| + | } | ||
| + | } | ||
| + | |||
| + | // ... restul metodelor rămân neschimbate (disconnect, isConnected, | ||
| + | // publishImage, connectionLost, messageArrived, deliveryComplete) | ||
| + | } | ||
| + | </file> | ||
| + | |||
| + | **Modificări cheie:** | ||
| + | * Constructorul primește opțional un ''SSLSocketFactory'' | ||
| + | * Protocolul se determină automat: ''ssl://'' pentru TLS, ''tcp://'' pentru conexiuni simple | ||
| + | * ''MqttConnectOptions.socketFactory'' este configurat cu ''SSLSocketFactory'' pentru handshake-ul TLS | ||
| + | |||
| + | ===== Pasul 7: Actualizarea ''SetupActivity'' pentru selecția protocolului ===== | ||
| + | |||
| + | Modificați ecranul de setup pentru a permite utilizatorului să aleagă între conexiune simplă și conexiune securizată: | ||
| + | |||
| + | ==== 7.1 Actualizarea layout-ului ==== | ||
| + | |||
| + | Adăugați un switch pentru activarea TLS în ''activity_setup.xml'', **înainte** de butonul de conectare: | ||
| + | |||
| + | <file xml activity_setup.xml> | ||
| + | <!-- ... după TextInputLayout-ul pentru port ... --> | ||
| + | |||
| + | <com.google.android.material.materialswitch.MaterialSwitch | ||
| + | android:id="@+id/setup_card_view_tls_switch" | ||
| + | android:layout_width="match_parent" | ||
| + | android:layout_height="wrap_content" | ||
| + | android:layout_marginTop="8dp" | ||
| + | android:layout_marginBottom="8dp" | ||
| + | android:text="Conexiune securizată (mTLS)" | ||
| + | android:checked="false" /> | ||
| + | |||
| + | <com.google.android.material.button.MaterialButton | ||
| + | android:id="@+id/setup_card_view_connect_btn" | ||
| + | android:layout_width="wrap_content" | ||
| + | android:layout_height="wrap_content" | ||
| + | android:layout_gravity="end|center" | ||
| + | android:text="@string/setup_connect_btn" /> | ||
| + | </file> | ||
| + | |||
| + | ==== 7.2 Actualizarea ''SetupActivity.kt'' ==== | ||
| + | |||
| + | <file kotlin SetupActivity.kt> | ||
| + | package ro.pub.cs.systems.ssproject.ui.setup | ||
| + | |||
| + | import android.content.Intent | ||
| + | import android.os.Bundle | ||
| + | import android.util.Patterns | ||
| + | import android.widget.Button | ||
| + | import android.widget.EditText | ||
| + | import androidx.activity.ComponentActivity | ||
| + | import androidx.activity.enableEdgeToEdge | ||
| + | import com.google.android.material.materialswitch.MaterialSwitch | ||
| + | import ro.pub.cs.systems.ssproject.mqtt.MqttConstants | ||
| + | import ro.pub.cs.systems.ssproject.ui.dashboard.MainActivity | ||
| + | import ro.pub.cs.systems.ssproject.R | ||
| + | |||
| + | class SetupActivity : ComponentActivity() { | ||
| + | override fun onCreate(savedInstanceState: Bundle?) { | ||
| + | super.onCreate(savedInstanceState) | ||
| + | enableEdgeToEdge() | ||
| + | setContentView(R.layout.activity_setup) | ||
| + | |||
| + | val ipField = findViewById<EditText>(R.id.setup_card_view_ip_field) | ||
| + | val portField = findViewById<EditText>(R.id.setup_card_view_port_field) | ||
| + | val tlsSwitch = findViewById<MaterialSwitch>(R.id.setup_card_view_tls_switch) | ||
| + | val connect = findViewById<Button>(R.id.setup_card_view_connect_btn) | ||
| + | |||
| + | // Actualizarea portului implicit la activarea TLS | ||
| + | tlsSwitch.setOnCheckedChangeListener { _, isChecked -> | ||
| + | if (isChecked && portField.text.toString() == "1883") { | ||
| + | portField.setText(MqttConstants.DEFAULT_TLS_PORT) | ||
| + | } else if (!isChecked && portField.text.toString() == | ||
| + | MqttConstants.DEFAULT_TLS_PORT) { | ||
| + | portField.setText("1883") | ||
| + | } | ||
| + | } | ||
| + | |||
| + | connect.setOnClickListener { | ||
| + | val inputIp = ipField.text.toString().trim() | ||
| + | val inputPort = portField.text.toString().trim() | ||
| + | |||
| + | if (inputIp.isEmpty() || !Patterns.IP_ADDRESS.matcher(inputIp).matches()) { | ||
| + | ipField.error = "Invalid IP address" | ||
| + | ipField.requestFocus() | ||
| + | return@setOnClickListener | ||
| + | } | ||
| + | |||
| + | val portNumber = inputPort.toIntOrNull() | ||
| + | if (portNumber == null || portNumber !in 1..65535) { | ||
| + | portField.error = "Invalid port" | ||
| + | portField.requestFocus() | ||
| + | return@setOnClickListener | ||
| + | } | ||
| + | |||
| + | val intent = Intent(this, MainActivity::class.java) | ||
| + | intent.putExtra("brokerIp", inputIp) | ||
| + | intent.putExtra("brokerPort", inputPort) | ||
| + | intent.putExtra("useTls", tlsSwitch.isChecked) | ||
| + | startActivity(intent) | ||
| + | } | ||
| + | } | ||
| + | } | ||
| + | </file> | ||
| + | |||
| + | ==== 7.3 Actualizarea ''MainActivity.kt'' ==== | ||
| + | |||
| + | Modificați crearea ''MqttHandler'' în ''MainActivity'' pentru a include configurația TLS: | ||
| + | |||
| + | <file kotlin MainActivity.kt> | ||
| + | // În metoda onCreate(), înlocuiți blocul de creare a MqttHandler cu: | ||
| + | |||
| + | val useTls = intent.getBooleanExtra("useTls", false) | ||
| + | |||
| + | val sslSocketFactory = if (useTls) { | ||
| + | TlsHelper.createMtlsSocketFactory( | ||
| + | context = this, | ||
| + | trustStoreResId = R.raw.truststore, | ||
| + | keyStoreResId = R.raw.keystore | ||
| + | ) | ||
| + | } else { | ||
| + | null | ||
| + | } | ||
| + | |||
| + | mqttHandler = MqttHandler( | ||
| + | brokerIp, | ||
| + | brokerPort, | ||
| + | sslSocketFactory = sslSocketFactory, | ||
| + | isConnectedCallback = { isConnected -> | ||
| + | runOnUiThread { | ||
| + | handleConnectionStateChange(isConnected) | ||
| + | } | ||
| + | }, | ||
| + | onCommandReceived = { command -> | ||
| + | runOnUiThread { | ||
| + | appendLog(getString(R.string.main_logs_command_received_entry, command)) | ||
| + | handleReceivedCommand(command) | ||
| + | } | ||
| + | } | ||
| + | ) | ||
| + | </file> | ||
| + | |||
| + | ===== Pasul 8: Testarea conexiunii mTLS ===== | ||
| + | |||
| + | * Porniți brokerul Mosquitto cu configurația mTLS: | ||
| + | <code bash> | ||
| + | mosquitto -c mosquitto_mtls.conf -v | ||
| + | </code> | ||
| + | * Compilați și rulați aplicația pe dispozitivul Android / emulator. | ||
| + | * În ecranul de setup, activați switch-ul **Conexiune securizată (mTLS)** și introduceți IP-ul brokerului. Portul se va actualiza automat la ''8883''. | ||
| + | * Verificați log-urile brokerului pentru a confirma conexiunea mTLS: | ||
| + | <code bash> | ||
| + | # New connection from X.X.X.X:XXXXX on port 8883. | ||
| + | # New client connected from X.X.X.X:XXXXX as ... (p1, c1, k60, u'android'). | ||
| + | </code> | ||
| + | * Verificați cu Wireshark că traficul pe portul 8883 este criptat (comparativ cu portul 1883). | ||
| + | |||
| + | ===== Depanare ===== | ||
| + | |||
| + | ==== ''SSLHandshakeException: ...'' la conectare ==== | ||
| + | * Asigurați-vă că toate certificatele sunt semnate de același CA | ||
| + | * Verificați datele de expirare: | ||
| + | <code bash> | ||
| + | openssl x509 -in android.crt -noout -dates | ||
| + | </code> | ||
| + | * Verificați că fișierele BKS au fost generate corect și copiate în ''res/raw/'' | ||
| + | |||
| + | ==== ''Connection refused'' pe portul 8883 ==== | ||
| + | * Verificați că brokerul Mosquitto ascultă pe portul 8883: | ||
| + | <code bash> | ||
| + | netstat -tlnp | grep 8883 | ||
| + | </code> | ||
| + | * Verificați setările firewall-ului pe mașina broker | ||
| + | |||
| + | ==== ''java.io.IOException: Wrong version of key store'' la încărcarea BKS ==== | ||
| + | * Asigurați-vă că folosiți versiunea corectă a BouncyCastle Provider | ||
| + | * Recreați fișierele BKS cu comanda din Pasul 1.4 | ||
| + | |||
| + | ==== Emulator Android nu poate accesa broker-ul de pe host ==== | ||
| + | * Folosiți adresa ''10.0.2.2'' în loc de ''127.0.0.1'' (adresa specială a emulatorului Android pentru host-ul gazdă) | ||
| + | |||
| + | ===== Bune practici de securitate ===== | ||
| + | |||
| + | * **Nu comiteți niciodată certificate în git** — adăugați ''certs/'' în ''.gitignore'' | ||
| + | * **Nu hardcodați parolele keystore-urilor** — folosiți ''BuildConfig'' sau fișiere ''secrets.properties'' adăugate în ''.gitignore'' | ||
| + | * **Folosiți certificate cu durată scurtă de viață** — schimbați-le periodic | ||
| + | * **Folosiți dimensiuni mari ale cheilor** — 4096 biți pentru CA, minim 2048 biți pentru server/client | ||
| + | * **Stocați cheile private în siguranță** — cheia CA (''ca.key'') nu trebuie să părăsească niciodată mașina pe care a fost generată | ||
| + | |||
| + | ===== Cerințe ===== | ||
| + | |||
| + | - Urmăriți toți pașii din acest laborator pentru a implementa mTLS în aplicația Android. | ||
| + | - Demonstrați că aplicația se conectează cu succes la broker prin mTLS (screenshot cu log-urile brokerului care arată conexiunea securizată). | ||
| + | - Verificați cu Wireshark că traficul pe portul 8883 este criptat (comparativ cu portul 1883 din laboratoarele anterioare). | ||
| + | - Implementați un mecanism prin care parolele keystore-urilor să nu mai fie hardcodate în cod (de exemplu, folosind ''BuildConfig'' cu valori din ''local.properties''). | ||