четверг, 4 октября 2012 г.

Android. HTTPS-соединение с клиентским сертификатом

 Поступила мне задачка: Написать приложение под Андройд, которое ходит с клиентским сертификатом через https соединение. Вроде, что может быть проще! Снабжать свои приложения возможностью выхода в сеть я научилась еще давно. И думала, что легко будет сделать тоже самое, только еще и с сертификатом. Но не тут-то было. Долго искала примеры, читала различные доки. Простое https-соединение у меня получалось. Как и думала - нет ничего проще. Но заставить приложение использовать клиентский сертификат....оказалось сложнее. Это сейчас я знаю, что все на самом деле легко. Надо googl'у  задавать правильные вопросы и в различных примерах убирать все ненужные " красявости". В самом начале моего поиска сбила с толку меня статья где-то на каком-то зарубежном сайте. Там говорилось, что необходимо взять сертификат формата *.pem, импортировать его в файл-хранилище, например типа bks,закинуть на Android-устройство и им пользоваться. И вот тут-то я свернула с верного пути. И потратила много времени на поиск и на различные тестовые реализации. Но как говорится - "Если долго мучиться что-нибудь получится" и мой кропотливый труд наконец-то был вознагражден! Спасибо, конечно, некоторым прекрасным лицам на форуме и на работе, которые помогали и подсказывали мне. И конечно спасибо удаче, которая посетила меня вчера. В итоге весь мой полуторонедельный труд уложился вот в этот кусочек кода:
