diff options
author | Adam Ierymenko <adam.ierymenko@gmail.com> | 2014-08-05 09:56:49 -0700 |
---|---|---|
committer | Adam Ierymenko <adam.ierymenko@gmail.com> | 2014-08-05 09:56:49 -0700 |
commit | e3c5ada3a70f71fe117319cd335a4462387c744d (patch) | |
tree | 795a9b772147b9ecf48471991ca5e5f7b341c316 | |
parent | 7adf0eac7e99337befb421d1b3cf2d64888a9ea2 (diff) | |
download | infinitytier-e3c5ada3a70f71fe117319cd335a4462387c744d.tar.gz infinitytier-e3c5ada3a70f71fe117319cd335a4462387c744d.zip |
Add signatures to Dictionary, and fix unhex() API in Utils to be a little safer.
-rw-r--r-- | node/Dictionary.cpp | 154 | ||||
-rw-r--r-- | node/Dictionary.hpp | 137 | ||||
-rw-r--r-- | node/Utils.cpp | 46 | ||||
-rw-r--r-- | node/Utils.hpp | 26 | ||||
-rw-r--r-- | objects.mk | 1 |
5 files changed, 228 insertions, 136 deletions
diff --git a/node/Dictionary.cpp b/node/Dictionary.cpp new file mode 100644 index 00000000..0572783b --- /dev/null +++ b/node/Dictionary.cpp @@ -0,0 +1,154 @@ +/* + * ZeroTier One - Global Peer to Peer Ethernet + * Copyright (C) 2011-2014 ZeroTier Networks LLC + * + * 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/ + */ + +#include "Dictionary.hpp" +#include "C25519.hpp" +#include "Identity.hpp" +#include "Utils.hpp" + +namespace ZeroTier { + +void Dictionary::fromString(const char *s) +{ + clear(); + bool escapeState = false; + std::string keyBuf; + std::string *element = &keyBuf; + while (*s) { + if (escapeState) { + escapeState = false; + switch(*s) { + case '0': + element->push_back((char)0); + break; + case 'r': + element->push_back('\r'); + break; + case 'n': + element->push_back('\n'); + break; + default: + element->push_back(*s); + break; + } + } else { + if (*s == '\\') { + escapeState = true; + } else if (*s == '=') { + if (element == &keyBuf) + element = &((*this)[keyBuf]); + } else if ((*s == '\r')||(*s == '\n')) { + if ((element == &keyBuf)&&(keyBuf.length() > 0)) + (*this)[keyBuf]; + keyBuf = ""; + element = &keyBuf; + } else element->push_back(*s); + } + ++s; + } + if ((element == &keyBuf)&&(keyBuf.length() > 0)) + (*this)[keyBuf]; +} + +bool Dictionary::sign(const Identity &id) +{ + try { + std::string buf; + _mkSigBuf(buf); + char nows[32]; + Utils::snprintf(nows,sizeof(nows),"%llx",(unsigned long long)Utils::now()); + C25519::Signature sig(id.sign(buf.data(),buf.length())); + (*this)[ZT_DICTIONARY_SIGNATURE] = Utils::hex(sig.data,sig.size()); + (*this)[ZT_DICTIONARY_SIGNATURE_IDENTITY] = id.toString(false); + (*this)[ZT_DICTIONARY_SIGNATURE_TIMESTAMP] = nows; + return true; + } catch ( ... ) { + return false; + } +} + +bool Dictionary::verify(const Identity &id) const +{ + try { + std::string buf; + _mkSigBuf(buf); + const_iterator sig(find(ZT_DICTIONARY_SIGNATURE)); + if (sig == end()) + return false; + std::string sigbin(Utils::unhex(sig->second)); + return id.verify(buf.data(),buf.length(),sigbin.data(),sigbin.length()); + } catch ( ... ) { + return false; + } +} + +void Dictionary::_mkSigBuf(std::string &buf) const +{ + unsigned long pairs = 0; + for(const_iterator i(begin());i!=end();++i) { + if ((i->first.length() < 2)||( (i->first[0] != '~')&&(i->first[1] != '!') )) { + buf.append(i->first); + buf.push_back('='); + buf.append(i->second); + buf.push_back('\0'); + ++pairs; + } + } + buf.push_back((char)0xff); + buf.push_back((char)((pairs >> 24) & 0xff)); // pad with number of key/value pairs at end + buf.push_back((char)((pairs >> 16) & 0xff)); + buf.push_back((char)((pairs >> 8) & 0xff)); + buf.push_back((char)(pairs & 0xff)); +} + +void Dictionary::_appendEsc(const char *data,unsigned int len,std::string &to) +{ + for(unsigned int i=0;i<len;++i) { + switch(data[i]) { + case 0: + to.append("\\0"); + break; + case '\r': + to.append("\\r"); + break; + case '\n': + to.append("\\n"); + break; + case '\\': + to.append("\\\\"); + break; + case '=': + to.append("\\="); + break; + default: + to.push_back(data[i]); + break; + } + } +} + +} // namespace ZeroTier diff --git a/node/Dictionary.hpp b/node/Dictionary.hpp index 92568431..31930eff 100644 --- a/node/Dictionary.hpp +++ b/node/Dictionary.hpp @@ -34,8 +34,15 @@ #include "Constants.hpp" +// Three fields are added/updated by sign() +#define ZT_DICTIONARY_SIGNATURE "~!ed25519" +#define ZT_DICTIONARY_SIGNATURE_IDENTITY "~!sigid" +#define ZT_DICTIONARY_SIGNATURE_TIMESTAMP "~!sigts" + namespace ZeroTier { +class Identity; + /** * Simple key/value dictionary with string serialization * @@ -43,29 +50,29 @@ namespace ZeroTier { * It does not support comments or other syntactic complexities. It is * human-readable if the keys and values in the dictionary are also * human-readable. Otherwise it might contain unprintable characters. + * + * Keys beginning with "~!" are reserved for signatures and are ignored + * during the signature process. + * + * Note: the signature code depends on std::map<> being sorted, but no + * other code does. So if the underlying data structure is ever swapped + * out for an unsorted one, the signature code will have to be updated + * to sort before composing the string to sign. */ class Dictionary : public std::map<std::string,std::string> { public: - Dictionary() - { - } + Dictionary() {} /** * @param s String-serialized dictionary */ - Dictionary(const char *s) - { - fromString(s); - } + Dictionary(const char *s) { fromString(s); } /** * @param s String-serialized dictionary */ - Dictionary(const std::string &s) - { - fromString(s.c_str()); - } + Dictionary(const std::string &s) { fromString(s.c_str()); } /** * Get a key, throwing an exception if it is not present @@ -102,10 +109,7 @@ public: * @param key Key to check * @return True if dictionary contains key */ - inline bool contains(const std::string &key) const - { - return (find(key) != end()); - } + inline bool contains(const std::string &key) const { return (find(key) != end()); } /** * @return String-serialized dictionary @@ -113,14 +117,12 @@ public: inline std::string toString() const { std::string s; - for(const_iterator kv(begin());kv!=end();++kv) { _appendEsc(kv->first.data(),(unsigned int)kv->first.length(),s); s.push_back('='); _appendEsc(kv->second.data(),(unsigned int)kv->second.length(),s); s.append(ZT_EOL_S); } - return s; } @@ -129,78 +131,43 @@ public: * * @param s String-serialized dictionary */ - inline void fromString(const char *s) - { - clear(); - bool escapeState = false; - std::string keyBuf; - std::string *element = &keyBuf; - while (*s) { - if (escapeState) { - escapeState = false; - switch(*s) { - case '0': - element->push_back((char)0); - break; - case 'r': - element->push_back('\r'); - break; - case 'n': - element->push_back('\n'); - break; - default: - element->push_back(*s); - break; - } - } else { - if (*s == '\\') { - escapeState = true; - } else if (*s == '=') { - if (element == &keyBuf) - element = &((*this)[keyBuf]); - } else if ((*s == '\r')||(*s == '\n')) { - if ((element == &keyBuf)&&(keyBuf.length() > 0)) - (*this)[keyBuf]; - keyBuf = ""; - element = &keyBuf; - } else element->push_back(*s); - } - ++s; - } - if ((element == &keyBuf)&&(keyBuf.length() > 0)) - (*this)[keyBuf]; - } - inline void fromString(const std::string &s) + void fromString(const char *s); + inline void fromString(const std::string &s) { fromString(s.c_str()); } + + /** + * @return True if this dictionary is cryptographically signed + */ + inline bool hasSignature() const { return (find(ZT_DICTIONARY_SIGNATURE) != end()); } + + /** + * Remove any signature from this dictionary + */ + inline void removeSignature() { - fromString(s.c_str()); + erase(ZT_DICTIONARY_SIGNATURE); + erase(ZT_DICTIONARY_SIGNATURE_IDENTITY); + erase(ZT_DICTIONARY_SIGNATURE_TIMESTAMP); } + /** + * Add or update signature fields with a signature of all other keys and values + * + * @param with Identity to sign with (must have secret key) + * @return True on success + */ + bool sign(const Identity &id); + + /** + * Verify signature against an identity + * + * @param id Identity to verify against + * @return True if signature verification OK + */ + bool verify(const Identity &id) const; + private: - static inline void _appendEsc(const char *data,unsigned int len,std::string &to) - { - for(unsigned int i=0;i<len;++i) { - switch(data[i]) { - case 0: - to.append("\\0"); - break; - case '\r': - to.append("\\r"); - break; - case '\n': - to.append("\\n"); - break; - case '\\': - to.append("\\\\"); - break; - case '=': - to.append("\\="); - break; - default: - to.push_back(data[i]); - break; - } - } - } + void _mkSigBuf(std::string &buf) const; + static void _appendEsc(const char *data,unsigned int len,std::string &to); }; } // namespace ZeroTier diff --git a/node/Utils.cpp b/node/Utils.cpp index a1cdc8df..b1912198 100644 --- a/node/Utils.cpp +++ b/node/Utils.cpp @@ -97,12 +97,16 @@ std::string Utils::hex(const void *data,unsigned int len) return r; } -std::string Utils::unhex(const char *hex) +std::string Utils::unhex(const char *hex,unsigned int maxlen) { int n = 1; unsigned char c,b = 0; + const char *eof = hex + maxlen; std::string r; + if (!maxlen) + return r; + while ((c = (unsigned char)*(hex++))) { if ((c >= 48)&&(c <= 57)) { // 0..9 if ((n ^= 1)) @@ -117,48 +121,24 @@ std::string Utils::unhex(const char *hex) r.push_back((char)(b | (c - (97 - 10)))); else b = (c - (97 - 10)) << 4; } + if (hex == eof) + break; } return r; } -unsigned int Utils::unhex(const char *hex,void *buf,unsigned int len) +unsigned int Utils::unhex(const char *hex,unsigned int maxlen,void *buf,unsigned int len) { int n = 1; unsigned char c,b = 0; unsigned int l = 0; + const char *eof = hex + maxlen; - while ((c = (unsigned char)*(hex++))) { - if ((c >= 48)&&(c <= 57)) { // 0..9 - if ((n ^= 1)) { - if (l >= len) break; - ((unsigned char *)buf)[l++] = (b | (c - 48)); - } else b = (c - 48) << 4; - } else if ((c >= 65)&&(c <= 70)) { // A..F - if ((n ^= 1)) { - if (l >= len) break; - ((unsigned char *)buf)[l++] = (b | (c - (65 - 10))); - } else b = (c - (65 - 10)) << 4; - } else if ((c >= 97)&&(c <= 102)) { // a..f - if ((n ^= 1)) { - if (l >= len) break; - ((unsigned char *)buf)[l++] = (b | (c - (97 - 10))); - } else b = (c - (97 - 10)) << 4; - } - } - - return l; -} - -unsigned int Utils::unhex(const char *hex,unsigned int hexlen,void *buf,unsigned int len) -{ - int n = 1; - unsigned char c,b = 0; - unsigned int l = 0; - const char *const end = hex + hexlen; + if (!maxlen) + return 0; - while (hex != end) { - c = (unsigned char)*(hex++); + while ((c = (unsigned char)*(hex++))) { if ((c >= 48)&&(c <= 57)) { // 0..9 if ((n ^= 1)) { if (l >= len) break; @@ -175,6 +155,8 @@ unsigned int Utils::unhex(const char *hex,unsigned int hexlen,void *buf,unsigned ((unsigned char *)buf)[l++] = (b | (c - (97 - 10))); } else b = (c - (97 - 10)) << 4; } + if (hex == eof) + break; } return l; diff --git a/node/Utils.hpp b/node/Utils.hpp index 4298d045..bafe0b39 100644 --- a/node/Utils.hpp +++ b/node/Utils.hpp @@ -145,11 +145,12 @@ public: * This ignores all non-hex characters, just stepping over them and * continuing. Upper and lower case are supported for letters a-f. * - * @param hex Hexadecimal ASCII code (non-hex chars are ignored) + * @param hex Hexadecimal ASCII code (non-hex chars are ignored, stops at zero or maxlen) + * @param maxlen Maximum length of hex string buffer * @return Binary data */ - static std::string unhex(const char *hex); - static inline std::string unhex(const std::string &hex) { return unhex(hex.c_str()); } + static std::string unhex(const char *hex,unsigned int maxlen); + static inline std::string unhex(const std::string &hex) { return unhex(hex.c_str(),(unsigned int)hex.length()); } /** * Convert hexadecimal to binary data @@ -158,26 +159,13 @@ public: * continuing. Upper and lower case are supported for letters a-f. * * @param hex Hexadecimal ASCII + * @param maxlen Maximum length of hex string buffer * @param buf Buffer to fill * @param len Length of buffer * @return Number of characters actually written */ - static unsigned int unhex(const char *hex,void *buf,unsigned int len); - static inline unsigned int unhex(const std::string &hex,void *buf,unsigned int len) { return unhex(hex.c_str(),buf,len); } - - /** - * Convert hexadecimal to binary data - * - * This ignores all non-hex characters, just stepping over them and - * continuing. Upper and lower case are supported for letters a-f. - * - * @param hex Hexadecimal ASCII - * @param hexlen Length of hex ASCII - * @param buf Buffer to fill - * @param len Length of buffer - * @return Number of bytes actually written to buffer - */ - static unsigned int unhex(const char *hex,unsigned int hexlen,void *buf,unsigned int len); + static unsigned int unhex(const char *hex,unsigned int maxlen,void *buf,unsigned int len); + static inline unsigned int unhex(const std::string &hex,void *buf,unsigned int len) { return unhex(hex.c_str(),hex.length(),buf,len); } /** * Generate secure random bytes @@ -3,6 +3,7 @@ OBJS=\ node/C25519.o \ node/CertificateOfMembership.o \ node/Defaults.o \ + node/Dictionary.o \ node/HttpClient.o \ node/Identity.o \ node/InetAddress.o \ |