iOS: DeviceCheck reference and save are implemented in Java. (Account Ban, measures to prevent resemara, etc.)

What you can do with iOS device check.

--Based on the device token that can be obtained from the terminal, two bit values can be recorded and referenced in Apple's API for each terminal. --The saved bit value is not reset even if the terminal is initialized and the application is uninstalled and installed.

What can it be used for?

Prevention of lycemala.

-(At the time of initial installation, you can determine that it has been reinstalled by recording it in the device check API.)

Account ban users who commit serious fraud on a terminal-by-terminal basis.

--Record that the terminal is account banned by the API of device check. --Even after reinstalling the app and initializing the device, make it unavailable if the device was previously banned.

Precautions when using

--Even if the terminal is passed to another person in the second-hand market, etc., it is a value that is retained, so careful operation is required. --It seems that the device token obtained from the terminal has a time expiration date. (I'm not sure about the memory of this part, so please test it.) If you use it with account ban, send a device token to the server side when the next application starts after ban, and immediately there It is necessary to devise such as updating with API.

Advance preparation, premise

--Obtain the p8 file of the authentication key. --Get KEY_ID, TEAM_ID --Obtain DeviceToken on the terminal and pass it to the server side.

Reference article

--About Device Check added in iOS 11 - https://qiita.com/owen/items/85dff1e45083d2805140

--Apple Official: - https://developer.apple.com/documentation/devicecheck/accessing_and_modifying_per-device_data

DeviceCheck API URL

base API

development of

https://api.development.devicecheck.apple.com/v1/

During production operation

https://api.devicecheck.apple.com/v1/

API features

See also: query_two_bits

https://api.devicecheck.apple.com/v1/query_two_bits

Update: update_two_bits

https://api.devicecheck.apple.com/v1/update_two_bits

Device token validation: validate_device_token

https://api.devicecheck.apple.com/v1/validate_device_token

program

Get the device token with the iOS app.

import DeviceCheck

DCDevice.current.generateToken {
    (data, error) in
    guard let data = data else {
        return
    }

    let token = data.base64EncodedString()
    print(token)
}

Server side (Java)

Get and update sample

//Get
private void queryIOSDeviceCheckSample() {
    //With this value you can get two bool values and the last modified date.
    Response response = postRequest(DEVELOPMENT_BASE_API_URL + "query_two_bits", "Device token obtained from the terminal", null, null);
}

//update
private void updateIOSDeviceCheckSample(Boolean bit0, Boolean bit1) {
    Response response = postRequest(DEVELOPMENT_BASE_API_URL + "update_two_bits", "Device token obtained from the terminal", null, null);
}

API call processing

--Set the JWT token in the "Authorization" header. --Set each parameter in json format in request body. - device_token, transaction_id, timestamp, bit0, bit1

private static Response postRequest(String url, String deviceToken, Boolean bit0, Boolean bit1) throws IOException {
    MediaType JSON = MediaType.get("application/json; charset=utf-8");

    JSONObject jsonObject = new JSONObject();
    jsonObject.put("device_token", deviceToken);
    jsonObject.put("transaction_id", UUID.randomUUID().toString());
    jsonObject.put("timestamp", new Date().getTime());
    if (bit0 != null) {
        jsonObject.put("bit0", bit0);
    }
    if (bit1 != null) {
        jsonObject.put("bit1", bit1);
    }
    String json = jsonObject.toJSONString();

    RequestBody body = RequestBody.create(JSON, json);

    String jwt = getJWTStr();
    if (jwt == null) {
        return null;
    }
    Request request = new Request.Builder()
            .url(url)
            .header("Content-Type", "application/x-www-form-urlencoded")
            .header("Content-Length", String.valueOf(json.length()))
            .header("Authorization", "Bearer " + jwt)
            .post(body)
            .build();
    OkHttpClient client = new OkHttpClient();
    return client.newCall(request).execute();
}

Get the JWT token from the p8 file.

Implemented with reference to the source of the push notification library "pushy": JWT token acquisition process

--There is a library for iOS push notifications called pushy, There is a process to get a JWT token from a p8 file. --Implemented with reference to that part. https://github.com/jchambers/pushy/blob/master/pushy/src/main/java/com/eatthepath/pushy/apns/auth/ApnsSigningKey.java#L124-L170

JTW(=JSON Web Token) https://ja.wikipedia.org/wiki/JSON_Web_Token

private static String getJWTStr() {
    try {
        ECPrivateKey privateKey = getECPrivateKey(P8_SECRET_KEY_PATH);
        return Jwts.builder()
            .setHeaderParam("kid", KEY_ID)
            .setIssuer(TEAM_ID)
            .setIssuedAt(new Date())
            .signWith(privateKey, SignatureAlgorithm.ES256)
            .compact();
    } catch (Exception e) {
        return null;
    }
}

private static ECPrivateKey getECPrivateKey(String p8FilePath) throws Exception {

    final FileInputStream fileInputStream = new FileInputStream(new File(p8FilePath));
    final ECPrivateKey signingKey;
    {
        final String base64EncodedPrivateKey;
        {
            final StringBuilder privateKeyBuilder = new StringBuilder();

            final BufferedReader reader = new BufferedReader(new InputStreamReader(fileInputStream));
            boolean haveReadHeader = false;
            boolean haveReadFooter = false;

            for (String line; (line = reader.readLine()) != null; ) {
                if (!haveReadHeader) {
                    if (line.contains("BEGIN PRIVATE KEY")) {
                        haveReadHeader = true;
                    }
                } else {
                    if (line.contains("END PRIVATE KEY")) {
                        haveReadFooter = true;
                        break;
                    } else {
                        privateKeyBuilder.append(line);
                    }
                }
            }

            if (!(haveReadHeader && haveReadFooter)) {
                throw new IOException("Could not find private key header/footer");
            }

            base64EncodedPrivateKey = privateKeyBuilder.toString();
        }

        final byte[] keyBytes = Base64.getDecoder().decode(base64EncodedPrivateKey.getBytes(StandardCharsets.US_ASCII));

        final PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes);
        final KeyFactory keyFactory = KeyFactory.getInstance("EC");

        try {
            signingKey = (ECPrivateKey) keyFactory.generatePrivate(keySpec);
        } catch (InvalidKeySpecException e) {
            throw new InvalidKeyException(e);
        }
    }
    return signingKey;
}

Recommended Posts

iOS: DeviceCheck reference and save are implemented in Java. (Account Ban, measures to prevent resemara, etc.)
Java to C and C to Java in Android Studio