Recent

Author Topic: Anyone interested in using GPGME.dll (GNU PG) could use some help, it's working  (Read 13659 times)

molly

  • Hero Member
  • *****
  • Posts: 2330
Well, you did achieve progress, didn't you ? :-)

The smallest example i was able to find so quickly, using the passphrase callback (in c of course, and using old api calls it seems) i was able to find here.

snorkel

  • Hero Member
  • *****
  • Posts: 817
Hi Molly,
Yes i did make some progress.
That example you found is Unix/Linux.
From my research write does not work on windows.



Check out these mailing list links:
(I found a bunch with people having the same issue with the callback hanging in a endless loop.)
One guy recompiled gpgme.dll and exposed the internal function _gpgme_io_write() and had success.

On windows gpgme uses standard system.handles for FD, which is what the windows api writefile uses.

I found this also:

Code: Pascal  [Select][+][-]
  1. #ifdef HAVE_GPGME_IO_WRITEN
  2.   295         gpgme_io_writen(fd, last_pass, strlen(last_pass));
  3.   296         gpgme_io_writen(fd, "\n", 1);
  4.   297 #elif defined(G_OS_WIN32)
  5.   298         WriteFile(hd, last_pass, strlen(last_pass), &n, NULL);   //<-- this should work exactly like this in FPC, but it doesn't for some reason.
  6.   299         WriteFile(hd, "\n", 1, &n, NULL);
  7.   300 #else
  8.   301         write(fd, last_pass, strlen(last_pass));
  9.   302         write(fd, "\n", 1);
  10.   303 #endif

I added code exactly like the win32 block in the above example and it loops endlessly, it just keeps
calling back over and over that the passhprase was bad.
I seriously the gpgme code is just buggy on win32.  I guess I could try and find source version that has
gpgme_io_writen exported as a entry point in the dll.

It shoud be called GPGMD  (gpg made difficult ha ha)

I spend like 16 hours on this digging through mailing lists etc, not sure it's worth it.   I think it would be easier to just make a wrap a object around Tprocess and examine stdout for errors.



http://www.gossamer-threads.com/lists/gnupg/devel/41524



***Snorkel***
If I forget, I always use the latest stable 32bit version of Lazarus and FPC. At the time of this signature that is Laz 3.0RC2 and FPC 3.2.2
OS: Windows 10 64 bit

snorkel

  • Hero Member
  • *****
  • Posts: 817
This is the function I came up with that should work:
the var ret does contain the number of bytes written so write file does indeed work.   
Finding binary win32 builds of gpgme is kind of difficult, I may just build them myself and see if versions newer than 1.1.8 work any better.
The key is to not have the pinentry support.

Code: Pascal  [Select][+][-]
  1. function gpgme_passphrase_cb(handle:pointer;uid_hint:pchar;passphrase_info:pchar;prev_was_bad:integer;fd:integer):Tgpgme_error;cdecl;
  2. var
  3.    password:pchar;
  4.    ret:dword;
  5.    filehandle:windows.HANDLE;
  6.    newline:pchar = #10;
  7. begin
  8.      filehandle:=windows.HANDLE(fd);
  9.      password:='12345678';
  10.      windows.WriteFile(filehandle,password,Length(password),ret,nil);
  11.      windows.WriteFile(filehandle,newline,Length(newline),ret,nil);
  12.      //windows.FlushFileBuffers(filehandle);   // Tried this to see if it was not fully writing to handle.
  13.      windows.CloseHandle(filehandle);    //Loops forever if you don't close handle, errors out on second try because handle is closed.
  14.  
  15.      result.errorcode:=GPG_ERR_NO_ERROR;
  16.      result.error:=GPG_ERR_NO_ERROR;;
  17.      result.errorsource:=0;
  18. end;
***Snorkel***
If I forget, I always use the latest stable 32bit version of Lazarus and FPC. At the time of this signature that is Laz 3.0RC2 and FPC 3.2.2
OS: Windows 10 64 bit

snorkel

  • Hero Member
  • *****
  • Posts: 817
Code: Pascal  [Select][+][-]
  1. Success!!!
  2.  
  3. function gpgme_passphrase_cb(handle:pointer;uid_hint:pchar;passphrase_info:pchar;prev_was_bad:integer;fd:integer):Tgpgme_error;cdecl;
  4. var
  5.    password:pchar;
  6.    ret:dword;
  7.    filehandle:windows.HANDLE;
  8.    newline:pchar = #10;
  9. begin
  10.      filehandle:=windows.HANDLE(fd);
  11.      password:='12345678';
  12.      gpgme_io_writen(fd,password,length(password));
  13.      gpgme_io_writen(fd,newline,length(newline));
  14.      result.errorcode:=GPG_ERR_NO_ERROR;
  15.      result.error:=GPG_ERR_NO_ERROR;;
  16.      result.errorsource:=0;
  17. end;
  18.  

