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 }