summaryrefslogtreecommitdiff
path: root/node/CertificateOfMembership.hpp
blob: 9a03374dc8cc34bda559fd1321b1119277db9ba0 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
/*
 * ZeroTier One - Network Virtualization Everywhere
 * Copyright (C) 2011-2015  ZeroTier, Inc.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * --
 *
 * ZeroTier may be used and distributed under the terms of the GPLv3, which
 * are available at: http://www.gnu.org/licenses/gpl-3.0.html
 *
 * If you would like to embed ZeroTier into a commercial application or
 * redistribute it in a modified binary form, please contact ZeroTier Networks
 * LLC. Start here: http://www.zerotier.com/
 */

#ifndef ZT_CERTIFICATEOFMEMBERSHIP_HPP
#define ZT_CERTIFICATEOFMEMBERSHIP_HPP

#include <stdint.h>
#include <string.h>

#include <string>
#include <vector>
#include <stdexcept>

#include "Constants.hpp"
#include "Buffer.hpp"
#include "Address.hpp"
#include "C25519.hpp"
#include "Identity.hpp"
#include "Utils.hpp"

namespace ZeroTier {

/**
 * Certificate of network membership
 *
 * The COM contains a sorted set of three-element tuples called qualifiers.
 * These contain an id, a value, and a maximum delta.
 *
 * The ID is arbitrary and should be assigned using a scheme that makes
 * every ID globally unique. IDs beneath 65536 are reserved for global
 * assignment by ZeroTier Networks.
 *
 * The value's meaning is ID-specific and isn't important here. What's
 * important is the value and the third member of the tuple: the maximum
 * delta. The maximum delta is the maximum difference permitted between
 * values for a given ID between certificates for the two certificates to
 * themselves agree.
 *
 * Network membership is checked by checking whether a peer's certificate
 * agrees with your own. The timestamp provides the fundamental criterion--
 * each member of a private network must constantly obtain new certificates
 * often enough to stay within the max delta for this qualifier. But other
 * criteria could be added in the future for very special behaviors, things
 * like latitude and longitude for instance.
 */
class CertificateOfMembership
{
public:
	/**
	 * Certificate type codes, used in serialization
	 *
	 * Only one so far, and only one hopefully there shall be for quite some
	 * time.
	 */
	enum Type
	{
		COM_UINT64_ED25519 = 1 // tuples of unsigned 64's signed with Ed25519
	};

	/**
	 * Reserved qualifier IDs
	 *
	 * IDs below 65536 should be considered reserved for future global
	 * assignment here.
	 *
	 * Addition of new required fields requires that code in hasRequiredFields
	 * be updated as well.
	 */
	enum ReservedId
	{
		/**
		 * Revision number of certificate
		 *
		 * Certificates may differ in revision number by a designated max
		 * delta. Differences wider than this cause certificates not to agree.
		 */
		COM_RESERVED_ID_REVISION = 0,

		/**
		 * Network ID for which certificate was issued
		 *
		 * maxDelta here is zero, since this must match.
		 */
		COM_RESERVED_ID_NETWORK_ID = 1,

		/**
		 * ZeroTier address to whom certificate was issued
		 *
		 * maxDelta will be 0xffffffffffffffff here since it's permitted to differ
		 * from peers obviously.
		 */
		COM_RESERVED_ID_ISSUED_TO = 2
	};

	/**
	 * Create an empty certificate
	 */
	CertificateOfMembership() { memset(_signature.data,0,_signature.size()); }

	/**
	 * Create from required fields common to all networks
	 *
	 * @param revision Revision number of certificate
	 * @param timestampMaxDelta Maximum variation between timestamps on this net
	 * @param nwid Network ID
	 * @param issuedTo Certificate recipient
	 */
	CertificateOfMembership(uint64_t revision,uint64_t revisionMaxDelta,uint64_t nwid,const Address &issuedTo)
	{
		_qualifiers.push_back(_Qualifier(COM_RESERVED_ID_REVISION,revision,revisionMaxDelta));
		_qualifiers.push_back(_Qualifier(COM_RESERVED_ID_NETWORK_ID,nwid,0));
		_qualifiers.push_back(_Qualifier(COM_RESERVED_ID_ISSUED_TO,issuedTo.toInt(),0xffffffffffffffffULL));
		memset(_signature.data,0,_signature.size());
	}

