<?php
/**
* gpg.php
* -----------
* GPG plugin class file.
* This file contains the GnuPG class, as well as the GnuPGKey and GnuPGSig classes
*
* Copyright (c) 2002-2004 GPG Plugin Development Team
* Licensed under the GNU Lesser GPL. For full terms see the file COPYING.
*
* @package gpg
* @author Brian Peterson
* @author Ryan
* @author Aaron van Meerten
*
* $Id: gpg.php,v 1.58 2005/12/21 02:55:46 ke Exp $
*
*/
define( 'GPGSTDIN', 0 );
define( 'GPGSTDOUT', 1 );
define( 'GPGSTDERR', 2 );
define( 'STATUS_FD', 5 );
define( 'PASSPHRASE_FD', 7 );
define( 'CHECKPASSWORD_FD', 3 );
// from gnupg include/cipher.h
define( 'GNUPG_HASH_MD5', 1 );
define( 'GNUPG_HASH_SHA1', 2 );
define( 'GNUPG_HASH_RMD160', 3 );
define( 'GNUPG_HASH_TIGER', 6 );
define( 'GNUPG_HASH_SHA256', 8 );
define( 'GNUPG_HASH_SHA384', 9 );
define( 'GNUPG_HASH_SHA512', 10 );
define( 'GNUPG_PUBKEY_RSA', 1 );
define( 'GNUPG_PUBKEY_RSA_E', 2 ); /* RSA encrypt only */
define( 'GNUPG_PUBKEY_RSA_S', 3 ); /* RSA sign only */
define( 'GNUPG_PUBKEY_ELGAMAL_E', 16 ); /* encrypt only ElGamal (but not for v3)*/
define( 'GNUPG_PUBKEY_DSA', 17);
define( 'GNUPG_PUBKEY_ELGAMAL', 20 ); /* sign and encrypt elgamal */
// Trust Values (from GnuPG's DETAILS docs)
define( 'GNUPG_TRUST_ULTIMATE', 'u' );
define( 'GNUPG_TRUST_FULL', 'f' );
define( 'GNUPG_TRUST_MARGINAL', 'm' );
define( 'GNUPG_TRUST_NONE', 'n' );
define( 'GNUPG_TRUST_EXPIRED', 'e' );
define( 'GNUPG_TRUST_REVOKED', 'r' );
define( 'GNUPG_TRUST_UNKNOWN', '-' );
/*********************************************************************/
/**
* class GnuPGuid
*
* This class contains user information about a key or signature
*
*/
class GnuPGuid
{
var $email_name = '';
var $email_addr = '';
var $email_extra = '';
var $signatures = array();
/***********************************************/
/**
* function GnuPGuid
*
* Constructor
*
* @param string $uidstring containing userid information
*
*/
function GnuPGuid ($uidstring) {
$j = substr_count($uidstring, "[User id not found]");
if ($j) { $this->email_name='Unknown'; return; }
$matches = split("[<>]", $uidstring);
switch (count($matches)) {
case 1:
//Assume it's just an address.
$this->email_addr = htmlspecialchars(trim($matches[0]));
$this->email_name = htmlspecialchars(trim($matches[0]));
break;
case 2:
//Assume it's a name followed by an address.
$this->email_name = htmlspecialchars(trim($matches[0]));
$this->email_addr = htmlspecialchars(trim($matches[1]));
break;
default:
//Assume name, address, extra.
$this->email_name = htmlspecialchars(trim($matches[0]));
$this->email_addr = htmlspecialchars(trim($matches[1]));
$this->email_extra = htmlspecialchars(trim(join(array_slice($matches, 2, (count($matches) - 2)), "")));
break;
}
}
}
/*********************************************************************/
/**
* class GnuPGsig
*
* This class contains information about a signature on data or a file
*
*/
class GnuPGSignature
{
var $valid=false;
var $id=''; //fingerprint in hex
var $creation_date=''; //sig_creation_date
var $timestamp='';//sig-timestamp
var $exp='';//expire-timestamp
var $version='';//sig-versionreserved
var $reserved='';//reserved
var $pubkey_algorithm=0;//pubkey-algo
var $algorithm = ''; //text of algorithm
var $hash_algorithm=0;//hash-algo
var $hash; //text of hash algorithm
var $sigclass='';//sig-class
var $fpr=''; //primary-key-fpr
var $uid = false; //userid
/***********************************************/
/**
* function GnuPGSignature
*
* Constructor, takes two types of parameters, either an array or a string
*
* @param array $keyInfo containing data about a signature
* @param string $keyInfo matching gpg: Signature made DATE by ALG key ID
*/
function GnuPGSignature($keyInfo) {
if (is_array($keyInfo)) {
if ($keyInfo[1]=='VALIDSIG') {
$this->valid=true;
$this->id=$keyInfo[2];
$this->creation_date = $keyInfo[3]; //date('Y-m-d',$keyInfo[3]);
$this->timestamp = $keyInfo[4];
$this->exp = $keyInfo[5];
$this->version=$keyInfo[6];
$this->reserved=$keyInfo[7];
$this->pubkey_algorithm=$keyInfo[8];
$this->hash_algorithm=$keyInfo[9];
$this->sigclass=$keyInfo[10];
$this->fpr=$keyInfo[11];
} elseif ($keyInfo[1]=='ERRSIG') {
$this->valid=false;
$this->id=$keyInfo[2];
$this->pubkey_algorithm=$keyInfo[3];
$this->hash_algorithm=$keyInfo[4];
$this->sigclass=$keyInfo[5];
$this->timestamp=$keyInfo[6];
}
switch ($this->pubkey_algorithm) {
case GNUPG_PUBKEY_RSA:
$this->algorithm='RSA';
break;
case GNUPG_PUBKEY_RSA_S:
$this->algorithm='RSA_S';
break;
case GNUPG_PUBKEY_ELGAMAL:
$this->algorithm='ELGAMAL';
break;
case GNUPG_PUBKEY_DSA:
$this->algorithm='DSA';
break;
}
switch ($this->hash_algorithm) {
case GNUPG_HASH_MD5:
$this->hash='MD5';
break;
case GNUPG_HASH_SHA1:
$this->hash='SHA1';
break;
case GNUPG_HASH_RMD160:
$this->hash='RMD160';
break;
case GNUPG_HASH_TIGER:
$this->hash='TIGER';
break;
case GNUPG_HASH_SHA256:
$this->hash='SHA256';
break;
case GNUPG_HASH_SHA384:
$this->hash='SHA384';
break;
case GNUPG_HASH_SHA512:
$this->hash='SHA512';
break;
}
} else {
preg_match('/^gpg: Signature made (.*) using (.*) key ID (.*)/',$keyInfo,$matches);
$this->id=$matches[3];
$this->creation_date = $matches[1];
$this->timestamp = strtotime($matches[1]);
$this->algorithm=$matches[2];
switch ($matches[2]) {
case 'RSA':
$this->pubkey_algorithm=GNUPG_PUBKEY_RSA;
break;
case 'RSA_S':
$this->pubkey_algorithm= GNUPG_PUBKEY_RSA_S;
break;
case 'ELGAMAL':
$this->pubkey_algorithm= GNUPG_PUBKEY_ELGAMAL;
break;
case 'DSA':
$this->pubkey_algorithm= GNUPG_PUBKEY_DSA;
break;
}
}
}
}
/*********************************************************************/
/**
* class GnuPGsig
*
* This class contains information about a signature on a key or uid
*
*/
class GnuPGsig
{
var $algorithm=0;
var $id='';
var $date='';
var $exp='';
var $uid = false;
var $trust='';
/***********************************************/
/**
* function GnuPGsig
*
* Constructor
*
* @param array $keyInfo containing data about a signature
*
*/
function GnuPGsig($keyInfo) {
$this->algorithm=$keyInfo[3];
$this->id=$keyInfo[4];
$this->date = date('Y-m-d',$keyInfo[5]);
if ($keyInfo[6])
$this->exp = date('Y-m-d',$keyInfo[6]);
$this->uid = new GnuPGuid($keyInfo[9]);
if (strpos ($keyInfo[10], 'l'))
$this->trust='local';
if (strpos ($keyInfo[10],'x'))
$this->trust='exportable';
}
}
/*********************************************************************/
/**
* class GnuPGkey
*
* This class contains information about a key and a function to output as an array
*
*/
class GnuPGkey
{
var $haveSecret = false;
var $capabilities = array();
var $fingerprint = '';
var $userIDs = array();
var $subkeys = array();
var $signatures = array();
var $userpassphrase = '';
var $type=0;
var $len=0;
var $algorithm=0;
var $id='';
var $date='';
var $exp='';
// See defines above for possible values
var $trust = '';
/***********************************************/
/**
* function GnuPGkey
*
* Constructor
*
* @param array $keyInfo containing data for the key
*
*/
function GnuPGkey($keyInfo) {
if( strpos( $keyInfo[11], 'E' ) )
$this->capabilities[] = "encrypt";
if( strpos( $keyInfo[11], 'S' ) )
$this->capabilities[] = "sign";
$this->type = $keyInfo[0];
$this->trust = $keyInfo[1];
$this->len = $keyInfo[2];
$this->algorithm = $keyInfo[3];
$this->id = $keyInfo[4];
$this->date = date('Y-m-d',$keyInfo[5]);
if ($keyInfo[6])
$this->exp = date('Y-m-d',$keyInfo[6]);
}
/***********************************************/
/**
* function get_email_name
*
* Retrieves name for the first or active uid on the key
*
* @param void
*
*/
function get_email_name() {
if (count($this->userIDs) > 0) {
$uid = current($this->userIDs);
return $uid->email_name;
}
else return '';
}
/***********************************************/
/**
* function get_email_addr
*
* Retrieves email address for the first or active uid on the key
*
* @param void
*
*/
function get_email_addr() {
if (count($this->userIDs) > 0) {
$uid = current($this->userIDs);
return $uid->email_addr;
}
else return '';
}
/***********************************************/
/**
* function get_email_extra
*
* Retrieves extra information for the first or active uid on the key
*
* @param void
*
*/
function get_email_extra() {
if (count($this->userIDs) > 0) {
$uid = current($this->userIDs);
return $uid->email_extra;
}
else return '';
}
/***********************************************/
/**
* function arrayKey
*
* Returns an array with key data indexed by field name
*
* @param void
* @return array of particular key data indexed by field
*
*/
function arrayKey() {
return array(
'type' => $this->type,
'len' => $this->len,
'id' => $this->id,
'date'=> $this->date,
'exp' => $this->exp,
'email_name' => $this->get_email_name(),
'email_addr' => $this->get_email_addr(),
'email_extra' => $this->get_email_extra(),
'alg' => $this->algorithm
);
}
/***********************************************/
/**
* function get_fpr
*
* Returns a string containing the formatted fingerprint for the key
*
* @param void
* @return string with lowercase fingerprint, split every 4 characters
*
*/
function get_fpr() {
$fpr = $this->fingerprint;
$pos=0;
$return='';
while ($pos < strlen($fpr)) {
$return .= substr($fpr,$pos,4) . ' ';
$pos=$pos+4;
}
return strtolower($return);
}
}
/*********************************************************************/
/**
* class GnuPG
*
* This is the main class to use for GPG functions and data
*
*/
class GnuPG
{
// Turn on some debug output
var $debug = false;
var $force_exec=false; //set true to disable proc_open facilities
var $indata = '', $outdata='';
var $gpg_pipes = array();
var $pipeOpen = array();
var $systemKeyring=false;
var $alternateKeyring=false;
var $alternateSecretKeyring=false;
var $trustedKeys=array();
var $gpg_exe = "/usr/bin/gpg";
var $gpg_options = "--no-tty --yes --openpgp";
var $gpgHomeDir = ""; // if you want to specify some other GnuPG home directory (use full path)
var $comment = ''; // if you want a comment added to the command line sent to gpg
var $tempDir = ''; //set this if you want to use a directory other than the standard TEMP dir
var $tempFileMode = 0600;
var $passphrase = false;
var $newpassphrase = false;
var $statusout = '', $stdout='', $stderr='';
var $fileDescriptors = array (
GPGSTDIN => array( 'pipe', 'r' ), // this is stdin for the child (We write to this one)
GPGSTDOUT => array( 'pipe', 'w' ), // child writes here (stdout)
GPGSTDERR => array( 'pipe', 'w' ), // stderr
STATUS_FD => array( 'pipe', 'w' ),
PASSPHRASE_FD => array( 'pipe', 'r' )
);
//These variables control the environmental variables that we try to set before executing the gpg binary
//This is used now only for language, but could be extended to add shell environmental values, like gpg-agent strings
var $lang_env_vars = array('LC_ALL'=>'en_US','LC_LANG'=>'en_US','LC_LANGUAGE'=>'en_US');
//this var contains the previous values, for use when setting the value back to the original before the gpg binary was called
var $lang_env_values= array();
//allowEntropyIncrease sets whether or not to spawn extra ls processes to use the disk when generating keys
var $allowEntropyIncrease=true;
//sets the key preferences when generating new keys. This is a sensible default
var $defaultKeyPrefs="S2 S7 S3 H2 H3 Z2 Z1";
// Temporary directory code
var $tmpdir='/tmp';
var $tmp_locations = array('/tmp','/dev/shm', '/dev/mfs', '/var/tmp', 'c:\temp', 'c:\windows\temp', 'c:\winnt\temp');
// Signature stuff
var $signHashAlgo = 0, $signPubKeyAlgo = 0;
// Keys stuff
var $keys = false;
var $arraykeys = false;
var $defaultKeyFingerprint = '';
//stateful information
var $action=false; // main way of keeping track of our state
var $activeKey=false; //what key does gpg want the passphrase
var $interactionData = array();
var $newKeys = array(); // new keys created
var $encryptKeys = array(); //keys used to encrypt message
// error stuff
var $error = false;
var $errorCode = '';
var $errorDescription = '';
// Verify Stuff
// Whenever we check something that's signed, the fingerprint and userid of the signed key goes here
var $signedKeyFingerprint = '';
var $signedKeyUserID = false;
// can be "GOOD", "MISSING PUBLIC KEY", "EXPIRED", "BAD", "ERROR"
var $verifyStatus = '';
var $verifiedSignature=false;
var $verifiedUserID=false;
// Encrypt Stuff
var $invalidRecipients = array(); // array of recipients who didn't get processed properly
/***********************************************/
/**
* function setHomeDir
*
* set the GnuPG home directory
*
* @param string $newHomeDir containing path to gnupg home directory
*
*/
function setHomeDir( $newHomeDir )
{
$this->gpgHomeDir = $newHomeDir;
}
/***********************************************/
/**
* Adds a directory to the end of the list of directories used when attempting to find a writeable temp directory
*
* @param string $newTempDir containing path to gnupg home directory
*
*/
function addTempDir($newTempDir) {
if ($newTempDir) {
$this->tmp_locations[]=$newTempDir;
}
$this->tmp_locations=array_unique($this->tmp_locations);
}
/***********************************************/
/**
* set the temporary directory (for very temporary output of files, which are securely overwritten)
*
* @param string $newTempDir containing path to gnupg home directory
*
*/
function setTempDir($newTempDir) {
$this->tmp_dir=$newTempDir;
}
function verifyDetachedSignature( $data=false, $signature )
{
$this->clearError();
$this->verified = false;
$this->verifyStatus = '';
// first, we put the detached signature in a file
// then we send the signed data as GPGSTDIN and tell GnuPG to get the
// signature from the file
$detachedSignatureFilename = tempnam( $this->tempDir, "GPG" );
if( $detachedSignatureFilename != "FALSE" )
{
chmod( $detachedSignatureFilename, $this->tempFileMode );
$detachSigFile = fopen($detachedSignatureFilename,'w');
fwrite( $detachSigFile, $signature );
fclose( $detachSigFile );
$this->printDebug( "executing verify detach signature" );
$command = "--verify ".$detachedSignatureFilename." - ";
$return = $this->execute_gpg( $command, $data );
// delete our temp file
unlink( $detachedSignatureFilename );
}
return $return;
}
function verifyUsingFiles( $data=false, $signature )
{
$this->clearError();
$this->verified = false;
$this->verifyStatus = '';
// first, we put the detached signature in a file
// then we send the signed data as GPGSTDIN and tell GnuPG to get the
// signature from the file
$detachedSignatureFilename = tempnam( $this->tempDir, "GPG" );
$dataFilename = tempnam( $this->tempDir, "GPG" );
if( $detachedSignatureFilename != "FALSE" && $dataFilename != "FALSE")
{
chmod( $detachedSignatureFilename, $this->tempFileMode );
$detachSigFile = fopen($detachedSignatureFilename,'w');
fwrite( $detachSigFile, $signature );
fclose( $detachSigFile );
chmod( $dataFilename, $this->tempFileMode );
$dataFile = fopen($dataFilename,'w');
fwrite( $dataFile, $data );
fclose( $dataFile );
$this->printDebug( "executing verify detach signature" );
$command = "--verify ".$detachedSignatureFilename." ".$dataFilename." ";
$return = $this->execute_gpg( $command, $data );
// delete our temp file
unlink( $detachedSignatureFilename );
unlink( $dataFilename );
}
return $return;
}
function verifyFileSignature($filename, $signature) {
$this->action='verify';
$params = "--verify - $filename";
$return = $this->execute_gpg($params, $signature);
return $return;
}
// verify an inline signature (clear-text signature)
function verify( $data, $type="clear" )
{
$this->clearError();
$this->verified = false;
$this->verifyStatus = '';
if ($type=="notclear") {
$command = "--decrypt";
} else {
$command = "--verify";
}
$return = $this->execute_gpg( $command, $data );
$return['GnuPGSignature'] = $this->verifiedSignature;
$return['output'] = $this->stdout;
return $return;
}
/***********************************************/
/**
* lists the encryption keys used for encrypted data
*
* @param string $data containing encrypted data to find keys for
* @return array $return containing $return['encryptKeys'] with key_ids of recipients,
* also contains $return['missingSecretKeys'] with key_ids of secret keys not on the included keyrings
**/
function list_encrypt_keys( $data )
{
$this->clearError();
$command = "--decrypt --list-only";
$return = $this->execute_gpg( $command, $data );
$return['encryptKeys'] = $this->encryptKeys;
$return['missingSecretKeys'] = $this->interactionData['missingSecretKeys'];
return $return;
}
/***********************************************/
/**
* function verifyPassphrase
*
* Verifies a passphrase for a secret key
* The default key is used if no secret key is specified
*
* @param string $passphrase containing passphrase to verify
* @param string $keyfpr containing fingerprint of secret key
* @param array $return containing $return['verified'] equal to 'true'
* if passphrase is verified or 'false' if not
*
*/
function verifyPassphrase( $passphrase, $keyfpr=false) {
if ($passphrase)
$this->passphrase=$passphrase;
$return = $this->sign('Authenticated',$keyfpr);
$return['verified'] = false;
if ($return['output']) {
$sep = '-----BEGIN PGP SIGNED MESSAGE-----';
list ($front, $cyphertext_tail) = explode ($sep, $return['output']);
if ($cyphertext_tail) {
$return['verified'] = true;
} else {
$return['errors'][] = $return['output'];
}
} //else { $return['errors'][] = _("Error: no output received, cannot verify passphrase"); }
return $return;
}
/*********************************************************************/
/**
* function decrypt - This function does the decryption.
*
* This is the workhorse of decryption
*
* @param string $body Body String to decrypt
* @param string $passphrase Passphrase to pass to gpg
* @param optional string $filename Filename to decrypt binary file
* @return array with results
*/
function decrypt($body, $passphrase, $filename='', $outfile='')
{
$params='';
$this->action="decrypt";
if ($filename=='') {
$params.= " --decrypt";
} else {
if ($outfile== '') {
$params.=" --use-embedded-filename --decrypt-files $filename";
} else { $params .=" --output \"$outfile\" --decrypt \"$filename\""; }
}
if ($passphrase) {
$this->passphrase= $passphrase;
}
$return=$this->execute_gpg($params, $body);
$return['plaintext'] = $return['output'];
if ($this->verifiedSignature) {
$return['GnuPGSignature'] = $this->verifiedSignature;
}
return $return;
}
/***********************************************/
/**
* function signFile
*
* Clearsigns and ascii-armors a signature on an external file
*
* @param string $filename containing path of file to sign
* @param string $passphrase containing passphrase to secret key
* @param string $signingKey containing fingerprint of key to use for signing
* @return array $return containing $return['output'] with clearsigned data
*
*/
function signFile($filename=false, $passphrase=false, $signingKey=false)
{
if (!$filename) { return false; }
if (!$signingKey) {
$this->printDebug("No key specified for siging, using default: " . $this->defaultKeyFingerprint);
$signingKey=$this->defaultKeyFingerprint;
}
$sigfilename=$filename.".asc";
if (file_exists($sigfilename)) {
unlink($sigfilename);
}
$params = "--armor";
if (!$this->keys[$signingKey]) {
$this->refreshKeys($signingKey);
}
$signingKey=$this->getKeyIndexFromFingerprint($signingKey);
$this->action="sign";
if ($signingKey) {
$params.= " --default-key $signingKey";
if ($passphrase) {
$this->keys[$signingKey]->passphrase=$passphrase;
}
} else {
if ($passphrase) {
$this->passphrase = $passphrase;
}
}
$params .=" --detach-sign $filename";
$return = $this->execute_gpg($params);
if (is_file($sigfilename)) {
$return['filename'] = $sigfilename;
}
return $return;
}
/**
* Sign data using files
*/
function signUsingFiles($data, $passphrase=false, $signingKey=false){
$tmpFile = $this->getTempFile();
file_put_contents($tmpFile,$data);
$ret = $this->signFile($tmpFile,$passphrase,$signingKey);
//copy($tmpFile,'/tmp/ZOPA');
//copy($ret['filename'],'/tmp/ZOPA.asc');
if(is_file($ret['filename'])){
$ret['output'] = file_get_contents($ret['filename']);
$this->secure_unlink($ret['filename']);
unset($ret['filename']);
}
$this->secure_unlink($tmpFile);
return $ret;
}
/***********************************************/
/**
* function sign
*
* Clearsigns and ascii-armors passed data with a secret key
*
* @param string $data containing information to sign
* @param string $signingKey containing fingerprint of key to use for signing
* @return array $return containing $return['output'] with clearsigned data
*
*/
function sign( $data = false, $signingKey = false, $passphrase='', $type='clear')
{
$this->clearError();
$commandExtras='';
// if( $this->defaultKeyFingerprint != '' )
// $commandExtras = '--default-key '.$defaultKeyFingerprint.' ';
if (!$signingKey) {
$this->printDebug("No key specified, going with default key: " . $this->defaultKeyFingerprint);
$signingKey=$this->defaultKeyFingerprint;
}
if ($signingKey) {
if (!$this->keys[$signingKey]) {
$this->refreshKeys($signingKey);
$signingKey=$this->getKeyIndexFromFingerprint($signingKey);
$signingKey=strtoupper($signingKey);
}
if ($passphrase)
$this->keys[$signingKey]->passphrase=$passphrase;
} else { if ($passphrase) $this->passphrase=$passphrase; }
$this->action="sign";
if ($signingKey) {
$commandExtras = '--default-key '.$signingKey.' ';
}
$command = $commandExtras."--armor";
switch ($type) {
case 'notclear':
$command .= " --sign";
break;
case 'clear':
default:
$command .= " --clearsign";
break;
}
$return = $this->execute_gpg( $command, $data );
return $return;
}
/*********************************************************************/
/**
*
* function update_trustdb()
*
* This function will update the gpg trustdb for the current user
* including a scan of the system keyring if enabled
*
* @return array $return containing info, warnings, etc
*/
function update_trustdb()
{
$params = '';
//now add our check trustdb command
$params .= ' --check-trustdb';
return $this->execute_gpg($params);
}
/*********************************************************************/
/**
* function encrypt
* This function does the encryption
* This is the workhorse of the encryption side of the plugin
*
* Add code here to use user preferences to modify the gpg command line
*
* @param string $body Body text string
* @param array $send_to containing recipient list
* @param optional boolean $sign (true/false) do we want to sign the message/file
* @param optional string $passphrase passphrase string needed for signing functions
* @param optional string $filename if we are going to encrypt a file
* @return array with results
*/
function encrypt($body,$send_to_list, $sign='false', $passphrase=false, $filename ='',$signingkeyfpr=false)
{
if (!$signingkeyfpr) {
$signingkeyfpr=$this->defaultKeyFingerprint;
}
if ($signingkeyfpr && $sign == 'true') {
if (!$this->keys[$signingkeyfpr]) {
$this->refreshKeys($signingkeyfpr);
$signing_key_id=$this->getKeyIndexFromFingerprint($signingkeyfpr);
}
$this->keys[$signing_key_id]->passphrase = $passphrase;
}
$this->interactionData['encrypt_to_list']=$send_to_list;
$send_to_list_param = implode(' -r ',$send_to_list);
$this->printDebug("Encrypting to: $send_to_list_param");
$params='';
//add the signing parameters
if ($sign=='true' and $signing_key_id and $filename!='') {
$params .= " --sign --default-key $signing_key_id ";
} elseif ($sign=='true' and $signing_key_id) {
$params .= " --output - --sign --default-key $signing_key_id ";
} elseif ($sign=='true') {
//$return['errors'][] = _("GPG Plugin: You must specify a signing key in the Options screen to sign messages.");
$params .= " --output - --sign ";
};
//add the trusted key parameters if needed
if (!(count($this->trustedKeys)>0)) {
$params .= ' --always-trust ';
};
// wrap it up by setting the recipients to the sender list
// and redirect the output to stderr using 2>&1
$params .= " --force-mdc --armor --encrypt -r $send_to_list_param ".escapeshellarg($filename);
$this->action="encrypt";
if ($passphrase)
$this->passphrase=$passphrase;
$return=$this->execute_gpg($params,$body);
$return['cyphertext']=$return['output'];
return $return;
}
/***********************************************/
/**
* function deleteKey
*
* This function deletes a key from the keyring in the homedir
* It will not remove the key from the keys list, so a refreshkeys is need to reflect the new status of the keyring
*
* @param string $fpr
* @param string $type
* @return array $return containing errors, warnings, etc
*/
function deleteKey($fpr, $type="all")
{
//Choose a flag.
switch ($type) {
case "private":
$flag = "--delete-secret-key";
break;
default:
case "all":
$flag = "--delete-secret-and-public-key";
break;
}
$params = "--yes --batch $flag $fpr";
return $this->execute_gpg($params);
}
/***********************************************/
/**
* function deleteUID
*
* Deletes a UID from a key
*
* @param string $fpr containing fingerprint or key id of the key with the uid to delete
* @param string $uidnos containing the uid number to delete
* @return array $return containing errors and output
*
*/
function deleteUID($fpr, $uidnos)
{
$this->action="delUID";
$params="--edit-key $fpr $uidnos deluid";
$return=$this->execute_gpg($params);
return $return;
}
/***********************************************/
/**
* function addUID
*
* Adds a UID to a key
*
* @param string $fpr containing fingerprint or key id of the private key for which to add a uid
* @param string $passphrase containing passphrase to the secret key
* @param string $uidname containing the name associated with this uid
* @param string $uidemail containing the email address associated with this uid
* @param string $comment containing a comment or extra information about the uid
* @return array $return containing errors and output
*
*/
function addUID($fpr, $passphrase, $uidname, $uidemail, $comment)
{
if (!$this->keys[$fpr]) {
$this->refreshKeys($fpr);
$fpr = $this->getKeyIndexFromFingerprint($fpr);
}
$this->keys[$fpr]->passphrase=$passphrase;
$this->action='addUID';
$this->interactionData['name'] = $uidname;
$this->interactionData['email'] = $uidemail;
$this->interactionData['comment'] = $comment;
$params = "--edit-key $fpr adduid";
$return = $this->execute_gpg($params);
return $return;
}
/*******************************************************/
/**
* function expireKey
*
* Sets the expiration on a primary key
*
* @param string $fpr containing fingerprint or key id of the primary key to set expiration date on
* @param string $passphrase containing passphrase for the secret key
* @param string $expiration containing string of either # for days or #w for weeks or #y for years
* @return array $return containing info, errors, etc
*/
function expireKey($fpr, $passphrase, $expiration)
{
if (!$this->keys[$fpr]) {
$this->refreshKeys($fpr);
$fpr = $this->getKeyIndexFromFingerprint($fpr);
}
$this->keys[$fpr]->passphrase=$passphrase;
$this->action='expireKey';
$this->interactionData['valid'] = $expiration;
return $this->execute_gpg("--edit-key $fpr");
}
/*******************************************************/
/**
* function expireSubKey
*
* Sets the expiration on a secondary key
*
* @param string $fpr containing fingerprint or key id of the primary key to set expiration date on
* @param string $subkey containing index of subkey to expire
* @param string $passphrase containing passphrase for the secret key
* @param string $expiration containing string of either # for days or #w for weeks or #y for years
* @return array $return containing info, errors, etc
*/
function expireSubKey($fpr, $subkey, $passphrase, $expiration)
{
if (!$this->keys[$fpr]) {
$this->refreshKeys($fpr);
$fpr = $this->getKeyIndexFromFingerprint($fpr);
}
$this->keys[$fpr]->passphrase=$passphrase;
$this->action='selectSubKey';
$this->interactionData['action'] = 'expireKey';
$this->interactionData['subkeyno'] = $subkey;
$this->interactionData['valid'] = $expiration;
return $this->execute_gpg("--edit-key $fpr");
}
/***********************************************/
/**
* function deleteSubKey
*
* Deletes a subkey pair from a main keypair
*
* @param string $fpr containing fingerprint or key id of the private key from which to delete a subkey
* @param integer $subkeyno containing the offset of subkey to delete (1 is the first);
* @return array $return containing errors and output
*
*/
function deleteSubKey($fpr, $subkeyno)
{
$this->action="selectSubKey";
$this->interactionData['action']='deleteSubKey';
$this->interactionData['subkeyno'] = $subkeyno;
$params = "--edit-key $fpr";
$return = $this->execute_gpg($params);
return $return;
}
/*********************************************************************/
/**
* function generateKey
* This function generates a keypair
*
* Keys created with the option set below are PGP compatible
* Key-Type: DSA
* Key-Length: 1024
* Subkey-Type: ELG-E
* Preferences: S2 S7 S3 H2 H3 Z2 Z1
* (don't forget to put in the other options needed for actual key creation)
*
* @param integer $debug
* @param string $real_name Full Name for the uid
* @param string $email Email address to be oput in the uid
* @param string $passphrase Passphrase to protect te secret key
* @param optional string $comment Comment to be appended to the default comment
* @param optional integer $keylength Length of key to generate
* @param optional date $expiredate when should this key expire?
* @return array $return with output we were able to retrieve from the gpg command including $return['newkeys'] first element being fingerprint of new key generated, if available
*
*/
function generateKey($name, $email, $passphrase, $comment = '', $keylength = 1024, $expiredate=0,$algo=1,$prefs=false)
{
if ($this->force_exec) {
//use alternate method if we aren't using pipes
return $this->generateKey_nopipes($name, $email, $passphrase, $comment, $keylength, $expiredate, $prefs);
}
$this->action="generateKey";
$this->newpassphrase=$passphrase;
$this->interactionData['name']=$name;
$this->interactionData['email']=$email;
$this->interactionData['comment']=$comment;
$this->interactionData['algo']=$algo;
$this->interactionData['size']=$keylength;
$this->interactionData['valid']= $expiredate;
$return= $this->execute_gpg('--gen-key');
$return['newkeys'] = $this->newKeys;
$fpr=$this->newKeys[0];
$this->setKeyPrefs($fpr, $prefs, $passphrase);
return $return;
}
function generateKey_nopipes($name, $email, $passphrase, $comment, $keylength=1024, $expiredate=0, $prefs=false)
{
$this->refreshKeys('','secret');
$lastkeys=array_keys($this->keys);
$this->action="generateKey_nopipes";
$this->newpassphrase=$passphrase;
if (!$prefs) $prefs=$this->defaultKeyPrefs;
$data='';
$data.="Key-Type: DSA\n";
$data.="Key-Length: 1024\n";
$data.="Subkey-Type: ELG-E\n";
$data.="Subkey-Length: " . $keylength . "\n";
$data.="Name-Real: " . $name . "\n";
if ($comment)
$data.="Name-Comment: " . $comment . "\n";
$data.="Name-Email: " . $email . "\n";
$data.="Expire-Date: ". $expiredate ."\n";
$data.="Passphrase: " . $passphrase . "\n";
$data.="Preferences: $prefs\n";
$data.="%commit\n";
$cmd="--gen-key --batch --armor";
$return= $this->execute_gpg($cmd, $data);
//check for new keys
$this->refreshKeys('','secret');
$diff=array_diff(array_keys($this->keys), $lastkeys);
if (count($diff)==0) { $return['errors']=_("Keys did not generate. Please contact your system administrator for assistance debugging this."); }
foreach ($diff as $newfpr) {
$this->newKeys[]=$newfpr;
}
$return['newkeys'] = $this->newKeys;
$fpr=$this->newKeys[0];
return $return;
}
/*********************************************************************/
/**
* function gpg_generate_keypair
* This function generates a keypair
*
* @param string $fpr containing fingerprint of key to change preferences on
* @param string $prefs containing space seperated list of parameters, defaults to S2 S7 S3 H2 H3 Z2 Z1
* @param string $passphrase containing passphrase to decrypt and change key with
* @return @array $return containing output, erros, etc
*
*/
function setKeyPrefs($fpr, $prefs=false,$passphrase)
{
if (!$prefs) {
$prefs=$this->defaultKeyPrefs;
}
if (!$this->keys[$fpr]) {
$this->refreshKeys($fpr);
$fpr=$this->getKeyIndexFromFingerprint($fpr);
}
$this->action="setKeyPref";
$this->keys[$fpr]->passphrase=$passphrase;
$this->interactionData['keyprefs'] = $prefs;
return $this->execute_gpg("--edit-key $fpr");
}
/***********************************************/
/**
* function addSubKey
*
* Adds a subkey pair to a main keypair
*
* @param string $privatekey containing fingerprint or key id of the private key for which to add a subkey
* @param string $passphrase containing the passphrase to the secret key
* @param integer $type containing what type of key to create (default of (3) ElGamal (encrypt only))
* @param integer $keysize containing size of key to create
* @param string $valid containing length of time the key is valid, # of days or 1y for 1 year
* @return array $return containing errors and output, $return['newkeys'] array of new key fingerprints
*
*/
function addSubKey($privatekey, $passphrase, $algo=3, $keysize, $expiredate)
{
if (!$this->keys[$privatekey]) {
$this->refreshKeys($privatekey);
$privatekey=$this->getKeyIndexFromFingerprint($privatekey);
}
if ($this->keys[$privatekey]->haveSecret) {
$this->action="addSubKey";
$this->keys[$privatekey]->passphrase=$passphrase;
$this->interactionData['algo'] = $algo;
$this->interactionData['size'] = $keysize;
$this->interactionData['valid'] = $expiredate;
$params = "--edit-key $privatekey addkey";
$return= $this->execute_gpg($params);
$return['newkeys'] = $this->newKeys;
return $return;
} else {
$this->printDebug("No secret key available");
return array('errors'=>array('No secret key available'));
}
}
/***********************************************/
/**
* function addRevoker
*
* Adds a revoking key to a private key
*
* @param string $privatekey containing fingerprint or key id of the private key for which to set a revoker
* @param string $revokingkey containing fingerprint or key id of the key to use as revoker
* @param string $passphrase containing the passphrase to the secret key
* @return array $return containing errors and output
*
*/
function addRevoker($privatekey, $revokingkey, $passphrase)
{
$this->refreshKeys('','all');
$privatekey=$this->getKeyIndexFromFingerprint($privatekey);
$revokingkey=$this->getKeyIndexFromFingerprint($revokingkey);
$this->action="addRevoker";
$this->keys[$privatekey]->passphrase=$passphrase;
$this->activeKey = $revokingkey;
$params = "--edit-key $privatekey addrevoker";
return $this->execute_gpg($params);
}
/**
* function setPrimaryUID
*
* Sets the uid number specified as the primary UID
*
* @param string $keyfpr
* @param integer $uid
* @param string $passphrase
*
* @return array $return with errors, warnings, output
*/
function setPrimaryUID($keyfpr, $uid, $passphrase)
{
if (!$this->keys[$keyfpr]) {
$this->refreshKeys($keyfpr,'all');
$keyfpr=$this->getKeyIndexFromFingerprint($keyfpr);
}
// if ($this->keys[$keyfpr]->haveSecret) {
$this->action="setPrimaryUID";
$this->keys[$keyfpr]->passphrase=$passphrase;
$params = "--edit-key $keyfpr $uid primary";
return $this->execute_gpg($params);
// } else return array ( 'errors'=>array(0=>"No secret key available for $keyfpr") );
}
/**
* function getExportText
*
* Exports a key defined by $fpr to ASCII Armored text.
*
* @param string $fpr
* @param enum $ringname
*
* @return array $return with $return['output'] containing ascii-armored key
*/
function getExportText($fpr) {
$exportstring='';
// make sure there aren't any funny characters in fingerprint
$fpr = escapeshellarg($fpr);
//Make the command and execute.
$params = "--armor $exportstring --export $fpr";
$return=$this->execute_gpg($params);
return $return;
}
/***********************************************/
/**
* function uploadKey
*
* Uploads a public key to a keyserver
*
* @param string $fpr containing fingerprint or keyid to upload
* @param string $keyserver containing name of server to upload to
* @return array $return containing errors, info, etc
*/
function uploadKey($fpr, $keyserver)
{
if (!$this->keys[$fpr]) {
$this->refreshKeys($fpr);
$fpr=$this->getKeyIndexFromFingerprint($fpr);
}
$this->action="uploadKey";
$cmd = "--keyserver hkp://$keyserver --send-keys $fpr";
return $this->execute_gpg($cmd);
}
/***********************************************/
/**
* function signUID
*
* Adds a signature to a specific or multiple uids on a public key
* The key is signed by the default secret key if no secret key is specified
* By default the signature is exportable and revokable
*
* @param string $signedKeyfpr containing fingerprint of the key to sign
* @param string $uids containing space seperated list of uids
* @param string $signingKeyfpr containing fingerprint of secret key to use for signing
* @param string $passphrase containing the passphrase to the secret key
* @param bool $exportsig flags the signature exportable, true by default
* @param bool $revokable flags the signature revokable, true by default
* @return array $return containing errors and output
*
*/
function signUID($signedKeyfpr,$uids='',$signingKeyfpr=false,$passphrase=false,$exportsig=true,$revokable=true) {
return $this->signKey($signedKeyfpr,$signingKeyfpr,$passphrase,$exportsig,$revokable,$uids);
}
/***********************************************/
/**
* function signKey
*
* Adds a signature to a public key
* The key is signed by the default secret key if no secret key is specified
* By default the signature is exportable and revokable
*
* @param string $signedKeyfpr containing fingerprint of the key to sign
* @param string $signingKeyfpr containing fingerprint of secret key to use for signing
* @param string $passphrase containing the passphrase to the secret key
* @param bool $exportsig flags the signature exportable, true by default
* @param bool $revokable flags the signature revokable, true by default
* @return array $return containing errors and output
*
*/
function signKey($signedKeyfpr,$signingKeyfpr=false,$passphrase=false,$exportsig=true,$revokable=true,$uids='') {
$signtype='sign';
if (!$revokable) $signtype = 'nr' . $signtype;
if (!$exportsig) $signtype = 'l' . $signtype;
$this->printDebug("signing started, refreshing...");
// read keys from gpg
$this->refreshKeys(); //$signingKeyfpr . ' ' . $signedKeyfpr);
if (!$signingKeyfpr) {
// use default key fingerprint if none is specified
$this->printDebug("Going with default " . $this->defaultKeyFingerprint);
$signingKeyfpr = $this->defaultKeyFingerprint;
}
//ensure we have the secret key to sign with
if ($this->keys[$signingKeyfpr]->haveSecret) {
if ($passphrase) {
//set passphrase on the secret key
$this->keys[$signingKeyfpr]->passphrase=$passphrase;
}
$command = " -u " . $this->keys[$signingKeyfpr]->id . ' --edit-key ' . $this->keys[$signedKeyfpr]->id . ' ' . $uids . $signtype;
$this->printDebug("Going with the signing command $command.");
//run command into gpg
$this->action="signKey";
$return = $this->execute_gpg($command );
}
else { $return['errors'][] = _("Key with Fingerprint ") . $this->keys[$signingKeyfpr] . _(" has no secret key associated with it.");
$this->printDebug("No secret key found, ending without signing.");
}
return $return;
}
/***********************************************/
/**
* function changePassphrase
*
* Changes the passphrase on a secret key
*
* @param string $keyfpr containing fingerprint of the secret key to changne
* @param string $passphrase containing the current passphrase to the secret key
* @param string $newpassphrase containing the new passphrase to the secret key
* @return array $return containing $return['verified'] equal to 'true' if successful or 'false' if not
*
*/
function changePassphrase($keyfpr,$passphrase,$newpassphrase) {
//load key information
if (!$this->keys[$keyfpr]) {
$this->refreshKeys($keyfpr);
}
$this->printDebug("Change Passphrase: verifying");
//verify that the current passphrase is correct
$return=$this->verifyPassphrase($passphrase,$keyfpr);
$this->printDebug("Verification: " . $return['verified']);
//if not correct, return an error
if ($return['verified']!='true') { $return['errors'][] = _("Bad Passphrase"); return $return; }
//set our intended action. Set after refresh keys so it isn't reset
$this->action='changePassphrase';
//if the key fingerprint sent isn't the size of a fingerprint, find the fingerprint
if (!(strlen($keyfpr)>16)) { $keyfpr = $this->getKeyIndexFromFingerprint($keyfpr); }
//ensure we have the secret key
if ($this->keys[$keyfpr]->haveSecret) {
//set passphrase on the secret key
$this->keys[$keyfpr]->passphrase=$passphrase;
$this->newpassphrase=$newpassphrase;
$command = "--edit-key $keyfpr passwd";
$return=$this->execute_gpg($command);
return $return;
} else { $this->printDebug("No secret key found for $keyfpr"); }
//verify that the new passphrase is now correct for the key
$return=$this->verifyPassphrase($newpassphrase,$keyfpr);
return $return;
}
/***********************************************/
/**
* function readStatus
*
* Reads and loops on the status pipe from gpg.
* Contains the main loop for interaction with gpg with pipes
*
* @param void
*
*/
function readStatus( )
{
$this->printDebug( "readStatus()");
$this->statusout = '';
while( $this->pipeOpen[ STATUS_FD ] == true && !feof( $this->gpg_pipes[ STATUS_FD ] ) ) {
$this->printDebug("Starting read status loop.");
//create array of pipes to check for data
$readArray = array($this->gpg_pipes[STATUS_FD], $this->gpg_pipes[GPGSTDOUT]);
//check pipes for data, readArray is changed to include only pipes which have data
$this->printDebug("Checking if read would block on pipes");
$numRead = stream_select($readArray, $write=NULL, $except=NULL, 10);
$this->printDebug("Streams read for reading: $numRead");
if ($numRead !== false) {
foreach ($readArray as $pipe) {
//Do not block when reading these pipes
stream_set_blocking($this->gpg_pipes[STATUS_FD],FALSE);
if ($this->gpg_pipes[STATUS_FD] == $pipe) {
$this->printDebug("Reading Status");
stream_set_blocking($this->gpg_pipes[STATUS_FD],FALSE);
$thisStatusLine = fgets( $this->gpg_pipes[ STATUS_FD ] );
$this->printDebug( "readStatus: ".$thisStatusLine );
$this->parseStatusLine( $thisStatusLine );
//append status output to record
$this->statusout .= $thisStatusLine;
}
if ($this->gpg_pipes[GPGSTDOUT] == $pipe) {
// $this->printDebug("GPGSTDOUT ready for reading.");
stream_set_blocking($this->gpg_pipes[GPGSTDOUT],0);
while (!feof($this->gpg_pipes[ GPGSTDOUT ])) {
$line=fgets( $this->gpg_pipes[ GPGSTDOUT ]);
if (strlen($line)>0) { $this->printDebug("GPGSTDOUT: $line"); }
$this->stdout .= $line;
// if (strlen(trim($line))==0) break;
}
//$this->stdout .= $this->readData(GPGSTDOUT);
}
}
}
if ($this->writingData && ($numRead==false)) break;
}
}
/**********************************************/
/**
* function increaseEntropy
*
* Runs commands to increase entropy for gpg
* definitely needs to be made more robust, currently lists filesystem
*
*
*/
function increaseEntropy()
{
if ($this->allowEntropyIncrease) {
$this->printDebug("Increasing Entropy");
//maybe change /usr to something else?
exec("ls -lR /usr > /dev/null &");
}
}
/***********************************************/
/**
* function parseStatusLine
*
* Parse a line from GnuPG's status file descriptor and act on it
*
* @param string $line containing status output from GnuPG
*
*/
function parseStatusLine( $line )
{
$line = trim( $line );
$param = preg_split("/[\s]+/", $line );
if( $param[0] == "[GNUPG:]" )
{
// parse it!
switch ($param[1])
{
case "PROGRESS":
if (($param[2]=='primegen') && ($param[3]=='X') && ($param[4]=='100') && ($param[5]=='100')) {
$this->increaseEntropy();
}
break;
case "KEY_CREATED":
$this->newKeys[] = $param[3];
break;
case "ENC_TO":
$this->encryptKeys[] = $param[2];
break;
case "NO_SECKEY":
$this->interactionData['missingSecretKeys'][]=$param[2];
break;
case "IMPORT_OK":
$this->newKeys[] = $param[3];
break;
case "IMPORT_RES":
$count = $param[2];
$no_user_id = $param[3];
$imported = $param[4];
$imported_rsa = $param[5];
$unchanged = $param[6];
$n_uids=$param[7];
$n_subks=$param[8];
$n_sigs=$param[9];
$n_revoc=$param[10];
$sec_read=$param[11];
$sec_imported=$param[12];
$sec_dups=$param[13];
$not_imported=$param[14];
break;
case "IMPORTED":
break;
case "END_DECRYPTION":
//encryption ended, read data from stdout
// $this->stdout .= $this->readData(GPGSTDOUT);
break;
case "BEGIN_ENCRYPTION":
//beginning encryption, start reading data from stdout
// $this->stdout .= $this->readData(GPGSTDOUT);
$this->printDebug("Encryption begun.");
break;
case "BEGIN_DECRYPTION":
// $this->stdout .= $this->readData(GPGSTDOUT);
$this->printDebug("Decryption begun.");
break;
case "NEED_PASSPHRASE":
//passphrase requested by gpg, store keyID requested
$this->activeKey=$this->getKeyIndexFromFingerprint($param[2]);
$this->printDebug("GPG requests passphrase for key " . $param[2] . " setting as active Key");
break;
case "NEED_PASSPHRASE_SYM":
//PASSPHRASE_SYM is requested when changing the passphrase
switch ($this->action) {
case "changePassphrase":
case "generateKey":
// Need a new passphrase
$this->writeNewPassword();
break;
}
break;
case "GET_HIDDEN":
switch($param[2])
{
case "passphrase.enter":
//passphrase prompt
switch($this->action) {
case 'changePassphrase':
case 'updatedKeyPref':
case 'expiredKey':
case 'sign':
case 'encrypt':
case 'signKey':
case 'addRevoker':
case 'generateKey':
case 'setPrimaryUID':
case 'addUID':
case 'delUID':
case 'addSubKey':
case 'decrypt':
$this->printDebug("GPG requests passphrase, sending active key " . $this->activeKey . " .");
//send the passphrase
$this->writePassword($this->activeKey);
break;
default:
$this->printDebug("This action: " . $this->action . " isn't written to allow passing of the passphrase.");
$this->closePipe(PASSPHRASE_FD);
$this->setError("NOSTATUS","Couldn't handle the status");
break;
}
break;
}
break;
case "GET_LINE":
switch($param[2])
{
case "pklist.user_id.enter":
if (count($this->interactionData['encrypt_to_list'])>0) {
$this->writeData(array_shift($this->interactionData['encrypt_to_list']) . "\n",PASSPHRASE_FD);
} else {
$this->writeData("\n",PASSPHRASE_FD);
}
break;
case "keygen.name":
$this->writeData($this->interactionData['name'] . "\n",PASSPHRASE_FD);
break;
case "keygen.email":
$this->writeData($this->interactionData['email'] . "\n",PASSPHRASE_FD);
break;
case "keygen.comment":
$this->writeData($this->interactionData['comment'] . "\n",PASSPHRASE_FD);
break;
case "keygen.algo":
$this->writeData($this->interactionData['algo']."\n",PASSPHRASE_FD);
break;
case "keygen.size":
$this->writeData($this->interactionData['size'] . "\n",PASSPHRASE_FD);
break;
case "keygen.valid":
$this->writeData($this->interactionData['valid'] . "\n",PASSPHRASE_FD);
break;
case "keyedit.prompt":
// keyedit prompt, save and quit
switch($this->action) {
case "setKeyPref":
$this->writeData("setpref ". $this->interactionData['keyprefs'] . "\n",PASSPHRASE_FD);
$this->action="updateKeyPref";
break;
case "updateKeyPref":
$this->writeData("updpref\n", PASSPHRASE_FD);
$this->action="updatedKeyPref";
break;
case "expireKey":
$this->writeData("expire\n", PASSPHRASE_FD);
$this->action="expiredKey";
break;
case "deleteSubKey":
$this->writeData("delkey\n", PASSPHRASE_FD);
$this->action="deletedSubKey";
break;
case "selectSubKey":
if ($this->interactionData['subkeyno']) {
$this->writeData('key ' . $this->interactionData['subkeyno'] . "\n",PASSPHRASE_FD);
$this->action = $this->interactionData['action'];
break;
}
default:
$this->confirmSave();
break;
}
break;
case "sign_uid.expire":
//signature expired, confirm ok
$this->writeOkay();
break;
case "ask_revocation_reason.code":
$this->writeData($this->interactionData['ask_revocation_reason.code']."\n",PASSPHRASE_FD);
break;
case "ask_revocation_reason.text":
//if we still have lines left in our reason array, shift off the top one and write it to gpg
if (count($this->interactionData['ask_revocation_reason.text'])>0) {
$this->writeData(array_shift($this->interactionData['ask_revocation_reason.text'])."\n",PASSPHRASE_FD);
} else {
//otherwise write last empty line to stop loop
$this->writeData("\n",PASSPHRASE_FD);
}
break;
case "sign_uid.class":
//request for what level of confirmation done on the identity of the user
$this->writeCheckLevel();
break;
case "sign_uid.okay":
//confirm to sign a uid on a key
$this->writeOkay();
break;
case "keyedit.sign_all.okay":
//confirm to sign all uids on a key
$this->writeOkay();
break;
case "keyedit.add_revoker":
$this->printDebug("Writing revoking key: " . $this->activeKey . " email address: " . $this->keys[$this->activeKey]->get_email_addr());
$this->writeData($this->keys[$this->activeKey]->get_email_addr() . "\n",PASSPHRASE_FD);
break;
}
break;
case "GET_BOOL":
switch($param[2])
{
case "delete_key.secret.okay":
//confirm to delete a secret key
$this->writeOkay();
break;
case "delete_key.okay":
//confirm to delete a public key
$this->writeOkay();
break;
case "untrusted_key.override":
//confirm to use an untrusted key
$this->writeOkay();
break;
case "keyedit.sign_all.okay":
$this->writeOkay();
break;
case "sign_uid.okay":
//confirm to sign a uid
$this->writeOkay();
break;
case "delete_uid.okay":
$this->writeOkay();
break;
case "keyedit.updpref.okay":
$this->writeOkay();
break;
case "keyedit.setpref.okay":
$this->writeOkay();
break;
case "keyedit.add_revoker.okay":
$this->writeOkay();
break;
case "keyedit.remove.subkey.okay":
$this->writeOkay();
break;
case "keyedit.remove.uid.okay":
$this->writeOkay();
break;
case "ask_revocation_reason.okay":
$this->writeOkay();
break;
case "keyedit.revoke.subkey.okay":
$this->writeOkay();
break;
}
continue;
case "KEYEXPIRED":
// $this->stdout .= $this->readData(GPGSTDOUT);
$this->printDebug("Key expired with number " . $param[2]);
break;
case "SIGEXPIRED":
$this->printDebug("Sig expired: deprecated");
break;
case "GOOD_PASSPHRASE":
$this->printDebug( "Password accepted!" );
break;
case "BAD_PASSPHRASE":
$this->setError("BAD PASSWORD", "Incorrect or missing password!" );
break;
case "ALREADY_SIGNED":
$this->setError("SIGNATURE FAILED","Key is already signed." );
$this->printDebug("Signature already present by key " . $param[2]);
break;
case "GOODSIG":
// The signature has been verified and is good
$this->verified = true;
$this->verifyStatus = "GOOD";
$this->signedKeyFingerprint = $param[2];
// Get the UserID of the user who signed this text
$this->verifiedUserID = new GnuPGuid(implode(' ', array_slice($param,3)));
break;
case "VALIDSIG":
$this->verifiedSignature = new GnuPGSignature($param);
$this->verifiedSignature->uid=$this->verifiedUserID;
$this->verifiedKeyIndex = $param[11];
break;
case "SIG_CREATED":
// GnuPG has successfully signed something
$this->signPubKeyAlgo = $param[3];
$this->signHashAlgo = $param[4];
break;
case "BADSIG":
// GnuPG could not verify the signature
$this->signedKeyFingerprint = $param[2];
$tempUserID = array();
$this->verifiedUserID = new GnuPGuid(implode(' ', array_slice($param,3)));
$this->verifyStatus = "BAD";
break;
case "EXPKEYSIG":
// The Key used to sign this message has expired
$this->signedKeyFingerprint = $param[2];
$tempUserID = array();
for( $index = 3; $index < count( $param ); $index++ )
$tempUserID[] = $param[$index];
$this->signedKeyUserID = implode(' ', $tempUserID );
$this->verifyStatus = "EXPIRED";
break;
case "EXPSIG":
// The signature has expired
$this->signedKeyFingerprint = $param[2];
$tempUserID = array();
for( $index = 3; $index < count( $param ); $index++ )
$tempUserID[] = $param[$index];
$this->signedKeyUserID = implode(' ', $tempUserID );
$this->verifyStatus = "EXPIRED";
break;
case "ERRSIG":
$this->verifiedSignature=new GnuPGSignature($param);
if ($this->verifiedUserID) {
$this->verifiedSignature->uid=$this->verifiedUserID;
}
// Something is wrong with this signature...
if( $param[7] == "9" )
{
// 9 means we don't have the public key
$this->signedKeyFingerprint = $param[2];
$this->verifyStatus = "MISSING PUBLIC KEY";
}
else
{
$this->setError("VERIFY ERROR", "Error while verifying signature!" );
$this->verifyStatus = "ERROR";
}
break;
case "INV_RECP":
// Uh oh... trying to send something encrypted to someone that we don't know
$this->setError("INVALID RECIPIENT", "Don't have any information about a recipient!" );
$tempUserID = array();
for( $index = 3; $index < count( $param ); $index++ )
$tempUserID[] = $param[$index];
$userID = implode(' ', $tempUserID );
switch( $param[2] )
{
case "1":
$reason = "Missing Key";
break;
case "3":
$reason = "Incorrect Key Use";
break;
case "4":
$reason = "Key Revoked";
break;
case "5":
$reason = "Key Expired";
break;
case "10":
$reason = "Key not trusted";
break;
default:
$reason = "";
break;
}
$this->invalidRecipient[] = array( "userid" => $userID, "reason" => $reason );
break;
case "BEGIN ENCRYPTION":
$this->printDebug("Encryption begun.");
$this->closePipe( PASSPHRASE_FD );
break;
}
} //end [GNUPG]: if
else {
$this->printDebug("Caught default case of $line");
} // end [GNUPG]: else
}
/***********************************************/
/**
* function writePassword
*
* Writes a password to the passphrase file descriptor
* By default writes the passphrase set in the object ($this->passphrase)
* If not available, writes the passphrase for the fingerprint specified
* or the passphrase to the default key if no fingerprint if specified
*
* @param string $fingerprint containing fingerprint of the key with the passphrase
*
*/
// write the password for the given key to the password input
function writePassword( $fingerprint=false )
{
if ($this->passphrase) {
$this->printDebug( "Sending Passphrase" );
$this->writeData( $this->passphrase."\n", PASSPHRASE_FD );
$this->printDebug( "Done Sending Passphrase" );
} else {
$keyIndex = $this->getKeyIndexFromFingerprint( $fingerprint );
// $this->printDebug( "writePassword() [".$keyIndex."]-[".$this->keys[ $keyIndex ]->passphrase."]");
if( $keyIndex !== false && $this->keys[ $keyIndex ]->passphrase!='' )
{
$this->printDebug( "Sending Passphrase" );
$this->writeData( $this->keys[ $keyIndex]->passphrase."\n", PASSPHRASE_FD );
$this->printDebug( "Done Sending Passphrase" );
}
else {
$this->printDebug( "No Password: " . $this->keys[ $keyIndex ]->passphrase . '?' );
$this->writeData("\n", PASSPHRASE_FD );
}
}
// $this->closePipe( PASSPHRASE_FD );
}
/***********************************************/
/**
* function writeNewPassword
*
* Writes the new passphrase to the passphrase file descriptor, twice.
*
* @param void
*
*/
function writeNewPassword()
{
if ($this->newpassphrase) {
$this->printDebug("Writing new passphrase." );
$this->writeData($this->newpassphrase."\n",PASSPHRASE_FD );
$this->writeData($this->newpassphrase."\n",PASSPHRASE_FD );
} else {
$this->writeData("\n",PASSPHRASE_FD );
$this->writeData("\n",PASSPHRASE_FD );
$this->writeData("\n",PASSPHRASE_FD );
$this->writeOkay();
//$this->printDebug("Failed to find new passphrase." );
$this->printDebug("Reseting passphrase." );
}
// $this->closePipe( PASSPHRASE_FD );
}
/***********************************************/
/**
* function writeOkay
*
* Writes a confirmation to the GPG command file descriptor
* Used to confirm choices during interaction with the status pipe
*
* @param void
*
*/
function writeOkay()
{
$this->printDebug("Answering Y to gpg.");
$this->writeData("Y\n",PASSPHRASE_FD);
}
/***********************************************/
/**
* function writeCheckLevel
*
* Writes a confirmation level for signature uid verification query
* Used during keys signing. Defaults to 0 (Not specified)
*
* @param int $checkLevel indicating level of confirmation
*
*/
function writeCheckLevel($checkLevel=0)
{
$this->printDebug("Writing a checkLevel of $checkLevel");
$this->writeData("$checkLevel\n",PASSPHRASE_FD);
}
/***********************************************/
/**
* function confirmSave
*
* Writes a save command to the GPG command file descriptor
* Used during key edit actions
*
* @param void
*
*/
function confirmSave()
{
$this->printDebug("Saving changes.");
$this->writeData("save\n", PASSPHRASE_FD);
// $this->closePipe( PASSPHRASE_FD );
}
/***********************************************/
/**
* function getKeys
*
* Retreives keys from gpg if no keys have been loaded
* Does not force a refresh of the key information
*
* @param void
*
*/
function getKeys($fpr=false )
{
if (!$fpr) {
if( $this->keys == false )
$this->refreshKeys( );
} else {
if (!$this->keys[$fpr])
$this->refreshKeys($fpr);
}
}
/***********************************************/
/**
* function getKey
*
* Retreives a key from the keyring
*
* @param string $keyid containing fingerprint or key id of the key to retrieve
* @return GnuPGKey $key corresponding to id or fingerprint
*
*/
function getKey( $keyid )
{
//call this function to ensure that this key exists in the list
$this->getKeys($keyid);
if (strlen($keyid) > 16) {
if (array_key_exists($keyid,$this->keys)) {
return $this->keys[$keyid];
} else { return array('errors'=>array(0=>_("No Key Found"))); }
} else {
$fpr = $this->getKeyIndexFromFingerprint($keyid);
if (array_key_exists($fpr,$this->keys)) {
return $this->keys[$fpr];
} else { return array('errors'=>array(0=>_("No Key Found"))); }
}
}
/***********************************************/
/**
* function makearrayKeys
*
* Creates an array of keys on the keyring, making use of the arrayKey function
* Each key creates an array of data, indexed by fieldname
* This function is used for keyring sorting and viewing
*
* @param void
* @return array of array of keys, including subkeys
*
*/
function makearrayKeys()
{
$return=array();
foreach( $this->keys as $lkey) {
$return[$lkey->fingerprint] = $lkey->arrayKey();
if (count($lkey->subkeys) > 0) {
foreach ($lkey->subkeys as $lsubkey) {
$return[$lkey->fingerprint]['sub'][$lsubkey->fingerprint] = $lsubkey->arrayKey();
}
}
}
$this->arraykeys = $return;
}
/***********************************************/
/**
* function getKeymap_chunked
*
* Returns the map of keys, chunked in to chunks of size <= $len
*
* @param integer $len
*
* @return array keys
*/
function getKeyMap_chunked($len) {
if (!$this->arraykeys) {
$this->printDebug("No array made to sort, running makearrayKeys");
$this->makearrayKeys();
}
if (function_exists('array_chunk')){
return array_chunk($this->arraykeys, $len, true);
} else {
$this->printDebug("<br>Your PHP version does not support te array_chunk function. Returning entire array instead.\n");
$return = array();
$return [] = $this->arraykeys;
return $return;
} //end check for array_chunk
}
/***********************************************/
/**
* function sortKeys
*
* Sorts the keys in order of key data name $dataName (e.g. "email_addr", "date", etc)
* if $asc is true, sorts in ascending order.
*
* @param string $dataName
* @param boolean $asc value either '<' or '>'
*
* @return array keys
*/
function sortKeys($dataName, $asc) {
if (!$this->arraykeys) {
$this->printDebug("No array made to sort, running makearrayKeys");
$this->makearrayKeys();
}
//Determine ascending v. descending.
if ($asc) $op = ">";
else $op = "<";
//Form the body of the lambda function.
$code =
"if (strtolower(\$key1['$dataName']) $op strtolower(\$key2['$dataName'])) { return 1; } " .
"else if (strtolower(\$key1['$dataName']) == strtolower(\$key2['$dataName'])) { return 0; } " .
"else return -1;";
//Create the function and sort.
$lambda = create_function('$key1,$key2', $code);
uasort($this->arraykeys, $lambda);
}
function fetchKeys($search, $ring='public') {
return $this->refreshKeys($search,$ring);
}
/***********************************************/
/**
* function numKeys
*
* Returns the total number of keys in the object,
* as determined by options passed to fetchKeys().
*
* @param void
*
* @return array keys
*/
function numKeys() {
return count($this->keys);
}
/***********************************************/
/**
* function refreshKeys
*
* Main function for retreiving key and signature information from gpg
* Parses output from gpg and creates the array of GnuPGKey objects in $this->keys
*
* @param string $search containing string to limit keys shown, default '' to load all keys
* @param string $ring containing the name of the keyring to search, default 'all' for public
*
*/
// Load up all the information about the keys in the key ring from GnuPG
function refreshKeys($search='', $ring='all' )
{
$params='';
$addHomeDir=true;
$addSystemRing=true;
$this->action='listKeys';
switch ($ring) {
case 'new':
case 'public':
case '':
$addSystemRing=false;
default:
case 'all':
$params .= '--list-sigs';
break;
case 'secret':
$params = '--list-secret-keys ';
break;
case 'system':
{
if ($this->systemKeyring) {
$system_keyring_file = $this->systemKeyring;
if (is_file($system_keyring_file)) {
$this->alternateKeyring = $system_keyring_file;
$system_trustdb_file = escapeshellarg(dirname($system_keyring_file) . DIRECTORY_SEPARATOR . 'trustdb.gpg');
$params = "--trustdb $system_trustdb_file --list-sigs";
$addHomeDir = false;
$addSystemRing = false;
}
}
}
}
$this->keys = array();
// $search=escapeshellarg($search);
if ($search) if (strpos($search,"'")===false) { $search = "'$search'"; }
$command = "--fixed-list-mode --with-colons $params --with-fingerprint --with-fingerprint $search";
$return=$this->execute_gpg( $command, false, $addHomeDir, $addSystemRing );
$stdoutLines = explode( "\n", $return['output'] );
$currentKey = false;
$currentsubKey = false;
$currentUid = false;
$lastKeyRecordRead = '';
$this->keys = array();
foreach( $stdoutLines as $line )
{
if( strpos( $line, ':' ) )
{
// make sure this is a key line
$keyInfo = explode( ':', $line );
switch( $keyInfo[0] )
{
case 'sec':
case 'pub':
// save the current key
if( $currentKey ) {
if ($currentsubKey) {
if ($currentsubKey->fingerprint) {
$currentKey->subkeys[$currentsubKey->fingerprint] = $currentsubKey;
}
else { $currentKey->subkeys[] = $currentsubKey; }
$currentsubKey=false;
}
if ($currentUid) {
$currentKey->userIDs[]=$currentUid;
$currentUid=false;
}
if ($currentKey->fingerprint) {
$this->keys[$currentKey->fingerprint] = $currentKey;
}
else { $this->keys[] = $currentKey; }
}
$currentKey = new GnuPGkey($keyInfo);
$lastKeyRecordRead = 'pub';
break;
case 'ssb':
case 'sub':
$lastKeyRecordRead = 'sub';
if ($currentsubKey) {
if ($currentsubKey->fingerprint) {
$currentKey->subkeys[$currentsubKey->fingerprint] = $currentsubKey;
}
else { $currentKey->subkeys[] = $currentsubKey; }
}
$currentsubKey = new GnuPGkey($keyInfo);
break;
case 'uid':
if ($currentUid) {
$currentKey->userIDs[]=$currentUid;
}
$currentUid = new GnuPGuid($keyInfo[9]);
break;
case 'sig':
if ( $lastKeyRecordRead == 'sub')
break;
if ($currentUid) {
$currentUid->signatures[] = new GnuPGsig($keyInfo);
} else {
$currentKey->signatures[] = new GnuPGsig($keyInfo);
}
break;
case 'fpr':
if( $lastKeyRecordRead == 'pub' )
$currentKey->fingerprint = $keyInfo[9];
if( $lastKeyRecordRead == 'sub' )
$currentsubKey->fingerprint = $keyInfo[9];
}
}
}
// save the last key
if( $currentKey ) {
if ($currentsubKey) {
if ($currentsubKey->fingerprint) {
$currentKey->subkeys[$currentsubKey->fingerprint] = $currentsubKey;
}
else { $currentKey->subkeys[] = $currentsubKey; }
$currentsubKey=false;
}
if ($currentUid) {
$currentKey->userIDs[]=$currentUid;
}
if ($currentKey->fingerprint) {
$this->keys[$currentKey->fingerprint] = $currentKey;
}
else { $this->keys[] = $currentKey; }
}
$this->printDebug("Number of keys: " . $this->numKeys());
// now get which keys we have secret keys for
$command = "--fixed-list-mode --with-colons --list-secret-keys --with-fingerprint --with-fingerprint";
$return=$this->execute_gpg( $command );
$stdoutLines = explode( "\n", $return['output'] );
$lastKeyRecordRead = '';
foreach( $stdoutLines as $line )
{
if( strpos( $line, ':' ) )
{
// make sure this is a key line
$keyInfo = explode( ':', $line );
switch( $keyInfo[0] )
{
case 'sec':
$lastKeyRecordRead = 'sec';
break;
case 'ssb':
$lastKeyRecordRead = 'ssb';
break;
case 'fpr':
if( $lastKeyRecordRead == 'sec' )
{
foreach( array_keys( $this->keys ) as $keyIndex )
{
if( $this->keys[ $keyIndex ]->fingerprint == $keyInfo[9] )
$this->keys[ $keyIndex ]->haveSecret = true;
}
}
break;
} // end key info switch
}
} // end secret key loop
$return['keys']=$this->keys;
return $return;
} //end function refreshKeys
/*********************************************************************/
/**
* function parse_output()
*
* This will parse the string that gpg returns for info, warnings, errors
* and return them in arrays. This function also returns any other output seperately
*
* @param string $gpg_output text output from gpg
*
* @return array $return ['errors'],['warnings'],['info'] contain gpg messages ['output'] contains the rest of the output
*/
function parse_output( $gpg_output)
{
global $insecure_mem_warning;
$insecure_mem_warning = $GLOBALS['GPG_SYSTEM_OPTIONS']['insecure_mem_warning'];
$return['errors'] = array();
$return['warnings'] = array();
$return['info'] = array();
$return['signature'] = array();
$return['verified'] = array();
$return['skipped_keys'] = array();
if (count($this->invalidRecipients) > 0) {
$return['skipped_keys']=$this->invalidRecipients;
}
$return['output'] = '';
$return['untrusted'] = '';
$return['verified'] = '';
$trimmed = array();
if (!is_array($gpg_output)) {
$gpg_output = explode("\n",$gpg_output);
}
foreach ($gpg_output as $line) {
$j = 0;
$j = substr_count ($line, 'Signature Status');
if ($j) {
$return['info'][] = gpg_stripstr($line);
continue;
};
$j = substr_count ($line, "couldn't set locale correctly");
if ($j) {
$return['info'][] = $line;
continue;
}
$j = substr_count ($line, 'gpg: success sending to');
if ($j) {
$return['info'][] = gpg_stripstr($line);
continue;
}
$j = substr_count ($line, 'gpg: encrypted with');
if ($j) {
$return['info'][] = gpg_stripstr($line);
continue;
};
$j = substr_count ($line, 'Primary key fingerprint:');
if ($j) {
$return['info'][] = gpg_stripstr($line);
continue;
};
$j = substr_count ($line, 'gpg: keyring ');
if ($j) {
$return['info'][] = gpg_stripstr($line);
};
$j = substr_count ($line, 'gpg: Signature made');
if ($j) {
$return['signature'][] = gpg_stripstr($line);
if (!$this->verifiedSignature) {
$this->verifiedSignature=new GnuPGSignature($line);
}
$return['info'][] = gpg_stripstr($line);
continue;
};
$j = substr_count ($line, 'gpg: Good signature');
if ($j) {
$return['verified'] = 'true';
$return['signature'][] = gpg_stripstr($line);
if (($this->verifiedSignature) && (!$this->verifiedSignature->uid)) {
if ($this->verifiedUserID) {
$this->verifiedSignature->uid=$this->verifiedUserID;
} else {
$uidstr = preg_match('/^gpg: Good signature from \"(.*)\"/',$line,$matches);
$this->verifiedSignature->uid = new GnuPGuid($matches[1]);
$this->verifiedSignature->valid=true;
}
}
$return['info'][] = gpg_stripstr($line);
continue;
};
$j = substr_count ($line, 'gpg: aka');
if ($j) {
$return['info'][] = gpg_stripstr($line);
continue;
};
$j = substr_count ($line, ' "');
if ($j) {
$return['info'][] = gpg_stripstr($line);
continue;
};
$j = substr_count ($line, 'gpg: WARNING: message was not integrity protected');
if ($j) {
$line = gpg_stripstr($line);
$return['warnings'][] = gpg_stripstr($line, 'WARNING:');
continue;
};
$j = substr_count($line, 'gpg: WARNING: This key is not certified with a trusted signature!');
if ($j) {
$line = gpg_stripstr($line);
$return['signature'][] = $line;
$return['warnings'][] = gpg_stripstr($line, 'WARNING:');
$return['untrusted'] = 'true';
continue;
};
$j = substr_count ($line, 'gpg: Bad signature');
if ($j) {
if (($this->verifiedSignature) && (!$this->verifiedSignature->uid)) {
$uidstr = preg_match('/^gpg: Bad signature from \"(.*)\"/',$line,$matches);
$this->verifiedSignature->uid = new GnuPGuid($matches[1]);
$this->verifiedSignature->valid=false;
}
$return['signature'][] = gpg_stripstr($line);
$return['warnings'][] = gpg_stripstr($line);
continue;
};
$j = substr_count ($line, 'gpg: BAD signature');
if ($j) {
if (($this->verifiedSignature) && (!$this->verifiedSignature->uid)) {
$uidstr = preg_match('/^gpg: BAD signature from \"(.*)\"/',$line,$matches);
$this->verifiedSignature->uid = new GnuPGuid($matches[1]);
$this->verifiedSignature->valid=false;
}
$return['signature'][] = gpg_stripstr($line);
$return['warnings'][] = gpg_stripstr($line);
continue;
};
$j = substr_count ($line, "gpg: can't open");
if ($j) {
$return['errors'][] = gpg_stripstr($line);
continue;
};
$j = substr_count($line, 'gpg: Sorry, no terminal');
if ($j) {
$return['errors'][] = gpg_stripstr($line);
$return['errors'][] = _("Problem with interaction with GPG, probably trying to execute a command with no pipes that requires pipes for interaction");
continue;
}
$j = substr_count ($line, 'gpg: keydb_search failed');
if ($j) {
$return['errors'][] = gpg_stripstr($line);
continue;
};
$j = substr_count ($line, 'gpg: key ');
if ($j) {
$return['info'][] = gpg_stripstr($line);
continue;
};
$j = substr_count ($line, 'usage: gpg');
if ($j) {
$return['errors'][] = _("Problem with command syntax. Check Debug Output");
};
$j = substr_count ($line, 'decryption failed');
if ($j) {
$return['errors'][] = gpg_stripstr($line);
continue;
}
$j = substr_count ($line, 'gpg: Warning:');
if ($j) {
$return['warnings'][] = gpg_stripstr($line);
continue;
};
$j = substr_count ($line, "gpg: Can't check signature: public key not found");
if ($j) {
$return['warnings'][] = gpg_stripstr($line);
continue;
};
$j = substr_count ($line, 'gpg: some signal caught ... exiting');
if ($j) {
$return['errors'][] = gpg_stripstr($line);
continue;
}
$j = substr_count ($line, 'gpg: Error:');
if ($j) {
$return['errors'][] = gpg_stripstr($line);
continue;
};
$j = substr_count ($line, 'gpg: no valid OpenPGP data found.');
if ($j) {
$return['errors'][] = gpg_stripstr($line);
continue;
};
$j = substr_count ($line, 'gpg: decrypt_message failed');
if ($j) {
$return['errors'][] = gpg_stripstr($line);
continue;
};
$j = substr_count ($line, 'gpg: invalid radix64 character');
if ($j) {
$return['warnings'][] = gpg_stripstr($line);
continue;
};
$j = substr_count ($line, 'gpg: CRC error');
if ($j) {
$return['errors'][] = gpg_stripstr($line);
continue;
};
$j = substr_count ($line, 'invalid packet');
if ($j) {
$return['errors'][] = gpg_stripstr($line);
continue;
};
$j = substr_count ($line, 'gpg: out of secure memory while allocating');
if ($j) {
$return['errors'][] = gpg_stripstr($line);
continue;
};
$j = substr_count ($line, 'gpg: (this may be caused by too many');
if ($j) {
$return['errors'][] = gpg_stripstr($line);
continue;
};
$j = substr_count ($line, 'gpg: Oops:');
if ($j) {
$return['warnings'][] = gpg_stripstr($line);
continue;
};
$j = substr_count ($line, 'gpg: WARNING: Using untrusted key!');
if ($j) {
$line = gpg_stripstr($line);
$return['signature'][] = _("WARNING: This key is not certified with a trusted signature!");
$return['signature'][] = _("There is no indication that this key really belongs to the owner");
$return['warnings'][] = gpg_stripstr($line,'WARNING:');
$return['untrusted'] = 'true';
continue;
}
$j = substr_count ($line, 'gpg: WARNING: using');
if ($j) {
if ($insecure_mem_warning) {
$return['warnings'][] = gpg_stripstr($line);
}
continue;
};
$j = substr_count ($line, 'gpg: please see http://');
if ($j) {
if ($insecure_mem_warning) {
$return['warnings'][] = gpg_stripstr($line);
};
continue;
};
$j = substr_count ($line, 'encryption failed');
if ($j) {
$return['errors'][] = gpg_stripstr($line);
continue;
};
$j = substr_count ($line, 'gpg: keyblock resource');
if ($j) {
$return['errors'][] = gpg_stripstr($line);
continue;
};
$j = substr_count ($line, 'No such file or directory');
if ($j) {
$return['errors'][] = gpg_stripstr($line);
continue;
};
$j = substr_count ($line, 'gpg: Warning:');
if ($j) {
$return['warnings'][] = gpg_stripstr($line);
continue;
};
$j = substr_count ($line, 'gpg: WARNING:');
if ($j) {
$return['warnings'][] = gpg_stripstr($line);
continue;
};
$j = substr_count ($line, 'usage: gpg');
if ($j) {
$return['errors'][] = _("Problem with command syntax. Check Debug Output");
continue;
};
$j = substr_count ($line, 'gpg: Error:');
if ($j) {
$return['errors'][] = gpg_stripstr($line);
continue;
};
$j = substr_count ($line, 'gpg: Oops:');
if ($j) {
$return['warnings'][] = gpg_stripstr($line);
continue;
};
$j = substr_count ($line, 'gpg: ERROR:');
if ($j) {
$return['errors'][] = gpg_stripstr($line);
continue;
};
$j = substr_count ($line, 'skipped: unusable public key');
if ($j) {
$return['errors'][] = gpg_stripstr($line);
continue;
};
$j = substr_count ($line, 'skipped: public key not found');
if ($j) {
$return['skipped_keys'][] = gpg_stripstr($line);
continue;
};
$j = substr_count ($line, 'gpg: Missing argument for option');
if ($j) {
$return['errors'][] = gpg_stripstr($line);
continue;
};
$j = substr_count ($line, 'gpg: NOTE: secret key');
if ($j) {
$return['warnings'][]=gpg_stripstr($line);
continue;
};
$j = substr_count ($line, 'gpg: no default secret key: bad passphrase');
if ($j) {
$return['errors'][] = gpg_stripstr($line);
continue;
};
$j = substr_count ($line, 'gpg: no default secret key');
if ($j) {
$return['errors'][] = gpg_stripstr($line);
continue;
};
$j = substr_count ($line, 'gpg: [stdin]: sign+encrypt failed: bad passphrase');
if ($j) {
$return['errors'][] = gpg_stripstr($line);
continue;
};
$j = substr_count ($line, 'gpg: [stdin]: clearsign failed: bad passphrase');
if ($j) {
$return['errors'][] = gpg_stripstr($line);
continue;
};
$j = substr_count ($line, 'gpg: protection algorithm 1 (IDEA) is not supported');
if ($j) {
$return['errors'][] = gpg_stripstr($line);
continue;
};
$j = substr_count ($line, 'gpg: protection algorithm');
if ($j) {
$return['errors'][] = gpg_stripstr($line);
continue;
};
$j = substr_count ($line, 'gpg: Invalid option');
if ($j) {
$return['errors'][] = gpg_stripstr($line);
continue;
};
$j = substr_count ($line, ': There is no indication that this key really belongs to the owner');
if ($j) {
$return['signature'][] = gpg_stripstr($line);
$return['warnings'][] = _("There is no indication that this key really belongs to the owner");
$return['warnings'][] = _("This error usually occurs because you have not set a trusted key, or because you have not signed the key you are trying to encrypt to.");
continue;
};
$j = substr_count($line, 'gpg: There is no indication that the signature belongs to the owner');
if ($j) {
$return['signature'][] = gpg_stripstr($line);
$return['warnings'][] = _("There is no indication that this key really belongs to the owner");
$return['warnings'][] = _("This error usually occurs because you have not set a trusted key, or because you have not signed the key you are trying to encrypt to.");
continue;
};
$j = substr_count ($line, "gpg: checking the trustdb");
if ($j) {
$return['info'][] = gpg_stripstr($line);
continue;
};
$j = substr_count($line, "gpg: error reading key: public key not found");
if ($j) {
$return['errors'][] = gpg_stripstr($line);
continue;
};
$j = substr_count($line, "gpg: protection algorithm");
if ($j) {
$return['errors'][] = gpg_stripstr($line);
continue;
};
$j = substr_count ($line, "gpg: next trustdb check due at");
if ($j) {
$return['info'][] = gpg_stripstr($line);
continue;
};
$j = substr_count ($line, "gpg: checking at depth");
if ($j) {
$return['info'][] = gpg_stripstr($line);
continue;
};
$j = substr_count ($line, "gpg: public key of ultimately trusted key 00000000 not found");
if ($j) {
$return['warnings'][] = gpg_stripstr($line);
continue;
};
$j = substr_count ($line, "gpg: Oops: keyid_from_fingerprint: no pubkey");
if ($j) {
$return['warnings'][] = gpg_stripstr($line);
continue;
};
$j = substr_count ($line, "gpg: Oops: keyid_from_fingerprint: no pubkey");
if ($j) {
$return['warnings'][] = gpg_stripstr($line);
continue;
};
//some kind of key message, trap 'em all for now
$j = substr_count ($line, "gpg: key");
if ($j) {
$return['info'][] = gpg_stripstr($line);
continue;
};
$j = substr_count ($line, "gpg: Total number processed:");
if ($j) {
$return['info'][] = gpg_stripstr($line);
continue;
};
//@todo add some info about how many imported to $return
$j = substr_count ($line, "gpg: imported:");
if ($j) {
$return['info'][] = gpg_stripstr($line);
continue;
};
$j = substr_count ($line, "gpg: unchanged:");
if ($j) {
$return['info'][] = gpg_stripstr($line);
continue;
};
$j = substr_count ($line, "gpg: new user IDs:");
if ($j) {
$return['info'][] = gpg_stripstr($line);
continue;
};
$j = substr_count ($line, "gpg: new signatures:");
if ($j) {
$return['info'][] = gpg_stripstr($line);
continue;
};
$trimmed[] = $line;
// $this->printDebug("Line passed on: $line<br>");
}
$return['output'] = implode($trimmed,"\n");
return $return;
} // end parse_output fn
/***********************************************/
/**
* function importKey_file
*
* import keys from a file into the keyring.
* Sets the keys in the keyring to the newly imported/updated keys
*
* @param string $fname containing the path to the file to import
* @return array $return with element $return['newkeys'] array of affected keys fingerprints
*/
function importKey_file($fname)
{
$this->action='importKey';
$params = " --allow-secret-key-import --import $fname";
$return=$this->execute_gpg($params);
$return['newkeys']=$this->newKeys;
$newkeystr=''; foreach ($this->newKeys as $nkey) { $newkeystr.= " '$nkey'"; }
$this->refreshKeys($newkeystr);
return $return;
}
/***********************************************/
/**
*
* function importKey_text
*
* Imports a key from ascii-armored keyblock text
* @param string $keytext containing the ascii-armored key information
* @return array $return with element $return['newkeys'] array of affected keys fingerprints
*/
function importKey_text($keystring) {
$this->action='importKey';
$params = " --allow-secret-key-import --import";
$return=$this->execute_gpg($params, $keystring);
$return['newkeys'] =$this->newKeys;
$newkeystr=''; foreach ($this->newKeys as $nkey) { $newkeystr.= " '$nkey'"; }
$this->refreshKeys($newkeystr);
return $return;
}
/***********************************************/
/**
*
* function importKey_server
*
* Imports a key from ascii-armored keyblock text
* @param string $keytext containing the id of the key to look up
* @param string $keyserver containing the url of the keyserver (i.e. hkp://pgp.mit.edu:11371)
* @return array $return with element $return['newkeys'] array of affected keys fingerprints
*/
function importKey_server($keystring, $keyserver) {
$this->action='importKey';
$params = " --keyserver $keyserver --recv-key $keystring";
$return=$this->execute_gpg($params );
$return['newkeys'] =$this->newKeys;
$this->refreshKeys(implode(' ',$this->newKeys));
return $return;
}
/***********************************************/
/**
* function getKeyIndexFromFingerprint
*
* Retreives a fingerprint based on a key id
*
* @param string $fingerprint containing fingerprint or key id of the key to retrieve
* @return string fingerprint of the key, if found
*
*/
function getKeyIndexFromFingerprint( $fingerprint )
{
$lookForFingerprintLength = strlen( $fingerprint );
if ($lookForFingerprintLength> 16) { return $fingerprint; }
$this->printDebug( "Looking for key fingerprint: ".$fingerprint );
// loop over all the keys looking for the one with the given fingerprint
if (is_array($this->keys)) {
foreach( array_keys( $this->keys ) as $keyIndex )
{
// Because we want to deal with fingerprints smaller than the one from gnupg, we must do some trivial math
$fingerprintLength = strlen( $this->keys[ $keyIndex ]->id );
if( $fingerprintLength>=$lookForFingerprintLength )
{
// we just want to match the right-most characters of the fingerprint
$shrunkFingerprint = substr( $this->keys[ $keyIndex ]->id,
$fingerprintLength - $lookForFingerprintLength );
$this->printDebug( "Matching [".$fingerprint."] against [".$shrunkFingerprint."] which is the shorter "
."version of ".$this->keys[ $keyIndex ]->id );
if( $fingerprint == $shrunkFingerprint )
return $keyIndex;
}
}
}
$this->printDebug( "Couldn't match key!" );
return false;
}
/***********************************************/
/**
* function setKeyPassphrase
*
* Sets the passphrase for a secret key identified by its fingerprint or key id
*
* @param string $fingerprint containing fingerprint or key id of the secret key
* @param string $passphrase containing passphrase for the secret key
*
*/
function setKeyPassphrase( $fingerprint, $passphrase )
{
$keyIndex = $this->getKeyIndexFromFingerprint( $fingerprint );
$this->printDebug("setKeyPassphrase: ".$keyIndex);
// Do we have a valid key?
if( $keyIndex!==false )
{
$this->keys[ $keyIndex ]->passphrase = $passphrase;
$this->printDebug( "writePassword() [".$keyIndex."]-[".$this->keys[ $keyIndex ]->passphrase."][".$passphrase."]");
}
}
/***********************************************/
/**
* function guessAction
*
* Guess the current action being execute, based on parameters sent
* Used by execute functions when action does not come from inside the object
*
* @param string $options containing parameters sent to gpg
*
*/
function guessAction($options) {
$j=0;
$j=substr_count($options,'--verify');
if ($j) {
$this->action='verify';
}
$j=substr_count($options,'--detach-sign');
if ($j) {
$this->action='sign';
}
$j=substr_count($options,'--decrypt');
if ($j) {
$this->action='decrypt';
}
$j=substr_count($options,'--encrypt');
if ($j) {
$this->action='encrypt';
}
$j=substr_count($options,'--sign');
if ($j) {
$this->action='sign';
}
$j=substr_count($options,'--clearsign');
if ($j) {
$this->action='sign';
}
$j=substr_count($options,'--list');
if ($j) {
$this->action='listKeys';
}
$j=substr_count($options,'--genkey');
if ($j) {
$this->action='generateKey_nopipes';
}
}
/***********************************************/
/**
* function execute_gpg
*
* Main function for execute commands with gpg
* All calls to gpg should be made using this function
*
* @param string $options containing parameters to pass to gpg
* @param string $data containing data to pass to gpg
* @param bool $addHomeDir specifies if the home directory should be appended to the command. True by default
*
*/
function execute_gpg( $options, $data = false, $addHomeDir = true,$addSystemRing=true )
{
if (!$this->action) { $this->guessAction($options); }
$lastSecondCommands='';
if( ($this->gpgHomeDir != '') && $addHomeDir ) {
$this->printDebug("Adding homedir to parameter list. " . $this->gpgHomeDir);
$lastSecondCommands .= ' --homedir '.$this->gpgHomeDir . ' ';
}
if ($this->alternateKeyring) {
$lastSecondCommands .= ' --no-default-keyring --keyring ' . escapeshellarg($this->alternateKeyring) . ' ';
if ($this->alternateSecretKeyring) {
$lastSecondCommands .= ' --secret-keyring ' . escapeshellarg($this->alternateSecretKeyring) . ' ';
}
} else {
if ($this->alternateSecretKeyring) {
$lastSecondCommands .= ' --no-default-keyring --secret-keyring ' . escapeshellarg($this->alternateSecretKeyring) . ' ';
}
}
if ($this->systemKeyring && ($this->systemKeyring!=$this->alternateKeyring) &&$addSystemRing) {
if (is_file($this->systemKeyring)) {
$lastSecondCommands .= ' --keyring ' . escapeshellarg($this->systemKeyring) . ' ';
} else { $this->printDebug("System keyring " . $this->systemKeyring . " failed is_file test."); }
}
if (count($this->trustedKeys) > 0) {
foreach($this->trustedKeys as $trusted_key_id) {
if ($trusted_key_id) {
$trusted_key_id=escapeshellarg($trusted_key_id);
$lastSecondCommands .= " --trusted-key $trusted_key_id ";
}
}
}
if (!check_php_version(4,3)) {
$this->force_exec=true;
}
$this->printDebug("Executing action ". $this->action ." with GnuPG Object.");
if ($this->force_exec) {
$this->printDebug("Forcing exec functionality, some functions may not function properly.");
return $this->execute_gpg_nopipes($lastSecondCommands.$options, $this->passphrase, $data);
} else {
switch ($this->action) {
case 'changePassphrase':
case 'addRevoker':
case 'addUID':
case 'delUID':
case 'addSubKey':
case 'deleteSubKey':
case 'selectSubKey':
case 'setKeyPref':
case 'setPrimaryUID':
case 'generateKey':
case 'expireKey':
case 'signKey':
if (!check_php_version(4,3)) {
return array( 'errors' => array( 0 => _("proc_open is not available in this environment. PHP 4.3 or greater is require for this functionality.")));
}
case 'encrypt':
case 'decrypt':
case 'sign':
case 'verify':
case 'listKeys':
case 'importKey':
$this->printDebug("Using pipes to communicate with gpg.");
return $this->execute_gpg_pipes($lastSecondCommands.$options, $data );
break;
default:
$this->printDebug("Using exec to communicate with gpg by default.");
return $this->execute_gpg_nopipes($lastSecondCommands.$options, false, $data);
}
}
$this->action=false;
$this->printDebug("GPGSTDOUT:<pre>" . $this->stdout . "</pre><p>GPGSTDERR:<pre>" . $this->stderr . "</pre>");
}
/***********************************************/
/**
* function execute_gpg_pipes
*
* Execute commands using proc_open method
*
* @param string $options containing parameters to pass to gpg
* @param string $data containing data to pass to gpg
* @return array containing errors, output, etc
*
*/
function execute_gpg_pipes($options, $data )
{
$this->opengpg( $options, true);
$this->printDebug("proc_open commandline executed, pipes open");
// $this->statusout = $this->readData(STATUS_FD);
// Send the user supplied data to GPGSTDIN
if ($data) {
$this->printDebug("Data available, sending to gpg: <pre>".htmlspecialchars(substr($data,0,120).'...')."</pre>");
// stream_set_blocking($this->gpg_pipes[GPGSTDIN],FALSE);
$this->writeData( $data );
}
$this->closePipe( GPGSTDIN );
// $this->stdout .= $this->readData( GPGSTDOUT );
// Parse the status stuff
$this->readStatus( );
// Get the output
// $this->stdout .= $this->readData( GPGSTDOUT );
$this->stderr = $this->readData( GPGSTDERR );
// Terminate the pipes to GnuPG
$retValue = $this->closegpg();
$this->printDebug( "gpg return status: ".$retValue );
if ($this->stderr == '') {
$return=$this->parse_output($this->stdout);
} else {
$return=$this->parse_output($this->stderr);
}
$return['returnval'] = $retValue;
$return['output'] = $this->stdout;
$return['rawoutput'] = $this->stdout;
if ($this->error) { $return['errors'][] = $this->errorCode . ": " . $this->errorDescription; }
return $return;
}
/***********************************************/
/**
* function execute_gpg_nopipes
*
* Function for executing gpg using the exec method
*
* @param string $parameterlist containing parameters to pass to gpg
* @param string $passphrase containing passphrase for operation, if needed
* @param string $data containing data to send to gpg, if needed
* @return array $rerturn containing errors, warnings, etc
*
*/
function execute_gpg_nopipes( $parameterlist, $passphrase=false, $data=false)
{
$this->printDebug("Path to GPG: " . $this->gpg_exe . "<p>");
$this->printDebug("Parameter List: $parameterlist<p>");
if ($data) $this->printDebug("Data: <pre>$data</pre><p>");
$options = ' --no-tty ';
if ($passphrase) {
$options .= ' --passphrase-fd 0 ';
//we do need to still add the newline between passphrase and anything else
$passphrase.="\n";
// $passphrase = escapeshellarg($passphrase . "\n");
}
$cmd = $this->gpg_exe . $options . $parameterlist;
$nopass = $cmd;
//don't need to escapeshellarg if we are passing with fwrite to popen
if ($data) {
// $data = escapeshellarg($data);
} else $data='';
if ($passphrase!='') {
$data = $passphrase . $data;
}
$cmd .= " 2>&1";
$nopass .= " 2>&1";
$this->printDebug("Command: $nopass<p>");
//set language environmental variables
$this->setEnvVars();
if ($data) {
//since we are passing data, let us use the temporary file magic
//thanks to Robert Peake for this one
$temp = $this->getTempFile();
$this->printDebug("Accessing Temp FIle: $temp<p>");
$tempLog = $this->getTempFile();
// log file
$this->printDebug("Accessing Temp Log FIle: $tempLog<p>");
//add pipe to temp file for output from GPG, and popen the process for writing
$cmd = $cmd." --logger-file $tempLog > $temp";
$pw = popen($cmd,'w');
//write data to the process
fwrite($pw,$data);
//get return value from the process
$returnval=pclose($pw);
$this->printDebug("Log file Contents: <pre>".file_get_contents($tempLog)."</pre>");
//open and retrieve output from the process, ensuring that filesize is up to date
clearstatcache();
$fsize=filesize($temp);
if ($fsize) {
$fr = fopen($temp,'r');
$output = fread($fr,$fsize);
//close the file again
fclose($fr);
} else {
$output=''; //something went wrong
}
$output .= @file_get_contents($tempLog);
//immediately securely unlink this file
$this->secure_unlink($temp);
$this->secure_unlink($tempLog);
} else {
//no data, so we can just exec as usual
exec($cmd,$output,$returnval);
}
//reset environmental variables
$this->setEnvVars(true);
if ($this->debug) {
$this->printDebug("Output:<br><textarea cols=80 rows=25 name=output>" . print_r($output, true) . "</textarea><p>");
}
$return=$this->parse_output($output);
$return['rawoutput'] = $output;
$return['returnval'] = $returnval;
return $return;
}
/***********************************************/
/**
* function opengpg
*
* Open link to gnupg and sets up various pipes
* Used during the proc_open interactions with gpg
*
* @param string $command_options containing parameters to pass to gpg
* @param bool $usePassphrase specifies if the command is interactive or batch
*
*/
function opengpg( $command_options, $usePassphrase = false )
{
$this->stdout = '';
$this->stderr = '';
$this->statusout = '';
$lastSecondCommands='';
if( $usePassphrase )
$lastSecondCommands .= ' --command-fd '.PASSPHRASE_FD;
else
$lastSecondCommands .= ' --batch';
// Add a comment line to signatures
if( $this->comment != '' )
$lastSecondCommands .= " --comment \"".$this->comment."\"";
$this->setEnvVars();
$gpg_execute_string = $this->gpg_exe.' '.$lastSecondCommands.
' --status-fd '.STATUS_FD.' '.$this->gpg_options.' '.$command_options;
$this->printDebug( "opengpg - executing \"".$gpg_execute_string."\"" );
$this->gpg_resource = proc_open( $gpg_execute_string, $this->fileDescriptors, $this->gpg_pipes );
if( (!is_resource( $this->gpg_resource )) or (!is_resource( $this->gpg_pipes[GPGSTDOUT])) ) {
$this->setError( "GPG_OPEN_FAILED", "proc_open error!" );
}
else
{
foreach( $this->gpg_pipes as $pipeIndex => $pipe )
{
stream_set_write_buffer($pipe, 0);
// stream_set_blocking($pipe,FALSE);
// Mark the pipe as open
$this->pipeOpen[ $pipeIndex ] = true;
}
}
//set environmental variables back to previous values
$this->setEnvVars(true);
}
//Function to set and reset shell environmental variables before executing the GPG binary. This is currently used only for languages.
//This could could be easily extended to setting other environmental variables, like the gpg-agent environmental vars
function setEnvVars($reset_vars=false) {
$values=$this->lang_env_values;
$lang_vars=$this->lang_env_vars;
if ($lang_vars) {
foreach ($lang_vars as $lkey=>$lval) {
if ($reset_vars) {
if ($values[$lkey]) {
$this->printDebug("Resetting environmental variable $lkey={$values[$lkey]}");
putenv("$lkey={$values[$lkey]}");
}
} else {
$cval=getenv($lkey);
if ($cval) {
$values[$lkey]=$cval;
$this->printDebug("Saving previously set environmental variable $lkey=$cval");
}
$this->printDebug("Setting environmental variable $lkey=$lval");
putenv("$lkey=$lval");
}
}
}
$this->lang_env_values=$values;
}
// read data (until EOF) from the given pipe index
function readData( $pipeIndex )
{
$data = "";
$readPipes = array( $this->gpg_pipes[ $pipeIndex ] );
if( $this->pipeOpen[ $pipeIndex ] == true )
{
$numRead = stream_select($readPipes,$write=NULL,$except=NULL,5);
if ($numRead !==false ) {
$this->printDebug("Reading data from pipe $pipeIndex. ");
while( !feof( $this->gpg_pipes[ $pipeIndex ] ) )
$data .= fgets( $this->gpg_pipes[ $pipeIndex ]);
} else { $this->printDebug("Pipe $pipeIndex not ready for reading"); }
}
return $data;
}
// write data to the given pipe index
function writeData( $data = false, $pipeIndex = GPGSTDIN )
{
$this->printDebug("entering writeData");
if( $data == false ) {
$this->printDebug("No data passed, grabbing from indata (" . strlen($this->indata) . " bytes) ");
$data = $this->indata;
}
$writePipes=array($this->gpg_pipes[$pipeIndex]);
if( $this->pipeOpen[$pipeIndex] == true )
{
$this->printDebug("Pipe $pipeIndex open for writing, checking to see if it would block on write");
$numWrite=stream_select($read=NULL,$writePipes,$except=NULL,5);
if ($numWrite !==false) {
switch($this->action) {
case 'sign':
case 'encrypt':
$this->printDebug("Setting write pipe $pipeIndex to not block");
stream_set_blocking($this->gpg_pipes[$pipeIndex],FALSE);
break;
case 'verify':
default:
break;
}
$this->printDebug( "Sending Data!... (".strlen($data).")" );
$datalen=strlen($data);
if ($datalen>4096) {
$this->writingData=true;
$this->printDebug("Data bigger than 4096, chunking");
$pos=0;
$nowritecount=0;
$chunk_size = 4096;
while (($pos<$datalen)) {
$writePipes=array($this->gpg_pipes[$pipeIndex]);
if ( stream_select($read=NULL,$writePipes,$except=NULL,5)!==false) {
$this->printDebug("Writing next chunk");
$outstr=substr($data, $pos, $chunk_size);
$numwrote=fwrite( $this->gpg_pipes[$pipeIndex], $outstr);
if ($numwrote>0) {
$nowritecount=0;
$pos+=$numwrote;
fflush($this->gpg_pipes[$pipeIndex]);
$this->printDebug("Wrote chunk size $numwrote");
}
else {
$nowritecount=$nowritecount+1;
if (($nowritecount>2) or $this->error) break;
}
$this->readStatus();
} else { $this->printDebug("Pipe not ready for writing"); }
} //end while for writing data in chunks to pipe
$this->writingData=false;
} else { // data is not greater than 4096 bytes, just write it all
if( ($numwrote=fwrite( $this->gpg_pipes[$pipeIndex], $data,$datalen )) === false )
$this->setError( "WRITE ERROR", "Can't write to process' pipe: ".$pipeIndex );
else $this->printDebug("Data written $numwrote of ".strlen($data));
}
} else $this->printDebug("Pipe $pipeIndex not writeable.");
}
else
$this->setError( "PIPE CLOSED", "PIPE ".$pipeIndex." isn't open! Make sure the process is open" );
}
// close the given pipe
function closePipe( $whichPipe )
{
if( $this->pipeOpen[ $whichPipe ] == true )
{
$this->printDebug( "Closing Pipe ".$whichPipe );
fclose( $this->gpg_pipes[ $whichPipe ] );
$this->pipeOpen[ $whichPipe ] = false;
}
}
// close the link to gpg and all of the associated resources (i.e. pipes)
function closegpg( )
{
// close any open pipes
foreach( $this->gpg_pipes as $pipeIndex => $value )
$this->closePipe( $pipeIndex );
// Close the resource
if( is_resource( $this->gpg_resource ) )
return proc_close( $this->gpg_resource );
else
return false;
}
/***********************************************/
/**
* function clearError
*
* Clears last error
*
* @param void
*
*/
function clearError( )
{
$this->error = false;
$this->errorCode = '';
$this->errorDescription = '';
}
/***********************************************/
/**
* function isError
*
* Check to see if last action returned an error
*
* @param void
* @return bool if error occured
*
*/
function isError( )
{
return $this->error;
}
/***********************************************/
/**
* function printDebug
*
* Prints debug strings
*
* @param string $code of error
* @param string $description containing error description
*
*/
function setError( $code, $description )
{
$this->errorCode = $code;
$this->errorDescription = $description;
$this->error = true;
$this->printDebug( "ERROR: ".$this->errorDescription );
}
/***********************************************/
/**
* function getErrorCode
*
* Returns code of last error
*
* @param void
* @return error code
*
*/
function getErrorCode( )
{
return $this->errorCode;
}
/***********************************************/
/**
* function getErrorDescription
*
* Returns description of most recent error
*
* @param void
* @return string with error description
*
*/
function getErrorDescription( )
{
return $this->errorDescription;
}
/***********************************************/
/**
* function printDebug
*
* Prints debug strings
*
* @param string $string to print
*
*/
function printDebug( $string )
{
if( $this->debug )
{
echo $string."<br>\n";
flush();
if (ob_get_level() > 0) {
ob_end_flush();
}
}
}
/*** TEMP DIRECTORY FUNCTIONS **/
/*********************************************************************/
/**
* function getTempDir()
*
* Determine the location of the system temporary directory.
* If a specific setting cannot be found, it defaults to /tmp
*
* Original Source: Horde.php (class Horde)
*
* @return string A directory name which can be used for temp files.
* Returns false if one could not be found.
*/
function getTempDir()
{
/* If one has been specifically set, then use that */
if (@is_dir($this->tmp_dir)) {
$tmp_check = $this->tmp_dir;
if (is_dir($tmp_check) and is_writable($tmp_check)) {
$tmp = $tmp_check;
break;
} else {
$this->setError( "NO_WRITE", 'GPG Plugin option directory tmp_dir: '.$tmp." is not writable.\n" );
}
}
/* If we haven't set a value, then cycle through a
* list of preset possibilities. */
$tmp_locations=$this->tmp_locations;
while (empty($tmp) && sizeof($tmp_locations)) {
$tmp_check = array_shift($tmp_locations);
if (@is_dir($tmp_check)) {
if (is_writable ($tmp_check)) {
$tmp = $tmp_check;
} else {
$this->setError( "NO_WRITE", 'GPG Plugin directory tmp_dir: '.$tmp." is not writable.\n" );
}
}
}
/* Next, try PHP's upload_tmp_dir directive. */
if (empty($tmp)) {
$tmp_check = ini_get('upload_tmp_dir');
if (is_dir ($tmp_check) and is_writable ($tmp_check)) {
$tmp = $tmp_check;
break;
} else {
$this->setError( "NO_WRITE", 'GPG Plugin option PHP upload directory upload_tmp_dir: '.$tmp." is not writable.\n" );
}
}
/* Otherwise, try to determine the system
temporary directory environment variable. */
if (empty($tmp)) {
$tmp = getenv('TMPDIR');
}
if (empty($tmp)) {
$tmp = getenv('TEMP');
}
if (empty($tmp)) {
$tmp = getenv('TMP');
}
/* If it is still empty, we have failed, so return false;
* otherwise return the directory determined. */
return empty($tmp) ? false : $tmp;
}
/**
* function getTempFile()
*
* Create a temporary filename for the lifetime of the script, and
* (optionally) register it to be deleted at request shutdown.
*
* Original Source: Horde.php (class Horde)
*
* @access public
*
* @param string $prefix Prefix to make the temporary name more
* recognizable.
* @param optional boolean $delete Delete the file at the end of the
* request?
* @param optional string $dir Directory to create the temporary file
* in.
*
* @return string Returns the full path-name to the temporary file.
* Returns false if a temp file could not be created.
*/
function getTempFile($prefix = 'GPGPlugin', $delete = true, $dir = '')
{
if (empty($dir) || !is_dir($dir)) {
$tmp_dir = $this->getTempDir();
} else {
$tmp_dir = $dir;
}
if (empty($tmp_dir)) {
return false;
}
$tmp_file = tempnam($tmp_dir, $prefix);
/* If the file was created, then register it for deletion and return */
if (empty($tmp_file)) {
return false;
} else {
if ($delete) {
$this->deleteAtShutdown($tmp_file);
}
return $tmp_file;
}
}
/**
* function deleteTempFile
*
* Securely delte a temporary file
* Should be redundant, as the deleteat shutdown functions should work
* but just to be sure, and to minimize the time the file is in existence
*
* @param string $filename
*
* @return void
*/
function deleteTempFile ($filename) {
if (@file_exists($filename)) {
filesize ($filename); //get the size
$fp = fopen ($filename, 'r+'); //open the file and set the pointer to the beginning
$randstring = rand_string ($size); //get a random string of the right size
fwrite ($fp, $randstring); //overwrite the file contents
fclose ($fp);
@unlink($filename);
}
}
/**
* function createTempDir
*
* Create a temporary directory in the system's temporary directory.
*
* Original Source: Horde.php (class Horde)
*
* @param optional boolean $delete Delete the temporary directory at the
* end of the request?
*
* @return string The pathname to the new temporary directory.
* Returns false if directory not created.
*/
function createTempDir($delete = true)
{
$temp_dir = $this->getTempDir();
if (empty($temp_dir)) return false;
/* Get the first 8 characters of a random string to use as a temporary
directory name. */
do {
$temp_dir .= '/' . substr(md5(mt_rand()), 0, 8);
} while (file_exists($temp_dir));
$old_umask = umask(0000);
if (!mkdir($temp_dir, 0700)) {
$temp_dir = false;
} else {
if ($delete) {
$this->deleteAtShutdown($temp_dir);
}
}
umask($old_umask);
return $temp_dir;
}
/**
* function deleteAtShutdown
*
* Original Source: Horde.php (class Horde)
*
* Removes given elements at request shutdown.
*
* If called with a filename will delete that file at request
* shutdown; if called with a directory will remove that directory
* and all files in that directory at request shutdown.
*
* If called with no arguments, return all elements to be deleted
* (this should only be done by _deleteAtShutdown).
*
* The first time it is called, it initializes the array and
* registers _deleteAtShutdown() as a shutdown function -
* no need to do so manually.
*
* The second parameter allows the unregistering of previously
* registered elements.
*
* @access public
*
* @param optional string $filename The filename to be deleted at the end of
* the request.
* @param optional boolean $register If true, then register the element for
* deletion, otherwise, unregister it.
*/
function deleteAtShutdown($filename = false, $register = true)
{
static $dirs, $files;
/* Initialization of variables and shutdown functions. */
if (is_null($dirs)){
$dirs = array();
$files = array();
register_shutdown_function(array(&$this, '_deleteAtShutdown'));
}
if ($filename) {
if ($register) {
if (@is_dir($filename)) {
$dirs[$filename] = true;
} else {
$files[$filename] = true;
}
} else {
unset($dirs[$filename]);
unset($files[$filename]);
}
} else {
return array($dirs, $files);
}
}
/**
*
* Securely unlink a file, by opening it, writing random characters to it, and then close and unlinking it
*
* @param $file with string path to file that should be securely unliked
*
*/
function secure_unlink($file) {
if (@file_exists($file)) {
$size = filesize ($file); //get the size
$fp = fopen ($file, 'r+');
$randstring = $this->rand_string ($size); //get a random string of the right size
fwrite ($fp, $randstring); //overwrite the file contents
fclose ($fp);
$ret=@unlink($file);
return $ret;
}
return false;
}
/**
* function _deleteAtShutdown
*
* Original Source: Horde.php (class Horde)
*
* Delete registered files at request shutdown.
*
* This function should never be called manually; it is registered
* as a shutdown function by deleteAtShutdown() and called
* automatically at the end of the request. It will retrieve the
* list of folders and files to delete from
* deleteAtShutdown()'s static array, and then iterate
* through, deleting folders recursively.
*
* @access private
*
* @param void
*
* @return void
*/
function _deleteAtShutdown()
{
$registered = $this->deleteAtShutdown();
$dirs = $registered[0];
$files = $registered[1];
foreach ($files as $file => $val) {
/* Delete files */
if ($val) {
$this->secure_unlink($file);
}
}
foreach ($dirs as $dir => $val) {
/* Delete directories */
if ($val && @file_exists($dir)) {
/* Make sure directory is empty. */
$dir_class = dir($dir);
while (false !== ($entry = $dir_class->read())) {
if ($entry != '.' && $entry != '..') {
$this->secure_unlink($dir.'/'.$entry);
}
}
$dir_class->close();
@rmdir($dir);
}
}
}
/*********************************************************************/
/**
* function make_seed
*
* Create the seed for the random functions.
*
* make_seed will only be called for older versions of PHP
*
* @param void
* @return float Seed value
*
*/
function make_seed() {
list($usec, $sec) = explode(' ', microtime());
return (float) $sec + ((float) $usec * 100000);
}
/**
* function rand_string
*
* Function to ease the creation of random strings for
* overwriting temp files or memory buffers.
*
* make_seed will only be called for older versions of PHP
*
* @param integer $length The length of the random string to generate
* @return string $ret The Random String generated.
*
*/
function rand_string ($length) {
mt_srand($this->make_seed());
$ret = "";
for ($i = 0; $i < $length; $i++) {
$ret .= chr (mt_rand(0,255));
};
return $ret;
}
}
/**********************************************************************/
/*
* function gpg_stripstr()
*
* function to strip a gpg: from the beginning of a string, if it exists
*
* @param string $inline line of output from gpg
*
* @return string $line of output with gpg: stripped off
*/
function gpg_stripstr($inline,$stripstr='gpg:') {
$pos = strpos($inline,$stripstr);
if ($pos !== false) {
$pos = $pos + strlen($stripstr);
$inline=substr($inline,$pos,strlen($inline)-$pos);
}
return $inline;
}
if (!function_exists('check_php_version')) {
function check_php_version ($a = '0', $b = '0', $c = '0')
{
global $SQ_PHP_VERSION;
if(!isset($SQ_PHP_VERSION))
$SQ_PHP_VERSION = substr( str_pad( preg_replace('/\D/','', PHP_VERSION), 3, '0'), 0, 3);
return $SQ_PHP_VERSION >= ($a.$b.$c);
}
}
/*********************************************************************?
/*
* $Log: gpg.php,v $
* Revision 1.58 2005/12/21 02:55:46 ke
* - added code to set environmental variables before executing GPG
* - used to set the default language to english, because our string parser only traps for known english strings
*
* Revision 1.57 2005/11/11 07:31:22 ke
* - added option to skip entropy creation on key creation
* - added functions to handle temporary files/directories, and secure delete of these with register_shutdown_function
* - altered the method of calling GPG when passing data, now uses popen with temp files for output
* - Thanks to Robert Peake for the above idea
* - added alternate generateKey function for use with no pipes
*
* Revision 1.56 2005/06/08 23:16:16 ke
* - added function to discover encryption keys on a message directly
*
* Revision 1.55 2005/02/02 06:54:48 ke
* - added needed space for options
*
* Revision 1.54 2005/02/02 06:53:08 ke
* - added --yes and --openpgp to list of default options
* - set nopipes execution function to set global options first
*
* Revision 1.53 2005/01/26 20:21:46 ke
* - added check for provided passphrase in functions, otherwise use already set passphrase
*
* Revision 1.52 2004/10/11 19:52:12 ke
* -make incoming fingerprint capitalized
*
* Revision 1.51 2004/08/26 23:59:03 ke
* -no longer require passphrase as a parameter for encryption
*
* Revision 1.50 2004/08/25 09:58:55 ke
* changed signKey to search for all keys on keyring before signing
*
* Revision 1.49 2004/08/23 09:25:55 ke
* -fix of variable case
* -fix of nasty parse error
*
* Revision 1.48 2004/08/23 09:12:27 ke
* -added function to import key from keyserver
* -removed escapeshellarg on search term, breaks searches for multiple keys
* Bug 29
*
* Revision 1.47 2004/08/23 07:43:36 ke
* -fixed definition of GPGSTDIN to operate properly
* -check every time before running refreshKeys if the key data is already loaded
* -read stdout all the way through without breaking, just don't output
* -fixes to timeout on stream_select
* Bug 29
*
* Revision 1.46 2004/08/22 23:21:24 ke
* -added code to send longer than 4096 byte messages
* -changed definitions of STDOUT,STDIN,STDERR to allow object to operate properly on the shell
* -should solve large message hangs in GPG, also shell operations
* bug 206
*
* Revision 1.45 2004/08/19 19:14:35 ke
* -trap keyring creation message to ensure it won't appear in an emailed or sent key
*
* Revision 1.44 2004/08/09 21:56:00 ke
* -changed to use fgets for reading again, but with a while(!feof) loop to read everything
* -removed limit on fgets size for status line
*
* Revision 1.43 2004/08/09 01:31:21 ke
* -changed read parameters
*
* Revision 1.42 2004/08/09 01:15:40 ke
* -changed fread in status line to fgets
*
* Revision 1.41 2004/08/09 00:42:25 ke
* -status pipe now reads up to 4096 bytes at a time
* -read $return['output'] rather than stdout for secret keys
*
* Revision 1.40 2004/08/08 05:27:21 ke
* -added check in signing to allow passphrase to be passed even without pipes or known fpr
*
* Revision 1.39 2004/08/08 05:22:08 ke
* -fixed header block on deleteUID function
*
* Revision 1.38 2004/08/08 03:58:38 ke
* -added code to break if data writing fails.
* -fixes hang in encryption with last changes
* Bug 206
*
* Revision 1.37 2004/08/06 20:26:16 ke
* -Added function setPrimaryUID to set the primary UID on a key
* -added extra section when writing data that is >4096 bytes, breaks it up into a loop, reads between every write
* -changed fgets calls to fread call, should solve issue with missing keys or data from GPG
* Bug 206
*
* Revision 1.36 2004/07/30 04:14:36 joelm
* print_r() with two params is only available in PHP > 4.3. Wrapped a two-param print_r with if($debug) so that users with PHP < 4.3 will not see an error messageeven when they aren't in debug mode.
*
* Revision 1.35 2004/07/07 18:57:43 ke
* -added checking for action, and set GPGSTDOUT blocking off on stream for encrypt/sign
* -hopefully fixes stalling interaction with gpg, might have to add other cases for other actions that stall
* Bug 206
*
* Revision 1.34 2004/06/28 21:55:26 ke
* -added space aftterr passphrase fd to allow for parameters added after without spaces
*
* Revision 1.33 2004/06/28 21:51:41 ke
* -added functions to verify signature data on a detached file
* -added function to sign detached file
* -removed all reads on GPGSTDOUT that are not in the main I/O loop
*
* Revision 1.32 2004/06/23 15:11:52 ke
* -added \n even if passphrase is not provided, so gpg does not hang
* -added strtolower for sort of keyring, to allow upper/lowercase to sort together
*
* Revision 1.31 2004/06/18 16:36:38 ke
* -consolidated all system keyring and trusted key operations into execute code
* -removed all system keyring additions from external functions
*
* Revision 1.30 2004/06/17 19:48:19 ke
* -Added alternate keyring option to use instead of the default keyrings
* -Added alternate secret keyring
*
* Revision 1.29 2004/05/20 00:31:00 ke
* -added function to check php version, if not defined by squirrelmail
*
* Revision 1.28 2004/05/11 23:31:06 ke
* -added function to import ascii-armored keyblocks
*
* Revision 1.27 2004/05/11 23:10:03 ke
* -added option to explicitly set a secret keyring
*
* Revision 1.26 2004/04/30 17:53:54 ke
* -removed newline from end of file
*
* Revision 1.25 2004/04/13 15:10:40 ke
* -changed phpdoc comments for addUID to correctly reflect variable names
*
* Revision 1.24 2004/04/09 16:43:50 ke
* -added check for contents of trusted key, ignore if blank
* -added increaseEntropy function and a trigger for it from the status pipe
* -fixed encryption parameter line
*
* Revision 1.23 2004/04/06 00:18:50 ke
* -added a check to ensure that select_stream did not return false
* -changed final NULL value to 0, as per php.net's suggestion
* done to attempt to solve problem in PHP for windows
*
* Revision 1.22 2004/03/31 21:08:48 ke
* -small typo fixed to allow signature object creation on ERRSIG
*
* Revision 1.21 2004/03/30 19:45:37 ke
* Added functions for decrypt, sign, verify signatures
* Added object for signatures on data
* Signature object created with combination of pipes/text parsing
*
* Revision 1.20 2004/03/30 02:11:49 ke
* -changed to reflect current licensing (LGPL, copyright GPG Plugin Development Team)
*
* Revision 1.19 2004/03/30 01:35:08 ke
* -added generate key function
* -added set key preferences function (goes with generate key, can be used standalone too)
* -added encrypt function with sign capabilities
* -added trustedKeys array to append to command line in signing/encryption operations
* -added pipes interaction code to allow above functions to operate properly
*
* Revision 1.18 2004/03/24 17:46:43 ke
* -changed cannot confirm signature: missing public key message to be a warning instead of an error
*
* Revision 1.17 2004/03/23 20:58:16 ke
* -added cases for subkey revocation interactions
* -removed --batch from non-pipe commands, to allow for more operations to function properly
*
* Revision 1.16 2004/03/22 22:31:50 ke
* -added function to change expiration on keys or subkeys
* -added debug output of stdout and stderr upon completion of a command
*
* Revision 1.15 2004/03/16 20:25:58 ke
* -added upload key function to GnuPG object
* -trap upload key success string with parse_output
* bug 27
*
* Revision 1.14 2004/03/15 22:15:19 ke
* -added new key functionality to import
* -fixed bug with flag sent to gpg
*
* Revision 1.13 2004/03/11 23:33:05 ke
* -removed deprecated --always-trust option from main command line
*
* Revision 1.12 2004/03/11 21:49:39 ke
* -added case for signing all uids on a key
*
* Revision 1.11 2004/03/09 18:12:29 ke
* -added functions for add/delete subkey
* -added functions for add/delete uid on a key
* -added proc_open interactions to actually add/delete subkeys&uids
*
* Revision 1.10 2004/03/04 01:50:41 ke
* -added function to output ascii armored keys
* -added signature array on main key object
* -added signatures to main keys in refreshKeys
*
* Revision 1.9 2004/03/03 20:38:25 ke
* -changed default case and all case of list keys to include system keyring
*
* Revision 1.8 2004/03/03 19:42:27 ke
* -rewrote loop to no longer block on pipes
* -system keyring view no longer includes keys in homedir
* -added fingerprint formatting output to key object
*
* Revision 1.7 2004/02/27 01:43:09 ke
* -added system key to refresh on addRevoker
* -added code to use system keyring when adding revoker
*
* Revision 1.6 2004/02/26 23:11:40 ke
* -added addRevoker function and neccessary interactions with gpg
* -successfully adds a revoker, but gpg is lacking any way to immediately determine this fact
* -public key contains revoker information successfully, and can be exported by email/view key
* bug 52
*
* Revision 1.5 2004/02/26 18:17:12 ke
* -added listener for GPG signal BEGIN_DECRYPTION to read data
* -more debug output
* -added patch to ignore locale not being set message from Chuck Foster
* bug 161
*
* Revision 1.4 2004/02/19 21:33:37 ke
* -added real check for php version in time to not run proc_open
*
* Revision 1.3 2004/02/19 18:08:39 ke
* -added function line back in to fix parse error
*
* Revision 1.2 2004/02/19 00:37:19 ke
* -added function and class headers for phpdoc
*
*
*/
?>