diff --git a/INSTALL b/INSTALL index 0bd4da8..19399c9 100644 --- a/INSTALL +++ b/INSTALL @@ -20,3 +20,13 @@ The Makefile is tailored for g++, but should work with other compilers. It doesn't matter where you install the git-crypt binary - choose wherever is most convenient for you. + +BUILDING ON WINDOWS + + * Install mingw + * Download OpenSSL for Windows + * Put libeay32.lib on mingw lib folder + * Put openssl header´s folder on mingw include folder + + $ make + diff --git a/Makefile b/Makefile index c5f339b..9aff2dc 100644 --- a/Makefile +++ b/Makefile @@ -5,15 +5,32 @@ PREFIX := /usr/local OBJFILES = git-crypt.o commands.o crypto.o util.o +INSTALL = install -m 755 git-crypt $(PREFIX)/bin/ + +ifeq ($(OS),Windows_NT) + LDFLAGS = -llibeay32 -lwsock32 +# CXXFLAGS += -static-libgcc + CXXFLAGS += -static-libgcc -static-libstdc++ + OBJFILES = git-crypt.o commands.o crypto.o util_win32.o + INSTALL = cp git-crypt.exe $(PREFIX)/bin/ +endif + all: git-crypt git-crypt: $(OBJFILES) $(CXX) $(CXXFLAGS) -o $@ $^ $(LDFLAGS) clean: - rm -f *.o git-crypt + rm -f *.o git-crypt git-crypt.exe \ + rm -fr test install: - install -m 755 git-crypt $(PREFIX)/bin/ + $(INSTALL) + +test: + ./test.sh + +strip: + strip git-crypt.exe -.PHONY: all clean install +.PHONY: all clean install test strip diff --git a/commands.cpp b/commands.cpp index b3180c5..fe6d246 100644 --- a/commands.cpp +++ b/commands.cpp @@ -28,6 +28,7 @@ * as that of the covered work. */ + #include "commands.hpp" #include "crypto.hpp" #include "util.hpp" @@ -45,6 +46,13 @@ #include #include +#ifdef __WIN32__ +#define system(a) win32_system(a) +#else +typedef std::fstream temp_fstream; +#endif + + // Encrypt contents of stdin and write to stdout void clean (const char* keyfile) { @@ -56,7 +64,7 @@ void clean (const char* keyfile) hmac_sha1_state hmac(keys.hmac, HMAC_KEY_LEN); // Calculate the file's SHA1 HMAC as we go uint64_t file_size = 0; // Keep track of the length, make sure it doesn't get too big std::string file_contents; // First 8MB or so of the file go here - std::fstream temp_file; // The rest of the file spills into a temporary file on disk + temp_fstream temp_file; // The rest of the file spills into a temporary file on disk temp_file.exceptions(std::fstream::badbit); char buffer[1024]; @@ -163,7 +171,7 @@ void diff (const char* keyfile, const char* filename) load_keys(keyfile, &keys); // Open the file - std::ifstream in(filename); + std::ifstream in(filename, std::ios::binary); if (!in) { perror(filename); std::exit(1); @@ -234,7 +242,7 @@ void init (const char* argv0, const char* keyfile) // git config filter.git-crypt.smudge "git-crypt smudge /path/to/key" std::string command("git config filter.git-crypt.smudge "); command += escape_shell_arg(escape_shell_arg(git_crypt_path) + " smudge " + escape_shell_arg(keyfile_path)); - + if (system(command.c_str()) != 0) { std::clog << "git config failed\n"; std::exit(1); diff --git a/crypto.cpp b/crypto.cpp index e1a8594..29da246 100644 --- a/crypto.cpp +++ b/crypto.cpp @@ -1,4 +1,4 @@ -/* +/* * Copyright 2012 Andrew Ayer * * This file is part of git-crypt. @@ -38,17 +38,22 @@ #include #include #include +#ifdef __WIN32__ +#include +#else #include +#endif void load_keys (const char* filepath, keys_t* keys) { - std::ifstream file(filepath); + std::ifstream file(filepath, std::ios::binary); if (!file) { perror(filepath); std::exit(1); } char buffer[AES_KEY_BITS/8 + HMAC_KEY_LEN]; file.read(buffer, sizeof(buffer)); + if (file.gcount() != sizeof(buffer)) { std::clog << filepath << ": Premature end of key file\n"; std::exit(1); diff --git a/git-crypt.cpp b/git-crypt.cpp index bd58391..11d88d4 100644 --- a/git-crypt.cpp +++ b/git-crypt.cpp @@ -58,6 +58,10 @@ try { std::cin.exceptions(std::ios_base::badbit); std::cout.exceptions(std::ios_base::badbit); +#ifdef __WIN32__ + set_cin_cout_binary_mode(); +#endif + if (argc < 3) { print_usage(argv[0]); return 2; diff --git a/test.sh b/test.sh new file mode 100755 index 0000000..dfd9ca0 --- /dev/null +++ b/test.sh @@ -0,0 +1,161 @@ +#!/usr/bin/perl +use strict; +use warnings; +use Cwd; +use File::Path; +use File::Copy; +use Term::ANSIColor; +use Digest::MD5 qw(md5); + +print "\n"."========= GIT-CRYPT TEST ========="."\n"; + +my $cdir=Cwd::cwd(); +my $dir=$cdir."/test"; + +### Set test folder +if ( -d $dir ) { + rmtree($dir) or die "Cannot remove '$dir' : $!"; +} +mkpath($dir) or die "Cannot create '$dir' : $!"; +chdir($dir); + +### Generate git-crypt key +my $key="test.key"; + +print "\n"; +`git-crypt keygen $key`; !($?) or die; + +### Create git repository + +print "\n"."========= CREATE GIT ORIGIN REPO ========="."\n"; + +my $repo="repo"; +mkpath($repo) or die "Cannot create '$repo' : $!"; +chdir($repo); + +print "\n"; +my $out=`git init `; !($?) or die; +print $out; + +## put some files +my @files_hpp=glob "$cdir/*.hpp"; +my @files_cpp=glob "$cdir/*.cpp"; + +foreach my $f (@files_hpp) { copy "$f", "." or die; } +print "add: *.hpp files"."\n"; +foreach my $f (@files_cpp) { copy "$f", "." or die; } +print "add: *.cpp files"."\n"; + +my $all_hpp = join ' ', @files_hpp; +`tar -zcf "hpp.tgz" $all_hpp 2>&1`; !($?) or die; +print "add: hpp.tgz file"."\n"; +my $all_cpp = join ' ', @files_cpp; +`tar -zcf "cpp.tgz" $all_cpp 2>&1`; !($?) or die; +print "add: cpp.tgz file"."\n"; + +open (FILE, '>>.gitattributes'); +print FILE "*.hpp filter=git-crypt diff=git-crypt"."\n"; +print FILE "*.cpp filter=git-crypt diff=git-crypt"."\n"; +print FILE "*.tgz filter=git-crypt diff=git-crypt"."\n"; +close (FILE); +print "set .gitattributes filter for: *.hpp *.cpp *.tgz"."\n"; + +print "Initialized git-crypt repository with key: $dir/$key"."\n"; +`git-crypt init $dir/$key`; !($?) or die; + +print "git add & commit files"."\n"; +`git add --all`; !($?) or die; +`git commit -m "test git-crypt"`; !($?) or die; + +chdir($dir); + + +print "\n"."======= CLONE GIT ENCRYPTED REPO ======="."\n"; + +my $clonerepo="clonerepo"; + +`git clone --quiet file://'$dir/$repo' $clonerepo`; !($?) or die; +print "\n"."Clone Encrypted Git repository in "."$dir/$clonerepo/"."\n"; + + + +### TEST + +### Test#1 +## isencrypted clone repo ? + +chdir("$dir/$clonerepo"); + +print "\n"."======= TEST 1: isencrypted clone repo? ======="."\n"; +print "\n"; + +my @all_files=glob("*.hpp *.cpp *.tgz"); +foreach my $file (@all_files) { + print "\"$file\" encrypted?: "; + if ( isencrypted($file) ){ + print colored("OK", "green")."\n"; + } else{ + print colored("FAIL", "red")."\n"; + } +} + +print "\n"; + + +### Test#2 +## decrypt clone repo + +print "\n"."======= TEST 2: decrypt clone repo ======="."\n"; +print "\n"; + +print "Initialized git-crypt repository with key: $dir/$key"."\n"; +`git-crypt init $dir/$key`; !($?) or die; + +print "\n"; +foreach my $file (@all_files) { + print "\"$file\" decrypted?: "; + if ( isdecrypted($file) ){ + print colored("OK", "green")."\n"; + } else{ + print colored("FAIL", "red")."\n"; + } +} + +print "\n"; + + +sub isencrypted { + my $crypthead="\x00GITCRYPT\x00"; + my $file = "$dir/$clonerepo/".shift; + open (FILE, $file) or die "Can't open '$file' : $!"; + binmode(FILE) or die "Can't binmode '$file' : $!"; + my $filehead; + read (FILE, $filehead, 10); + close (FILE); + + if ( "$crypthead" eq "$filehead" ) { + return 1; + } + return 0; +} + + +sub isdecrypted { + my $file = shift; + if ( getmd5("$dir/$repo/$file") eq getmd5("$dir/$clonerepo/$file") ) { + return 1; + } + return 0; +} + +sub getmd5 { + my $file = shift; + open (FILE, $file) or die "Can't open '$file' : $!"; + binmode(FILE) or die "Can't binmode '$file' : $!"; + my $data = ; + close(FILE); + return md5($data); +} + + +exit 0; diff --git a/util.cpp b/util.cpp index 575d616..3849cc8 100644 --- a/util.cpp +++ b/util.cpp @@ -125,4 +125,3 @@ std::string escape_shell_arg (const std::string& str) new_str.push_back('"'); return new_str; } - diff --git a/util.hpp b/util.hpp index aa76982..fb71e94 100644 --- a/util.hpp +++ b/util.hpp @@ -34,11 +34,29 @@ #include #include #include +#include -int exec_command (const char* command, std::ostream& output); + +int exec_command (const char* command, std::ostream& output); std::string resolve_path (const char* path); void open_tempfile (std::fstream&, std::ios_base::openmode); std::string escape_shell_arg (const std::string&); + +#ifdef __WIN32__ +int win32_system (const char* command); +void set_cin_cout_binary_mode (void); + +class temp_fstream : public std::fstream { +public: + temp_fstream(); + void open (const char *fname, std::ios_base::openmode mode); + virtual ~temp_fstream(); +private: + char *fileName; +}; +#endif + + #endif diff --git a/util_win32.cpp b/util_win32.cpp new file mode 100644 index 0000000..3f298aa --- /dev/null +++ b/util_win32.cpp @@ -0,0 +1,334 @@ +/* + * Copyright 2012 Andrew Ayer + * Copyright 2014 bySabi Files + * + * This file is part of git-crypt. + * + * git-crypt 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. + * + * git-crypt 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 git-crypt. If not, see . + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify the Program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, the licensors of the Program + * grant you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#undef __STRICT_ANSI__ //needed for _fullpath +#include "util.hpp" +#include +#include +#include +#include +#include +#include +#include +#include + + +int LaunchChildProcess (HANDLE hChildStdOut, + HANDLE hChildStdIn, + HANDLE hChildStdErr, + const char* command); +void ReadAndHandleOutput (HANDLE hPipeRead, std::ostream& output); +int mkstemp (char *name); +void DisplayError (LPCSTR wszPrefix); + + + +int exec_command (const char* command, std::ostream& output) +{ + HANDLE hOutputReadTmp,hOutputRead,hOutputWrite; + HANDLE hErrorWrite; + SECURITY_ATTRIBUTES sa; + + // Set up the security attributes struct. + sa.nLength = sizeof(SECURITY_ATTRIBUTES); + sa.lpSecurityDescriptor = NULL; + sa.bInheritHandle = TRUE; + + // Create the child output pipe. + if (!CreatePipe(&hOutputReadTmp,&hOutputWrite,&sa,0)) + DisplayError("CreatePipe"); + + // Create a duplicate of the output write handle for the std error + // write handle. This is necessary in case the child application + // closes one of its std output handles. + if (!DuplicateHandle(GetCurrentProcess(),hOutputWrite, + GetCurrentProcess(),&hErrorWrite,0, + TRUE,DUPLICATE_SAME_ACCESS)) + DisplayError("DuplicateHandle"); + + // Create new output read handle. Set + // the Properties to FALSE. Otherwise, the child inherits the + // properties and, as a result, non-closeable handles to the pipes + // are created. + if (!DuplicateHandle(GetCurrentProcess(),hOutputReadTmp, + GetCurrentProcess(), + &hOutputRead, // Address of new handle. + 0,FALSE, // Make it uninheritable. + DUPLICATE_SAME_ACCESS)) + DisplayError("DuplicateHandle"); + + // Close inheritable copies of the handles you do not want to be + // inherited. + if (!CloseHandle(hOutputReadTmp)) DisplayError("CloseHandle"); + + // Launch child process + int status = LaunchChildProcess(hOutputWrite ,NULL ,hErrorWrite, command); + + // Close pipe handles (do not continue to modify the parent). + // You need to make sure that no handles to the write end of the + // output pipe are maintained in this process or else the pipe will + // not close when the child process exits and the ReadFile will hang. + if (!CloseHandle(hOutputWrite)) DisplayError("CloseHandle"); + if (!CloseHandle(hErrorWrite)) DisplayError("CloseHandle"); + + // Read the child's output. + ReadAndHandleOutput(hOutputRead, output); + + if (!CloseHandle(hOutputRead)) DisplayError("CloseHandle"); + + return status; +} + +std::string escape_shell_arg (const std::string& str) +{ + std::string new_str; + new_str.push_back('"'); + for (std::string::const_iterator it(str.begin()); it != str.end(); ++it) { + if (*it == '"' || *it == '$' || *it == '`') { + new_str.push_back('\\'); + } + if (*it == '\\') { + new_str += '/'; + } + else + new_str.push_back(*it); + } + new_str.push_back('"'); + return new_str; +} + +std::string resolve_path (const char* path) +{ + char retname[_MAX_PATH]; + _fullpath(retname, path, _MAX_PATH); + return std::string(retname);; +} + +void open_tempfile (std::fstream& file, std::ios_base::openmode mode) +{ + const char* tmpdir = getenv("TEMP"); + size_t tmpdir_len; + if (tmpdir) { + tmpdir_len = strlen(tmpdir); + } else { + tmpdir = "/tmp"; + tmpdir_len = 4; + } + char* path = new char[tmpdir_len + 18]; + strcpy(path, tmpdir); + strcpy(path + tmpdir_len, "/git-crypt.XXXXXX"); + int fd = mkstemp(path); + if (fd == -1) { + perror("mkstemp"); + std::exit(9); + } + static_cast(file).open(path, mode); + if (!file.is_open()) { + perror("open"); + _unlink(path); + std::exit(9); + } + // on windows can´t remove open files. + // unlink(path); + close(fd); + delete[] path; +} + +temp_fstream::temp_fstream () : std::fstream(), fileName(NULL) +{} + +void temp_fstream::open (const char *fname, std::ios_base::openmode mode) +{ + fileName = strdup(fname); + std::fstream::open(fname, mode); +} + +temp_fstream::~temp_fstream () +{ + if (this->is_open()) + this->close(); + if (fileName != NULL) + _unlink(fileName); +} + +void set_cin_cout_binary_mode () +{ + int result = _setmode(_fileno(stdin), _O_BINARY ); + if (result == -1) + throw std::ios_base::failure("Cannot set input mode to binary."); + result = _setmode(_fileno(stdout), _O_BINARY ); + if ( result == -1) + throw std::ios_base::failure("Cannot set output mode to binary."); +} + + +char* str_replace (const char *string, const char *substr, const char *replacement) +{ + /* if either substr or replacement is NULL, duplicate string a let caller handle it */ + if (substr == NULL || replacement == NULL) return strdup (string); + char* newstr = strdup (string); + char* head = newstr; + char* tok; + while ( (tok = strstr( head, substr )) ) + { + char* oldstr = newstr; + newstr = (char *)malloc ( strlen ( oldstr ) - strlen ( substr ) + strlen ( replacement ) + 1 ); + /*failed to alloc mem, free old string and return NULL */ + if ( newstr == NULL ) { + free (oldstr); + return NULL; + } + memcpy ( newstr, oldstr, tok - oldstr ); + memcpy ( newstr + (tok - oldstr), replacement, strlen ( replacement ) ); + memcpy ( newstr + (tok - oldstr) + strlen( replacement ), tok + strlen ( substr ), strlen ( oldstr ) - strlen ( substr ) - ( tok - oldstr ) ); + memset ( newstr + strlen ( oldstr ) - strlen ( substr ) + strlen ( replacement ) , 0, 1 ); + /* move back head right after the last replacement */ + head = newstr + (tok - oldstr) + strlen( replacement ); + free (oldstr); + } + return newstr; +} + +int win32_system (const char* command) +{ + // >/dev/null TO >nul + return system(str_replace(command, "/dev/null", "nul")); +} + +int mkstemp (char * filetemplate) +{ + // on windows _mktemp generate only 26 unique filename + char* filename = _mktemp(filetemplate); + if (filename == NULL) { + return -1; + } + return open(filename, _O_RDWR |_O_BINARY | O_CREAT); +} + +/* + * LaunchChildProcess + * Sets up STARTUPINFO structure, and launches redirected child. + */ +int LaunchChildProcess (HANDLE hChildStdOut, + HANDLE hChildStdIn, + HANDLE hChildStdErr, + const char* command) +{ + PROCESS_INFORMATION pi; + STARTUPINFO si; + DWORD exit_code; + // Set up the start up info struct. + ZeroMemory(&pi, sizeof(PROCESS_INFORMATION)); + ZeroMemory(&si, sizeof(STARTUPINFO)); + si.cb = sizeof(STARTUPINFO); + si.dwFlags = STARTF_USESTDHANDLES; + si.wShowWindow = SW_HIDE; + si.hStdOutput = hChildStdOut; + si.hStdInput = hChildStdIn; + si.hStdError = hChildStdErr; + + if (CreateProcess(NULL, /* module: null means use command line */ + (LPSTR)command, /* modified command line */ + NULL, /* thread handle inheritance */ + NULL, /* thread handle inheritance */ + TRUE, /* handles inheritable? */ + CREATE_NO_WINDOW, + NULL, /* environment: use parent */ + NULL, /* starting directory: use parent */ + &si,&pi)) + + { + WaitForSingleObject(pi.hProcess, INFINITE); + } + else + { + char error_msg[] = "Error launching process: "; + char buf[_MAX_PATH + sizeof(error_msg)]; + snprintf(buf, sizeof buf, "%s%s", error_msg, command); + DisplayError(buf); + } + GetExitCodeProcess(pi.hProcess, &exit_code); + // Close any unnecessary handles. + if (!CloseHandle(pi.hProcess)) DisplayError("CloseHandle"); + if (!CloseHandle(pi.hThread)) DisplayError("CloseHandle"); + return exit_code; +} + +/* + * ReadAndHandleOutput + * Monitors handle for input. Exits when child exits or pipe breaks. + */ +void ReadAndHandleOutput (HANDLE hPipeRead, std::ostream& output) +{ + UCHAR lpBuffer[256]; + DWORD nBytesRead; + + while (TRUE) + { + memset(lpBuffer, 0, sizeof(lpBuffer)); + if (!ReadFile(hPipeRead,lpBuffer,sizeof(lpBuffer),&nBytesRead,NULL) || !nBytesRead) + { + if (GetLastError() == ERROR_BROKEN_PIPE) + break; // pipe done - normal exit path. + else + DisplayError("ReadFile"); // Something bad happened. + } + output.write((const char *)lpBuffer, nBytesRead); + } +} + +/* + * DisplayError + * Displays the error number and corresponding message. + */ +void DisplayError (LPCSTR szPrefix) +{ + LPSTR lpsz = NULL; + DWORD cch = 0; + DWORD dwError = GetLastError(); + + cch = FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER + | FORMAT_MESSAGE_FROM_SYSTEM + | FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, dwError, LANG_NEUTRAL, + (LPTSTR)&lpsz, 0, NULL); + if (cch < 1) { + cch = FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER + | FORMAT_MESSAGE_FROM_STRING + | FORMAT_MESSAGE_ARGUMENT_ARRAY, + "Code 0x%1!08x!", + 0, LANG_NEUTRAL, (LPTSTR)&lpsz, 0, + (va_list*)&dwError); + } + fprintf(stderr, "%s: %s", szPrefix, lpsz); + LocalFree((HLOCAL)lpsz); + ExitProcess(-1); +}