Skip to content

Instantly share code, notes, and snippets.

@spllr
Last active August 28, 2023 12:04
Show Gist options
  • Save spllr/4bf3fadb7f6168f67698 to your computer and use it in GitHub Desktop.
Save spllr/4bf3fadb7f6168f67698 to your computer and use it in GitHub Desktop.
Setting up an authenticated CloudKit server-to-server request
/**
* Demonstrates how to use Apple's CloudKit server-to-server authentication
*
* Create private key with: `openssl ecparam -name prime256v1 -genkey -noout -out eckey.pem`
* Generate the public key to register at the CloudKit dashboard: `openssl ec -in eckey.pem -pubout`
*
* @see https://developer.apple.com/library/prerelease/ios/documentation/DataManagement/Conceptual/CloutKitWebServicesReference/SettingUpWebServices/SettingUpWebServices.html#//apple_ref/doc/uid/TP40015240-CH24-SW6
*
* @author @spllr
*/
var crypto = require("crypto"),
https = require("https"),
fs = require("fs")
var CloudKitRequest = function(payload) {
this.payload = payload
this.requestOptions = { // Used with `https.request`
hostname: "api.apple-cloudkit.com",
port: 443,
path: "/database/1/iCloud.your.container.id/development/public/records/modify", // Update container ID
method: "POST",
headers: { // We will add more headers in the sign methods
"X-Apple-CloudKit-Request-KeyID": "your-cloudkit-request-key-id"
}
}
}
CloudKitRequest.prototype.sign = function(privateKey) {
var dateString = new Date().toISOString().replace(/\.[0-9]+?Z/, "Z"), // NOTE: No milliseconds
hash = crypto.createHash("sha256"),
sign = crypto.createSign("RSA-SHA256")
// Create the hash of the payload
hash.update(this.payload, "utf8")
var payloadHash = hash.digest("base64")
// Create the signature string to sign
var signatureData = [
dateString,
payloadHash,
this.requestOptions.path
].join(":") // [Date]:[Request body]:[Web Service URL]
// Construct the signature
sign.update(signatureData)
var signature = sign.sign(privateKey, "base64")
// Update the request headers
this.requestOptions.headers["X-Apple-CloudKit-Request-ISO8601Date"] = dateString
this.requestOptions.headers["X-Apple-CloudKit-Request-SignatureV1"] = signature
return signature // This might be useful to keep around
}
CloudKitRequest.prototype.send = function(cb) {
var request = https.request(this.requestOptions, function(response) {
var responseBody = ""
response.on("data", function(chunk) {
responseBody += chunk.toString("utf8")
})
response.on("end", function() {
cb(null, JSON.parse(responseBody))
})
})
request.on("error", function(err) {
cb(err, null)
})
request.end(this.payload)
}
var privateKey = fs.readFileSync("./cloudkit/eckey.pem"),
creationPayload = JSON.stringify({
"operations": [{
"operationType" : "create",
"record" : {
"recordType" : "Post",
"fields" : {
"title" : { "value" : "A Post From The Server" }
}
}
}]
})
var creationRequest = new CloudKitRequest(creationPayload)
creationRequest.sign(privateKey)
creationRequest.send(function(err, response) {
console.log("Created a new entry with error", err, "and respone", response)
})
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment