Parcourir la source

Add PPRF publish message handling

Billy Barrow il y a 1 an
Parent
commit
3d554f5fe1
3 fichiers modifiés avec 140 ajouts et 5 suppressions
  1. 2 0
      config.php
  2. 68 4
      ppcl.php
  3. 70 1
      pprf.php

+ 2 - 0
config.php

@@ -16,5 +16,7 @@ define("BSPATCH_PATH", "/usr/bin/bspatch");
 define("PPRF_DATA_DIR", "pprf-data");
 define("PPRF_MAX_UPLOAD_CHUNK_SIZE", 1024);
 define("THEME", "baseline");
+define("PPCL_AGENT_PUBLIC_KEY", "RC0aPTwfOmvdZranz4oDpyZ6uYmWiOS4MvaP2UAWgUw=");
+define("PPCL_AGENT_SECRET_KEY", "Iu+Fewn/KZRftygPA48GOYybFlYqi6tHRhsqvnpXKCM=");
 
 ?>

+ 68 - 4
ppcl.php

@@ -5,38 +5,55 @@ class Ppcl {
     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("\nMOD ", $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") {
+            if($entry[0] == "SSK" && $authoritative) {
                 $this->shared_signature_key = base64_decode($entry[1]);
             }
-            else if($entry[0] == "DOM") {
+            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") {
+            else if($entry[0] == "MEM" && $authoritative) {
                 array_push($this->members, new CollectionMember($entry[1], base64_decode($entry[2]), base64_decode($entry[3]), base64_decode($entry[4])));
             }
-            else if($entry[0] == "SIG") {
+            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]);
@@ -65,6 +82,39 @@ class Ppcl {
             throw new Exception("Invalid shared signature", 1);
         }
     }
+
+    public function to_string() {
+        $str = $this->authoritative_part;
+        $now = new DateTime();
+        $token = random_bytes(256);
+        $str .= "\nCST " . $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;
+    }
 }
 
 class CollectionMember {
@@ -81,14 +131,28 @@ class CollectionMember {
     }
 }
 
+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]);
 

+ 70 - 1
pprf.php

@@ -40,7 +40,7 @@ function get_member($ppcl, $name) {
 function verify_collection_message($handle, $ppcl) {
     $id = fread($handle, SODIUM_CRYPTO_SIGN_PUBLICKEYBYTES);
     if($ppcl->identifier != $id) {
-        send_failure(2, "Provided collection not hosted by this server");
+        send_failure(2, "Requested collection not hosted by this server");
     }
 }
 
@@ -99,6 +99,17 @@ if(fread($handle, 5) != "PPRF\x00") {
 
 $message_info = unpack("Psize/Ctype", fread($handle, 9));
 
+// Get collection
+if($message_info["type"] == 6) {
+    $ppcl = get_ppcl();
+    verify_collection_message($handle, $ppcl);
+    $data = file_get_contents(PUBLICATION_DIR . "/collection.ppcl");
+    
+    $message = pack("V", strlen($data));
+    $message .= $data;
+    send_message(135, $message);
+}
+
 // Register name
 if($message_info["type"] == 32) {
     $ppcl = get_ppcl();
@@ -221,6 +232,13 @@ if($message_info["type"] == 34) {
         send_failure(10, "The destination \"" . $dest_name . "\" is not empty");
     }
 
+    // If destination is already published
+    foreach($ppcl->publications as $pub) {
+        if($pub->name == $dest_name) {
+            send_failure(15, "The destination \"" . $dest_name . "\" cannot be overwritten as it is currently published");
+        }
+    }
+
     // Verify data
     $checksum = hash_file("sha512", $upload_path, true);
     verify_upload_auth($session_header, $session, $member, $checksum);
@@ -254,6 +272,57 @@ if($message_info["type"] == 34) {
     send_confirmation();
 }
 
+// Publish message
+if($message_info["type"] == 36) {
+    $ppcl = get_ppcl();
+    verify_collection_message($handle, $ppcl);
+    $str_len = unpack("v", fread($handle, 2))[1];
+    $pub_str = fread($handle, $str_len);
+    $auth_len = unpack("v", fread($handle, 2))[1];
+    $authentication = fread($handle, $auth_len);
+
+    try {
+        $pub_entry = new CollectionPublication($pub_str, $ppcl->members);
+
+        $member = get_member($ppcl, $pub_entry->member_name);
+        $signature = sodium_crypto_sign_open($authentication, $member->signing_public_key);
+        if($signature == null) {
+            send_failure(5, "Could not verify member signature");
+        }
+
+        $expected_auth = hash("sha512", $pub_str, true);
+        $expected_auth .= $ppcl->current_state_token;
+        if($expected_auth != $signature) {
+            send_failure(5, "Invalid authorisation token");
+        }
+
+        array_push($ppcl->publications, $pub_entry);
+        file_put_contents(PUBLICATION_DIR . "/collection.ppcl", $ppcl->to_string());
+        send_confirmation();
+    }
+    catch(Exception $e) {
+        send_failure(14, $e->getMessage());
+    }
+}
+
+
+// // Get identity message
+// if($message_info["type"] == 41) {
+//     $ppcl = get_ppcl();
+//     verify_collection_message($handle, $ppcl);
+//     $member_name_size = unpack("C", fread($handle, 1))[1];
+//     $member_name = fread($handle, $member_name_size);
+//     $member = get_member($ppcl, $member_name);
+
+//     $message = pack("v", strlen($ppcl->shared_signature_key));
+//     $message .= $ppcl->shared_signature_key;
+//     $message = pack("v", strlen($ppcl->current_state_token));
+//     $message .= $ppcl->current_state_token;
+//     $message .= pack("v", strlen($member->collection_secret));
+//     $message .= $member->collection_secret;
+//     send_message(161, $message);
+// }
+
 
 send_failure(1, "Messages of type " . $message_info["type"] . " are not supported by this server");