【问题标题】:Self-signed certificate with android app带有安卓应用程序的自签名证书
【发布时间】:2018-12-31 03:34:24
【问题描述】:

我能够正确加载证书并可以访问我的服务器(小步骤...)我现在的问题是参数(“username=admin&password=admin”)没有进入服务器(请求是以空值出现)。这是我从here 获得的 Android 代码(其中一些):

    String host = "turbo";
    int port = 8081;
    String command = "get_status";
    String user = "admin";
    String password = "admin";
    HttpsURLConnection connection = null;

    try {
        Certificate ca;
        String keyStoreType;
        KeyStore keyStore;
        String tmfAlgorithm;
        TrustManagerFactory tmf;
        Scanner scanner;
        boolean hasInput;
        /**
         * Set up certificate stuff first
         */
        CertificateFactory cf = CertificateFactory.getInstance("X.509");
        InputStream ins = myContext.getResources().openRawResource(
                myContext.getResources().getIdentifier("server_crt", "raw",
                        myContext.getPackageName()));
        BufferedInputStream caInput = new BufferedInputStream(ins);
        ca = cf.generateCertificate(caInput);
        caInput.close();
        keyStoreType = KeyStore.getDefaultType();
        keyStore = KeyStore.getInstance(keyStoreType);
        keyStore.load(null, null);
        keyStore.setCertificateEntry("ca", ca);
        tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
        tmf = TrustManagerFactory.getInstance(tmfAlgorithm);
        tmf.init(keyStore);
        SSLContext context = SSLContext.getInstance("TLS");
        context.init(null, tmf.getTrustManagers(), null);

        URL url = new URL(String.format("https://%s:%d/test/%s", host, port, command));

        connection = (HttpsURLConnection) url.openConnection();
        connection.setSSLSocketFactory(context.getSocketFactory());
        connection.setReadTimeout(1500);
        connection.setConnectTimeout(1500);
        connection.setRequestMethod("POST");
        connection.setDoInput(true);
        connection.setDoOutput(true);
        DataOutputStream os = new DataOutputStream(connection.getOutputStream());
        os.writeBytes("username=admin&password=admin");
        String b = getPostDataString(params);
        os.flush();
        os.close();

我不得不将我的 Flask 运行方法更改为 host='0.0.0.0',现在我正在连接下面的 Android 代码,但我遇到了其他问题:我在获取参数时遇到问题(用户名和密码)。

在服务器端,我收到一个参数(用户名)为空的错误。

我也试过这个:

        Map<String,Object> params = new LinkedHashMap<>();
        params.put("username", user);
        params.put("password", password);
        StringBuilder postData = new StringBuilder();
        for (Map.Entry<String,Object> param : params.entrySet()) {
            if (postData.length() != 0) postData.append('&');
            postData.append(URLEncoder.encode(param.getKey(), "UTF-8"));
            postData.append('=');
            postData.append(URLEncoder.encode(String.valueOf(param.getValue()), "UTF-8"));
        }
        byte[] postDataBytes = postData.toString().getBytes("UTF-8");
        connection.setRequestProperty("Content-Length", String.valueOf(postDataBytes.length));
        connection.setDoOutput(true);
        connection.getOutputStream().write(postDataBytes);

        connection.setReadTimeout(30000);
        connection.setConnectTimeout(30000);
        int responseCode = connection.getResponseCode();

但是 responseCode 是 400,我在服务器上遇到了同样的错误(用户名未定义) 有什么建议吗?

app.run(debug=server_debug, ssl_context=context, host='0.0.0.0', port=server_port)

我在使用自签名认证的 Android 应用程序通过 HTTPS 连接时遇到问题。

我创建了 create_key.cfg:

[req]
default_bits=2048
prompt=no
default_md=sha256
x509_extensions=v3_req
#req_extensions=req_ext
distinguished_name=dn

[dn]
C=US
ST=Florida
L=Satellite Beach
O=ThompCo, Inc
OU=turbo
emailAddress=Jordan@ThompCo.com
CN=turbo

[v3_req]
subjectAltName=@alt_names

[alt_names]
DNS.1=turbo
DNS.2=turbo.thompco.com
DNS.3=thompco.com

然后我运行了以下命令:

openssl req -x509 -nodes -days 730 -newkey rsa:2048 -keyout server.key -out server.crt -config create_key.cfg -sha256
openssl x509 -in server.crt -out server.pem -outform PEM

创建文件:

server.crt  server.key  server.pem

当我使用 Chrome 访问该站点时,我得到了正确的证书信息(显然存在证书不安全的警告)

当我运行我的测试 python 脚本(使用 urllib2)时,一切都按预期工作。

我试过直接卷曲:

curl https://turbo:8081

但是我收到了这个错误:

curl: (60) SSL certificate problem: self signed certificate
More details here: https://curl.haxx.se/docs/sslcerts.html

curl performs SSL certificate verification by default, using a "bundle"
 of Certificate Authority (CA) public keys (CA certs). If the default
 bundle file isn't adequate, you can specify an alternate file
 using the --cacert option.
If this HTTPS server uses a certificate signed by a CA represented in
 the bundle, the certificate verification probably failed due to a
 problem with the certificate (it might be expired, or the name might
 not match the domain name in the URL).
If you'd like to turn off curl's verification of the certificate, use
 the -k (or --insecure) option.

我尝试按照here 的建议将 crt 文件添加到 /usr/lib/ssl/certs 但没有帮助(也许这是问题的根源?)

我在 Android 中涉足(阅读“我不知道我在做什么”),并(在 Google 和世界各地许多优秀开发人员的帮助下)编写了以下内容(请注意,我将 server.pem 放入res/raw 文件夹):

package com.thompco.test;

import android.content.Context;

import java.io.BufferedInputStream;
import java.io.InputStream;
import java.net.URL;
import java.security.KeyStore;
import java.security.cert.Certificate;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.Scanner;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManagerFactory;

class DoCurl extends Thread {
    Context myParentContext;

    public DoCurl(Context context) {
        myParentContext = context;
    }

    public void run() {
        String host = "turbo";
        int port = 8081;
        HttpsURLConnection connection = null;
        String command = "get_status";
        String http = "https";
        String user = "admin";
        String password = "admin";

        try {
            Certificate ca;
            String keyStoreType;
            KeyStore keyStore;
            String tmfAlgorithm;
            TrustManagerFactory tmf;
            SSLContext context;
            URL url;
            InputStream in;
            Scanner scanner;
            boolean hasInput;
            CertificateFactory cf = CertificateFactory.getInstance("X.509");
            InputStream ins = myParentContext.getResources().openRawResource(
                    myParentContext.getResources().getIdentifier("server",
                            "raw", myParentContext.getPackageName()));
            BufferedInputStream caInput = new BufferedInputStream(ins);
            try {
                ca = cf.generateCertificate(caInput);
                System.out.println("ca=" + ((X509Certificate) ca).getSubjectDN());
            } finally {
                caInput.close();
            }
            keyStoreType = KeyStore.getDefaultType();
            keyStore = KeyStore.getInstance(keyStoreType);
            keyStore.load(null, null);
            keyStore.setCertificateEntry("ca", ca);
            tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
            tmf = TrustManagerFactory.getInstance(tmfAlgorithm);
            tmf.init(keyStore);
            context = SSLContext.getInstance("TLS");
            context.init(null, tmf.getTrustManagers(), null);
            url = new URL(String.format("%s://%s:%d/test/%s", http, host, port, command));
            connection = (HttpsURLConnection) url.openConnection();
            connection.setSSLSocketFactory(context.getSocketFactory());
            connection.setRequestProperty("username", user);
            connection.setRequestProperty("password", password);
            connection.setReadTimeout(30000);
            connection.setConnectTimeout(30000);
            connection.setRequestMethod("POST");
            connection.setDoInput(true);
            connection.connect();
            if (connection.getResponseCode() == 200) {
                // Success
                // Further processing here
            } else {
                // Error handling code goes here
            }
            in = connection.getInputStream();

            scanner = new Scanner(in);
            scanner.useDelimiter("\\A");

            hasInput = scanner.hasNext();
            if (hasInput) {
                String tmp = scanner.next();
            }
        } catch (Exception e) {
            Exception blah = e;
        } finally {
            if (connection != null) {
                connection.disconnect();
            }
        }
    }
}

当它运行时,它会抛出一个 java.security.cert.CertPathValidatorException:尝试连接时找不到证书路径的信任锚。

我的目标是能够部署此设备(当前运行 Flask)并能够从本地网络上的 Android 手机访问它。如果我能侥幸成功,我不想安装真正受信任的证书。

服务器恰好在 Windows 机器上以 python 运行。它适用于我的 curl 测试,我知道这不是最好的方法,但我现在只是在测试 Android 应用程序。

我回到我的 Python 测试代码并确认我连接正确(注意我使用的是适当的 .crt 文件)应该验证自签名证书的有效性:

def send_curl(command, host, port, data, use_ssh=True):
    logger = get_logger()
    logger.debug("host:{}, port:{}".format(host, port))
    context = ssl.create_default_context()
    context.load_verify_locations("../keys/server.crt")
    url = '{}://{}:{}/test/{}'.format("https", host, port, command)
    for k, v in data.items():
        data[k] = str(v)
    data = json.dumps(data)
    logger.debug("URL is {}".format(url))
    logger.debug("data is {}".format(data))
    req = urllib2.Request(url, data, {'Content-Type': 'application/json'})
    try:
        rtn = urllib2.urlopen(req, context=context)
        pp = pprint.PrettyPrinter()
        pp.format = my_safe_repr
        print "Success:" + str(rtn.code)
        pp.pprint(data)
    except urllib2.HTTPError as err:
        print str(err) + ":"
        print err.read()
        data = None
    return data

【问题讨论】:

标签: android python-2.7 ssl ssl-certificate httpsurlconnection


【解决方案1】:

你可以在你的安卓应用上创建这个类。

    import java.security.KeyManagementException;
    import java.security.NoSuchAlgorithmException;
    import java.security.SecureRandom;
    import java.security.cert.X509Certificate;

    import javax.net.ssl.HttpsURLConnection;
    import javax.net.ssl.SSLContext;
    import javax.net.ssl.TrustManager;
    import javax.net.ssl.X509TrustManager;

    public class HttpsTrustManager implements X509TrustManager {

        private static final X509Certificate[] _AcceptedIssuers = new X509Certificate[]{};
        private static TrustManager[] trustManagers;

        public static void allowAllSSL() {
    //        HttpsURLConnection.setDefaultHostnameVerifier(new HostnameVerifier() {
    //
    //            @Override
    //            public boolean verify(String arg0, SSLSession arg1) {
    //                return true;
    //            }
    //        });

            SSLContext context = null;
            if (trustManagers == null) {
                trustManagers = new TrustManager[]{new HttpsTrustManager()};
            }

            try {
                context = SSLContext.getInstance("TLS");
                context.init(null, trustManagers, new SecureRandom());
            } catch (NoSuchAlgorithmException e) {
                e.printStackTrace();
            } catch (KeyManagementException e) {
                e.printStackTrace();
            }

            HttpsURLConnection.setDefaultSSLSocketFactory(context
                    .getSocketFactory());
        }

        @Override
        public void checkClientTrusted(
                X509Certificate[] x509Certificates, String s)
                throws java.security.cert.CertificateException {

        }

        @Override
        public void checkServerTrusted(
                X509Certificate[] x509Certificates, String s)
                throws java.security.cert.CertificateException {

        }

        public boolean isClientTrusted(X509Certificate[] chain) {
            return true;
        }

        public boolean isServerTrusted(X509Certificate[] chain) {
            return true;
        }

        @Override
        public X509Certificate[] getAcceptedIssuers() {
            return _AcceptedIssuers;
        }

    }

并在OnCreate()上使用HttpsTrustManager.allowAllSSL();方法

【讨论】:

  • 我不确定我是否要允许所有 SSL 连接(即使这是应用程序连接的唯一位置。)我希望使用与网络相同的证书网站并以“正确”的方式进行操作。对于它的价值,我确实尝试了你的建议并且得到了同样的例外,这让我觉得可能还有其他事情发生?
【解决方案2】:

这是我的工作解决方案,它解决了几个问题(使用自签名证书安全连接、向/从服务器传递和接收 json、让 Flask 服务器侦听所有接口)。希望这会对某人有所帮助:

    String host = "turbo";
    int port = 8081;
    String command = "get_status";
    String user = "admin";
    String password = "admin";
    HttpsURLConnection connection = null;

    try {
        Certificate ca;
        String keyStoreType;
        KeyStore keyStore;
        String tmfAlgorithm;
        TrustManagerFactory tmf;

        CertificateFactory cf = CertificateFactory.getInstance("X.509");
        InputStream ins = myContext.getResources().openRawResource(
                myContext.getResources().getIdentifier("server_crt", "raw",
                        myContext.getPackageName()));
        BufferedInputStream caInput = new BufferedInputStream(ins);
        ca = cf.generateCertificate(caInput);
        caInput.close();
        keyStoreType = KeyStore.getDefaultType();
        keyStore = KeyStore.getInstance(keyStoreType);
        keyStore.load(null, null);
        keyStore.setCertificateEntry("ca", ca);
        tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
        tmf = TrustManagerFactory.getInstance(tmfAlgorithm);
        tmf.init(keyStore);
        SSLContext context = SSLContext.getInstance("TLS");
        context.init(null, tmf.getTrustManagers(), null);
        URL url = new URL(String.format("https://%s:%d/test/%s", host, port, command));

        JSONObject params = new JSONObject();
        params.put("username", user);
        params.put("password", password);

        connection = (HttpsURLConnection) url.openConnection();
        connection.setSSLSocketFactory(context.getSocketFactory());
        connection.setReadTimeout(4500);
        connection.setConnectTimeout(4500);
        connection.setRequestMethod("POST");
        connection.setRequestProperty("User-Agent", "Mozilla/5.0");
        connection.setRequestProperty("Accept-Language", "en-US,en;q=0.5");
        connection.setRequestProperty("Content-Type", "application/json");
        connection.setDoInput(true);
        connection.setDoOutput(true);
        OutputStreamWriter wr= new OutputStreamWriter(connection.getOutputStream());
        wr.write(params.toString());
        wr.flush();
        wr.close();

       switch (connection.getResponseCode()) {
           case HttpURLConnection.HTTP_CREATED:
           case HttpURLConnection.HTTP_OK:
               BufferedReader br = new BufferedReader(
                       new InputStreamReader(connection.getInputStream()));
               StringBuilder sb = new StringBuilder();
               String line;
               while ((line = br.readLine()) != null) {
                   sb.append(line+"\n");
               }
               br.close();;
               JSONObject jsonObj = new JSONObject(sb.toString());
        }
    } catch (Exception e) {
        Exception blah = e;
    } finally {
        if (connection != null) {
            connection.disconnect();
        }
    }
}

我不得不更改我的 Flask run() 方法以包含参数:

host='0.0.0.0'

我创建了 create_key.cfg:

[req]
default_bits=2048
prompt=no
default_md=sha256
x509_extensions=v3_req
#req_extensions=req_ext
distinguished_name=dn

[dn]
C=US
ST=Florida
L=Satellite Beach
O=ThompCo, Inc
OU=turbo
emailAddress=Jordan@ThompCo.com
CN=turbo

[v3_req]
subjectAltName=@alt_names

[alt_names]
DNS.1=turbo
DNS.2=turbo.thompco.com
DNS.3=thompco.com

然后我运行了以下命令:

openssl req -x509 -nodes -days 730 -newkey rsa:2048 -keyout server.key -out server.crt -config create_key.cfg -sha256

创建文件:

server.crt  server.key

【讨论】:

    猜你喜欢
    • 2014-03-19
    • 2013-11-25
    • 1970-01-01
    • 2016-03-23
    • 1970-01-01
    • 2014-05-05
    • 2015-06-18
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多