securemail: update pgp library

Remove php-gpg, add openpgp-php and phpseclib.
Add template file for settings.
Add "send test" functionality.
Add messages.po
This commit is contained in:
fabrixxm 2017-04-11 21:19:54 +02:00
parent 02b2fe9b10
commit 50f91a338c
52 changed files with 35258 additions and 3205 deletions

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,261 @@
<?php
// This is free and unencumbered software released into the public domain.
/**
* OpenPGP_Crypt_RSA.php is a wrapper for using the classes from OpenPGP.php with Crypt_RSA
*
* @package OpenPGP
*/
// From http://phpseclib.sourceforge.net/
require_once 'Crypt/RSA.php';
require_once dirname(__FILE__).'/openpgp.php';
@include_once dirname(__FILE__).'/openpgp_crypt_symmetric.php'; /* For encrypt/decrypt */
class OpenPGP_Crypt_RSA {
protected $key, $message;
// Construct a wrapper object from a key or a message packet
function __construct($packet) {
if(!is_object($packet)) $packet = OpenPGP_Message::parse($packet);
if($packet instanceof OpenPGP_PublicKeyPacket || $packet[0] instanceof OpenPGP_PublicKeyPacket) { // If it's a key (other keys are subclasses of this one)
$this->key = $packet;
} else {
$this->message = $packet;
}
}
function key($keyid=NULL) {
if(!$this->key) return NULL; // No key
if($this->key instanceof OpenPGP_Message) {
foreach($this->key as $p) {
if($p instanceof OpenPGP_PublicKeyPacket) {
if(!$keyid || strtoupper(substr($p->fingerprint, strlen($keyid)*-1)) == strtoupper($keyid)) return $p;
}
}
}
return $this->key;
}
// Get Crypt_RSA for the public key
function public_key($keyid=NULL) {
return self::convert_public_key($this->key($keyid));
}
// Get Crypt_RSA for the private key
function private_key($keyid=NULL) {
return self::convert_private_key($this->key($keyid));
}
// Pass a message to verify with this key, or a key (OpenPGP or Crypt_RSA) to check this message with
// Second optional parameter to specify which signature to verify (if there is more than one)
function verify($packet) {
$self = $this; // For old PHP
if(!is_object($packet)) $packet = OpenPGP_Message::parse($packet);
if(!$this->message) {
$m = $packet;
$verifier = function($m, $s) use($self) {
$key = $self->public_key($s->issuer());
if(!$key) return false;
$key->setHash(strtolower($s->hash_algorithm_name()));
return $key->verify($m, reset($s->data));
};
} else {
if(!($packet instanceof Crypt_RSA)) {
$packet = new self($packet);
}
$m = $this->message;
$verifier = function($m, $s) use($self, $packet) {
if(!($packet instanceof Crypt_RSA)) {
$key = $packet->public_key($s->issuer());
}
if(!$key) return false;
$key->setHash(strtolower($s->hash_algorithm_name()));
return $key->verify($m, reset($s->data));
};
}
return $m->verified_signatures(array('RSA' => array(
'MD5' => $verifier,
'SHA1' => $verifier,
'SHA224' => $verifier,
'SHA256' => $verifier,
'SHA384' => $verifier,
'SHA512' => $verifier
)));
}
// Pass a message to sign with this key, or a secret key to sign this message with
// Second parameter is hash algorithm to use (default SHA256)
// Third parameter is the 16-digit key ID to use... defaults to the key id in the key packet
function sign($packet, $hash='SHA256', $keyid=NULL) {
if(!is_object($packet)) {
if($this->key) {
$packet = new OpenPGP_LiteralDataPacket($packet);
} else {
$packet = OpenPGP_Message::parse($packet);
}
}
if($packet instanceof OpenPGP_SecretKeyPacket || $packet instanceof Crypt_RSA
|| ($packet instanceof ArrayAccess && $packet[0] instanceof OpenPGP_SecretKeyPacket)) {
$key = $packet;
$message = $this->message;
} else {
$key = $this->key;
$message = $packet;
}
if(!$key || !$message) return NULL; // Missing some data
if($message instanceof OpenPGP_Message) {
$sign = $message->signatures();
$message = $sign[0][0];
}
if(!($key instanceof Crypt_RSA)) {
$key = new self($key);
if(!$keyid) $keyid = substr($key->key()->fingerprint, -16, 16);
$key = $key->private_key($keyid);
}
$key->setHash(strtolower($hash));
$sig = new OpenPGP_SignaturePacket($message, 'RSA', strtoupper($hash));
$sig->hashed_subpackets[] = new OpenPGP_SignaturePacket_IssuerPacket($keyid);
$sig->sign_data(array('RSA' => array($hash => function($data) use($key) {return array($key->sign($data));})));
return new OpenPGP_Message(array($sig, $message));
}
/** Pass a message with a key and userid packet to sign */
// TODO: merge this with the normal sign function
function sign_key_userid($packet, $hash='SHA256', $keyid=NULL) {
if(is_array($packet)) {
$packet = new OpenPGP_Message($packet);
} else if(!is_object($packet)) {
$packet = OpenPGP_Message::parse($packet);
}
$key = $this->private_key($keyid);
if(!$key || !$packet) return NULL; // Missing some data
if(!$keyid) $keyid = substr($this->key->fingerprint, -16);
$key->setHash(strtolower($hash));
$sig = NULL;
foreach($packet as $p) {
if($p instanceof OpenPGP_SignaturePacket) $sig = $p;
}
if(!$sig) {
$sig = new OpenPGP_SignaturePacket($packet, 'RSA', strtoupper($hash));
$sig->signature_type = 0x13;
$sig->hashed_subpackets[] = new OpenPGP_SignaturePacket_KeyFlagsPacket(array(0x01, 0x02));
$sig->hashed_subpackets[] = new OpenPGP_SignaturePacket_IssuerPacket($keyid);
$packet[] = $sig;
}
$sig->sign_data(array('RSA' => array($hash => function($data) use($key) {return array($key->sign($data));})));
return $packet;
}
function decrypt($packet) {
if(!is_object($packet)) $packet = OpenPGP_Message::parse($packet);
if($packet instanceof OpenPGP_SecretKeyPacket || $packet instanceof Crypt_RSA
|| ($packet instanceof ArrayAccess && $packet[0] instanceof OpenPGP_SecretKeyPacket)) {
$keys = $packet;
$message = $this->message;
} else {
$keys = $this->key;
$message = $packet;
}
if(!$keys || !$message) return NULL; // Missing some data
if(!($keys instanceof Crypt_RSA)) {
$keys = new self($keys);
}
foreach($message as $p) {
if($p instanceof OpenPGP_AsymmetricSessionKeyPacket) {
if($keys instanceof Crypt_RSA) {
$sk = self::try_decrypt_session($keys, substr($p->encrypted_data, 2));
} else if(strlen(str_replace('0', '', $p->keyid)) < 1) {
foreach($keys->key as $k) {
$sk = self::try_decrypt_session(self::convert_private_key($k), substr($p->encrypted_data, 2));
if($sk) break;
}
} else {
$key = $keys->private_key($p->keyid);
$sk = self::try_decrypt_session($key, substr($p->encrypted_data, 2));
}
if(!$sk) continue;
$r = OpenPGP_Crypt_Symmetric::decryptPacket(OpenPGP_Crypt_Symmetric::getEncryptedData($message), $sk[0], $sk[1]);
if($r) return $r;
}
}
return NULL; /* Failed */
}
static function try_decrypt_session($key, $edata) {
$key->setEncryptionMode(CRYPT_RSA_ENCRYPTION_PKCS1);
$data = $key->decrypt($edata);
$sk = substr($data, 1, strlen($data)-3);
$chk = unpack('n', substr($data, -2));
$chk = reset($chk);
$sk_chk = 0;
for($i = 0; $i < strlen($sk); $i++) {
$sk_chk = ($sk_chk + ord($sk{$i})) % 65536;
}
if($sk_chk != $chk) return NULL;
return array(ord($data{0}), $sk);
}
static function crypt_rsa_key($mod, $exp, $hash='SHA256') {
$rsa = new Crypt_RSA();
$rsa->setSignatureMode(CRYPT_RSA_SIGNATURE_PKCS1);
$rsa->setHash(strtolower($hash));
$rsa->modulus = new Math_BigInteger($mod, 256);
$rsa->k = strlen($rsa->modulus->toBytes());
$rsa->exponent = new Math_BigInteger($exp, 256);
$rsa->setPublicKey();
return $rsa;
}
static function convert_key($packet, $private=false) {
if(!is_object($packet)) $packet = OpenPGP_Message::parse($packet);
if($packet instanceof OpenPGP_Message) $packet = $packet[0];
$mod = $packet->key['n'];
$exp = $packet->key['e'];
if($private) $exp = $packet->key['d'];
if(!$exp) return NULL; // Packet doesn't have needed data
$rsa = self::crypt_rsa_key($mod, $exp);
if($private) {
if($packet->key['p'] && $packet->key['q']) $rsa->primes = array($packet->key['p'], $packet->key['q']);
if($packet->key['u']) $rsa->coefficients = array($packet->key['u']);
}
return $rsa;
}
static function convert_public_key($packet) {
return self::convert_key($packet, false);
}
static function convert_private_key($packet) {
return self::convert_key($packet, true);
}
}
?>

