如何针对 iCloud 验证 iCloud ID 令牌?
Posted
技术标签:
【中文标题】如何针对 iCloud 验证 iCloud ID 令牌?【英文标题】:how to verify an iCloud ID token against iCloud? 【发布时间】:2017-10-18 23:58:55 【问题描述】:我是just reading,关于在移动设备上使用 iCloud ID 令牌进行应用识别。
如果我的服务器通过 Internet 接收到带有 iCloud ID 令牌的请求,它有没有办法验证它是由 Apple 发出的,而不是由发送方编造的?
【问题讨论】:
【参考方案1】:查看Device Check Framework.“访问每个设备、每个开发人员的数据,您的关联服务器可以在其业务逻辑中使用这些数据。”这是在最近对this SO thread 的答案的评论中提出的。
这是如何使用带有 iCloud 用户 ID 哈希的设备检查来确保对您的 API 的请求是合法的。以下很多代码都改编自this。
在您的 ios 应用中从 Apple 获取一个临时的 Device Check 令牌,然后将其与您的请求以及 iCloud 用户名哈希一起发送到您的后端。
在 Swift 4 中:
import DeviceCheck
let currDevice = DCDevice.current
if ViewController.currDevice.isSupported
ViewController.currDevice.generateToken (data, error) in
if let data = data
let url = "your-url"
let sesh = URLSession(configuration: .default)
var req = URLRequest(url: url)
req.addValue("application/json", forHTTPHeaderField: "Content-Type")
req.httpMethod = "POST"
DispatchQueue.main.sync
var jsonObj = [
"deviceCheckToken" : data.base64EncodedString(),
"iCloudUserNameHash": self.iCloudUserID,
"moreParams": "moreParamsHere"
]
let data = try! JSONSerialization.data(withJSONObject: jsonObj, options: [])
req.httpBody = data
let task = sesh.dataTask(with: req, completionHandler: (data, response, error) in
if let data = data, let jsonData = try? JSONSerialization.jsonObject(with: data, options: .mutableContainers), let jsonDictionary = jsonData as? [String: Any]
DispatchQueue.main.async
// Process response here
)
task.resume()
else if let error = error
print("Error when generating a token:", error.localizedDescription)
else
print("Platform is not supported. Make sure you aren't running in an emulator.")
您可以在设备检查框架中为每个设备的每个应用存储两个位。使用 bit0 记住您已经向当前设备提供了请求。首先调用 Device Check 验证端点以查看请求是否来自 iOS 应用程序——而不是例如某人的终端。接下来,使用 Device Check 查询端点获取当前设备的两个 Device Check 位。如果 bit0 为真,则假设此设备在您的请求表中已经至少有一行键入了给定的 iCloud 用户名哈希。如果有这样的一行,这可能是一个合法的请求,因为很难猜出其他键。如果没有这样的行,用户可能生成了一个虚假的 iCloud 用户哈希。但是,如果 bit0 为 false,则该设备尚未在 requests 表中放入一行。在给定的 iCloud 用户名哈希上键入一行,并使用 Device Check 更新端点将此设备的 bit0 设置为 true。这是 AWS Lambda 中节点 8.10 中的一个示例,其中 requests 表位于 DynamoDB 中。
endpoint.js
const AWS = require('aws-sdk');
const utf8 = require('utf8');
const asyncAWS = require('./lib/awsPromiseWrappers');
const deviceCheck = require('./lib/deviceCheck');
const util = require('./lib/util');
// AWS globals
const lambda = new AWS.Lambda(
region: process.env.AWS_REGION,
);
const dynamodb = new AWS.DynamoDB.DocumentClient();
// Apple Device Check keys
const cert = utf8.encode([
process.env.BEGIN_PRIVATE_KEY,
process.env.APPLE_DEVICE_CHECK_CERT,
process.env.END_PRIVATE_KEY,
].join('\n')); // utf8 encoding and newlines are necessary for jwt to do job
const keyId = process.env.APPLE_DEVICE_CHECK_KEY_ID;
const teamId = process.env.APPLE_ITUNES_CONNECT_TEAM_ID;
// Return true if device check succeeds
const isLegitDevice = async (deviceCheckToken, iCloudUserNameHash) =>
// Pick the correct (dev or prod) Device Check API URL
var deviceCheckHost;
if (process.env.STAGE === 'dev')
deviceCheckHost = process.env.DEV_DEVICE_CHECK_API_URL;
else if (stage === 'prod')
deviceCheckHost = process.env.PROD_DEVICE_CHECK_API_URL;
else
util.cloudwatchLog(`--> Unrecognized stage $stage. Aborting DC`);
return;
// Make sure device is valid. If not, return false
try
await deviceCheck.validateDevice(
cert, keyId, teamId, deviceCheckToken, deviceCheckHost);
catch (err)
util.cloudwatchLog(`--> DC validation failed. $err`);
return false;
// Query for Device Check bits
var dcQueryResults;
try
dcQueryResults = await deviceCheck.queryTwoBits(
cert, keyId, teamId, deviceCheckToken, deviceCheckHost);
catch (err)
dcQueryResults = null;
// If bit0 is true, then this device already has at least one row in the
// search counts table
if (dcQueryResults && dcQueryResults.bit0)
// Try to get the counts row keyed on given user name
const getParams =
TableName: process.env.SEARCH_COUNTS_TABLE,
Key: u: iCloudUserNameHash ,
;
var countsRow;
try
countsRow = await asyncAWS.invokeDynamoDBGet(dynamodb, getParams);
catch (err)
const msg = `--> Couldn't get counts row during DC call: $err`;
util.cloudwatchLog(msg);
return false;
// If it doesn't exist, return false
if (!countsRow)
return false;
else // if it DOES exist, this is a legit request
return true;
else
// Initialize the row in memory
const secsSinceEpoch = (new Date()).getTime() / 1000;
const countsRow =
h: [0, secsSinceEpoch],
d: [0, secsSinceEpoch],
w: [0, secsSinceEpoch],
m: [0, secsSinceEpoch],
y: [0, secsSinceEpoch],
a: 0,
u: iCloudUserNameHash,
;
// Put it in the search counts table
const putParams =
Item: countsRow,
TableName: process.env.SEARCH_COUNTS_TABLE,
;
try
await asyncAWS.invokeDynamoDBPut(dynamodb, putParams);
catch (err)
const msg = `--> Couldn't set counts row in DC call: $err`
util.cloudwatchLog(msg);
return false;
// Set the device check bit
try
await deviceCheck.updateTwoBits(true, false,
cert, keyId, teamId, deviceCheckToken, deviceCheckHost);
catch (err)
const msg = `--> DC update failed. $iCloudUserNameHash $err`;
util.cloudwatchLog(msg);
return false;
// If we got here, the request was legit
return true;
;
exports.main = async (event, context, callback) =>
// Handle inputs
const body = JSON.parse(event.body);
const iCloudUserNameHash = body.iCloudUserNameHash;
const deviceCheckToken = body.deviceCheckToken;
const otherParams = body.otherParams;
// If allowed to search, increment search counts then search
var deviceCheckSucceeded;
try
deviceCheckSucceeded =
await isLegitDevice(deviceCheckToken, iCloudUserNameHash);
catch (err)
util.cloudwatchLog(`--> Error checking device: $err`);
return callback(null, resp.failure());
if (deviceCheckSucceeded)
// Do your stuff here
return callback(null, resp.success());
else
return callback(null, resp.failure());
;
deviceCheck.js
const https = require('https');
const jwt = require('jsonwebtoken');
const uuidv4 = require('uuid/v4');
const util = require('../lib/util');
// Set the two Device Check bits for this device.
// Params:
// bit0 (boolean) - true if never seen given iCloud user ID
// bit1 (boolean) - TODO not used yet
// cert (string) - Device Check certificate. Get from developer.apple.com)
// keyId (string) - Part of metadata for Device Check certificate)
// teamId (string) - My developer team ID. Can be found in iTunes Connect
// dcToken (string) - Ephemeral Device Check token passed from frontend
// deviceCheckHost (string) - API URL, which is either for dev or prod env
const updateTwoBits = async (
bit0, bit1, cert, keyId, teamId, dcToken, deviceCheckHost) =>
return new Promise((resolve, reject) =>
var jwToken = jwt.sign(, cert,
algorithm: 'ES256',
keyid: keyId,
issuer: teamId,
);
var postData =
'device_token' : dcToken,
'transaction_id': uuidv4(),
'timestamp': Date.now(),
'bit0': bit0,
'bit1': bit1,
var postOptions =
host: deviceCheckHost,
port: '443',
path: '/v1/update_two_bits',
method: 'POST',
headers:
'Authorization': 'Bearer ' + jwToken,
,
;
var postReq = https.request(postOptions, function(res)
res.setEncoding('utf8');
var data = '';
res.on('data', function (chunk)
data += chunk;
);
res.on('end', function()
util.cloudwatchLog(
`--> Update bits done with status code $res.statusCode`);
resolve();
);
res.on('error', function(data)
util.cloudwatchLog(
`--> Error $res.statusCode in update bits: $data`);
reject();
);
);
postReq.write(new Buffer.from(JSON.stringify(postData)));
postReq.end();
);
;
// Query the two Device Check bits for this device.
// Params:
// cert (string) - Device Check certificate. Get from developer.apple.com)
// keyId (string) - Part of metadata for Device Check certificate)
// teamId (string) - My developer team ID. Can be found in iTunes Connect
// dcToken (string) - Ephemeral Device Check token passed from frontend
// deviceCheckHost (string) - API URL, which is either for dev or prod env
// Return:
// bit0 (boolean), bit1 (boolean), lastUpdated (String)
const queryTwoBits = async (cert, keyId, teamId, dcToken, deviceCheckHost) =>
return new Promise((resolve, reject) =>
var jwToken = jwt.sign(, cert,
algorithm: 'ES256',
keyid: keyId,
issuer: teamId,
);
var postData =
'device_token' : dcToken,
'transaction_id': uuidv4(),
'timestamp': Date.now(),
var postOptions =
host: deviceCheckHost,
port: '443',
path: '/v1/query_two_bits',
method: 'POST',
headers:
'Authorization': 'Bearer ' + jwToken,
,
;
var postReq = https.request(postOptions, function(res)
res.setEncoding('utf8');
var data = '';
res.on('data', function (chunk)
data += chunk;
);
res.on('end', function()
try
var json = JSON.parse(data);
resolve(
bit0: json.bit0,
bit1: json.bit1,
lastUpdated: json.last_update_time,
);
catch (e)
const rc = res.statusCode;
util.cloudwatchLog(
`--> DC query call failed. $e, $data, $rc`);
reject();
);
res.on('error', function(data)
const code = res.statusCode;
util.cloudwatchLog(
`--> Error $code with query bits call: $data`);
reject();
);
);
postReq.write(new Buffer.from(JSON.stringify(postData)));
postReq.end();
);
;
// Make sure devie is valid.
// Params:
// cert (string) - Device Check certificate. Get from developer.apple.com)
// keyId (string) - Part of metadata for Device Check certificate)
// teamId (string) - My developer team ID. Can be found in iTunes Connect
// dcToken (string) - Ephemeral Device Check token passed from frontend
// deviceCheckHost (string) - API URL, which is either for dev or prod env
const validateDevice = async (
cert, keyId, teamId, dcToken, deviceCheckHost) =>
return new Promise((resolve, reject) =>
var jwToken = jwt.sign(, cert,
algorithm: 'ES256',
keyid: keyId,
issuer: teamId,
);
var postData =
'device_token' : dcToken,
'transaction_id': uuidv4(),
'timestamp': Date.now(),
var postOptions =
host: deviceCheckHost,
port: '443',
path: '/v1/validate_device_token',
method: 'POST',
headers:
'Authorization': 'Bearer ' + jwToken,
,
;
var postReq = https.request(postOptions, function(res)
res.setEncoding('utf8');
var data = '';
res.on('data', function (chunk)
data += chunk;
);
res.on('end', function()
util.cloudwatchLog(
`--> DC validation done w/ status code $res.statusCode`);
if (res.statusCode === 200)
resolve();
else
reject();
);
res.on('error', function(data)
util.cloudwatchLog(
`--> Error $res.statusCode in DC validate: $data`);
reject();
);
);
postReq.write(new Buffer.from(JSON.stringify(postData)));
postReq.end();
);
;
exports.updateTwoBits = updateTwoBits;
exports.queryTwoBits = queryTwoBits;
exports.validateDevice = validateDevice;
【讨论】:
以上是关于如何针对 iCloud 验证 iCloud ID 令牌?的主要内容,如果未能解决你的问题,请参考以下文章