	/**
	 * Create from string-serialized data
	 *
	 * @param s String-serialized COM
	 */
	CertificateOfMembership(const char *s) { fromString(s); }

	/**
	 * Create from string-serialized data
	 *
	 * @param s String-serialized COM
	 */
	CertificateOfMembership(const std::string &s) { fromString(s.c_str()); }

	/**
	 * Create from binary-serialized COM in buffer
	 *
	 * @param b Buffer to deserialize from
	 * @param startAt Position to start in buffer
	 */
	template<unsigned int C>
	CertificateOfMembership(const Buffer<C> &b,unsigned int startAt = 0)
		throw(std::out_of_range,std::invalid_argument)
	{
		deserialize(b,startAt);
	}

	/**
	 * @return True if there's something here
	 */
	inline operator bool() const
		throw()
	{
		return (_qualifiers.size() != 0);
	}

	/**
	 * Check for presence of all required fields common to all networks
	 *
	 * @return True if all required fields are present
	 */
	inline bool hasRequiredFields() const
		throw()
	{
		if (_qualifiers.size() < 3)
			return false;
		if (_qualifiers[0].id != COM_RESERVED_ID_REVISION)
			return false;
		if (_qualifiers[1].id != COM_RESERVED_ID_NETWORK_ID)
			return false;
		if (_qualifiers[2].id != COM_RESERVED_ID_ISSUED_TO)
			return false;
		return true;
	}

	/**
	 * @return Maximum delta for mandatory revision field or 0 if field missing
	 */
	inline uint64_t revisionMaxDelta() const
		throw()
	{
		for(std::vector<_Qualifier>::const_iterator q(_qualifiers.begin());q!=_qualifiers.end();++q) {
			if (q->id == COM_RESERVED_ID_REVISION)
				return q->maxDelta;
		}
		return 0ULL;
	}

	/**
	 * @return Revision number for this cert
	 */
	inline uint64_t revision() const
		throw()
	{
		for(std::vector<_Qualifier>::const_iterator q(_qualifiers.begin());q!=_qualifiers.end();++q) {
			if (q->id == COM_RESERVED_ID_REVISION)
				return q->value;
		}
		return 0ULL;
	}

	/**
	 * @return Address to which this cert was issued
	 */
	inline Address issuedTo() const
		throw()
	{
		for(std::vector<_Qualifier>::const_iterator q(_qualifiers.begin());q!=_qualifiers.end();++q) {
			if (q->id == COM_RESERVED_ID_ISSUED_TO)
				return Address(q->value);
		}
		return Address();
	}

	/**
	 * @return Network ID for which this cert was issued
	 */
	inline uint64_t networkId() const
		throw()
	{
		for(std::vector<_Qualifier>::const_iterator q(_qualifiers.begin());q!=_qualifiers.end();++q) {
			if (q->id == COM_RESERVED_ID_NETWORK_ID)
				return q->value;
		}
		return 0ULL;
	}

	/**
	 * Add or update a qualifier in this certificate
	 *
	 * Any signature is invalidated and signedBy is set to null.
	 *
	 * @param id Qualifier ID
	 * @param value Qualifier value
	 * @param maxDelta Qualifier maximum allowed difference (absolute value of difference)
	 */
	void setQualifier(uint64_t id,uint64_t value,uint64_t maxDelta);
	inline void setQualifier(ReservedId id,uint64_t value,uint64_t maxDelta) { setQualifier((uint64_t)id,value,maxDelta); }

	/**
	 * @return String-serialized representation of this certificate
	 */
	std::string toString() const;

