如何从Android应用程序中的标题数据验证Safety Net JWS签名

时间:2016-09-02 07:13:02

标签: java android validation cryptography jwt

我正在使用 SafetyNet API 来检查设备是否已植根 并使用以下有用的代码,但这使用Android验证API 验证JWT签名:

https://github.com/scottyab/safetynethelper

我想在客户端验证只是为了减少所有其他Web服务的开销,而且每天只有10k的请求限制。

因此在解码JWS后我得到以下信息

示例JWS消息响应

xxxx.yyy.zzzz

标题数据

{"alg":"RS256","x5c":["<certificate1 string>","<certificate2 string>"]}

有效载荷数据

{"nonce":"<nounce>",
"timestampMs":1472794339527,
"apkPackageName":"<apkPackageName>",
"apkDigestSha256":"<sha digest string>",
"ctsProfileMatch":true,
"extension":"<extension string>",
"apkCertificateDigestSha256":["<apkCertificateDigestSha256 string>"],"basicIntegrity":true}

签名 在这一部分,如果执行Base64解码,它变得不可读,所以下面是在JWS最后一个元素中收到的签名字符串

Gw09rv1aBbtd4Er7F5ww_3TT1mPRD5YouMkPkwnRXJq8XW_cxlO4428DHTJdD8Tbep-Iv3nrVRWt2t4pH1uSr2kJ9budQJuXqzOUhN93r2Hfk-UAKUYQYhp89_wOWjSCG4ySVHD4jc9S1HrZlngaUosocOmhN4SzLZN5o8BXyBdXkjhWwgArd4bcLhCWJzmxz5iZfkhDiAyeNRq09CeqjRx_plqAy8eR_OaI_2idZBNIGfd2KmLK_CKaeVjDxuC4BzJsIlVRiuLrvP362Wwhz4r1bHh8flmHr88nK99apP2jkQD2l7lPv8y5F3FN3DKhJ15CzHR6ZbiTOw1fUteifg

现在按照谷歌

  

“验证兼容性检查响应:提取SSL证书   来自JWS消息的链。验证SSL证书链并使用   SSL主机名匹配以验证是否已颁发叶证书   到主机名attest.android.com。使用证书验证   JWS消息的签名。“

我确实有cert字符串和签名我该如何验证SSL证书,这是第二个证书上的字符串和主机名匹配 如何验证签名。

我需要关于此的指针,并且代码剪切将非常有用。

3 个答案:

答案 0 :(得分:2)

您希望在设备上验证JWT签名的方式并不安全。想想下一个案例:

  • 设备已植根,具有root权限的恶意软件应用程序    将您的请求发送到Google的SafetyNet并返回自签名    响应。

  • 当您使用自己的服务器服务验证响应时,您会得到Google未提供的回复。如果您在设备上本地执行此操作 - 相同的恶意软件应用程序可能会抓住您验证JWT签名并使用true进行回复的请求。

