【物联网智能网关-18】多通道远程安全升级

一个典型的物联网系统,往往有上百个,甚至成千上万个联网节点,并且每个节点联网的方式可能不同,比如有Zigbee联网,有430/470M无线方式联网,有GPRS/3G无线模块联网,有RS485/CAN总线方式联网,有以太网TCP/IP方式联网等等。如此种种节点,如果需要进行程序更新,这是一个非常令人头疼的事。

另外,随着物联网各种项目的大量实施,在运行维护过程中,其技术人员的交通住宿成本及人力成本的逐年增加,让设备的远程维护,远程升级功能变得越来越重要了。

但是要实现远程升级,也不是一件容易的事,需要重点解决如下三方面的事情:第一就是安全,如何防止有恶意的人篡改和远程升级相关设备的程序;第二就是远程升级通信信道的问题,比如国内的GPRS和3G模块,由于被分配的IP地址不是公网地址,所以必须反连,也就是说模块要主动请求服务器进行远程升级,而不是常规程序升级的做法,直接访问设备对设备进行升级,另外就是各种通信接口的支持,比如Zigbee,430/470M无线信道,由于这类设备,每次传输的字节数有限,实现远程升级功能也要充分考虑到这一点。第三就是可靠升级,比如升级一半,设备突然掉电,如何确保设备不变砖。

基于.NET Micro Framework系统的物联网智能网关,分别采用如下技术来解决以上所提到的问题。

【安全问题】

.NET Micro Framework系统,在V4.1版本之前,一直提供了两种加解密方法,一种是非对称加密RSA和对称加密XTEA(这部分代码一直没有开源,提供各种ARM版本和X86的连接库),但是这个库其中的RSA有两个致命的问题,一个是其生成的公钥和私钥无法和Windows或其他平台的目前已有的方法提供的一致,互不通用。另外一个问题就是其X86版本和嵌入式版本,其加解密竟然无法实现互解(另外也发现嵌入式版本的RSA加密,无法对特定长度的明文进行加解密)。

4.2版本之后,封装了OpenSSL中的RSA、AES,DES等加密方法,但是对一些运行.NET Micro Framework系统比较小的嵌入式设备来说, 集成一个OpenSSL库有些太大了,大概增加400K字节大小,比一个.NET Micro Framework系统还要大,这是让人无法承受的。

后续重新自行调整和构建了RSA相关代码,算相对完美的解决了这个问题。(关于加解密的详情,后续有专门的文件进行介绍)。

远程升级的思路:

首先对要升级的文件进行SHA1哈希计算,拼装成标准签名文件后,用RSA的私钥对该数据进行加密 –- 对升级文件进行签名。

考虑到嵌入式系统RAM空间有限,数据可以分批,分片写入到Flash区域中,等完全写入完毕后,直接在Flash上(非NandFlash,可以直接访问),用预先下载好的公钥进行签名验证。

【信道问题】

其实.NET Micro Framework系统也提供了官方的远程升级方案,甚至为了实现这个功能,新提供一个MicroBooter来配合完成远程升级。但是其通信信道是基于.NET Micro Framework调试口的,这就有一个很大的问题,比如GPRS/3G通信设备无法远程直接访问,Zigbee,430/470M无线模块,每次传输的数据有限,都无法被配置为调试通道。

我们的解决方案是,信道和具体的升级功能功能完全剥离,这样信道的问题,就简化成如何远程获取升级文件和签名的问题。针对这个问题,可实现的方案有很多种,也可以分为主动式和被动式升级。

【可靠升级】

我们采取的方案是,接收到的数据,先更新到一个系统Flash区,下载完毕后,用公钥进行签名验证,验证通过后(一是验证来源是否可靠,另外就是验证升级文件的完整性),才置相关标志位,然后让系统复位,复位后根据相关的标志,把相关数据转移到正常程序区,并加载运行。

从以上步骤中可以看出,如果远程升级过程没有完成,原来的程序还将继续保持,并不会被破坏。

 

升级流程示意图如下:

 

 

下面让我们详细介绍完整的远程升级流程:

创建公钥和私钥

在示例“数据加密解密”中,我们提供了一个和.NET Micro Framework相对应的Windows版本的数据加密解密程序(界面如下图所示)。其用到的加解密函数,都是.NET Framework的标准加密解密库。

考虑到存储空间,设备端(.NET Micro Framework系统)的二进制格式的密钥和上位机的二进制格式略有不同,实际要小一些,比如公钥的指数,一般都固定为0x1,0x0,0x1三个字节的数,所以就省略了。同理私钥也是做了这样的处理。