View file

@ -0,0 +1,196 @@
<?php
require_once dirname(__FILE__).'/openpgp.php';
@include_once dirname(__FILE__).'/openpgp_crypt_rsa.php';
@include_once dirname(__FILE__).'/openpgp_mcrypt_wrapper.php';
@include_once 'Crypt/AES.php';
@include_once 'Crypt/TripleDES.php';
require_once 'Crypt/Random.php'; // part of phpseclib is absolutely required
class OpenPGP_Crypt_Symmetric {
public static function encrypt($passphrases_and_keys, $message, $symmetric_algorithm=9) {
list($cipher, $key_bytes, $key_block_bytes) = self::getCipher($symmetric_algorithm);
if(!$cipher) throw new Exception("Unsupported cipher");
$prefix = crypt_random_string($key_block_bytes);
$prefix .= substr($prefix, -2);
$key = crypt_random_string($key_bytes);
$cipher->setKey($key);
$to_encrypt = $prefix . $message->to_bytes();
$mdc = new OpenPGP_ModificationDetectionCodePacket(hash('sha1', $to_encrypt . "\xD3\x14", true));
$to_encrypt .= $mdc->to_bytes();
$encrypted = array(new OpenPGP_IntegrityProtectedDataPacket($cipher->encrypt($to_encrypt)));
if(!is_array($passphrases_and_keys) && !($passphrases_and_keys instanceof IteratorAggregate)) {
$passphrases_and_keys = (array)$passphrases_and_keys;
}
foreach($passphrases_and_keys as $pass) {
if($pass instanceof OpenPGP_PublicKeyPacket) {
if(!in_array($pass->algorithm, array(1,2,3))) throw new Exception("Only RSA keys are supported.");
$crypt_rsa = new OpenPGP_Crypt_RSA($pass);
$rsa = $crypt_rsa->public_key();
$rsa->setEncryptionMode(CRYPT_RSA_ENCRYPTION_PKCS1);
$esk = $rsa->encrypt(chr($symmetric_algorithm) . $key . pack('n', self::checksum($key)));
$esk = pack('n', OpenPGP::bitlength($esk)) . $esk;
array_unshift($encrypted, new OpenPGP_AsymmetricSessionKeyPacket($pass->algorithm, $pass->fingerprint(), $esk));
} else if(is_string($pass)) {
$s2k = new OpenPGP_S2K(crypt_random_string(10));
$cipher->setKey($s2k->make_key($pass, $key_bytes));
$esk = $cipher->encrypt(chr($symmetric_algorithm) . $key);
array_unshift($encrypted, new OpenPGP_SymmetricSessionKeyPacket($s2k, $esk, $symmetric_algorithm));
}
}
return new OpenPGP_Message($encrypted);
}
public static function decryptSymmetric($pass, $m) {
$epacket = self::getEncryptedData($m);
foreach($m as $p) {
if($p instanceof OpenPGP_SymmetricSessionKeyPacket) {
if(strlen($p->encrypted_data) > 0) {
list($cipher, $key_bytes, $key_block_bytes) = self::getCipher($p->symmetric_algorithm);
if(!$cipher) continue;
$cipher->setKey($p->s2k->make_key($pass, $key_bytes));
$padAmount = $key_block_bytes - (strlen($p->encrypted_data) % $key_block_bytes);
$data = substr($cipher->decrypt($p->encrypted_data . str_repeat("\0", $padAmount)), 0, strlen($p->encrypted_data));
$decrypted = self::decryptPacket($epacket, ord($data{0}), substr($data, 1));
} else {
list($cipher, $key_bytes, $key_block_bytes) = self::getCipher($p->symmetric_algorithm);
$decrypted = self::decryptPacket($epacket, $p->symmetric_algorithm, $p->s2k->make_key($pass, $key_bytes));
}
if($decrypted) return $decrypted;
}
}
return NULL; /* If we get here, we failed */
}
public static function decryptSecretKey($pass, $packet) {
$packet = clone $packet; // Do not mutate orinigal
list($cipher, $key_bytes, $key_block_bytes) = self::getCipher($packet->symmetric_algorithm);
if(!$cipher) throw new Exception("Unsupported cipher");
$cipher->setKey($packet->s2k->make_key($pass, $key_bytes));
$cipher->setIV(substr($packet->encrypted_data, 0, $key_block_bytes));
$material = $cipher->decrypt(substr($packet->encrypted_data, $key_block_bytes));
if($packet->s2k_useage == 254) {
$chk = substr($material, -20);
$material = substr($material, 0, -20);
if($chk != hash('sha1', $material, true)) return NULL;
} else {
$chk = unpack('n', substr($material, -2));
$chk = reset($chk);
$material = substr($material, 0, -2);
$mkChk = self::checksum($material);
if($chk != $mkChk) return NULL;
}
$packet->s2k_useage = 0;
$packet->symmetric_algorithm = 0;
$packet->encrypted_data = NULL;
$packet->input = $material;
$packet->key_from_input();
unset($packet->input);
return $packet;
}
public static function decryptPacket($epacket, $symmetric_algorithm, $key) {
list($cipher, $key_bytes, $key_block_bytes) = self::getCipher($symmetric_algorithm);
if(!$cipher) return NULL;
$cipher->setKey($key);
if($epacket instanceof OpenPGP_IntegrityProtectedDataPacket) {
$padAmount = $key_block_bytes - (strlen($epacket->data) % $key_block_bytes);
$data = substr($cipher->decrypt($epacket->data . str_repeat("\0", $padAmount)), 0, strlen($epacket->data));
$prefix = substr($data, 0, $key_block_bytes + 2);
$mdc = substr(substr($data, -22, 22), 2);
$data = substr($data, $key_block_bytes + 2, -22);
$mkMDC = hash("sha1", $prefix . $data . "\xD3\x14", true);
if($mkMDC !== $mdc) return false;
try {
$msg = OpenPGP_Message::parse($data);
} catch (Exception $ex) { $msg = NULL; }
if($msg) return $msg; /* Otherwise keep trying */
} else {
// No MDC mean decrypt with resync
$iv = substr($epacket->data, 2, $key_block_bytes);
$edata = substr($epacket->data, $key_block_bytes + 2);
$padAmount = $key_block_bytes - (strlen($edata) % $key_block_bytes);
$cipher->setIV($iv);
$data = substr($cipher->decrypt($edata . str_repeat("\0", $padAmount)), 0, strlen($edata));
try {
$msg = OpenPGP_Message::parse($data);
} catch (Exception $ex) { $msg = NULL; }
if($msg) return $msg; /* Otherwise keep trying */
}
return NULL; /* Failed */
}
public static function getCipher($algo) {
$cipher = NULL;
switch($algo) {
case 2:
if(class_exists('Crypt_TripleDES')) {
$cipher = new Crypt_TripleDES(CRYPT_DES_MODE_CFB);
$key_bytes = 24;
$key_block_bytes = 8;
}
break;
case 3:
if(defined('MCRYPT_CAST_128')) {
$cipher = new MCryptWrapper(MCRYPT_CAST_128);
}
break;
case 7:
if(class_exists('Crypt_AES')) {
$cipher = new Crypt_AES(CRYPT_AES_MODE_CFB);
$cipher->setKeyLength(128);
}
break;
case 8:
if(class_exists('Crypt_AES')) {
$cipher = new Crypt_AES(CRYPT_AES_MODE_CFB);
$cipher->setKeyLength(192);
}
break;
case 9:
if(class_exists('Crypt_AES')) {
$cipher = new Crypt_AES(CRYPT_AES_MODE_CFB);
$cipher->setKeyLength(256);
}
break;
}
if(!$cipher) return array(NULL, NULL, NULL); // Unsupported cipher
if(!isset($key_bytes)) $key_bytes = $cipher->key_size;
if(!isset($key_block_bytes)) $key_block_bytes = $cipher->block_size;
return array($cipher, $key_bytes, $key_block_bytes);
}
public static function getEncryptedData($m) {
foreach($m as $p) {
if($p instanceof OpenPGP_EncryptedDataPacket) return $p;
}
throw new Exception("Can only decrypt EncryptedDataPacket");
}
public static function checksum($s) {
$mkChk = 0;
for($i = 0; $i < strlen($s); $i++) {
$mkChk = ($mkChk + ord($s{$i})) % 65536;
}
return $mkChk;
}
}

View file

@ -0,0 +1,31 @@
<?php
if(function_exists('mcrypt_encrypt') && defined('MCRYPT_MODE_CFB')) {
class MCryptWrapper {
public $cipher, $key, $iv, $key_size, $block_size;
function __construct($cipher) {
$this->cipher = $cipher;
$this->key_size = mcrypt_module_get_algo_key_size($cipher);
$this->block_size = mcrypt_module_get_algo_block_size($cipher);
$this->iv = str_repeat("\0", mcrypt_get_iv_size($cipher, 'ncfb'));
}
function setKey($key) {
$this->key = $key;
}
function setIV($iv) {
$this->iv = $iv;
}
function encrypt($data) {
return mcrypt_encrypt($this->cipher, $this->key, $data, 'ncfb', $this->iv);
}
function decrypt($data) {
return mcrypt_decrypt($this->cipher, $this->key, $data, 'ncfb', $this->iv);
}
}
}