private void onClickConnect(String path,String password,String https_url) {  
     //Создание файл хранилища для сертификата формата p12 
     //https_url - то, куда будем ходить 
     //path - путь к файлу на андройд-устройстве
     //password - пароль от сертификата. На многих сайтах пишут, 
     //чтобы пароль от файл-хранилища был такой же как и у сертификата.
            //Иначе будет ошибка.
     InputStream in = null;
     try {
  in = new FileInputStream(path);
     } catch (FileNotFoundException e1) {
  e1.printStackTrace();
     }
 
 
     //Фабрика клиентских сертификатов
     KeyManagerFactory mgrFact = null;
     try {
  mgrFact = KeyManagerFactory.getInstance("X509");
     } catch (NoSuchAlgorithmException e1) {
  e1.printStackTrace();
     }
 
     KeyStore clientStore = null;
     try {
  clientStore = KeyStore.getInstance("PKCS12");
     } catch (KeyStoreException e1) {
  e1.printStackTrace();
     }
 
     try {
      clientStore.load(in, password.toCharArray());
     } catch (NoSuchAlgorithmException e1) {
      e1.printStackTrace();
     } catch (CertificateException e1) {
      e1.printStackTrace();
     } catch (IOException e1) {
      e1.printStackTrace();
     }
 
     try {
  mgrFact.init(clientStore, password.toCharArray());
     } catch (UnrecoverableKeyException e1) {
  e1.printStackTrace();
     } catch (KeyStoreException e1) {
  e1.printStackTrace();
     } catch (NoSuchAlgorithmException e1) {
  e1.printStackTrace();
     }
 
            //Фабрика управления доверительными соединениями
     //она будет использоваться для проверки сертифкатов 
            //всех https соединений 
     final TrustManager[] trustAllCerts = new TrustManager[] {new     
                  X509TrustManager() {
         public X509Certificate[] getAcceptedIssuers() {
            System.out.println("getAcceptedIssuers");
            return null;
         }
 
         public void checkServerTrusted(X509Certificate[] chain, String authType)
             throws CertificateException {
             System.out.println("Сведения о сертификате : " + 
                    chain[0].getIssuerX500Principal().getName() +
                    " Тип авторизации : " + authType);
         }
 
         public void checkClientTrusted(X509Certificate[] chain, String authType)
      throws CertificateException {
           System.out.println("checkClientTrusted : " + authType);
         }
     } };
 
 
     //Далее создаем sslContext
     SSLContext sslContext = null;
     try {
  sslContext = SSLContext.getInstance("TLS");
     } catch (NoSuchAlgorithmException e) {
  e.printStackTrace();
     }
 
            //Настраиваем его с помощью наших фабрик
     try {
  sslContext.init(mgrFact.getKeyManagers(),trustAllCerts, new     
                java.security.SecureRandom());
     } catch (KeyManagementException e) {
  e.printStackTrace();
     }  
 
         final javax.net.ssl.SSLSocketFactory sslSocketFactory = 
                sslContext.getSocketFactory();
 
     //Создаем соединение
      HttpURLConnection conn = null;   
     try {
   conn = (HttpURLConnection) new URL(https_url).openConnection();
     } catch (MalformedURLException e) {
  e.printStackTrace();
     } catch (IOException e) {
         e.printStackTrace();
     }
 
     //Настраиваем его
     try {
  conn.setRequestMethod("GET");
  conn.setUseCaches(false);
  conn.setDoInput(true);
  conn.setReadTimeout(10000 );
  conn.setConnectTimeout(15000);   
      } catch (ProtocolException e) {
  e.printStackTrace();
      }
 
      //encodeBase64 - функция преобразует строку в Base64
             //это если еще требуется авторизация
      conn.setRequestProperty("Authorization", "Basic  
                "+encodeBase64("user:password") ); 
 
             conn.setRequestProperty("Connection", "close");  
      conn.setRequestProperty("Accept-Charset", "cp1251"); 
      сonn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");  
      ((HttpsURLConnection) conn).setSSLSocketFactory(sslSocketFactory);
 
      //Проверка имени хоста. Но проверку мы не делаем, а подтверждаем любой хост.
             //Кому надо, могут сделать проверку на что-нибудь
      ((HttpsURLConnection) conn).setHostnameVerifier(new HostnameVerifier() {
   public boolean verify(String arg0, SSLSession arg1) {
      return true;
  }
       });  
 
       // Устанавливаем соединение
       try {
  if (conn.getResponseCode() != HttpURLConnection.HTTP_OK) {
   System.out.println("Ошибка соединения"+ conn.getResponseCode());
  }else{
   System.out.println("Соединение состоялось "+conn.getResponseCode()+
                        " ["+conn.getResponseMessage()+"]");
 
   StringBuffer buf = new StringBuffer();
   InputStreamReader in = new 
                             InputStreamReader(conn.getInputStream(), "cp1251");
   int ch;
   while ((ch = in.read()) != -1) {
    buf.append((char)c);
   }
   in.close();
 
   String response = buf.toString();
                 System.out.println("response "+response);
   //вывод различной информации по сертифкату, если надо.
   System.out.println("Cipher Suite : " + conn.getCipherSuite());
   Certificate[] certs = conn.getServerCertificates();
   System.out.println("n");
   for(Certificate cert : certs){
       System.out.println("Cert Type : " + cert.getType());
       System.out.println("Cert Hash Code : " + cert.hashCode());
       System.out.println("Cert Public Key Algorithm : " + 
                               cert.getPublicKey().getAlgorithm());
       System.out.println("Cert Public Key Format : " + 
                               cert.getPublicKey().getFormat());
   }    
  }
       } catch (IOException e) {
  e.printStackTrace();
       } catch (Exception e) {
  e.printStackTrace();
       }  
 }

 Это написано все в обработчике кнопки. Но желательно вынести все в отдельные функции или даже класс, что я реализую для себя сегодня )
 Спасибо Дмитрию и obrazel за их помощь, а так же моим коллегам.
Полезные статьи:
http://my-it-notes.com/2011/09/how-to-approve-ssl-certificate-on-android/ ,
http://www.cyberforum.ru/android-dev/thread613608-page2.html ,
http://www.javadocexamples.com/java/security/KeyStore/getInstance%28String%20type%29.html - вот эта полезная статья, которая наконец-то пнула меня в верном направлении и дала завершить проект!
 Если будут какие-то замечания по коду или предложения - буду рада услышать и узнать что-то новенькое!

пятница, 14 сентября 2012 г.

Небольшой минус запуска Android-приложения в eclipse

 Во время запуска приложения я сталкивалась с тем, что eclipse ругался на то, что не может собрать проект из-за ошибки в xml файле
