DM协议使用了SyncML认证框架,并在《SyncML_DM_Security》文档中定义了扩展。
DM协议中的终端和服务端相互之间都需要作认证。 安全认证可以采用不同的级别。如果传输层中已经内建了认证机制,那么可以不使用DM协议级的认证。如果传输层没有提供足够安全的认证机制,那么DM协议级的认证是必需的。如果在原始请求中没有包含信息凭证或信息凭证不足,Server和终端都可以认证对方。
假设DM协议使用传输层级的认证,那么它能够提供Session级的认证,这样可以在会话开始的时候交换认证凭证(比如TLS或WTLS协议)。如果传输层不能够提供Session级的认证,每次请求都必须作认证。
认证凭证
认证凭证 主要包含以下内容:
- Server ID(唯一标识DM Server)
- Username
- Password
- Nonce 允许通过使用静态数据的Hash算法防止重复攻击(静态数据是为了克服重复攻击)
为了作终端到Server的认证,username、password和nonce都是必须的(即使没有填写它们,这三者也会被作Hash处理)。
为了作Server到终端的认证,Server ID、password和nonce都是必须的。Server必须为它所服务的每一个终端使用不同的password,这样是为了避免使用一个共享密码时,当Server与一个终端正在交互时,另一个终端进入造假。
认证
DM协议使用与SyncML协议相同的安全认证技术。也就是,终端和Server都可以互相发送认证凭证或要求对方发送认证凭证。
对于DM协议,syncml:auth-MD5认证类型是必须的。
1 DM协议中的MD-5认证
MD-5认证是在SyncHdr的Cred中提供原始的userid:password。
如下所示:
<SyncML>
<SyncHdr>
<VerDTD>1.1</VerDTD>
<VerProto>DM/1.1</VerProto>
<SessionID>1</SessionID>
<MsgID>1</MsgID>
<Target>
<LocURI>http://www.syncml.org/mgmt-server</LocURI>
</Target>
<Source>
<LocURI>IMEI:493005100592800</LocURI>
</Source>
<Cred>
<Meta>
<Type xmlns="syncml:metinf">syncml:auth-MD5</Type>
</Meta>
<Data>18EA3F……</Data>
<!-- base64 formatting of MD-5 Digest -->
</Cred>
<Meta>
<MaxMsgSize xmlns="syncml:metinf">5000</MaxMsgSize>
</Meta>
</SyncHdr>
<!—regular body information here -->
<SyncBody>
</SyncBody>
</SyncML>
2 计算MD-5摘要
Cred中提供的MD-5摘要是根据如下规则计算的:
H=MD5 Hash函数
Digest=MD5 Hash函数的输出
B64=base64编码函数
Digest=H(B64 (H(username:password) ):nonce)
这个算法允许认证方在不清楚password的情况下作认证。Password既没有作为Cred的一部分发送,也不需要认证方知道,认证方只需要保存一份计算好Hash值的username:password字串。
3 Password和nonce的使用方法
password和nonce的长度都建议至少要128位。
无论是从终端设备,还是从DM Server,要求对方认证时,nonce是必须的。
一旦认证凭证发给了对方,整个过程中nonce不变。
认证方必须考虑到认证凭证中的nonce失效的问题(可能是因为之前的通讯失败)。鉴于此,如果认证失败,认证方必须生成一个新的nonce,然后再次进行认证过程。
每个会话都要使用一个新的nonce,nonce值的序列应该是不可预料的。
4 认证过程实例
- 首先终端向DM Server发送Package 1。这时nonce是随便填的,如下例中的"sfOMmg54tOJfhp12mwiEFg=="
<SyncML xmlns='SYNCML:SYNCML1.1'>
<SyncHdr>
<VerDTD>1.1</VerDTD>
<VerProto>DM/1.1</VerProto>
<SessionID>4</SessionID>
<MsgID>1</MsgID>
<Target>
<LocURI>http://10.5.24.32:8080/funambol/dm</LocURI>
</Target>
<Source>
<LocURI>dmtest</LocURI>
<LocName>funambol</LocName>
</Source>
<Cred>
<Meta>
<Format xmlns='syncml:metinf'>b64</Format>
<Type xmlns='syncml:metinf'>syncml:auth-md5</Type>
</Meta>
<Data> sfOMmg54tOJfhp12mwiEFg== </Data>
</Cred>
<Meta>
<MaxMsgSize xmlns='syncml:metinf'>5000</MaxMsgSize>
<MaxObjSize xmlns='syncml:metinf'>25000</MaxObjSize>
</Meta>
</SyncHdr>
<SyncBody>
<Alert>
<CmdID>1</CmdID>
<Data>1201</Data>
</Alert>
<Replace>
<CmdID>2</CmdID>
<Item>
<Source>
<LocURI>./DevInfo/Lang</LocURI>
</Source>
<Data>en-us</Data>
</Item>
<Item>
<Source>
<LocURI>./DevInfo/DmV</LocURI>
</Source>
<Data>4.0</Data>
</Item>
<Item>
<Source>
<LocURI>./DevInfo/Mod</LocURI>
</Source>
<Data>scts devman</Data>
</Item>
<Item>
<Source>
<LocURI>./DevInfo/Man</LocURI>
</Source>
<Data>SyncML</Data>
</Item>
<Item>
<Source>
<LocURI>./DevInfo/DevId</LocURI>
</Source>
<Data>dmtest</Data>
</Item>
</Replace>
<Final/>
</SyncBody>
</SyncML>
- DM Server经过认证检查,发现终端的授权不合法,要求终端用新生成nonce重新认证
<SyncML>
<SyncHdr>
<VerDTD>1.1</VerDTD>
<VerProto>DM/1.1</VerProto>
<SessionID>4</SessionID>
<MsgID>1</MsgID>
<Target>
<LocURI>dmtest</LocURI>
<LocName>funambol</LocName>
</Target>
<Source>
<LocURI>http://10.5.24.32:8080/funambol/dm</LocURI>
</Source>
<RespURI>http://10.5.24.32:8080/funambol/dm?sid=W0JAMWE4ODg1Zi0xMjcyNTI2NzE5NTc4</RespURI>
<Cred>
<Meta>
<Format xmlns='syncml:metinf'>b64</Format>
<Type xmlns='syncml:metinf'>syncml:auth-md5</Type>
</Meta>
<Data>Dq77Ns5kBvpeE0/KG7lMGg==</Data>
</Cred>
<Meta>
<MaxMsgSize xmlns='syncml:metinf'>5000</MaxMsgSize>
<MaxObjSize xmlns='syncml:metinf'>25000</MaxObjSize>
</Meta>
</SyncHdr>
<SyncBody>
<Status>
<CmdID>1</CmdID>
<MsgRef>1</MsgRef>
<CmdRef>0</CmdRef>
<Cmd>SyncHdr</Cmd>
<TargetRef>http://10.5.24.32:8080/funambol/dm</TargetRef>
<SourceRef>dmtest</SourceRef>
<Chal>
<Meta>
<Format xmlns='syncml:metinf'>b64</Format>
<Type xmlns='syncml:metinf'>syncml:auth-md5</Type>
<NextNonce xmlns='syncml:metinf'>gDslXkBVXH0xXipPdjl6Lg==</NextNonce>
</Meta>
</Chal>
<Data>401</Data>
</Status>
<Status>
<CmdID>2</CmdID>
<MsgRef>1</MsgRef>
<CmdRef>1</CmdRef>
<Cmd>Alert</Cmd>
<Data>401</Data>
</Status>
<Status>
<CmdID>3</CmdID>
<MsgRef>1</MsgRef>
<CmdRef>2</CmdRef>
<Cmd>Replace</Cmd>
<Data>401</Data>
</Status>
<Final>
</Final>
</SyncBody>
</SyncML>
- 终端根据Server发过来的新nonce,重新生成认证凭证,再发给Server (此时还是Package 1)
<SyncML xmlns='SYNCML:SYNCML1.1'>
<SyncHdr>
<VerDTD>1.1</VerDTD>
<VerProto>DM/1.1</VerProto>
<SessionID>4</SessionID>
<MsgID>2</MsgID>
<Target>
<LocURI>http://10.5.24.32:8080/funambol/dm?sid=W0JAMWE4ODg1Zi0xMjcyNTI2NzE5NTc4</LocURI>
</Target>
<Source>
<LocURI>dmtest</LocURI>
<LocName>funambol</LocName>
</Source>
<Cred>
<Meta>
<Format xmlns='syncml:metinf'>b64</Format>
<Type xmlns='syncml:metinf'>syncml:auth-md5</Type>
</Meta>
<Data>gDwKeu7afMRzKdSIyt1RGg==</Data>
</Cred>
<Meta>
<MaxMsgSize xmlns='syncml:metinf'>5000</MaxMsgSize>
<MaxObjSize xmlns='syncml:metinf'>25000</MaxObjSize>
</Meta>
</SyncHdr>
<SyncBody>
<Status>
<CmdID>1</CmdID>
<MsgRef>1</MsgRef>
<CmdRef>0</CmdRef>
<Cmd>SyncHdr</Cmd>
<TargetRef>dmtest</TargetRef>
<SourceRef>http://10.5.24.32:8080/funambol/dm</SourceRef>
<Data>212</Data>
</Status>
<Alert>
<CmdID>2</CmdID>
<Data>1201</Data>
</Alert>
<Replace>
<CmdID>3</CmdID>
<Item>
<Source>
<LocURI>./DevInfo/Lang</LocURI>
</Source>
<Data>en-us</Data>
</Item>
<Item>
<Source>
<LocURI>./DevInfo/DmV</LocURI>
</Source>
<Data>4.0</Data>
</Item>
<Item>
<Source>
<LocURI>./DevInfo/Mod</LocURI>
</Source>
<Data>scts devman</Data>
</Item>
<Item>
<Source>
<LocURI>./DevInfo/Man</LocURI>
</Source>
<Data>SyncML</Data>
</Item>
<Item>
<Source>
<LocURI>./DevInfo/DevId</LocURI>
</Source>
<Data>dmtest</Data>
</Item>
</Replace>
<Final/>
</SyncBody>
</SyncML>
终端通过像上面那样第二次发送Package 1后,获得Server的认证。
终端通过使用Server产生的nonce计算认证凭证的过程如下:¶
假设Server返回的nonce为“gDslXkBVXH0xXipPdjl6Lg==”。
1) 对nonce进行B64解码
byte[] clientNonce = new BASE64Decoder().decodeBuffer("gDslXkBVXH0xXipPdjl6Lg==");
2) 根据公式 B64 (H(username:password)) 计算username:password的编码
StringBuffer userpwd=new StringBuffer();
userpwd.append(username).append(':').append(password);
MessageDigest md = MessageDigest.getInstance("MD5");
byte[] userDigestBytes =new BASE64Encoder().encode(md.digest(userpwd.toString().getBytes())).getBytes();
3) 建立一个缓冲区buf,长度等于userDigestBytes.length + 1 + clientNonce.length
byte[] buf = new byte[userDigestBytes.length + 1 + clientNonce.length];
4) 先将userDigestBytes中的内容复制到buf中,再将clientNonce中的内容复制到后面
System.arraycopy(userDigestBytes, 0, buf, 0, userDigestBytes.length);
buf[userDigestBytes.length] = (byte)':';
System.arraycopy(clientNonce, 0, buf, userDigestBytes.length+1, clientNonce.length);
5) 对缓冲区进行MD编码
byte[] digest = MD5.digest(buf);
6) 再进行B64编码
String serverDigestNonceB64 = new String(Base64.encode(digest));
最后得到的serverDigestNonceB64就是新的认证凭证。