123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210 |
- <?php
- class Ppcl {
- public $identifier;
- public $serial;
- public $domains = array();
- public $members = array();
- public $agents = array();
- public $last_modified;
- public $signature;
- public $shared_signature_key;
- public $shared_signature;
- public $name;
- public $current_state_token;
- public $publications = array();
- private $authoritative_part;
- public function from_string($str) {
- $lines = explode("\n", $str);
- $header = explode(" ", $lines[0]);
- $this->authoritative_part = explode("\nCST ", $str, 2)[0];
- if($header[0] != "PPCL") {
- throw new Exception("Header does not start with PPCL magic number", 1);
- }
- $this->identifier = base64_decode($header[1]);
- $this->serial = (int)$header[2];
- $authoritative = true;
- foreach ($lines as $line) {
- $entry = explode(" ", $line);
- if($entry[0] == "PPCL") {
- continue;
- }
- if($entry[0] == "SSK" && $authoritative) {
- $this->shared_signature_key = base64_decode($entry[1]);
- }
- else if($entry[0] == "NAM" && $authoritative) {
- $this->name = explode(" ", $line, 2)[1];
- }
- else if($entry[0] == "DOM" && $authoritative) {
- array_push($this->domains, $entry[1]);
- }
- else if($entry[0] == "MEM" && $authoritative) {
- array_push($this->members, new CollectionMember($entry[1], $entry[2], base64_decode($entry[3])));
- }
- else if($entry[0] == "AGT" && $authoritative) {
- array_push($this->agents, new CollectionAgent($entry[1], base64_decode($entry[2]), base64_decode($entry[3])));
- }
- else if($entry[0] == "SIG" && $authoritative) {
- $this->signature = base64_decode($entry[1]);
- $authoritative = false;
- }
- else if($entry[0] == "CST") {
- $this->current_state_token = base64_decode($entry[1]);
- }
- else if($entry[0] == "MOD") {
- $this->last_modified = new DateTime($entry[1]);
- }
- else if($entry[0] == "PUB") {
- array_push($this->publications, new CollectionPublication($line, $this->members));
- }
- else if($entry[0] == "SSG") {
- $this->shared_signature = base64_decode($entry[1]);
- }
- }
- // Verify authoritative signature
- $parts = explode("\nSIG ", $str, 2);
- $hash = hash("sha512", $parts[0], true);
- $sig_data = sodium_crypto_sign_open($this->signature, $this->identifier);
- if($hash != $sig_data) {
- throw new Exception("Invalid authoritative signature", 1);
- }
- // Verify shared signature
- $parts = explode("\nSSG ", $str, 2);
- $hash = hash("sha512", $parts[0], true);
- $sig_data = sodium_crypto_sign_open($this->shared_signature, $this->shared_signature_key);
- if($hash != $sig_data) {
- throw new Exception("Invalid shared signature", 1);
- }
- }
- public function to_string() {
- $str = $this->authoritative_part;
- $now = new DateTime();
- $token = random_bytes(256);
- $str .= "\nCST " . base64_encode($token) . "\nMOD " . $now->format(DateTime::ATOM) . "\n";
- usort($this->publications, function ($a, $b) { return $a->timestamp <=> $b->timestamp; });
- foreach($this->publications as $pub) {
- $str .= "\n" . $pub->raw_data;
- }
- $checksum = hash("sha512", $str, true);
- $agent = null;
- foreach($this->agents as $agt) {
- if($agt->public_key == base64_decode(PPCL_AGENT_PUBLIC_KEY)) {
- $agent = $agt;
- break;
- }
- }
- if($agent == null) {
- throw new Exception("Public key defined in config (PPCL_AGENT_PUBLIC_KEY) not present in PPCL file", 1);
- }
- $key = sodium_crypto_box_keypair_from_secretkey_and_publickey(base64_decode(PPCL_AGENT_SECRET_KEY), $agent->public_key);
- $secret = sodium_crypto_box_seal_open($agent->collection_secret, $key);
- if($secret == null) {
- throw new Exception("Could not decrypt collection secret", 1);
- }
- $shared_signature = sodium_crypto_sign($checksum, $secret);
- $str .= "\nSSG " . base64_encode($shared_signature);
- return $str;
- }
- public function get_publication($name) {
- foreach($this->publications as $pub) {
- if($pub->name == $name) {
- return $pub;
- }
- }
- return null;
- }
- }
- class CollectionMember {
- public $name;
- public $signing_public_key;
- public $sealing_public_key;
- public $collection_secret;
-
- public function __construct($name, $keys, $secret) {
- $this->name = $name;
- $this->collection_secret = $secret;
-
- $key_parts = explode(":", $keys);
- if($key_parts[0] != "CLMPK") {
- error_log($keys);
- throw new Exception("Invalid member public key");
- }
- $key_data = base64_decode($key_parts[1]);
- $this->signing_public_key = substr($key_data, 0, SODIUM_CRYPTO_SIGN_PUBLICKEYBYTES);
- $this->sealing_public_key = substr($key_data, SODIUM_CRYPTO_SIGN_PUBLICKEYBYTES, SODIUM_CRYPTO_BOX_PUBLICKEYBYTES);
- }
- }
- class CollectionAgent {
- public $name;
- public $public_key;
- public $collection_secret;
-
- public function __construct($name, $key, $secret) {
- $this->name = $name;
- $this->public_key = $key;
- $this->collection_secret = $secret;
- }
- }
- class CollectionPublication {
- public $name;
- public $member_name;
- public $timestamp;
- public $signature;
- public $checksum;
- public $raw_data;
- public function __construct($line, $members) {
- $this->raw_data = $line;
- $parts = explode(": ", $line, 2);
- $params = explode(" ", $parts[1]);
- $this->name = substr($parts[0], 4);
- $this->member_name = $params[0];
- $this->timestamp = new DateTime($params[1]);
- $this->signature = base64_decode($params[2]);
-
- $member = null;
- foreach($members as $m) {
- if($m->name == $this->member_name) {
- $member = $m;
- break;
- }
- }
- if($member == null) {
- throw new Exception("PUB entry references member (" . $this->member_name . ") that is not present in the collection", 1);
- }
- $hash_data = $this->name . ": " . $this->member_name . " " . $params[1];
- $hash = hash("sha512", $hash_data, true);
- $sig_data = sodium_crypto_sign_open($this->signature, $member->signing_public_key);
- if(substr($sig_data, 0, 64) != $hash) {
- throw new Exception("Invalid member signature on publication " . $this->name , 1);
- }
- $this->checksum = substr($sig_data, 64, 64);
- }
-
- public function verify_ppub() {
- $file_hash = hash_file("sha512", PUBLICATION_DIR . "/" . $this->name, true);
- return $file_hash == $this->checksum;
- }
- }
- ?>
|