18:07:20,257 INFO  [main] Main  - javax.xml.transform.TransformerFactory=null
18:07:20,273 INFO  [main] Main  - java.endorsed.dirs=C:\Program Files\Java\jre7\lib\endorsed
18:07:20,289 INFO  [main] Main  - launchFile: D:\Work\Project\Android\workspace\.metadata\.plugins\org.eclipse.wst.xsl.jaxp.launching\launch\launch.xml
18:07:20,414 FATAL [main] Main  - No embedded stylesheet instruction for file: file:/C:/Project/Android/workspace/MyProject/res/layout/activity_enter_account.xml
org.eclipse.wst.xsl.jaxp.debug.invoker.TransformationException: No embedded stylesheet instruction for file: file:/C:/Project/Android/workspace/MyProject/res/layout/activity_enter_account.xml
    at org.eclipse.wst.xsl.jaxp.debug.invoker.internal.JAXPSAXProcessorInvoker.transform(JAXPSAXProcessorInvoker.java:225)
    at org.eclipse.wst.xsl.jaxp.debug.invoker.internal.JAXPSAXProcessorInvoker.transform(JAXPSAXProcessorInvoker.java:186)
    at org.eclipse.wst.xsl.jaxp.debug.invoker.internal.Main.main(Main.java:73)
Caused by: org.eclipse.wst.xsl.jaxp.debug.invoker.TransformationException: No embedded stylesheet instruction for file: file:/C:/Project/Android/workspace/MyProject/res/layout/activity_enter_account.xml
    at org.eclipse.wst.xsl.jaxp.debug.invoker.internal.JAXPSAXProcessorInvoker.transform(JAXPSAXProcessorInvoker.java:214)
    ... 2 more

 и при этом создавал в ..\res\layout\ файл ,который у меня был открыт из layout, но с доп. расширением. Т.е. был файл activity_menu.xml, создаст activity_menu.out.xml . При этом приходилось удалять этот файл.
 Порывшись в интернете - я ничего не смогла найти(лог ошибки мне тогда ничего не смог сказать). Поэтому пришлось проследить в чем же дело. На это ушло несколько дней, потому что я благополучно забывала про это, пока eclipse не начинал снова ругаться. И наконец-то мне надоело и я заметила в чем проблема. Если во время запуска выделен файл с расширением *.xml в разделе res например, то, как я поняла, eclipse пытался его скомпилировать и проект рушился. Теперь я стараюсь не забывать перед компиляцией перемещать выделение хотя бы на папку. Но иногда забываю и это очень не удобно = )

Java ME. Проблемы при установке соединения при использовании JSON.

 Недавно кодила небольшой проект для сотовых телефонов. Это был простой мидлет, который отправлял определенный запрос на сервер, получал ответ и показывал его пользователю. Я несколько лет уже писала небольшие программы для телефонов. И написать эту программу быстро, мне не составило труда. Отличие этой программы от других моих программ, в которых тоже были соединения с сервером - стало использование JSON. Написав приложение, провела тесты на своем стареньком, но замечательном телефоне Sony Ericsson. Программа заработала! Все было бы замечательно, если бы эта программа должна работать только на этом телефоне. После проведения тестовых запусков на других моделях телефона, я расстроилась. На них приложение ни в какую не устанавливало соединение. Обычно, при установке соединения, в телефоне появляется соответствующий значок - глобус, буква Е и т.д. Эти значки говорят о том, что телефон устанавливает интернет-соединение. А тут какая-то чертовщина. Телефон выдавал запрос на подтверждение выхода в интернет, я ему разрешала и он в ответ писал "Нет соединения. error in http operation". Перерыла весь интернет в поисках ответа. Все что смогла найти, это рекомендации по настройки точки доступа правильно. Я могла бы с этим согласиться, если бы не одно но! Другие приложения на этих "тестовых" телефонах спокойно выходили в интернет. На некоторое время я забыла про эту проблему, возникло много другой работы. Но пришло время, когда проект надо было показать. Поэтому пришлось заново взяться за эту задачу. Начала "установку соединения" с нуля. Попробовала отправку как обычно отправляла, все работает. Но стоило отправлять данные в формате JSON и все просто валилось. Привожу код, который крушил все мои надежды на то, что приложение установить соединение с сервером (кто-то может уже увидит ошибку=) ):
