Last active
August 17, 2023 22:05
-
-
Save ggsubs/fbbc5ae8b02fda73a7c93a681a6381b7 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package testclient.cloudkit; | |
import org.apache.http.HttpEntity; | |
import org.apache.http.client.ClientProtocolException; | |
import org.apache.http.client.methods.CloseableHttpResponse; | |
import org.apache.http.client.methods.HttpPost; | |
import org.apache.http.entity.StringEntity; | |
import org.apache.http.impl.client.CloseableHttpClient; | |
import org.apache.http.impl.client.HttpClients; | |
import org.bouncycastle.crypto.digests.SHA256Digest; | |
import org.bouncycastle.openssl.PEMKeyPair; | |
import org.bouncycastle.openssl.PEMParser; | |
import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter; | |
import java.io.IOException; | |
import java.io.StringReader; | |
import java.io.UnsupportedEncodingException; | |
import java.security.*; | |
import java.text.DateFormat; | |
import java.text.SimpleDateFormat; | |
import java.util.Base64; | |
import java.util.Date; | |
import java.util.TimeZone; | |
public class CloudKitRequest { | |
private String containerId; | |
private String keyId; | |
private String environment; | |
private PrivateKey privateKey; | |
private final String host = "api.apple-cloudkit.com"; | |
private String cloudKitResource; | |
private String payload; | |
private String operation; | |
private String version = "1"; | |
public CloudKitRequest(String containerId, String keyId, String environment, String privateKeyText) { | |
this.containerId = containerId; | |
this.keyId = keyId; | |
this.environment = environment; | |
this.privateKey = loadPrivateKey(privateKeyText); | |
} | |
public CloudKitRequest setResource(String cloudKitResource) { | |
this.cloudKitResource = cloudKitResource; | |
return this; | |
} | |
public CloudKitRequest setEntity(String payload) { | |
this.payload = payload; | |
return this; | |
} | |
public CloudKitRequest setOperation(String operation) { | |
this.operation = operation; | |
return this; | |
} | |
public CloseableHttpResponse execute() { | |
String dateString = getIsoDate(); | |
String requestUrl = "/database/" + version + "/" + containerId + "/" + environment + cloudKitResource; | |
String concat = dateString+":"+hashRequestBody(payload)+":"+requestUrl; | |
CloseableHttpClient httpclient = HttpClients.createDefault(); | |
final HttpPost httpPostRequest = new HttpPost("https://" + host + requestUrl); | |
httpPostRequest.setHeader("X-Apple-CloudKit-Request-KeyID", keyId); | |
httpPostRequest.setHeader("X-Apple-CloudKit-Request-ISO8601Date", dateString); | |
httpPostRequest.setHeader("X-Apple-CloudKit-Request-SignatureV1", signRequest(concat)); | |
httpPostRequest.setHeader("Content-Type", "text/plain"); | |
try { | |
httpPostRequest.setEntity( new StringEntity(payload)); | |
return httpclient.execute(httpPostRequest); | |
} catch (UnsupportedEncodingException e) { | |
throw new RuntimeException(e.getMessage()); | |
} catch (ClientProtocolException e) { | |
throw new RuntimeException(e.getMessage()); | |
} catch (IOException e) { | |
throw new RuntimeException(e.getMessage()); | |
} | |
} | |
private PrivateKey loadPrivateKey(String keyText) { | |
PEMParser pemPrivateKeyReader = new PEMParser(new StringReader(keyText)); | |
Object pemObject = null; | |
try { | |
pemObject = pemPrivateKeyReader.readObject(); | |
if (pemObject instanceof PEMKeyPair) { | |
KeyPair pair = new JcaPEMKeyConverter().setProvider("BC").getKeyPair((PEMKeyPair) pemObject); | |
return pair.getPrivate(); | |
} else { | |
throw new RuntimeException("Unexpected key spec"); | |
} | |
} catch (IOException e) { | |
throw new RuntimeException(e.getMessage()); | |
} | |
} | |
private String getIsoDate() { | |
TimeZone tz = TimeZone.getTimeZone("UTC"); | |
DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); | |
df.setTimeZone(tz); | |
return df.format(new Date())+"Z"; | |
} | |
public static String hashRequestBody(String body) { | |
SHA256Digest digest = new SHA256Digest(); | |
byte[] ret = new byte[digest.getDigestSize()]; | |
byte[] data = body.getBytes(); | |
digest.update(data, 0, data.length); | |
digest.doFinal(ret, 0); | |
return Base64.getEncoder().encodeToString(ret); | |
} | |
private String signRequest(String request) { | |
byte[] requestBytes = new byte[0]; | |
try { | |
requestBytes = request.getBytes("UTF-8"); | |
Signature ecdsaSignature = Signature.getInstance("SHA256withECDSA", "BC"); | |
ecdsaSignature.initSign(privateKey); | |
ecdsaSignature.update(requestBytes); | |
byte[] signature = ecdsaSignature.sign(); | |
return Base64.getEncoder().encodeToString(signature); | |
} catch (UnsupportedEncodingException e) { | |
throw new RuntimeException(e.getMessage()); | |
} catch (NoSuchAlgorithmException e) { | |
throw new RuntimeException(e.getMessage()); | |
} catch (SignatureException e) { | |
throw new RuntimeException(e.getMessage()); | |
} catch (NoSuchProviderException e) { | |
throw new RuntimeException(e.getMessage()); | |
} catch (InvalidKeyException e) { | |
throw new RuntimeException(e.getMessage()); | |
} | |
} | |
} | |
public class Main { | |
static String privateKeyText = | |
"-----BEGIN EC PRIVATE KEY-----\n" + | |
"your prive key is here as from openssl" + | |
"-----END EC PRIVATE KEY-----"; | |
public static void main(String[] args) throws UnsupportedEncodingException { | |
if (Security.getProvider("BC") == null) { | |
Security.addProvider(new BouncyCastleProvider()); | |
} | |
CloseableHttpResponse response = new CloudKitRequest("iCloud.your-id-here", | |
"your-key-id-here as created in the dashboard for serevr to server API", | |
"development", | |
privateKeyText) | |
.setOperation("POST") | |
.setEntity("{\"operations\":[{\"operationType\":\"create\",\"record\":{\"recordType\":\"Post\",\"fields\":{\"title\":{\"value\":\"A Post From The Server\"}}}}]}") | |
.setResource("/public/records/modify") | |
.execute(); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment