1: <?php
2: /*
3: * Copyright (C) 2013 Bryan Nielsen - All Rights Reserved
4: *
5: * Author: Bryan Nielsen (bnielsen1965@gmail.com)
6: *
7: *
8: * This file is part of cryptUser.
9: * cryptUser is free software: you can redistribute it and/or modify
10: * it under the terms of the GNU General Public License as published by
11: * the Free Software Foundation, either version 3 of the License, or
12: * (at your option) any later version.
13: *
14: * cryptUser is distributed in the hope that it will be useful,
15: * but WITHOUT ANY WARRANTY; without even the implied warranty of
16: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17: * GNU General Public License for more details.
18: *
19: * You should have received a copy of the GNU General Public License
20: * along with cryptUser. If not, see <http://www.gnu.org/licenses/>.
21: */
22:
23: namespace BSN\CryptUser;
24: use Exception;
25:
26: /**
27: * Provides encryption and decryption methods using openssl.
28: *
29: * @author Bryan Nielsen, bnielsen1965@gmail.com
30: * @copyright Copyright 2013, Bryan Nielsen
31: */
32: class SSLKey {
33:
34: /**
35: * Class properties
36: */
37: private $passphrase; // pass phrase used to protect private key
38: private $privateKey; // passphrase protected private key in PEM format
39: private $certificate; // SSL certificate in PEM format
40: private $errors; // error messages
41:
42:
43: /**
44: * Object constructor
45: * @param string $phrase Passphrase to use with protected key.
46: * @param string $key A PEM encoded private key, NULL when creating a new key pair.
47: * @param string $cert A PEM encoded certificate, NULL when creating a new key pair.
48: * @param array $SSLParams Associative array of parameters used when creating a new
49: * key pair. The array is a combination of the Distinquished Name and configargs
50: * values. The array may include the following...
51: * 'ssl_key_bits' => '4096', // bit size of the encryption key
52: * 'private_key_type' => OPENSSL_KEYTYPE_RSA, // the openssl key type
53: * 'countryName' => 'US', // distinquished name country
54: * 'stateOrProvinceName' => 'Nevada', // distinquished name province
55: * 'localityName' => 'Las Vegas', // distinquished name locality
56: * 'organizationName' => 'SSLKey', // distinquished name organization
57: * 'commonName' => 'localhost', // distinquished name domain
58: * 'emailAddress' => 'admin@localhost' // distinquished name email
59: * @param array $CACert Associative array containing a certificate authority parameters
60: * used to sign a new key pair. If NULL then the new key pair will be self signed.
61: * The array must include the following elements...
62: * 'certificate' => CA certificate in PEM format,
63: * 'privateKey' => CA private key in PEM format,
64: * 'passPhrase' => CA passphrase.
65: */
66: function __construct($phrase = "", $key = NULL, $cert = NULL, $SSLParams = NULL, $CACert = NULL) {
67: // initialize error array
68: $this->errors = array();
69:
70: // if a key and cert were not passed then this is a new user, generate ssl keys
71: if ($key == NULL || $cert == NULL) {
72: // create a distinguished name using SSL parameters
73: $dn = $this->makeDN($SSLParams);
74:
75: // create the configargs array
76: $configArgs = $this->makeConfigArgs($SSLParams);// array();
77:
78: // generate a new key pair
79: $keyPair = openssl_pkey_new($configArgs);
80:
81: // export the private key in PEM format and encrypt with new user password
82: if (openssl_pkey_export($keyPair, $key, $phrase)) {
83: // generate a certificate signing request with the defined dn identity
84: if (($csr = openssl_csr_new($dn, $keyPair))) {
85: if (is_null($CACert)) {
86: // self sign the csr to generate the master certificate
87: $rawCert = openssl_csr_sign($csr, null, $keyPair, 0);
88: } else {
89: // certificate authority sisgned csr to generate a user certificate
90: $rawCert = openssl_csr_sign($csr, $CACert['certificate'], array($CACert['privateKey'], $CACert['passPhrase']), 0);
91: }
92:
93: if ($rawCert) {
94: // export certificate in PEM format
95: if (!openssl_x509_export($rawCert, $cert))
96: $this->errors[] = "Error exporting certificate in PEM format. ";
97: }
98: else $this->error[] = "Error signing certificate. ";
99: }
100: else $this->errors[] = "Error generating certificate signing request. ";
101: }
102: else $this->errors[] = "Error exporting private key. ";
103: }
104:
105: $this->privateKey = $key;
106: $this->passphrase = $phrase;
107: $this->certificate = $cert;
108: }
109:
110:
111: /**
112: * Decrypt the sealed package using this SSL key pair.
113: * @param string $package The package to decrypt.
114: * @param mixed $envelope The envelope for the package.
115: * @param string $phrase The passphrase to use as the pass phrase for decryption.
116: * @return mixed The decrypted package or FALSE if there was an error.
117: */
118: public function decryptPackage($package, $envelope, $phrase = NULL) {
119: $decrypted = NULL;
120:
121: // use the passphrase from this instance if not provided in arguments
122: if ($phrase == NULL) $phrase = $this->passphrase;
123:
124: // try to get the private key from the certificate
125: if (($key = openssl_get_privatekey($this->privateKey, $phrase)) === FALSE) {
126: $this->errors[] = 'Failed to get private key!';
127: return NULL;
128: }
129:
130: // try to decrypt the package
131: if ((openssl_open($package, $decrypted, $envelope, $key)) === FALSE) {
132: $this->errors[] = 'Failed to decrypt package!';
133: return NULL;
134: }
135:
136: return $decrypted;
137: }
138:
139:
140: /**
141: * Encrypt a package using this SSL key pair.
142: * @param mixed $package The package to encrypt.
143: * @return array An array containing the envelope and encrypted package or NULL if an error occurs.
144: */
145: public function encryptPackage($package) {
146: $encrypted = NULL;
147: $envelope = NULL;
148:
149: if (is_null($package)) {
150: $this->errors[] = 'No package provided to encrypt!';
151: return NULL;
152: }
153:
154: // try to get the public key from the certificate
155: if (($key = openssl_get_publickey($this->certificate)) === FALSE) {
156: $this->errors[] = 'Failed to get public key from certificate!';
157: return NULL;
158: }
159:
160: // try to encrypt the package
161: if ((openssl_seal($package, $encrypted, $envelope, array($key))) === FALSE) {
162: $this->errors[] = 'Failed to encrypt package!';
163: return NULL;
164: }
165:
166: // return the encrypted package and the envelope in an array
167: return array("package" => $encrypted, "envelope" => $envelope[0]);
168: }
169:
170:
171: /**
172: * Parses the passed string looking for a phrase using PEM style
173: * encoding. This is a custom PEM parameter for SSLKey.
174: * @param string $str The string to parse looking for the pass phrase component.
175: * @param boolean $includeWrapper (optional) Specify if the PEM wrapper should be returned with the phrase.
176: * @return string The discovered pass phrase or NULL if not found.
177: */
178: public static function parsePhrase($str, $includeWrapper = FALSE) {
179: if ($includeWrapper) {
180: $regEx = '/(-----BEGIN PHRASE-----.*-----END PHRASE-----)/msU';
181: }
182: else {
183: $regEx = '/-----BEGIN PHRASE-----(.*)-----END PHRASE-----/msU';
184: }
185: if (preg_match($regEx, $str, $ma, PREG_OFFSET_CAPTURE)) {
186: return trim($ma[1][0]);
187: }
188:
189: return NULL;
190: }
191:
192:
193: /**
194: * Parses the passed string looking for a certificate.
195: * @param string $str The string to parse looking for the certificate component.
196: * @return string The discovered certificate or NULL if not found.
197: */
198: public static function parseCertificate($str) {
199: if (preg_match('/(-----BEGIN CERTIFICATE-----.*-----END CERTIFICATE-----)/msU', $str, $ma, PREG_OFFSET_CAPTURE)) {
200: return trim($ma[1][0]);
201: }
202:
203: return NULL;
204: }
205:
206:
207: /**
208: * Parses the passed string looking for a private key.
209: * @param string $str The string to parse looking for the private key component.
210: * @return string The discovered private key or NULL if not found.
211: */
212: public static function parsePrivateKey($str) {
213: if (preg_match('/(-----BEGIN ENCRYPTED PRIVATE KEY-----.*-----END ENCRYPTED PRIVATE KEY-----)/msU', $str, $ma, PREG_OFFSET_CAPTURE)) {
214: return trim($ma[1][0]);
215: }
216:
217: return NULL;
218: }
219:
220:
221: /**
222: * Creates a random passphrase.
223: * @param integer $len The length of the generated pass phrase.
224: * @param boolean $alphnumeric Determines if the generated pass phrase includes only alphanumeric characters.
225: * @return string The generated pass phrase.
226: */
227: public static function makePhrase($len = 64, $alphanumeric = FALSE) {
228: // determine the character set to use
229: if ($alphanumeric === TRUE) {
230: $charlist = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
231: }
232: else
233: $charlist = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz!@#&*(),.{}[];:";
234: $phrase = "";
235:
236: // loop to create random phrase
237: do {
238: $phrase .= substr($charlist, mt_rand(0, strlen($charlist) - 1), 1);
239: } while (--$len > 0);
240:
241: return $phrase;
242: }
243:
244:
245: /**
246: * Retrieve the private key from this instance.
247: */
248: public function getPrivateKey() {
249: return $this->privateKey;
250: }
251:
252:
253: /**
254: * Retrieve the certificate from this instance.
255: */
256: public function getCertificate() {
257: return $this->certificate;
258: }
259:
260:
261: /**
262: * Retrieve the PEM encoded key (privateKey + certificate)
263: */
264: public function getKey() {
265: $key = $this->getPrivateKey();
266: $key .- $this->getCertificate();
267: return $key;
268: }
269:
270:
271: /**
272: * Retrieve a custom PEM encoded key with all needed components for encryption and decryption.
273: */
274: public function getFullKey() {
275: // assemble all key components into a single PEM string.
276: $key = "-----BEGIN PHRASE-----\n" . $this->passphrase . "\n-----END PHRASE-----\n\n";
277: $key .= $this->getKey();
278: /*
279: $key .= $this->getPrivateKey();
280: $key .= $this->getCertificate();
281: */
282: return $key;
283: }
284:
285:
286: /**
287: * Creates a distinquished name array for SSL certificate generation.
288: * @param array $sslParams The parameters to use when generating the distinquished name.
289: */
290: public function makeDN($sslParams = NULL) {
291: $newName = array();
292:
293: // fields to be constructed and their default values
294: $parameterFields = array(
295: 'countryName' => 'XX',
296: 'stateOrProvinceName' => 'XXXX',
297: 'localityName' => 'XXXX',
298: 'organizationName' => 'SSLKey',
299: 'commonName' => 'localhost',
300: 'emailAddress' => 'admin@localhost'
301: );
302:
303: // build Distinquished Name from parameters and defaults
304: foreach ($parameterFields as $field => $parameter) {
305: if (isset($sslParams[$field])) $newName[$field] = $sslParams[$field];
306: else $newName[$field] = $parameter;
307: }
308:
309: return $newName;
310: }
311:
312:
313: /**
314: * Creates a configArgs array to be used when creating certificates
315: * @param array $sslParams The parameters to use when generating the configArgs.
316: */
317: public function makeConfigArgs($sslParams = NULL) {
318: $newConfig = array();
319:
320: // fields to be constructed and their default values
321: $parameterFields = array(
322: 'private_key_bits' => 1024,
323: 'private_key_type' => OPENSSL_KEYTYPE_RSA
324: );
325:
326: // build configArgs from parameters and defaults
327: foreach ($parameterFields as $field => $value) {
328: if (isset($SSLParams[$field])) $newConfig[$field] = $SSLParams[$field];
329: else $newConfig[$field] = $value;
330: }
331:
332: return $newConfig;
333: }
334:
335:
336: /**
337: * Get the current errors.
338: */
339: public function getErrors() {
340: return $this->errors;
341: }
342:
343: }
344:
345: