4

用免费开放的华为安全检测功能,评估App运行设备安全

 3 years ago
source link: https://my.oschina.net/u/4956408/blog/4950975
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

系统完整性检测,是App需具备的一个实用功能。我们都知道,在系统不完整的手机上,例如被root过,运行App将面临被恶意攻击、窃取隐私等威胁,尤其是商城类App,购买环节的环境安全性至关重要,因此在App中增加能快速检测手机系统风险的功能必不可少。

这一重要功能目前是免费,其基本的技术原理是,App集成华为HMS Core的SDK,调用免费提供的安全检测服务,在TEE****可信执行环境中评估,得到的检测结果经过X.509****数字证书签名,双重保障下,检测到的结果真实可信、不会被恶意更改~

功能运行起来的效果:

以下是开发过程,分享给大家。

1.开发前准备

1.1 Android Studio安装

还没装开发工具的小伙伴下载指路:Android Studio官网下载

1.2 在AppGallery Connect中配置相关信息

在开发应用前,需在AppGallery Connect中配置相关信息。具体操作步骤

1.3 配置华为maven仓地址

打开Android Studio项目级“build.gradle”文件:

添加HUAWEI agcp插件以及Maven代码库:

  1. 在“buildscript > repositories”中配置HMS Core SDK的Maven仓地址。
  2. 在“allprojects > repositories”中配置HMS Core SDK的Maven仓地址。
  3. 如果App中添加了“agconnect-services.json”文件则需要在“buildscript > dependencies”中增加agcp配置。
buildscript {
    repositories {
        google()
        jcenter()
        // 配置HMS Core SDK的Maven仓地址。
        maven {url 'https://developer.huawei.com/repo/'}
    }
    dependencies {
        ...
        // 增加agcp配置。
        classpath 'com.huawei.agconnect:agcp:1.4.2.300'
    }
}
 
allprojects {
    repositories {
        google()
        jcenter()
        // 配置HMS Core SDK的Maven仓地址。
        maven {url 'https://developer.huawei.com/repo/'}
    }
}


这里需要说明的是,Maven仓地址只能在IDE中配置。需要添加多个Maven代码库的话,将华为公司的Maven仓地址配置在最后哦。

1.4 添加编译依赖

打开应用级的“build.gradle”文件:

在文件头apply plugin: 'com.android.application'下一行添加如下配置:

apply plugin: 'com.huawei.agconnect'


在“dependencies”中添加如下编译依赖:

dependencies {
    implementation 'com.huawei.hms:safetydetect:5.0.5.302'
}


1.5 配置混淆脚本

如果你自己开发时要用到AndResGuard,那就还需要在应用级的“build.gradle”文件中加入AndResGuard允许清单,代码可以参考官网的混淆配置

2 代码开发

2.1 创建SafetyDetectClient 并生成nonce值

这里的nonce值会被包含在后面的检测结果里,我们要通过校验nonce值,来确定返回结果是对应我们的请求的、没有被重放攻击。要注意nonce值需满足:

  • 一个nonce值只能被使用一次;
  • 长度在16~66字节间;
  • 建议从发送到您的服务器的数据中派生nonce值。

2.2 请求系统完整性检测接口

  1. SysIntegrity API有两个参数:第1个参数是nonce值,可以从上一步骤获取;第2个参数是appid,可以从agconnect-services.json 文件中读取:
  • 登录AppGallery Connect网站,-点击“我的项目”。
  • 在项目列表中找到您的项目,在项目中点击需要配置签名证书指纹的应用。
  • 在“项目设置 > 常规” > “应用”,可以查看。

2. 这里设定的是,用户在购买会员时,调用系统完整性检测接口,以检测支付环境是否存在风险。实际编码中,在MemberCenterAct.java类中的列表点击事件处理方法,调用SafetyDetectUtil 类的detectSysIntegrity的接口,具体代码:

private void onAdapterItemClick(int position) {
       // 调用系统完整性检测接口以检测支付环境风险
    SafetyDetectUtil.detectSysIntegrity(this, new ICallBack<Boolean>() {
        @Override
        public void onSuccess(Boolean baseIntegrity) {
            if (baseIntegrity) {
                // 系统完整性未遭到破坏,可以继续购买
                buy(productInfo);
            } else {
                // 系统完整性遭到破坏,弹出提示框,来提醒用户,并让用户选择是否继续
                showRootTipDialog(productInfo);
            }
        }
        …
    });
}


3. 在商场App中,把系统完整性检测接口的调用放到SafetyDetectUtil.java这个工具类中来实现,封装在detectSysIntegrity的方法中,具体示例代码如下:

