Adam Langley | 4866a02 | 2016-09-01 11:24:21 -0700 | [diff] [blame] | 1 | /* Copyright 2016 The Roughtime Authors. |
| 2 | * |
| 3 | * Licensed under the Apache License, Version 2.0 (the "License"); |
| 4 | * you may not use this file except in compliance with the License. |
| 5 | * You may obtain a copy of the License at |
| 6 | * |
| 7 | * http://www.apache.org/licenses/LICENSE-2.0 |
| 8 | * |
| 9 | * Unless required by applicable law or agreed to in writing, software |
| 10 | * distributed under the License is distributed on an "AS IS" BASIS, |
| 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 12 | * See the License for the specific language governing permissions and |
| 13 | * limitations under the License. */ |
| 14 | |
| 15 | #include <string> |
| 16 | |
| 17 | #include <stdint.h> |
| 18 | |
Adam Langley | 4866a02 | 2016-09-01 11:24:21 -0700 | [diff] [blame] | 19 | #include <openssl/curve25519.h> |
| 20 | |
| 21 | #include "client.h" |
Ankur Mittal | bde0239 | 2017-03-22 17:48:25 -0700 | [diff] [blame] | 22 | #include "logging.h" |
Adam Langley | 4866a02 | 2016-09-01 11:24:21 -0700 | [diff] [blame] | 23 | |
| 24 | namespace roughtime { |
| 25 | |
| 26 | std::string CreateRequest(const uint8_t nonce[kNonceLength]) { |
| 27 | uint8_t query_bytes[kMinRequestSize]; |
| 28 | size_t query_len; |
| 29 | |
| 30 | Builder query(query_bytes, sizeof(query_bytes), 2); |
| 31 | |
| 32 | uint8_t* padding; |
| 33 | |
| 34 | static_assert(kTagNONC < kTagPAD, "Tags must be written in order"); |
Ankur Mittal | bde0239 | 2017-03-22 17:48:25 -0700 | [diff] [blame] | 35 | ROUGHTIME_CHECK(query.AddTagData(kTagNONC, nonce, kNonceLength) && |
Adam Langley | 4866a02 | 2016-09-01 11:24:21 -0700 | [diff] [blame] | 36 | query.AddTag(&padding, kTagPAD, kPaddingLen) && |
| 37 | query.Finish(&query_len)); |
Ankur Mittal | bde0239 | 2017-03-22 17:48:25 -0700 | [diff] [blame] | 38 | ROUGHTIME_CHECK_EQ(query_len, sizeof(query_bytes)); |
Adam Langley | 4866a02 | 2016-09-01 11:24:21 -0700 | [diff] [blame] | 39 | |
| 40 | memset(padding, 0, kPaddingLen); |
| 41 | |
| 42 | return std::string(reinterpret_cast<char*>(query_bytes), query_len); |
| 43 | } |
| 44 | |
Adam Langley | a5d2c83 | 2016-09-21 17:10:22 -0700 | [diff] [blame] | 45 | bool ParseResponse(rough_time_t* out_time, uint32_t* out_radius, |
Adam Langley | 4866a02 | 2016-09-01 11:24:21 -0700 | [diff] [blame] | 46 | std::string* out_error, |
| 47 | const uint8_t root_public_key[ED25519_PUBLIC_KEY_LEN], |
| 48 | const uint8_t* response_bytes, size_t response_len, |
| 49 | const uint8_t nonce[kNonceLength]) { |
| 50 | *out_time = 0; |
| 51 | *out_radius = 0; |
| 52 | |
| 53 | Parser response(response_bytes, response_len); |
| 54 | if (!response.is_valid()) { |
| 55 | *out_error = "structural error"; |
| 56 | return false; |
| 57 | } |
| 58 | |
| 59 | const uint8_t* cert_bytes; |
| 60 | size_t cert_len; |
| 61 | if (!response.GetTag(&cert_bytes, &cert_len, kTagCERT)) { |
| 62 | *out_error = "no certificate provided"; |
| 63 | return false; |
| 64 | } |
| 65 | |
| 66 | Parser cert(cert_bytes, cert_len); |
| 67 | if (!cert.is_valid()) { |
| 68 | *out_error = "structural error in certificate"; |
| 69 | return false; |
| 70 | } |
| 71 | |
| 72 | const uint8_t* signature; |
| 73 | if (!cert.GetFixedLen(&signature, kTagSIG, ED25519_SIGNATURE_LEN)) { |
| 74 | *out_error = "no signature in certificate"; |
| 75 | return false; |
| 76 | } |
| 77 | |
| 78 | const uint8_t* delegation_bytes; |
| 79 | size_t delegation_len; |
| 80 | if (!cert.GetTag(&delegation_bytes, &delegation_len, kTagDELE)) { |
| 81 | *out_error = "no delegation in certificate"; |
| 82 | return false; |
| 83 | } |
| 84 | |
| 85 | std::string signed_message = |
| 86 | std::string(kCertContextString, sizeof(kCertContextString)) + |
| 87 | std::string(reinterpret_cast<const char*>(delegation_bytes), |
| 88 | delegation_len); |
| 89 | |
| 90 | if (!ED25519_verify(reinterpret_cast<const uint8_t*>(signed_message.data()), |
| 91 | signed_message.size(), signature, root_public_key)) { |
| 92 | *out_error = "bad signature in certificate"; |
| 93 | return false; |
| 94 | } |
| 95 | |
| 96 | const uint8_t* delegated_public_key; |
Adam Langley | a5d2c83 | 2016-09-21 17:10:22 -0700 | [diff] [blame] | 97 | rough_time_t min_time, max_time; |
Adam Langley | 4866a02 | 2016-09-01 11:24:21 -0700 | [diff] [blame] | 98 | Parser delegation(delegation_bytes, delegation_len); |
| 99 | if (!delegation.is_valid() || |
| 100 | !delegation.Get(&min_time, kTagMINT) || |
| 101 | !delegation.Get(&max_time, kTagMAXT) || |
| 102 | !delegation.GetFixedLen(&delegated_public_key, kTagPUBK, |
| 103 | ED25519_PUBLIC_KEY_LEN)) { |
| 104 | *out_error = "delegation missing required value"; |
| 105 | return false; |
| 106 | } |
| 107 | |
| 108 | if (max_time < min_time) { |
| 109 | *out_error = "invalid delegation validity period"; |
| 110 | return false; |
| 111 | } |
| 112 | |
| 113 | const uint8_t* signed_response_bytes; |
| 114 | size_t signed_response_len; |
| 115 | if (!response.GetTag(&signed_response_bytes, &signed_response_len, |
| 116 | kTagSREP)) { |
| 117 | *out_error = "no signed response"; |
| 118 | return false; |
| 119 | } |
| 120 | |
| 121 | const uint8_t* response_signature; |
| 122 | if (!response.GetFixedLen(&response_signature, kTagSIG, |
| 123 | ED25519_SIGNATURE_LEN)) { |
| 124 | *out_error = "no signature in response"; |
| 125 | return false; |
| 126 | } |
| 127 | |
| 128 | signed_message = |
| 129 | std::string(kContextString, sizeof(kContextString)) + |
| 130 | std::string(reinterpret_cast<const char*>(signed_response_bytes), |
| 131 | signed_response_len); |
| 132 | |
| 133 | if (!ED25519_verify(reinterpret_cast<const uint8_t*>(signed_message.data()), |
| 134 | signed_message.size(), response_signature, |
| 135 | delegated_public_key)) { |
| 136 | *out_error = "bad signature in response"; |
| 137 | return false; |
| 138 | } |
| 139 | |
| 140 | Parser signed_response(signed_response_bytes, signed_response_len); |
| 141 | if (!signed_response.is_valid()) { |
| 142 | *out_error = "invalid signed response in response"; |
| 143 | return false; |
| 144 | } |
| 145 | |
| 146 | const uint8_t* root; |
Adam Langley | a5d2c83 | 2016-09-21 17:10:22 -0700 | [diff] [blame] | 147 | rough_time_t timestamp; |
Adam Langley | 4866a02 | 2016-09-01 11:24:21 -0700 | [diff] [blame] | 148 | uint32_t radius; |
| 149 | if (!signed_response.GetFixedLen(&root, kTagROOT, kNonceLength) || |
| 150 | !signed_response.Get(×tamp, kTagMIDP) || |
| 151 | !signed_response.Get(&radius, kTagRADI)) { |
| 152 | *out_error = "signed response missing required values"; |
| 153 | return false; |
| 154 | } |
| 155 | |
| 156 | if (timestamp < min_time || max_time < timestamp) { |
| 157 | *out_error = "timestamp out of range for delegation"; |
| 158 | return false; |
| 159 | } |
| 160 | |
| 161 | const uint8_t* path; |
| 162 | size_t path_len; |
| 163 | uint32_t tree_index; |
| 164 | if (!response.Get(&tree_index, kTagINDX) || |
| 165 | !response.GetTag(&path, &path_len, kTagPATH)) { |
| 166 | *out_error = "response missing required values"; |
| 167 | return false; |
| 168 | } |
| 169 | |
| 170 | uint8_t hash[kNonceLength]; |
| 171 | HashLeaf(hash, nonce); |
| 172 | |
| 173 | if (path_len % kNonceLength != 0) { |
| 174 | *out_error = "tree path is not a multiple of the hash size"; |
| 175 | return false; |
| 176 | } |
| 177 | |
| 178 | for (size_t i = 0; i < path_len; i += kNonceLength) { |
| 179 | const bool path_element_is_right = tree_index & 1; |
| 180 | if (path_element_is_right) { |
| 181 | HashNode(hash, hash, path + i); |
| 182 | } else { |
| 183 | HashNode(hash, path + i, hash); |
| 184 | } |
| 185 | tree_index /= 2; |
| 186 | } |
| 187 | |
| 188 | if (memcmp(root, hash, kNonceLength) != 0) { |
| 189 | *out_error = "calculated tree root doesn't match signed root"; |
| 190 | return false; |
| 191 | } |
| 192 | |
| 193 | *out_time = timestamp; |
| 194 | *out_radius = radius; |
| 195 | |
| 196 | return true; |
| 197 | } |
| 198 | |
| 199 | } // namespace roughtime |