openssl signing
authorMichael Wallner <mike@php.net>
Sun, 8 Mar 2015 11:25:26 +0000 (12:25 +0100)
committerMichael Wallner <mike@php.net>
Mon, 9 Mar 2015 07:48:37 +0000 (08:48 +0100)
.gitignore
bin/pharext
bin/pharext.pubkey [new file with mode: 0644]
build/create-phar.php
composer.json
src/pharext/CliCommand.php
src/pharext/Installer.php
src/pharext/Openssl/PrivateKey.php [new file with mode: 0644]
src/pharext/Packager.php
tests/src/pharext/FilteredSourceDirTest.php

index 623b4511dffc01cde730162f802cacfda51028ca..a660ac2d946787485d019c09514eccbdde5df3f3 100644 (file)
@@ -6,4 +6,7 @@ nbproject/
 *.tmp
 *.phar
 *.phar.gz
-*.phar.bz2
\ No newline at end of file
+*.phar.bz2
+build/pharext.key
+*.phar*
+*.pubkey
index 3277bb88e6091baefa70c1af5338d1225f1f9a0d..76396714bb29123affc8317d1e3c3176596f018f 100755 (executable)
Binary files a/bin/pharext and b/bin/pharext differ
diff --git a/bin/pharext.pubkey b/bin/pharext.pubkey
new file mode 100644 (file)
index 0000000..b772e48
--- /dev/null
@@ -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-----
index c03ba8e2f3aac586a15256c57eb212be83ec700d..db9013faa4a8f72876a5d2274c411ee3129f18be 100644 (file)
@@ -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());
index a47fb0bf7787f297219fa85cf70a53e3fb55824d..b4aea135e463a9c24ad7c7baf1e216573ac56159 100644 (file)
@@ -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"]
 }
index 46ead1ccd977db7b03893ee9e00e4d128278dec3..9ae9989391c8f46acb8553f3f12de071d5398b25 100644 (file)
@@ -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);
+               }
+       }
+}
index c30e11a5137d5c7a57f80013e3b6424d2d3f9000..ed8221113a2105a0a400967f250bc8719c66e94c 100644 (file)
@@ -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 (file)
index 0000000..1d3aed1
--- /dev/null
@@ -0,0 +1,27 @@
+<?php
+
+namespace pharext\Openssl;
+
+class PrivateKey
+{
+       private $key;
+       
+       function __construct($file, $password) {
+               $this->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"]);
+               }
+       }
+}
index a342e2f25d5c8795bc983f7194cf1bb4052e7a2e..28b705069ed8d9f01c9534362bdb381b2ab73619 100644 (file)
@@ -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());
+                               }
+                       }
                } 
        }
 }
index c1b2b1f5acc8fe1d3728fd9bdad8632b3cfe5020..c561583f73ec96a3e833b89b73f5b71ab3457a1a 100644 (file)
@@ -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