但是xml格式的都是一样的,底层也提供了一个接口函数,可以直接导入XML格式的密钥。

 

为了各自的方便,我们为对应的PC端程序(远程升级的发起方)提供的是XML格式的私钥,为设备端(被升级的一方)提供的是二进制格式的公钥。

分别用刚才提到的工具进行导出。

导出私钥的相关代码其实很简单,默认是1024位的Key,代码如下:

  RSACryptoServiceProvider mskey = new RSACryptoServiceProvider();

    string xmlKey = mskey.ToXmlString(true);

针对二进制格式的公钥,其实我们仅导出128字节的Modulus的数据:

  byte[] key = new byte[128];

    Array.Copy(Modulus, 0, key, 0, 128);

 

公钥部署

采用最新版本的YFAccessFlash工具(V3.12.0以上),把导出的二进制格式的公钥部署到设备上(.NET Micro Framework系统)。

 

 公钥提取及程序部署、校验

以下代码完成公钥的提取(需要引用YFSoft.Config库)

byte[] PublicKey = new byte[128];

    if (YFSoft.Config.Read("PublicKey", PublicKey, 0, 128) == 0)

{

    //…

}

分以下四步分别完成程序的部署和校验(需要引用YFSoft.RemoteUpgrade库)

第一步:清空系统部署区

RemoteUpgrade.Initialize();  

 

第二步:向系统数据区写数据(可以分块写)

RemoteUpgrade.Write(0, peFile, 0, peFile.Length);

 

第三步:RSA+SHA1校验

bool IsVerifyOK = (RemoteUpgrade.Verify(peFile.Length, Signature, PublicKey)==0);

 

第四步:置标志位,系统重启

    RemoteUpgrade.Finish();

   

升级文件及签名数据远程传输

以上几步,基本上流程一致,没有多少变化,但是这一步,变数比较大。从物理信道上来说,可以是以太网、Wifi、蓝牙、无线(430M/470M)、Zigbee、RS232/RS485串口通信、GPRS/3G等等。

即使同一个信道,采用的升级策略也不相同,比如可以是主动式,和被动式。所谓的主动式,就是远程可以直接对远程的模块进行升级,是相对即时的一种的方式。而被动式,是远程模块,每隔特定的时间间隔,主动去访问指定的服务器,根据获取的信息,判断是否该升级。比如国内通过GPRS/3G通道,也只能采用这种方式了。

我们提供的两个例子,一个是基于以太网TCP/IP通信,一个是基于串口通信,都是主动式的。不过例子中提供了一个YFSoft.WireProtocol源码类,和特定的信道,还有主动被动都没有关系,只需要实现相关的委托接口就可以完成远程升级文件的接收或发送。

要实现的三个接口分别是:

public delegate int TransmitBytesHandle(byte[] buffer, int offset, int count);

    public delegate int ReceiveBytesHandle(byte[] buffer, int offset, int count);

    public delegate int GetReceiveCountHandle();

第一个是发送数据用的,第二个是接收数据用的,第三个是获取接收缓冲区数据个数的。并且这个YFSoft.WireProtocol.cs文件,桌面.NET Framework和设备.NET Micro Framework完全一样的。

比如如果通信信道是串口,则这三个接口的实现代码如下:

private int TransmitBytesHandle(byte[] buffer, int offset, int count)

     {

          port.Write(buffer, offset, count);

          return 0;

     }

 private int ReceiveBytesHandle(byte[] buffer, int offset, int count)

     {

         return port.Read(buffer, offset, count);

     }

     private int GetReceiveCountHandle()

     {

         return port.BytesToRead;

     }      

如果是网口,则代码如下:

private int TransmitBytesHandle(byte[] buffer, int offset, int count)

     {

          return socket.Send(buffer, offset, count, SocketFlags.None);

     }

     private int ReceiveBytesHandle(byte[] buffer, int offset, int count)

    {

         return socket.Receive(buffer, offset, count, SocketFlags.None);

     }

    private int GetReceiveCountHandle()

     {

         return socket.Available;

     }

   

接下来,在设备端主程序中,只需要添加两句代码,就可以实现远程升级的服务端功能。

如果是串口,代码如下:

    //远程升级服务(基于串口)

    PortServerUpdate tsu = new PortServerUpdate("COM1",115200,true);

    tsu.Launch();

 

如果是网口,代码如下:

//远程升级服务(基于TCP)

    TcpServerUpdate tsu = new TcpServerUpdate(10189,true);

    tsu.Launch()

 

PC端的接口实现类似,但是远程数据有所不同,由于串口方式和网口方式基本一样,所以我们仅举以太网通信方式。

