SecurityConfiguration:定制安全传输行为
场景介绍
在软件开发中,安全是非常重要的一环。Remote Communication Kit提供的SecurityConfiguration是一个用于定制安全传输行为的工具,能够帮助开发者更好地保护其应用程序。通过合理的配置和使用,可以显著降低应用程序遭受攻击的风险。下面将详细说明如何使用证书校验来增强安全性。
| 证书校验方式 | 可以配置的参数 |
|---|---|
| remoteValidation(客户端校验服务端证书) | 'system':使用系统默认证书。 'skip':跳过证书校验。 CertificateAuthority:通过content指定证书内容,filePath指定证书文件路径,folderPath指定证书目录。 ValidationCallback:自定义证书。 |
| certificate(服务端校验客户端证书) | ClientCertificate(客户端证书校验):通过content指定证书内容,filePath指定证书文件路径,type指定证书类型,并通过key和keyPassword分别指定私钥及其密码。 |
约束与限制
定制安全传输行为能力支持Phone、2in1、Tablet、Wearable设备。并且从5.1.1(19)开始,新增支持TV设备。
客户端校验服务端证书
1. 使用系统默认证书
-
导入需要的模块。
import { rcp } from '@kit.RemoteCommunicationKit'; -
在remoteValidation中配置'system'时,将使用系统中默认的CA。
async function TestRcp() {const session = rcp.createSession();const request = new rcp.Request('https://example.com'); // 请替换为实际的网址request.configuration = {security: {remoteValidation: 'system',}}try {const response = await session.fetch(request);console.info(`response statusCode: ${JSON.stringify(response.statusCode)}`);console.info(`response: ${JSON.stringify(response.toString())}`);} catch (err) {console.error(`response error code is ${err.code}, error data is ${err.data}`);} finally {session.close();}}
2. 跳过证书校验
-
导入需要的模块。
import { rcp } from '@kit.RemoteCommunicationKit'; -
在remoteValidation中配置'skip'跳过证书校验。
async function TestRcp() {const session = rcp.createSession();const request = new rcp.Request('https://example.com'); // 请替换为实际的网址request.configuration = {security: {remoteValidation: 'skip',}}try {const response = await session.fetch(request);console.info(`response statusCode: ${JSON.stringify(response.statusCode)}`);console.info(`response: ${JSON.stringify(response.toString())}`);} catch (err) {console.error(`response error code is ${err.code}, error data is ${err.data}`);} finally {session.close();}}
3. 使用字符串指定证书内容
-
导入需要的模块。
import { rcp } from '@kit.RemoteCommunicationKit'; -
在remoteValidation中配置content,可以配置string类型证书内容。
const PEM_CA = '-----BEGIN CERTIFICATE-----\n' +'MIICPzCCAcWgAwIBAgIQBVVWvPJepDU1w6QP1atFcjAKBggqhkjOPQQDAzBhMQswCQYDVQQGEwJV\n' +'UzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSAwHgYD\n' +'VQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBHMzAeFw0xMzA4MDExMjAwMDBaFw0zODAxMTUxMjAw\n' +'MDBaMGExCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5k\n' +'aWdpY2VydC5jb20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEczMHYwEAYHKoZIzj0C\n' +'AQYFK4EEACIDYgAE3afZu4q4C/sLfyHS8L6+c/MzXRq8NOrexpu80JX28MzQC7phW1FGfp4tn+6O\n' +'YwwX7Adw9c+ELkCDnOg/QW07rdOkFFk2eJ0DQ+4QE2xy3q6Ip6FrtUPOZ9wj/wMco+I+o0IwQDAP\n' +'BgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAdBgNVHQ4EFgQUs9tIpPmhxdiuNkHMEWNp\n' +'Yim8S8YwCgYIKoZIzj0EAwMDaAAwZQIxAK288mw/EkrRLTnDCgmXc/SINoyIJ7vmiI1Qhadj+Z4y\n' +'3maTD/HMsQmP3Wyr+mt/oAIwOWZbwmSNuJ5Q3KjVSaLtx9zRSX8XAbjIho9OjIgrqJqpisXRAL34\n' +'VOKa5Vt8sycX\n' +'-----END CERTIFICATE-----';async function TestRcp() {const session = rcp.createSession();const request = new rcp.Request('https://example.com'); // 请替换为实际的网址request.configuration = {security: {remoteValidation: {content: PEM_CA,}}}try {const response = await session.fetch(request);console.info(`response statusCode: ${JSON.stringify(response.statusCode)}`);console.info(`response: ${JSON.stringify(response.toString())}`);} catch (err) {console.error(`response error code is ${err.code}, error data is ${err.data}`);} finally {session.close();}}
4. 使用二进制指定证书内容
-
导入需要的模块。
import { rcp } from '@kit.RemoteCommunicationKit';import { util } from '@kit.ArkTS'; -
在remoteValidation中配置content,可以配置ArrayBuffer类型证书内容。
const PEM_CA = '-----BEGIN CERTIFICATE-----\n' +'MIICPzCCAcWgAwIBAgIQBVVWvPJepDU1w6QP1atFcjAKBggqhkjOPQQDAzBhMQswCQYDVQQGEwJV\n' +'UzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSAwHgYD\n' +'VQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBHMzAeFw0xMzA4MDExMjAwMDBaFw0zODAxMTUxMjAw\n' +'MDBaMGExCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5k\n' +'aWdpY2VydC5jb20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEczMHYwEAYHKoZIzj0C\n' +'AQYFK4EEACIDYgAE3afZu4q4C/sLfyHS8L6+c/MzXRq8NOrexpu80JX28MzQC7phW1FGfp4tn+6O\n' +'YwwX7Adw9c+ELkCDnOg/QW07rdOkFFk2eJ0DQ+4QE2xy3q6Ip6FrtUPOZ9wj/wMco+I+o0IwQDAP\n' +'BgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAdBgNVHQ4EFgQUs9tIpPmhxdiuNkHMEWNp\n' +'Yim8S8YwCgYIKoZIzj0EAwMDaAAwZQIxAK288mw/EkrRLTnDCgmXc/SINoyIJ7vmiI1Qhadj+Z4y\n' +'3maTD/HMsQmP3Wyr+mt/oAIwOWZbwmSNuJ5Q3KjVSaLtx9zRSX8XAbjIho9OjIgrqJqpisXRAL34\n' +'VOKa5Vt8sycX\n' +'-----END CERTIFICATE-----';async function TestRcp() {const session = rcp.createSession();const request = new rcp.Request('https://example.com'); // 请替换为实际的网址const buffer = new ArrayBuffer(PEM_CA.length);util.TextEncoder.create('utf-8').encodeIntoUint8Array(PEM_CA, new Uint8Array(buffer));request.configuration = {security: {remoteValidation: {content: buffer,}}}try {const response = await session.fetch(request);console.info(`response statusCode: ${JSON.stringify(response.statusCode)}`);console.info(`response: ${JSON.stringify(response.toString())}`);} catch (err) {console.error(`response error code is ${err.code}, error data is ${err.data}`);} finally {session.close();}}
5. 使用文件指定证书内容
-
导入需要的模块。
import { rcp } from '@kit.RemoteCommunicationKit'; -
准备证书文件。在本例中,假设证书是example.pem,使用OpenSSL命令生成目标文件。
openssl x509 -subject_hash -in ./example.pem命令输出如下。以第一行为目标文件名,以.0为文件扩展名,以其他内容为文件内容,将输出保存为dd8e9d41.0。如果您使用Windows系统,OpenSSL可能会等待用户输入才会退出,按Enter键即可。
dd8e9d41-----BEGIN CERTIFICATE-----MIICPzCCAcWgAwIBAgIQBVVWvPJepDU1w6QP1atFcjAKBggqhkjOPQQDAzBhMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBHMzAeFw0xMzA4MDExMjAwMDBaFw0zODAxMTUxMjAwMDBaMGExCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEczMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE3afZu4q4C/sLfyHS8L6+c/MzXRq8NOrexpu80JX28MzQC7phW1FGfp4tn+6OYwwX7Adw9c+ELkCDnOg/QW07rdOkFFk2eJ0DQ+4QE2xy3q6Ip6FrtUPOZ9wj/wMco+I+o0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAdBgNVHQ4EFgQUs9tIpPmhxdiuNkHMEWNpYim8S8YwCgYIKoZIzj0EAwMDaAAwZQIxAK288mw/EkrRLTnDCgmXc/SINoyIJ7vmiI1Qhadj+Z4y3maTD/HMsQmP3Wyr+mt/oAIwOWZbwmSNuJ5Q3KjVSaLtx9zRSX8XAbjIho9OjIgrqJqpisXRAL34VOKa5Vt8sycX-----END CERTIFICATE----- -
在remoteValidation中通过filePath指定证书路径。关于文件路径,请参考应用文件。
async function TestRcp() {const session = rcp.createSession();const request = new rcp.Request('https://example.com'); // 请替换为实际的网址request.configuration = {security: {remoteValidation: {filePath: '/data/storage/el1/bundle/entry/resources/resfile/dd8e9d41.0', // 正式使用时,需替换为证书的沙箱路径。}}}try {const response = await session.fetch(request);console.info(`response statusCode: ${JSON.stringify(response.statusCode)}`);console.info(`response: ${JSON.stringify(response.toString())}`);} catch (err) {console.error(`response error code is ${err.code}, error data is ${err.data}`);} finally {session.close();}}
6. 使用文件目录指定证书。
-
导入需要的模块。
import { rcp } from '@kit.RemoteCommunicationKit'; -
在remoteValidation中配置folderPath指定证书所在的沙箱目录。关于文件路径,请参考应用文件。目录中的证书文件格式和内容请参考示例5中的步骤2。
async function TestRcp() {const session = rcp.createSession();const request = new rcp.Request('https://example.com'); // 请替换为实际的网址request.configuration = {security: {remoteValidation: {folderPath: '/data/storage/el1/bundle/entry/resources/resfile/', // 正式使用时需替换成证书所在的沙箱目录。}}}try {const response = await session.fetch(request);console.info(`response statusCode: ${JSON.stringify(response.statusCode)}`);console.info(`response: ${JSON.stringify(response.toString())}`);} catch (err) {console.error(`response error code is ${err.code}, error data is ${err.data}`);} finally {session.close();}}
7. 自定义证书校验
-
导入需要的模块。
import { rcp } from '@kit.RemoteCommunicationKit';import { networkSecurity } from '@kit.NetworkKit'; -
校验证书的日期是否符合预期。
function makeItTwoChar(s: string): string {if (s.length === 1) {return '0' + s;}return s;}function getASNDateString(): string {const date = new Date();let dateStr = date.getFullYear().toString().slice(2);dateStr += makeItTwoChar((date.getMonth() + 1).toString());dateStr += makeItTwoChar(date.getDate().toString());dateStr += makeItTwoChar(date.getHours().toString());dateStr += makeItTwoChar(date.getMinutes().toString());dateStr += makeItTwoChar(date.getSeconds().toString());return dateStr + 'Z';} -
自定义证书校验逻辑。
async function ValidationRemoteServer(context: rcp.ValidationContext): Promise<boolean> {// 服务器未返回证书,校验失败let length = context.pemCerts.length;if (length <= 0) {return Promise.reject();}// 此示例中最后一个证书为根证书,开发中请根据实际情况调整const firstCaBlob: networkSecurity.CertBlob = {type: networkSecurity.CertType.CERT_TYPE_PEM,data: context.pemCerts[length - 1],}// 如果 certVerificationSync 的第二个参数未填写,则使用系统默认的 CA("/etc/security/certificates")进行验证if (networkSecurity.certVerificationSync(firstCaBlob) !== 0) {return Promise.reject();}// 此处可以添加所需的校验逻辑,如校验日期for (const x of context.x509Certs) {let dateStr = getASNDateString();try {x.checkValidityWithDate(dateStr);} catch (e) {return Promise.reject();}}// 校验成功return Promise.resolve(true);} -
将定义好的自定义证书校验器配置到configuration中,并利用fetch发起请求。
async function TestRcp() {const session = rcp.createSession();const request = new rcp.Request('https://example.com'); // 请替换为实际的网址request.configuration = {security: {remoteValidation: ValidationRemoteServer,}}try {const response = await session.fetch(request);console.info(`response statusCode: ${JSON.stringify(response.statusCode)}`);console.info(`response: ${JSON.stringify(response.toString())}`);} catch (err) {console.error(`response error code is ${err.code}, error data is ${err.data}`);} finally {session.close();}}
服务器校验客户端证书
1. PEM证书
-
导入需要的模块。
import { rcp } from '@kit.RemoteCommunicationKit'; -
本示例使用自签名证书,以便完整演示本地证书生成流程。请通过以下OpenSSL命令依次完成:生成私钥、创建证书签名请求(CSR)文件、基于CSR签发自签名证书、将私钥与证书合并为PEM格式的完整证书文件。
注意:本示例使用自签名证书演示流程,实际生产环境中一般采用由可信证书颁发机构(CA)正式签发的证书。
openssl genrsa -out cert.key 2048openssl req -new -key cert.key -out cert.csropenssl x509 -req -in cert.csr -out cert.crt -signkey cert.key -CAcreateserial -days 3650openssl x509 -in cert.crt -out cert.pem -outform PEM -
在certificate中通过filePath指定证书路径(也可以通过content指定证书内容),type配置PEM指定证书类型,key和keyPassword指定证书私钥和密码。关于文件路径,请参考应用文件。
async function TestRcp() {const session = rcp.createSession();const request = new rcp.Request('https://example.com'); // 请替换为实际的网址request.configuration = {security: {certificate: {filePath: '/data/storage/el1/bundle/entry/resources/resfile/cert.pem', // 可以使用content字段指定证书内容type: 'PEM',key: '/data/storage/el1/bundle/entry/resources/resfile/cert.key', // 这是证书私钥,在使用中请替换为实际的证书秘钥路径,可选keyPassword: 'keyPassword', // 证书私钥的密码,在使用中请替换为实际的证书秘钥密码,可选,本例中不生效}}};try {const response = await session.fetch(request);console.info(`response statusCode: ${JSON.stringify(response.statusCode)}`);console.info(`response: ${JSON.stringify(response.toString())}`);} catch (err) {console.error(`response error code is ${err.code}, error data is ${err.data}`);} finally {session.close();}}
2. DER证书
-
导入需要的模块。
import { rcp } from '@kit.RemoteCommunicationKit'; -
在certificate中通过filePath指定证书内容(也可以通过content指定证书内容),type配置DER指定证书类型,key和keyPassword指定证书私钥和密码。使用openssl x509 -in cert.pem -outform der -out cert.der命令将上面例子中的pem文件转为der文件。关于文件路径,请参考应用文件。
async function TestRcp() {const session = rcp.createSession();const request = new rcp.Request('https://example.com'); // 请替换为实际的网址request.configuration = {security: {certificate: {filePath: '/data/storage/el1/bundle/entry/resources/resfile/cert.der', // 可以使用content字段指定证书内容type: 'DER',key: '/data/storage/el1/bundle/entry/resources/resfile/cert.key', // 这是证书私钥,在使用中请替换为实际的证书秘钥路径,可选keyPassword: 'keyPassword', // 证书私钥的密码,在使用中请替换为实际的证书秘钥密码,可选,本例中不生效}}};try {const response = await session.fetch(request);console.info(`response statusCode: ${JSON.stringify(response.statusCode)}`);console.info(`response: ${JSON.stringify(response.toString())}`);} catch (err) {console.error(`response error code is ${err.code}, error data is ${err.data}`);} finally {session.close();}}
3. P12证书
-
导入需要的模块。
import { rcp } from '@kit.RemoteCommunicationKit'; -
在certificate中配置filePath指定证书路径(也可以配置content指定证书内容),type配置P12指定证书类型,keyPassword指定证书私钥的密码。使用openssl pkcs12 -export -out cert.p12 -inkey cert.key -in cert.pem命令将上面例子中的pem文件转为P12证书,设置密码“1234”。关于文件路径,请参考应用文件。
async function TestRcp() {const session = rcp.createSession();const request = new rcp.Request('https://example.com'); // 请替换为实际的网址request.configuration = {security: {certificate: {filePath: '/data/storage/el1/bundle/entry/resources/resfile/cert.p12', // 可以使用content字段指定证书内容type: 'P12', // P12证书已包含私钥,不需要使用key指定私钥keyPassword: '1234', // 证书私钥的密码,在使用中请替换为实际的证书秘钥密码,可选}}};try {const response = await session.fetch(request);console.info(`response statusCode: ${JSON.stringify(response.statusCode)}`);console.info(`response: ${JSON.stringify(response.toString())}`);} catch (err) {console.error(`response error code is ${err.code}, error data is ${err.data}`);} finally {session.close();}}
证书锁定
证书锁定可以设置信任的证书的范围。通过比较证书公钥的SHA256哈希值的BASE64编码,限定可信任的证书。
-
可以通过以下OpenSSL命令获得证书公钥,其中,example.pem是服务器的PEM格式的证书,请替换为实际证书:
openssl x509 -in example.pem -noout -pubkey | openssl asn1parse -noout -inform pem -out example.public.key -
可以通过以下OpenSSL命令计算公钥的SHA256哈希值的BASE64编码,其中,example.public.key是上一步生成的证书的公钥:
openssl dgst -sha256 -binary example.public.key | openssl enc -base64
-
导入需要的模块。
import { rcp } from '@kit.RemoteCommunicationKit'; -
在certificatePinning中配置可信任的证书公钥的SHA256哈希值。
const TEST_URL = 'https://example.com'; // 请替换成需要的URLconst RIGHT_EXAMPLE_PUBLIC_KEY_SHA256_HASH = ['iMMpIJdSf5VlClHaxZReyhaLxLsmZMMNAiA2pMR8/M4=', // 请替换成需要的字符串'qBRjZmOmkSNJL0p70zek7odSIzqs/muR4Jk9xYyCP+E=', // 请替换成需要的字符串];async function TestRcp() {const session = rcp.createSession();const request = new rcp.Request(TEST_URL);request.configuration = {security: {certificatePinning: [{kind: 'public-key',publicKeyHash: RIGHT_EXAMPLE_PUBLIC_KEY_SHA256_HASH[0],hashAlgorithm: 'SHA-256'},{kind: 'public-key',publicKeyHash: RIGHT_EXAMPLE_PUBLIC_KEY_SHA256_HASH[1],hashAlgorithm: 'SHA-256'},]}};try {const response = await session.fetch(request);console.info('Test certificate pinning ' + response.statusCode);} catch (e) {console.error('Test certificate pinning ' + JSON.stringify(e));} finally {session.close();}}