package org.ovirt.mobile.movirt.util; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.util.Log; import org.androidannotations.annotations.Background; import org.androidannotations.annotations.Bean; import org.androidannotations.annotations.EBean; import org.ovirt.mobile.movirt.auth.properties.manager.AccountPropertiesManager; import org.ovirt.mobile.movirt.auth.properties.manager.OnThread; import org.ovirt.mobile.movirt.auth.properties.property.Cert; import org.spongycastle.asn1.ASN1OctetString; import org.spongycastle.asn1.x500.RDN; import org.spongycastle.asn1.x500.X500Name; import org.spongycastle.asn1.x500.style.BCStyle; import org.spongycastle.asn1.x500.style.IETFUtils; import org.spongycastle.asn1.x509.AccessDescription; import org.spongycastle.asn1.x509.AuthorityInformationAccess; import org.spongycastle.asn1.x509.Extension; import org.spongycastle.cert.jcajce.JcaX509CertificateHolder; import java.io.BufferedInputStream; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.net.URL; import java.security.cert.Certificate; import java.security.cert.CertificateEncodingException; import java.security.cert.CertificateException; import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; @EBean(scope = EBean.Scope.Singleton) public class CertHelper { private static final String TAG = CertHelper.class.getSimpleName(); @Bean AccountPropertiesManager propertiesManager; /** * Deletes certificate chain and valid hostnames */ public void deleteAllCerts() { propertiesManager.setCertificateChain(new Cert[]{}); propertiesManager.setValidHostnameList(new String[]{}); } /** * Deletes certificate chain and valid hostnames */ @Background public void deleteAllCertsInBackground() { deleteAllCerts(); } /** * @param url cert url * @param startNewChain if true deletes old certificates before starting new chain, otherwise appends to the chain * @throws IllegalStateException if failed, can also delete all certificates if it gets to inconsistent state */ public void downloadAndStoreCert(@NonNull URL url, @NonNull URL validHostname, boolean startNewChain) { CertificateFactory cf = CertHelper.getX509CertificateFactory(); InputStream caInput = null; ByteArrayOutputStream caOutput = null; try { caInput = new BufferedInputStream(url.openStream()); caOutput = new ByteArrayOutputStream(); byte[] buffer = new byte[1024]; int len; while ((len = caInput.read(buffer, 0, buffer.length)) != -1) { caOutput.write(buffer, 0, len); } caOutput.flush(); caInput = new ByteArrayInputStream(caOutput.toByteArray()); } catch (IOException e) { ObjectUtils.closeSilently(caInput, caOutput); throw new IllegalStateException("Error loading certificate: " + e.getMessage()); } List<Cert> certs; try { certs = startNewChain ? new ArrayList<Cert>(1) : new ArrayList<>(Arrays.asList(propertiesManager.getCertificateChain())); Certificate issuer = cf.generateCertificate(caInput); if (!startNewChain) { Certificate lastInChain = certs.get(certs.size() - 1).asCertificate(); lastInChain.verify(issuer.getPublicKey()); } Cert cert = Cert.fromCertificate(issuer); cert.setLocation(url.toString()); cert.setLocationType(Cert.LOCATION_TYPE.NETWORK); certs.add(cert); } catch (CertificateException e) { throw new IllegalStateException("Error parsing certificate: " + e.getMessage()); } catch (Exception e) { throw new IllegalStateException("New certificate doesn't sign last certificate in the chain: " + e.getMessage()); } finally { ObjectUtils.closeSilently(caInput, caOutput); } try { propertiesManager.setCertificateChain(certs.toArray(new Cert[certs.size()]), OnThread.BACKGROUND); if (startNewChain) { propertiesManager.setValidHostnameList(new String[]{validHostname.getHost()}, OnThread.BACKGROUND); } } catch (Exception e) { deleteAllCertsInBackground(); // hostname and ca must be atomic throw new IllegalStateException("Error storing certificate: " + e.getMessage()); } finally { ObjectUtils.closeSilently(caInput, caOutput); } } /** * @param certificate certificate * @return common name * @throws IllegalArgumentException if certificate is incorrect type */ @NonNull public static String getCommonName(Certificate certificate) { assertX509Certificate(certificate); String result = null; try { X500Name x500name = new JcaX509CertificateHolder((X509Certificate) certificate).getSubject(); RDN cn = x500name.getRDNs(BCStyle.CN)[0]; result = IETFUtils.valueToString(cn.getFirst().getValue()); } catch (CertificateEncodingException ignored) { } return (result == null) ? "" : result; } public static boolean isCA(Certificate certificate) { try { certificate.verify(certificate.getPublicKey()); } catch (Exception e) { return false; } return true; } @NonNull public static List<Certificate> asCertificatesSafe(Cert[] certs) { try { return asCertificates(certs); } catch (Exception ignored) { } return Collections.emptyList(); } /** * @throws IllegalStateException * @throws IllegalArgumentException */ @NonNull public static List<Certificate> asCertificates(Cert[] certs) { if (certs == null) { throw new IllegalArgumentException("certs are null"); } List<Certificate> certificates = new ArrayList<>(certs.length); for (Cert cert : certs) { certificates.add(cert.asCertificate()); } return certificates; } @Nullable public static String getIssuerUrl(Certificate certificate) { assertX509Certificate(certificate); byte[] encodedExtensionValue = ((X509Certificate) certificate).getExtensionValue(Extension.authorityInfoAccess.getId()); if (encodedExtensionValue == null) { return null; } ASN1OctetString octetString = ASN1OctetString.getInstance(encodedExtensionValue); AuthorityInformationAccess informationAccess = AuthorityInformationAccess.getInstance(octetString.getOctets()); for (AccessDescription description : informationAccess.getAccessDescriptions()) { if (description.getAccessMethod().equals(AccessDescription.id_ad_caIssuers)) { return description.getAccessLocation().getName().toString(); } } return null; } /** * @return CertificateFactory * @throws IllegalStateException */ private static CertificateFactory getX509CertificateFactory() { try { return CertificateFactory.getInstance("X.509"); } catch (CertificateException e) { Log.i(TAG, ObjectUtils.throwableToString(e)); throw new IllegalStateException("Problem getting the certificate factory"); } } private static void assertX509Certificate(Certificate certificate) { if (!(certificate instanceof X509Certificate)) { throw new IllegalArgumentException("Certificate is not X509Certificate"); } } }