精华内容
下载资源
问答
  • 如何检查LED透明屏?需要根据具体情况进行判断,以下是检查、维修时的注意事项。一,LED透明格栅屏黑屏不工作1.确认控制盒是否通电,并且信号传输正常。 2.LED透明格栅屏是否已正常通电,在开电或者关电瞬间会闪烁一...
    0fe7d126ce117649576bb21875d617ff.png

    快点关注我们吧

    e3e8d9d09135abd8a703b38b852ef39e.png

    如果透明格栅LED显示屏的使用时间变长,则产品需要定期维护。 如何检查LED透明屏? 需要根据具体情况进行判断,以下是检查、维修时的注意事项。

    59e8cb7f504101a0e67b099a1ee27672.gif一,LED透明格栅屏黑屏不工作  1.确认控制盒是否通电,并且信号传输正常。  2.LED透明格栅屏是否已正常通电,在开电或者关电瞬间会闪烁一次后黑屏。  3.如果以上两种检查确认无误,确认网线是否插好。二,  LED显示屏画面显示不全
    1. 请确认播放内容是否是按照屏体的分辨率来制作的。
      2.确认视频处理器分辨率是否一致。  3. 一个模组内一根或者多根灯条或者整个模组不正常工作。7568df530f598c15bb136d162f81148b.png首先确认好故障出现的位置,并做好标记,然后对故障模组进行更换,更换具体方法如下: 1.切断屏体全部电源开关,然后拔下模组上电源和信号线;2.拆掉模组左右的固定螺丝;3.取下故障模组包装好;4.装上对应的新模组,锁紧固定模组螺丝并插好电源线和信号线。5.通电测试画面是否正常。

    以上就是关于LED透明格栅屏怎么检修的常见问题和解决方法,从简单到复杂。如果您有解决不了问题的话可以来联系我们力诺光电,我们会有专业工程师为您解决。

    展开全文
  • 封装成帧透明传输透明传输解决方法差错检测 数据传输详解 OSI(开放系统互联参考模型)   OSI(Open System Interconnection),开放式系统互联参考模型。是一个逻辑上的定义,一个规范,它把网络...

    数据传输详解

    OSI(开放系统互联参考模型)

      OSI(Open System Interconnection),开放式系统互联参考模型。是一个逻辑上的定义,一个规范,它把网络协议从逻辑上分成了7层。每一层都有相关、相对应的物理设备,比如常规的路由器是三层交换设备,常规的交换机是二层交换设备。
      OSI七层模型是一种框架性的设计方法,建立七层模型的主要目的是为了解决异种网络互连时所遇到的兼容性,其最主要的功能就是帮助不同类型的主机实现数据传输。它最大的优点是将服务、接口和协议这三个概念明确的区分开来,通过七个层次化的结构模型使不同的网络之间实现可靠的通信。
    [外链图片转存失败(img-yiPG67Id-1565324871258)(02img/001.png)]

    经典比喻:
    [外链图片转存失败(img-0EX7ZUfj-1565324871259)(02img/002.png)]
    [外链图片转存失败(img-VeLkDV6W-1565324871265)(02img/003.png)]

    Internet网络体系结构

    emsp; Internet目前使用的协议式TCP/IP协议。TCP/IP协议是4层结构的集网络通信,应用,服务,管理等多种功能的协议族,这4层协议分别是物理网络接口层协议、网际层协议、传输层协议、和应用层协议。
    [外链图片转存失败(img-pJT388au-1565324871267)(02img/004.png)]
    [外链图片转存失败(img-dQbV3DRg-1565324871268)(02img/005.png)]

    OSI模型与TCP/IP模型对比

    [外链图片转存失败(img-ypdBxUM4-1565324871274)(02img/006.png)]

    数据包转发实例详解

    [外链图片转存失败(img-7xDhXgQf-1565324871275)(02img/007.png)]

    1. 数据生成(应用层提交请求)
    [外链图片转存失败(img-h2eO61DW-1565324871276)(02img/008.png)]
    2. 调用传输层服务
    将数据分段后加上传输层首部,传输层首部的格式以及每个字段是为了实现传输层功能,比如可靠传输、流量控制、拥塞避免等。传输层首部的一个重要功能是为这些数据段编号(保证传输的有序性),传输层首部的各个字段以及各个字段数值代表的具体含义是由传输层的协议决定的。添加了传输层首部的TCP协议的数据单元称为数据段(Segments)添加UDP协议的数据单元称为数据报(Datagrams)。
    [外链图片转存失败(img-tLY2z8CO-1565324871280)(02img/009.png)]
    3. 调用网路层服务

    数据段要想通过网络层进行传输,就必须给数据段添加源IP地址,目标IP地址以及网络层控制信息,也就是网络层首部。网络层首部的格式和每个字段是为了实现网络层的功能,以便网络中的路由器根据网络层首部为数据包选择路径。添加了网络层首部的数据段称为数据包(Package)。
    [外链图片转存失败(img-DDRekVbA-1565324871281)(02img/010.png)]

    • 查找主机路由表
      [外链图片转存失败(img-T34kfpCK-1565324871282)(02img/011.png)]

    4. 通过arp表查找下一跳mac的地址

    [外链图片转存失败(img-WiVabT29-1565324871286)(02img/012.png)]

    • 发送arp请求
      [外链图片转存失败(img-Mq9Qdstf-1565324871286)(02img/013.png)]

    • 收到arp响应报文
      [外链图片转存失败(img-chZX3j5t-1565324871287)(02img/014.png)]
      [外链图片转存失败(img-j8PXyKeb-1565324871288)(02img/015.png)]

    5. 封装数据链路层包头并发送出接口
    [外链图片转存失败(img-rXv0j8ys-1565324871289)(02img/016.png)]
    6. 交换机1接收到数据包
    [外链图片转存失败(img-CnzxKqGI-1565324871291)(02img/017.png)]
    7. 查找mac地址表
    [外链图片转存失败(img-0FH5ZP1M-1565324871291)(02img/018.png)]
    8. 数据包到达路由器1接口
    [外链图片转存失败(img-Lup4ZCG3-1565324871294)(02img/019.png)]
    9. 解封装链路层包头,检查ip层
    [外链图片转存失败(img-cmhwD1mK-1565324871296)(02img/020.png)]
    10. 封装链路层包头,发送数据包
    [外链图片转存失败(img-o0kB7BWc-1565324871297)(02img/021.png)]

    • 数据包到达路由器2
      [外链图片转存失败(img-tv2aDJrO-1565324871297)(02img/022.png)]
      11. 数据包到达交换机2
      [外链图片转存失败(img-izMGasUR-1565324871298)(02img/023.png)]
      12. 数据包达到PC3
      [外链图片转存失败(img-h0tiMSPo-1565324871299)(02img/024.png)]
      13. 送网络层处理
      [外链图片转存失败(img-JPFHfUt0-1565324871299)(02img/025.png)]
      13. 送网络层处理
      [外链图片转存失败(img-JcpPvdaR-1565324871300)(02img/026.png)]
      14. 应用程序处理
      [外链图片转存失败(img-ObcieNFU-1565324871300)(02img/027.png)]

    数据链路层的三个基本问题

    链路与数据链路概念的区别?

    链路:

    • 从一个节点到相邻节点的一段物理线路,而中间没有任何其他的交换节点。
    • 在进行数据通信时,两个计算机之间的通信路径往往要经过多段这样的链路,可见链路只是一条路径的组成部分。

    数据链路:

    • 当需要在一条线路上传输数据时,除了需要有一条物力路线外,还需要有一些必要的通信协议来控制数据的传输。若把这些协议和硬件加到链路上就构成了数据链路。
      [外链图片转存失败(img-qWcD56qn-1565324871301)(02img/028.png)]
      数据链路层协议有许多中,但是有三个基本问题则是共同的。这三个基本问题是:

    封装成帧

    [外链图片转存失败(img-TtGe2wdf-1565324871301)(02img/029.png)]

    透明传输

    当传送的帧是用文本文件组成的帧(文本文件中的字符都是从键盘上输入的)时,其数据部分显然不会出现像SOH(0x01)或者EOT(0x04)这样的帧界定控制字符。可见不管从键盘上输入什么字符都可以放在这样的帧中传输过去,因此这样的传输就是透明传输。
    [外链图片转存失败(img-cMw7hs2V-1565324871302)(02img/030.png)]

    透明传输的解决方法

    [外链图片转存失败(img-sXtr60Zv-1565324871305)(02img/031.png)]

    差错检测

    现实的通信链路都不会是理想的。比特在传输过程中会产生差错。
    为了保证数据传输的可靠性,在计算机网络传输数据时,必须采用各种差错检测错误措施。
    目前在数据链路层广泛采用了循环冗余校验(CRC)的检错技术。
    [外链图片转存失败(img-3hRexEma-1565324871307)(02img/032.png)]
    [外链图片转存失败(img-iNdOSVtB-1565324871308)(02img/033.png)]

    数据链路层提供的可靠性与传输层提供的可靠性的区别

      数据链路层关心的是任何数据的可靠性传送,例如尾部插入校验码等方法,也就是说在数据封装的最后一层上提供校验,在数据通过网络层封装之后,网络层也会做一个校验的,然后数据链路层对封装的所有数据做校验。传输层提供的针对特定端口的校验,顺序为:在封装时,传输层封装数据,同时提供可靠性校验,然后网络层封装同时提供可靠性校验,最后数据链路层封装数据并提供校验,所谓封装数据只不过是打上自己层的标签而已,传输层提供端口,网络层提供IP,数据链路层提供mac;当数据到达对端时,解封便使按照相反的顺序了。

    展开全文
  • 对于SSL/TLS协议,如果要每个开发者都自己去实现显然会带来不必要的麻烦,正是为了解决这个问题Java为广大开发者提供了Java安全套接字扩展——JSSE,它包含了实现Internet安全通信的一系列包的集合,是SSL和TLS的纯...

    对于SSL/TLS协议,如果要每个开发者都自己去实现显然会带来不必要的麻烦,正是为了解决这个问题Java为广大开发者提供了Java安全套接字扩展——JSSE,它包含了实现Internet安全通信的一系列包的集合,是SSL和TLS的纯Java实现,同时它是一个开放的标准,每个公司都可以自己实现JSSE,通过它可以透明地提供数据加密、服务器认证、信息完整性等功能,就像使用普通的套接字一样使用安全套接字,大大减轻了开发者的负担,使开发者可以很轻松将SSL协议整合到程序中,并且JSSE能将安全隐患降到了最低点。

    在用JSSE实现SSL通信过程中主要会遇到以下类和接口,由于过程中涉及到加解密、密钥生成等运算的框架和实现,所以也会间接用到JCE包的一些类。如图为JSSE接口的主要类图:

    ① 通信核心类——SSLSocket和SSLServerSocket。对于使用过socket进行通信开发的朋友比较好理解,它们对应的就是Socket与ServerSocket,只是表示实现了SSL协议的Socket和ServerSocket,同时它们也是Socket与ServerSocket的子类。SSLSocket负责的事情包括设置加密套件、管理SSL会话、处理握手结束时间、设置客户端模式或服务器模式。
    ② 客户端与服务器端Socket工厂——SSLSocketFactory和SSLServerSocketFactory。在设计模式中工厂模式是专门用于生产出需要的实例,这里也是把SSLSocket、SSLServerSocket对象创建的工作交给这两个工厂类。
    ③ SSL会话——SSLSession。安全通信握手过程需要一个会话,为了提高通信的效率,SSL协议允许多个SSLSocket共享同一个SSL会话,在同一个会话中,只有第一个打开的SSLSocket需要进行SSL握手,负责生成密钥及交换密钥,其余SSLSocket都共享密钥信息。
    ④ SSL上下文——SSLContext。它是对整个SSL/TLS协议的封装,表示了安全套接字协议的实现。主要负责设置安全通信过程中的各种信息,例如跟证书相关的信息。并且负责构建SSLSocketFactory、SSLServerSocketFactory和SSLEngine等工厂类。
    ⑤ SSL非阻塞引擎——SSLEngine。假如你要进行NIO通信,那么将使用这个类,它让通过过程支持非阻塞的安全通信。
    ⑥ 密钥管理器——KeyManager。此接口负责选择用于证实自己身份的安全证书,发给通信另一方。KeyManager对象由KeyManagerFactory工厂类生成。
    ⑦ 信任管理器——TrustManager。此接口负责判断决定是否信任对方的安全证书,TrustManager对象由TrustManagerFactory工厂类生成。
    ⑧ 密钥证书存储设施——KeyStore。这个对象用于存放安全证书,安全证书一般以文件形式存放,KeyStore负责将证书加载到内存。

    通过上面这些类就可以完成SSL协议的安全通信了,在利用SSL/TLS进行安全通信时,客户端跟服务器端都必须要支持SSL/TLS协议,不然将无法进行通信。而且客户端和服务器端都可能要设置用于证实自己身份的安全证书,并且还要设置信任对方的哪些安全证书。

    关于身份认证方面有个名词叫客户端模式,一般情况客户端要对服务器端的身份进行验证,但是无需向服务器证实自己的身份,这样不用向对方证实自己身份的通信端我们就说它处于客户模式,否则成它处于服务器模式。SSLSocket的setUseClientMode(Boolean mode)方法可以设置客户端模式或服务器模式。

    BIO模式实现SSL通信

    使用BIO模式实现SSL通信除了对一些证书密钥生成外,只需使用JDK自带的SSLServerSocket和SSLSocket等相关类的API即可实现,简洁直观。

    ① 解决证书问题。

    一般而言作为服务器端必须要有证书以证明这个服务器的身份,并且证书应该描述此服务器所有者的一些基本信息,例如公司名称、联系人名等。证书由所有人以密码形式签名,基本不可伪造,证书获取的途径有两个:一是从权威机构购买证书,权威机构担保它发出的证书的真实性,而且这个权威机构被大家所信任,进而你可以相信这个证书的有效性;另外一个是自己用JDK提供的工具keytool创建一个自我签名的证书,这种情况下一般是我只想要保证数据的安全性与完整性,避免数据在传送的过程中被窃听或篡改,此时身份的认证已不重要,重点已经在端与端传输的秘密性上,证书的作用只体现在加解密签名。

    另外,关于证书的一些概念在这里陈述,一个证书是一个实体的数字签名,这个实体可以是一个人、一个组织、一个程序、一个公司、一个银行,同时证书还包含这个实体的公共钥匙,此公共钥匙是这个实体的数字关联,让所有想同这个实体发生信任关系的其他实体用来检验签名。而这个实体的数字签名是实体信息用实体的私钥加密后的数据,这条数据可以用这个实体的公共钥匙解密,进而鉴别实体的身份。这里用到的核心算法是非对称加密算法。

    SSL协议通信涉及密钥储存的文件格式比较多,很容易搞混,例如xxx.cer、xxx.pfx、xxx.jks、xxx.keystore、xxx.truststore等格式文件。如图,搞清楚他们有助于理解后面的程序,.cer格式文件俗称证书,但这个证书中没有私钥,只包含了公钥;.pfx格式文件也称为证书,它一般供浏览器使用,而且它不仅包含了公钥,还包含了私钥,当然这个私钥是加密的,不输入密码是解不了密的;.jks格式文件表示java密钥存储器(java key store),它可以同时容纳N个公钥跟私钥,是一个密钥库;.keystore格式文件其实跟.jks基本是一样的,只是不同公司叫法不太一样,默认生成的证书存储库格式;.truststore格式文件表示信任证书存储库,它仅仅包含了通信对方的公钥,当然你可以直接把通信对方的jks作为信任库(就算如此你也只能知道通信对方的公钥,要知道密钥都是加密的,你无从获取,只要算法不被破解)。有些时候我们需要把pfx或cert转化为jks以便于用java进行ssl通信,例如一个银行只提供了pfx证书,而我们想用java进行ssl通信时就要将pfx转化为jks格式。

    按照理论上,我们一共需要准备四个文件,两个keystore文件和两个truststore文件,通信双方分别拥有一个keystore和一个truststore,keystore用于存放自己的密钥和公钥,truststore用于存放所有需要信任方的公钥。这里为了方便直接使用jks即keystore替代truststore(免去证书导来导去),因为对方的keystore包含了自己需要的信任公钥。

    下面使用jdk自带的工具分别生成服务器端证书,通过如下命令并输入姓名、组织单位名称、组织名称、城市、省份、国家信息即可生成证书密码为tomcat的证书,此证书存放在密码也为tomcat的tomcat.jks证书存储库中。如果你继续创建证书将继续往tomcat.jks证书存储库中添加证书。如果你仅仅输入keytool -genkey -alias tomcat -keyalg RSA -keypass tomcat -storepass tomcat,不指定证书存储库的文件名及路径,则工具会在用户的home directory目录下生产一个“.keystore”文件作为证书存储库。


    类似的,客户端证书也用此方式进行生成。如下

    ② 服务端TomcatSSLServer.java

    public class TomcatSSLServer {
      private static final String SSL_TYPE = "SSL";
      private static final String KS_TYPE = "JKS";
      private static final String X509 = "SunX509";
      private final static int PORT = 443;
      private static TomcatSSLServer sslServer;
      private SSLServerSocket svrSocket;
    
      public static TomcatSSLServer getInstance() throws Exception {
        if (sslServer == null) {
          sslServer = new TomcatSSLServer();
        }
        return sslServer;
      }
    
      private TomcatSSLServer() throws Exception {
        SSLContext sslContext = createSSLContext();
        SSLServerSocketFactory serverFactory = sslContext.getServerSocketFactory();
        svrSocket = (SSLServerSocket) serverFactory.createServerSocket(PORT);
        svrSocket.setNeedClientAuth(true);
        String[] supported = svrSocket.getEnabledCipherSuites();
        svrSocket.setEnabledCipherSuites(supported);
      }
    
      private SSLContext createSSLContext() throws Exception {
        KeyManagerFactory kmf = KeyManagerFactory.getInstance(X509);
        TrustManagerFactory tmf = TrustManagerFactory.getInstance(X509);
        String serverKeyStoreFile = "c:\\tomcat.jks";
        String svrPassphrase = "tomcat";
        char[] svrPassword = svrPassphrase.toCharArray();
        KeyStore serverKeyStore = KeyStore.getInstance(KS_TYPE);
        serverKeyStore.load(new FileInputStream(serverKeyStoreFile), svrPassword);
        kmf.init(serverKeyStore, svrPassword);
        String clientKeyStoreFile = "c:\\client.jks";
        String cntPassphrase = "client";
        char[] cntPassword = cntPassphrase.toCharArray();
        KeyStore clientKeyStore = KeyStore.getInstance(KS_TYPE);
        clientKeyStore.load(new FileInputStream(clientKeyStoreFile), cntPassword);
        tmf.init(clientKeyStore);
        SSLContext sslContext = SSLContext.getInstance(SSL_TYPE);
        sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
        return sslContext;
      }
    
      public void startService() {
        SSLSocket cntSocket = null;
        BufferedReader ioReader = null;
        PrintWriter ioWriter = null;
        String tmpMsg = null;
        while (true) {
          try {
            cntSocket = (SSLSocket) svrSocket.accept();
            ioReader = new BufferedReader(new InputStreamReader(cntSocket.getInputStream()));
            ioWriter = new PrintWriter(cntSocket.getOutputStream());
            while ((tmpMsg = ioReader.readLine()) != null) {
              System.out.println("客户端通过SSL协议发送信息:" + tmpMsg);
              tmpMsg = "欢迎通过SSL协议连接";
              ioWriter.println(tmpMsg);
              ioWriter.flush();
            }
          } catch (IOException e) {
            e.printStackTrace();
          } finally {
            try {
              if (cntSocket != null) cntSocket.close();
            } catch (Exception ex) {
              ex.printStackTrace();
            }
          }
        }
      }
    
      public static void main(String[] args) throws Exception {
        TomcatSSLServer.getInstance().startService();
      }
    }
    

    基本顺序是先得到一个SSLContext实例,再对SSLContext实例进行初始化,密钥管理器及信任管理器作为参数传入,证书管理器及信任管理器按照指定的密钥存储器路径和密码进行加载。接着设置支持的加密套件,最后让SSLServerSocket开始监听客户端发送过来的消息。

    ③ 客户端TomcatSSLClient.java

    public class TomcatSSLClient {
      private static final String SSL_TYPE = "SSL";
      private static final String X509 = "SunX509";
      private static final String KS_TYPE = "JKS";
      private SSLSocket sslSocket;
    
      public TomcatSSLClient(String targetHost, int port) throws Exception {
        SSLContext sslContext = createSSLContext();
        SSLSocketFactory sslcntFactory = (SSLSocketFactory) sslContext.getSocketFactory();
        sslSocket = (SSLSocket) sslcntFactory.createSocket(targetHost, port);
        String[] supported = sslSocket.getSupportedCipherSuites();
        sslSocket.setEnabledCipherSuites(supported);
      }
    
      private SSLContext createSSLContext() throws Exception {
        KeyManagerFactory kmf = KeyManagerFactory.getInstance(X509);
        TrustManagerFactory tmf = TrustManagerFactory.getInstance(X509);
        String clientKeyStoreFile = "c:\\client.jks";
        String cntPassphrase = "client";
        char[] cntPassword = cntPassphrase.toCharArray();
        KeyStore clientKeyStore = KeyStore.getInstance(KS_TYPE);
        clientKeyStore.load(new FileInputStream(clientKeyStoreFile), cntPassword);
        String serverKeyStoreFile = "c:\\tomcat.jks";
        String svrPassphrase = "tomcat";
        char[] svrPassword = svrPassphrase.toCharArray();
        KeyStore serverKeyStore = KeyStore.getInstance(KS_TYPE);
        serverKeyStore.load(new FileInputStream(serverKeyStoreFile), svrPassword);
        kmf.init(clientKeyStore, cntPassword);
        tmf.init(serverKeyStore);
        SSLContext sslContext = SSLContext.getInstance(SSL_TYPE);
        sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
        return sslContext;
      }
    
      public String sayToSvr(String sayMsg) throws IOException {
        BufferedReader ioReader = new BufferedReader(new InputStreamReader(sslSocket.getInputStream()));
        PrintWriter ioWriter = new PrintWriter(sslSocket.getOutputStream());
        ioWriter.println(sayMsg);
        ioWriter.flush();
        return ioReader.readLine();
      }
    
      public static void main(String[] args) throws Exception {
        TomcatSSLClient sslSocket = new TomcatSSLClient("127.0.0.1", 443);
        BufferedReader ioReader = new BufferedReader(new InputStreamReader(System.in));
        String sayMsg = "";
        String svrRespMsg = "";
        while ((sayMsg = ioReader.readLine()) != null) {
          svrRespMsg = sslSocket.sayToSvr(sayMsg);
          if (svrRespMsg != null && !svrRespMsg.trim().equals("")) {
            System.out.println("服务器通过SSL协议响应:" + svrRespMsg);
          }
        }
      }
    }
    

    客户端的前面操作基本跟服务器端的一样,先创建一个SSLContext实例,再用密钥管理器及信任管理器对SSLContext进行初始化,当然这里密钥存储的路径是指向客户端的client.jks。接着设置加密套件,最后使用SSLSocket进行通信。

    注意服务器端有行代码svrSocket.setNeedClientAuth(true);它是非常重要的一个设置方法,用于设置是否验证客户端的身份。假如我们把它注释掉或设置为false,此时客户端将不再需要自己的密钥管理器,即服务器不需要通过client.jks对客户端的身份进行验证,把密钥管理器直接设置为null也可以跟服务器端进行通信。

    最后谈谈信任管理器,它的职责是决定是否信任远端的证书,那么它凭借什么去判断呢?如果不显式设置信任存储器的文件路径,将遵循如下规则:①如果系统属性javax.NET.ssl.truststore指定了truststore文件,那么信任管理器将去jre路径下的lib/security目录寻找这个文件作为信任存储器;②如果没设置①中的系统属性,则去寻找一个%java_home%/lib/security/jssecacerts文件作为信任存储器;③如果jssecacerts不存在而cacerts存在,则cacerts作为信任存储器。

    至此,一个利用JSSE实现BIO模式的SSL协议通信的例子已完成。

    NIO模式实现SSL通信

    在jdk1.5之前,由于互联网还没快速发展起来,对于常见的应用使用BIO模式即可满足需求,而这时jdk的JSSE接口也仅仅只是提供了基于流的安全套接字,但随着网络的发展,BIO模型明显已经不足以满足一些高并发多连接接入的场景,体现在机器上就是要不同的线程模型以至于能最大程度地压榨计算器的运算,于是此时引入了NIO模式,原来基于流的阻塞模式IO只需使用SSLServerSocket和SSLSocket即可完成SSL通信,而JDK中对于NIO模式并没有提供与之对应的“SSLServerSocketChannel”和“SSLSocketChannel”,这是由NIO模式决定的,很难设计一个“SSLServerSocketChannel”类与Selector交互,强行地引入将带来更多的问题,这更像解决一个问题引入了三个问题,并且还会导致API更加复杂,另外Nio细节也不适合屏蔽,它应该由应用开发层去控制。所有的这些都决定了jdk不会也不能有NIO安全套接字。

    jdk1.5为了支持NIO模式的SSL通信,引入了SSLEngine引擎,它负责了底层ssl协议的握手、加密、解密、关闭会话等等操作,根据前面SSL\TLS协议章节我们知道SSL协议在握手阶段会有十三个步骤,在握手过程中不会有应用层的数据传输,只有在握手认证完成后双方才会进行应用层数据交换。大致把握手分为四阶段:
    ①客户端发送hello消息;
    ②服务端响应hello消息且发送附带的认证消息;
    ③客户端向客户端发送证书和其他认证消息;
    ④完成握手。

    SSLEngine在握手过程中定义了五种HandshakeStatus状态,【NEED_WRAP、NEED_UNWRAP、NEED_TASK、FINISHED、NOT_HANDSHAKING】,通过他们实现协议通信过程中状态管理,按照四个阶段其中的状态是这样转换的,刚开始它的状态为NEED_UNWRAP,表示等待解包,读取客户端数据并解包后,把状态置为NEED_WRAP,表示等待打包,打包完向客户端响应数据后状态又重置为NEED_UNWRAP,如此切换直至握手完成时状态被置为FINISHED,表示握手已经完成,此后状态置为NOT_HANDSHAKING,表示已经不在握手阶段了。另外还有一个NEED_TASK状态表示SSLEngine有额外的任务需要执行,而且这些任务都是比较耗时或者可能阻塞的,例如访问密钥文件、连接远程证书认证服务、密钥管理器使用何种认证方式作为客户端认证等等操作。为了保证NIO特性,这些操作不能直接由当前线程操作,当前线程只会把状态改为NEED_TASK,后面处理线程会交由其他线程处理。

    看看程序是如何使用nio模式进行ssl通信的,主要看服务端如何实现。

    public class NioSSLServer {
      private SSLEngine sslEngine;
      private Selector selector;
      private SSLContext sslContext;
      private ByteBuffer netInData;
      private ByteBuffer appInData;
      private ByteBuffer netOutData;
      private ByteBuffer appOutData;
      private static final String SSL_TYPE = "SSL";
      private static final String KS_TYPE = "JKS";
      private static final String X509 = "SunX509";
      private final static int PORT = 443;
    
      public void run() throws Exception {
        createServerSocket();
        createSSLContext();
        createSSLEngine();
        createBuffer();
        while (true) {
          selector.select();
          Iterator<SelectionKey> it = selector.selectedKeys().iterator();
          while (it.hasNext()) {
            SelectionKey selectionKey = it.next();
            it.remove();
            handleRequest(selectionKey);
          }
        }
      }
    
      private void createBuffer() {
        SSLSession session = sslEngine.getSession();
        appInData = ByteBuffer.allocate(session.getApplicationBufferSize());
        netInData = ByteBuffer.allocate(session.getPacketBufferSize());
        appOutData = ByteBuffer.wrap("Hello\n".getBytes());
        netOutData = ByteBuffer.allocate(session.getPacketBufferSize());
      }
    
      private void createSSLEngine() {
        sslEngine = sslContext.createSSLEngine();
        sslEngine.setUseClientMode(false);
      }
    
      private void createServerSocket() throws Exception {
        ServerSocketChannel serverChannel = ServerSocketChannel.open();
        serverChannel.configureBlocking(false);
        selector = Selector.open();
        ServerSocket serverSocket = serverChannel.socket();
        serverSocket.bind(new InetSocketAddress(PORT));
        serverChannel.register(selector, SelectionKey.OP_ACCEPT);
      }
    
      private void createSSLContext() throws Exception {
        KeyManagerFactory kmf = KeyManagerFactory.getInstance(X509);
        TrustManagerFactory tmf = TrustManagerFactory.getInstance(X509);
        String serverKeyStoreFile = "c:\\tomcat.jks";
        String svrPassphrase = "tomcat";
        char[] svrPassword = svrPassphrase.toCharArray();
        KeyStore serverKeyStore = KeyStore.getInstance(KS_TYPE);
        serverKeyStore.load(new FileInputStream(serverKeyStoreFile), svrPassword);
        kmf.init(serverKeyStore, svrPassword);
        String clientKeyStoreFile = "c:\\client.jks";
        String cntPassphrase = "client";
        char[] cntPassword = cntPassphrase.toCharArray();
        KeyStore clientKeyStore = KeyStore.getInstance(KS_TYPE);
        clientKeyStore.load(new FileInputStream(clientKeyStoreFile), cntPassword);
        tmf.init(clientKeyStore);
        sslContext = SSLContext.getInstance(SSL_TYPE);
        sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
      }
    
      private void handleRequest(SelectionKey key) throws Exception {
        if (key.isAcceptable()) {
          ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
          SocketChannel channel = ssc.accept();
          channel.configureBlocking(false);
          doHandShake(channel);
        } else if (key.isReadable()) {
          if (sslEngine.getHandshakeStatus() == HandshakeStatus.NOT_HANDSHAKING) {
            SocketChannel sc = (SocketChannel) key.channel();
            netInData.clear();
            appInData.clear();
            sc.read(netInData);
            netInData.flip();
            SSLEngineResult engineResult = sslEngine.unwrap(netInData, appInData);
            doTask();
            if (engineResult.getStatus() == SSLEngineResult.Status.OK) {
              appInData.flip();
              System.out.println(new String(appInData.array()));
    
            }
            sc.register(selector, SelectionKey.OP_WRITE);
          }
        } else if (key.isWritable()) {
          SocketChannel sc = (SocketChannel) key.channel();
          netOutData.clear();
          SSLEngineResult engineResult = sslEngine.wrap(appOutData, netOutData);
          doTask();
          netOutData.flip();
          while (netOutData.hasRemaining())
            sc.write(netOutData);
          sc.register(selector, SelectionKey.OP_READ);
        }
      }
    
      private void doHandShake(SocketChannel sc) throws IOException {
        boolean handshakeDone = false;
        sslEngine.beginHandshake();
        HandshakeStatus hsStatus = sslEngine.getHandshakeStatus();
        while (!handshakeDone) {
          switch (hsStatus) {
            case FINISHED:
              break;
            case NEED_TASK:
              hsStatus = doTask();
              break;
            case NEED_UNWRAP:
              netInData.clear();
              sc.read(netInData);
              netInData.flip();
              do {
                SSLEngineResult engineResult = sslEngine.unwrap(netInData, appInData);
                hsStatus = doTask();
              } while (hsStatus == SSLEngineResult.HandshakeStatus.NEED_UNWRAP
                  && netInData.remaining() > 0);
              netInData.clear();
              break;
            case NEED_WRAP:
              SSLEngineResult engineResult = sslEngine.wrap(appOutData, netOutData);
              hsStatus = doTask();
              netOutData.flip();
              sc.write(netOutData);
              netOutData.clear();
              break;
            case NOT_HANDSHAKING:
              sc.configureBlocking(false);
              sc.register(selector, SelectionKey.OP_READ);
              handshakeDone = true;
              break;
          }
        }
      }
    
      private HandshakeStatus doTask() {
        Runnable task;
        while ((task = sslEngine.getDelegatedTask()) != null) {
          new Thread(task).start();
        }
        return sslEngine.getHandshakeStatus();
      }
    
      public static void main(String[] args) throws Exception {
        new NioSSLServer().run();
      }
    }
    

    根据程序大致说明程序过程,
    ①创建用于非阻塞通信的主要对象ServerSocketChannel和Selector、绑定端口、注册接收事件;
    ②创建SSL上下文,此过程主要是根据前面创建好的密钥存储器tomcat.jks和client.jks去创建密钥管理器和信任管理器,并通过密钥管理器和信任管理器去初始化SSL上下文;
    ③创建SSL引擎,主要通过SSL上下文创建SSL引擎,并将它设为不验证客户端身份;
    ④创建缓冲区,使用SSL协议通信的过程中涉及到四个缓冲区,如下图,netInData表示实际从网络接收到的字节流,它是包含了SSL协议和应用数据的字节流,通过SSLEngine引擎进行认证解密等处理后的应用可直接使用的数据则用appInData表示,同样地,应用层要传递的数据为appOutData,而经过SSLEngine引擎认证加密处理后放到网络中传输的字节流则为netOutData;
    ⑤接下去开始监听处理客户端的连接请求,一旦有可接受的连接则会先进行SSL协议握手,完成握手后才能进行传输,即对通道的读写操作。

    握手操作是一个比较复杂的过程,必须要保证握手完成后才能进行应用层数据交换,所以这里使用一个while循环不断做握手操作直到完成。前面已经介绍了握手阶段会有五种状态,【NEED_WRAP、NEED_UNWRAP、NEED_TASK、FINISHED、NOT_HANDSHAKING】,由于SSL协议握手的报文都由SSLEngine引擎自动生成,所以我们只需对不同状态做不同操作即可,例如,NEED_UNWRAP状态则调用unwrap方法,NEED_WRAP则调用wrap方法,NEED_TASK则使用其他线程处理委托任务,握手报文自动由这些方法完成,当握手完成后状态则被置为FINISHED,接着状态变为NOT_HANDSHAKING,表示已经不在握手阶段了,已经可以进行应用层通信了,此时整个SSL握手结束。

    应用层安全通信过程其实也是靠SSLEngine引擎的unwrap和wrap方法对数据进行加解密并且对通信双方进行认证,例如应用层读操作是将netInData和appInData传入unwrap方法,处理后的appInData即为应用需要的数据,而写操作则是将appOutData和netOutData传入wrap方法,处理后的netOutData即为传输给对方的数据。

    至此,通过在网络与应用直接增加一个SSLEngine引擎层,则实现了安全通信,并且使用了NIO模式让服务端拥有更加出色的处理性能。

    ====广告时间,可直接跳过====

    鄙人的新书《Tomcat内核设计剖析》已经在京东预售了,有需要的朋友可以到 https://item.jd.com/12185360.html 进行预定。感谢各位朋友。

    =========================

    欢迎关注:

    这里写图片描述

    展开全文
  • 对于SSL/TLS协议,如果要每个开发者都自己去实现显然会带来不必要的麻烦,正是为了解决这个问题Java为广大开发者提供了Java安全套接字扩展——JSSE,它包含了实现Internet安全通信的一系列包的集合,是SSL和TLS的纯...

    对于SSL/TLS协议,如果要每个开发者都自己去实现显然会带来不必要的麻烦,正是为了解决这个问题Java为广大开发者提供了Java安全套接字扩展——JSSE,它包含了实现Internet安全通信的一系列包的集合,是SSL和TLS的纯Java实现,同时它是一个开放的标准,每个公司都可以自己实现JSSE,通过它可以透明地提供数据加密、服务器认证、信息完整性等功能,就像使用普通的套接字一样使用安全套接字,大大减轻了开发者的负担,使开发者可以很轻松将SSL协议整合到程序中,并且JSSE能将安全隐患降到了最低点。

    在用JSSE实现SSL通信过程中主要会遇到以下类和接口,由于过程中涉及到加解密、密钥生成等运算的框架和实现,所以也会间接用到JCE包的一些类。如图为JSSE接口的主要类图:

    ① 通信核心类——SSLSocket和SSLServerSocket。对于使用过socket进行通信开发的朋友比较好理解,它们对应的就是Socket与ServerSocket,只是表示实现了SSL协议的Socket和ServerSocket,同时它们也是Socket与ServerSocket的子类。SSLSocket负责的事情包括设置加密套件、管理SSL会话、处理握手结束时间、设置客户端模式或服务器模式。
    ② 客户端与服务器端Socket工厂——SSLSocketFactory和SSLServerSocketFactory。在设计模式中工厂模式是专门用于生产出需要的实例,这里也是把SSLSocket、SSLServerSocket对象创建的工作交给这两个工厂类。
    ③ SSL会话——SSLSession。安全通信握手过程需要一个会话,为了提高通信的效率,SSL协议允许多个SSLSocket共享同一个SSL会话,在同一个会话中,只有第一个打开的SSLSocket需要进行SSL握手,负责生成密钥及交换密钥,其余SSLSocket都共享密钥信息。
    ④ SSL上下文——SSLContext。它是对整个SSL/TLS协议的封装,表示了安全套接字协议的实现。主要负责设置安全通信过程中的各种信息,例如跟证书相关的信息。并且负责构建SSLSocketFactory、SSLServerSocketFactory和SSLEngine等工厂类。
    ⑤ SSL非阻塞引擎——SSLEngine。假如你要进行NIO通信,那么将使用这个类,它让通过过程支持非阻塞的安全通信。
    ⑥ 密钥管理器——KeyManager。此接口负责选择用于证实自己身份的安全证书,发给通信另一方。KeyManager对象由KeyManagerFactory工厂类生成。
    ⑦ 信任管理器——TrustManager。此接口负责判断决定是否信任对方的安全证书,TrustManager对象由TrustManagerFactory工厂类生成。
    ⑧ 密钥证书存储设施——KeyStore。这个对象用于存放安全证书,安全证书一般以文件形式存放,KeyStore负责将证书加载到内存。

    通过上面这些类就可以完成SSL协议的安全通信了,在利用SSL/TLS进行安全通信时,客户端跟服务器端都必须要支持SSL/TLS协议,不然将无法进行通信。而且客户端和服务器端都可能要设置用于证实自己身份的安全证书,并且还要设置信任对方的哪些安全证书。

    关于身份认证方面有个名词叫客户端模式,一般情况客户端要对服务器端的身份进行验证,但是无需向服务器证实自己的身份,这样不用向对方证实自己身份的通信端我们就说它处于客户模式,否则成它处于服务器模式。SSLSocket的setUseClientMode(Boolean mode)方法可以设置客户端模式或服务器模式。

    BIO模式实现SSL通信

    使用BIO模式实现SSL通信除了对一些证书密钥生成外,只需使用JDK自带的SSLServerSocket和SSLSocket等相关类的API即可实现,简洁直观。

    ① 解决证书问题。

    一般而言作为服务器端必须要有证书以证明这个服务器的身份,并且证书应该描述此服务器所有者的一些基本信息,例如公司名称、联系人名等。证书由所有人以密码形式签名,基本不可伪造,证书获取的途径有两个:一是从权威机构购买证书,权威机构担保它发出的证书的真实性,而且这个权威机构被大家所信任,进而你可以相信这个证书的有效性;另外一个是自己用JDK提供的工具keytool创建一个自我签名的证书,这种情况下一般是我只想要保证数据的安全性与完整性,避免数据在传送的过程中被窃听或篡改,此时身份的认证已不重要,重点已经在端与端传输的秘密性上,证书的作用只体现在加解密签名。

    另外,关于证书的一些概念在这里陈述,一个证书是一个实体的数字签名,这个实体可以是一个人、一个组织、一个程序、一个公司、一个银行,同时证书还包含这个实体的公共钥匙,此公共钥匙是这个实体的数字关联,让所有想同这个实体发生信任关系的其他实体用来检验签名。而这个实体的数字签名是实体信息用实体的私钥加密后的数据,这条数据可以用这个实体的公共钥匙解密,进而鉴别实体的身份。这里用到的核心算法是非对称加密算法。

    SSL协议通信涉及密钥储存的文件格式比较多,很容易搞混,例如xxx.cer、xxx.pfx、xxx.jks、xxx.keystore、xxx.truststore等格式文件。如图,搞清楚他们有助于理解后面的程序,.cer格式文件俗称证书,但这个证书中没有私钥,只包含了公钥;.pfx格式文件也称为证书,它一般供浏览器使用,而且它不仅包含了公钥,还包含了私钥,当然这个私钥是加密的,不输入密码是解不了密的;.jks格式文件表示java密钥存储器(java key store),它可以同时容纳N个公钥跟私钥,是一个密钥库;.keystore格式文件其实跟.jks基本是一样的,只是不同公司叫法不太一样,默认生成的证书存储库格式;.truststore格式文件表示信任证书存储库,它仅仅包含了通信对方的公钥,当然你可以直接把通信对方的jks作为信任库(就算如此你也只能知道通信对方的公钥,要知道密钥都是加密的,你无从获取,只要算法不被破解)。有些时候我们需要把pfx或cert转化为jks以便于用java进行ssl通信,例如一个银行只提供了pfx证书,而我们想用java进行ssl通信时就要将pfx转化为jks格式。

    按照理论上,我们一共需要准备四个文件,两个keystore文件和两个truststore文件,通信双方分别拥有一个keystore和一个truststore,keystore用于存放自己的密钥和公钥,truststore用于存放所有需要信任方的公钥。这里为了方便直接使用jks即keystore替代truststore(免去证书导来导去),因为对方的keystore包含了自己需要的信任公钥。

    下面使用jdk自带的工具分别生成服务器端证书,通过如下命令并输入姓名、组织单位名称、组织名称、城市、省份、国家信息即可生成证书密码为tomcat的证书,此证书存放在密码也为tomcat的tomcat.jks证书存储库中。如果你继续创建证书将继续往tomcat.jks证书存储库中添加证书。如果你仅仅输入keytool -genkey -alias tomcat -keyalg RSA -keypass tomcat -storepass tomcat,不指定证书存储库的文件名及路径,则工具会在用户的home directory目录下生产一个“.keystore”文件作为证书存储库。


    类似的,客户端证书也用此方式进行生成。如下

    ② 服务端TomcatSSLServer.java

    public class TomcatSSLServer {
      private static final String SSL_TYPE = "SSL";
      private static final String KS_TYPE = "JKS";
      private static final String X509 = "SunX509";
      private final static int PORT = 443;
      private static TomcatSSLServer sslServer;
      private SSLServerSocket svrSocket;
    
      public static TomcatSSLServer getInstance() throws Exception {
        if (sslServer == null) {
          sslServer = new TomcatSSLServer();
        }
        return sslServer;
      }
    
      private TomcatSSLServer() throws Exception {
        SSLContext sslContext = createSSLContext();
        SSLServerSocketFactory serverFactory = sslContext.getServerSocketFactory();
        svrSocket = (SSLServerSocket) serverFactory.createServerSocket(PORT);
        svrSocket.setNeedClientAuth(true);
        String[] supported = svrSocket.getEnabledCipherSuites();
        svrSocket.setEnabledCipherSuites(supported);
      }
    
      private SSLContext createSSLContext() throws Exception {
        KeyManagerFactory kmf = KeyManagerFactory.getInstance(X509);
        TrustManagerFactory tmf = TrustManagerFactory.getInstance(X509);
        String serverKeyStoreFile = "c:\\tomcat.jks";
        String svrPassphrase = "tomcat";
        char[] svrPassword = svrPassphrase.toCharArray();
        KeyStore serverKeyStore = KeyStore.getInstance(KS_TYPE);
        serverKeyStore.load(new FileInputStream(serverKeyStoreFile), svrPassword);
        kmf.init(serverKeyStore, svrPassword);
        String clientKeyStoreFile = "c:\\client.jks";
        String cntPassphrase = "client";
        char[] cntPassword = cntPassphrase.toCharArray();
        KeyStore clientKeyStore = KeyStore.getInstance(KS_TYPE);
        clientKeyStore.load(new FileInputStream(clientKeyStoreFile), cntPassword);
        tmf.init(clientKeyStore);
        SSLContext sslContext = SSLContext.getInstance(SSL_TYPE);
        sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
        return sslContext;
      }
    
      public void startService() {
        SSLSocket cntSocket = null;
        BufferedReader ioReader = null;
        PrintWriter ioWriter = null;
        String tmpMsg = null;
        while (true) {
          try {
            cntSocket = (SSLSocket) svrSocket.accept();
            ioReader = new BufferedReader(new InputStreamReader(cntSocket.getInputStream()));
            ioWriter = new PrintWriter(cntSocket.getOutputStream());
            while ((tmpMsg = ioReader.readLine()) != null) {
              System.out.println("客户端通过SSL协议发送信息:" + tmpMsg);
              tmpMsg = "欢迎通过SSL协议连接";
              ioWriter.println(tmpMsg);
              ioWriter.flush();
            }
          } catch (IOException e) {
            e.printStackTrace();
          } finally {
            try {
              if (cntSocket != null) cntSocket.close();
            } catch (Exception ex) {
              ex.printStackTrace();
            }
          }
        }
      }
    
      public static void main(String[] args) throws Exception {
        TomcatSSLServer.getInstance().startService();
      }
    }复制代码

    基本顺序是先得到一个SSLContext实例,再对SSLContext实例进行初始化,密钥管理器及信任管理器作为参数传入,证书管理器及信任管理器按照指定的密钥存储器路径和密码进行加载。接着设置支持的加密套件,最后让SSLServerSocket开始监听客户端发送过来的消息。

    ③ 客户端TomcatSSLClient.java

    public class TomcatSSLClient {
      private static final String SSL_TYPE = "SSL";
      private static final String X509 = "SunX509";
      private static final String KS_TYPE = "JKS";
      private SSLSocket sslSocket;
    
      public TomcatSSLClient(String targetHost, int port) throws Exception {
        SSLContext sslContext = createSSLContext();
        SSLSocketFactory sslcntFactory = (SSLSocketFactory) sslContext.getSocketFactory();
        sslSocket = (SSLSocket) sslcntFactory.createSocket(targetHost, port);
        String[] supported = sslSocket.getSupportedCipherSuites();
        sslSocket.setEnabledCipherSuites(supported);
      }
    
      private SSLContext createSSLContext() throws Exception {
        KeyManagerFactory kmf = KeyManagerFactory.getInstance(X509);
        TrustManagerFactory tmf = TrustManagerFactory.getInstance(X509);
        String clientKeyStoreFile = "c:\\client.jks";
        String cntPassphrase = "client";
        char[] cntPassword = cntPassphrase.toCharArray();
        KeyStore clientKeyStore = KeyStore.getInstance(KS_TYPE);
        clientKeyStore.load(new FileInputStream(clientKeyStoreFile), cntPassword);
        String serverKeyStoreFile = "c:\\tomcat.jks";
        String svrPassphrase = "tomcat";
        char[] svrPassword = svrPassphrase.toCharArray();
        KeyStore serverKeyStore = KeyStore.getInstance(KS_TYPE);
        serverKeyStore.load(new FileInputStream(serverKeyStoreFile), svrPassword);
        kmf.init(clientKeyStore, cntPassword);
        tmf.init(serverKeyStore);
        SSLContext sslContext = SSLContext.getInstance(SSL_TYPE);
        sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
        return sslContext;
      }
    
      public String sayToSvr(String sayMsg) throws IOException {
        BufferedReader ioReader = new BufferedReader(new InputStreamReader(sslSocket.getInputStream()));
        PrintWriter ioWriter = new PrintWriter(sslSocket.getOutputStream());
        ioWriter.println(sayMsg);
        ioWriter.flush();
        return ioReader.readLine();
      }
    
      public static void main(String[] args) throws Exception {
        TomcatSSLClient sslSocket = new TomcatSSLClient("127.0.0.1", 443);
        BufferedReader ioReader = new BufferedReader(new InputStreamReader(System.in));
        String sayMsg = "";
        String svrRespMsg = "";
        while ((sayMsg = ioReader.readLine()) != null) {
          svrRespMsg = sslSocket.sayToSvr(sayMsg);
          if (svrRespMsg != null && !svrRespMsg.trim().equals("")) {
            System.out.println("服务器通过SSL协议响应:" + svrRespMsg);
          }
        }
      }
    }复制代码

    客户端的前面操作基本跟服务器端的一样,先创建一个SSLContext实例,再用密钥管理器及信任管理器对SSLContext进行初始化,当然这里密钥存储的路径是指向客户端的client.jks。接着设置加密套件,最后使用SSLSocket进行通信。

    注意服务器端有行代码svrSocket.setNeedClientAuth(true);它是非常重要的一个设置方法,用于设置是否验证客户端的身份。假如我们把它注释掉或设置为false,此时客户端将不再需要自己的密钥管理器,即服务器不需要通过client.jks对客户端的身份进行验证,把密钥管理器直接设置为null也可以跟服务器端进行通信。

    最后谈谈信任管理器,它的职责是决定是否信任远端的证书,那么它凭借什么去判断呢?如果不显式设置信任存储器的文件路径,将遵循如下规则:①如果系统属性javax.NET.ssl.truststore指定了truststore文件,那么信任管理器将去jre路径下的lib/security目录寻找这个文件作为信任存储器;②如果没设置①中的系统属性,则去寻找一个%java_home%/lib/security/jssecacerts文件作为信任存储器;③如果jssecacerts不存在而cacerts存在,则cacerts作为信任存储器。

    至此,一个利用JSSE实现BIO模式的SSL协议通信的例子已完成。

    NIO模式实现SSL通信

    在jdk1.5之前,由于互联网还没快速发展起来,对于常见的应用使用BIO模式即可满足需求,而这时jdk的JSSE接口也仅仅只是提供了基于流的安全套接字,但随着网络的发展,BIO模型明显已经不足以满足一些高并发多连接接入的场景,体现在机器上就是要不同的线程模型以至于能最大程度地压榨计算器的运算,于是此时引入了NIO模式,原来基于流的阻塞模式IO只需使用SSLServerSocket和SSLSocket即可完成SSL通信,而JDK中对于NIO模式并没有提供与之对应的“SSLServerSocketChannel”和“SSLSocketChannel”,这是由NIO模式决定的,很难设计一个“SSLServerSocketChannel”类与Selector交互,强行地引入将带来更多的问题,这更像解决一个问题引入了三个问题,并且还会导致API更加复杂,另外Nio细节也不适合屏蔽,它应该由应用开发层去控制。所有的这些都决定了jdk不会也不能有NIO安全套接字。

    jdk1.5为了支持NIO模式的SSL通信,引入了SSLEngine引擎,它负责了底层ssl协议的握手、加密、解密、关闭会话等等操作,根据前面SSL\TLS协议章节我们知道SSL协议在握手阶段会有十三个步骤,在握手过程中不会有应用层的数据传输,只有在握手认证完成后双方才会进行应用层数据交换。大致把握手分为四阶段:
    ①客户端发送hello消息;
    ②服务端响应hello消息且发送附带的认证消息;
    ③客户端向客户端发送证书和其他认证消息;
    ④完成握手。

    SSLEngine在握手过程中定义了五种HandshakeStatus状态,【NEED_WRAP、NEED_UNWRAP、NEED_TASK、FINISHED、NOT_HANDSHAKING】,通过他们实现协议通信过程中状态管理,按照四个阶段其中的状态是这样转换的,刚开始它的状态为NEED_UNWRAP,表示等待解包,读取客户端数据并解包后,把状态置为NEED_WRAP,表示等待打包,打包完向客户端响应数据后状态又重置为NEED_UNWRAP,如此切换直至握手完成时状态被置为FINISHED,表示握手已经完成,此后状态置为NOT_HANDSHAKING,表示已经不在握手阶段了。另外还有一个NEED_TASK状态表示SSLEngine有额外的任务需要执行,而且这些任务都是比较耗时或者可能阻塞的,例如访问密钥文件、连接远程证书认证服务、密钥管理器使用何种认证方式作为客户端认证等等操作。为了保证NIO特性,这些操作不能直接由当前线程操作,当前线程只会把状态改为NEED_TASK,后面处理线程会交由其他线程处理。

    看看程序是如何使用nio模式进行ssl通信的,主要看服务端如何实现。

    public class NioSSLServer {
      private SSLEngine sslEngine;
      private Selector selector;
      private SSLContext sslContext;
      private ByteBuffer netInData;
      private ByteBuffer appInData;
      private ByteBuffer netOutData;
      private ByteBuffer appOutData;
      private static final String SSL_TYPE = "SSL";
      private static final String KS_TYPE = "JKS";
      private static final String X509 = "SunX509";
      private final static int PORT = 443;
    
      public void run() throws Exception {
        createServerSocket();
        createSSLContext();
        createSSLEngine();
        createBuffer();
        while (true) {
          selector.select();
          Iterator<SelectionKey> it = selector.selectedKeys().iterator();
          while (it.hasNext()) {
            SelectionKey selectionKey = it.next();
            it.remove();
            handleRequest(selectionKey);
          }
        }
      }
    
      private void createBuffer() {
        SSLSession session = sslEngine.getSession();
        appInData = ByteBuffer.allocate(session.getApplicationBufferSize());
        netInData = ByteBuffer.allocate(session.getPacketBufferSize());
        appOutData = ByteBuffer.wrap("Hello\n".getBytes());
        netOutData = ByteBuffer.allocate(session.getPacketBufferSize());
      }
    
      private void createSSLEngine() {
        sslEngine = sslContext.createSSLEngine();
        sslEngine.setUseClientMode(false);
      }
    
      private void createServerSocket() throws Exception {
        ServerSocketChannel serverChannel = ServerSocketChannel.open();
        serverChannel.configureBlocking(false);
        selector = Selector.open();
        ServerSocket serverSocket = serverChannel.socket();
        serverSocket.bind(new InetSocketAddress(PORT));
        serverChannel.register(selector, SelectionKey.OP_ACCEPT);
      }
    
      private void createSSLContext() throws Exception {
        KeyManagerFactory kmf = KeyManagerFactory.getInstance(X509);
        TrustManagerFactory tmf = TrustManagerFactory.getInstance(X509);
        String serverKeyStoreFile = "c:\\tomcat.jks";
        String svrPassphrase = "tomcat";
        char[] svrPassword = svrPassphrase.toCharArray();
        KeyStore serverKeyStore = KeyStore.getInstance(KS_TYPE);
        serverKeyStore.load(new FileInputStream(serverKeyStoreFile), svrPassword);
        kmf.init(serverKeyStore, svrPassword);
        String clientKeyStoreFile = "c:\\client.jks";
        String cntPassphrase = "client";
        char[] cntPassword = cntPassphrase.toCharArray();
        KeyStore clientKeyStore = KeyStore.getInstance(KS_TYPE);
        clientKeyStore.load(new FileInputStream(clientKeyStoreFile), cntPassword);
        tmf.init(clientKeyStore);
        sslContext = SSLContext.getInstance(SSL_TYPE);
        sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
      }
    
      private void handleRequest(SelectionKey key) throws Exception {
        if (key.isAcceptable()) {
          ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
          SocketChannel channel = ssc.accept();
          channel.configureBlocking(false);
          doHandShake(channel);
        } else if (key.isReadable()) {
          if (sslEngine.getHandshakeStatus() == HandshakeStatus.NOT_HANDSHAKING) {
            SocketChannel sc = (SocketChannel) key.channel();
            netInData.clear();
            appInData.clear();
            sc.read(netInData);
            netInData.flip();
            SSLEngineResult engineResult = sslEngine.unwrap(netInData, appInData);
            doTask();
            if (engineResult.getStatus() == SSLEngineResult.Status.OK) {
              appInData.flip();
              System.out.println(new String(appInData.array()));
    
            }
            sc.register(selector, SelectionKey.OP_WRITE);
          }
        } else if (key.isWritable()) {
          SocketChannel sc = (SocketChannel) key.channel();
          netOutData.clear();
          SSLEngineResult engineResult = sslEngine.wrap(appOutData, netOutData);
          doTask();
          netOutData.flip();
          while (netOutData.hasRemaining())
            sc.write(netOutData);
          sc.register(selector, SelectionKey.OP_READ);
        }
      }
    
      private void doHandShake(SocketChannel sc) throws IOException {
        boolean handshakeDone = false;
        sslEngine.beginHandshake();
        HandshakeStatus hsStatus = sslEngine.getHandshakeStatus();
        while (!handshakeDone) {
          switch (hsStatus) {
            case FINISHED:
              break;
            case NEED_TASK:
              hsStatus = doTask();
              break;
            case NEED_UNWRAP:
              netInData.clear();
              sc.read(netInData);
              netInData.flip();
              do {
                SSLEngineResult engineResult = sslEngine.unwrap(netInData, appInData);
                hsStatus = doTask();
              } while (hsStatus == SSLEngineResult.HandshakeStatus.NEED_UNWRAP
                  && netInData.remaining() > 0);
              netInData.clear();
              break;
            case NEED_WRAP:
              SSLEngineResult engineResult = sslEngine.wrap(appOutData, netOutData);
              hsStatus = doTask();
              netOutData.flip();
              sc.write(netOutData);
              netOutData.clear();
              break;
            case NOT_HANDSHAKING:
              sc.configureBlocking(false);
              sc.register(selector, SelectionKey.OP_READ);
              handshakeDone = true;
              break;
          }
        }
      }
    
      private HandshakeStatus doTask() {
        Runnable task;
        while ((task = sslEngine.getDelegatedTask()) != null) {
          new Thread(task).start();
        }
        return sslEngine.getHandshakeStatus();
      }
    
      public static void main(String[] args) throws Exception {
        new NioSSLServer().run();
      }
    }复制代码

    根据程序大致说明程序过程,
    ①创建用于非阻塞通信的主要对象ServerSocketChannel和Selector、绑定端口、注册接收事件;
    ②创建SSL上下文,此过程主要是根据前面创建好的密钥存储器tomcat.jks和client.jks去创建密钥管理器和信任管理器,并通过密钥管理器和信任管理器去初始化SSL上下文;
    ③创建SSL引擎,主要通过SSL上下文创建SSL引擎,并将它设为不验证客户端身份;
    ④创建缓冲区,使用SSL协议通信的过程中涉及到四个缓冲区,如下图,netInData表示实际从网络接收到的字节流,它是包含了SSL协议和应用数据的字节流,通过SSLEngine引擎进行认证解密等处理后的应用可直接使用的数据则用appInData表示,同样地,应用层要传递的数据为appOutData,而经过SSLEngine引擎认证加密处理后放到网络中传输的字节流则为netOutData;
    ⑤接下去开始监听处理客户端的连接请求,一旦有可接受的连接则会先进行SSL协议握手,完成握手后才能进行传输,即对通道的读写操作。

    握手操作是一个比较复杂的过程,必须要保证握手完成后才能进行应用层数据交换,所以这里使用一个while循环不断做握手操作直到完成。前面已经介绍了握手阶段会有五种状态,【NEED_WRAP、NEED_UNWRAP、NEED_TASK、FINISHED、NOT_HANDSHAKING】,由于SSL协议握手的报文都由SSLEngine引擎自动生成,所以我们只需对不同状态做不同操作即可,例如,NEED_UNWRAP状态则调用unwrap方法,NEED_WRAP则调用wrap方法,NEED_TASK则使用其他线程处理委托任务,握手报文自动由这些方法完成,当握手完成后状态则被置为FINISHED,接着状态变为NOT_HANDSHAKING,表示已经不在握手阶段了,已经可以进行应用层通信了,此时整个SSL握手结束。

    应用层安全通信过程其实也是靠SSLEngine引擎的unwrap和wrap方法对数据进行加解密并且对通信双方进行认证,例如应用层读操作是将netInData和appInData传入unwrap方法,处理后的appInData即为应用需要的数据,而写操作则是将appOutData和netOutData传入wrap方法,处理后的netOutData即为传输给对方的数据。

    至此,通过在网络与应用直接增加一个SSLEngine引擎层,则实现了安全通信,并且使用了NIO模式让服务端拥有更加出色的处理性能。

    ====广告时间,可直接跳过====

    鄙人的新书《Tomcat内核设计剖析》已经在京东预售了,有需要的朋友可以到 item.jd.com/12185360.ht… 进行预定。感谢各位朋友。

    =========================

    欢迎关注:

    这里写图片描述
    展开全文
  • stplanr, R 软件包为传输研究提供功能和...它提供了解决传输规划和建模常见问题的函数,如如何从点A 到点B的最佳问题。 它的总体目的是提供一个可以重复。透明和可以访问的工具包,帮助人们更好地理解传输系统和。项
  • TCP/IP网络分层模型 TCP/IP五层模型将网络功能五层。 每一层实现各自的功能。...物理层:透明传输比特流。 每一层都有对应的具体协议。 每一层通过接口为上层提供服务。 收发两端的对等层...
  • 不能传输 从本地文本文件上载数据的自建数据源的转换到生产机。但是上载到PSA中没有问题。2 因此联想到是否可以写代码,来将数据放到DSO中。结果发现开发机的PSA透明表跟生产机的PSA透明表名是不一样的。3. 将生产...
  • 问题1-15:什么是“无缝的”、“透明的”和“虚拟的”? 问题1-16:在教材的1.7.2节提到协议有三个要素,即语法、语义和同步。语义是否已经包括了同步的意思? 问题1-17:为什么协议不能设计成100%可靠的? 问题1-18...
  • 主要功能:解决计算机间比特传输问题,即透明地传送比特流,关心的是点到点的问题。 透明传输:指不管所传输的数据是什么样的比特组合,都能够在链路上传输。要尽可能地屏蔽掉不同传输媒体和通信手段的差异。 ...
  • 数据链路层.pptx

    2020-12-20 19:14:55
    数据链路层结构:“MAC子层”的最基本功能就是如何控制不同用户数据传输中对物理层传输介质的访问,其中包括:介质访问时的寻址和介质访问冲突的解决。“LLC子层”的最基本功能就是负责数据链路层中“逻辑链路”的...
  • 物理层解决了相邻结点透明传输比特的问题 物理层没有解决比特传输出现错误的问题: 发送端发送比特1,而接收端收到比特0,接收端无法知道接收的是否正确? 多个设备连接问题:谁能发送数据?数据发送给谁?谁负责...
  • 随着各类可调谐光器件在网络中的普及应用,如何在线优化它们的参数配置来改善网络的传输性能,还存在许多研究问题需要解决。  光路传输的限制  传统观点认为:光通道的信号质量都是有保证的,所有光纤链路具有...
  • 物理层要解决问题: 线路配置 如两个或两个以上的设备如何能实际地连接起来,传输线路时被共享还是由两个设备专用,线路是否可用等 ...【将来自数据链路层的数据进行二进制比特流透明传输】 物理层的主要功能是为数
  • 数据链路层解决三个重要问题:封装成帧,差错检测,透明传输。数据链路层所用的硬件:网卡(网络适配器)封装成帧数据链路层是以帧为单位进行传输和处理的。所以必须将上层的IP数据报在头部和尾部加上信息,封装成帧...
  • ## 物理层

    2020-07-13 15:34:59
    电气特性:指明在接口电缆的各条线上电压的范围,解决的是如何表达逻辑0和逻辑1的问题,什么样的电压范围表示0,什么样的电压范围表示1。传输速度,最大传输速度等 功能特性:指明某条线上出现的某一电平表示何种...
  • 目前主流的方式有两种,一种是在IP网上实现话音压缩和实时传送的VOIP方式,另一种则是在IP网络上透明传输E1电路信号,即TDMOIP方式。如何有效降低企业运营成本,是一个企业的生存、发展的基础。而降低成本,无非是...
  • 而计算机网络所讨论的透明传输,是指比特流看不见电路的存在。这样看来,两种“透明”的意思很不一样。应当怎样理解? 问题1-25:怎样才能知道哪些RFC文档已经成为因特网的正式标准(草案或建议标准)? 问题1-26:...
  • 比特币是一个去中心化的货币,总量有限,公开透明,它解决了信任的问题,可以把它看成是一个世界银行,它的价值是由共识构成的,认可这个世界银行的人越多,比特币就越有价值。 比特币价格一直是投资者最大的关心...
  • 5.解决SkinTabControl left和right绘制模式下tab标签悬浮样式不变化问题。 6.所有控件采用最高质量模式绘制文字,防止字体模糊以及锯齿。 7.解决SkinDataGridView的CellDoubleClick事件在不可编辑状态下双击不触发...
  • 即RSA的重大缺陷是无法从理论上把握它的保密性能如何,而且密码学界多数人士倾向于因子分解不是NPC问题, RSA算法是第一个能同时用于加密和数字签名的算法,也易于理解和操作。RSA是被研究得最广泛的公钥算法,从...
  • Modbus TCP转Modbus RTU

    2014-09-15 11:09:17
    另外,将ZLAN5142的“转化协议”选择为“无”,也可以当作普通的透明传输的串口服务器使用。但是不同于普通的串口服务器例如ZLAN5102,ZLAN5142即使作为普通串口服务器模式下也可以支持“多主机”功能,可解决多个...
  • 《Windows Sockets网络编程》是Windows...C.3 用户可以解决的错误 C.4 详细的错误描述 C.5 按数值排序的错误代码表 附录D 用户必备 D.1 重要文件 D.2 编译与链接机制 D.3 各种WinSock的使用 D.4 各种编程语言的使用
  • 金融行业移动信息化集中于满足业务增长需求,但如何在安全前提下提升使用效率、促成客户交易是金融行业不得不重点考虑的问题;流通业对信息发布、流程控制等移动应用需求较强,亟待契合行业特点的解决方案出现,同时...
  • 多媒体教室

    2013-06-14 08:10:31
    注: TCP/IP 设置完成后请用 PING 命令验证网络是否连通,如网络不通请尝试检查相应网络设备、重新安装 TCP/IP 协议等手段来解决问题。  2.3产品安装  教师机的安装 1. 插入安装光盘后会自动运行安装程序,进入...
  • 然而,由于HTML5的W3C标准规范还未制定,安卓系统中类浏览器Webview自身存在一些局限性,因此仍存在着诸多问题亟需解决,包括:(1)多窗口类浏览器模式问题。安卓上用于加载的Webview视图窗口只是作为类浏览器而...
  • 11.3.4 解决绑定竞争问题 339 11.3.5 分配接收和发送的包池与缓冲池 340 11.3.6 OID请求的发送和请求完成回调 342 11.3.7 ndisprotCreateBinding的最终实现 345 11.4 绑定的解除 351 11.4.1 解除绑定使用的API 351 ...
  • 11.3.4 解决绑定竞争问题 339 11.3.5 分配接收和发送的包池与缓冲池 340 11.3.6 OID请求的发送和请求完成回调 342 11.3.7 ndisprotCreateBinding的最终实现 345 11.4 绑定的解除 351 11.4.1 解除绑定使用的API 351 ...

空空如也

空空如也

1 2 3
收藏数 54
精华内容 21
关键字:

如何解决透明传输问题