	/**
	 * Set this certificate equal to the hex-serialized string
	 *
	 * Invalid strings will result in invalid or undefined certificate
	 * contents. These will subsequently fail validation and comparison.
	 * Empty strings will result in an empty certificate.
	 *
	 * @param s String to deserialize
	 */
	void fromString(const char *s);
	inline void fromString(const std::string &s) { fromString(s.c_str()); }

	/**
	 * Compare two certificates for parameter agreement
	 *
	 * This compares this certificate with the other and returns true if all
	 * paramters in this cert are present in the other and if they agree to
	 * within this cert's max delta value for each given parameter.
	 *
	 * Tuples present in other but not in this cert are ignored, but any
	 * tuples present in this cert but not in other result in 'false'.
	 *
	 * @param other Cert to compare with
	 * @return True if certs agree and 'other' may be communicated with
	 */
	bool agreesWith(const CertificateOfMembership &other) const
		throw();

	/**
	 * Sign this certificate
	 *
	 * @param with Identity to sign with, must include private key
	 * @return True if signature was successful
	 */
	bool sign(const Identity &with);

	/**
	 * Verify certificate against an identity
	 *
	 * @param id Identity to verify against
	 * @return True if certificate is signed by this identity and verification was successful
	 */
	bool verify(const Identity &id) const;

	/**
	 * @return True if signed
	 */
	inline bool isSigned() const throw() { return (_signedBy); }

	/**
	 * @return Address that signed this certificate or null address if none
	 */
	inline const Address &signedBy() const throw() { return _signedBy; }

	/**
	 * Serialize to std::string or compatible class
	 *
	 * @param b String or other class supporting push_back() and append() like std::string
	 */
	template<typename T>
	inline void serialize2(T &b) const
	{
		uint64_t tmp[3];
		char tmp2[ZT_ADDRESS_LENGTH];
		b.push_back((char)COM_UINT64_ED25519);
		b.push_back((char)((_qualifiers.size() >> 8) & 0xff));
		b.push_back((char)(_qualifiers.size() & 0xff));
		for(std::vector<_Qualifier>::const_iterator q(_qualifiers.begin());q!=_qualifiers.end();++q) {
			tmp[0] = Utils::hton(q->id);
			tmp[1] = Utils::hton(q->value);
			tmp[2] = Utils::hton(q->maxDelta);
			b.append(reinterpret_cast<const char *>(reinterpret_cast<void *>(tmp)),sizeof(tmp));
		}
		_signedBy.copyTo(tmp2,ZT_ADDRESS_LENGTH);
		b.append(tmp2,ZT_ADDRESS_LENGTH);
		if (_signedBy)
			b.append((const char *)_signature.data,_signature.size());
	}

	/**
	 * Deserialize from std::string::iterator or compatible iterator or char* pointer
	 *
	 * @param p Iterator
	 * @param end End of buffer
	 */
	template<typename T>
	inline void deserialize2(T &p,const T &end)
	{
		uint64_t tmp[3];
		char tmp2[ZT_ADDRESS_LENGTH];
		unsigned int qcount;

		_qualifiers.clear();
		_signedBy.zero();

		if (p == end) throw std::out_of_range("incomplete certificate of membership");
		if (*(p++) != (char)COM_UINT64_ED25519) throw std::invalid_argument("unknown certificate of membership type");

		if (p == end) throw std::out_of_range("incomplete certificate of membership");
		qcount = (unsigned int)*(p++) << 8;
		if (p == end) throw std::out_of_range("incomplete certificate of membership");
		qcount |= (unsigned int)*(p++);

		for(unsigned int i=0;i<qcount;++i) {
			char *p2 = reinterpret_cast<char *>(reinterpret_cast<void *>(tmp));
			for(unsigned int j=0;j<sizeof(tmp);++j) {
				if (p == end) throw std::out_of_range("incomplete certificate of membership");
				*(p2++) = *(p++);
			}
			_qualifiers.push_back(_Qualifier(Utils::ntoh(tmp[0]),Utils::ntoh(tmp[1]),Utils::ntoh(tmp[2])));
		}

		for(unsigned int j=0;j<ZT_ADDRESS_LENGTH;++j) {
			if (p == end) throw std::out_of_range("incomplete certificate of membership");
			tmp2[j] = *(p++);
		}
		_signedBy.setTo(tmp2,ZT_ADDRESS_LENGTH);

		if (_signedBy) {
			for(unsigned int j=0;j<_signature.size();++j) {
				if (p == end) throw std::out_of_range("incomplete certificate of membership");
				_signature.data[j] = (unsigned char)*(p++);
			}
		}
	}

