File indexing completed on 2025-02-02 04:47:49

0001 /*
0002  * SPDX-FileCopyrightText: 2015 Vineet Garg <grg.vineet@gmail.com>
0003  *
0004  * SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0005 */
0006 
0007 package org.kde.kdeconnect.Helpers.SecurityHelpers;
0008 
0009 import android.content.Context;
0010 import android.content.SharedPreferences;
0011 import android.content.res.Configuration;
0012 import android.content.res.Resources;
0013 import android.preference.PreferenceManager;
0014 import android.util.Base64;
0015 import android.util.Log;
0016 
0017 import org.bouncycastle.asn1.x500.RDN;
0018 import org.bouncycastle.asn1.x500.X500Name;
0019 import org.bouncycastle.asn1.x500.X500NameBuilder;
0020 import org.bouncycastle.asn1.x500.style.BCStyle;
0021 import org.bouncycastle.asn1.x500.style.IETFUtils;
0022 import org.bouncycastle.cert.X509v3CertificateBuilder;
0023 import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder;
0024 import org.bouncycastle.operator.ContentSigner;
0025 import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
0026 import org.bouncycastle.util.Arrays;
0027 import org.kde.kdeconnect.Helpers.DeviceHelper;
0028 import org.kde.kdeconnect.Helpers.RandomHelper;
0029 
0030 import java.io.ByteArrayInputStream;
0031 import java.io.IOException;
0032 import java.math.BigInteger;
0033 import java.net.Socket;
0034 import java.net.SocketException;
0035 import java.security.KeyStore;
0036 import java.security.MessageDigest;
0037 import java.security.NoSuchAlgorithmException;
0038 import java.security.PrivateKey;
0039 import java.security.PublicKey;
0040 import java.security.cert.Certificate;
0041 import java.security.cert.CertificateEncodingException;
0042 import java.security.cert.CertificateException;
0043 import java.security.cert.CertificateFactory;
0044 import java.security.cert.X509Certificate;
0045 import java.time.Instant;
0046 import java.time.LocalDate;
0047 import java.time.ZoneId;
0048 import java.util.Date;
0049 import java.util.Formatter;
0050 import java.util.Locale;
0051 
0052 import javax.net.ssl.KeyManagerFactory;
0053 import javax.net.ssl.SSLContext;
0054 import javax.net.ssl.SSLSocket;
0055 import javax.net.ssl.SSLSocketFactory;
0056 import javax.net.ssl.TrustManager;
0057 import javax.net.ssl.TrustManagerFactory;
0058 import javax.net.ssl.X509TrustManager;
0059 import javax.security.auth.x500.X500Principal;
0060 
0061 public class SslHelper {
0062 
0063     public static Certificate certificate; //my device's certificate
0064     private static CertificateFactory factory;
0065     static {
0066         try {
0067             factory = CertificateFactory.getInstance("X.509");
0068         } catch (CertificateException e) {
0069             throw new RuntimeException(e);
0070         }
0071     }
0072 
0073     private final static TrustManager[] trustAllCerts = new TrustManager[]{new X509TrustManager() {
0074         public java.security.cert.X509Certificate[] getAcceptedIssuers() {
0075             return new X509Certificate[0];
0076         }
0077 
0078         @Override
0079         public void checkClientTrusted(X509Certificate[] certs, String authType) {
0080         }
0081 
0082         @Override
0083         public void checkServerTrusted(X509Certificate[] certs, String authType) {
0084         }
0085 
0086     }
0087     };
0088 
0089     public static void initialiseCertificate(Context context) {
0090         PrivateKey privateKey;
0091         PublicKey publicKey;
0092 
0093         try {
0094             privateKey = RsaHelper.getPrivateKey(context);
0095             publicKey = RsaHelper.getPublicKey(context);
0096         } catch (Exception e) {
0097             Log.e("SslHelper", "Error getting keys, can't create certificate");
0098             return;
0099         }
0100 
0101         Log.i("SslHelper", "Key algorithm: " + publicKey.getAlgorithm());
0102 
0103         String deviceId = DeviceHelper.getDeviceId(context);
0104 
0105         boolean needsToGenerateCertificate = false;
0106         SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(context);
0107         if (settings.contains("certificate")) {
0108             try {
0109                 SharedPreferences globalSettings = PreferenceManager.getDefaultSharedPreferences(context);
0110                 byte[] certificateBytes = Base64.decode(globalSettings.getString("certificate", ""), 0);
0111                 X509Certificate cert = (X509Certificate) parseCertificate(certificateBytes);
0112 
0113                 String certDeviceId = getCommonNameFromCertificate(cert);
0114                 if (!certDeviceId.equals(deviceId)) {
0115                     Log.e("KDE/SslHelper", "The certificate stored is from a different device id! (found: " + certDeviceId + " expected:" + deviceId + ")");
0116                     needsToGenerateCertificate = true;
0117                 } else {
0118                     certificate = cert;
0119                 }
0120             } catch (Exception e) {
0121                 Log.e("KDE/SslHelper", "Exception reading own certificate", e);
0122                 needsToGenerateCertificate = true;
0123             }
0124 
0125         } else {
0126             needsToGenerateCertificate = true;
0127         }
0128 
0129         if (needsToGenerateCertificate) {
0130             Log.i("KDE/SslHelper", "Generating a certificate");
0131             try {
0132                 //Fix for https://issuetracker.google.com/issues/37095309
0133                 Locale initialLocale = Locale.getDefault();
0134                 setLocale(Locale.ENGLISH, context);
0135 
0136                 X500NameBuilder nameBuilder = new X500NameBuilder(BCStyle.INSTANCE);
0137                 nameBuilder.addRDN(BCStyle.CN, deviceId);
0138                 nameBuilder.addRDN(BCStyle.OU, "KDE Connect");
0139                 nameBuilder.addRDN(BCStyle.O, "KDE");
0140                 final LocalDate localDate = LocalDate.now().minusYears(1);
0141                 final Instant notBefore = localDate.atStartOfDay(ZoneId.systemDefault()).toInstant();
0142                 final Instant notAfter = localDate.plusYears(10).atStartOfDay(ZoneId.systemDefault())
0143                         .toInstant();
0144                 X509v3CertificateBuilder certificateBuilder = new JcaX509v3CertificateBuilder(
0145                         nameBuilder.build(),
0146                         BigInteger.ONE,
0147                         Date.from(notBefore),
0148                         Date.from(notAfter),
0149                         nameBuilder.build(),
0150                         publicKey
0151                 );
0152                 String keyAlgorithm = privateKey.getAlgorithm();
0153                 String signatureAlgorithm = "RSA".equals(keyAlgorithm)? "SHA512withRSA" : "SHA512withECDSA";
0154                 ContentSigner contentSigner = new JcaContentSignerBuilder(signatureAlgorithm).build(privateKey);
0155                 byte[] certificateBytes = certificateBuilder.build(contentSigner).getEncoded();
0156                 certificate = parseCertificate(certificateBytes);
0157 
0158                 SharedPreferences.Editor edit = settings.edit();
0159                 edit.putString("certificate", Base64.encodeToString(certificateBytes, 0));
0160                 edit.apply();
0161 
0162                 setLocale(initialLocale, context);
0163             } catch (Exception e) {
0164                 Log.e("KDE/initialiseCert", "Exception", e);
0165             }
0166         }
0167     }
0168 
0169     private static void setLocale(Locale locale, Context context) {
0170         Locale.setDefault(locale);
0171         Resources resources = context.getResources();
0172         Configuration config = resources.getConfiguration();
0173         config.locale = locale;
0174         resources.updateConfiguration(config, resources.getDisplayMetrics());
0175     }
0176 
0177     public static boolean isCertificateStored(Context context, String deviceId) {
0178         SharedPreferences devicePreferences = context.getSharedPreferences(deviceId, Context.MODE_PRIVATE);
0179         String cert = devicePreferences.getString("certificate", "");
0180         return !cert.isEmpty();
0181     }
0182 
0183     /**
0184      * Returns the stored certificate for a trusted device
0185      **/
0186     public static Certificate getDeviceCertificate(Context context, String deviceId) throws CertificateException {
0187         SharedPreferences devicePreferences = context.getSharedPreferences(deviceId, Context.MODE_PRIVATE);
0188         byte[] certificateBytes = Base64.decode(devicePreferences.getString("certificate", ""), 0);
0189         return parseCertificate(certificateBytes);
0190     }
0191 
0192     private static SSLContext getSslContextForDevice(Context context, String deviceId, boolean isDeviceTrusted) {
0193         //TODO: Cache
0194         try {
0195             // Get device private key
0196             PrivateKey privateKey = RsaHelper.getPrivateKey(context);
0197 
0198             // Setup keystore
0199             KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
0200             keyStore.load(null, null);
0201             keyStore.setKeyEntry("key", privateKey, "".toCharArray(), new Certificate[]{certificate});
0202 
0203             // Add device certificate if device trusted
0204             if (isDeviceTrusted) {
0205                 Certificate remoteDeviceCertificate = getDeviceCertificate(context, deviceId);
0206                 keyStore.setCertificateEntry(deviceId, remoteDeviceCertificate);
0207             }
0208 
0209             // Setup key manager factory
0210             KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
0211             keyManagerFactory.init(keyStore, "".toCharArray());
0212 
0213 
0214             // Setup default trust manager
0215             TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
0216             trustManagerFactory.init(keyStore);
0217 
0218             // Setup custom trust manager if device not trusted
0219             SSLContext tlsContext = SSLContext.getInstance("TLSv1.2"); // Use TLS up to 1.2, since 1.3 seems to cause issues in some (older?) devices
0220             if (isDeviceTrusted) {
0221                 tlsContext.init(keyManagerFactory.getKeyManagers(), trustManagerFactory.getTrustManagers(), RandomHelper.secureRandom);
0222             } else {
0223                 tlsContext.init(keyManagerFactory.getKeyManagers(), trustAllCerts, RandomHelper.secureRandom);
0224             }
0225             return tlsContext;
0226         } catch (Exception e) {
0227             Log.e("KDE/SslHelper", "Error creating tls context", e);
0228         }
0229         return null;
0230 
0231     }
0232 
0233     private static void configureSslSocket(SSLSocket socket, boolean isDeviceTrusted, boolean isClient) throws SocketException {
0234        socket.setSoTimeout(10000);
0235         if (isClient) {
0236             socket.setUseClientMode(true);
0237         } else {
0238             socket.setUseClientMode(false);
0239             if (isDeviceTrusted) {
0240                 socket.setNeedClientAuth(true);
0241             } else {
0242                 socket.setWantClientAuth(true);
0243             }
0244         }
0245 
0246     }
0247 
0248     public static SSLSocket convertToSslSocket(Context context, Socket socket, String deviceId, boolean isDeviceTrusted, boolean clientMode) throws IOException {
0249         SSLSocketFactory sslsocketFactory = SslHelper.getSslContextForDevice(context, deviceId, isDeviceTrusted).getSocketFactory();
0250         SSLSocket sslsocket = (SSLSocket) sslsocketFactory.createSocket(socket, socket.getInetAddress().getHostAddress(), socket.getPort(), true);
0251         SslHelper.configureSslSocket(sslsocket, isDeviceTrusted, clientMode);
0252         return sslsocket;
0253     }
0254 
0255     public static String getCertificateHash(Certificate certificate) {
0256         byte[] hash;
0257         try {
0258             hash = MessageDigest.getInstance("SHA-256").digest(certificate.getEncoded());
0259         } catch (NoSuchAlgorithmException | CertificateEncodingException e) {
0260             throw new RuntimeException(e);
0261         }
0262         Formatter formatter = new Formatter();
0263         for (byte b : hash) {
0264             formatter.format("%02x:", b);
0265         }
0266         return formatter.toString();
0267     }
0268 
0269     public static Certificate parseCertificate(byte[] certificateBytes) throws CertificateException {
0270         return factory.generateCertificate(new ByteArrayInputStream(certificateBytes));
0271     }
0272 
0273     private static String getCommonNameFromCertificate(X509Certificate cert) {
0274         X500Principal principal = cert.getSubjectX500Principal();
0275         X500Name x500name = new X500Name(principal.getName());
0276         RDN rdn = x500name.getRDNs(BCStyle.CN)[0];
0277         return IETFUtils.valueToString(rdn.getFirst().getValue());
0278     }
0279 
0280     public static String getVerificationKey(Certificate certificateA, Certificate certificateB) {
0281         try {
0282             byte[] a = certificateA.getPublicKey().getEncoded();
0283             byte[] b = certificateB.getPublicKey().getEncoded();
0284 
0285             if (Arrays.compareUnsigned(a, b) < 0) {
0286                 // Swap them so on both devices they are in the same order
0287                 byte[] aux = a;
0288                 a = b;
0289                 b = aux;
0290             }
0291 
0292             byte[] concat = new byte[a.length + b.length];
0293             System.arraycopy(a, 0, concat, 0, a.length);
0294             System.arraycopy(b, 0, concat, a.length, b.length);
0295 
0296             byte[] hash = MessageDigest.getInstance("SHA-256").digest(concat);
0297             Formatter formatter = new Formatter();
0298             for (byte value : hash) {
0299                 formatter.format("%02x", value);
0300             }
0301             return formatter.toString();
0302         } catch(Exception e) {
0303             e.printStackTrace();
0304             return "error";
0305         }
0306     }
0307 }