/* * Copyright (c) 2012-2016 Arne Schwabe * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt */ package de.blinkt.openvpn.core; import android.annotation.SuppressLint; import android.content.Context; import android.content.res.Resources; import android.text.TextUtils; import se.leap.bitmaskclient.R; import de.blinkt.openvpn.VpnProfile; import org.spongycastle.util.io.pem.PemObject; import org.spongycastle.util.io.pem.PemReader; import javax.security.auth.x500.X500Principal; import java.io.*; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.security.cert.Certificate; import java.security.cert.CertificateException; import java.security.cert.CertificateExpiredException; import java.security.cert.CertificateFactory; import java.security.cert.CertificateNotYetValidException; import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.Date; import java.util.Hashtable; import java.util.Vector; public class X509Utils { public static Certificate[] getCertificatesFromFile(String certfilename) throws FileNotFoundException, CertificateException { CertificateFactory certFact = CertificateFactory.getInstance("X.509"); Vector certificates = new Vector<>(); if(VpnProfile.isEmbedded(certfilename)) { int subIndex = certfilename.indexOf("-----BEGIN CERTIFICATE-----"); do { // The java certifcate reader is ... kind of stupid // It does NOT ignore chars before the --BEGIN ... subIndex = Math.max(0, subIndex); InputStream inStream = new ByteArrayInputStream(certfilename.substring(subIndex).getBytes()); certificates.add(certFact.generateCertificate(inStream)); subIndex = certfilename.indexOf("-----BEGIN CERTIFICATE-----", subIndex+1); } while (subIndex > 0); return certificates.toArray(new Certificate[certificates.size()]); } else { InputStream inStream = new FileInputStream(certfilename); return new Certificate[] {certFact.generateCertificate(inStream)}; } } public static PemObject readPemObjectFromFile (String keyfilename) throws IOException { Reader inStream; if(VpnProfile.isEmbedded(keyfilename)) inStream = new StringReader(VpnProfile.getEmbeddedContent(keyfilename)); else inStream = new FileReader(new File(keyfilename)); PemReader pr = new PemReader(inStream); PemObject r = pr.readPemObject(); pr.close(); return r; } public static String getCertificateFriendlyName (Context c, String filename) { if(!TextUtils.isEmpty(filename)) { try { X509Certificate cert = (X509Certificate) getCertificatesFromFile(filename)[0]; String friendlycn = getCertificateFriendlyName(cert); friendlycn = getCertificateValidityString(cert, c.getResources()) + friendlycn; return friendlycn; } catch (Exception e) { VpnStatus.logError("Could not read certificate" + e.getLocalizedMessage()); } } return c.getString(R.string.cannotparsecert); } public static String getCertificateValidityString(X509Certificate cert, Resources res) { try { cert.checkValidity(); } catch (CertificateExpiredException ce) { return "EXPIRED: "; } catch (CertificateNotYetValidException cny) { return "NOT YET VALID: "; } Date certNotAfter = cert.getNotAfter(); Date now = new Date(); long timeLeft = certNotAfter.getTime() - now.getTime(); // Time left in ms // More than 72h left, display days // More than 3 months display months if (timeLeft > 90l* 24 * 3600 * 1000) { long months = getMonthsDifference(now, certNotAfter); return res.getQuantityString(R.plurals.months_left, (int) months, months); } else if (timeLeft > 72 * 3600 * 1000) { long days = timeLeft / (24 * 3600 * 1000); return res.getQuantityString(R.plurals.days_left, (int) days, days); } else { long hours = timeLeft / (3600 * 1000); return res.getQuantityString(R.plurals.hours_left, (int)hours, hours); } } public static int getMonthsDifference(Date date1, Date date2) { int m1 = date1.getYear() * 12 + date1.getMonth(); int m2 = date2.getYear() * 12 + date2.getMonth(); return m2 - m1 + 1; } public static String getCertificateFriendlyName(X509Certificate cert) { X500Principal principal = cert.getSubjectX500Principal(); byte[] encodedSubject = principal.getEncoded(); String friendlyName=null; /* Hack so we do not have to ship a whole Spongy/bouncycastle */ Exception exp=null; try { @SuppressLint("PrivateApi") Class X509NameClass = Class.forName("com.android.org.bouncycastle.asn1.x509.X509Name"); Method getInstance = X509NameClass.getMethod("getInstance",Object.class); Hashtable defaultSymbols = (Hashtable) X509NameClass.getField("DefaultSymbols").get(X509NameClass); if (!defaultSymbols.containsKey("1.2.840.113549.1.9.1")) defaultSymbols.put("1.2.840.113549.1.9.1","eMail"); Object subjectName = getInstance.invoke(X509NameClass, encodedSubject); Method toString = X509NameClass.getMethod("toString",boolean.class,Hashtable.class); friendlyName= (String) toString.invoke(subjectName,true,defaultSymbols); } catch (ClassNotFoundException e) { exp =e ; } catch (NoSuchMethodException e) { exp =e; } catch (InvocationTargetException e) { exp =e; } catch (IllegalAccessException e) { exp =e; } catch (NoSuchFieldException e) { exp =e; } if (exp!=null) VpnStatus.logException("Getting X509 Name from certificate", exp); /* Fallback if the reflection method did not work */ if(friendlyName==null) friendlyName = principal.getName(); // Really evil hack to decode email address // See: http://code.google.com/p/android/issues/detail?id=21531 String[] parts = friendlyName.split(","); for (int i=0;i