/** * @group delete-private */ public function testDeletePrivateKey() { $keyId = '*****@*****.**'; $this->gpg->deletePrivateKey($keyId); $expectedKeys = array(); // {{{ first-keypair@example.com $key = new Crypt_GPG_Key(); $expectedKeys[] = $key; $userId = new Crypt_GPG_UserId(); $userId->setName('First Keypair Test Key'); $userId->setComment('do not encrypt important data with this key'); $userId->setEmail('*****@*****.**'); $key->addUserId($userId); $subKey = new Crypt_GPG_SubKey(); $subKey->setId('C097D9EC94C06363'); $subKey->setAlgorithm(Crypt_GPG_SubKey::ALGORITHM_DSA); $subKey->setFingerprint('8D2299D9C5C211128B32BBB0C097D9EC94C06363'); $subKey->setLength(1024); $subKey->setCreationDate(1221785805); $subKey->setExpirationDate(0); $subKey->setCanSign(true); $subKey->setCanEncrypt(false); $subKey->setHasPrivate(false); $key->addSubKey($subKey); $subKey = new Crypt_GPG_SubKey(); $subKey->setId('9F93F9116728EF12'); $subKey->setAlgorithm(Crypt_GPG_SubKey::ALGORITHM_ELGAMAL_ENC); $subKey->setFingerprint('C9C65B3BBF040E40D0EA27B79F93F9116728EF12'); $subKey->setLength(2048); $subKey->setCreationDate(1221785821); $subKey->setExpirationDate(0); $subKey->setCanSign(false); $subKey->setCanEncrypt(true); $subKey->setHasPrivate(false); $key->addSubKey($subKey); // }}} $keys = $this->gpg->getKeys($keyId); $this->assertEquals($expectedKeys, $keys); }
/** * Parses a user id object from a user id string * * A user id string is of the form: * <b><kbd>name (comment) <email-address></kbd></b> with the <i>comment</i> * and <i>email-address</i> fields being optional. * * @param string $string the user id string to parse. * * @return Crypt_GPG_UserId the user id object parsed from the string. */ public static function parse($string) { $userId = new Crypt_GPG_UserId(); $email = ''; $comment = ''; // get email address from end of string if it exists $matches = array(); if (preg_match('/^(.+?) <([^>]+)>$/', $string, $matches) === 1) { $string = $matches[1]; $email = $matches[2]; } // get comment from end of string if it exists $matches = array(); if (preg_match('/^(.+?) \\(([^\\)]+)\\)$/', $string, $matches) === 1) { $string = $matches[1]; $comment = $matches[2]; } $name = $string; $userId->setName($name); $userId->setComment($comment); $userId->setEmail($email); return $userId; }
/** * Gets a user id object from parameters * * @param string|Crypt_GPG_UserId $name either a {@link Crypt_GPG_UserId} * object, or a string containing * the name of the user id. * @param string $email optional. If <i>$name</i> is * specified as a string, this is * the email address of the user id. * @param string $comment optional. If <i>$name</i> is * specified as a string, this is * the comment of the user id. * * @return Crypt_GPG_UserId a user id object for the specified parameters. */ protected function getUserId($name, $email = '', $comment = '') { if ($name instanceof Crypt_GPG_UserId) { $userId = $name; } else { $userId = new Crypt_GPG_UserId(); $userId->setName($name)->setEmail($email)->setComment($comment); } return $userId; }
/** * @group fluent */ public function testFluentInterface() { $userId = new Crypt_GPG_UserId(); $returnedUserId = $userId->setName('Alice'); $this->assertEquals($userId, $returnedUserId, 'Failed asserting fluent interface works for setName() method.'); $userId = new Crypt_GPG_UserId(); $returnedUserId = $userId->setComment('encryption is fun'); $this->assertEquals($userId, $returnedUserId, 'Failed asserting fluent interface works for setComment() method.'); $userId = new Crypt_GPG_UserId(); $returnedUserId = $userId->setEmail('*****@*****.**'); $this->assertEquals($userId, $returnedUserId, 'Failed asserting fluent interface works for setEmail() method.'); $userId = new Crypt_GPG_UserId(); $returnedUserId = $userId->setRevoked(true); $this->assertEquals($userId, $returnedUserId, 'Failed asserting fluent interface works for setRevoked() method.'); $userId = new Crypt_GPG_UserId(); $returnedUserId = $userId->setValid(true); $this->assertEquals($userId, $returnedUserId, 'Failed asserting fluent interface works for setValid() method.'); }
/** * Parses a user id object from a user id string * * A user id string is of the form: * <b><kbd>name (comment) <email-address></kbd></b> with the <i>comment</i> * and <i>email-address</i> fields being optional. * * @param string $string the user id string to parse. * * @return Crypt_GPG_UserId the user id object parsed from the string. */ public static function parse($string) { $userId = new Crypt_GPG_UserId(); $name = ''; $email = ''; $comment = ''; // get email address from end of string if it exists $matches = array(); if (preg_match('/^(.*?)<([^>]+)>$/', $string, $matches) === 1) { $string = trim($matches[1]); $email = $matches[2]; } // get comment from end of string if it exists $matches = array(); if (preg_match('/^(.+?) \\(([^\\)]+)\\)$/', $string, $matches) === 1) { $string = $matches[1]; $comment = $matches[2]; } // there can be an email without a name if (!$email && preg_match('/^[\\S]+@[\\S]+$/', $string, $matches) === 1) { $email = $string; } else { $name = $string; } $userId->setName($name); $userId->setComment($comment); $userId->setEmail($email); return $userId; }
/** * Handles error values in the status output from GPG * * This method is responsible for setting the * {@link self::$errorCode}. See <b>doc/DETAILS</b> in the * {@link http://www.gnupg.org/download/ GPG distribution} for detailed * information on GPG's status output. * * @param string $line the status line to handle. * * @return void */ public function handleStatus($line) { $tokens = explode(' ', $line); switch ($tokens[0]) { case 'NODATA': $this->errorCode = Crypt_GPG::ERROR_NO_DATA; break; case 'DECRYPTION_OKAY': // If the message is encrypted, this is the all-clear signal. $this->data['DecryptionOkay'] = true; $this->errorCode = Crypt_GPG::ERROR_NONE; break; case 'DELETE_PROBLEM': if ($tokens[1] == '1') { $this->errorCode = Crypt_GPG::ERROR_KEY_NOT_FOUND; break; } elseif ($tokens[1] == '2') { $this->errorCode = Crypt_GPG::ERROR_DELETE_PRIVATE_KEY; break; } break; case 'IMPORT_OK': $this->data['Import']['fingerprint'] = $tokens[2]; if (empty($this->data['Import']['fingerprints'])) { $this->data['Import']['fingerprints'] = array($tokens[2]); } else { if (!in_array($tokens[2], $this->data['Import']['fingerprints'])) { $this->data['Import']['fingerprints'][] = $tokens[2]; } } break; case 'IMPORT_RES': $this->data['Import']['public_imported'] = intval($tokens[3]); $this->data['Import']['public_unchanged'] = intval($tokens[5]); $this->data['Import']['private_imported'] = intval($tokens[11]); $this->data['Import']['private_unchanged'] = intval($tokens[12]); break; case 'NO_PUBKEY': case 'NO_SECKEY': $this->data['ErrorKeyId'] = $tokens[1]; if ($this->errorCode != Crypt_GPG::ERROR_MISSING_PASSPHRASE && $this->errorCode != Crypt_GPG::ERROR_BAD_PASSPHRASE) { $this->errorCode = Crypt_GPG::ERROR_KEY_NOT_FOUND; } // note: this message is also received if there are multiple // recipients and a previous key had a correct passphrase. $this->data['MissingKeys'][$tokens[1]] = $tokens[1]; // @FIXME: remove missing passphrase registered in ENC_TO handler // This is for GnuPG 2.1 unset($this->data['MissingPassphrases'][$tokens[1]]); break; case 'KEY_CONSIDERED': // In GnuPG 2.1.x exporting/importing a secret key requires passphrase // However, no NEED_PASSPRASE is returned, https://bugs.gnupg.org/gnupg/issue2667 // So, handling KEY_CONSIDERED and GET_HIDDEN is needed. if (!array_key_exists('KeyConsidered', $this->data)) { $this->data['KeyConsidered'] = $tokens[1]; } break; case 'USERID_HINT': // remember the user id for pretty exception messages // GnuPG 2.1.15 gives me: "USERID_HINT 0000000000000000 [?]" $keyId = $tokens[1]; if (strcspn($keyId, '0')) { $username = implode(' ', array_splice($tokens, 2)); $this->data['BadPassphrases'][$keyId] = $username; } break; case 'ENC_TO': // Now we know the message is encrypted. Set flag to check if // decryption succeeded. $this->data['DecryptionOkay'] = false; // this is the new key message $this->data['CurrentSubKeyId'] = $keyId = $tokens[1]; // For some reason in GnuPG 2.1.11 I get only ENC_TO and no // NEED_PASSPHRASE/MISSING_PASSPHRASE/USERID_HINT // This is not needed for GnuPG 2.1.15 if (!empty($_ENV['PINENTRY_USER_DATA'])) { $passphrases = json_decode($_ENV['PINENTRY_USER_DATA'], true); } else { $passphrases = array(); } // @TODO: Get user name/email $this->data['BadPassphrases'][$keyId] = $keyId; if (empty($passphrases) || empty($passphrases[$keyId])) { $this->data['MissingPassphrases'][$keyId] = $keyId; } break; case 'GOOD_PASSPHRASE': // if we got a good passphrase, remove the key from the list of // bad passphrases. if (isset($this->data['CurrentSubKeyId'])) { unset($this->data['BadPassphrases'][$this->data['CurrentSubKeyId']]); unset($this->data['MissingPassphrases'][$this->data['CurrentSubKeyId']]); } $this->needPassphrase--; break; case 'BAD_PASSPHRASE': $this->errorCode = Crypt_GPG::ERROR_BAD_PASSPHRASE; break; case 'MISSING_PASSPHRASE': if (isset($this->data['CurrentSubKeyId'])) { $this->data['MissingPassphrases'][$this->data['CurrentSubKeyId']] = $this->data['CurrentSubKeyId']; } $this->errorCode = Crypt_GPG::ERROR_MISSING_PASSPHRASE; break; case 'GET_HIDDEN': if ($tokens[1] == 'passphrase.enter' && isset($this->data['KeyConsidered'])) { $tokens[1] = $this->data['KeyConsidered']; } else { break; } // no break // no break case 'NEED_PASSPHRASE': $passphrase = $this->getPin($tokens[1]); $this->engine->sendCommand($passphrase); if ($passphrase === '') { $this->needPassphrase++; } break; case 'SIG_CREATED': $this->data['SigCreated'] = $line; break; case 'SIG_ID': // note: signature id comes before new signature line and may not // exist for some signature types $this->data['SignatureId'] = $tokens[1]; break; case 'EXPSIG': case 'EXPKEYSIG': case 'REVKEYSIG': case 'BADSIG': case 'ERRSIG': $this->errorCode = Crypt_GPG::ERROR_BAD_SIGNATURE; // no break // no break case 'GOODSIG': $signature = new Crypt_GPG_Signature(); // if there was a signature id, set it on the new signature if (!empty($this->data['SignatureId'])) { $signature->setId($this->data['SignatureId']); $this->data['SignatureId'] = ''; } // Detect whether fingerprint or key id was returned and set // signature values appropriately. Key ids are strings of either // 16 or 8 hexadecimal characters. Fingerprints are strings of 40 // hexadecimal characters. The key id is the last 16 characters of // the key fingerprint. if (mb_strlen($tokens[1], '8bit') > 16) { $signature->setKeyFingerprint($tokens[1]); $signature->setKeyId(mb_substr($tokens[1], -16, null, '8bit')); } else { $signature->setKeyId($tokens[1]); } // get user id string if ($tokens[0] != 'ERRSIG') { $string = implode(' ', array_splice($tokens, 2)); $string = rawurldecode($string); $signature->setUserId(Crypt_GPG_UserId::parse($string)); } $this->data['Signatures'][] = $signature; break; case 'VALIDSIG': if (empty($this->data['Signatures'])) { break; } $signature = end($this->data['Signatures']); $signature->setValid(true); $signature->setKeyFingerprint($tokens[1]); if (strpos($tokens[3], 'T') === false) { $signature->setCreationDate($tokens[3]); } else { $signature->setCreationDate(strtotime($tokens[3])); } if (array_key_exists(4, $tokens)) { if (strpos($tokens[4], 'T') === false) { $signature->setExpirationDate($tokens[4]); } else { $signature->setExpirationDate(strtotime($tokens[4])); } } break; case 'KEY_CREATED': if (isset($this->data['Handle']) && $tokens[3] == $this->data['Handle']) { $this->data['KeyCreated'] = $tokens[2]; } break; case 'KEY_NOT_CREATED': if (isset($this->data['Handle']) && $tokens[1] == $this->data['Handle']) { $this->errorCode = Crypt_GPG::ERROR_KEY_NOT_CREATED; } break; case 'PROGRESS': // todo: at some point, support reporting status async break; // GnuPG 2.1 uses FAILURE and ERROR responses // GnuPG 2.1 uses FAILURE and ERROR responses case 'FAILURE': case 'ERROR': $errnum = (int) $tokens[2]; $source = $errnum >> 24; $errcode = $errnum & 0xffffff; switch ($errcode) { case 11: // bad passphrase // bad passphrase case 87: // bad PIN $this->errorCode = Crypt_GPG::ERROR_BAD_PASSPHRASE; break; case 177: // no passphrase // no passphrase case 178: // no PIN $this->errorCode = Crypt_GPG::ERROR_MISSING_PASSPHRASE; break; case 58: $this->errorCode = Crypt_GPG::ERROR_NO_DATA; break; } break; } }
/** * Handles a status line * * @param string $line the status line to handle. * * @return void */ public function handle($line) { $tokens = explode(' ', $line); switch ($tokens[0]) { case 'GOODSIG': case 'EXPSIG': case 'EXPKEYSIG': case 'REVKEYSIG': case 'BADSIG': $signature = new Crypt_GPG_Signature(); // if there was a signature id, set it on the new signature if ($this->signatureId != '') { $signature->setId($this->signatureId); $this->signatureId = ''; } // Detect whether fingerprint or key id was returned and set // signature values appropriately. Key ids are strings of either // 16 or 8 hexadecimal characters. Fingerprints are strings of 40 // hexadecimal characters. The key id is the last 16 characters of // the key fingerprint. if (strlen($tokens[1]) > 16) { $signature->setKeyFingerprint($tokens[1]); $signature->setKeyId(substr($tokens[1], -16)); } else { $signature->setKeyId($tokens[1]); } // get user id string $string = implode(' ', array_splice($tokens, 2)); $string = rawurldecode($string); $signature->setUserId(Crypt_GPG_UserId::parse($string)); $this->index++; $this->signatures[$this->index] = $signature; break; case 'ERRSIG': $signature = new Crypt_GPG_Signature(); // if there was a signature id, set it on the new signature if ($this->signatureId != '') { $signature->setId($this->signatureId); $this->signatureId = ''; } // Detect whether fingerprint or key id was returned and set // signature values appropriately. Key ids are strings of either // 16 or 8 hexadecimal characters. Fingerprints are strings of 40 // hexadecimal characters. The key id is the last 16 characters of // the key fingerprint. if (strlen($tokens[1]) > 16) { $signature->setKeyFingerprint($tokens[1]); $signature->setKeyId(substr($tokens[1], -16)); } else { $signature->setKeyId($tokens[1]); } $this->index++; $this->signatures[$this->index] = $signature; break; case 'VALIDSIG': if (!array_key_exists($this->index, $this->signatures)) { break; } $signature = $this->signatures[$this->index]; $signature->setValid(true); $signature->setKeyFingerprint($tokens[1]); if (strpos($tokens[3], 'T') === false) { $signature->setCreationDate($tokens[3]); } else { $signature->setCreationDate(strtotime($tokens[3])); } if (array_key_exists(4, $tokens)) { if (strpos($tokens[4], 'T') === false) { $signature->setExpirationDate($tokens[4]); } else { $signature->setExpirationDate(strtotime($tokens[4])); } } break; case 'SIG_ID': // note: signature id comes before new signature line and may not // exist for some signature types $this->signatureId = $tokens[1]; break; } }
/** * @group generate-key */ public function testGenerateKeyWithExpirationDate() { if (!$this->config['enable-key-generation']) { $this->markTestSkipped('Key generation tests are disabled. To run key generation ' . 'tests, enable them in the test configuration. See the ' . 'configuration in \'config.php.dist\' for an exampe.'); } // {{{ generate-test@example.com $expectedKey = new Crypt_GPG_Key(); $userId = new Crypt_GPG_UserId(); $userId->setName('Test Keypair'); $userId->setEmail('*****@*****.**'); $expectedKey->addUserId($userId); $subKey = new Crypt_GPG_SubKey(); $subKey->setAlgorithm(Crypt_GPG_SubKey::ALGORITHM_DSA); $subKey->setLength(1024); $subKey->setExpirationDate(1999998000); // truncated to day $subKey->setCanSign(true); $subKey->setCanEncrypt(false); $subKey->setHasPrivate(true); $expectedKey->addSubKey($subKey); $subKey = new Crypt_GPG_SubKey(); $subKey->setAlgorithm(Crypt_GPG_SubKey::ALGORITHM_ELGAMAL_ENC); $subKey->setLength(2048); $subKey->setExpirationDate(1999998000); // truncated to day $subKey->setCanSign(false); $subKey->setCanEncrypt(true); $subKey->setHasPrivate(true); $expectedKey->addSubKey($subKey); // }}} $key = $this->generator->setExpirationDate(2000000000)->generateKey(new Crypt_GPG_UserId('Test Keypair <*****@*****.**>')); $this->assertKeyEquals($expectedKey, $key); }
/** * Gets the available keys in the keyring * * Calls GPG with the <kbd>--list-keys</kbd> command and grabs keys. See * the first section of <b>doc/DETAILS</b> in the * {@link http://www.gnupg.org/download/ GPG package} for a detailed * description of how the GPG command output is parsed. * * @param string $keyId optional. Only keys with that match the specified * pattern are returned. The pattern may be part of * a user id, a key id or a key fingerprint. If not * specified, all keys are returned. * * @return array an array of {@link Crypt_GPG_Key} objects. If no keys * match the specified <kbd>$keyId</kbd> an empty array is * returned. * * @throws Crypt_GPG_Exception if an unknown or unexpected error occurs. * Use the <kbd>debug</kbd> option and file a bug report if these * exceptions occur. * * @see Crypt_GPG_Key */ public function getKeys($keyId = '') { // get private key fingerprints if ($keyId == '') { $operation = '--list-secret-keys'; } else { $operation = '--list-secret-keys ' . escapeshellarg($keyId); } // According to The file 'doc/DETAILS' in the GnuPG distribution, using // double '--with-fingerprint' also prints the fingerprint for subkeys. $arguments = array('--with-colons', '--with-fingerprint', '--with-fingerprint', '--fixed-list-mode'); $output = ''; $this->engine->reset(); $this->engine->setOutput($output); $this->engine->setOperation($operation, $arguments); $this->engine->run(); $code = $this->engine->getErrorCode(); switch ($code) { case Crypt_GPG::ERROR_NONE: case Crypt_GPG::ERROR_KEY_NOT_FOUND: // ignore not found key errors break; case Crypt_GPG::ERROR_FILE_PERMISSIONS: $filename = $this->engine->getErrorFilename(); if ($filename) { throw new Crypt_GPG_FileException(sprintf('Error reading GnuPG data file \'%s\'. Check to make ' . 'sure it is readable by the current user.', $filename), $code, $filename); } throw new Crypt_GPG_FileException('Error reading GnuPG data file. Check to make GnuPG data ' . 'files are readable by the current user.', $code); default: throw new Crypt_GPG_Exception('Unknown error getting keys. Please use the \'debug\' option ' . 'when creating the Crypt_GPG object, and file a bug report ' . 'at ' . self::BUG_URI, $code); } $privateKeyFingerprints = array(); $lines = explode(PHP_EOL, $output); foreach ($lines as $line) { $lineExp = explode(':', $line); if ($lineExp[0] == 'fpr') { $privateKeyFingerprints[] = $lineExp[9]; } } // get public keys if ($keyId == '') { $operation = '--list-public-keys'; } else { $operation = '--list-public-keys ' . escapeshellarg($keyId); } $output = ''; $this->engine->reset(); $this->engine->setOutput($output); $this->engine->setOperation($operation, $arguments); $this->engine->run(); $code = $this->engine->getErrorCode(); switch ($code) { case Crypt_GPG::ERROR_NONE: case Crypt_GPG::ERROR_KEY_NOT_FOUND: // ignore not found key errors break; case Crypt_GPG::ERROR_FILE_PERMISSIONS: $filename = $this->engine->getErrorFilename(); if ($filename) { throw new Crypt_GPG_FileException(sprintf('Error reading GnuPG data file \'%s\'. Check to make ' . 'sure it is readable by the current user.', $filename), $code, $filename); } throw new Crypt_GPG_FileException('Error reading GnuPG data file. Check to make GnuPG data ' . 'files are readable by the current user.', $code); default: throw new Crypt_GPG_Exception('Unknown error getting keys. Please use the \'debug\' option ' . 'when creating the Crypt_GPG object, and file a bug report ' . 'at ' . self::BUG_URI, $code); } $keys = array(); $key = null; // current key $subKey = null; // current sub-key $lines = explode(PHP_EOL, $output); foreach ($lines as $line) { $lineExp = explode(':', $line); if ($lineExp[0] == 'pub') { // new primary key means last key should be added to the array if ($key !== null) { $keys[] = $key; } $key = new Crypt_GPG_Key(); $subKey = Crypt_GPG_SubKey::parse($line); $key->addSubKey($subKey); } elseif ($lineExp[0] == 'sub') { $subKey = Crypt_GPG_SubKey::parse($line); $key->addSubKey($subKey); } elseif ($lineExp[0] == 'fpr') { $fingerprint = $lineExp[9]; // set current sub-key fingerprint $subKey->setFingerprint($fingerprint); // if private key exists, set has private to true if (in_array($fingerprint, $privateKeyFingerprints)) { $subKey->setHasPrivate(true); } } elseif ($lineExp[0] == 'uid') { $string = stripcslashes($lineExp[9]); // as per documentation $userId = new Crypt_GPG_UserId($string); if ($lineExp[1] == 'r') { $userId->setRevoked(true); } $key->addUserId($userId); } } // add last key if ($key !== null) { $keys[] = $key; } return $keys; }
/** * @group file */ public function testVerifyFileDualDetachedSignature() { // {{{ first signature $firstSignature = new Crypt_GPG_Signature(); $firstSignature->setId('T7+toJbsFr8KMTWN+M7lF3xSmmA'); $firstSignature->setKeyFingerprint('8D2299D9C5C211128B32BBB0C097D9EC94C06363'); $firstSignature->setKeyId('C097D9EC94C06363'); $firstSignature->setCreationDate(1221960707); $firstSignature->setExpirationDate(0); $firstSignature->setValid(true); $userId = new Crypt_GPG_UserId(); $userId->setName('First Keypair Test Key'); $userId->setComment('do not encrypt important data with this key'); $userId->setEmail('*****@*****.**'); $firstSignature->setUserId($userId); // }}} // {{{ second signature $secondSignature = new Crypt_GPG_Signature(); $secondSignature->setId('HJd1yvMbEbW5facuxkDtvwymKrw'); $secondSignature->setKeyFingerprint('880922DBEA733E906693E4A903CC890AFA1DAD4B'); $secondSignature->setKeyId('03CC890AFA1DAD4B'); $secondSignature->setCreationDate(1221960707); $secondSignature->setExpirationDate(0); $secondSignature->setValid(true); $userId = new Crypt_GPG_UserId(); $userId->setName('Second Keypair Test Key'); $userId->setComment('do not encrypt important data with this key'); $userId->setEmail('*****@*****.**'); $secondSignature->setUserId($userId); // }}} // {{{ signature data $signatureData = <<<TEXT -----BEGIN PGP SIGNATURE----- Version: GnuPG v1.4.6 (GNU/Linux) iD8DBQBI1aQDwJfZ7JTAY2MRAvkzAKDPnJ030GdYE15mE8smz2oV7zYziwCeJFxf UaTrAgP1Dck9DhHOBhvhwLuIPwMFAEjVpAMDzIkK+h2tSxEC+TMAn38yx3mXk6wP JaPThD7lRVE9ve57AJ0Yy7JwiT9sGXomln4JtRvuSpGtsg== =Gw9D -----END PGP SIGNATURE----- TEXT; // }}} $expectedSignatures = array($firstSignature, $secondSignature); $filename = $this->getDataFilename('testFileMedium.plain'); $signatures = $this->gpg->verifyFile($filename, $signatureData); $this->assertSignaturesEquals($expectedSignatures, $signatures); }
/** * Gets the available keys in the keyring * * Calls GPG with the <kbd>--list-keys</kbd> command and grabs keys. See * the first section of <b>doc/DETAILS</b> in the * {@link http://www.gnupg.org/download/ GPG package} for a detailed * description of how the GPG command output is parsed. * * @param string $keyId optional. Only keys with that match the specified * pattern are returned. The pattern may be part of * a user id, a key id or a key fingerprint. If not * specified, all keys are returned. * * @return array an array of {@link Crypt_GPG_Key} objects. If no keys * match the specified <kbd>$keyId</kbd> an empty array is * returned. * * @throws Crypt_GPG_Exception if an unknown or unexpected error occurs. * Use the <kbd>debug</kbd> option and file a bug report if these * exceptions occur. * * @see Crypt_GPG_Key */ protected function _getKeys($keyId = '') { // get private key fingerprints if ($keyId == '') { $operation = '--list-secret-keys'; } else { $operation = '--utf8-strings --list-secret-keys ' . escapeshellarg($keyId); } // According to The file 'doc/DETAILS' in the GnuPG distribution, using // double '--with-fingerprint' also prints the fingerprint for subkeys. $arguments = array('--with-colons', '--with-fingerprint', '--with-fingerprint', '--fixed-list-mode'); $output = ''; $this->engine->reset(); $this->engine->setOutput($output); $this->engine->setOperation($operation, $arguments); $this->engine->run(); $privateKeyFingerprints = array(); foreach (explode(PHP_EOL, $output) as $line) { $lineExp = explode(':', $line); if ($lineExp[0] == 'fpr') { $privateKeyFingerprints[] = $lineExp[9]; } } // get public keys if ($keyId == '') { $operation = '--list-public-keys'; } else { $operation = '--utf8-strings --list-public-keys ' . escapeshellarg($keyId); } $output = ''; $this->engine->reset(); $this->engine->setOutput($output); $this->engine->setOperation($operation, $arguments); $this->engine->run(); $keys = array(); $key = null; // current key $subKey = null; // current sub-key foreach (explode(PHP_EOL, $output) as $line) { $lineExp = explode(':', $line); if ($lineExp[0] == 'pub') { // new primary key means last key should be added to the array if ($key !== null) { $keys[] = $key; } $key = new Crypt_GPG_Key(); $subKey = Crypt_GPG_SubKey::parse($line); $key->addSubKey($subKey); } elseif ($lineExp[0] == 'sub') { $subKey = Crypt_GPG_SubKey::parse($line); $key->addSubKey($subKey); } elseif ($lineExp[0] == 'fpr') { $fingerprint = $lineExp[9]; // set current sub-key fingerprint $subKey->setFingerprint($fingerprint); // if private key exists, set has private to true if (in_array($fingerprint, $privateKeyFingerprints)) { $subKey->setHasPrivate(true); } } elseif ($lineExp[0] == 'uid') { $string = stripcslashes($lineExp[9]); // as per documentation $userId = new Crypt_GPG_UserId($string); if ($lineExp[1] == 'r') { $userId->setRevoked(true); } $key->addUserId($userId); } } // add last key if ($key !== null) { $keys[] = $key; } return $keys; }
/** * @group file */ public function testDecryptVerifyFileSignedOnly() { // {{{ signature $signature = new Crypt_GPG_Signature(); $signature->setId('vctnI/HnsRYmqcVwCJcJhS60lKU'); $signature->setKeyFingerprint('8D2299D9C5C211128B32BBB0C097D9EC94C06363'); $signature->setKeyId('C097D9EC94C06363'); $signature->setCreationDate(1221960707); $signature->setExpirationDate(0); $signature->setValid(true); $userId = new Crypt_GPG_UserId(); $userId->setName('First Keypair Test Key'); $userId->setComment('do not encrypt important data with this key'); $userId->setEmail('*****@*****.**'); $signature->setUserId($userId); // }}} $expectedMd5Sum = 'f96267d87551ee09bfcac16921e351c1'; $expectedResults = array('data' => null, 'signatures' => array($signature)); $inputFilename = $this->getDataFilename('testVerifyFileNormalSignedData.asc'); $outputFilename = $this->getTempFilename('testDecryptVerifyFileSignedData.plain'); $results = $this->gpg->decryptAndVerifyFile($inputFilename, $outputFilename); $this->assertDecryptAndVerifyResultsEquals($expectedResults, $results); $md5Sum = $this->getMd5Sum($outputFilename); $this->assertEquals($expectedMd5Sum, $md5Sum); }