一、概述
TeamViewer (Classic)提供了一个REACH API,用于集成到RMM或MDM解决方案中。通过这个API,客户可以方便地启动无人值守和有人值守的远程控制会话,从而提高工作效率。本教程解释了REACH API的基本概念,并说明和指导如何将API集成到管理解决方案中。
之后,我们用由TeamViewer (Classic)开发集成示例源代码(也称为VendorExampleApp)来解释如何利用API实现集成。
版权所有 (c) 2018 TeamViewer GmbH
TeamViewer特此授予任何获得本软件和相关文档文件(“软件”)副本的人免费许可,无限制地交易本软件,包括但不限于使用,复制,修改,合并的权利,出版,分发,再许可和/或出售本软件的副本,并根据以下条件允许被授权人员从事以下事项:
上述版权声明和本许可声明应包含在本软件的所有副本或实质部分。
本软件按“原样”提供,不提供任何形式的明示或暗示保证和担保,包括但不限于适销性,特定用途的适用性的保证。在任何情况下,作者或版权所有者均不对任何索赔,损害或其他债承担任何责任,无论这些责任是否由使用本软件而产生的,或者与使用本软件相关的,或者与交易本软件而产生的合同缔结,侵权或其他方面的行为。
再次使用或再次发表软件时,请尊重所包含的第三方软件的许可。
二、概念和安全性
REACH API所包含的概念分为三种类型:
- 注册REACH API管理的设备,也称为部署阶段;
- 取消注册设备,通过REACH API删除访问权限;
- 控制阶段允许启动无人职守(连接到远程设备时没有用户交互)和有人值守(连接远程设备时用户手动接受连接)连接到已注册的设备。两种连接模式都可以是全功能远程控制会话,或仅限于纯视图连接(仅支持在特定平台上)
1、设备注册
部署用于在设备和管理解决方案之间交换加密密钥。 这些加密密钥用于控制和注销阶段,并确保与设备的所有通信都是安全的。
该图解释了注册流程:
Management Solution Agent(MSA)是必须在设备上运行的组件。运行此代理必须具有设备的管理权限,并能获取完成部署而所需的数据。开始部署时,MSA会读取一个特殊文件(步骤1),该文件在TeamViewer (Classic)客户端启动时以及每次成功或不成功的部署请求发送到TeamViewer (Classic)客户端之后生成。
注意:每个平台上的步骤1可能不同。文件方法仅适用于Windows和macOS平台。但是,为所有平台提供的部署数据是相同的。
此文件包含RolloutKey(ROK仅对一个请求有效并用于解密API响应),全局唯一标识符(GUID)以及设备的RemotecontrolID(TeamViewer (Classic) ID)。
API可用RemotecontrolID、部署数据的GUID和一系列权限调用(POST / oem / devices / createdevicekey)以创建新的设备密钥(步骤3)。API调用能够启动TeamViewer (Classic)后端至目标客户端的通信。如果数据有效,TeamViewer (Classic)客户端将为以后的远程控制会话创建密钥对。
通过TeamViewer (Classic) 的后端,TeamViewer (Classic)客户端对具有RolloutKey的新创建的密钥对的私钥(= DeviceKey)进行加密。同时将与密钥对的标识符和API调用的解除授权令牌一起作为回复发送给API调用行。(步骤4)管理解决方案必须使用先前获得的RolloutKey来解密DeviceKey。为了简化解码过程,使用标准PEM格式加密DeviceKey。DeviceKey和DeviceKeyID是在控制阶段使用的,而解除授权令牌是在需要注销DeviceKey的时候使用的。
注意:解密的DeviceKey,DeviceKeyID以及解除授权令牌必须安全存储在管理解决方案端。
2、控制阶段
完成第一步后,从现在开始,可以启动到已注册设备的远程控制会话。启动远程控制会话按以下过程:
由于DeviceKey和DeviceKeyID是启动远程控制会话必须的前提,所以只有在设备成功注册以后远程连接才变为可能。
第一步是WebAPI调用(POST / oem / devices / requestcontrol),其参数包括RemotecontrolID,DeviceKeyID和控制类型。这些参数将传递给目标客户端,目标客户端验证DeviceKey是否拥有API调用请求的控制权限。
成功验证请求后,目标TeamViewer (Classic)客户端会生成一次性临时密钥(一个Hash身份验证消息,HMAC),该秘钥与会话密码同等效果。HMAC的确定是用控制请求所接收的数据和TeamViewer (Classic)客户端生成的数据(Target nonce,DeviceSecret)进行计算。在计算HMAC之后,客户端使用通过DeviceKeyID在API调用中指定的DeviceKey加密DeviceSecret。
DeviceSecret作为API的调用响应参数EncryptedDeviceSecret与目标随机数以及TeamViewer (Classic)协议URL(“teamviewerapi://”)的URL模板一起返回,该URL模板包括占位符YOURMASTERSECRET。ISV执行必须使用DeviceKey解密EncryptedDeviceSecret,并根据解密的设备密钥,目标随机数和API调用请求中发送的其他信息计算HMAC。
计算出HMAC后,就必须用HMAC替换占位符YOURMASTERSECRET以构建有效的TeamViewer (Classic)协议URL。必须将包含HMAC的完整URL提供给源TeamViewer (Classic)客户端以启动连接。这可能会在浏览器或命令行参数中发生(当TeamViewer (Classic)安装并与TeamViewer (Classic)应用程序关联时,TeamViewer (Classic)协议已在操作系统中注册)。
TeamViewer (Classic)客户端现在使用URL中的信息(包括HMAC)建立与目标客户端的连接。
3、注销阶段
如果不再使用DeviceKey,则可以注销DeviceKey。该图显示了基本原理:
注销DeviceKey需要WebAPI调用DELETE / oem / devices / unregister。要执行注销,需要DeviceKeyID识别正确的密钥、设备的RemotecontrolID以及部署阶段在POST / oem / devices / createdevicekey API调用所发出的解除授权令牌。之后,此信息将由设备上的TeamViewer (Classic)客户端处理。
如果API中的信息与客户端上的信息匹配,则部署阶段所创建的密钥对的本地部分将会被删除,并通过WebAPI返回确认信息。从此时起,不能再使用此DeviceKey建立进一步的远程控制会话。
三、集成实例VendorExampleApp
1、要求
按照本说明实际执行集成时,需要在继承的不同阶段中提供一些信息,包括:
- 供应商ID;
- 租户帐户;
- 具有远程控制权限的脚本令牌。
供应商ID已由TeamViewer (Classic)发送给ISV。之后,可以使用具有租户管理权限的供应商帐户的脚本令牌创建租户帐户。可以使用类似Postman的工具来发布WebAPI调用。最后,需要来自租户帐户的脚本令牌,该令牌需要有创建设备密钥,请求控制和删除设备密钥的权限。
本集成实例所提供的代码使用以下技术和语言库:
- C# and .NET runtime
- BouncyCastle security library
Android系统还需要以下信息:
- APK签名值为MSA;
- APK签名的ID(由TeamViewer (Classic)的WebAPI发布;
- TeamViewer (Classic)帐户的ID。
AppKey是Android设备上MSA的签名,AppKeyID与帐户ID可一起用作索引。这两个值都与WebAPI调用一起返回,用于在Web控制台中注册AppKey。
提示:请注意,解释过程中使用的所有值都可替换具体值。
2、供应商端集成实施概述
下图简要介绍了供应商方面的组件,需要进行集成。
3、REACH API设备的部署
API调用可以使用任何HTTP客户端类完成,此WebAPI调用的前提条件是从TeamViewer (Classic)客户端编写的部署文件中读取信息。此文件在Windows和macOs平台上需要管理员权限才可读。
在Android设备上,访问部署数据需要注册MSA应用程序。这将在下面的Android集成一章中单独描述。对于我们这里的示例,我们引用的代码是VendorExampleApp中源代码的一部分。首先,必须创建JSON主体,可以选用数据类进行创建,例如下列示例中的方法创建此类数据类。
请注意,RemotecontrolID和RequestID的信息来自部署文件:
(1)创建请求的正文
var createDeviceKeyRequest = new CreateDeviceKeyRequest { key_permissions = "unattended", remotecontrol_id = "r124124124", request_id = "{8c4fa1a7-9d1c-41e9-be17-70e95c289080}", tenant_id = "t0001" };
创建数据类并用信息填写后,指定具体的url和方法,在HTTP请求标头上填写所需的身份验证就可以发出WepAPI调用,然后将包含上面的序列化数据类的JSON主体附上。将授权添加到HTTP标头必须通过TeamViewer (Classic)管理控制台创建应用程序脚本标记。
必须确保此脚本标记具有执行WebAPI调用的正确权限。如果该脚本令牌的帐户是租户,则这些权限只能授予给该脚本令牌。请参阅以下框中的示例代码,该代码发出调用用以解析响应,并返回包含响应值的反序列化数据类。此示例代码来源于供应商应用程序。
(2)请求DeviceSecretKey
HttpWebRequest webReq = (HttpWebRequest)WebRequest.Create(apiUrl + "/api/v1/oem/devices/createdevicekey"); webReq.Method = "POST"; webReq.ContentType = "application/json"; webReq.Headers.Add("Authorization", "Bearer "+accessToken); using (var streamWriter = new StreamWriter(webReq.GetRequestStream())) { var createSecretKeyRequestJson = JsonConvert.SerializeObject(createDeivceKeyRequest); streamWriter.Write(createDeviceKeyRequestJson); streamWriter.Flush(); streamWriter.Close(); } var httpResponse = (HttpWebResponse) await webReq.GetResponseAsync(); if (httpResponse == null) { throw new InvalidDataException("No response received"); } var responseStream = httpResponse.GetResponseStream(); if (responseStream == null) { throw new InvalidDataException("No response stream received."); } using (var streamReader = new StreamReader(responseStream)) { var result = streamReader.ReadToEnd(); return JsonConvert.DeserializeObject<CreateDeviceKeyResponse>(result); }
API响应后,您现在就拥有加密的DeviceKey。
为了解码PEM格式的加密DeviceKey,我们在这里选择了BouncyCastle作为库,这是很多操作更容易。
BouncyCastle也可用于Java。但是每种语言都有很多库可以使用PEM文件。
加密算法会在您从API请求获得的PEM文件中直接声明。
示例:
"-----BEGIN RSA PRIVATE KEY-----\nProc-Type: 4,ENCRYPTED\nDEK-Info: AES-256-CBC,309AA16\n-----END RSA PRIVATE KEY-----\n"
出于安全考虑,我们强烈建议您使用库来处理工作,因为库通常能够自动识别正确的算法
如果算法本身存在安全问题,我们将会更改它。
如果使用适当的库,则ISV端不需要进行任何更改,而手动实现则不会那么灵活。
PEM密钥使用Bouncy Castle解密需要以下代码,如下例所示。
为简单起见,我们将deviceKey存储为已解密的PEM格式字符串,因为这可以很好地由BouncyCastle库处理并且可以作为字符串存储。
解密PEM密钥
TextReader textReader = new StringReader(encryptedDeviceKey); PemReader pemReader = new PemReader(textReader, new PasswordFinder(password)); object deviceKeyObject = pemReader.ReadObject(); AsymmetricCipherKeyPair rsaPrivatekey = (AsymmetricCipherKeyPair)deviceKeyObject; TextWriter tw = new StringWriter(); var pemWriter = new PemWriter(tw); pemWriter.WriteObject(rsaPrivatekey.Private); pemWriter.Writer.Flush(); string deviceKey = tw.ToString(); return deviceKey;
3、发起远程控制会话
用上一阶段得到DeviceKey和DeviceKeyID的数据,可以向注册的设备发起远程控制会话。在请求远程控制期间,需要使用与部署阶段期间请求权限相同控件类型。同样需要首先将WebAPI调用所需的信息填充到数据类实例中。
(1)请求控制数据类
var requestControlRequest = new RequestControlRequest() { control_type = "attended", device_key_id = "{114fa1a7-abab-41e9-be17-70e95c289080}", remotecontrol_id = "r124124124", tenant_id = "t0001", tenant_nonce = "243dd78d07324ab7befa41390d08d35f" };
有了这个数据实例,就可以下面的C#代码方式发出WebAPI调用。首先添加URL,然后添加带有脚本令牌的HTTP authorization-header,最后添加序列化数据类实例作为JSON主体。一旦请求返回并带有响应,就可以将数据反序列化为自己的数据类实例,该实例为TeamViewer (Classic)链接提供模板。之后,将使用Hash验证消息(HMAC)完成此链接。
(2)远程控制呼叫
HttpWebRequest webReq = (HttpWebRequest)WebRequest.Create(apiUrl + "/api/v1/oem/devices/requestcontrol"); webReq.Method = "POST"; webReq.ContentType = "application/json"; webReq.Headers.Add("Authorization", "Bearer " + accessToken); using (var streamWriter = new StreamWriter(webReq.GetRequestStream())) { var requestControlRequestJson = JsonConvert.SerializeObject(requestControlRequest); streamWriter.Write(requestControlRequestJson); streamWriter.Flush(); streamWriter.Close(); } var httpResponse = (HttpWebResponse)(await webReq.GetResponseAsync()); if (httpResponse == null) { throw new InvalidDataException("No response received"); } var responseStream = httpResponse.GetResponseStream(); if (responseStream == null) { throw new InvalidDataException("No response stream received."); } using (var streamReader = new StreamReader(responseStream)) { var result = streamReader.ReadToEnd(); return JsonConvert.DeserializeObject<RequestControlResponse>(result); }
- 由于API响应仅返回链接模板,因此必须使用HMAC完成链接;
- 创建HMAC的方法在API响应中以加密方式返回,因此第一步是解密;
- 用于解密的密钥是DeviceKey,这在部署阶段通过API响应接收;
- 由于DeviceKey存储为PEM格式的字符串,代码的第一部分将此字符串转换为适合BouncyCastle的结构(“AsymmetricCipherKeyPair”类型变量)。
(3)解密PreMasterSecret
using (var reader = new StringReader(DeviceKey)) { //Convert to right format var bytesToDecrypt = encryptedDeviceSecret.FromBase64SafeUrl(); //--------DECRYPT WITH PEM DEVICE KEY-------------// AsymmetricCipherKeyPair keyPair = (AsymmetricCipherKeyPair) new PemReader(reader).ReadObject(); var decryptEngine = new OaepEncoding(new RsaEngine()); decryptEngine.Init(false, keyPair.Private); var decryptedDeviceSecret = decryptEngine.ProcessBlock(bytesToDecrypt, 0, bytesToDecrypt.Length); }
使用已解密的DeviceSecret,TenantID,目标的RemotecontrolID以及目标和租户nonce,可以按下面示例的方法创建HMAC,这也可以使用安全库BouncyCastle实现的。
(4)生成YOURMASTERSECRET
//---------HMAC Values to be hashed, recombined in a string ------------// var hashVerifier = $"{tenantNonce}{targetNonce}{tenantId.Substring(1)}{remotecontrolId.Substring(1)}"; HMACSHA512 hmacsha512 = new HMACSHA512(decryptedDeviceSecret); byte[] hashmessage = hmacsha512.ComputeHash(Encoding.UTF8.GetBytes(hashVerifier)); var masterSecret = hashmessage.ToBase64SafeUrl();
结果将放入TeamViewer (Classic)链接模板而不是字符串YOURMASTERSECRET。字符串完成后,打开有TeamViewer (Classic)企业客户端的系统的Internet浏览器链接,或者通将此链接传递给TeamViewer (Classic).exe调用的命令行来建立远程控制会话。
4、取消注册设备
取消注册设备调用需要以下参数才能成功执行:
- 具有正确权限的有效脚本令牌(删除设备密钥)
- 解除授权令牌
- 设备密钥ID
- 遥控器ID
- 租户ID
与前一的阶段一样,创建此特定调用的数据类实例,并填充用于调用WebAPI的具体数据:
(1)数据结构创建
var unregisterDeviceRequest = new UnregisterDeviceRequest { tenant_id = "t0001", remotecontrol_id = "r124124124", device_key_id = "{114fa1a7-abab-41e9-be17-70e95c289080}", decommission_token = _mState.LastDecommissionToken };
如前所示用相同的实现模式,将WebAPI调用URL,方式,应用程序脚本标记添加至HTTP授权标头,最后以JSON格式附加先前创建的数据类的序列化内容。
(2)注销
HttpWebRequest webReq = (HttpWebRequest)WebRequest.Create(apiUrl + "/api/v1/oem/devices"); webReq.Method = "DELETE"; webReq.ContentType = "application/json"; webReq.Headers.Add("Authorization", "Bearer " + accessToken); using (var streamWriter = new StreamWriter(webReq.GetRequestStream())) { var unregisterDeviceRequestJson = JsonConvert.SerializeObject(unregisterDeviceRequest); streamWriter.Write(unregisterDeviceRequestJson); streamWriter.Flush(); streamWriter.Close(); } var httpResponse = (HttpWebResponse)await webReq.GetResponseAsync(); if (httpResponse == null) { throw new InvalidDataException("No response received"); } var responseStream = httpResponse.GetResponseStream(); if (responseStream == null) { throw new InvalidDataException("No response stream received."); } using (var streamReader = new StreamReader(responseStream)) { var result = streamReader.ReadToEnd(); return; }
在这种情况下,返回的结果将不包含任何数据。操作是否成功只能通过检查返回值是否为204来验证(也可以参见HTTP公共返回值)。
四、Android集成的细节
由于Android平台限制(没有适当的管理权限分离),部署阶段与其他平台不同。需要先验证MSA才能与TeamViewer (Classic) Android应用程序通信,并以不同的方式获取RolloutKey。
MSA签名密钥的SHA-256 Hash值(此处称为AppKey)必须在TeamViewer (Classic)中注册为十六进制字符串,并使用WebAPI调用POST“/ oem / appregistrations”。此注册步骤对于每个AppKey仅需要一次,并且可以使用任何REST客户端(如Postman)完成。
TeamViewer (Classic)应用程序和MSA之间的通信使用本地Android Binder界面,在注册REACH API时得到的AIDL文件中有描述。
在进行任何其他调用之前,验证方法需要首先被验证。验证方法需要用到注册MSA AppKey帐户时的AccountID和注册时返回的KeyID。如果验证成功,则可以任意调用Binder服务的方法,否则会出现SecurityException。
除了应用程序注册以外,Android上的主要区别还在于如何获取RolloutKey。含有转出数据的文件将写入TeamViewer (Classic)软件中的私有应用程序中存储,且只能用于读取TeamViewer (Classic)应用程序的信息。因此,MSA应用程序必须通过Binder界面请求部署数据。成功获取部署数据后,使用Binder接口上的requestPreKeyData,可以按照《REACH API设备的部署》中所述的方式继续操作。