Переглянути джерело

PPRF+PPCL happy path to upload and publish to php-ppub

Billy Barrow 1 рік тому
батько
коміт
cd55081580

+ 15 - 2
src/lib/Ppcl.vala

@@ -15,6 +15,7 @@ namespace Ppub {
     
     public class Collection {
         public uint8[] id { get; private set; }
+        public string name { get; private set; }
         public int serial { get; private set; }
         public uint8[] shared_signature_key { get; private set; }
         public Vector<string> domains { get; private set; }
@@ -24,6 +25,7 @@ namespace Ppub {
         public uint8[] shared_signature { get; private set; }
         public Vector<CollectionPublication> publications { get; private set; }
         public DateTime modified { get; private set; }
+        public uint8[] current_state_token { get; private set; }
 
         private string authoritative_section;
         private string shared_section;
@@ -105,6 +107,14 @@ namespace Ppub {
 
             line = stream.read_line();
             shared_section += line;
+            var state_token_entry = line.split(" ", 2);
+            if(state_token_entry.length != 2 || state_token_entry[0] != "CST") {
+                throw new CollectionError.INVALID_FORMAT(@"Malformed or missing current state token");
+            }
+            current_state_token = Base64.decode(state_token_entry[1]);
+
+            line = stream.read_line();
+            shared_section += @"\n$line";
             var modified_entry = line.split(" ", 2);
             if(modified_entry.length != 2 || modified_entry[0] != "MOD") {
                 throw new CollectionError.INVALID_FORMAT(@"Malformed or missing last modified date");
@@ -201,7 +211,10 @@ namespace Ppub {
 
         public string member_update(uint8[] collection_secret) {
             modified = new DateTime.now_local();
-            var full_str = @"MOD $(modified.format_iso8601())\n";
+            current_state_token = new uint8[128];
+            Sodium.Random.random_bytes(current_state_token);
+
+            var full_str = @"CST $(Base64.encode(current_state_token))\nMOD $(modified.format_iso8601())\n";
             var pub_str = publications.to_string(p => p.to_string(), "\n");
             if(pub_str != "") {
                 full_str += @"\n$pub_str";
@@ -224,7 +237,7 @@ namespace Ppub {
             signed_data.append_string(data);
             var checksum_calculator = new Checksum(ChecksumType.SHA512);
             var arr = signed_data.to_array();
-            print(@"ARR.length $(arr.length), str $data\n");
+            print(@"ARR.length $(arr.length), str\n\n$data\n");
             checksum_calculator.update(arr, arr.length);
 
             var checksum = new uint8[ChecksumType.SHA512.get_length()];

+ 39 - 0
src/pprf/Client.vala

@@ -0,0 +1,39 @@
+
+using Pprf.Messages;
+using Invercargill;
+
+namespace Pprf {
+
+    public errordomain ClientError {
+        FAILED_REQUEST,
+        UNEXPECTED_RESPONSE,
+    }
+
+    public abstract class Client {
+        public abstract Message send_message(Message message) throws Error;
+
+        public Message send_authenticated_message(AuthenticatedMessage message, MemberIdentity identity) throws Error {
+            var collection = get_collection(identity.collection_id);
+            identity.refresh(collection);
+            message.authenticate(identity);
+            return send_message(message);
+        }
+
+        public Ppub.Collection get_collection(BinaryData identifier) throws Error {
+            var message = new Messages.GetCollection();
+            message.collection_id = identifier;
+
+            var response = send_message(message);
+            if(response is Messages.Failure) {
+                throw new ClientError.FAILED_REQUEST(response.message);
+            }
+            else if(response is Messages.Collection) {
+                return response.collection;
+            }
+            else {
+                throw new ClientError.UNEXPECTED_RESPONSE("Expected Collection or Failure message");
+            }
+        }
+    }
+
+}

+ 2 - 2
src/pprf/HttpClient.vala

@@ -4,7 +4,7 @@ using Invercargill;
 
 namespace Pprf {
 
-    public class HttpPprfClient {
+    public class HttpPprfClient : Client {
 
         public string endpoint { get; set; }
         public const string HTTP_CONTENT_TYPE = "application/pprf";
@@ -16,7 +16,7 @@ namespace Pprf {
             session = new Soup.Session();
         }
 
-        public Message send_message(Message message) throws Error {
+        public override Message send_message(Message message) throws Error {
             var soup_message = new Soup.Message("POST", endpoint);
 
             var streamer = new MessageStreamer(message);

+ 60 - 0
src/pprf/Identity.vala

@@ -0,0 +1,60 @@
+using Invercargill;
+using Invercargill.Convert;
+
+namespace Pprf {
+
+    public errordomain MemberIdentityError {
+        MEMBER_REMOVED,
+    }
+
+    public class MemberIdentity {
+
+        public string name { get; private set; }
+        public BinaryData collection_id { get; private set; }
+        public Ppub.CollectionMemberCredentials credentials { get; private set; }
+        public BinaryData current_state_token { get; private set; }
+        private uint8[] collection_secret;
+        private uint8[] collection_shared_signature_key;
+
+        public MemberIdentity(Ppub.Collection collection, string member_name, Ppub.CollectionMemberCredentials credentials) {
+            name = member_name;
+            this.collection_id = new BinaryData.from_byte_array(collection.id);
+            this.credentials = credentials;
+            refresh(collection);
+        }
+
+        public void refresh(Ppub.Collection collection) {
+            collection_shared_signature_key = collection.shared_signature_key;
+            collection_secret = collection.members.first(m => m.name == name).collection_secret.copy();
+            current_state_token = new BinaryData.from_byte_array(collection.current_state_token);
+        }
+
+        public uint8[] sign_with_individual_signature(uint8[] data) {
+            return Sodium.Asymmetric.Signing.sign(data, credentials.secret_signing_key);
+        }
+
+        public uint8[] sign_with_shared_signature(uint8[] data) {
+            var secret = Sodium.Asymmetric.Sealing.unseal(collection_secret, credentials.public_sealing_key, credentials.secret_sealing_key);
+            return Sodium.Asymmetric.Signing.sign(data, secret);
+        }
+        
+        public uint8[] decrypt(uint8[] data) {
+            return Sodium.Asymmetric.Sealing.unseal(data, credentials.public_sealing_key, credentials.secret_sealing_key);
+        }
+
+        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 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) {
+                    identities.add(new MemberIdentity(collection, member.name, cred));
+                }
+            }
+            return identities;
+        }
+    }
+
+}

+ 8 - 0
src/pprf/Messages/AuthenticatedMessage.vala

@@ -0,0 +1,8 @@
+
+namespace Pprf.Messages {
+
+    public interface AuthenticatedMessage : Message {
+        public abstract void authenticate(MemberIdentity identity);
+    }
+
+}

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

@@ -51,7 +51,7 @@ namespace Pprf.Messages {
         UNKNOWN_COLLECTION = 2,
         MEMBER_NOT_IN_COLLECTION = 3,
         INVALID_UPLOAD_SESSION = 4,
-        INVALID_UPLOAD_AUTH = 5,
+        AUTHENTICATION_FAILED = 5,
         INVALID_UPLOAD_OFFSET = 6,
         INVALID_NAME = 7,
         NAME_EXISTS = 8,
@@ -60,5 +60,7 @@ namespace Pprf.Messages {
         INVALID_MEMBER_SIGNATURE = 11,
         INVALID_BSPATCH_CHECKSUM = 12,
         BSPATCH_ERROR = 13,
+        MALFORMED_PUBLICATION_STRING = 14,
+        DESTINATION_PUBLISHED = 15
     }
 }

+ 47 - 0
src/pprf/Messages/GetCollection.vala

@@ -0,0 +1,47 @@
+using Invercargill;
+
+namespace Pprf.Messages {
+
+    public class GetCollection : CollectionMessage {
+        public GetCollection() {
+            message_type = MessageType.GET_COLLECTION;
+        }
+    }
+
+    public class Collection : Message {
+    
+        public Ppub.Collection collection;
+    
+        public Collection() {
+            message_type = MessageType.COLLECTION;
+        }
+    
+        public override void deserialise (GLib.DataInputStream stream) throws Error {
+            base.deserialise (stream);
+            
+            var data_len = (size_t)stream.read_uint32();
+            var data = new BinaryData();
+            while(data.read_in(stream.read_bytes(data_len), ref data_len));
+
+            var ds = new MemoryInputStream.from_data(data.to_array());
+            collection = new Ppub.Collection.from_stream(new DataInputStream(ds));
+        }
+    
+        public override uint64 calculate_size() {
+            return base.calculate_size() + 
+                4 + // Data length
+                collection.to_string().data.length;
+        }
+        
+        public override void serialise(DataOutputStream stream) throws Error {
+            base.serialise(stream);
+
+            var str = collection.to_string();
+            var data_len = str.data.length;
+            stream.put_uint32((uint16)data_len);
+            if(data_len > 0) {
+                stream.put_string(str);
+            }
+        }
+    }
+}

+ 3 - 0
src/pprf/Messages/Message.vala

@@ -95,6 +95,7 @@ namespace Pprf.Messages {
         GET_ASSET = 3,
         GET_PUBLICATION = 4,
         GET_INDEX = 5,
+        GET_COLLECTION = 6,
 
         // 32 - 63: Administration requests
         REGISTER_NAME = 32,
@@ -105,6 +106,7 @@ namespace Pprf.Messages {
         UNPUBLISH = 37,
         UPDATE_COLLECTION = 38,
         REBUILD_INDEX = 39,
+        GET_REGISTRATIONS = 40,
 
         // 64 - 95: Encapsulated messages
         ENCRYPTED_MESSAGE = 64,
@@ -124,6 +126,7 @@ namespace Pprf.Messages {
         ASSET = 132,
         PUBLICATION = 133,
         TRY_FIRST = 134,
+        COLLECTION = 135,
 
         // 160 - 191: Server responses to administration requests
         UPLOAD_SESSION = 160,

+ 4 - 0
src/pprf/Messages/MessageFactory.vala

@@ -5,6 +5,8 @@ namespace Pprf.Messages {
 
         public static Message construct_message(MessageType mtype) {
             switch (mtype) {
+                case MessageType.GET_COLLECTION:
+                    return new GetCollection();
                 case MessageType.REGISTER_NAME:
                     return new RegisterName();
                 case MessageType.BEGIN_UPLOAD:
@@ -17,6 +19,8 @@ namespace Pprf.Messages {
                     return new FinaliseUpload();
                 case MessageType.PUBLISH:
                     return new RegisterName();
+                case MessageType.COLLECTION:
+                    return new Collection();
                 case MessageType.CONFIRMATION:
                     return new Confirmation();
                 case MessageType.FAILURE:

+ 25 - 2
src/pprf/Messages/Publish.vala

@@ -2,13 +2,22 @@ using Invercargill;
 
 namespace Pprf.Messages {
 
-    public class Publish : CollectionMessage {
+    public class Publish : CollectionMessage, AuthenticatedMessage {
     
         public string publication_string { get; set; }
+        public uint8[] authentication { get; set; }
     
         public Publish() {
             message_type = MessageType.PUBLISH;
         }
+
+        public void authenticate(MemberIdentity identity) {
+            var checksum = Util.string_checksum(publication_string);
+            var auth = new BinaryData.from_byte_array(checksum);
+            auth.append(identity.current_state_token);
+
+            authentication = identity.sign_with_individual_signature(auth.to_array());
+        }
     
         public override void deserialise (GLib.DataInputStream stream) throws Error {
             base.deserialise (stream);
@@ -17,12 +26,20 @@ namespace Pprf.Messages {
             if(data_len > 0) {
                 publication_string = new BinaryData.from_bytes(stream.read_bytes (data_len)).to_raw_string();
             }
+
+            data_len = stream.read_uint16();
+            if(data_len > 0) {
+                authentication = new uint8[data_len];
+                stream.read(authentication);
+            }
         }
     
         public override uint64 calculate_size() {
             return base.calculate_size() + 
                 2 + // Data length
-                publication_string.data.length;
+                publication_string.data.length +
+                2 + // Authentication length
+                authentication.length;
         }
         
         public override void serialise(DataOutputStream stream) throws Error {
@@ -33,6 +50,12 @@ namespace Pprf.Messages {
             if(data_len > 0) {
                 stream.put_string(publication_string);
             }
+
+            data_len = authentication.length;
+            stream.put_uint16((uint16)data_len);
+            if(data_len > 0) {
+                stream.write(authentication);
+            }
         }
     }
 }

+ 37 - 0
src/pprf/Util.vala

@@ -0,0 +1,37 @@
+using Invercargill;
+namespace Pprf.Util {
+
+    public static uint8[] string_checksum(string str) {
+        return data_checksum(str.data);
+    }
+
+    public static uint8[] data_checksum(uint8[] data) {
+        var checksum_calculator = new Checksum(ChecksumType.SHA512);
+        checksum_calculator.update(data, data.length);
+
+        var checksum = new uint8[ChecksumType.SHA512.get_length()];
+        size_t chk_len = checksum.length;
+        checksum_calculator.get_digest(checksum, ref chk_len);
+        checksum.length = (int)chk_len;
+        return checksum;
+    }
+
+    public static uint8[] file_checksum(File file) throws Error {
+        var buffer = new uint8[10240];
+        var checksum = new Checksum(ChecksumType.SHA512);
+        var stream = file.read();
+        while(true) {
+            var read = stream.read(buffer);
+            if(read == 0) {
+                break;
+            }
+            checksum.update(buffer, read);
+        }
+        size_t dig_len = 64;
+        var digest = new uint8[64];
+        checksum.get_digest(digest, ref dig_len);
+        digest.length = (int)dig_len;
+        return digest;
+    }
+
+}

+ 6 - 1
src/pprf/meson.build

@@ -12,10 +12,14 @@ dependencies = [
     ppub_dep,
 ]
 
-sources = files('HttpClient.vala')
+sources = files('Client.vala')
+sources += files('HttpClient.vala')
+sources += files('Util.vala')
+sources += files('Identity.vala')
 sources += files('Messages/Message.vala')
 sources += files('Messages/CollectionMessage.vala')
 sources += files('Messages/UploadSessionMessage.vala')
+sources += files('Messages/AuthenticatedMessage.vala')
 sources += files('Messages/MessageFactory.vala')
 sources += files('Messages/GetAsset.vala')
 sources += files('Messages/BeginUpload.vala')
@@ -25,6 +29,7 @@ sources += files('Messages/Publish.vala')
 sources += files('Messages/Failure.vala')
 sources += files('Messages/Confirmation.vala')
 sources += files('Messages/UploadSession.vala')
+sources += files('Messages/GetCollection.vala')
 
 sources += files('Messages/GetListing.vala')
 sources += files('Messages/GetPublicationInformation.vala')

+ 5 - 0
src/tools/ppcl/Ppcl.vala

@@ -41,6 +41,11 @@ public static int main(string[] args) {
         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);
+    }
+
     else if(args[1] == "add-domain") {
         collection.domains.add(args[3]);
         collection.authoratative_update(signing_key);

+ 31 - 6
src/tools/pprf/Pprf.vala

@@ -4,10 +4,21 @@ public static int main(string[] args) {
 
     var creds = new Ppub.CollectionMemberCredentials.from_string("PYuKgL7SdQYc2Kf6UGG9pCE58m27qrYnCaM45cnxs64=:JbJ6OoNn2KcGX+Tk5C/hotGZCoHOkTNbadUrlk6aCRs=:tL+557eP7kE6ObAW0b5RjvYyU8Dl3oVTOvYA7LAwSdI9i4qAvtJ1BhzYp/pQYb2kITnybbuqticJozjlyfGzrg==:AJbFO6n/cOuD7kk+wu7DmQ58w6z0G3HsukVmIzxGaUM=");
 
+    var 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("http://localhost:8080/test.php");
     var file_name = upload_file.get_basename();
+    
+    var collection = client.get_collection(collection_id);
+    var member = Pprf.MemberIdentity.get_usable_identities(Invercargill.single(creds), collection).first_or_default();
+    if(member == null) {
+        print("No usable identity\n");
+        return -2;
+    }
+
+    print(@"Acting as $(member.name)\n");
 
     //  print("Regisering name\n");
     //  var rmessage = new Pprf.Messages.RegisterName();
@@ -23,9 +34,9 @@ public static int main(string[] args) {
 
     print("Beginning upload\n");
     var message = new Pprf.Messages.BeginUpload();
-    message.collection_id = new Invercargill.BinaryData.from_base64("ctA1tlLAk1hZgEvHcstCJWM+0OkdT0tYdIUeNmRvC5o=");
+    message.collection_id = collection_id;
     message.file_size = file_size;
-    message.member_name = "billy";
+    message.member_name = member.name;
 
     var response = client.send_message(message);
     if(response is Pprf.Messages.Failure) {
@@ -51,7 +62,7 @@ public static int main(string[] args) {
         digest.length = (int)dig_len;
 
         var umessage = new Pprf.Messages.Upload();
-        umessage.collection_id = new Invercargill.BinaryData.from_base64("ctA1tlLAk1hZgEvHcstCJWM+0OkdT0tYdIUeNmRvC5o=");
+        umessage.collection_id = collection_id;
         umessage.offset = offset;
         umessage.upload_chunk = new Pprf.Messages.BytesMessageBody(data);
         umessage.authenticate(session.session_authentication, digest, creds);
@@ -72,17 +83,31 @@ public static int main(string[] args) {
     digest.length = (int)dig_len;
 
     var fmessage = new Pprf.Messages.FinaliseUpload();
-    fmessage.collection_id = new Invercargill.BinaryData.from_base64("ctA1tlLAk1hZgEvHcstCJWM+0OkdT0tYdIUeNmRvC5o=");
+    fmessage.collection_id = collection_id;
     fmessage.destination = file_name;
     fmessage.authenticate(session.session_authentication, digest, creds);
-    fmessage.bspatch_old_checksum = new Invercargill.BinaryData.from_base64("2+1VxEx6bMUfJ0g0b3vnJwDFVf/acGG5IXENvb3ElCFU9dFXi+6muFb2GXUVA8B5+PV2AWiOEKhD3RqNTYwBpw==").to_array();
-    fmessage.flags = Pprf.Messages.FinaliseUploadFlags.BSPATCH | Pprf.Messages.FinaliseUploadFlags.OVERWRITE_DESTINATION;
+    //  fmessage.bspatch_old_checksum = new Invercargill.BinaryData.from_base64("2+1VxEx6bMUfJ0g0b3vnJwDFVf/acGG5IXENvb3ElCFU9dFXi+6muFb2GXUVA8B5+PV2AWiOEKhD3RqNTYwBpw==").to_array();
+    fmessage.flags = Pprf.Messages.FinaliseUploadFlags.OVERWRITE_DESTINATION;
 
     response = client.send_message(fmessage);
     if(response is Pprf.Messages.Failure) {
         print(@"Failure $(response.code): $(response.message)\n");
         return -1;
     }
+
+    print("Publishing publication\n");
+    
+    var publication = new Ppub.CollectionPublication(file_name, new DateTime.now_local(), member.name, creds, digest);
+
+    var pmessage = new Pprf.Messages.Publish();
+    pmessage.collection_id = collection_id;
+    pmessage.publication_string = publication.to_string();
+
+    response = client.send_authenticated_message(pmessage, member);
+    if(response is Pprf.Messages.Failure) {
+        print(@"Failure $(response.code): $(response.message)\n");
+        return -1;
+    }
     
     print("Done\n");
     return 0;