diff options
Diffstat (limited to 'node/EllipticCurveKeyPair.cpp')
-rw-r--r-- | node/EllipticCurveKeyPair.cpp | 374 |
1 files changed, 374 insertions, 0 deletions
diff --git a/node/EllipticCurveKeyPair.cpp b/node/EllipticCurveKeyPair.cpp new file mode 100644 index 00000000..bed0725e --- /dev/null +++ b/node/EllipticCurveKeyPair.cpp @@ -0,0 +1,374 @@ +/* + * ZeroTier One - Global Peer to Peer Ethernet + * Copyright (C) 2012-2013 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 <iostream> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include <openssl/bn.h> +#include <openssl/obj_mac.h> +#include <openssl/rand.h> +#include <openssl/ec.h> +#include <openssl/ecdh.h> +#include <openssl/ecdsa.h> +#include <openssl/sha.h> + +#include "EllipticCurveKey.hpp" +#include "EllipticCurveKeyPair.hpp" + +namespace ZeroTier { + +class _EC_Group +{ +public: + _EC_Group() + { + g = EC_GROUP_new_by_curve_name(ZT_EC_OPENSSL_CURVE); + } + ~_EC_Group() {} + EC_GROUP *g; +}; +static _EC_Group ZT_EC_GROUP; + +/* Key derivation function */ +static void *_zt_EC_KDF(const void *in,size_t inlen,void *out,size_t *outlen) +{ + SHA256_CTX sha; + unsigned char dig[SHA256_DIGEST_LENGTH]; + + SHA256_Init(&sha); + SHA256_Update(&sha,(const unsigned char *)in,inlen); + SHA256_Final(dig,&sha); + for(unsigned long i=0,k=0;i<(unsigned long)*outlen;) { + if (k == SHA256_DIGEST_LENGTH) { + k = 0; + SHA256_Init(&sha); + SHA256_Update(&sha,(const unsigned char *)in,inlen); + SHA256_Update(&sha,dig,SHA256_DIGEST_LENGTH); + SHA256_Final(dig,&sha); + } + ((unsigned char *)out)[i++] = dig[k++]; + } + + return out; +} + +EllipticCurveKeyPair::EllipticCurveKeyPair() : + _pub(), + _priv(), + _internal_key((void *)0) +{ +} + +EllipticCurveKeyPair::EllipticCurveKeyPair(const EllipticCurveKeyPair &pair) : + _pub(pair._pub), + _priv(pair._priv), + _internal_key((void *)0) +{ +} + +EllipticCurveKeyPair::EllipticCurveKeyPair(const EllipticCurveKey &pubk,const EllipticCurveKey &privk) : + _pub(pubk), + _priv(privk), + _internal_key((void *)0) +{ +} + +EllipticCurveKeyPair::~EllipticCurveKeyPair() +{ + if (_internal_key) + EC_KEY_free((EC_KEY *)_internal_key); +} + +const EllipticCurveKeyPair &EllipticCurveKeyPair::operator=(const EllipticCurveKeyPair &pair) +{ + if (_internal_key) + EC_KEY_free((EC_KEY *)_internal_key); + _pub = pair._pub; + _priv = pair._priv; + _internal_key = (void *)0; + return *this; +} + +bool EllipticCurveKeyPair::generate() +{ + unsigned char tmp[16384]; + EC_KEY *key; + int len; + + // Make sure OpenSSL libcrypto has sufficient randomness (on most + // platforms it auto-seeds, so this is a sanity check). + if (!RAND_status()) { +#if defined(__APPLE__) || defined(__linux__) || defined(linux) || defined(__LINUX__) || defined(__linux) + FILE *rf = fopen("/dev/urandom","r"); + if (rf) { + fread(tmp,sizeof(tmp),1,rf); + fclose(rf); + } else { + fprintf(stderr,"WARNING: cannot open /dev/urandom\n"); + for(unsigned int i=0;i<sizeof(tmp);++i) + tmp[i] = (unsigned char)(rand() >> 3); + } + RAND_seed(tmp,sizeof(tmp)); +#else +#ifdef _WIN32 + error need win32; +#else + error; +#endif +#endif + } + + key = EC_KEY_new(); + if (!key) return false; + + if (!EC_KEY_set_group(key,ZT_EC_GROUP.g)) { + EC_KEY_free(key); + return false; + } + + if (!EC_KEY_generate_key(key)) { + EC_KEY_free(key); + return false; + } + + memset(_priv._key,0,sizeof(_priv._key)); + len = BN_num_bytes(EC_KEY_get0_private_key(key)); + if ((len > ZT_EC_PRIME_BYTES)||(len < 0)) { + EC_KEY_free(key); + return false; + } + BN_bn2bin(EC_KEY_get0_private_key(key),&(_priv._key[ZT_EC_PRIME_BYTES - len])); + _priv._bytes = ZT_EC_PRIME_BYTES; + + memset(_pub._key,0,sizeof(_pub._key)); + len = EC_POINT_point2oct(ZT_EC_GROUP.g,EC_KEY_get0_public_key(key),POINT_CONVERSION_COMPRESSED,_pub._key,sizeof(_pub._key),0); + if (len != ZT_EC_PUBLIC_KEY_BYTES) { + EC_KEY_free(key); + return false; + } + _pub._bytes = ZT_EC_PUBLIC_KEY_BYTES; + + if (_internal_key) + EC_KEY_free((EC_KEY *)_internal_key); + _internal_key = key; + + return true; +} + +bool EllipticCurveKeyPair::agree(const EllipticCurveKey &theirPublicKey,unsigned char *agreedUponKey,unsigned int agreedUponKeyLength) const +{ + if (theirPublicKey._bytes != ZT_EC_PUBLIC_KEY_BYTES) + return false; + + if (!_internal_key) { + if (!(const_cast <EllipticCurveKeyPair *> (this))->initInternalKey()) + return false; + } + + EC_POINT *pub = EC_POINT_new(ZT_EC_GROUP.g); + if (!pub) + return false; + EC_POINT_oct2point(ZT_EC_GROUP.g,pub,theirPublicKey._key,ZT_EC_PUBLIC_KEY_BYTES,0); + + int i = ECDH_compute_key(agreedUponKey,agreedUponKeyLength,pub,(EC_KEY *)_internal_key,&_zt_EC_KDF); + EC_POINT_free(pub); + + return (i == (int)agreedUponKeyLength); +} + +std::string EllipticCurveKeyPair::sign(const void *sha256) const +{ + unsigned char buf[256]; + std::string sigbin; + + if (!_internal_key) { + if (!(const_cast <EllipticCurveKeyPair *> (this))->initInternalKey()) + return std::string(); + } + + ECDSA_SIG *sig = ECDSA_do_sign((const unsigned char *)sha256,SHA256_DIGEST_LENGTH,(EC_KEY *)_internal_key); + if (!sig) + return std::string(); + + int rlen = BN_num_bytes(sig->r); + if ((rlen > 255)||(rlen <= 0)) { + ECDSA_SIG_free(sig); + return std::string(); + } + sigbin.push_back((char)rlen); + BN_bn2bin(sig->r,buf); + sigbin.append((const char *)buf,rlen); + + int slen = BN_num_bytes(sig->s); + if ((slen > 255)||(slen <= 0)) { + ECDSA_SIG_free(sig); + return std::string(); + } + sigbin.push_back((char)slen); + BN_bn2bin(sig->s,buf); + sigbin.append((const char *)buf,slen); + + ECDSA_SIG_free(sig); + + return sigbin; +} + +std::string EllipticCurveKeyPair::sign(const void *data,unsigned int len) const +{ + SHA256_CTX sha; + unsigned char dig[SHA256_DIGEST_LENGTH]; + + SHA256_Init(&sha); + SHA256_Update(&sha,(const unsigned char *)data,len); + SHA256_Final(dig,&sha); + + return sign(dig); +} + +bool EllipticCurveKeyPair::verify(const void *sha256,const EllipticCurveKey &pk,const void *sigbytes,unsigned int siglen) +{ + bool result = false; + ECDSA_SIG *sig = (ECDSA_SIG *)0; + EC_POINT *pub = (EC_POINT *)0; + EC_KEY *key = (EC_KEY *)0; + int rlen,slen; + + if (!siglen) + goto verify_sig_return; + rlen = ((const unsigned char *)sigbytes)[0]; + if (!rlen) + goto verify_sig_return; + if (siglen < (unsigned int)(rlen + 2)) + goto verify_sig_return; + slen = ((const unsigned char *)sigbytes)[rlen + 1]; + if (!slen) + goto verify_sig_return; + if (siglen < (unsigned int)(rlen + slen + 2)) + goto verify_sig_return; + + sig = ECDSA_SIG_new(); + if (!sig) + goto verify_sig_return; + + BN_bin2bn((const unsigned char *)sigbytes + 1,rlen,sig->r); + BN_bin2bn((const unsigned char *)sigbytes + (1 + rlen + 1),slen,sig->s); + + pub = EC_POINT_new(ZT_EC_GROUP.g); + if (!pub) + goto verify_sig_return; + EC_POINT_oct2point(ZT_EC_GROUP.g,pub,pk._key,ZT_EC_PUBLIC_KEY_BYTES,0); + + key = EC_KEY_new(); + if (!key) + goto verify_sig_return; + if (!EC_KEY_set_group(key,ZT_EC_GROUP.g)) + goto verify_sig_return; + EC_KEY_set_public_key(key,pub); + + result = (ECDSA_do_verify((const unsigned char *)sha256,SHA256_DIGEST_LENGTH,sig,key) == 1); + +verify_sig_return: + if (key) + EC_KEY_free(key); + if (pub) + EC_POINT_free(pub); + if (sig) + ECDSA_SIG_free(sig); + + return result; +} + +bool EllipticCurveKeyPair::verify(const void *data,unsigned int len,const EllipticCurveKey &pk,const void *sigbytes,unsigned int siglen) +{ + SHA256_CTX sha; + unsigned char dig[SHA256_DIGEST_LENGTH]; + + SHA256_Init(&sha); + SHA256_Update(&sha,(const unsigned char *)data,len); + SHA256_Final(dig,&sha); + + return verify(dig,pk,sigbytes,siglen); +} + +bool EllipticCurveKeyPair::initInternalKey() +{ + EC_KEY *key; + EC_POINT *kxy; + BIGNUM *pn; + + if (_priv._bytes != ZT_EC_PRIME_BYTES) return false; + if (_pub._bytes != ZT_EC_PUBLIC_KEY_BYTES) return false; + + key = EC_KEY_new(); + if (!key) return false; + + if (!EC_KEY_set_group(key,ZT_EC_GROUP.g)) { + EC_KEY_free(key); + return false; + } + + pn = BN_new(); + if (!pn) { + EC_KEY_free(key); + return false; + } + if (!BN_bin2bn(_priv._key,ZT_EC_PRIME_BYTES,pn)) { + BN_free(pn); + EC_KEY_free(key); + return false; + } + if (!EC_KEY_set_private_key(key,pn)) { + BN_free(pn); + EC_KEY_free(key); + return false; + } + BN_free(pn); + + kxy = EC_POINT_new(ZT_EC_GROUP.g); + if (!kxy) { + EC_KEY_free(key); + return false; + } + EC_POINT_oct2point(ZT_EC_GROUP.g,kxy,_pub._key,ZT_EC_PUBLIC_KEY_BYTES,0); + if (!EC_KEY_set_public_key(key,kxy)) { + EC_POINT_free(kxy); + EC_KEY_free(key); + return false; + } + EC_POINT_free(kxy); + + if (_internal_key) + EC_KEY_free((EC_KEY *)_internal_key); + _internal_key = key; + + return true; +} + +} // namespace ZeroTier + |