public static void detectSysIntegrity(final Activity activity, final ICallBack<? super Boolean> callBack) {
    // 生成 nonce值
    byte[] nonce = ("Sample" + System.currentTimeMillis()).getBytes(StandardCharsets.UTF_8); 
    // 从app目录下的agconnect-services.json文件中读取app_id字段
    String appId = AGConnectServicesConfig.fromContext(activity).getString("client/app_id");
    // 获取 Safety Detect 服务客户端,调用sysIntegrity API,并添加成功事件监听
SysIntegrityRequest  sysintegrityrequest = new SysIntegrityRequest();
   sysintegrityrequest.setAppid(appId);
   sysintegrityrequest.setNonce(nonce);
	//PS256 or RS256
   sysintegrityrequest.setAlg("RS256"); 
	Task task = mClient.sysIntegrity(sysintegrityrequest);
        task.addOnSuccessListener(new OnSuccessListener<SysIntegrityResp>() {
            @Override
            public void onSuccess(SysIntegrityResp response) {
            //Safety Detect 服务接口成功响应。可以通过 SysIntegrityResp 类的 getResult 方法来获取检测结果
                String jwsStr = response.getResult(); 
VerifyResultHandler verifyResultHandler = new VerifyResultHandler(jwsStr, callBack);
                //将检测结果发送至开发者的服务器进行验证
                verifyJws(activity, jwsStr, verifyResultHandler);
            }
        });
}


4. 这里在verifyJws 方法中请求App Server的相关接口,来对检测结果进行验证。这个方法的第3个参数是一个 VerifyResultHandler 类对象, 它实现了一个回调接口,以便在服务器验证结束后,对返回的结果进行后续的处理。接下来介绍如何在App Server中验证检测结果。

2.3 在App Server中验证检测结果

App在获得TSMS Server返回的检测结果后,将其发送到App Server,由App Server使用HUAWEI CBG根证书来对结果中的签名和证书链进行校验,从而确认本次系统完整性检测结果是否有效。
App Server侧读取证书并验证 JWS 字符串的示例代码如下:
1)    解析 JWS字符串,获取其中的 header、payload和signature

public JwsVerifyResp verifyJws(JwsVerifyReq jwsVerifyReq) {
    // 获取端侧发送到服务器侧的jws信息
    String jwsStr = jwsVerifyReq.getJws();    
    // 解析JWS, 分段, 该JWS固定为三段,使用"."号分隔
    String[] jwsSplit = jwsStr.split("\\."); 
    try {
        // 解析JWS, Base64解码, 并构造JWSObject
        JWSObject jwsObject = new JWSObject(new Base64URL(jwsSplit[0]), new Base64URL(jwsSplit[1]), new Base64URL(jwsSplit[2]));
        // 验证JWS并设置验证结果
        boolean result = VerifySignatureUtil.verifySignature(jwsObject);
// 服务器侧检测结果验证响应消息体
        JwsVerifyResp jwsVerifyResp = new JwsVerifyResp();  
        jwsVerifyResp.setResult(result);
    } catch (ParseException | NoSuchAlgorithmException e) {
        RUN_LOG.catching(e);
    }
    return jwsVerifyResp;
}


2. 这里使用VerifySignatureUtil工具类中的verifySignature方法完成相关信息的验证,包括JWS签名算法、证书链、签名证书主机名、JWS签名等,示例代码:

public static boolean verifySignature(JWSObject jws) throws NoSuchAlgorithmException {
    JWSAlgorithm jwsAlgorithm = jws.getHeader().getAlgorithm();
    // 1. 验证JWS签名算法
    if ("RS256".equals(jwsAlgorithm.getName())) {
        // 进行证书链校验,并根据签名算法获取 Signature 类实例,用来验证签名
        return verify(Signature.getInstance("SHA256withRSA"), jws);
    }
    return false;
}
private static boolean verify(Signature signature, JWSObject jws) {
    // 提取JWS头部证书链信息, 并转换为合适的类型, 以便进行后续操作
    X509Certificate[] certs = extractX509CertChain(jws);
    // 2. 校验证书链
    try {
        verifyCertChain(certs);
    } catch (Exception e) {
        return false;
    }
    // 3. 校验签名证书(叶子证书)域名信息, 该域名固定为sysintegrity.platform.hicloud.com
    try {
        new DefaultHostnameVerifier().verify("sysintegrity.platform.hicloud.com", certs[0]);
    } catch (SSLException e) {
        return false;
    }
    // 4. 验证JWS签名信息,使用签名证书里的公钥来验证
    PublicKey pubKey = certs[0].getPublicKey();
    try {
        // 使用签名证书里的公钥初始化 Signature 实例
        signature.initVerify(pubKey);
        // 从 JWS 提取签名输入,并输入到 Signature 实例
        signature.update(jws.getSigningInput());
        // 使用Signature 实例来验证签名信息
        return signature.verify(jws.getSignature().decode());
    } catch (InvalidKeyException | SignatureException e) {
        return false;
    }
}


