Skip to content

Commit

Permalink
Add support for encrypt and sign/encrypt MIME parts
Browse files Browse the repository at this point in the history
  • Loading branch information
slusarz committed Apr 16, 2015
1 parent a382094 commit b6672b4
Show file tree
Hide file tree
Showing 2 changed files with 173 additions and 63 deletions.
134 changes: 80 additions & 54 deletions framework/Pgp/lib/Horde/Pgp/Mime.php
Expand Up @@ -104,93 +104,119 @@ public function signPart(
/**
* Encrypts a MIME part using PGP.
*
* @param Horde_Mime_Part $mime_part The object to encrypt.
* @param array $params The parameters required for
* encryption
* ({@see _encryptMessage()}).
* @param Horde_Mime_Part $part The object to encrypt.
* @param mixed $keys The public key(s) to use for encryption.
* @param array $opts Additional options:
* - nocompress: (boolean) If true, don't compress encrypted data.
*
* @return mixed A Horde_Mime_Part object that is encrypted according to
* RFC 3156.
* @return Horde_Mime_Part An encrypted object.
* @throws Horde_Pgp_Exception
*/
public function encryptMIMEPart($mime_part, $params = array())
public function encryptPart(
Horde_Mime_Part $part, $keys, array $opts = array()
)
{
$params = array_merge($params, array('type' => 'message'));

$signenc_body = $mime_part->toString(array(
'canonical' => true,
'headers' => true
));
$message_encrypt = $this->encrypt($signenc_body, $params);

/* Set up MIME Structure according to RFC 3156. */
$part = new Horde_Mime_Part();
$part->setType('multipart/encrypted');
$part->setHeaderCharset('UTF-8');
$part->setContentTypeParameter(
'protocol',
'application/pgp-encrypted'
$encrypted = $this->encrypt(
$part->toString(array(
'canonical' => true,
'headers' => true
)),
$keys,
$opts
);
$part->setDescription(

$base = $this->_encryptPart($encrypted);
$base->setHeaderCharset('UTF-8');
$base->setDescription(
Horde_Pgp_Translation::t("PGP Encrypted Data")
);
$part->setContents(
$base->setContents(
"This message is in MIME format and has been PGP encrypted.\n"
);

return $base;
}

/**
* Create the base MIME part used for encryption (RFC 3156 [4]).
*
* @param Horde_Pgp_Element_Message $encrypted Encrypted data.
*
* @return Horde_Mime_Part Base encrypted MIME part.
*/
protected function _encryptPart($encrypted)
{
$base = new Horde_Mime_Part();
$base->setType('multipart/encrypted');
$base->setHeaderCharset('UTF-8');
$base->setContentTypeParameter(
'protocol',
'application/pgp-encrypted'
);

$part1 = new Horde_Mime_Part();
$part1->setType('application/pgp-encrypted');
$part1->setCharset(null);
$part1->setContents("Version: 1\n", array('encoding' => '7bit'));
$part->addPart($part1);
$base->addPart($part1);

$part2 = new Horde_Mime_Part();
$part2->setType('application/octet-stream');
$part2->setCharset(null);
$part2->setContents($message_encrypt, array('encoding' => '7bit'));
$part2->setContents(strval($encrypted), array('encoding' => '7bit'));
$part2->setDisposition('inline');
$part->addPart($part2);
$base->addPart($part2);

return $part;
return $base;
}

/**
* Signs and encrypts a MIME part using PGP.
*
* @param Horde_Mime_Part $mime_part The object to sign and encrypt.
* @param array $sign_params The parameters required for
* signing
* ({@see _encryptSignature()}).
* @param array $encrypt_params The parameters required for
* encryption
* ({@see _encryptMessage()}).
* @param Horde_Mime_Part $part The part to sign and encrypt.
* @param mixed $privkey The private key to use for signing (must
* be decrypted).
* @param mixed $pubkeys The public keys to use for encryption.
* @param array $opts Additional options:
* - nocompress: (boolean) If true, don't compress signed/encrypted
* data.
*
* @return mixed A Horde_Mime_Part object that is signed and encrypted
* according to RFC 3156.
* @return Horde_Mime_Part A signed and encrypted part.
* @throws Horde_Pgp_Exception
*/
public function signAndEncryptMIMEPart($mime_part, $sign_params = array(),
$encrypt_params = array())
public function signAndEncryptPart(
Horde_Mime_Part $part, $privkey, $pubkeys, array $opts = array()
)
{
/* RFC 3156 requires that the entire signed message be encrypted. We
* need to explicitly call using Horde_Pgp_Pgp:: because we don't
* know whether a subclass has extended these methods. */
$part = $this->signMIMEPart($mime_part, $sign_params);
$part = $this->encryptMIMEPart($part, $encrypt_params);
$part->setContents(
"This message is in MIME format and has been PGP signed and encrypted.\n"
/* We use the combined method of sign & encryption in a single
* OpenPGP packet (RFC 3156 [6.2]). */
$signed = $this->sign(
$part->toString(array(
'canonical' => true,
'headers' => true
)),
$privkey,
$opts
);

$encrypted = $this->encrypt(
$signed->message,
$pubkeys,
array_merge($opts, array(
'nocompress' => true
))
);

$part->setCharset($this->_params['email_charset']);
$part->setDescription(
Horde_String::convertCharset(
Horde_Pgp_Translation::t("PGP Signed/Encrypted Data"),
'UTF-8',
$this->_params['email_charset']
)
$base = $this->_encryptPart($encrypted);
$base->setHeaderCharset('UTF-8');
$base->setDescription(
Horde_Pgp_Translation::t("PGP Signed/Encrypted Data")
);
$base->setContents(
"This message is in MIME format and has been PGP signed and encrypted.\n"
);

return $part;
return $base;
}

}
102 changes: 93 additions & 9 deletions framework/Pgp/test/Horde/Pgp/MimeTest.php
Expand Up @@ -25,18 +25,26 @@
*/
class Horde_Pgp_MimeTest extends PHPUnit_Framework_TestCase
{
public function testSignMimePart()
private $key;
private $part;
private $pgp_mime;

protected function setUp()
{
$part = new Horde_Mime_Part();
$part->setType('text/plain');
$part->setContents('Foo Bar.');
$this->part = new Horde_Mime_Part();
$this->part->setType('text/plain');
$this->part->setContents('Foo Bar.');

$key = Horde_Pgp_Element_PrivateKey::create(
$this->key = Horde_Pgp_Element_PrivateKey::create(
file_get_contents(__DIR__ . '/fixtures/pgp_private_rsa.txt')
)->getUnencryptedKey('Secret');

$pgp_mime = new Horde_Pgp_Mime();
$signed = $pgp_mime->signPart($part, $key);
$this->pgp_mime = new Horde_Pgp_Mime();
}

public function testSignPart()
{
$signed = $this->pgp_mime->signPart($this->part, $this->key);

$this->assertEquals(
'multipart/signed',
Expand Down Expand Up @@ -71,13 +79,13 @@ public function testSignMimePart()
$detach_sig->getContents()
);

$result = $pgp_mime->verifyDetached(
$result = $this->pgp_mime->verifyDetached(
$signed['1']->toString(array(
'canonical' => true,
'headers' => true
)),
$detach_sig->getContents(),
$key->getPublicKey()
$this->key->getPublicKey()
);

$this->assertTrue(isset($result[0][1][0]));
Expand All @@ -87,4 +95,80 @@ public function testSignMimePart()
);
}

public function testEncryptPart()
{
$encrypted = $this->pgp_mime->encryptPart(
$this->part,
$this->key->getPublicKey()
);

$this->_testEncryptPart($encrypted);
}

protected function _testEncryptPart($encrypted)
{
$this->assertEquals(
'multipart/encrypted',
$encrypted->getType()
);

$this->assertEquals(
'application/pgp-encrypted',
$encrypted->getContentTypeParameter('protocol')
);

$this->assertEquals(
2,
count($encrypted->getParts())
);

$encrypted->buildMimeIds();
$version = $encrypted['1'];
$data = $encrypted['2'];

$this->assertEquals(
'application/pgp-encrypted',
$version->getType()
);

$this->assertEquals(
'Version: 1',
rtrim($version->getContents())
);

$this->assertEquals(
'application/octet-stream',
$data->getType()
);

$this->assertStringStartsWith(
'-----BEGIN PGP MESSAGE-----',
$data->getContents()
);

$result = $this->pgp_mime->decrypt(
$data->toString(array(
'canonical' => true,
'headers' => true
)),
$this->key
);

$this->assertInstanceOf(
'Horde_Pgp_Element_Message',
$result
);
}

public function testSignAndEncryptPart()
{
$result = $this->pgp_mime->signAndEncryptPart(
$this->part,
$this->key,
$this->key->getPublicKey()
);

$this->_testEncryptPart($result);
}

}

0 comments on commit b6672b4

Please sign in to comment.