具体的升级代码如下:

    private void btnUpdate_Click(object sender, EventArgs e)

    {

        if (txtFiles.Text != null && txtFiles.Text.Length > 0)

        {

            txtInfo.Text = ">>> 提取以下文件的数据...\r\n";              

            var files = Directory.EnumerateFiles(txtFiles.Text, "*.pe");

            MemoryStream ms = new MemoryStream();

            foreach (string file in files)

            {

                txtInfo.Text += ">>> "+file + "\r\n";

                FileStream tfs = new FileStream(file, FileMode.Open, FileAccess.Read);

                byte[] buffer = new byte[tfs.Length];

                tfs.Read(buffer, 0, buffer.Length);

                tfs.Close();

                ms.Write(buffer, 0, buffer.Length);

            }

            txtInfo.Text += ">>> 提取并合成数据成功!\r\n";

 

            //创建签名文件

            string xmlPrivateKey = "<RSAKeyValue><Modulus>vBjTkuBruYPSBE5y5T7hd4KEADy6UuBk1v+Es8BOvggOsfEGvxJNraDTCxTPaNhVkbaCFIavw8amXoIkFzjDw7fV3JVDflsqZ4qg23DOcWz/DvF+12sNcXTsHX7HELJYObZI1lo1kE2fFej1uuRzr7v9DgoFurgg9tGU9gD3dCU=</Modulus><Exponent>AQAB</Exponent><P>4NIKd4E+/0RRT58FZriPTWkfysy8c1Hl7CbeKet1m7KDsatwxNYm6u+oKltmG8UQ73pUfsEbBFpCo/UgL36osQ==</P><Q>1i73MFObZqHBvAhBP+uAbDa33k9yBqePDC0lKL5bqw4AhWzKb3EJd0OWUYf+LxZKnYbmbYNIMolDskcnbPjftQ==</Q><DP>2L7HJoWthY6I0blfDLRcO+ZgpzURbiCECVNDlqiRvySwwIandq174b5ho0xwuc8Yz7hhY76qXFzkqIt3lzKGUQ==</DP><DQ>lkj0F0vC8bu0dZyRNCmpvcSTNYEnMDYoMFIJDdKr/ZVglj5kuNdm3fFlqyWyHBYXGvtJ+jOw2AzqnFBDALqMNQ==</DQ><InverseQ>jQhrgi6tUQ0XpH1QIzLmHEMZn1PukayA+5tBps3SCswnFQC3iSW58N/m2YX2Z37USXQtqG8/HmZTyrUdAzkgMA==</InverseQ><D>hzCUyCjyY/ihdqTnoWqrZFjzBLSg+jX7ZCdsOkFKlvx1i2D/h07hc5x2cq13URTDk6IIJjaTl3NsWdrRk7shv4sXcW5bKg57GPvV6CHRij0Af1xQRLpYsgzeyVjRgKaU+Ea9KZV+mYQ8Ey56krF8MW0/Y4IsVvc1sGWG3HixFEE=</D></RSAKeyValue>";

            RSACryptoServiceProvider rsa = new RSACryptoServiceProvider();

            rsa.FromXmlString(xmlPrivateKey);

 

            byte[] pe = ms.ToArray();

            byte[] sis = rsa.SignData(pe, typeof(System.Security.Cryptography.SHA1));

            txtInfo.Text += ">>> 对合成数据进行签名成功!\r\n";

            txtInfo.Text += ">>> 正在升级...";

            Application.DoEvents();

            int ret = +tcu.Update(pe, sis);

            if (ret == 0)

            {

                txtInfo.Text += "成功!\r\n";

            }

            else

            {

                txtInfo.Text += "失败("+ ret.ToString() + ")!\r\n";

            }

        }           

    }

升级演示

1、先运行以太网远程升级示例下的MF程序。这个示例LED灯是慢闪的

2、运行远程升级PC端程序(开发板默认IP为192.168.1.100),连接成功后,选择MFSample目录下的

 

灯快速闪烁示例,然后远程升级。如果成功,会看到灯快速闪烁。这个时候PC端程序断开连接,重新连接,然后远程部署灯慢速闪烁程序,交换部署,以便更好的观察程序是否远程部署成功。

 

源码下载

源码链接:http://www.yfiot.com/MFRelease/Sample/RemoteUpgradeSample.rar

--------------------------------------------------------------------------------------

MF简介:http://blog.csdn.net/yefanqiu/article/details/5711770

MF资料:http://www.yfiot.com/DownloadList.asp?Id=2&page=1

posted on 2014-03-25 22:05  刘洪峰IoT  阅读(2805)  评论(2编辑  收藏  举报