Not getting SafetyNet Api Attestation Response

486 views Asked by At

I got an issue with google safetynet api attestation response. Even if i supplied the safetynet client, nonce and the apikey to the "client.AttestAsync()" method it won't return SafetyNetApiAttestationResponse. What could possibly wrong with below code? How to get attestation response to check whether android device is run on emulator or rooted device in xamarin forms?

protected override void OnCreate(Bundle bundle)
{
    Instance = this;

    TabLayoutResource = Resource.Layout.Tabbar;
    ToolbarResource = Resource.Layout.Toolbar;

    BackgroundAggregator.Init(this);
    base.OnCreate(bundle);

    DependencyService.Register<ToastNotification>();
    ToastNotification.Init(this);

    Rg.Plugins.Popup.Popup.Init(this);
    global::Xamarin.Forms.Forms.Init(this, bundle);

    micService = DependencyService.Resolve<IMicrophoneService>();

    if (GoogleApiAvailability.Instance.IsGooglePlayServicesAvailable(this, 13000000) == ConnectionResult.Success)
    {
        SafetyNetClient client = Android.Gms.SafetyNet.SafetyNetClass.GetClient(this);
        byte[] nonce = Android.Gms.SafetyNet.Nonce.Generate();

        bool valid = Task.Run(() => RunSafetyNetCheck(client, nonce)).Result;

        if (!valid)
            LoadApplication(new App());
        else
            Toast.MakeText(this, "Failed response validation with Google!", ToastLength.Short).Show();
    }
    else
    {
        Toast.MakeText(this, "Update Google Play services.!", ToastLength.Short).Show();
    }
}

private async Task<bool> RunSafetyNetCheck(SafetyNetClient client, byte[] nonce)
{
    bool valid = false;
    SafetyNetApiAttestationResponse r = await client.AttestAsync(nonce, apiKey);

    if (r != null && !string.IsNullOrEmpty(r.JwsResult))
    {
        attestationResponse = r;
        var decodedResult = r.DecodeJwsResult(nonce);

        string error = null;
        if (VerifyAttestResponse(decodedResult, nonce, out error))
        {
            valid = await attestationResponse.ValidateWithGoogle(apiKey);
        }
        else
        {
            Toast.MakeText(this, "Compatibility Failed: " + error, ToastLength.Long).Show();
        }
    }
    else
    {
        Toast.MakeText(this, "Failed to Check Compatibility", ToastLength.Long).Show();
    }
    return valid;
}

private bool VerifyAttestResponse(string data, byte[] sentNonce, out string errorMessage)
{
    errorMessage = null;
    var json = JsonObject.Parse(data);

    var error = GetValue(json, "error");
    var nonce = GetValue(json, "nonce");
    var timestampMs = GetValue(json, "timestampMs");
    var apkPackageName = GetValue(json, "apkPackageName");
    var apkCertDigestSha256 = GetValue(json, "apkCertificateDigestSha256");
    var apkDigestSha256 = GetValue(json, "apkDigestSha256");
    var ctsProfileMatch = GetValue(json, "ctsProfileMatch") == "true";

    if (!string.IsNullOrEmpty(error))
    {
        errorMessage = "Response Contained an Error: " + error;
        return false;
    }

    var sentNonceStr = Convert.ToBase64String(sentNonce);
    if (!nonce.Equals(sentNonceStr))
    {
        errorMessage = "Nonce's do no match";
        return false;
    }
    if (PackageName != apkPackageName)
    {
        errorMessage = "Package Names do not match";
        return false;
    }
    if (!ctsProfileMatch)
    {
        errorMessage = "CTS Profile was false";
        return false;
    }
    return true;
}

private string GetValue(JsonValue json, string field)
{
    if (!json.ContainsKey(field))
        return string.Empty;

    return json[field].ToString().Trim('"');
}
1

There are 1 answers

7
Cheesebaron On BEST ANSWER

I made my own sample here and cannot reproduce the behavior you see. You can find the full code in the following repository: https://github.com/Cheesebaron/Xamarin-SafetyNet

The relevant parts are in my Activity I do:

protected override async void OnCreate(Bundle savedInstanceState)
{
    ...

    var attestation = await safetynetHelper.RequestAttestation();
    ctsProfileMatch.Text = $"{attestation.ctsProfileMatch}";
    basicIntegrity.Text = $"{attestation.basicIntegrity}";
}

Inside of my safetynet helper I do the following:

public async Task<(bool ctsProfileMatch, bool basicIntegrity)> RequestAttestation()
{
    SafetyNetClient client = SafetyNetClass.GetClient(context);
    var nonce = Nonce.Generate(24);

    try
    {
        var response = await client.AttestAsync(nonce, attestationApiKey).ConfigureAwait(false);
        var result = response.JwsResult;
        var validSignature = await VerifyAttestationOnline(result).ConfigureAwait(false);
        ...
    }
    catch (Exception)
    {
        // handle errors here
    }

    return (false, false);
}

Notice all calls have .ConfigureAwait(false). We don't need to switch contexts all the time.

This works fine and loads the attestation as expected.