I got it working with a version of GPGM (1.4.3)  from the windows version of Slypheed (email client).
So I took the 3 dlls and the latest gpg.exe 1.4 version and stuck them in the app dir, at first it kept say invalid encyption engine... did some more digging and it turns out it has a dependency on gpgme-w32spawn.exe, so pulled that from the slypheed dir and of course writefile failed, then I checked if the dll had the gpgme_io_writen and it did, so I added that to the code and then to the callback and it works.

What a PITA......

When I get more time I will do a WIKI page on use gpgme with lazarus and I will improve the header import code.

If anyone wants to help I will post a link to google drive item with eveything I have with the dlls and the deps.
***Snorkel***
If I forget, I always use the latest stable 32bit version of Lazarus and FPC. At the time of this signature that is Laz 3.0RC2 and FPC 3.2.2
OS: Windows 10 64 bit

molly

  • Hero Member
  • *****
  • Posts: 2330
Quote
(I found a bunch with people having the same issue with the callback hanging in a endless loop.)
Yes, i noticed these reports. TBH i don't know much about the project itself, let alone port specifics. e.g. i wasn't even aware there was a windows port.

Quote
the var ret does contain the number of bytes written so write file does indeed work.   
I should probably have mentioned that you could (just as well) use (other) windows specific api with regards to handling c file descriptors. I didn't know what was easier for you to use.

Quote
The key is to not have the pinentry support.
Although i realize that to be the case, it seems to me a bit counterproductive as that is exactly why the function was invented. e.g. to have a popup asking user for input. But perhaps i misunderstood, as there seems more people after this particular behaviour as you seem to be looking for.

Quote
I got it working with a version of GPGM (1.4.3)  from the windows version of Slypheed (email client).
Congratz, and thanks for keeping us updated.

Quote
What a PITA......
Yes, i noticed as well :-S

Seems you are persistent enough ;-)

Quote
If anyone wants to help I will post a link to google drive item with eveything I have with the dlls and the deps.
Wish i had the time to do so, but i am currently swamped. Not that i'm particulary interrested in the GPG stuff, but more for the general experience of getting such (spartanic) project/port to work with Free Pascal :-)

Thank you very much for the snippets and informing on how you solved things.

snorkel

  • Hero Member
  • *****
  • Posts: 817
Quote
"Although i realize that to be the case, it seems to me a bit counterproductive as that is exactly why the function was invented. e.g. to have a popup asking user for input. But perhaps i misunderstood, as there seems more people after this particular behaviour as you seem to be looking for."

Hi Molly,
Yes you would want that in desktop app, but you don't want that in a service that say implements FTP or SFTP and you want the incoming files to be decrypted and moved.

I did try the other windows API commands like _write but the problem is on win32 the gpgme FD var is a windows system.handle so writefile should work, but it doesn't I suspect there is some internal critical sections or something that causes the writefile to fail.

Anyways I did some more testing and got it work with the latest version of gpgme (1.6)  the key is not to have a install of win4gpg on your PC as gpgme will look if it's installed via the system path and try to execute gpgconf.exe in that other install directory and then the copy you are using will then use the pin entry agent in the other install and skip the callback.

I was also able to pass the actual password to the call back using the hook pointer var of gpgme_set_passphrase  i.e.

Code: Pascal  [Select][+][-]
  1. buffer:=StrAlloc(length(pass));
  2. StrCopy(buffer,pass);  
  3. gpgme_set_passphrase_cb(context,fproc,buffer);

then in the callback:

Code: Pascal  [Select][+][-]
  1. function gpgme_passphrase_cb(hook:pointer;uid_hint:pchar;passphrase_info:pchar;prev_was_bad:integer;fd:integer):Tgpgme_error;cdecl;
  2. var
  3.    password:pchar;
  4.    newline:pchar = #10;
  5. begin
  6.      password:=pchar(hook);
  7.      try
  8.         gpgme_io_writen(fd,password,length(password));
  9.         gpgme_io_writen(fd,newline,length(newline));
  10.         result.errorcode:=GPG_ERR_NO_ERROR;
  11.         result.error:=GPG_ERR_NO_ERROR;;
  12.         result.errorsource:=0;
  13.      finally
  14.           if Assigned(password) then
  15.              StrDispose(password);
  16.      end;
  17. end;


 I hope this helps someone else in the future looking to implement OpenPGP in their apps or services using FPC and Lazarus :-)



***Snorkel***
If I forget, I always use the latest stable 32bit version of Lazarus and FPC. At the time of this signature that is Laz 3.0RC2 and FPC 3.2.2
OS: Windows 10 64 bit

snorkel

  • Hero Member
  • *****
  • Posts: 817
In case anyone is interested here is how the decrypt currently works:

