27

Apple は、サーバー間で CloudKit に対して認証する新しい方法を公開しました。https://developer.apple.com/library/content/documentation/DataManagement/Conceptual/CloudKitWebServicesReference/SettingUpWebServices.html#//apple_ref/doc/uid/TP40015240-CH24-SW6

CloudKit とこのメソッドに対して認証を試みました。最初にキー ペアを生成し、CloudKit に公開キーを渡しましたが、今のところ問題はありません。

リクエストヘッダーの作成を開始しました。ドキュメントによると、次のようになります。

X-Apple-CloudKit-Request-KeyID: [keyID]  
X-Apple-CloudKit-Request-ISO8601Date: [date]  
X-Apple-CloudKit-Request-SignatureV1: [signature]
  • [keyID]、問題ありません。これは、CloudKit ダッシュボードで見つけることができます。
  • [日付]、これでうまくいくと思います: 2016-02-06T20:41:00Z
  • [署名] ここに問題があります...

ドキュメントには次のように記載されています。

ステップ 1 で作成した署名。

ステップ 1 は次のように述べています。

次のパラメータを連結し、コロンで区切ります。
[Current date]:[Request body]:[Web Service URL]

「なぜ鍵ペアを生成しなければならないのか?」と自問自答しました。
しかし、ステップ 2 は次のように述べています。

秘密鍵を使用して、このメッセージの ECDSA 署名を計算します。

おそらく、連結された署名に秘密鍵で署名し、これをヘッダーに入れることを意味しているのでしょうか? とにかく、両方試してみました...

この (署名されていない) 署名値のサンプルは次のようになります。

2016-02-06T20:41:00Z:YTdkNzAwYTllNjI1M2EyZTllNDNiZjVmYjg0MWFhMGRiMTE2MjI1NTYwNTA2YzQyODc4MjUwNTQ0YTE5YTg4Yw==:https://api.apple-cloudkit.com/database/1/[iCloud Container]/development/public/records/lookup  

リクエスト ボディの値は SHA256 でハッシュされ、その後 base64 でエンコードされます。私の質問は、「:」で連結する必要がありますが、URL と日付にも「:」が含まれています。それが正しいか?(また、URL を URL エンコードして、日付の「:」を削除しようとしました)。
次に、この署名文字列を ECDSA で署名し、ヘッダーに入れて送信します。しかし、常に401「認証に失敗しました」が返されます。それに署名するために、次のコマンドで ecdsa python モジュールを使用しました。

from ecdsa import SigningKey  
a = SigningKey.from_pem(open("path_to_pem_file").read())  
b = "[date]:[base64(request_body)]:/database/1/iCloud....."  
print a.sign(b).encode('hex')

python モジュールが正しく動作していない可能性があります。ただし、秘密鍵から正しい公開鍵を生成できます。ですから、他の機能も機能することを願っています。

サーバー間方式で CloudKit に対して認証できた人はいますか? それはどのように正しく機能しますか?

編集:動作する正しいpythonバージョン

from ecdsa import SigningKey
import ecdsa, base64, hashlib  

a = SigningKey.from_pem(open("path_to_pem_file").read())  
b = "[date]:[base64(sha256(request_body))]:/database/1/iCloud....."  
signature = a.sign(b, hashfunc=hashlib.sha256, sigencode=ecdsa.util.sigencode_der)  
signature = base64.b64encode(signature)
print signature #include this into the header
4

6 に答える 6

13

メッセージの最後の部分

[Current date]:[Request body]:[Web Service URL]

ドメインを含めないでください (クエリパラメータを含める必要があります)。

2016-02-06T20:41:00Z:YTdkNzAwYTllNjI1M2EyZTllNDNiZjVmYjg0MWFhMGRiMTE2MjI1NTYwNTA2YzQyODc4MjUwNTQ0YTE5YTg4Yw==:/database/1/[iCloud Container]/development/public/records/lookup

読みやすくするために改行を使用します。

2016-02-06T20:41:00Z
:YTdkNzAwYTllNjI1M2EyZTllNDNiZjVmYjg0MWFhMGRiMTE2MjI1NTYwNTA2YzQyODc4MjUwNTQ0YTE5YTg4Yw==
:/database/1/[iCloud Container]/development/public/records/lookup

以下は、疑似コードでヘッダー値を計算する方法を示しています

正確な API 呼び出しは、使用する具体的な言語と暗号化ライブラリによって異なります。

//1. Date
//Example: 2016-02-07T18:58:24Z
//Pitfall: make sure to not include milliseconds
date = isoDateWithoutMilliseconds() 

//2. Payload
//Example (empty string base64 encoded; GET requests):
//47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=
//Pitfall: make sure the output is base64 encoded (not hex)
payload = base64encode(sha256(body))  

//3. Path
//Example: /database/1/[containerIdentifier]/development/public/records/lookup
//Pitfall: Don't include the domain; do include any query parameter
path = stripDomainKeepQueryParams(url) 

//4. Message
//Join date, payload, and path with colons
message = date + ':' + payload + ':' + path

//5. Compute a signature for the message using your private key.
//This step looks very different for every language/crypto lib.
//Pitfall: make sure the output is base64 encoded.
//Hint: the key itself contains information about the signature algorithm 
//      (on NodeJS you can use the signature name 'RSA-SHA256' to compute a 
//      the correct ECDSA signature with an ECDSA key).
signature = base64encode(sign(message, key))

//6. Set headers
X-Apple-CloudKit-Request-KeyID = keyID 
X-Apple-CloudKit-Request-ISO8601Date = date  
X-Apple-CloudKit-Request-SignatureV1 = signature

//7. For POST requests, don't forget to actually send the unsigned request body
//   (not just the headers)
于 2016-02-07T19:56:28.213 に答える
7

PHP で動作するコード例を作成しました: https://gist.github.com/Mauricevb/87c144cec514c5ce73bd (@Jessedc の JavaScript の例に基づく)

ちなみに、日付時刻を UTC タイムゾーンで設定してください。このため、私のコードは機能しませんでした。

于 2016-02-07T21:37:30.257 に答える
6

これは、Node.js で取り組んでいるプロジェクトから抽出したものです。多分あなたはそれが役に立つと思うでしょう。X-Apple-CloudKit-Request-KeyIDとコンテナ識別子を置き換えて機能さrequestOptions.pathせます。

秘密鍵/ pem は以下で生成されます: openssl ecparam -name prime256v1 -genkey -noout -out eckey.pemCloudKit ダッシュボードに登録する公開鍵を生成しますopenssl ec -in eckey.pem -pubout

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.com.your.container/development/public/records/modify',
    method: 'POST',
    headers: { // We will add more headers in the sign methods
      "X-Apple-CloudKit-Request-KeyID": "your-ck-request-keyID"
    }
  }
}

リクエストに署名するには:

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 payloadSignature = hash.digest("base64")

  // Create the signature string to sign
  var signatureData = [
    dateString,
    payloadSignature,
    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("./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)
})

コピーペーストの喜びのために:https ://gist.github.com/spllr/4bf3fadb7f6168f67698 (編集済み)

于 2016-02-09T20:01:05.433 に答える
2

私は同じ問題を抱えており、Python で CloudKit API とやり取りするためにpython-requestsで動作するライブラリを作成することになりました。

pip install requests-cloudkit

インストール後、認証ハンドラー ( CloudKitAuth) をインポートして、リクエストで直接使用します。CloudKit API に対して行うすべての要求を透過的に認証します。

>>> import requests
>>> from requests_cloudkit import CloudKitAuth
>>> auth = CloudKitAuth(key_id=YOUR_KEY_ID, key_file_name=YOUR_PRIVATE_KEY_PATH)
>>> requests.get("https://api.apple-cloudkit.com/database/[version]/[container]/[environment]/public/zones/list", auth=auth)

貢献したり、問題を報告したりしたい場合は、 https://github.com/lionheart/requests-cloudkitで GitHub プロジェクトを利用できます。

于 2016-05-29T01:54:34.700 に答える