| /* 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(×tamp, 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 |