public class Connect {
...
public String Request(String url_str){
        String sBuff = null;
        String url = url_str;
        try{
            ErrorConnectMessage = "";
            con = (HttpConnection)Connector.open(url);
            con.setRequestMethod(HttpConnection.GET);//GET
            con.setRequestProperty("User-Agent","Profile/MIDP-2.0 Confirguration/CLDC-1.0");
            con.setRequestProperty("Accept_Language","en-US");
            con.setRequestProperty("Content-Type","//text plain;charset=windows-1251");
            //con.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
            con.setRequestProperty("Connection", "close");
            in = con.openInputStream();
            ErrorConnect = con.getResponseCode();
            if (ErrorConnect == 200){
                StringBuffer sb = new StringBuffer(); 
                int chr;
                while ((chr = in.read()) != -1)
                      sb.append((char ) ((chr >= 0xc0 && chr <= 0xFF) ? (chr + 0x350) : chr));
                sBuff =  sb.toString();          
 
                if (sBuff.equals("access denided")){
                    ErrorConnectMessage = sBuff;
                    sBuff = "";
                }
                else if (sBuff.equals("") || (sBuff == null)){
                    ErrorConnectMessage = "empty";
                    sBuff = "";
                }
                else 
                    ErrorConnectMessage = "";
            }else {
                String res = new ListOfConnectionErrors().GetErrorText(ErrorConnect);
                ErrorConnectMessage = "error."+res;
                sBuff = "";
            }
        }catch ( IllegalArgumentException e ) {
            ErrorConnectMessage = "error. Неверный URL. "+e.getMessage();
            sBuff = "";
            return sBuff;
        }catch(IOException e){
            ErrorConnectMessage="error. Нет соединения. "+e.getMessage();
            sBuff = "";
            return sBuff;
        }catch ( Exception e ) {
            ErrorConnectMessage="error. Cервер не ответил. "+e.getMessage();
            sBuff = "";
            return sBuff;
        }finally {
            nullSafeClose(in);
            nullSafeClose(con);
            System.out.println("close");
            return sBuff;
        }
    }
 
 Строка, которую передавала в функцию
 
String Value = "12аа23456789"; //символы русские
String sURL = "http://.../myrequests/?json="+"{"key":""+urlEncode(Value)+""}";

 В итоге потеряв еще полчаса до меня дошло, не без помощи моего коллеги, что я совершила "орфографическую" ошибку в своем коде. Вот так всегда бывает, пишешь приложение и глаз замыливается на столько, что банальных ошибок уже не видно. И помогает только взгляд со стороны. Я оказывается в URL обертку брала только сам параметр, который передавала на сервер и никак не задумывалась о строчке JSON с такими красивыми символами как наклонная черта, фигурные скобки. А ведь из-за них мое приложение вылетало в ошибку. Поколдовав еще 5 минут над строкой, привела ее к такому виду:
String Value = "12аа23456789"; //символы русские
String sURL = "http://.../myrequests/?json="+urlEncode("{"key":""+Value+""}");
 
 И после такого изменения в коде все телефоны, которые были в шаговой доступности от меня успешно установили соединение с сервером. Так что совет мне и всем, будьте внимательны, если в обычное, добавляете немного необычного.Проверьте еще все раз и если все безуспешно, то дайте кому-нибудь глянуть ваш код со стороны. Это очень помогает)))

четверг, 13 сентября 2012 г.

Изучение иностранного языка — искусство!

Знаете ли вы, что изучение иностранного языка — это искусство?!
Искусство, потому что оно требует не только желания и действия, но и особых знаний. Можно потратить годы на изучение английского языка, но так и не получить желаемый результат. И дело совсем не в отсутствии способностей к изучению!
Те, кому легко удаётся освоить иностранный язык, делают это не благодаря «врождённым способностям», а за счёт сознательного или бессознательного соблюдения похожих принципов.
LinguaLeo активно использует 7 секретов успешного освоения английского языка.


Что такое Форекс? Кто такой Трейдер?

 А и правда что такое Форекс и с чем его едят? Кто такие трейдеры: это современные рыцари или какая-то рок-группа?