From 50e5714c543ed50557a1b644c6df27b8d298b6e1 Mon Sep 17 00:00:00 2001 From: Michael Wallner Date: Sun, 8 Mar 2015 12:25:26 +0100 Subject: [PATCH] openssl signing --- .gitignore | 5 +- bin/pharext | Bin 37674 -> 39819 bytes bin/pharext.pubkey | 9 ++++ build/create-phar.php | 27 +++++++++- composer.json | 2 +- src/pharext/CliCommand.php | 37 ++++++++++++- src/pharext/Installer.php | 35 ------------ src/pharext/Openssl/PrivateKey.php | 27 ++++++++++ src/pharext/Packager.php | 56 ++++++++++++++++++-- tests/src/pharext/FilteredSourceDirTest.php | 2 +- 10 files changed, 156 insertions(+), 44 deletions(-) create mode 100644 bin/pharext.pubkey create mode 100644 src/pharext/Openssl/PrivateKey.php diff --git a/.gitignore b/.gitignore index 623b451..a660ac2 100644 --- a/.gitignore +++ b/.gitignore @@ -6,4 +6,7 @@ nbproject/ *.tmp *.phar *.phar.gz -*.phar.bz2 \ No newline at end of file +*.phar.bz2 +build/pharext.key +*.phar* +*.pubkey diff --git a/bin/pharext b/bin/pharext index 3277bb88e6091baefa70c1af5338d1225f1f9a0d..76396714bb29123affc8317d1e3c3176596f018f 100755 GIT binary patch delta 2030 zcmZuyTWlLe6t$B+SW`rziboSCnrxDGU1B>)mB% zHi`SdC=x=nr4o!PB)*UksN$!URy-smpi+gzN52pffAHHP_(c_baPO?`IJ8#e@$TF? z_nb5L-hA8OC6Eym1b+BH^nPEXP@o}6t8PU70R^X}i`gLcw#_xa!@t}U(Kej@I)$0U_M z+Yd=9_aJ(dpE!q9o=IkG&&|5)0EKaTzJrMMp5Cvm=sCggfQ z8pqY&(=ppEKHX>)iMj8?{o=2=Q~o!=9sc_xAMf!`%uj6dAD*AxSy`y+7>Y!4 zrp_4I)DyWFQJg>>=~o}4Tqc&yiD~Q0q{bJrs4+Z$If8!;yqQt3w1qU>BJ$0x=mlD(TYZWTGs-g%rE|}v zqh`T&4PGn-X9fqS(#Iz=nb&|P%Bx6ym}zSky6)I6PkT$IkvmDtfkQzasR%V^9gm}l zoLZb|s??w0B?mls#I0q@b;D#0>SOaNh}@?x#z5tzOG=kZExiPGHsZ+N7$9z6iHgM+ z+jj(}i1^f~&x_Z>s^ytxA5qa2sTY!~<6g-j^*7?)^ngg5zUTXITx&k8%&QrU!EId8 zD8h@B>`y>fKnsAcYv32r8}9-zj)WKem#kNz2<_d;IylgmwW82<1L`QKnEI~P-)&C)?-C{5YJ|k#ZaKi}0RSjDP0Y=7R$}pCC^w`T9FPWJU0Xb_sl9AdD6#XT zmn~Sm41^)Zyjs**G0V#iRTr@^flMC(Hx(sU^sJREb4+Ai55=g%2f>vEON|AI6qiC7 zS)_kR*l%`;Qw#fBt8WJCVe=_^Y}Z8xlwIy_H&bEE`N;GAY3z0_}@e#;IE}EZs!5RWgmnAQni5+;UfT zH?YWqolMJ|8K0Q!BdPv@gK~EZ70E=tK8MxLyd@YEAOz_5k$5Ubj)EA>TD>pa0OYN4 z{W7%1t7@9Wklp5o`AoQimw_p&m7}`_m^g4BRu#H7gbXWhYmjn=8CIdOG6}b31rS-e zi9~|b+<*awcLGfY)GWG2s*`ddU7z?`#-+OH=)CwC0^nXy&b5oIRYVOoAd0pGQwXKP z81bjQ8)5&!>WwCUqkJoT=<)i<-o3Ep9!vS*J^XC|g5NBsQto7})dm(~(slY}S+(N1e0SWP}HtsuZ43=ss;0r_(tPyQ{f3D%)5V+y9@W%R)G z1Q`th69$N45WPR2-*NH@8FjGaC#WK2SzQo+bFgfL^5n-p%A3QSCreJ=>TrzFbaIWO zFC*vX`!T^Rn=d=YGXmKfiAn5}d4rTUvu5`&ZvLNR&$4+#p*!Pd`{G7sM$XBf%YRSO z6?U_`ymWMKF)xI6he0RYiebBh1~ diff --git a/bin/pharext.pubkey b/bin/pharext.pubkey new file mode 100644 index 0000000..b772e48 --- /dev/null +++ b/bin/pharext.pubkey @@ -0,0 +1,9 @@ +-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA5x9bwisjDBDV/bwDiju2 +Ebx4kPir32WwT3+hxV0/qAPclA1WsrpcUJ7BChk+Rlz8ujOcyENTidgI1vj3oUpo +/P9XlLQOSrJHYz+AOg7qwhTe89xIJspS4gHHiXUAmxz0TyCNMbOyrLcjP5CmZdll +n+e3HP8Kfipr4XyWBhsKbdYUZ8Ga6IeFMYzNqCzWazcOasdCpsablmyrfCaZoJ0l +bFald0nF3/YoeYgo3fWb4Md9Xf/grpz8Ocqyq4OY49Vb0/p8FMwzBV6vbVh/eAV/ +jrP7L40Jw97nSBrP/5nK8Ylc5BayVRq/HhT3kLMC//zvPjb8xz3ZgVTQrwWTF3Zy ++wIDAQAB +-----END PUBLIC KEY----- diff --git a/build/create-phar.php b/build/create-phar.php index c03ba8e..db9013f 100644 --- a/build/create-phar.php +++ b/build/create-phar.php @@ -4,9 +4,8 @@ * Creates bin/pharext, invoked through the Makefile */ -$pkguniq = uniqid(); $pkgname = __DIR__."/../bin/pharext"; -$tmpname = "$pkgname.$pkguniq.phar.tmp"; +$tmpname = __DIR__."/pharext.phar"; if (file_exists($tmpname)) { if (!unlink($tmpname)) { @@ -16,6 +15,30 @@ if (file_exists($tmpname)) { } $package = new \Phar($tmpname, 0, "pharext.phar"); + +if (getenv("SIGN")) { + shell_exec("stty -echo"); + printf("Password: "); + $password = fgets(STDIN, 1024); + printf("\n"); + shell_exec("stty echo"); + if (substr($password, -1) == "\n") { + $password = substr($password, 0, -1); + } + + $pkey = openssl_pkey_get_private("file://".__DIR__."/pharext.key", $password); + if (!is_resource($pkey)) { + $this->error("Could not load private key %s/pharext.key", __DIR__); + exit(3); + } + if (!openssl_pkey_export($pkey, $key)) { + $this->error(null); + exit(3); + } + + $package->setSignatureAlgorithm(Phar::OPENSSL, $key); +} + $package->buildFromDirectory(dirname(__DIR__)."/src", "/^.*\.php$/"); $package->setDefaultStub("pharext_packager.php"); $package->setStub("#!/usr/bin/php -dphar.readonly=0\n".$package->getStub()); diff --git a/composer.json b/composer.json index a47fb0b..b4aea13 100644 --- a/composer.json +++ b/composer.json @@ -4,5 +4,5 @@ "keywords": ["ext", "extension", "phar", "package", "install"], "type": "project", "license": "BSD-2-Clause", - "bin": ["bin/pharext"] + "bin": ["bin/pharext", "bin/pharext.pubkey"] } diff --git a/src/pharext/CliCommand.php b/src/pharext/CliCommand.php index 46ead1c..9ae9989 100644 --- a/src/pharext/CliCommand.php +++ b/src/pharext/CliCommand.php @@ -125,4 +125,39 @@ trait CliCommand } return sprintf("%s/%s.%s", sys_get_temp_dir(), $prefix, $suffix); } -} \ No newline at end of file + + /** + * Create a new temp directory + * @param string $prefix + * @return string + */ + private function newtemp($prefix) { + $temp = $this->tempname($prefix); + if (!is_dir($temp)) { + if (!mkdir($temp, 0700, true)) { + $this->error(null); + exit(3); + } + } + return $temp; + } + + /** + * rm -r + * @param string $dir + */ + private function rm($dir) { + foreach (scandir($dir) as $entry) { + if ($entry === "." || $entry === "..") { + continue; + } elseif (is_dir("$dir/$entry")) { + $this->rm("$dir/$entry"); + } elseif (!unlink("$dir/$entry")) { + $this->error(null); + } + } + if (!rmdir($dir)) { + $this->error(null); + } + } +} diff --git a/src/pharext/Installer.php b/src/pharext/Installer.php index c30e11a..ed82211 100644 --- a/src/pharext/Installer.php +++ b/src/pharext/Installer.php @@ -120,22 +120,6 @@ class Installer implements Command } } - /** - * Create a new temp directory - * @param string $prefix - * @return string - */ - private function newtemp($prefix) { - $temp = $this->tempname($prefix); - if (!is_dir($temp)) { - if (!mkdir($temp, 0750, true)) { - $this->error(null); - exit(3); - } - } - return $temp; - } - /** * Prepares, configures, builds and installs the extension */ @@ -178,25 +162,6 @@ class Installer implements Command } } - /** - * rm -r - * @param string $dir - */ - private function rm($dir) { - foreach (scandir($dir) as $entry) { - if ($entry === "." || $entry === "..") { - continue; - } elseif (is_dir("$dir/$entry")) { - $this->rm("$dir/$entry"); - } elseif (!unlink("$dir/$entry")) { - $this->error(null); - } - } - if (!rmdir($dir)) { - $this->error(null); - } - } - /** * Execute a program with escalated privileges handling interactive password prompt * @param string $command diff --git a/src/pharext/Openssl/PrivateKey.php b/src/pharext/Openssl/PrivateKey.php new file mode 100644 index 0000000..1d3aed1 --- /dev/null +++ b/src/pharext/Openssl/PrivateKey.php @@ -0,0 +1,27 @@ +key = openssl_pkey_get_private("file://$file", $password); + if (!is_resource($this->key)) { + throw new \Exception("Could not load private key"); + } + } + + function sign(\Phar $package) { + $package->setSignatureAlgorithm(\Phar::OPENSSL, $this->key); + } + + function exportPublicKey($file) { + if (!file_put_contents("$file.tmp", openssl_pkey_get_details($this->key)["key"]) + || !rename("$file.tmp", $file) + ) { + throw new \Exception(error_get_last()["message"]); + } + } +} diff --git a/src/pharext/Packager.php b/src/pharext/Packager.php index a342e2f..28b7050 100644 --- a/src/pharext/Packager.php +++ b/src/pharext/Packager.php @@ -24,6 +24,8 @@ class Packager implements Command $this->args = new CliArgs([ ["h", "help", "Display this help", CliArgs::OPTIONAL|CliArgs::SINGLE|CliArgs::NOARG|CliArgs::HALT], + [null, "signature", "Dump signature", + CliArgs::OPTIONAL|CliArgs::SINGLE|CliArgs::NOARG|CliArgs::HALT], ["v", "verbose", "More output", CliArgs::OPTIONAL|CliArgs::SINGLE|CliArgs::NOARG], ["q", "quiet", "Less output", @@ -45,6 +47,8 @@ class Packager implements Command CliArgs::OPTIONAL|CliArgs::SINGLE|CliArgs::NOARG], ["Z", "bzip", "Create additional PHAR compressed with bzip", CliArgs::OPTIONAL|CliArgs::SINGLE|CliArgs::NOARG], + ["S", "sign", "Sign the *.ext.phar with a private key", + CliArgs::OPTIONAL|CliArgs::SINGLE|CliArgs::REQARG] ]); } @@ -64,6 +68,9 @@ class Packager implements Command $this->help($prog); exit; } + if ($this->args["signature"]) { + exit($this->signature($prog)); + } try { if ($this->args["source"]) { @@ -101,7 +108,19 @@ class Packager implements Command $this->createPackage(); } - + + function signature($prog) { + try { + $sig = (new Phar(Phar::running(false)))->getSignature(); + printf("%s signature of %s\n%s", $sig["hash_type"], $prog, + chunk_split($sig["hash"], 64, "\n")); + return 0; + } catch (\Exception $e) { + $this->error("%s\n", $e->getMessage()); + return 2; + } + } + /** * Traverses all pharext source files to bundle * @return Generator @@ -114,6 +133,20 @@ class Packager implements Command } } + private function askpass($prompt = "Password:") { + system("stty -echo", $retval); + if ($retval) { + $this->error("Could not disable echo on the terminal\n"); + } + printf("%s ", $prompt); + $pass = fgets(STDIN, 1024); + system("stty echo"); + if (substr($pass, -1) == "\n") { + $pass = substr($pass, 0, -1); + } + return $pass; + } + /** * Creates the extension phar */ @@ -125,6 +158,13 @@ class Packager implements Command $this->info("Creating phar %s ...%s", $pkgtemp, $this->args->verbose ? "\n" : " "); try { $package = new Phar($pkgtemp); + + if ($this->args->sign) { + $this->info("\nUsing private key to sign phar ... \n"); + $privkey = new Openssl\PrivateKey(realpath($this->args->sign), $this->askpass()); + $privkey->sign($package); + } + $package->startBuffering(); $package->buildFromIterator($this->source, $this->source->getBaseDir()); $package->buildFromIterator($this->bundle()); @@ -132,7 +172,7 @@ class Packager implements Command $package->setDefaultStub("pharext_installer.php"); $package->setStub("#!/usr/bin/php -dphar.readonly=1\n".$package->getStub()); $package->stopBuffering(); - + if (!chmod($pkgtemp, 0777)) { $this->error(null); } elseif ($this->args->verbose) { @@ -160,7 +200,7 @@ class Packager implements Command $this->error("%s\n", $e->getMessage()); } } - + unset($package); } catch (\Exception $e) { $this->error("%s\n", $e->getMessage()); @@ -176,6 +216,16 @@ class Packager implements Command exit(5); } $this->info("OK\n"); + if ($this->args->sign && isset($privkey)) { + $keyname = $this->args->dest ."/". basename($pkgfile) . ".pubkey"; + $this->info("Public Key %s ... ", $keyname); + try { + $privkey->exportPublicKey($keyname); + $this->info("OK\n"); + } catch (\Exception $e) { + $this->error("%s", $e->getMessage()); + } + } } } } diff --git a/tests/src/pharext/FilteredSourceDirTest.php b/tests/src/pharext/FilteredSourceDirTest.php index c1b2b1f..c561583 100644 --- a/tests/src/pharext/FilteredSourceDirTest.php +++ b/tests/src/pharext/FilteredSourceDirTest.php @@ -35,7 +35,7 @@ class FilteredSourceDirTest extends \PHPUnit_Framework_TestCase return $fi->getRealpath(); }, $filtered); sort($fltfiles); - + $this->assertEquals(array(), array_diff($gitfiles, $fltfiles)); $this->assertEquals($gitfiles, $fltfiles); } } \ No newline at end of file -- 2.30.2