CloudKit Server-to-Server authentication

后端 未结 6 1823
慢半拍i
慢半拍i 2020-12-07 19:11

Apple published a new method to authenticate against CloudKit, server-to-server. https://developer.apple.com/library/content/documentation/DataManagement/Conceptual/CloudKit

6条回答
  •  無奈伤痛
    2020-12-07 19:46

    Extracting Apple's cloudkit.js implementation and using the first call from the Apple sample code node-client-s2s/index.js you can construct the following:

    You hash the request body request with sha256:

    var crypto = require('crypto');
    var bodyHasher = crypto.createHash('sha256');
    bodyHasher.update(requestBody);
    var hashedBody = bodyHasher.digest("base64");
    

    The sign the [Current date]:[Request body]:[Web Service URL] payload with the private key provided in the config.

    var c = crypto.createSign("RSA-SHA256");
    c.update(rawPayload);
    var requestSignature = c.sign(key, "base64");
    

    Another note is the [Web Service URL] payload component must not include the domain but it does need any query parameters.

    Make sure the date value is the same in X-Apple-CloudKit-Request-ISO8601Date as it is in the signature. (These details are not documented completely, but is observed by looking through the CloudKit.js implementation).

    A more complete nodejs example looks like this:

    (function() {
    
    const https = require('https');
    var fs = require('fs');
    var crypto = require('crypto');
    
    var key = fs.readFileSync(__dirname + '/eckey.pem', "utf8");
    var authKeyID = 'auth-key-id';
    
    // path of our request (domain not included)
    var requestPath = "/database/1/iCloud.containerIdentifier/development/public/users/current";
    
    // request body (GET request is blank)
    var requestBody = '';
    
    // date string without milliseconds
    var requestDate = (new Date).toISOString().replace(/(\.\d\d\d)Z/, "Z");
    
    var bodyHasher = crypto.createHash('sha256');
    bodyHasher.update(requestBody);
    var hashedBody = bodyHasher.digest("base64");
    
    var rawPayload = requestDate + ":" + hashedBody + ":" + requestPath;
    
    // sign payload
    var c = crypto.createSign("sha256");
    c.update(rawPayload);
    var requestSignature = c.sign(key, "base64");
    
    // put headers together
    var headers = {
        'X-Apple-CloudKit-Request-KeyID': authKeyID,
        'X-Apple-CloudKit-Request-ISO8601Date': requestDate,
        'X-Apple-CloudKit-Request-SignatureV1': requestSignature
    };
    
    var options = {
        hostname: 'api.apple-cloudkit.com',
        port: 443,
        path: requestPath,
        method: 'GET',
        headers: headers
    };
    
    var req = https.request(options, (res) => {
       //... handle nodejs response
    });
    
    req.end();
    
    })();
    

    This also exists as a gist: https://gist.github.com/jessedc/a3161186b450317a9cb5

    On the command line with openssl (Updated)

    The first hashing can be done with this command:

    openssl sha -sha256 -binary < body.txt | base64
    

    To sign the second part of the request you need a more modern version of openSSL than what OSX 10.11 comes with and use the following command:

    /usr/local/bin/openssl dgst -sha256WithRSAEncryption -binary -sign ck-server-key.pem raw_signature.txt | base64
    

    Thanks to @maurice_vB below and on twitter for this info

提交回复
热议问题