packets); } // ArrayAccess interface function offsetExists($offset) { return isset($this->packets[$offset]); } function offsetGet($offset) { return $this->packets[$offset]; } function offsetSet($offset,$value) { return is_null($offset) ? $this->packets[] = $value : $this->packets[$offset] = $value; } function offsetUnset($offset) { unset($this->packets[$offset]); } // Class function __construct(array $packets=[]) { $this->packets = $packets; } public function __toString() { $result = ''; foreach ($this as $p) { $result .= (string)$p; } return $result; } /** * @see http://tools.ietf.org/html/rfc4880#section-4.1 * @see http://tools.ietf.org/html/rfc4880#section-4.2 */ static function parse($input): self { if (is_resource($input)) { return self::parse_stream($input); } if (is_string($input)) { return self::parse_string($input); } } static function parse_file($path): self { if (($msg=self::parse(file_get_contents($path)))) { $msg->uri = preg_match('!^[\w\d]+://!',$path) ? $path : 'file://'.realpath($path); return $msg; } } static function parse_stream($input): self { return self::parse_string(stream_get_contents($input)); } static function parse_string($input): self { $msg = new self; while (($length=strlen($input)) > 0) { if (($packet=Packet::parse($input))) { $msg[] = $packet; } // is parsing stuck? if ($length == strlen($input)) { break; } } return $msg; } /** * Extract signed objects from a well-formatted message * * Recurses into CompressedDataPacket * * @see http://tools.ietf.org/html/rfc4880#section-11 */ public function signatures(): array { $msg = $this; $key = NULL; $userid = NULL; $subkey = NULL; $sigs = []; $final_sigs = []; while ($msg[0] instanceof CompressedDataPacket) $msg = $msg[0]->data; foreach ($msg as $idx => $p) { if ($p instanceof LiteralDataPacket) { return [ [ $p, array_values(array_filter($msg->packets,function($p) { return $p instanceof SignaturePacket; })) ] ]; } elseif ($p instanceof PublicSubkeyPacket || $p instanceof SecretSubkeyPacket) { if ($userid) { array_push($final_sigs,[$key,$userid,$sigs]); $userid = NULL; } elseif ($subkey) { array_push($final_sigs,[$key,$subkey,$sigs]); $key = NULL; } $sigs = []; $subkey = $p; } elseif ($p instanceof PublicKeyPacket) { if ($userid) { array_push($final_sigs,[$key,$userid,$sigs]); $userid = NULL; } elseif ($subkey) { array_push($final_sigs,[$key,$subkey,$sigs]); $subkey = NULL; } elseif ($key) { array_push($final_sigs,[$key,$sigs]); $key = NULL; } $sigs = []; $key = $p; } elseif ($p instanceof UserIDPacket) { if ($userid) { array_push($final_sigs,[$key,$userid,$sigs]); $userid = NULL; } elseif ($key) { array_push($final_sigs,[$key,$sigs]); } $sigs = []; $userid = $p; } elseif ($p instanceof SignaturePacket) { $sigs[] = $p; } } if ($userid) { array_push($final_sigs,[$key,$userid,$sigs]); } elseif ($subkey) { array_push($final_sigs,[$key,$subkey,$sigs]); } elseif ($key) { array_push($final_sigs,[$key,$sigs]); } return $final_sigs; } public function to_bytes(): string { $bytes = ''; foreach ($this as $p) { $bytes .= (string)$p; } return $bytes; } /** * Function to extract verified signatures * * $verifiers is an array of callbacks formatted like array('RSA' => array('SHA256' => CALLBACK)) that take two parameters: raw message and signature packet */ function verified_signatures($verifiers): array { $signed = $this->signatures(); $vsigned = []; foreach ($signed as $sign) { $signatures = array_pop($sign); $vsigs = []; foreach ($signatures as $sig) { $verifier = $verifiers[$sig->key_algorithm_name()][$sig->hash_algorithm_name()]; if ($verifier && $this->verify_one($verifier,$sign,$sig)) { $vsigs[] = $sig; } } array_push($sign,$vsigs); $vsigned[] = $sign; } return $vsigned; } function verify_one($verifier,$sign,$sig) { if ($sign[0] instanceof LiteralDataPacket) { $sign[0]->normalize(); $raw = $sign[0]->data; } elseif (isset($sign[1]) && $sign[1] instanceof UserIDPacket) { $raw = implode( '', array_merge( $sign[0]->fingerprint_material(), array(chr(0xB4),pack('N',strlen($sign[1]->body())),$sign[1]->body()) )); } elseif (isset($sign[1]) && ($sign[1] instanceof PublicSubkeyPacket || $sign[1] instanceof SecretSubkeyPacket)) { $raw = implode('',array_merge($sign[0]->fingerprint_material(),$sign[1]->fingerprint_material())); } elseif ($sign[0] instanceof PublicKeyPacket) { $raw = implode('',$sign[0]->fingerprint_material()); } else { return NULL; } return call_user_func($verifier,$raw.$sig->trailer,$sig); } }