Skip to content

Instantly share code, notes, and snippets.

@aperfect
Created May 26, 2023 09:14
Show Gist options
  • Save aperfect/866a433d7494b8a5e67a0975388e2cf4 to your computer and use it in GitHub Desktop.
Save aperfect/866a433d7494b8a5e67a0975388e2cf4 to your computer and use it in GitHub Desktop.
CloudKit server-to-server example in Ruby
#!/usr/bin/env ruby
# CloudKit public database connection with server-to-server
# See https://developer.apple.com/library/archive/documentation/DataManagement/Conceptual/CloudKitWebServicesReference/SettingUpWebServices.html#//apple_ref/doc/uid/TP40015240-CH24-SW6
require 'time'
require 'openssl'
require 'digest'
require 'json'
require 'base64'
require 'uri'
require 'net/http'
# class Cloudkit
CK_PEM_STRING = """
-----BEGIN EC PRIVATE KEY-----
YOUR PRIVATE KEY
-----END EC PRIVATE KEY-----
"""
CK_SERVICE_BASE = "https://api.apple-cloudkit.com"
CK_KEY_ID = "YOUR_CLOUDKIT_KEY_ID"
def signature_for_request(body_json, url, iso8601_date)
puts "body_json: #{body_json}"
body_sha_hash = Digest::SHA256.digest body_json
payload_for_signature = [iso8601_date, Base64.strict_encode64(body_sha_hash), url].join(':')
puts "payload_for_signature: #{payload_for_signature}"
OpenSSL::PKey::EC.send(:alias_method, :private?, :private_key?)
ec = OpenSSL::PKey::EC.new(CK_PEM_STRING)
puts "ec: #{ec}"
digest = OpenSSL::Digest::SHA256.new
puts "digest: #{digest}"
signature = ec.sign(digest, payload_for_signature)
puts "signature: #{signature}"
base64_signature = Base64.strict_encode64(signature)
puts "base64_signature: #{base64_signature}"
return base64_signature
end
def perform_request_for(body, url, iso8601_date)
body = body.to_s
signature = self.signature_for_request(body, url, iso8601_date)
uri = URI.parse(CK_SERVICE_BASE + url)
header = {
"Content-Type": "text/plain",
"X-Apple-CloudKit-Request-KeyID": CK_KEY_ID,
"X-Apple-CloudKit-Request-ISO8601Date": iso8601_date,
"X-Apple-CloudKit-Request-SignatureV1": signature
}
# Create HTTP objects
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = Net::HTTP::Post.new(uri.request_uri, header)
request.body = body
# Send the request
response = http.request(request)
return response
end
# end
t = Time.now.utc.iso8601
# The path to the endpoint you want to query
# YOUR_CONTAINER_NAME = e.g. iCloud.com.your_app
# Swap development for production as needed
url = "/database/1/YOUR_CONTAINER_NAME/development/public/records/query"
body = """
{
\"zoneID\": {
\"zoneName\": \"_defaultZone\"
},
\"query\": {
\"recordType\": \"CD_YourRecordType\"
}
}
"""
puts perform_request_for(body, url, t).body
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment