blob: 8543e0d5fd071a0ca40dbf95d76e48f46109bf1d [file] [log] [blame]
/* 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(rough_time_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;
rough_time_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;
rough_time_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