Overview

Packages

  • Crypt

Classes

  • Horde_Crypt
  • Horde_Crypt_Exception
  • Horde_Crypt_Pgp
  • Horde_Crypt_Smime
  • Horde_Crypt_Translation
  • Overview
  • Package
  • Class
  • Tree
  1: <?php
  2: /**
  3:  * Library to interact with the OpenSSL library and implement S/MIME.
  4:  *
  5:  * Copyright 2002-2012 Horde LLC (http://www.horde.org/)
  6:  *
  7:  * See the enclosed file COPYING for license information (LGPL). If you
  8:  * did not receive this file, see http://www.horde.org/licenses/lgpl21.
  9:  *
 10:  * @author   Mike Cochrane <mike@graftonhall.co.nz>
 11:  * @author   Michael Slusarz <slusarz@horde.org>
 12:  * @category Horde
 13:  * @license  http://www.horde.org/licenses/lgpl21 LGPL 2.1
 14:  * @package  Crypt
 15:  */
 16: class Horde_Crypt_Smime extends Horde_Crypt
 17: {
 18:     /**
 19:      * Verify a passphrase for a given private key.
 20:      *
 21:      * @param string $private_key  The user's private key.
 22:      * @param string $passphrase   The user's passphrase.
 23:      *
 24:      * @return boolean  Returns true on valid passphrase, false on invalid
 25:      *                  passphrase.
 26:      */
 27:     public function verifyPassphrase($private_key, $passphrase)
 28:     {
 29:         $res = is_null($passphrase)
 30:             ? openssl_pkey_get_private($private_key)
 31:             : openssl_pkey_get_private($private_key, $passphrase);
 32: 
 33:         return is_resource($res);
 34:     }
 35: 
 36:     /**
 37:      * Encrypt text using S/MIME.
 38:      *
 39:      * @param string $text   The text to be encrypted.
 40:      * @param array $params  The parameters needed for encryption.
 41:      *                       See the individual _encrypt*() functions for
 42:      *                       the parameter requirements.
 43:      *
 44:      * @return string  The encrypted message.
 45:      * @throws Horde_Crypt_Exception
 46:      */
 47:     public function encrypt($text, $params = array())
 48:     {
 49:         /* Check for availability of OpenSSL PHP extension. */
 50:         $this->checkForOpenSSL();
 51: 
 52:         if (isset($params['type'])) {
 53:             if ($params['type'] === 'message') {
 54:                 return $this->_encryptMessage($text, $params);
 55:             } elseif ($params['type'] === 'signature') {
 56:                 return $this->_encryptSignature($text, $params);
 57:             }
 58:         }
 59:     }
 60: 
 61:     /**
 62:      * Decrypt text via S/MIME.
 63:      *
 64:      * @param string $text   The text to be smime decrypted.
 65:      * @param array $params  The parameters needed for decryption.
 66:      *                       See the individual _decrypt*() functions for
 67:      *                       the parameter requirements.
 68:      *
 69:      * @return string  The decrypted message.
 70:      * @throws Horde_Crypt_Exception
 71:      */
 72:     public function decrypt($text, $params = array())
 73:     {
 74:         /* Check for availability of OpenSSL PHP extension. */
 75:         $this->checkForOpenSSL();
 76: 
 77:         if (isset($params['type'])) {
 78:             if ($params['type'] === 'message') {
 79:                 return $this->_decryptMessage($text, $params);
 80:             } elseif (($params['type'] === 'signature') ||
 81:                       ($params['type'] === 'detached-signature')) {
 82:                 return $this->_decryptSignature($text, $params);
 83:             }
 84:         }
 85:     }
 86: 
 87:     /**
 88:      * Verify a signature using via S/MIME.
 89:      *
 90:      * @param string $text  The multipart/signed data to be verified.
 91:      * @param mixed $certs  Either a single or array of root certificates.
 92:      *
 93:      * @return stdClass  Object with the following elements:
 94:      * <pre>
 95:      * cert - (string) The certificate of the signer stored in the message (in
 96:      *        PEM format).
 97:      * email - (string) The email of the signing person.
 98:      * msg - (string) Status string.
 99:      * verify - (boolean) True if certificate was verified.
100:      * </pre>
101:      * @throws Horde_Crypt_Exception
102:      */
103:     public function verify($text, $certs)
104:     {
105:         /* Check for availability of OpenSSL PHP extension. */
106:         $this->checkForOpenSSL();
107: 
108:         /* Create temp files for input/output. */
109:         $input = $this->_createTempFile('horde-smime');
110:         $output = $this->_createTempFile('horde-smime');
111: 
112:         /* Write text to file */
113:         file_put_contents($input, $text);
114:         unset($text);
115: 
116:         $root_certs = array();
117:         if (!is_array($certs)) {
118:             $certs = array($certs);
119:         }
120:         foreach ($certs as $file) {
121:             if (file_exists($file)) {
122:                 $root_certs[] = $file;
123:             }
124:         }
125: 
126:         $ob = new stdClass;
127: 
128:         if (!empty($root_certs) &&
129:             (openssl_pkcs7_verify($input, 0, $output, $root_certs) === true)) {
130:             /* Message verified */
131:             $ob->msg = Horde_Crypt_Translation::t("Message verified successfully.");
132:             $ob->verify = true;
133:         } else {
134:             /* Try again without verfying the signer's cert */
135:             $result = openssl_pkcs7_verify($input, PKCS7_NOVERIFY, $output);
136: 
137:             if ($result === -1) {
138:                 throw new Horde_Crypt_Exception(Horde_Crypt_Translation::t("Verification failed - an unknown error has occurred."));
139:             } elseif ($result === false) {
140:                 throw new Horde_Crypt_Exception(Horde_Crypt_Translation::t("Verification failed - this message may have been tampered with."));
141:             }
142: 
143:             $ob->msg = Horde_Crypt_Translation::t("Message verified successfully but the signer's certificate could not be verified.");
144:             $ob->verify = false;
145:         }
146: 
147:         $ob->cert = file_get_contents($output);
148:         $ob->email = $this->getEmailFromKey($ob->cert);
149: 
150:         return $ob;
151:     }
152: 
153:     /**
154:      * Extract the contents from signed S/MIME data.
155:      *
156:      * @param string $data     The signed S/MIME data.
157:      * @param string $sslpath  The path to the OpenSSL binary.
158:      *
159:      * @return string  The contents embedded in the signed data.
160:      * @throws Horde_Crypt_Exception
161:      */
162:     public function extractSignedContents($data, $sslpath)
163:     {
164:         /* Check for availability of OpenSSL PHP extension. */
165:         $this->checkForOpenSSL();
166: 
167:         /* Create temp files for input/output. */
168:         $input = $this->_createTempFile('horde-smime');
169:         $output = $this->_createTempFile('horde-smime');
170: 
171:         /* Write text to file. */
172:         file_put_contents($input, $data);
173:         unset($data);
174: 
175:         exec($sslpath . ' smime -verify -noverify -nochain -in ' . $input . ' -out ' . $output);
176: 
177:         $ret = file_get_contents($output);
178:         if ($ret) {
179:             return $ret;
180:         }
181: 
182:         throw new Horde_Crypt_Exception(Horde_Crypt_Translation::t("OpenSSL error: Could not extract data from signed S/MIME part."));
183:     }
184: 
185:     /**
186:      * Sign a MIME part using S/MIME. This produces S/MIME Version 3.2
187:      * compatible data (see RFC 5751 [3.4]).
188:      *
189:      * @param Horde_Mime_Part $mime_part  The object to sign.
190:      * @param array $params               The parameters required for signing.
191:      *
192:      * @return Horde_Mime_Part  A signed MIME part object.
193:      * @throws Horde_Crypt_Exception
194:      */
195:     public function signMIMEPart($mime_part, $params)
196:     {
197:         /* Sign the part as a message */
198:         $message = $this->encrypt($mime_part->toString(array('headers' => true, 'canonical' => true)), $params);
199: 
200:         /* Break the result into its components */
201:         $mime_message = Horde_Mime_Part::parseMessage($message, array('forcemime' => true));
202: 
203:         $smime_sign = $mime_message->getPart('2');
204:         $smime_sign->setDescription(Horde_Crypt_Translation::t("S/MIME Cryptographic Signature"));
205:         $smime_sign->setTransferEncoding('base64', array('send' => true));
206: 
207:         $smime_part = new Horde_Mime_Part();
208:         $smime_part->setType('multipart/signed');
209:         $smime_part->setContents("This is a cryptographically signed message in MIME format.\n");
210:         $smime_part->setContentTypeParameter('protocol', 'application/pkcs7-signature');
211:         // Per RFC 5751 [3.4.3.2], 'sha1' has been deprecated for 'sha-1'.
212:         $smime_part->setContentTypeParameter('micalg', 'sha-1');
213:         $smime_part->addPart($mime_part);
214:         $smime_part->addPart($smime_sign);
215: 
216:         return $smime_part;
217:     }
218: 
219:     /**
220:      * Encrypt a MIME part using S/MIME. This produces S/MIME Version 3.2
221:      * compatible data (see RFC 5751 [3.3]).
222:      *
223:      * @param Horde_Mime_Part $mime_part  The object to encrypt.
224:      * @param array $params               The parameters required for
225:      *                                    encryption.
226:      *
227:      * @return Horde_Mime_Part  An encrypted MIME part object.
228:      * @throws Horde_Crypt_Exception
229:      */
230:     public function encryptMIMEPart($mime_part, $params = array())
231:     {
232:         /* Sign the part as a message */
233:         $message = $this->encrypt($mime_part->toString(array('headers' => true, 'canonical' => true)), $params);
234: 
235:         $msg = new Horde_Mime_Part();
236:         $msg->setCharset($this->_params['email_charset']);
237:         $msg->setHeaderCharset('UTF-8');
238:         $msg->setDescription(Horde_Crypt_Translation::t("S/MIME Encrypted Message"));
239:         $msg->setDisposition('inline');
240:         $msg->setType('application/pkcs7-mime');
241:         $msg->setContentTypeParameter('smime-type', 'enveloped-data');
242:         $msg->setContents(substr($message, strpos($message, "\n\n") + 2), array('encoding' => 'base64'));
243: 
244:         return $msg;
245:     }
246: 
247:     /**
248:      * Encrypt a message in S/MIME format using a public key.
249:      *
250:      * @param string $text   The text to be encrypted.
251:      * @param array $params  The parameters needed for encryption.
252:      * <pre>
253:      * Parameters:
254:      * ===========
255:      * 'type'   => 'message' (REQUIRED)
256:      * 'pubkey' => public key (REQUIRED)
257:      * </pre>
258:      *
259:      * @return string  The encrypted message.
260:      * @throws Horde_Crypt_Exception
261:      */
262:     protected function _encryptMessage($text, $params)
263:     {
264:         /* Check for required parameters. */
265:         if (!isset($params['pubkey'])) {
266:             throw new Horde_Crypt_Exception(Horde_Crypt_Translation::t("A public S/MIME key is required to encrypt a message."));
267:         }
268: 
269:         /* Create temp files for input/output. */
270:         $input = $this->_createTempFile('horde-smime');
271:         $output = $this->_createTempFile('horde-smime');
272: 
273:         /* Store message in file. */
274:         file_put_contents($input, $text);
275:         unset($text);
276: 
277:         /* Encrypt the document. */
278:         $ciphers = array(
279:             OPENSSL_CIPHER_3DES,
280:             OPENSSL_CIPHER_DES,
281:             OPENSSL_CIPHER_RC2_128,
282:             OPENSSL_CIPHER_RC2_64,
283:             OPENSSL_CIPHER_RC2_40
284:         );
285: 
286:         foreach ($ciphers as $val) {
287:             if (openssl_pkcs7_encrypt($input, $output, $params['pubkey'], array(), 0, $val)) {
288:                 $result = file_get_contents($output);
289:                 if (!empty($result)) {
290:                     return $this->_fixContentType($result, 'encrypt');
291:                 }
292:             }
293:         }
294: 
295:         throw new Horde_Crypt_Exception(Horde_Crypt_Translation::t("Could not S/MIME encrypt message."));
296:     }
297: 
298:     /**
299:      * Sign a message in S/MIME format using a private key.
300:      *
301:      * @param string $text   The text to be signed.
302:      * @param array $params  The parameters needed for signing.
303:      * <pre>
304:      * Parameters:
305:      * ===========
306:      * 'certs'       =>  Additional signing certs (Optional)
307:      * 'passphrase'  =>  Passphrase for key (REQUIRED)
308:      * 'privkey'     =>  Private key (REQUIRED)
309:      * 'pubkey'      =>  Public key (REQUIRED)
310:      * 'sigtype'     =>  Determine the signature type to use. (Optional)
311:      *                   'cleartext'  --  Make a clear text signature
312:      *                   'detach'     --  Make a detached signature (DEFAULT)
313:      * 'type'        =>  'signature' (REQUIRED)
314:      * </pre>
315:      *
316:      * @return string  The signed message.
317:      * @throws Horde_Crypt_Exception
318:      */
319:     protected function _encryptSignature($text, $params)
320:     {
321:         /* Check for required parameters. */
322:         if (!isset($params['pubkey']) ||
323:             !isset($params['privkey']) ||
324:             !array_key_exists('passphrase', $params)) {
325:             throw new Horde_Crypt_Exception(Horde_Crypt_Translation::t("A public S/MIME key, private S/MIME key, and passphrase are required to sign a message."));
326:         }
327: 
328:         /* Create temp files for input/output/certificates. */
329:         $input = $this->_createTempFile('horde-smime');
330:         $output = $this->_createTempFile('horde-smime');
331:         $certs = $this->_createTempFile('horde-smime');
332: 
333:         /* Store message in temporary file. */
334:         file_put_contents($input, $text);
335:         unset($text);
336: 
337:         /* Store additional certs in temporary file. */
338:         if (!empty($params['certs'])) {
339:             file_put_contents($certs, $params['certs']);
340:         }
341: 
342:         /* Determine the signature type to use. */
343:         $flags = (isset($params['sigtype']) && ($params['sigtype'] == 'cleartext'))
344:             ? PKCS7_TEXT
345:             : PKCS7_DETACHED;
346: 
347:         $privkey = (is_null($params['passphrase'])) ? $params['privkey'] : array($params['privkey'], $params['passphrase']);
348: 
349:         if (empty($params['certs'])) {
350:             $res = openssl_pkcs7_sign($input, $output, $params['pubkey'], $privkey, array(), $flags);
351:         } else {
352:             $res = openssl_pkcs7_sign($input, $output, $params['pubkey'], $privkey, array(), $flags, $certs);
353:         }
354: 
355:         if (!$res) {
356:             throw new Horde_Crypt_Exception(Horde_Crypt_Translation::t("Could not S/MIME sign message."));
357:         }
358: 
359:         /* Output from openssl_pkcs7_sign may contain both \n and \r\n EOLs.
360:          * Canonicalize to \r\n. */
361:         $fp = fopen($output, 'r');
362:         stream_filter_register('horde_eol', 'Horde_Stream_Filter_Eol');
363:         stream_filter_append($fp, 'horde_eol');
364:         $data = stream_get_contents($fp);
365:         fclose($fp);
366: 
367:         return $this->_fixContentType($data, 'signature');
368:     }
369: 
370:     /**
371:      * Decrypt an S/MIME encrypted message using a private/public keypair
372:      * and a passhprase.
373:      *
374:      * @param string $text   The text to be decrypted.
375:      * @param array $params  The parameters needed for decryption.
376:      * <pre>
377:      * Parameters:
378:      * ===========
379:      * 'type'        =>  'message' (REQUIRED)
380:      * 'pubkey'      =>  public key. (REQUIRED)
381:      * 'privkey'     =>  private key. (REQUIRED)
382:      * 'passphrase'  =>  Passphrase for Key. (REQUIRED)
383:      * </pre>
384:      *
385:      * @return string  The decrypted message.
386:      * @throws Horde_Crypt_Exception
387:      */
388:     protected function _decryptMessage($text, $params)
389:     {
390:         /* Check for required parameters. */
391:         if (!isset($params['pubkey']) ||
392:             !isset($params['privkey']) ||
393:             !array_key_exists('passphrase', $params)) {
394:             throw new Horde_Crypt_Exception(Horde_Crypt_Translation::t("A public S/MIME key, private S/MIME key, and passphrase are required to decrypt a message."));
395:         }
396: 
397:         /* Create temp files for input/output. */
398:         $input = $this->_createTempFile('horde-smime');
399:         $output = $this->_createTempFile('horde-smime');
400: 
401:         /* Store message in file. */
402:         file_put_contents($input, $text);
403:         unset($text);
404: 
405:         $privkey = is_null($params['passphrase'])
406:             ? $params['privkey']
407:             : array($params['privkey'], $params['passphrase']);
408:         if (openssl_pkcs7_decrypt($input, $output, $params['pubkey'], $privkey)) {
409:             return file_get_contents($output);
410:         }
411: 
412:         throw new Horde_Crypt_Exception(Horde_Crypt_Translation::t("Could not decrypt S/MIME data."));
413:     }
414: 
415:     /**
416:      * Sign and Encrypt a MIME part using S/MIME.
417:      *
418:      * @param Horde_Mime_Part $mime_part   The object to sign and encrypt.
419:      * @param array $sign_params           The parameters required for
420:      *                                     signing. @see _encryptSignature().
421:      * @param array $encrypt_params        The parameters required for
422:      *                                     encryption.
423:      *                                     @see _encryptMessage().
424:      *
425:      * @return mixed  A Horde_Mime_Part object that is signed and encrypted.
426:      * @throws Horde_Crypt_Exception
427:      */
428:     public function signAndEncryptMIMEPart($mime_part, $sign_params = array(),
429:                                            $encrypt_params = array())
430:     {
431:         $part = $this->signMIMEPart($mime_part, $sign_params);
432:         return $this->encryptMIMEPart($part, $encrypt_params);
433:     }
434: 
435:     /**
436:      * Convert a PEM format certificate to readable HTML version.
437:      *
438:      * @param string $cert   PEM format certificate.
439:      *
440:      * @return string  HTML detailing the certificate.
441:      */
442:     public function certToHTML($cert)
443:     {
444:         $fieldnames = array(
445:             /* Common Fields */
446:             'description' => Horde_Crypt_Translation::t("Description"),
447:             'emailAddress' => Horde_Crypt_Translation::t("Email Address"),
448:             'commonName' => Horde_Crypt_Translation::t("Common Name"),
449:             'organizationName' => Horde_Crypt_Translation::t("Organisation"),
450:             'organizationalUnitName' => Horde_Crypt_Translation::t("Organisational Unit"),
451:             'countryName' => Horde_Crypt_Translation::t("Country"),
452:             'stateOrProvinceName' => Horde_Crypt_Translation::t("State or Province"),
453:             'localityName' => Horde_Crypt_Translation::t("Location"),
454:             'streetAddress' => Horde_Crypt_Translation::t("Street Address"),
455:             'telephoneNumber' => Horde_Crypt_Translation::t("Telephone Number"),
456:             'surname' => Horde_Crypt_Translation::t("Surname"),
457:             'givenName' => Horde_Crypt_Translation::t("Given Name"),
458: 
459:             /* X590v3 Extensions */
460:             'exendedtKeyUsage' => Horde_Crypt_Translation::t("X509v3 Extended Key Usage"),
461:             'basicConstraints' => Horde_Crypt_Translation::t("X509v3 Basic Constraints"),
462:             'subjectAltName' => Horde_Crypt_Translation::t("X509v3 Subject Alternative Name"),
463:             'subjectKeyIdentifier' => Horde_Crypt_Translation::t("X509v3 Subject Key Identifier"),
464:             'certificatePolicies' => Horde_Crypt_Translation::t("Certificate Policies"),
465:             'crlDistributionPoints' => Horde_Crypt_Translation::t("CRL Distribution Points"),
466:             'keyUsage' => Horde_Crypt_Translation::t("Key Usage")
467:         );
468: 
469:         $details = $this->parseCert($cert);
470: 
471:         $text = '<pre class="fixed">';
472: 
473:         /* Subject (a/k/a Certificate Owner) */
474:         $text .= "<strong>" . Horde_Crypt_Translation::t("Certificate Owner") . ":</strong>\n";
475: 
476:         foreach ($details['subject'] as $key => $value) {
477:             $value = $this->_implodeValues($value);
478:             $text .= isset($fieldnames[$key])
479:                 ? sprintf("&nbsp;&nbsp;%s: %s\n", $fieldnames[$key], $value)
480:                 : sprintf("&nbsp;&nbsp;*%s: %s\n", $key, $value);
481:         }
482:         $text .= "\n";
483: 
484:         /* Issuer */
485:         $text .= "<strong>" . Horde_Crypt_Translation::t("Issuer") . ":</strong>\n";
486: 
487:         foreach ($details['issuer'] as $key => $value) {
488:             $value = $this->_implodeValues($value);
489:             $text .= isset($fieldnames[$key])
490:                 ? sprintf("&nbsp;&nbsp;%s: %s\n", $fieldnames[$key], $value)
491:                 : sprintf("&nbsp;&nbsp;*%s: %s\n", $key, $value);
492:         }
493:         $text .= "\n";
494: 
495:         /* Dates  */
496:         $text .= "<strong>" . Horde_Crypt_Translation::t("Validity") . ":</strong>\n" .
497:             sprintf("&nbsp;&nbsp;%s: %s\n", Horde_Crypt_Translation::t("Not Before"), strftime("%x %X", $details['validity']['notbefore']->getTimestamp())) .
498:             sprintf("&nbsp;&nbsp;%s: %s\n", Horde_Crypt_Translation::t("Not After"), strftime("%x %X", $details['validity']['notafter']->getTimestamp())) .
499:             "\n";
500: 
501:         /* X509v3 extensions */
502:         if (!empty($details['extensions'])) {
503:             $text .= "<strong>" . Horde_Crypt_Translation::t("X509v3 extensions") . ":</strong>\n";
504: 
505:             foreach ($details['extensions'] as $key => $value) {
506:                 $value = $this->_implodeValues($value, 6);
507:                 $text .= isset($fieldnames[$key])
508:                     ? sprintf("&nbsp;&nbsp;%s:\n&nbsp;&nbsp;&nbsp;&nbsp;%s\n", $fieldnames[$key], trim($value))
509:                     : sprintf("&nbsp;&nbsp;*%s:\n&nbsp;&nbsp;&nbsp;&nbsp;%s\n", $key, trim($value));
510:             }
511: 
512:             $text .= "\n";
513:         }
514: 
515:         /* Certificate Details */
516:         $text .= "<strong>" . Horde_Crypt_Translation::t("Certificate Details") . ":</strong>\n" .
517:             sprintf("&nbsp;&nbsp;%s: %d\n", Horde_Crypt_Translation::t("Version"), $details['version']) .
518:             sprintf("&nbsp;&nbsp;%s: %d\n", Horde_Crypt_Translation::t("Serial Number"), $details['serialNumber']);
519: 
520:         return $text . "\n</pre>";
521:     }
522: 
523:     /**
524:      * Formats a multi-value cert field.
525:      *
526:      * @param array|string $value  A cert field value.
527:      * @param integer $indent      The indention level.
528:      *
529:      * @return string  The formatted cert field value(s).
530:      */
531:     protected function _implodeValues($value, $indent = 4)
532:     {
533:         if (is_array($value)) {
534:             $value = "\n" . str_repeat('&nbsp;', $indent)
535:                 . implode("\n" . str_repeat('&nbsp;', $indent), $value);
536:         }
537:         return $value;
538:     }
539: 
540:     /**
541:      * Extract the contents of a PEM format certificate to an array.
542:      *
543:      * @param string $cert  PEM format certificate.
544:      *
545:      * @return array  All extractable information about the certificate.
546:      */
547:     public function parseCert($cert)
548:     {
549:         $data = openssl_x509_parse($cert, false);
550:         if (!$data) {
551:             throw new Horde_Crypt_Exception(sprintf(Horde_Crypt_Translation::t("Error parsing S/MIME certficate: %s"), openssl_error_string()));
552:         }
553: 
554:         $details = array(
555:             'extensions' => $data['extensions'],
556:             'issuer' => $data['issuer'],
557:             'serialNumber' => $data['serialNumber'],
558:             'subject' => $data['subject'],
559:             'validity' => array(
560:                 'notafter' => new DateTime('@' . $data['validTo_time_t']),
561:                 'notbefore' => new DateTime('@' . $data['validFrom_time_t'])
562:             ),
563:             'version' => $data['version']
564:         );
565: 
566:         // Add additional fields for BC purposes.
567:         $details['certificate'] = $details;
568: 
569:         $bc_changes = array(
570:             'emailAddress' => 'Email',
571:             'commonName' => 'CommonName',
572:             'organizationName' => 'Organisation',
573:             'organizationalUnitName' => 'OrganisationalUnit',
574:             'countryName' => 'Country',
575:             'stateOrProvinceName' => 'StateOrProvince',
576:             'localityName' => 'Location',
577:             'streetAddress' => 'StreetAddress',
578:             'telephoneNumber' => 'TelephoneNumber',
579:             'surname' => 'Surname',
580:             'givenName' => 'GivenName'
581:         );
582:         foreach (array('issuer', 'subject') as $val) {
583:             foreach (array_keys($details[$val]) as $key) {
584:                 if (isset($bc_changes[$key])) {
585:                     $details['certificate'][$val][$bc_changes[$key]] = $details[$val][$key];
586:                     unset($details['certificate'][$val][$key]);
587:                 }
588:             }
589:         }
590: 
591:         return $details;
592:     }
593: 
594:     /**
595:      * Decrypt an S/MIME signed message using a public key.
596:      *
597:      * @param string $text   The text to be verified.
598:      * @param array $params  The parameters needed for verification.
599:      *
600:      * @return string  The verification message.
601:      * @throws Horde_Crypt_Exception
602:      */
603:     protected function _decryptSignature($text, $params)
604:     {
605:         throw new Horde_Crypt_Exception('_decryptSignature() ' . Horde_Crypt_Translation::t("not yet implemented"));
606:     }
607: 
608:     /**
609:      * Check for the presence of the OpenSSL extension to PHP.
610:      *
611:      * @throws Horde_Crypt_Exception
612:      */
613:     public function checkForOpenSSL()
614:     {
615:         if (!Horde_Util::extensionExists('openssl')) {
616:             throw new Horde_Crypt_Exception(Horde_Crypt_Translation::t("The openssl module is required for the Horde_Crypt_Smime:: class."));
617:         }
618:     }
619: 
620:     /**
621:      * Extract the email address from a public key.
622:      *
623:      * @param string $key  The public key.
624:      *
625:      * @return mixed  Returns the first email address found, or null if
626:      *                there are none.
627:      */
628:     public function getEmailFromKey($key)
629:     {
630:         $key_info = openssl_x509_parse($key);
631:         if (!is_array($key_info)) {
632:             return null;
633:         }
634: 
635:         if (isset($key_info['subject'])) {
636:             if (isset($key_info['subject']['Email'])) {
637:                 return $key_info['subject']['Email'];
638:             } elseif (isset($key_info['subject']['emailAddress'])) {
639:                 return $key_info['subject']['emailAddress'];
640:             }
641:         }
642: 
643:         // Check subjectAltName per http://www.ietf.org/rfc/rfc3850.txt
644:         if (isset($key_info['extensions']['subjectAltName'])) {
645:             $names = preg_split('/\s*,\s*/', $key_info['extensions']['subjectAltName'], -1, PREG_SPLIT_NO_EMPTY);
646:             foreach ($names as $name) {
647:                 if (strpos($name, ':') === false) {
648:                     continue;
649:                 }
650:                 list($kind, $value) = explode(':', $name, 2);
651:                 if (Horde_String::lower($kind) == 'email') {
652:                     return $value;
653:                 }
654:             }
655:         }
656: 
657:         return null;
658:     }
659: 
660:     /**
661:      * Convert a PKCS 12 encrypted certificate package into a private key,
662:      * public key, and any additional keys.
663:      *
664:      * @param string $text   The PKCS 12 data.
665:      * @param array $params  The parameters needed for parsing.
666:      * <pre>
667:      * Parameters:
668:      * ===========
669:      * 'sslpath' => The path to the OpenSSL binary. (REQUIRED)
670:      * 'password' => The password to use to decrypt the data. (Optional)
671:      * 'newpassword' => The password to use to encrypt the private key.
672:      *                  (Optional)
673:      * </pre>
674:      *
675:      * @return stdClass  An object.
676:      *                   'private' -  The private key in PEM format.
677:      *                   'public'  -  The public key in PEM format.
678:      *                   'certs'   -  An array of additional certs.
679:      * @throws Horde_Crypt_Exception
680:      */
681:     public function parsePKCS12Data($pkcs12, $params)
682:     {
683:         /* Check for availability of OpenSSL PHP extension. */
684:         $this->checkForOpenSSL();
685: 
686:         if (!isset($params['sslpath'])) {
687:             throw new Horde_Crypt_Exception(Horde_Crypt_Translation::t("No path to the OpenSSL binary provided. The OpenSSL binary is necessary to work with PKCS 12 data."));
688:         }
689:         $sslpath = escapeshellcmd($params['sslpath']);
690: 
691:         /* Create temp files for input/output. */
692:         $input = $this->_createTempFile('horde-smime');
693:         $output = $this->_createTempFile('horde-smime');
694: 
695:         $ob = new stdClass;
696: 
697:         /* Write text to file */
698:         file_put_contents($input, $pkcs12);
699:         unset($pkcs12);
700: 
701:         /* Extract the private key from the file first. */
702:         $cmdline = $sslpath . ' pkcs12 -in ' . $input . ' -out ' . $output . ' -nocerts';
703:         if (isset($params['password'])) {
704:             $cmdline .= ' -passin stdin';
705:             if (!empty($params['newpassword'])) {
706:                 $cmdline .= ' -passout stdin';
707:             } else {
708:                 $cmdline .= ' -nodes';
709:             }
710:         } else {
711:             $cmdline .= ' -nodes';
712:         }
713: 
714:         if ($fd = popen($cmdline, 'w')) {
715:             fwrite($fd, $params['password'] . "\n");
716:             if (!empty($params['newpassword'])) {
717:                 fwrite($fd, $params['newpassword'] . "\n");
718:             }
719:             pclose($fd);
720:         } else {
721:             throw new Horde_Crypt_Exception(Horde_Crypt_Translation::t("Error while talking to smime binary."));
722:         }
723: 
724:         $ob->private = trim(file_get_contents($output));
725:         if (empty($ob->private)) {
726:             throw new Horde_Crypt_Exception(Horde_Crypt_Translation::t("Password incorrect"));
727:         }
728: 
729:         /* Extract the client public key next. */
730:         $cmdline = $sslpath . ' pkcs12 -in ' . $input . ' -out ' . $output . ' -nokeys -clcerts';
731:         if (isset($params['password'])) {
732:             $cmdline .= ' -passin stdin';
733:         }
734: 
735:         if ($fd = popen($cmdline, 'w')) {
736:             fwrite($fd, $params['password'] . "\n");
737:             pclose($fd);
738:         } else {
739:             throw new Horde_Crypt_Exception(Horde_Crypt_Translation::t("Error while talking to smime binary."));
740:         }
741: 
742:         $ob->public = trim(file_get_contents($output));
743: 
744:         /* Extract the CA public key next. */
745:         $cmdline = $sslpath . ' pkcs12 -in ' . $input . ' -out ' . $output . ' -nokeys -cacerts';
746:         if (isset($params['password'])) {
747:             $cmdline .= ' -passin stdin';
748:         }
749: 
750:         if ($fd = popen($cmdline, 'w')) {
751:             fwrite($fd, $params['password'] . "\n");
752:             pclose($fd);
753:         } else {
754:             throw new Horde_Crypt_Exception(Horde_Crypt_Translation::t("Error while talking to smime binary."));
755:         }
756: 
757:         $ob->certs = trim(file_get_contents($output));
758: 
759:         return $ob;
760:     }
761: 
762:     /**
763:      * The Content-Type parameters PHP's openssl_pkcs7_* functions return are
764:      * deprecated.  Fix these headers to the correct ones (see RFC 2311).
765:      *
766:      * @param string $text  The PKCS7 data.
767:      * @param string $type  Is this 'message' or 'signature' data?
768:      *
769:      * @return string  The PKCS7 data with the correct Content-Type parameter.
770:      */
771:     protected function _fixContentType($text, $type)
772:     {
773:         if ($type == 'message') {
774:             $from = 'application/x-pkcs7-mime';
775:             $to = 'application/pkcs7-mime';
776:         } else {
777:             $from = 'application/x-pkcs7-signature';
778:             $to = 'application/pkcs7-signature';
779:         }
780:         return str_replace('Content-Type: ' . $from, 'Content-Type: ' . $to, $text);
781:     }
782: 
783: }
784: 
API documentation generated by ApiGen