GPGShell.php 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482
  1. <?php
  2. /**
  3. * @file GPGShell.php
  4. * @brief Interface for gpg (GnuPG) shell commands. Project home: https://github.com/vajayattila/GPGShell
  5. * @author Vajay Attila (vajay.attila@gmail.com)
  6. * @copyright MIT License (MIT)
  7. * @date 2018.09.19-2019.07.18
  8. * @version 1.2.0.3
  9. *
  10. * Features:
  11. * --list-keys
  12. * --list-secret-keys
  13. * --export
  14. * --export-secret-key
  15. * --import
  16. * --encrypt
  17. * --decrypt
  18. * --detach-sign
  19. * --verify
  20. * --list-packets
  21. * --import-ownertrust
  22. * --gen-key
  23. * Special features:
  24. * getKeyFingerprintsByEmail
  25. * getSecretKeyFingerprintsByEmail
  26. * deleteSecretKeyByFingerprint
  27. * deleteKeyByFingerprint
  28. * deleteAllSecretKeyByEmail
  29. * deleteAllKeyByEmail
  30. */
  31. require_once(__DIR__.'/GPGColonParser.php');
  32. // Interface class for gpg (GnuPG) shell commands
  33. class GPGShell{
  34. // @brief GnuPG data home directory
  35. private $__homedir;
  36. // @brief Last output
  37. private $__output;
  38. // @brief Last exit code
  39. private $__exitcode;
  40. // @brief Colon parser
  41. private $__colonParser;
  42. // @brief gpg exe name
  43. private $__gpg_exe_name;
  44. // @brief Constructor with an optional homedir parameter
  45. function __construct(){
  46. $this->__colonParser=new GPGColonParser();
  47. $this->__gpg_exe_name="gpg";
  48. $i = func_num_args();
  49. if($i===1){
  50. $a = func_get_args();
  51. $this->__homedir=$a[0]; // homedir parameter
  52. } else {
  53. $this->__homedir=__DIR__; // default homedir parameter
  54. }
  55. }
  56. private function run($cmd)
  57. {
  58. $result = null;
  59. exec($cmd, $this->__output, $result);
  60. $this->__exitcode=$result;
  61. // Return true on successfull command invocation
  62. return $result === 0;
  63. }
  64. private function makeArgs()
  65. {
  66. $args = [
  67. '--batch',
  68. '--quiet',
  69. '--homedir',
  70. $this->homedir,
  71. '--local-user www-data'
  72. ];
  73. return $args;
  74. }
  75. private function parseListKeys($output){
  76. $retval=$output;
  77. //print_r($output);
  78. return $retval;
  79. }
  80. function isSpecialRecord($key){
  81. return $this->__colonParser->isSpecialRecord($key);
  82. }
  83. function getValueDescription($field){
  84. return $this->__colonParser->getValueDescription($field);
  85. }
  86. function __get($name) {
  87. if($name === 'exitCode'){
  88. return $this->__exitcode;
  89. } else if($name === 'output'){
  90. return $this->__output;
  91. } else if($name === 'homedir'){
  92. return $this->__homedir;
  93. } else if($name === 'gpg_exe_name'){
  94. return $this->__gpg_exe_name;
  95. } else {
  96. user_error("Invalid property: " . __CLASS__ . "->$name");
  97. }
  98. }
  99. function __set($name, $value) {
  100. if($name === 'homedir'){
  101. $this->__homedir=$value;
  102. }else if($name === 'gpg_exe_name'){
  103. $this->__gpg_exe_name=$value;
  104. }else{
  105. user_error("Can't set property: " . __CLASS__ . "->$name");
  106. }
  107. }
  108. // @brief List keys
  109. function listKeys($keyid=NULL)
  110. {
  111. $this->__output=NULL;
  112. $args = $this->makeArgs();
  113. $args[] = '--list-keys';
  114. $args[] = '--with-colons';
  115. $args[] = '--with-fingerprint';
  116. if($keyid!==NULL){
  117. $args[] = $keyid;
  118. }
  119. $cmd = sprintf($this->__gpg_exe_name.' %s ', implode(' ', $args));
  120. $retval=$this->run($cmd);
  121. $this->__output=$this->__colonParser->parseOutput($this->output);
  122. return $retval;
  123. }
  124. // @brief List Secret keys
  125. function listSecretKeys()
  126. {
  127. $this->__output=NULL;
  128. $args = $this->makeArgs();
  129. $args[] = '--list-secret-keys';
  130. $args[] = '--with-colons';
  131. $args[] = '--with-fingerprint';
  132. $cmd = sprintf($this->__gpg_exe_name.' %s ', implode(' ', $args));
  133. $retval=$this->run($cmd);
  134. $this->__output=$this->__colonParser->parseOutput($this->output);
  135. return $retval;
  136. }
  137. // @brief Export private key
  138. function exportSecretKey($userid){
  139. $this->__output=NULL;
  140. $args = $this->makeArgs();
  141. $args[] = '--armor';
  142. $args[] = '--export-secret-key';
  143. $args[] = $userid;
  144. $cmd = sprintf($this->__gpg_exe_name.' %s ', implode(' ', $args));
  145. return $retval=$this->run($cmd);
  146. }
  147. // @brief Export public key
  148. function exportKey($userid){
  149. $this->__output=NULL;
  150. $args = $this->makeArgs();
  151. $args[] = '--armor';
  152. $args[] = '--export';
  153. $args[] = $userid;
  154. $cmd = sprintf($this->__gpg_exe_name.' %s ', implode(' ', $args));
  155. return $retval=$this->run($cmd);
  156. }
  157. // @brief Import public or private key
  158. function importKey($key){
  159. $return=false;
  160. $filename=__DIR__.'/'.uniqid().'.tmp';
  161. if(file_put_contents($filename, $key)!==FALSE){
  162. $this->__output=NULL;
  163. $args = $this->makeArgs();
  164. $args[] = '--armor';
  165. $args[] = '--import';
  166. $args[] = $filename;
  167. //$args[] = '2>&1';
  168. $cmd = sprintf($this->__gpg_exe_name.' %s ', implode(' ', $args));
  169. $return=$this->run($cmd);
  170. //echo 'importKey->'.print_r($this->output, true).'<br>';
  171. unlink($filename);
  172. }
  173. return $return;
  174. }
  175. // @brief Import ownertrust
  176. function importOwnertrust($otfilename){
  177. $return=false;
  178. $args = array();
  179. $args = $this->makeArgs();
  180. $args[] = '--import-ownertrust';
  181. $args[] = $otfilename;
  182. //$args[] = '2>&1';
  183. $cmd = sprintf($this->__gpg_exe_name.' %s ', implode(' ', $args));
  184. $return=$this->run($cmd);
  185. return $return;
  186. }
  187. /* @brief genKey
  188. * @param [in] $keylength The requested length of the generated key in bits.
  189. * @param [in] $nameReal Real name
  190. * @param [in] $nameEmail email
  191. * @param [in] $expireDate Set the expiration date for the key (and the subkey). It may either be entered in ISO date format (e.g. "20000815T145012") or as number of days, weeks, month or years after the creation date. The special notation "seconds=N" is also allowed to specify a number of seconds since creation. Without a letter days are assumed. Note that there is no check done on the overflow of the type used by OpenPGP for timestamps. Thus you better make sure that the given value make sense. Although OpenPGP works with time intervals, GnuPG uses an absolute value internally and thus the last year we can represent is 2105.
  192. * @param [in] $Passphrase NULL or Passphrase
  193. */
  194. function genKey($keylength, $nameReal, $nameEmail, $expireDate, $Passphrase){
  195. $return=false;
  196. $scriptfilename=__DIR__.'/'.uniqid().'_script.tmp';
  197. $content=
  198. "Key-Type: 1\nKey-Length: $keylength\nSubkey-Type: 1\nSubkey-Length: $keylength\n".
  199. "Name-Real: $nameReal\nName-Email: $nameEmail\nExpire-Date: $expireDate\n".
  200. ($Passphrase===NULL?"%no-protection\n":"Passphrase: $Passphrase\n");
  201. file_put_contents($scriptfilename,$content);
  202. $args = $this->makeArgs();
  203. $args[] = '--gen-key';
  204. $args[] = $scriptfilename;
  205. $cmd = sprintf($this->__gpg_exe_name.' %s ', implode(' ', $args));
  206. $return=$this->run($cmd);
  207. //print_r($cmd."\n");
  208. //print_r($content."\n");
  209. unlink($scriptfilename);
  210. return $return;
  211. }
  212. /* @brief Get public key's fingerprints by email */
  213. function getKeyFingerprintsByEmail($nameEmail){
  214. $retval=FALSE;
  215. $record=array();
  216. if($this->listKeys()){
  217. foreach($this->output as $key=>$rec){
  218. if(!$this->isSpecialRecord($key)&&'tru'!=$key){
  219. foreach($rec as $item){
  220. //print_r( $item);
  221. switch($item['recordtype']){
  222. case 'pub':
  223. $r=$item['fields'][4];
  224. $record=array(
  225. $r['description']=>$r['value']
  226. );
  227. //print_r($item['fields'][4]);
  228. break;
  229. case 'fpr':
  230. if($record!==null){
  231. $r=$item['fields'][9];
  232. $record[$r['description']]=$r['value'];
  233. }
  234. break;
  235. case 'uid':
  236. if($record!==null){
  237. $r=$item['fields'][9];
  238. $userid=$r['value'];
  239. if(strpos($userid, "<$nameEmail>")){
  240. $record[$r['description']]=$userid;
  241. $retval[]=$record;
  242. }
  243. $record=null;
  244. }
  245. //print_r($item['fields'][9]);
  246. break;
  247. }
  248. }
  249. }
  250. }
  251. }
  252. //print_r($retval);
  253. return $retval;
  254. }
  255. /* @brief Get private key's fingerprints by email */
  256. function getSecretKeyFingerprintsByEmail($nameEmail){
  257. $retval=FALSE;
  258. $record=array();
  259. if($this->listSecretKeys()){
  260. foreach($this->output as $key=>$rec){
  261. if(!$this->isSpecialRecord($key)&&'tru'!=$key){
  262. foreach($rec as $item){
  263. //print_r( $item);
  264. switch($item['recordtype']){
  265. case 'sec':
  266. $r=$item['fields'][4];
  267. $record=array(
  268. $r['description']=>$r['value']
  269. );
  270. //print_r($item['fields'][4]);
  271. break;
  272. case 'fpr':
  273. if($record!==null){
  274. $r=$item['fields'][9];
  275. $record[$r['description']]=$r['value'];
  276. //print_r($record);
  277. }
  278. break;
  279. case 'uid':
  280. if($record!==null){
  281. $r=$item['fields'][9];
  282. $userid=$r['value'];
  283. if(strpos($userid, "$nameEmail")){
  284. $record[$r['description']]=$userid;
  285. $retval[]=$record;
  286. }
  287. $record=null;
  288. }
  289. //print_r($item['fields'][9]);
  290. break;
  291. }
  292. }
  293. }
  294. }
  295. }
  296. //echo 'getSecretKeyFingerprintsByEmail->'.print_r($retval, true).'<br>';
  297. return $retval;
  298. }
  299. // @brief Delete private key by fingerprint
  300. function deleteSecretKeyByFingerprint($fingerprint){
  301. $args = $this->makeArgs();
  302. $args[] = '--yes';
  303. $args[] = '--delete-secret-key';
  304. $args[] = $fingerprint;
  305. //$args[] = '2>&1';
  306. $cmd = sprintf($this->__gpg_exe_name.' %s ', implode(' ', $args));
  307. $return=$this->run($cmd);
  308. //echo 'deleteSecretKeyByFingerprint->'.print_r($this->output, true).'<br>';
  309. return $return;
  310. }
  311. // @brief Delete private key by fingerprint
  312. function deleteKeyByFingerprint($fingerprint){
  313. $args = $this->makeArgs();
  314. $args[] = '--yes';
  315. $args[] = '--delete-key';
  316. $args[] = $fingerprint;
  317. //$args[] = '2>&1';
  318. $cmd = sprintf($this->__gpg_exe_name.' %s ', implode(' ', $args));
  319. $return=$this->run($cmd);
  320. //echo 'deleteKeyByFingerprint->'.print_r($this->output, true).'<br>';
  321. return $return;
  322. }
  323. // @brief Delete all private key by email
  324. function deleteAllSecretKeyByEmail($nameEmail){
  325. $return=true;
  326. $keys=$this->getSecretKeyFingerprintsByEmail($nameEmail);
  327. if(isset($keys)&&is_array($keys)){
  328. foreach($keys as $item){
  329. if($return===true){
  330. $return=$this->deleteSecretKeyByFingerprint($item['Fingerprint']);
  331. }
  332. }
  333. }
  334. return $return;
  335. }
  336. // @brief Delete all public key by email
  337. function deleteAllKeyByEmail($nameEmail){
  338. $return=true;
  339. $keys=$this->getKeyFingerprintsByEmail($nameEmail);
  340. if(isset($keys)&&is_array($keys)){
  341. foreach($keys as $item){
  342. if($return===true){
  343. $return=$this->deleteKeyByFingerprint($item['Fingerprint']);
  344. }
  345. }
  346. }
  347. return $return;
  348. }
  349. // @brief Encrypt string
  350. function encrypt($userid, $data){
  351. $return=false;
  352. $infilename=__DIR__.'/'.uniqid().'_in.tmp';
  353. $outfilename=__DIR__.'/'.uniqid().'_out.tmp';
  354. if(file_put_contents($infilename, $data)!==FALSE){
  355. $this->__output=NULL;
  356. $args = $this->makeArgs();
  357. $args[] = '--armor';
  358. $args[] = '--encrypt';
  359. $args[] = '--output';
  360. $args[] = $outfilename;
  361. $args[] = '--recipient';
  362. $args[] = $userid;
  363. $args[] = $infilename;
  364. $cmd = sprintf($this->__gpg_exe_name.' %s ', implode(' ', $args));
  365. $return=$this->run($cmd);
  366. if($return!==FALSE){
  367. $return=file_get_contents($outfilename);
  368. unlink($outfilename);
  369. }
  370. unlink($infilename);
  371. }
  372. return $return;
  373. }
  374. // @brief Decrypt pgp_message
  375. function decrypt($userid, $pgpdata){
  376. $return=false;
  377. $infilename=__DIR__.'/'.uniqid().'_in.tmp';
  378. $outfilename=__DIR__.'/'.uniqid().'_out.tmp';
  379. if(file_put_contents($infilename, $pgpdata)!==FALSE){
  380. $this->__output=NULL;
  381. $args = $this->makeArgs();
  382. $args[] = '--armor';
  383. $args[] = '--decrypt';
  384. $args[] = '--output';
  385. $args[] = $outfilename;
  386. $args[] = $infilename;
  387. $cmd = sprintf($this->__gpg_exe_name.' %s ', implode(' ', $args));
  388. $return=$this->run($cmd);
  389. if($return!==FALSE){
  390. $return=file_get_contents($outfilename);
  391. unlink($outfilename);
  392. }
  393. unlink($infilename);
  394. }
  395. return $return;
  396. }
  397. // @brief Create sign file for a file
  398. public function detachSign($fileName, $sigName, $userid)
  399. {
  400. $args = $this->makeArgs();
  401. $args[] = '--detach-sign';
  402. $args[] ='--armor';
  403. $args[] = '--batch';
  404. $args[] = '--quiet';
  405. $args[] = '--yes';
  406. $args[] = '--output ';
  407. $args[] = $sigName;
  408. $args[] ='--recipient';
  409. $args[] = $userid;
  410. $args[] = $fileName;
  411. $cmd = sprintf($this->__gpg_exe_name.' %s', implode(' ', $args));
  412. return $this->run($cmd);
  413. }
  414. // @brief Verify sign file
  415. public function verify($fileName, $sigName, $userid)
  416. {
  417. $args = $this->makeArgs();
  418. $args[] = '--batch';
  419. $args[] = '--quiet';
  420. $args[] = '--recipient';
  421. $args[] = $userid;
  422. $args[] = '--verify';
  423. $args[] = $sigName;
  424. $args[] = $fileName;
  425. $cmd = sprintf($this->__gpg_exe_name.' %s', implode(' ', $args));
  426. return self::run($cmd);
  427. }
  428. public function listPackets($pgpdata, &$output){
  429. $return=false;
  430. $filename=__DIR__.'/'.uniqid().'.tmp';
  431. if(file_put_contents($filename, $pgpdata)!==FALSE){
  432. $args = $this->makeArgs();
  433. if (($key = array_search("--quiet", $args)) !== false) { // remove --quiet
  434. unset($args[$key]);
  435. }
  436. $args[] = '--batch';
  437. $args[] = '--list-packets';
  438. $args[] = $filename;
  439. $cmd = sprintf($this->__gpg_exe_name.' %s', implode(' ', $args));
  440. $return=self::run($cmd);
  441. if($return!==FALSE){
  442. $output=$this->output;
  443. }
  444. unlink($filename);
  445. }
  446. return $return;
  447. }
  448. }
  449. // EOF - GpgShell.php