无论如何,你可以在本地做到这一点:

  1. 您需要从Google开发人员那里获取适合您应用的API密钥。
  2. 使用Android设备验证API:
  3. 来自Android Developers

      

    注意:验证响应消息的API方法具有每个项目每天10,000个请求的固定速率限制。您应该仅在初始开发阶段使用verify()方法进行测试。 您不应该在生产方案中调用该方法

         

    [...]

         

    要使用Android设备验证API:

         

    创建包含JWS全部内容的JSON消息   消息采用以下格式:

    { "signedAttestation": "<output of> getJwsResult()>" }
    
         

    使用HTTP POST请求发送带有的消息   以下网址的内容类型"application/json":   https://www.googleapis.com/androidcheck/v1/attestations/verify?key= <your API key>

         

    该服务验证消息的完整性,如果   消息有效,它返回带有以下内容的JSON消息   内容:{ “isValidSignature”: true }

    实际上(来自SafetyNet Helper的代码):

    /**
     *
     * Validates the result with Android Device Verification API.
     *
     * Note: This only validates that the provided JWS (JSON Web Signature) message was received from the actual SafetyNet service.
     * It does *not* verify that the payload data matches your original compatibility check request.
     * POST to https://www.googleapis.com/androidcheck/v1/attestations/verify?key=<your API key>
     *
     * More info see {link https://developer.android.com/google/play/safetynet/start.html#verify-compat-check}
     *
     * Created by scottab on 27/05/2015.
     */
    public class AndroidDeviceVerifier {
    
        private static final String TAG = AndroidDeviceVerifier.class.getSimpleName();
    
        //used to verifiy the safety net response - 10,000 requests/day free
        private static final String GOOGLE_VERIFICATION_URL = "https://www.googleapis.com/androidcheck/v1/attestations/verify?key=";
    
        private final String apiKey;
        private final String signatureToVerify;
        private AndroidDeviceVerifierCallback callback;
    
        public interface AndroidDeviceVerifierCallback{
            void error(String s);
            void success(boolean isValidSignature);
        }
    
        public AndroidDeviceVerifier(@NonNull String apiKey, @NonNull String signatureToVerify) {
            this.apiKey = apiKey;
            this.signatureToVerify = signatureToVerify;
        }
    
        public void verify(AndroidDeviceVerifierCallback androidDeviceVerifierCallback){
            callback = androidDeviceVerifierCallback;
            AndroidDeviceVerifierTask task = new AndroidDeviceVerifierTask();
            task.execute();
        }
    
        /**
         * Provide the trust managers for the URL connection. By Default this uses the system defaults plus the GoogleApisTrustManager (SSL pinning)
         * @return array of TrustManager including system defaults plus the GoogleApisTrustManager (SSL pinning)
         * @throws KeyStoreException
         * @throws NoSuchAlgorithmException
         */
        protected TrustManager[] getTrustManagers() throws KeyStoreException, NoSuchAlgorithmException {
            TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
            //init with the default system trustmanagers
            trustManagerFactory.init((KeyStore)null);
            TrustManager[] defaultTrustManagers = trustManagerFactory.getTrustManagers();
            TrustManager[] trustManagers = Arrays.copyOf(defaultTrustManagers, defaultTrustManagers.length + 1);
            //add our Google APIs pinning TrustManager for extra security
            trustManagers[defaultTrustManagers.length] = new GoogleApisTrustManager();
            return trustManagers;
        }
    
    
    
        private class AndroidDeviceVerifierTask extends AsyncTask<Void, Void, Boolean>{
    
            private Exception error;
    
            @Override
            protected Boolean doInBackground(Void... params) {
    
                //Log.d(TAG, "signatureToVerify:" + signatureToVerify);
    
                try {
                    URL verifyApiUrl = new URL(GOOGLE_VERIFICATION_URL + apiKey);
    
                    SSLContext sslContext = SSLContext.getInstance("TLS");
                    sslContext.init(null, getTrustManagers(), null);
    
                    HttpsURLConnection urlConnection = (HttpsURLConnection) verifyApiUrl.openConnection();
                    urlConnection.setSSLSocketFactory(sslContext.getSocketFactory());
    
                    urlConnection.setRequestMethod("POST");
                    urlConnection.setRequestProperty("Content-Type", "application/json");
    
                    //build post body { "signedAttestation": "<output of getJwsResult()>" }
                    String requestJsonBody = "{ \"signedAttestation\": \""+signatureToVerify+"\"}";
                    byte[] outputInBytes = requestJsonBody.getBytes("UTF-8");
                    OutputStream os = urlConnection.getOutputStream();
                    os.write(outputInBytes);
                    os.close();
    
                    urlConnection.connect();
    
                    //resp ={ “isValidSignature”: true }
                    InputStream is = urlConnection.getInputStream();
                    StringBuilder sb = new StringBuilder();
                    BufferedReader rd = new BufferedReader(new InputStreamReader(is));
                    String line;
                    while ((line = rd.readLine()) != null) {
                        sb.append(line);
                    }
                    String response = sb.toString();
                    JSONObject responseRoot = new JSONObject(response);
                    if(responseRoot.has("isValidSignature")){
                        return responseRoot.getBoolean("isValidSignature");
                    }
                }catch (Exception e){
                    //something went wrong requesting validation of the JWS Message
                    error = e;
                    Log.e(TAG, "problem validating JWS Message :" + e.getMessage(), e);
                    return false;
                }
                return false;
            }
    
            @Override
            protected void onPostExecute(Boolean aBoolean) {
                if(error!=null){
                    callback.error(error.getMessage());
                }else {
                    callback.success(aBoolean);
                }
            }
        }
    
    }
    

答案 1 :(得分:0)

Android开发者博客文章10 things you might be doing wrong when using the SafetyNet Attestation API上的第5项说:

  

使用测试认证验证服务进行生产   为了简化SafetyNet Attestation API的开发和测试,Google提供了一个在线验证服务,该服务使用简单的HTTPS请求检查SafetyNet证明结果的数字签名。

     

尽管看起来很有用,但它仅用于测试目的,并且具有非常严格的使用配额,根据要求不会增加。相反,您应该以不依赖于Google服务器的方式在服务器上实施数字签名验证逻辑。大多数JWT库提供签名验证功能,我们有代码示例,展示如何在Java和C#中执行此验证。我们计划在未来为更多语言提供样本。

答案 2 :(得分:-1)

有一个开源的android库,能够帮助执行验证:jwtk/jjwt