	template<unsigned int C>
	inline void serialize(Buffer<C> &b) const
	{
		b.append((unsigned char)COM_UINT64_ED25519);
		b.append((uint16_t)_qualifiers.size());
		for(std::vector<_Qualifier>::const_iterator q(_qualifiers.begin());q!=_qualifiers.end();++q) {
			b.append(q->id);
			b.append(q->value);
			b.append(q->maxDelta);
		}
		_signedBy.appendTo(b);
		if (_signedBy)
			b.append(_signature.data,(unsigned int)_signature.size());
	}

	template<unsigned int C>
	inline unsigned int deserialize(const Buffer<C> &b,unsigned int startAt = 0)
	{
		unsigned int p = startAt;

		_qualifiers.clear();
		_signedBy.zero();

		if (b[p++] != COM_UINT64_ED25519)
			throw std::invalid_argument("unknown certificate of membership type");

		unsigned int numq = b.template at<uint16_t>(p); p += sizeof(uint16_t);
		uint64_t lastId = 0;
		for(unsigned int i=0;i<numq;++i) {
			uint64_t tmp = b.template at<uint64_t>(p);
			if (tmp < lastId)
				throw std::invalid_argument("certificate qualifiers are not sorted");
			else lastId = tmp;
			_qualifiers.push_back(_Qualifier(
					tmp,
					b.template at<uint64_t>(p + 8),
					b.template at<uint64_t>(p + 16)
				));
			p += 24;
		}

		_signedBy.setTo(b.field(p,ZT_ADDRESS_LENGTH),ZT_ADDRESS_LENGTH);
		p += ZT_ADDRESS_LENGTH;

		if (_signedBy) {
			memcpy(_signature.data,b.field(p,(unsigned int)_signature.size()),_signature.size());
			p += (unsigned int)_signature.size();
		}

		return (p - startAt);
	}

	inline bool operator==(const CertificateOfMembership &c) const
		throw()
	{
		if (_signedBy != c._signedBy)
			return false;
		// We have to compare in depth manually since == only compares id
		if (_qualifiers.size() != c._qualifiers.size())
			return false;
		for(unsigned long i=0;i<_qualifiers.size();++i) {
			const _Qualifier &a = _qualifiers[i];
			const _Qualifier &b = c._qualifiers[i];
			if ((a.id != b.id)||(a.value != b.value)||(a.maxDelta != b.maxDelta))
				return false;
		}
		return (_signature == c._signature);
	}
	inline bool operator!=(const CertificateOfMembership &c) const throw() { return (!(*this == c)); }

private:
	struct _Qualifier
	{
		_Qualifier() throw() {}
		_Qualifier(uint64_t i,uint64_t v,uint64_t m) throw() :
			id(i),
			value(v),
			maxDelta(m) {}

		uint64_t id;
		uint64_t value;
		uint64_t maxDelta;

		inline bool operator==(const _Qualifier &q) const throw() { return (id == q.id); } // for unique
		inline bool operator<(const _Qualifier &q) const throw() { return (id < q.id); } // for sort
	};

	Address _signedBy;
	std::vector<_Qualifier> _qualifiers; // sorted by id and unique
	C25519::Signature _signature;
};

} // namespace ZeroTier

#endif