diff --git a/libretroshare/src/libretroshare.pro b/libretroshare/src/libretroshare.pro index 277eb9419..246fb005f 100644 --- a/libretroshare/src/libretroshare.pro +++ b/libretroshare/src/libretroshare.pro @@ -477,6 +477,8 @@ HEADERS += util/folderiterator.h \ util/rswin.h \ util/rsrandom.h \ util/pugiconfig.h \ + util/radix64.h \ + util/pgpkey.h \ util/pugixml.h SOURCES += dbase/cachestrapper.cc \ @@ -616,6 +618,7 @@ SOURCES += util/folderiterator.cc \ util/rsversion.cc \ util/rswin.cc \ util/rsrandom.cc \ + util/pgpkey.cc \ util/pugixml.cc diff --git a/libretroshare/src/tests/general/Makefile b/libretroshare/src/tests/general/Makefile index a4d97f6a0..3b771b799 100644 --- a/libretroshare/src/tests/general/Makefile +++ b/libretroshare/src/tests/general/Makefile @@ -8,11 +8,14 @@ DHT_TOP_DIR = ../../../../libbitdht/src include $(RS_TOP_DIR)/tests/scripts/config.mk ############################################################### -TESTOBJ = netsetup_test.o random_test.o memory_management_test.o -TESTS = netsetup_test random_test memory_management_test +TESTOBJ = netsetup_test.o random_test.o memory_management_test.o pgpkey_test.o +TESTS = netsetup_test random_test memory_management_test pgpkey_test all: tests +pgpkey_test: pgpkey_test.o + $(CC) $(CFLAGS) -o pgpkey_test pgpkey_test.o $(LIBS) + netsetup_test: netsetup_test.o $(CC) $(CFLAGS) -o netsetup_test netsetup_test.o $(LIBS) diff --git a/libretroshare/src/tests/general/pgpkey_test.cc b/libretroshare/src/tests/general/pgpkey_test.cc new file mode 100644 index 000000000..8f9de261e --- /dev/null +++ b/libretroshare/src/tests/general/pgpkey_test.cc @@ -0,0 +1,59 @@ +#ifdef LINUX +#include +#endif +#include +#include +#include +#include +#include +#include "util/utest.h" +#include "util/pgpkey.h" + +INITTEST(); + +int main(int argc, char **argv) +{ +#ifdef LINUX + feenableexcept(FE_INVALID) ; + feenableexcept(FE_DIVBYZERO) ; +#endif + try + { + if(argc < 2) + { + std::cerr << argv[0] << ": test gpg certificate cleaning method. " << std::endl; + std::cerr << " Usage: " << argv[0] << " certificate.asc" << std::endl; + return 0 ; + } + + FILE *f = fopen(argv[1],"r") ; + + if(f == NULL) + throw std::runtime_error(std::string("Could not open file ") + argv[1]) ; + + std::string cert ; + int c ; + + while((c = getc(f) ) != EOF) + cert += (char)c ; + + std::cerr << "got this certificate: " << std::endl; + std::cerr << cert << std::endl; + + std::cerr << "Calling cert simplification code..." << std::endl; + std::string cleaned_key ; + + PGPKeyManagement::createMinimalKey(cert,cleaned_key) ; + + std::cerr << "Minimal key produced: " << std::endl; + std::cerr << cleaned_key << std::endl; + FINALREPORT("pgpkey_test"); + exit(TESTRESULT()); + } + catch(std::exception& e) + { + std::cerr << "Exception never handled: " << e.what() << std::endl ; + return 1 ; + } +} + diff --git a/libretroshare/src/util/pgpkey.cc b/libretroshare/src/util/pgpkey.cc new file mode 100644 index 000000000..b815efd21 --- /dev/null +++ b/libretroshare/src/util/pgpkey.cc @@ -0,0 +1,227 @@ +#include +#include +#include "pgpkey.h" + +#include +#include + +#define PGP_PACKET_TAG_PUBLIC_KEY 6 +#define PGP_PACKET_TAG_USER_ID 13 +#define PGP_PACKET_TAG_SIGNATURE 2 + +#define PGP_CRC24_INIT 0xB704CEL +#define PGP_CRC24_POLY 0x1864CFBL + +#define PGP_CERTIFICATE_START_STRING "-----BEGIN PGP PUBLIC KEY BLOCK-----" +#define PGP_CERTIFICATE_END_STRING "-----END PGP PUBLIC KEY BLOCK-----" +// +// All size are big endian +// MPI: 2 bytes size (length in bits) + string of octets +// +bool PGPKeyManagement::createMinimalKey(const std::string& pgp_certificate,std::string& cleaned_certificate) +{ + try + { + // 0 - Extract Radix64 portion of the certificate + // + int n = pgp_certificate.length() ; + int i=0 ; + std::string version_string = "" ; + + while(i < n && pgp_certificate[i] != '\n') ++i ; // remove first part -----BEGIN PGP CERTIFICATE----- + ++i ; + while(i < n && pgp_certificate[i] != '\n') version_string += pgp_certificate[i++] ; // remove first part Version: [fdfdfdf] + ++i ; + while(i < n && pgp_certificate[i] != '\n') ++i ; // remove blank line + + ++i ; + + int j=n-1 ; + + while(j>0 && pgp_certificate[j] != '=' && j>=i) --j ; + + std::string radix_cert = pgp_certificate.substr(i,j-i) ; + + std::cerr << "extracted radix cert: " << std::endl; + std::cerr << radix_cert ; + + // 1 - Convert armored key into binary key + // + + char *keydata = NULL ; + size_t len = 0 ; + + Radix64::decode(radix_cert,keydata,len) ; + + unsigned char *data = (unsigned char *)keydata ; + + std::cerr << "Total size: " << len << std::endl; + + uint8_t packet_tag; + uint32_t packet_length ; + + // 2 - parse key data, only keep public key data, user id and self-signature. + + bool public_key=false ; + bool own_signature=false ; + bool user_id=false ; + + while(true) + { + PGPKeyParser::read_packetHeader(data,packet_tag,packet_length) ; + + std::cerr << "Header:" << std::endl; + std::cerr << " Packet tag: " << (int)packet_tag << std::endl; + std::cerr << " Packet length: " << packet_length << std::endl; + + data += packet_length ; + + if(packet_tag == PGP_PACKET_TAG_PUBLIC_KEY) + public_key = true ; + if(packet_tag == PGP_PACKET_TAG_USER_ID) + user_id = true ; + if(packet_tag == PGP_PACKET_TAG_SIGNATURE) + own_signature = true ; + + if(public_key && own_signature && user_id) + break ; + + if( (uint64_t)data - (uint64_t)keydata >= len ) + break ; + } + + std::string outstring ; + Radix64::encode(keydata,(uint64_t)data - (uint64_t)keydata,outstring) ; + + uint32_t crc = compute24bitsCRC((unsigned char *)keydata,(uint64_t)data - (uint64_t)keydata) ; + + unsigned char tmp[3] = { (crc >> 16) & 0xff, (crc >> 8) & 0xff, crc & 0xff } ; + std::string crc_string ; + Radix64::encode((const char *)tmp,3,crc_string) ; + + std::cerr << "After signature pruning: " << std::endl; + std::cerr << outstring << std::endl; + + cleaned_certificate = std::string(PGP_CERTIFICATE_START_STRING) + "\n" + version_string + "\n\n" ; + + for(uint32_t i=0;i> 2 ; + + int length_size ; + switch(length_type) + { + case 0: length_size = 1 ; + break ; + case 1: length_size = 2 ; + break ; + case 2: length_size = 4 ; + break ; + default: + throw std::runtime_error("Unhandled length type!") ; + } + + packet_length = 0 ; + for(int k=0;k + * + * 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 2 + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + ****************************************************************/ + +#pragma once + +// refer to RFC4880 specif document for loading GPG public keys: +// +// 11.1: transferable public keys +// Global structure of transferable public keys +// +// - one public key packet (see 12.2) +// - zero or more revocation signatures (See signature type 5.2.1 for key signature types) +// +// - user certification signatures (0x10 or 0x13) +// +// - 5.2.2: Signature format packet +// - 5.2.3.1: signature subpacket specification +// +// - 4.3: packet tags (1 byte) + +#pragma once + +#include +#include + +// This class handles GPG keys. For now we only clean them from signatures, but +// in the future, we might cache them to avoid unnecessary calls to gpgme. +// +class PGPKeyManagement +{ + public: + // Create a minimal key, removing all signatures and third party info. + // Input: a clean PGP certificate (starts with "----BEGIN", + // ends with "-----END PGP PUBLIC KEY BLOCK-----" + // Output: the same certificate without signatures. + // + // Returns: + // + // true if the certificate cleaning succeeded + // false otherwise. + // + static bool createMinimalKey(const std::string& pgp_certificate,std::string& cleaned_certificate) ; + + private: + // Computes the 24 bits CRC checksum necessary to all PGP data. + // + static uint32_t compute24bitsCRC(unsigned char *data,size_t len) ; +}; + +// This class handles the parsing of PGP packet headers under various (old and new) formats. +// +class PGPKeyParser +{ + public: + static uint64_t read_KeyID(unsigned char *& data) ; + static uint32_t read_125Size(unsigned char *& data) ; + static uint32_t read_partialBodyLength(unsigned char *& data) ; + static void read_packetHeader(unsigned char *& data,uint8_t& packet_tag,uint32_t& packet_length) ; +}; + + diff --git a/libretroshare/src/util/radix64.h b/libretroshare/src/util/radix64.h new file mode 100644 index 000000000..441ecc01f --- /dev/null +++ b/libretroshare/src/util/radix64.h @@ -0,0 +1,179 @@ +#pragma once + +#include +#include +#include + +class Radix64 +{ + public: + static void decode(const std::string& buffer,char *& out, size_t& len) + { + char val; + int c = 0, c2;/* init c because gcc is not clever + enough for the continue */ + int idx; + size_t buffer_pos = 0; + + radix64_init(); + + std::vector buf ; + idx = 0; + val = 0; + + for (buffer_pos = 0; buffer_pos < buffer.length(); buffer_pos++) + { + c = buffer[buffer_pos]; + +again: + if (c == '\n' || c == ' ' || c == '\r' || c == '\t') + continue; + else if (c == '=') + { + /* pad character: stop */ + /* some mailers leave quoted-printable + * encoded characters so we try to + * workaround this */ + if (buffer_pos + 2 < buffer.length()) + { + int cc1, cc2, cc3; + cc1 = buffer[buffer_pos]; + cc2 = buffer[buffer_pos + 1]; + cc3 = buffer[buffer_pos + 2]; + + if (isxdigit((unsigned char)cc1) && isxdigit((unsigned char)cc2) && strchr("=\n\r\t ", cc3)) + { + /* well it seems to be the case - + * adjust */ + c = + isdigit((unsigned char)cc1) ? (cc1 - + '0') + : (toupper((unsigned char)cc1) - 'A' + 10); + c <<= 4; + c |= + isdigit((unsigned char)cc2) ? (cc2 - + '0') + : (toupper((unsigned char)cc2) - 'A' + 10); + buffer_pos += 2; + goto again; + } + } + + if (idx == 1) + buf.push_back(val) ;// buf[n++] = val; + break; + } + else if ((c = asctobin()[(c2 = c)]) == 255) + { + /* invalid radix64 character %02x skipped\n", c2; */ + continue; + } + + switch (idx) + { + case 0: + val = c << 2; + break; + case 1: + val |= (c >> 4) & 3; + buf.push_back(val);//buf[n++] = val; + val = (c << 4) & 0xf0; + break; + case 2: + val |= (c >> 2) & 15; + buf.push_back(val);//buf[n++] = val; + val = (c << 6) & 0xc0; + break; + case 3: + val |= c & 0x3f; + buf.push_back(val);//buf[n++] = val; + break; + } + idx = (idx + 1) % 4; + } + + idx = idx; + + len = buf.size() ; + out = new char[len] ; + + memcpy(out,buf.data(),len) ; + } + + /**************** + * create a radix64 encoded string. + */ + static void encode(const char *data,int len,std::string& out_string) + { + char *buffer, *p; + + radix64_init(); + + int size = (len + 2) / 3 * 4 +1; + buffer = p = new char[size] ; + + for (; len >= 3; len -= 3, data += 3) + { + *p++ = bintoasc()[(data[0] >> 2) & 077]; + *p++ = + bintoasc()[ + (((data[0] << 4) & 060) | + ((data[1] >> 4) & 017)) & 077]; + *p++ = + bintoasc()[ + (((data[1] << 2) & 074) | + ((data[2] >> 6) & 03)) & 077]; + *p++ = bintoasc()[data[2] & 077]; + } + if (len == 2) + { + *p++ = bintoasc()[(data[0] >> 2) & 077]; + *p++ = + bintoasc()[ + (((data[0] << 4) & 060) | + ((data[1] >> 4) & 017)) & 077]; + *p++ = bintoasc()[((data[1] << 2) & 074)]; + *p++ = '=' ; + } + else if (len == 1) + { + *p++ = bintoasc()[(data[0] >> 2) & 077]; + *p++ = bintoasc()[(data[0] << 4) & 060]; + *p++ = '=' ; + *p++ = '=' ; + } + //*p = 0; + out_string = std::string(buffer,p-buffer) ; + delete[] buffer ; + } + + private: + static inline char *bintoasc() { static char bta[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; return bta ; } + static inline char *asctobin() { static char s[256]; return s ; } /* runtime radix64_initd */ + static int& is_radix64_initd() { static int is_inited = false ; return is_inited ; } + + /* hey, guess what: this is a read-only table. + * we don't _care_ if multiple threads get to initialise it + * at the same time, _except_ that is_radix64_initd=1 _must_ + * be done at the end... + */ + static bool radix64_init() + { + if (is_radix64_initd()) + return true; + + int i; + char *s; + + /* build the helpapr_table_t for radix64 to bin conversion */ + for (i = 0; i < 256; i++) + asctobin()[i] = 255; /* used to detect invalid characters */ + for (s = bintoasc(), i = 0; *s; s++, i++) + asctobin()[(int)*s] = i; + + is_radix64_initd() = 1; + return true ; + } +}; + +