Explorar o código

Big push for PPRF + PPCL publish in Publicate happy path

Billy Barrow hai 1 ano
pai
achega
de2435a2ad

+ 123 - 45
src/lib/Ppcl.vala

@@ -11,12 +11,13 @@ namespace Ppub {
         INVALID_FORMAT,
         INVALID_SHARED_SIGNATURE,
         INVALID_CREDENTIALS,
-        INVALID_URI
+        INVALID_URI,
+        INVALID_MEMBER_KEYS
     }
     
     public class Collection {
         public uint8[] id { get; private set; }
-        public string name { get; private set; }
+        public string name { get; set; }
         public int serial { get; private set; }
         public uint8[] shared_signature_key { get; private set; }
         public Vector<string> domains { get; private set; }
@@ -46,7 +47,7 @@ namespace Ppub {
             initialise();
         }
 
-        public Collection.from_stream(DataInputStream stream) throws Error {
+        public Collection.from_stream(DataInputStream stream, bool verify = true) throws Error {
             authoritative_section = "";
             shared_section = "";
 
@@ -85,13 +86,16 @@ namespace Ppub {
                     shared_signature_key = Base64.decode(entry[1]);
                 }
                 else if(entry[0] == "MEM") {
-                    members.add(new CollectionMember.from_string(entry[1]));
+                    members.add(new CollectionMember.from_string(entry[1], verify));
                 }
                 else if(entry[0] == "DOM") {
                     domains.add(entry[1]);
                 }
                 else if(entry[0] == "AGT") {
-                    agents.add(new CollectionAgent.from_string(entry[1]));
+                    agents.add(new CollectionAgent.from_string(entry[1], verify));
+                }
+                else if(entry[0] == "NAM") {
+                    name = entry[1];
                 }
                 else if(entry[0] == "SIG") {
                     collection_signature = Base64.decode(entry[1]);
@@ -130,13 +134,15 @@ namespace Ppub {
             }
 
             // Verify authoratative section
-            var checksum = new BinaryData.from_byte_array(get_string_checksum(signed_portion));
-            var signed_checksum = Sodium.Asymmetric.Signing.verify(collection_signature, id);
-            if(signed_checksum == null) {
-                throw new CollectionError.INVALID_COLLECTION_SIGNATURE("Could not verify authoratative signature");
-            }
-            if(!checksum.equals(ate(signed_checksum))) {
-                throw new CollectionError.INVALID_COLLECTION_SIGNATURE("Authoratative signature checksum does not match calculated checksum");
+            if(verify) {
+                var checksum = new BinaryData.from_byte_array(get_string_checksum(signed_portion));
+                var signed_checksum = Sodium.Asymmetric.Signing.verify(collection_signature, id);
+                if(signed_checksum == null) {
+                    throw new CollectionError.INVALID_COLLECTION_SIGNATURE("Could not verify authoratative signature");
+                }
+                if(!checksum.equals(ate(signed_checksum))) {
+                    throw new CollectionError.INVALID_COLLECTION_SIGNATURE("Authoratative signature checksum does not match calculated checksum");
+                }
             }
 
             if(line == null) {
@@ -168,13 +174,15 @@ namespace Ppub {
             }
 
             // Verify shared signature
-            checksum = get_checksum();
-            signed_checksum = Sodium.Asymmetric.Signing.verify(shared_signature, shared_signature_key);
-            if(signed_checksum == null) {
-                throw new CollectionError.INVALID_SHARED_SIGNATURE("Could not verify shared signature");
-            }
-            if(!checksum.equals(ate(signed_checksum))) {
-                throw new CollectionError.INVALID_SHARED_SIGNATURE("Shared signature checksum does not match calculated checksum");
+            if(verify) {
+                var checksum = get_checksum();
+                var signed_checksum = Sodium.Asymmetric.Signing.verify(shared_signature, shared_signature_key);
+                if(signed_checksum == null) {
+                    throw new CollectionError.INVALID_SHARED_SIGNATURE("Could not verify shared signature");
+                }
+                if(!checksum.equals(ate(signed_checksum))) {
+                    throw new CollectionError.INVALID_SHARED_SIGNATURE("Shared signature checksum does not match calculated checksum");
+                }
             }
         }
 
@@ -205,6 +213,9 @@ namespace Ppub {
 
             var data = strings.to_string(s => s, "\n");
             var signed_portion = @"PPCL $(Base64.encode(id)) $serial\nSSK $(Base64.encode(shared_signature_key))";
+            if(name != null && name.length > 0) {
+                signed_portion += @"\nNAM $name";
+            }
             if(data != "") {
                 signed_portion += @"\n$data";
             }
@@ -255,37 +266,40 @@ namespace Ppub {
 
     public class CollectionMember {
         public string name { get; private set; }
-        public uint8[] public_signing_key { get; private set; }
-        public uint8[] public_sealing_key { get; private set; }
+        public CollectionMemberPublicKeys public_keys { get; private set; }
         public uint8[] collection_secret { get; private set; }
 
         public string to_string() {
-            return @"MEM $name $(Base64.encode(public_signing_key)) $(Base64.encode(public_sealing_key)) $(Base64.encode(collection_secret))";
+            return @"MEM $name $public_keys $(Base64.encode(collection_secret))";
         }
 
-        public CollectionMember(string name, uint8[] public_signing_key, uint8[] public_sealing_key) {
+        public CollectionMember(string name, CollectionMemberPublicKeys keys) {
             this.name = name;
-            this.public_signing_key = public_signing_key;
-            this.public_sealing_key = public_sealing_key;
+            public_keys = keys;
         }
 
-        public CollectionMember.from_string(string line) throws CollectionError {
+        public CollectionMember.from_string(string line, bool strict = true) throws CollectionError {
             var parts = line.split(" ");
-            if(parts.length < 4) {
-                throw new CollectionError.INVALID_FORMAT("Member line must contain at least four fields, name, signing public key, sealing public key, and collection secret");
+            if(parts.length < 3 && strict) {
+                throw new CollectionError.INVALID_FORMAT("Member line must contain at least three fields, name, public key, and collection secret");
+            }
+            if(parts.length < 2 && !strict) {
+                throw new CollectionError.INVALID_FORMAT("Member line must contain at least two fields, name, public key, and optionally collection secret");
             }
             name = parts[0];
-            public_signing_key = Base64.decode(parts[1]);
-            public_sealing_key = Base64.decode(parts[2]);
-            collection_secret = Base64.decode(parts[3]);
+            public_keys = new CollectionMemberPublicKeys.from_string(parts[1]);
+
+            if(parts.length > 2) {
+                collection_secret = Base64.decode(parts[2]);
+            }
         }
 
         public void set_secret(uint8[] secret) {
-            collection_secret = Sodium.Asymmetric.Sealing.seal(secret, public_sealing_key);
+            collection_secret = Sodium.Asymmetric.Sealing.seal(secret, public_keys.sealing_key);
         }
 
         public uint8[]? decrypt_collection_secret(CollectionMemberCredentials credentials) {
-            return Sodium.Asymmetric.Sealing.unseal(collection_secret, public_sealing_key, credentials.secret_sealing_key);
+            return Sodium.Asymmetric.Sealing.unseal(collection_secret, public_keys.sealing_key, credentials.secret_sealing_key);
         }
     }
 
@@ -303,14 +317,20 @@ namespace Ppub {
             this.public_sealing_key = public_key;
         }
 
-        public CollectionAgent.from_string(string line) throws CollectionError {
+        public CollectionAgent.from_string(string line, bool strict = true) throws CollectionError {
             var parts = line.split(" ");
-            if(parts.length < 3) {
-                throw new CollectionError.INVALID_FORMAT("Member line must contain at least two fields, name, public key, and collection secret");
+            if(parts.length < 3 && strict) {
+                throw new CollectionError.INVALID_FORMAT("Agent line must contain at least three fields, name, public key, and collection secret");
+            }
+            if(parts.length < 2 && !strict) {
+                throw new CollectionError.INVALID_FORMAT("Agent line must contain at least two fields, name, public key, and optionally collection secret");
             }
             name = parts[0];
             public_sealing_key = Base64.decode(parts[1]);
-            collection_secret = Base64.decode(parts[2]);
+
+            if(parts.length > 2) {
+                collection_secret = Base64.decode(parts[2]);
+            }
         }
 
         public uint8[]? decrypt_collection_secret(uint8[] private_sealing_key) {
@@ -328,6 +348,36 @@ namespace Ppub {
         }
     }
 
+    public class CollectionMemberPublicKeys {
+        public uint8[] signing_key { get; private set; }
+        public uint8[] sealing_key { get; private set; }
+
+        public CollectionMemberPublicKeys(uint8[] sign_key, uint8[] seal_key) {
+            signing_key = sign_key;
+            sealing_key = seal_key;
+        }
+
+        public string to_string() {
+            var combined = new BinaryData.from_byte_array(signing_key);
+            combined.append_byte_array(sealing_key);
+            return @"CLMPK:$(Base64.encode(combined.to_array()))";
+        }
+
+        public CollectionMemberPublicKeys.from_string(string str) throws CollectionError {
+            var parts = str.split(":", 2);
+            if(parts.length < 2 || parts[0] != "CLMPK") {
+                throw new CollectionError.INVALID_MEMBER_KEYS("Malformed CLMPK string");
+            }
+            var data = Base64.decode(parts[1]);
+            if(data.length != Sodium.Asymmetric.Signing.PUBLIC_KEY_BYTES + Sodium.Asymmetric.Sealing.PUBLIC_KEY_BYTES) {
+                throw new CollectionError.INVALID_MEMBER_KEYS("Wrong sized CLMPK string");
+            }
+
+            signing_key = data[0:Sodium.Asymmetric.Signing.PUBLIC_KEY_BYTES];
+            sealing_key = data[Sodium.Asymmetric.Signing.PUBLIC_KEY_BYTES:Sodium.Asymmetric.Signing.SECRET_KEY_BYTES];
+        }
+    }
+
     public class CollectionMemberCredentials {
         public uint8[] public_signing_key { get; private set; }
         public uint8[] secret_signing_key { get; private set; }
@@ -345,19 +395,43 @@ namespace Ppub {
         }
 
         public string to_string() {
-            return @"$(Base64.encode(public_signing_key)):$(Base64.encode(public_sealing_key)):$(Base64.encode(secret_signing_key)):$(Base64.encode(secret_sealing_key))";
+            var str = "PPCLMC\n";
+            str += @"PKSIG $(Base64.encode(public_signing_key))\n";
+            str += @"PKENC $(Base64.encode(public_sealing_key))\n";
+            str += @"SKSIG $(Base64.encode(secret_signing_key))\n";
+            str += @"SKENC $(Base64.encode(secret_sealing_key))";
+            return str;
         }
 
         public CollectionMemberCredentials.from_string(string str) throws CollectionError {
             var clean = str.chug().chomp();
-            var parts = clean.split(":");
-            if(parts.length < 4) {
+            var lines = clean.split("\n", 6);
+            if(lines.length < 5 || lines[0] != "PPCLMC") {
                 throw new CollectionError.INVALID_CREDENTIALS("Invalid member credential format");
             }
-            public_signing_key = Base64.decode(parts[0]);
-            public_sealing_key = Base64.decode(parts[1]);
-            secret_signing_key = Base64.decode(parts[2]);
-            secret_sealing_key = Base64.decode(parts[3]);
+
+            foreach (var line in lines) {
+                var parts = line.split(" ", 2);
+                if(lines.length < 2) {
+                    throw new CollectionError.INVALID_CREDENTIALS("Invalid member credential entry");
+                }
+                if(parts[0] == "PKSIG") {
+                    public_signing_key = Base64.decode(parts[1]);
+                }
+                if(parts[0] == "PKENC") {
+                    public_sealing_key = Base64.decode(parts[1]);
+                }
+                if(parts[0] == "SKSIG") {
+                    secret_signing_key = Base64.decode(parts[1]);
+                }
+                if(parts[0] == "SKENC") {
+                    secret_sealing_key = Base64.decode(parts[1]);
+                }
+            }
+        }
+
+        public CollectionMemberPublicKeys get_public_keys() {
+            return new CollectionMemberPublicKeys(public_signing_key, public_sealing_key);
         }
     }
 
@@ -437,7 +511,7 @@ namespace Ppub {
             }
 
             var checksum = new BinaryData.from_byte_array(Collection.get_string_checksum(get_signed_portion()));
-            var signature_data = Sodium.Asymmetric.Signing.verify(signature, collection_member.public_signing_key);
+            var signature_data = Sodium.Asymmetric.Signing.verify(signature, collection_member.public_keys.signing_key);
             if(signature_data == null) {
                 throw new CollectionError.INVALID_COLLECTION_SIGNATURE("Invalid publication signature");
             }
@@ -534,6 +608,10 @@ namespace Ppub {
             col_id += "=";
             collection_id = Base64.decode(col_id);
 
+            if(collection_id.length != Sodium.Asymmetric.Signing.PUBLIC_KEY_BYTES) {
+                throw new CollectionError.INVALID_URI("Invalid collection id");
+            }
+
             var path = uri.get_path();
             var parts = path.split("/", 2);
             if(parts[0] != "") {

+ 27 - 0
src/pprf/Client.vala

@@ -1,11 +1,13 @@
 
 using Pprf.Messages;
 using Invercargill;
+using Invercargill.Convert;
 
 namespace Pprf {
 
     public errordomain ClientError {
         UNEXPECTED_RESPONSE,
+        NO_PEERS
     }
 
     public enum UploadStatus {
@@ -19,6 +21,24 @@ namespace Pprf {
 
     public delegate void UploadProgressDelegate(uint64 bytes_sent, uint64 bytes_total, UploadStatus status);
 
+    public Client get_client_for_uri(Ppub.CollectionUri uri) throws Error {
+        if(uri.via_server_record == null){
+            throw new ClientError.NO_PEERS("Decentralised PPCL not yet supported, please specify domain");
+        }
+        var dns = Ppub.CollectionServerRecord.resolve_records(uri.via_server_record);
+        var collection_id = new BinaryData.from_byte_array(uri.collection_id);
+        foreach (var entry in dns) {
+            if(!collection_id.equals(ate(entry.collection_id))) {
+                continue;
+            }
+            if(entry.uri.get_scheme() == "http" || entry.uri.get_scheme() == "https") {
+                print(@"Creating client: $(entry.uri)\n");
+                return new HttpPprfClient(entry.uri.to_string() + "/pprf.php");
+            }
+        }
+        throw new ClientError.NO_PEERS("Can't find any HTTP or HTTPS PPRF servers listed for that domain");
+    }
+
     public abstract class Client {
         public abstract Message send_message(Message message) throws Error;
 
@@ -155,6 +175,13 @@ namespace Pprf {
             finalise_upload_session(collection_id, session, flags, destination, digest, identity);
             cb(offset, size, UploadStatus.COMPLETE);
         }
+
+        public void rebuild_index(BinaryData collection_id, MemberIdentity identity) throws Error {
+            var message = new Messages.RebuildIndex();
+            message.collection_id = collection_id;
+            var response = send_authenticated_message(message, identity);
+            assert_expected_type(response, typeof(Messages.Confirmation));
+        }
     }
 
 }

+ 2 - 2
src/pprf/Identity.vala

@@ -45,8 +45,8 @@ namespace Pprf {
         public static Enumerable<MemberIdentity> get_usable_identities(Enumerable<Ppub.CollectionMemberCredentials> credentials, Ppub.Collection collection) {
             var identities = new Vector<MemberIdentity>();
             foreach (var member in collection.members) {
-                var sign_key = new BinaryData.from_byte_array(member.public_signing_key);
-                var seal_key = new BinaryData.from_byte_array(member.public_sealing_key);
+                var sign_key = new BinaryData.from_byte_array(member.public_keys.signing_key);
+                var seal_key = new BinaryData.from_byte_array(member.public_keys.sealing_key);
 
                 var cred = credentials.first_or_default(c => sign_key.equals(ate(c.public_signing_key)) && seal_key.equals(ate(c.public_sealing_key)));
                 if(cred != null) {

+ 3 - 1
src/pprf/Messages/Failure.vala

@@ -69,6 +69,8 @@ namespace Pprf.Messages {
                     return new PprfFailureError.DESTINATION_NOT_EMPTY(message);
                 case 11:
                     return new PprfFailureError.INVALID_MEMBER_SIGNATURE(message);
+                case 12:
+                    return new PprfFailureError.INDEXER_ERROR(message);
                 case 13:
                     return new PprfFailureError.VCDIFF_ERROR(message);
                 case 14:
@@ -98,7 +100,7 @@ namespace Pprf.Messages {
         DESTINATION_NOT_REGISTERED = 9,
         DESTINATION_NOT_EMPTY = 10,
         INVALID_MEMBER_SIGNATURE = 11,
-        // Something here for 12
+        INDEXER_ERROR = 12,
         VCDIFF_ERROR = 13,
         PPCL_ERROR = 14,
         DESTINATION_PUBLISHED = 15,

+ 1 - 1
src/pprf/Messages/Publish.vala

@@ -22,7 +22,7 @@ namespace Pprf.Messages {
 
         public override bool verify(Ppub.Collection collection) {
             var member = collection.members.first_or_default(m => m.name == member_name);
-            var auth = Sodium.Asymmetric.Signing.verify(authentication, member.public_signing_key);
+            var auth = Sodium.Asymmetric.Signing.verify(authentication, member.public_keys.signing_key);
             if(auth == null)
                 return false;
 

+ 31 - 0
src/pprf/Messages/RebuildIndex.vala

@@ -0,0 +1,31 @@
+using Invercargill;
+using Invercargill.Convert;
+
+namespace Pprf.Messages {
+
+    public class RebuildIndex : AuthenticatedMessage {
+    
+        public RebuildIndex() {
+            message_type = MessageType.REBUILD_INDEX;
+        }
+
+        public override void authenticate(MemberIdentity identity) {
+            member_name = identity.name;
+            var auth = new BinaryData.from_byte_array(new uint8[] { 'P', 'P', 'I', 'X', 0xFF });
+            auth.append(identity.current_state_token);
+            authentication = identity.sign_with_individual_signature(auth.to_array());
+        }
+
+        public override bool verify(Ppub.Collection collection) {
+            var member = collection.members.first_or_default(m => m.name == member_name);
+            var auth = Sodium.Asymmetric.Signing.verify(authentication, member.public_keys.signing_key);
+            if(auth == null)
+                return false;
+
+            var compare = new BinaryData.from_byte_array(new uint8[] { 'P', 'P', 'I', 'X', 0xFF });
+            compare.append_byte_array(collection.current_state_token);
+            
+            return compare.equals(ate(auth));
+        }
+    }
+}

+ 1 - 1
src/pprf/Messages/RegisterName.vala

@@ -18,7 +18,7 @@ namespace Pprf.Messages {
 
         public override bool verify(Ppub.Collection collection) {
             var member = collection.members.first_or_default(m => m.name == member_name);
-            var auth = Sodium.Asymmetric.Signing.verify(authentication, member.public_signing_key);
+            var auth = Sodium.Asymmetric.Signing.verify(authentication, member.public_keys.signing_key);
             if(auth == null)
                 return false;
 

+ 1 - 1
src/pprf/Messages/Unpublish.vala

@@ -22,7 +22,7 @@ namespace Pprf.Messages {
 
         public override bool verify(Ppub.Collection collection) {
             var member = collection.members.first_or_default(m => m.name == member_name);
-            var auth = Sodium.Asymmetric.Signing.verify(authentication, member.public_signing_key);
+            var auth = Sodium.Asymmetric.Signing.verify(authentication, member.public_keys.signing_key);
             if(auth == null)
                 return false;
 

+ 1 - 0
src/pprf/meson.build

@@ -30,6 +30,7 @@ sources += files('Messages/Unpublish.vala')
 sources += files('Messages/Failure.vala')
 sources += files('Messages/Confirmation.vala')
 sources += files('Messages/UploadSession.vala')
+sources += files('Messages/RebuildIndex.vala')
 sources += files('Messages/GetCollection.vala')
 
 sources += files('Messages/GetListing.vala')

+ 100 - 56
src/tools/ppcl/Ppcl.vala

@@ -1,77 +1,121 @@
 
+public void usage(string[] args) {
+    print(@"USAGE: $(args[0]) [COMMAND]\n");
+    print("Commands:\n");
+    print("\tnew [name] [output]: create new PPCL and private key\n");
+    print("\tnew-member [member name]: generates a new collection member identity\n");
+    print("\tnew-agent: generates a new agent keypair\n");
+    print("\tresign [collection] [output]: signs a PPCL without validating it\n");
+    print("\tverify [collection]: validates and verifies a PPCL\n");
+    print("\tpublish [ppub] [credential file] [member name] [ISO timestamp (optional)]: generates a pub string\n");
+    print("\n");
+}
 
 public static int main(string[] args) {
 
     if(args.length < 2) {
-        print(@"USAGE: $(args[0]) [COMMAND]\nCommands:\n\tnew [output]: create new PPCL and private key\n\tnew-member [member name]: generates a new keypair for a member\n\tadd-member [collection] [member name] [member key]: add a member to the member register\n\tadd-domain [collection] [DNS name]: add an authorative domain\n\tpublish [collection] [publication] [member]: publish the specified PPUB to the collection\n");
+        usage(args);
         return -1;
     }
-
-    if(args[1] == "new") {
-        uint8[] signing_key;
-        var collection = new Ppub.Collection(out signing_key);
+    
+    try {
+        if(args[1] == "new") {
+            new_ppcl(args[2], args[3]);
+            return 0;
+        }
+    
+        if(args[1] == "verify") {
+            verify(args[2]);
+            return 0;
+        }
+    
+        if(args[1] == "new-member") {
+            var member_cred = new Ppub.CollectionMemberCredentials();
+            var cred_str = member_cred.to_string();
+            FileUtils.set_contents(args[2] + ".credentials", cred_str, cred_str.length);
+            print(@"Member public key: $(member_cred.get_public_keys().to_string())\n");
+            return 0;
+        }
+    
+        if(args[1] == "new-agent") {
+            uint8[] pk;
+            uint8[] sk; 
+            Ppub.CollectionAgent.new_keypair(out sk, out pk);
+            print(@"Generated new agent keypair\n\tPUBLIC KEY: $(Base64.encode(pk))\n\tSECRET KEY: $(Base64.encode(sk))\n");
+            return 0;
+        }
+    
+        if(args[1] == "publish") {
+            var date = new DateTime.now_local();
+            if(args.length > 5) {
+                date = new DateTime.from_iso8601(args[5], null);
+            }
+            publish(args[2], args[3], args[4], date);
+            return 0;
+        }
+    
         
-        var col_str = collection.authoratative_update(signing_key);
-        FileUtils.set_contents(args[2], col_str, col_str.length);
-
-        var key_str = Base64.encode(signing_key);
-        FileUtils.set_contents(args[2] + ".private.key", key_str, key_str.length);
-        return 0;
+        if(args[1] == "resign") {
+            string key_str;
+            FileUtils.get_contents(args[2] + ".secret", out key_str, null);
+            var signing_key = Base64.decode(key_str);
+            resign_ppcl(args[2], signing_key, args[3]);
+            return 0;
+        }
     }
-
-    if(args[1] == "new-member") {
-        var member_cred = new Ppub.CollectionMemberCredentials();
-        var cred_str = member_cred.to_string();
-        FileUtils.set_contents(args[2] + ".credentials", cred_str, cred_str.length);
-
-        return 0;
+    catch(Error e) {
+        print(@"Command failed: $(e.message)\n");
+        return e.code;
     }
 
-    var collection_name = args[2];
-    string key_str;
-    FileUtils.get_contents(args[2] + ".private.key", out key_str, null);
-    var signing_key = Base64.decode(key_str);
 
-    var collection_stream = new DataInputStream(File.new_for_path(collection_name).read());
-    var collection = new Ppub.Collection.from_stream(collection_stream);
+    print(@"Unrecognised command \"$(args[1])\"\n");
+    usage(args);
+    return -1;
+}
 
-    if(args[1] == "add-member") {
-        var keys = args[4].split(":", 2);
-        collection.members.add(new Ppub.CollectionMember(args[3], Base64.decode(keys[0]), Base64.decode(keys[1])));
-        collection.authoratative_update(signing_key);
-    }
 
-    else if(args[1] == "add-agent") {
-        collection.agents.add(new Ppub.CollectionAgent(args[3], Base64.decode(args[4])));
-        collection.authoratative_update(signing_key);
-    }
+public void new_ppcl(string name, string location) throws Error {
+    uint8[] signing_key;
+    var collection = new Ppub.Collection(out signing_key);
+    collection.name = name;
+    
+    var col_str = collection.authoratative_update(signing_key);
+    FileUtils.set_contents(location, col_str, col_str.length);
 
-    else if(args[1] == "add-domain") {
-        collection.domains.add(args[3]);
-        collection.authoratative_update(signing_key);
-    }
+    var key_str = Base64.encode(signing_key);
+    FileUtils.set_contents(location + ".secret", key_str, key_str.length);
+}
 
-    else if(args[1] == "publish") {
-        var member = collection.members.single(m => m.name == args[4]);
+public void resign_ppcl(string location, uint8[] key, string output_location) throws Error {
+    var old = File.new_for_path(location).read();
+    var stream = new DataInputStream(old);
+    var collection = new Ppub.Collection.from_stream(stream, false);
 
-        var checksum = Ppub.CollectionPublication.generate_ppub_checksum(File.new_for_path(args[3]));
-        
-        string cred_str;
-        FileUtils.get_contents(args[4] + ".credentials", out cred_str, null);
-        var credentials = new Ppub.CollectionMemberCredentials.from_string(cred_str);
-
-        var publication = new Ppub.CollectionPublication(args[3], new DateTime.now_local(), args[4], credentials, checksum);
-        collection.publications.add(publication);
-        var secret = member.decrypt_collection_secret(credentials);
-        collection.member_update(secret);
-    }
-    else {
-        print(@"Unrecognised command $(args[1])\n");
-        return -2;
+    collection.authoratative_update(key);
+    var col_str = collection.to_string();
+    FileUtils.set_contents(output_location, col_str, col_str.length);
+}
+
+public void verify(string location) throws Error {
+    var file = File.new_for_path(location).read();
+    var stream = new DataInputStream(file);
+    var collection = new Ppub.Collection.from_stream(stream, true);
+    if(collection.name != null && collection.name.length > 0) {
+        print(@"Collection \"$(collection.name)\" is good\n");
+        return;
     }
+    print(@"Collection \"$location\" is good\n");
+}
 
-    var col_str = collection.to_string();
-    FileUtils.set_contents(args[2], col_str, col_str.length);
+public void publish(string ppub, string credential_path, string member_name, DateTime publication_timestamp) throws Error {
+    string cred_str;
+    FileUtils.get_contents(credential_path, out cred_str, null);
+    var credentials = new Ppub.CollectionMemberCredentials.from_string(cred_str);
+
+    var ppub_file = File.new_for_path(ppub);
+    var checksum = Ppub.CollectionPublication.generate_ppub_checksum(ppub_file);
+    var publication = new Ppub.CollectionPublication(ppub_file.get_basename(), publication_timestamp, member_name, credentials, checksum);
 
-    return 0;
+    print(@"$publication\n");
 }

+ 5 - 2
src/tools/pprf/Pprf.vala

@@ -2,14 +2,14 @@
 
 public static int main(string[] args) {
 
-    var creds = new Ppub.CollectionMemberCredentials.from_string("PYuKgL7SdQYc2Kf6UGG9pCE58m27qrYnCaM45cnxs64=:JbJ6OoNn2KcGX+Tk5C/hotGZCoHOkTNbadUrlk6aCRs=:tL+557eP7kE6ObAW0b5RjvYyU8Dl3oVTOvYA7LAwSdI9i4qAvtJ1BhzYp/pQYb2kITnybbuqticJozjlyfGzrg==:AJbFO6n/cOuD7kk+wu7DmQ58w6z0G3HsukVmIzxGaUM=");
+    var creds = new Ppub.CollectionMemberCredentials.from_string("PPCLMC\nPKSIG EDXLsUvOZEne+xcv+huvSaqNBs8TTldCv6hd69GdmYw=\nPKENC WF6pIAlSeol1Ikpr2N5iO8fsjoFGf02DGafJ/aBuSB4=\nSKSIG 0njSgyndiE1tND5IGM8rrd/6QQQ284mDYGarkjfK57MQNcuxS85kSd77Fy/6G69Jqo0GzxNOV0K/qF3r0Z2ZjA==\nSKENC R18jlqqbdjmf4lbocONVudFfNoB+mhSe+iffoH8DEN4=");
 
     var uri = new Ppub.CollectionUri.from_string(args[2]);
     var collection_id = new Invercargill.BinaryData.from_byte_array(uri.collection_id); //new Invercargill.BinaryData.from_base64("y8ibw54A93LDBKbgWm1EJ/WlbOkGX60DK+qp2lBHpjk=");
     
     var upload_file = File.new_for_path(args[1]);
     var file_size = upload_file.query_info("*", FileQueryInfoFlags.NONE).get_size();
-    var client = new Pprf.HttpPprfClient("https://billy.barrow.nz/xdelta/pprf.php");
+    var client = new Pprf.HttpPprfClient("http://localhost:8080/");
     var file_name = upload_file.get_basename();
     
     var collection = client.get_collection(collection_id);
@@ -42,6 +42,9 @@ public static int main(string[] args) {
 
     print("Publishing publication\n");
     client.publish(collection_id, publication, member);
+
+    print("Rebuilding index\n");
+    client.rebuild_index(collection_id, member);
     
     print("Done\n");
     return 0;