3. 这里的extractX509CertChain方法,实现了从JWS Header中提取证书链的过程,详细代码如下:

private static X509Certificate[] extractX509CertChain(JWSObject jws) {
    List<X509Certificate> certs = new ArrayList<>();
    List<com.nimbusds.jose.util.Base64> x509CertChain = jws.getHeader().getX509CertChain();
    try {
        CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
        certs.addAll(x509CertChain.stream().map(cert -> {
            try {
            return (X509Certificate) certFactory.generateCertificate( new ByteArrayInputStream(cert.decode()) );
            } catch (CertificateException e) {
                RUN_LOG.error("X5c extract failed!");
            }
            return null;
        }).filter(Objects::nonNull).collect(Collectors.toList()));
    } catch (CertificateException e) {
        RUN_LOG.error("X5c extract failed!");
    }
    return (X509Certificate[]) certs.toArray();
}


4. 这里的verifyCertChain方法,实现了证书链校验的过程,具体实现如下:

private static void verifyCertChain(X509Certificate[] certs) throws CertificateException, NoSuchAlgorithmException,
    InvalidKeyException, NoSuchProviderException, SignatureException {
    // 逐一验证证书有效期及证书的签发关系
    for (int i = 0; i < certs.length - 1; ++i) {        
        certs[i].checkValidity();
        PublicKey pubKey = certs[i + 1].getPublicKey();
        certs[i].verify(pubKey);
    }
    // 使用预置的 HUAWEI CBG 根证书, 来验证证书链中的最后一张证书
    PublicKey caPubKey = huaweiCbgRootCaCert.getPublicKey();
    certs[certs.length - 1].verify(caPubKey);
}


5. 华为根证书的加载是在VerifySignatureUtil工具类的静态代码段中实现的。示例代码如下:

static {
    // 加载预置的 HUAWEI CBG 根证书
    File filepath = "~/certs/Huawei_cbg_root.cer"; 
    try (FileInputStream in = new FileInputStream(filepath)) {
        CertificateFactory cf = CertificateFactory.getInstance("X.509");
        huaweiCbgRootCaCert = (X509Certificate) cf.generateCertificate(in);
    } catch (IOException | CertificateException e) {
        RUN_LOG.error("HUAWEI CBG root cert load failed!");
    }
}


至此,我们已经在App Server侧完成了对检测结果的验证,验证通过的结果将返回给端侧进行后续业务处理。

2.4 获取系统完整性检测结果

1. 在上一步骤完成后,App就可以从payload中获取可信的系统完整性检测结果。我们在前述的VerifyResultHandler类的回调接口中,解析系统完整性检测结果,示例代码如下:

private static final class VerifyResultHandler implements ICallBack<Boolean> {
    private final String jwsStr;
    private final ICallBack<? super Boolean> callBack;
    private VerifyResultHandler(String jwsStr, ICallBack<? super Boolean> callBack) {
        this.jwsStr = jwsStr;
        this.callBack = callBack;
    }

    @Override
    public void onSuccess(Boolean verified) {
        if (verified) {
            // 服务器侧验证通过,提取系统完整性检测结果 
            String payloadDetail = new String(Base64.decode(jwsStr.split("\\.")[1].getBytes(StandardCharsets.UTF_8), Base64.URL_SAFE), StandardCharsets.UTF_8);
            try {
                final boolean basicIntegrity = new JSONObject(payloadDetail).getBoolean("basicIntegrity");
                // 通过回调返回系统完整性检测结果
                callBack.onSuccess(basicIntegrity);
            } catch (JSONException e) {
                …
            }
        } 
        …
    }
}    


2. 具体的检测报文的样例如下:

{
  "apkCertificateDigestSha256": [
    "osaUtTsdAvezjQBaW3IhN3/fsc6NQ5KwKuAQXcfrxb4="
  ],
  "apkDigestSha256": "vFcmE0uw5s+4tFjXF9rVycxk2xR1rXiZFHuuBFzTVy8=",
  "apkPackageName": "com.example.mockthirdapp",
  "basicIntegrity": false,
  "detail": [
    "root",
    "unlocked"
  ],
  "nonce": "UjJScmEyNGZWbTV4YTJNZw==",
  "timestampMs": 1604048377137,
"advice": "RESTORE_TO_FACTORY_ROM"
}


3)    当检测结果中basicIntegrity字段为false时,表示存在风险,App就可以对用户作风险提示。

官网开发指南,各位小伙伴们可以自行查阅参考。除了系统完整性检测(SysIntegrity),还有其他4个功能的代码,都是支持Java/Kotlin两种开发语言:华为官网的示例代码Java/Kotlin,下载后,根据提示说明进行操作就可运行。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK