Android使用自签名客户端证书身份验证连接到IIS中的WCF休息服务

时间:2011-03-16 15:07:16

标签: android wcf json authentication client-certificates

问候,

我正在尝试创建一个Android测试应用程序,该应用程序仅执行以下操作'使用自签名客户端证书身份验证连接到IIS中的JSON WCF休息服务',但此刻我一直收到“禁止访问”当我运行应用程序时。 WCF服务的客户端WCF似乎工作正常,当我禁用“需要客户端证书”时,WCF服务的Android客户端应用程序也可以正常工作。

奇怪的是,Eclipse通知已找到客户端证书,并且使用它创建了KeyManager,但没有任何内容发送到服务器。

已对自签名证书进行了以下步骤

  1. 使用serverCA.cer和clientCA.cer
  2. 作为子证书创建了一个rootCA.cer
  3. 已从clientCA.cer
  4. 创建私钥信息
  5. 使用Portecle,两个密钥库以BKS格式创建,一个包含clientCA的PKI,名为keystore.bks,另一个包含rootCA作为条目的truststore.bks
  6. PKI具有别名客户端,用于仔细检查是否可以检索PKI
  7. truststore.bks包含rootCA.cer
  8. 这两个密钥库都添加在Android的Eclipse的res / raw中


  9. 为了处理Android中的自签名证书,我尝试使用几个例子,但StackOverflow:self-signed-ssl-acceptance-android worked for the most part.中的EasySSLSocketFactory和EasySSLTrustManager我也尝试使用默认的keytool创建密钥库,但这会导致更多不正确的密钥库存在创建




    更新2011-03-17:系统信息
    托管IIS的系统操作系统是带有.NET 4.0的IIS-5的Windows XP。 IIS中的服务将serverCA.cer指定为服务器证书,并启用了require客户端证书。

    我正在研究的Android版本是2.3.3,它已经设置了Internet的权限,并在Eclipse项目中添加了密钥库和信任库作为原始资源。

    另外,当我在调试模式中查找KeyManagerFactory.getKeyManagers()返回的内容时,我看到列表中有一个项目。


    以下是我对此问题使用的操作/代码的详细信息:

    证书是使用makecert创建的,因为它首先必须在WCF服务和客户端之间工作。

    makecert.exe -r -n "CN=rootCA,O=Organization,OU=Org Unit,L=Location,S=SH,C=Country" -pe -ss root -sr LocalMachine -sky exchange -m 96 -a sha1 -len 2048 rootCA.cer -sv rootCA.pvk
    makecert.exe -n "CN=serverCA" -pe -ss my -sr LocalMachine -sky exchange -m 96 -in "rootCA" -is root -ir LocalMachine -a sha1 -eku 1.3.6.1.5.5.7.3.1,1.3.6.1.5.5.7.3.2 serverCA.cer
    makecert.exe -n "CN=clientCA" -pe -ss my -sr CurrentUser -sky exchange -m 96 -in "rootCA" -is root -ir LocalMachine -a sha1 -eku 1.3.6.1.5.5.7.3.2 clientCA.cer  -sv clientCA.
    pvk2pfx.exe -pvk clientCA.pvk -spc clientCA.cer -pfx clientCA.pfx
    



    配置的WCF如下:

    <?xml version="1.0"?>
    <configuration>
        <system.serviceModel>
        <extensions>
            <behaviorExtensions>
                <add name="consoleOutputBehavior" type="JsonTestService.ConsoleOutputBehaviorExtensionElement, JsonTestService, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
            </behaviorExtensions>
        </extensions>
        <standardEndpoints>
            <webHttpEndpoint>
                <standardEndpoint name="JsonStandardEndpoint" defaultOutgoingResponseFormat="Json"
                    automaticFormatSelectionEnabled="true">
                    <security mode="Transport">
                        <transport clientCredentialType="Certificate" proxyCredentialType="None" realm="" />
                    </security>
                </standardEndpoint>
            </webHttpEndpoint>
        </standardEndpoints>
        <bindings>
            <webHttpBinding>
                <binding name="JsonBinding">
                    <security mode="Transport">
                        <transport clientCredentialType="Certificate" proxyCredentialType="None" realm="" />
                    </security>
                </binding>
            </webHttpBinding>
        </bindings>
        <serviceHostingEnvironment aspNetCompatibilityEnabled="false" />
        <behaviors>
            <endpointBehaviors>
                <behavior name="jsonBehavior">
                    <webHttp defaultBodyStyle="Wrapped" defaultOutgoingResponseFormat="Json" />
                </behavior>
            </endpointBehaviors>
            <serviceBehaviors>
                <behavior name="defaultBehavior">
                    <serviceDebug includeExceptionDetailInFaults="true" />
                    <serviceCredentials>
                        <clientCertificate>
                            <authentication certificateValidationMode="Custom" mapClientCertificateToWindowsAccount="false"
                                            customCertificateValidatorType="JsonTestService.CustomX509CertificateValidator, JsonTestService"
                                            />
                        </clientCertificate>
                        <serviceCertificate findValue="serverCA" storeLocation="LocalMachine" storeName="My" x509FindType="FindBySubjectName" />
                    </serviceCredentials>
                </behavior>
            </serviceBehaviors>
        </behaviors>
        <services>
            <service behaviorConfiguration="defaultBehavior" name="JsonTestService.TestService">
                <endpoint address="json" behaviorConfiguration="jsonBehavior"
                    binding="webHttpBinding" bindingConfiguration="JsonBinding"
                    name="JsonEndpoint" contract="JsonTestService.ITestService" kind="webHttpEndpoint"
                    endpointConfiguration="JsonStandardEndpoint">
                </endpoint>
            </service>
        </services>
        </system.serviceModel>
        <system.web>
            <authentication mode="None" />
        </system.web>
    </configuration>
    



    WCF服务的对象

    namespace JsonTestService{
    /// 
    /// DataContract
    /// 
    [DataContract(Name = "Foo", Namespace = "http://www.example.com/data")]
    public class FooDataContract
    {
        [DataMember(Order = 0)]
        public string Item { get; set; }
        [DataMember(Order = 1)]
        public int Count { get; set; }
    }
    
    /// 
    /// Service Contract
    /// 
    [ServiceContract(Namespace = "http://www.example.com/service")]
    public interface ITestService
    {
        [OperationContract]
        [WebInvoke(Method = "POST"
        , ResponseFormat = WebMessageFormat.Json
        , RequestFormat = WebMessageFormat.Json
        , BodyStyle = WebMessageBodyStyle.WrappedRequest
        , UriTemplate = "GetFoo.json/{name}?item={item}&count={countOfFoo}")]
        FooDataContract[] GetFoo(string name, int item, int countOfFoo);
    
        [OperationContract]
        [WebInvoke(Method = "GET"
        , ResponseFormat = WebMessageFormat.Json
        , RequestFormat = WebMessageFormat.Json
        , BodyStyle = WebMessageBodyStyle.WrappedRequest
        , UriTemplate = "GetFooRaw.json")]
        FooDataContract[] GetFooRaw();
    }
    
    /// 
    /// Service Implementation
    /// 
    /// 
    /// Each request will have its own instance of the service
    /// 
    [ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)]
    public class TestService : ITestService
    {
        public FooDataContract[] GetFoo(string name, int item, int countOfFoo)
        {
            List result = null;
            for (int i = 0; i ();
                result.Add(new FooDataContract()
                {
                    // default to "null"
                    Name = (name ?? "null") + "_" + i,
                    Age = age
                });
            }
            return result == null ? null : result.ToArray();
        }
        public FooDataContract[] GetFooRaw()
        {
            List result = new List();
            for (int i = 0; i < 5; i++)
                result.Add(new FooDataContract() { Item = (i + 1) * 6, Name = "Test" + i.ToString() });
            return result.ToArray();
        }
    }



    调用WCF服务的Android方法如下

    private void testSSLDataTransfer() throws ClientProtocolException, IOException, Exception
    {
        try {
            SchemeRegistry schemeRegistry = new SchemeRegistry();
            schemeRegistry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));
            schemeRegistry.register(new Scheme("https", new EasySSLSocketFactory(getKeyStore(),"",getTrustStore()), 443));  //password is empty
    
            HttpParams params = new BasicHttpParams();
            params.setParameter(ConnManagerPNames.MAX_TOTAL_CONNECTIONS, 1);
            params.setParameter(ConnManagerPNames.MAX_CONNECTIONS_PER_ROUTE, new ConnPerRouteBean(1));
            params.setParameter(HttpProtocolParams.USE_EXPECT_CONTINUE, false);
            HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1);
            HttpProtocolParams.setContentCharset(params, "utf8");
    
            ClientConnectionManager clientConnectionManager = new ThreadSafeClientConnManager(params, schemeRegistry);
            HttpContext context = new BasicHttpContext();
            DefaultHttpClient client = new DefaultHttpClient(clientConnectionManager, params);
    
            HttpPost post = new HttpPost("https://10.12.14.16:443/JsonTest/TestService.svc/json/GetFoo.json/Test?item=12&count=2");
            HttpGet get = new HttpGet("https://10.12.14.16:443/JsonTest/TestService.svc/json/GetFooBar.json");
            post.setHeader("Accept", "application/json");
            post.setHeader("Content-type", "application/json");
            post.setHeader("User-Agent", "android");
            get.setHeader("Accept", "application/json");
            get.setHeader("Content-type", "application/json");
            get.setHeader("User-Agent", "android");
    
            HttpResponse response = client.execute(get, context);
            String statusLine = response.getStatusLine().toString(); //for debuf to see the response
            HttpEntity responseEntity = response.getEntity();
            InputStream stream = responseEntity.getContent();
            InputStreamReader reader = new InputStreamReader(stream);
    
            java.lang.StringBuffer stringBuffer = new java.lang.StringBuffer();
            int read = 0;
            while((read = reader.read()) >= 0)
             stringBuffer.append((char)read);
    
            String s = stringBuffer.toString(); 
            stream.close();     
        } catch (ClientProtocolException e) {
            throw e;
        } catch (IOException e) {
            String text = e.getMessage();
            throw e;
        } catch (Exception e) {
            throw e;
        }
    }
    



    testSSLDataTransfer方法使用以下部分来检索客户端证书密钥库和信任库

    private KeyStore getKeyStore() throws IOException, NoSuchAlgorithmException, CertificateException, KeyStoreException, UnrecoverableKeyException, Exception
    {
        KeyStore keystore = KeyStore.getInstance(KeyStore.getDefaultType());
        InputStream in = this.getApplicationContext().getResources().openRawResource(R.raw.keystore);
        try {
            keystore.load(in, "changeit".toCharArray());
            Key key = keystore.getKey("client", null); //It has no password and this way it finds the Key
        }
        catch (Exception e) {
            throw e;
        } finally {
            in.close();
        }
        return keystore;
    }
    
    private KeyStore getTrustStore() throws IOException, NoSuchAlgorithmException, CertificateException, KeyStoreException
    {
        KeyStore truststore = KeyStore.getInstance(KeyStore.getDefaultType());
        InputStream in = this.getApplicationContext().getResources().openRawResource(R.raw.truststore);
        try {
            truststore.load(in, "changeit".toCharArray());
        } finally {
            in.close();
        }
        return truststore;
    }
    



    EasySSLSocketFactory已经略微修改,以便代码如下所示:

    import java.io.IOException;
    import java.net.InetAddress;
    import java.net.InetSocketAddress;
    import java.net.Socket;
    import java.net.UnknownHostException;
    import java.security.KeyStore;
    
    import javax.net.ssl.KeyManager;
    import javax.net.ssl.KeyManagerFactory;
    import javax.net.ssl.SSLContext;
    import javax.net.ssl.SSLSocket;
    import javax.net.ssl.TrustManager;
    
    import org.apache.http.conn.ConnectTimeoutException;
    import org.apache.http.conn.scheme.LayeredSocketFactory;
    import org.apache.http.conn.scheme.SocketFactory;
    import org.apache.http.params.HttpConnectionParams;
    import org.apache.http.params.HttpParams;
    
    /*
     * Licensed to the Apache Software Foundation (ASF) under one
     * or more contributor license agreements.  See the NOTICE file
     * distributed with this work for additional information
     * regarding copyright ownership.  The ASF licenses this file
     * to you under the Apache License, Version 2.0 (the
     * "License"); you may not use this file except in compliance
     * with the License.  You may obtain a copy of the License at
     *
     *   http://www.apache.org/licenses/LICENSE-2.0
     *
     * Unless required by applicable law or agreed to in writing,
     * software distributed under the License is distributed on an
     * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
     * KIND, either express or implied.  See the License for the
     * specific language governing permissions and limitations
     * under the License.
     */
    
    
    /**
     * This socket factory will create ssl socket that accepts self signed
     * certificate
     * 
     * @author olamy
     * @version $Id: EasySSLSocketFactory.java 765355 2009-04-15 20:59:07Z evenisse
     *          $
     * @since 1.2.3
     */
    public class EasySSLSocketFactory implements SocketFactory, LayeredSocketFactory {
    
        private SSLContext sslcontext = null;
        private KeyStore keystore = null;
        private KeyStore truststore = null;
        String keystorepassword = null;
    
        public EasySSLSocketFactory()
        {
        }
    
        public EasySSLSocketFactory(KeyStore keystore, String keystorepassword,KeyStore truststore)
        {
            this.keystore = keystore;
            this.keystorepassword = keystorepassword;
            this.truststore = truststore;
        }
    
        private static SSLContext createEasySSLContext(KeyStore keystore, String keystorepassword,KeyStore truststore) throws IOException {
            try {
    
                KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
                keyManagerFactory.init(keystore, keystorepassword.toCharArray());
                KeyManager[] list = keyManagerFactory.getKeyManagers();
    
                SSLContext context = SSLContext.getInstance("TLS");
                context.init(list, new TrustManager[] { new EasyX509TrustManager(truststore) }, null);
                return context;
            } catch (Exception e) {
                    throw new IOException(e.getMessage());
            }
        }
    
        private SSLContext getSSLContext() throws IOException {
            if (this.sslcontext == null) {
                    this.sslcontext = createEasySSLContext(keystore, keystorepassword, truststore);
            }
            return this.sslcontext;
        }
    
        /**
         * @see org.apache.http.conn.scheme.SocketFactory#connectSocket(java.net.Socket,
         *      java.lang.String, int, java.net.InetAddress, int,
         *      org.apache.http.params.HttpParams)
         */
        public Socket connectSocket(Socket sock, String host, int port,
                        InetAddress localAddress, int localPort, HttpParams params)
                        throws IOException, UnknownHostException, ConnectTimeoutException {
            int connTimeout = HttpConnectionParams.getConnectionTimeout(params);
            int soTimeout = HttpConnectionParams.getSoTimeout(params);
    
            InetSocketAddress remoteAddress = new InetSocketAddress(host, port);
            SSLSocket sslsock = (SSLSocket) ((sock != null) ? sock : createSocket());
    
            if ((localAddress != null) || (localPort > 0)) {
                    // we need to bind explicitly
                    if (localPort < 0) {
                            localPort = 0; // indicates "any"
                    }
                    InetSocketAddress isa = new InetSocketAddress(localAddress,
                                    localPort);
                    sslsock.bind(isa);
            }
    
            sslsock.connect(remoteAddress, connTimeout);
            sslsock.setSoTimeout(soTimeout);
            return sslsock;
        }
    
        /**
         * @see org.apache.http.conn.scheme.SocketFactory#createSocket()
         */
        public Socket createSocket() throws IOException {
            return getSSLContext().getSocketFactory().createSocket();
        }
    
        /**
         * @see org.apache.http.conn.scheme.SocketFactory#isSecure(java.net.Socket)
         */
        public boolean isSecure(Socket socket) throws IllegalArgumentException {
            return true;
        }
    
        /**
         * @see org.apache.http.conn.scheme.LayeredSocketFactory#createSocket(java.net.Socket,
         *      java.lang.String, int, boolean)
         */
        public Socket createSocket(Socket socket, String host, int port, boolean autoClose) throws IOException, UnknownHostException {
            return getSSLContext().getSocketFactory().createSocket(socket, host, port,autoClose);
        }
    
        // -------------------------------------------------------------------
        // javadoc in org.apache.http.conn.scheme.SocketFactory says :
        // Both Object.equals() and Object.hashCode() must be overridden
        // for the correct operation of some connection managers
        // -------------------------------------------------------------------
    
        public boolean equals(Object obj) {
            return ((obj != null) && obj.getClass().equals(EasySSLSocketFactory.class));
        }
    
        public int hashCode() {
            return EasySSLSocketFactory.class.hashCode();
        }
    }

1 个答案:

答案 0 :(得分:2)

在未来版本之前,Android不支持客户端证书。 要在应用程序中拥有客户端证书,必须实现他/她自己的代码才能发送。