Code: Pascal  [Select][+][-]
  1. procedure TForm1.decryptWithObjectBtnClick(Sender: TObject);
  2. var
  3.   Src, Dst: TfileStream;
  4.   gpgme: TGpgmeContext;
  5. begin
  6.   try
  7.      src := TfileStream.Create('C:\Users\user\Desktop\guiSQLiteStudio.dll.pgp',fmShareDenyNone);
  8.      dst := TfileStream.Create('C:\Users\user\Desktop\guiSQLiteStudio.dll',fmCreate+fmShareDenyNone);
  9.      src.Position:=0;
  10.      dst.Position:=0;
  11.      //Create a new context to perform the decryption
  12.      gpgme := TGpgmeContext.Create(nil);
  13.      //Set the passphrase for the private key
  14.      gpgme.PassPhrase:='YourPrivKeyPass';
  15.      //Setup the engine info
  16.      //The home directory is the location of the keyring, for a server install we want it in the same directory as our server.
  17.      gpgme.Homedir:=extractfilepath(paramstr(0))+'gpghome';
  18.      //Set the actual encryption engine, in this case it's gpg.exe version 1.4.20
  19.      gpgme.Cryptopath:=extractfilepath(paramstr(0))+'gpg.exe';
  20.      //Set the location of the gpgme dll
  21.      gpgme.LibraryLocation := extractfilepath(paramstr(0))+'libgpgme-11.dll';
  22.      //we are doing files, so AsciiArmor is false.  If you need to encrypt text, just change the streams to stringstream
  23.      gpgme.AsciiArmor := false;
  24.      //Do the Decryption
  25.      gpgme.decrypt(Src, Dst);
  26.   finally
  27.     //Clean up
  28.     if Assigned(gpgme) then FreeAndNil(gpgme);
  29.     if assigned(dst) then FreeAndNil(dst);
  30.     if Assigned(src) then FreeAndNil(src);
  31.   end;
  32. end;
  33.  
  34.  
***Snorkel***
If I forget, I always use the latest stable 32bit version of Lazarus and FPC. At the time of this signature that is Laz 3.0RC2 and FPC 3.2.2
OS: Windows 10 64 bit

snorkel

  • Hero Member
  • *****
  • Posts: 817
Just a fyi

I got bit by the sysutils stralloc and the strings.stralloc.

I was allocating space using the sysutils which also adds a additional 4 bytes for the size and I was not accounting for that when I did:

buffer:=StrAlloc(length(PassPhrase));

and should have been doing: buffer:=StrAlloc(length(PassPhrase)+1);

I started getting weird AVs after using msg dialog after decypting a file with a passphrase.
Took forever to figure out there where 2 versions of stralloc.

***Snorkel***
If I forget, I always use the latest stable 32bit version of Lazarus and FPC. At the time of this signature that is Laz 3.0RC2 and FPC 3.2.2
OS: Windows 10 64 bit

snorkel

  • Hero Member
  • *****
  • Posts: 817
Here is a link of the updates I did to the original project.

https://drive.google.com/file/d/0B556ucir_o2kTHEtbnJvRFVFc2c/view?usp=sharing
The code is probably not perfect and CONSTRUCTIVE comments are welcome.

I think I have it stable enough to use. 
This is based off the original work in the fist message of this thread in the git hub link.
I have contacted the original author to see if he is interested in adding this as a branch or whatever.  If not I will just fork it on git hub.

Changes I made:
I only load the DLL once when the GPGME object  is created.
You only need to create a new context and free that per operation.  The original code loaded the library in each function.
Added Decrypt function that takes a source and dest filestream with the passphrase callback enabled and working.
The GPGME object now has a property called PassPhrase that you can set before a Decrypt to supply the password.
The password is passed via the callbacks hook pointer var as a null terminated string.
Changed the Keylist function to return rows of key value pairs so you can easily send across a network and use tstringlist to access.
Added a KeyListDS function that adds the keys to a tbufdataset which is returned as a stream for easy sending via TCP/IP
Added a function to output the engine information.
Added a property to access the GPGME.dll version number which is filled when the GPGME object is created.
The GPGME objects create accepts the library name and expects it to be in the same dir as the sample app.
If you want to encrypt or decrypt text, just use Tstringstream instead of Tfilestream and set ASCIIArmor to true.

Still need to add a method to import a key.   This could be used on a Indy FTP server to automatically decrypt files and encypt files using public keys for relay from the
FTP server to other locations.

This is geared for use on a server with a keyring not in the default location.   There are two properties one for homedir and one for cyrpto engine.
I set this to the gpghome dir in the same dir as the sample app and I set the crypto engine to the gpg.exe also contained in the app dir.

All the required DLLs and deps are included in the app dir.

The key to getting this to work is not to install the GPG or GPG4WIN setups as they add paths to the PATH system environment vars.
The GPGME dll will look for those ENV vars and attempt to use the Pineentry agent in the setup installed dirs.  This is fine if you are building a desktop app, but not for a server app.  you don't want a GTK dialog box popping up or trying to popup.
it will try and execute gpgconf.exe and then it won't use your keyring or gpg.exe. 
The default location for the keyring is the users roaming app dir.

Again this is based off the project GPGME4Pascal on gitbhub.   I do not take credit for the original work and this would not be possible without that initial work.
Many thanks to the GPGME4Pascal author.






« Last Edit: March 22, 2016, 02:13:59 am by snorkel »
***Snorkel***
If I forget, I always use the latest stable 32bit version of Lazarus and FPC. At the time of this signature that is Laz 3.0RC2 and FPC 3.2.2
OS: Windows 10 64 bit

 

TinyPortal © 2005-2018