2 /** @package php-gpg::GPG */
\r
4 /** require supporting files */
\r
5 require_once("Expanded_Key.php");
\r
7 define("PK_TYPE_ELGAMAL", 1);
\r
8 define("PK_TYPE_RSA", 0);
\r
9 define("PK_TYPE_UNKNOWN", -1);
\r
12 * Pure PHP implementation of PHP/GPG public key
\r
14 * @package php-gpg::GPG
\r
15 * @link http://www.verysimple.com/
\r
16 * @copyright 1997-2011 VerySimple, Inc.
\r
17 * @license http://www.gnu.org/licenses/lgpl.html LGPL
\r
18 * @todo implement decryption
\r
21 class GPG_Public_Key {
\r
31 return $this->version != -1 && $this->GetKeyType() != PK_TYPE_UNKNOWN;
\r
34 function GetKeyType()
\r
36 if (!strcmp($this->type, "ELGAMAL")) return PK_TYPE_ELGAMAL;
\r
37 if (!strcmp($this->type, "RSA")) return PK_TYPE_RSA;
\r
38 return PK_TYPE_UNKNOWN;
\r
41 function GetFingerprint()
\r
43 return strtoupper( trim(chunk_split($this->fp, 4, ' ')) );
\r
48 return (strlen($this->key_id) == 16) ? strtoupper($this->key_id) : '0000000000000000';
\r
51 function GetPublicKey()
\r
53 return str_replace("\n", "", $this->public_key);
\r
56 function GPG_Public_Key($asc) {
\r
59 // normalize line breaks
\r
60 $asc = str_replace("\r\n", "\n", $asc);
\r
62 if (strpos($asc, "-----BEGIN PGP PUBLIC KEY BLOCK-----\n") === false)
\r
63 throw new Exception("Missing header block in Public Key");
\r
65 if (strpos($asc, "\n\n") === false)
\r
66 throw new Exception("Missing body delimiter in Public Key");
\r
68 if (strpos($asc, "\n-----END PGP PUBLIC KEY BLOCK-----") === false)
\r
69 throw new Exception("Missing footer block in Public Key");
\r
71 // get rid of everything except the base64 encoded key
\r
72 $headerbody = explode("\n\n", str_replace("\n-----END PGP PUBLIC KEY BLOCK-----", "", $asc), 2);
\r
73 $asc = trim($headerbody[1]);
\r
77 $s = base64_decode($asc);
\r
78 $sa = str_split($s);
\r
80 for($i = 0; $i < strlen($s);) {
\r
81 $tag = ord($sa[$i++]);
\r
83 // echo 'TAG=' . $tag . '/';
\r
85 if(($tag & 128) == 0) break;
\r
89 $len = ord($sa[$i++]);
\r
90 if ($len > 191 && $len < 224) $len = (($len - 192) << 8) + ord($sa[$i++]);
\r
91 else if ($len == 255) $len = (ord($sa[$i++]) << 24) + (ord($sa[$i++]) << 16) + (ord($sa[$i++]) << 8) + ord($sa[$i++]);
\r
92 else if ($len > 223 && $len < 255) $len = (1 << ($len & 0x1f));
\r
95 $tag = ($tag >> 2) & 15;
\r
96 if ($len == 0) $len = ord($sa[$i++]);
\r
97 else if($len == 1) $len = (ord($sa[$i++]) << 8) + ord($sa[$i++]);
\r
98 else if($len == 2) $len = (ord($sa[$i++]) << 24) + (ord($sa[$i++]) << 16) + (ord($sa[$i++]) << 8) + ord($sa[$i++]);
\r
99 else $len = strlen($s) - 1;
\r
102 // echo $tag . ' ';
\r
104 if ($tag == 6 || $tag == 14) {
\r
106 $version = ord($sa[$i++]);
\r
108 $this->version = $version;
\r
110 $time = (ord($sa[$i++]) << 24) + (ord($sa[$i++]) << 16) + (ord($sa[$i++]) << 8) + ord($sa[$i++]);
\r
112 if($version == 2 || $version == 3) $valid = ord($sa[$i++]) << 8 + ord($sa[$i++]);
\r
114 $algo = ord($sa[$i++]);
\r
116 if($algo == 1 || $algo == 2) {
\r
118 $lm = floor((ord($sa[$i]) * 256 + ord($sa[$i + 1]) + 7) / 8);
\r
121 $mod = substr($s, $m, $lm + 2);
\r
122 $le = floor((ord($sa[$i]) * 256 + ord($sa[$i+1]) + 7) / 8);
\r
125 $this->public_key = base64_encode(substr($s, $m, $lm + $le + 4));
\r
126 $this->type = "RSA";
\r
128 if ($version == 3) {
\r
130 $this->key_id = bin2hex(substr($mod, strlen($mod) - 8, 8));
\r
131 } else if($version == 4) {
\r
133 // https://tools.ietf.org/html/rfc4880#section-12
\r
134 $headerPos = strpos($s, chr(0x04)); // TODO: is this always the correct starting point for the pulic key packet 'version' field?
\r
135 $delim = chr(0x01) . chr(0x00); // TODO: is this the correct delimiter for the end of the public key packet?
\r
136 $delimPos = strpos($s, $delim) + (3-$headerPos);
\r
138 // echo "POSITION: $delimPos\n";
\r
140 // this does not work, tried it with RSA 1024 and RSA 4096 keys generated by GnuPG v2 (2.0.29) on Windows running Apache and PHP 5.6.3
\r
141 // $pkt = chr(0x99) . chr($delimPos >> 8) . chr($delimPos & 255) . substr($s, $headerPos, $delimPos);
\r
143 // this is the original signing string which seems to have only worked for key lengths of 1024 or less
\r
144 $pkt = chr(0x99) . chr($len >> 8) . chr($len & 255) . substr($s, $k, $len); // use this for now
\r
148 $this->key_id = substr($fp, strlen($fp) - 16, 16);
\r
150 // uncomment to debug the start point for the signing string
\r
151 // for ($ii = 5; $ii > -1; $ii--) {
\r
152 // $pkt = chr(0x99) . chr($ii >> 8) . chr($ii & 255) . substr($s, $headerPos, $ii);
\r
153 // $fp = sha1($pkt);
\r
154 // echo "LENGTH=" . $headerPos . '->' . $ii . " CHR(" . ord(substr($s,$ii, 1)) . ") = " . substr($fp, strlen($fp) - 16, 16) . "\n";
\r
158 // uncomment to debug the end point for the signing string
\r
159 // for ($ii = strlen($s); $ii > 1; $ii--) {
\r
160 // $pkt = chr(0x99) . chr($ii >> 8) . chr($ii & 255) . substr($s, $headerPos, $ii);
\r
161 // $fp = sha1($pkt);
\r
162 // echo "LENGTH=" . $headerPos . '->' . $ii . " CHR(" . ord(substr($s,$ii, 1)) . ") = " . substr($fp, strlen($fp) - 16, 16) . "\n";
\r
165 throw new Exception('GPG Key Version ' . $version . ' is not supported');
\r
168 } else if(($algo == 16 || $algo == 20) && $version == 4) {
\r
171 $lp = floor((ord($sa[$i]) * 256 + ord($sa[$i +1]) + 7) / 8);
\r
174 $lg = floor((ord($sa[$i]) * 256 + ord($sa[$i + 1]) + 7) / 8);
\r
177 $ly = floor((ord($sa[$i]) * 256 + ord($sa[$i + 1]) + 7)/8);
\r
180 $this->public_key = base64_encode(substr($s, $m, $lp + $lg + $ly + 6));
\r
182 // TODO: should this be adjusted as it was for RSA (above)..?
\r
184 $pkt = chr(0x99) . chr($len >> 8) . chr($len & 255) . substr($s, $k, $len);
\r
187 $this->key_id = substr($fp, strlen($fp) - 16, 16);
\r
188 $this->type = "ELGAMAL";
\r
193 } else if ($tag == 13) {
\r
194 $this->user = substr($s, $i, $len);
\r
203 throw new Exception("Unable to parse Public Key");
\r
204 // $this->version = "";
\r
206 // $this->key_id = "";
\r
207 // $this->user = "";
\r
208 // $this->public_key = "";
\r
212 function GetExpandedKey()
\r
214 $ek = new Expanded_Key($this->public_key);
\r