Initial commit
diff --git a/client.cc b/client.cc
new file mode 100644
index 0000000..68b1953
--- /dev/null
+++ b/client.cc
@@ -0,0 +1,199 @@
+/* Copyright 2016 The Roughtime Authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License. */
+
+#include <string>
+
+#include <stdint.h>
+
+#include <google/protobuf/stubs/logging.h>
+#include <openssl/curve25519.h>
+
+#include "client.h"
+
+namespace roughtime {
+
+std::string CreateRequest(const uint8_t nonce[kNonceLength]) {
+  uint8_t query_bytes[kMinRequestSize];
+  size_t query_len;
+
+  Builder query(query_bytes, sizeof(query_bytes), 2);
+
+  uint8_t* padding;
+
+  static_assert(kTagNONC < kTagPAD, "Tags must be written in order");
+  GOOGLE_CHECK(query.AddTagData(kTagNONC, nonce, kNonceLength) &&
+               query.AddTag(&padding, kTagPAD, kPaddingLen) &&
+               query.Finish(&query_len));
+  GOOGLE_CHECK_EQ(query_len, sizeof(query_bytes));
+
+  memset(padding, 0, kPaddingLen);
+
+  return std::string(reinterpret_cast<char*>(query_bytes), query_len);
+}
+
+bool ParseResponse(uint64_t* out_time, uint32_t* out_radius,
+                   std::string* out_error,
+                   const uint8_t root_public_key[ED25519_PUBLIC_KEY_LEN],
+                   const uint8_t* response_bytes, size_t response_len,
+                   const uint8_t nonce[kNonceLength]) {
+  *out_time = 0;
+  *out_radius = 0;
+
+  Parser response(response_bytes, response_len);
+  if (!response.is_valid()) {
+    *out_error = "structural error";
+    return false;
+  }
+
+  const uint8_t* cert_bytes;
+  size_t cert_len;
+  if (!response.GetTag(&cert_bytes, &cert_len, kTagCERT)) {
+    *out_error = "no certificate provided";
+    return false;
+  }
+
+  Parser cert(cert_bytes, cert_len);
+  if (!cert.is_valid()) {
+    *out_error = "structural error in certificate";
+    return false;
+  }
+
+  const uint8_t* signature;
+  if (!cert.GetFixedLen(&signature, kTagSIG, ED25519_SIGNATURE_LEN)) {
+    *out_error = "no signature in certificate";
+    return false;
+  }
+
+  const uint8_t* delegation_bytes;
+  size_t delegation_len;
+  if (!cert.GetTag(&delegation_bytes, &delegation_len, kTagDELE)) {
+    *out_error = "no delegation in certificate";
+    return false;
+  }
+
+  std::string signed_message =
+      std::string(kCertContextString, sizeof(kCertContextString)) +
+      std::string(reinterpret_cast<const char*>(delegation_bytes),
+                  delegation_len);
+
+  if (!ED25519_verify(reinterpret_cast<const uint8_t*>(signed_message.data()),
+                      signed_message.size(), signature, root_public_key)) {
+    *out_error = "bad signature in certificate";
+    return false;
+  }
+
+  const uint8_t* delegated_public_key;
+  uint64_t min_time, max_time;
+  Parser delegation(delegation_bytes, delegation_len);
+  if (!delegation.is_valid() ||
+      !delegation.Get(&min_time, kTagMINT) ||
+      !delegation.Get(&max_time, kTagMAXT) ||
+      !delegation.GetFixedLen(&delegated_public_key, kTagPUBK,
+                              ED25519_PUBLIC_KEY_LEN)) {
+    *out_error = "delegation missing required value";
+    return false;
+  }
+
+  if (max_time < min_time) {
+    *out_error = "invalid delegation validity period";
+    return false;
+  }
+
+  const uint8_t* signed_response_bytes;
+  size_t signed_response_len;
+  if (!response.GetTag(&signed_response_bytes, &signed_response_len,
+                       kTagSREP)) {
+    *out_error = "no signed response";
+    return false;
+  }
+
+  const uint8_t* response_signature;
+  if (!response.GetFixedLen(&response_signature, kTagSIG,
+                            ED25519_SIGNATURE_LEN)) {
+    *out_error = "no signature in response";
+    return false;
+  }
+
+  signed_message =
+      std::string(kContextString, sizeof(kContextString)) +
+      std::string(reinterpret_cast<const char*>(signed_response_bytes),
+                  signed_response_len);
+
+  if (!ED25519_verify(reinterpret_cast<const uint8_t*>(signed_message.data()),
+                      signed_message.size(), response_signature,
+                      delegated_public_key)) {
+    *out_error = "bad signature in response";
+    return false;
+  }
+
+  Parser signed_response(signed_response_bytes, signed_response_len);
+  if (!signed_response.is_valid()) {
+    *out_error = "invalid signed response in response";
+    return false;
+  }
+
+  const uint8_t* root;
+  uint64_t timestamp;
+  uint32_t radius;
+  if (!signed_response.GetFixedLen(&root, kTagROOT, kNonceLength) ||
+      !signed_response.Get(&timestamp, kTagMIDP) ||
+      !signed_response.Get(&radius, kTagRADI)) {
+    *out_error = "signed response missing required values";
+    return false;
+  }
+
+  if (timestamp < min_time || max_time < timestamp) {
+    *out_error = "timestamp out of range for delegation";
+    return false;
+  }
+
+  const uint8_t* path;
+  size_t path_len;
+  uint32_t tree_index;
+  if (!response.Get(&tree_index, kTagINDX) ||
+      !response.GetTag(&path, &path_len, kTagPATH)) {
+    *out_error = "response missing required values";
+    return false;
+  }
+
+  uint8_t hash[kNonceLength];
+  HashLeaf(hash, nonce);
+
+  if (path_len % kNonceLength != 0) {
+    *out_error = "tree path is not a multiple of the hash size";
+    return false;
+  }
+
+  for (size_t i = 0; i < path_len; i += kNonceLength) {
+    const bool path_element_is_right = tree_index & 1;
+    if (path_element_is_right) {
+      HashNode(hash, hash, path + i);
+    } else {
+      HashNode(hash, path + i, hash);
+    }
+    tree_index /= 2;
+  }
+
+  if (memcmp(root, hash, kNonceLength) != 0) {
+    *out_error = "calculated tree root doesn't match signed root";
+    return false;
+  }
+
+  *out_time = timestamp;
+  *out_radius = radius;
+
+  return true;
+}
+
+}  // namespace roughtime