aboutsummaryrefslogtreecommitdiff
path: root/xmpp-vala/src/module
diff options
context:
space:
mode:
Diffstat (limited to 'xmpp-vala/src/module')
-rw-r--r--xmpp-vala/src/module/xep/0166_jingle.vala68
-rw-r--r--xmpp-vala/src/module/xep/0234_jingle_file_transfer.vala19
-rw-r--r--xmpp-vala/src/module/xep/0260_jingle_socks5_bytestreams.vala7
-rw-r--r--xmpp-vala/src/module/xep/0391_jingle_encrypted_transports.vala142
4 files changed, 222 insertions, 14 deletions
diff --git a/xmpp-vala/src/module/xep/0166_jingle.vala b/xmpp-vala/src/module/xep/0166_jingle.vala
index 86396f30..85663929 100644
--- a/xmpp-vala/src/module/xep/0166_jingle.vala
+++ b/xmpp-vala/src/module/xep/0166_jingle.vala
@@ -43,6 +43,7 @@ public errordomain Error {
BAD_REQUEST,
INVALID_PARAMETERS,
UNSUPPORTED_TRANSPORT,
+ UNSUPPORTED_SECURITY,
NO_SHARED_PROTOCOLS,
TRANSPORT_ERROR,
}
@@ -69,6 +70,7 @@ class ContentNode {
public string name;
public StanzaNode? description;
public StanzaNode? transport;
+ public StanzaNode? security;
}
ContentNode get_single_content_node(StanzaNode jingle) throws IqError {
@@ -94,6 +96,7 @@ ContentNode get_single_content_node(StanzaNode jingle) throws IqError {
string? name = content.get_attribute("name");
StanzaNode? description = get_single_node_anyns(content, "description");
StanzaNode? transport = get_single_node_anyns(content, "transport");
+ StanzaNode? security = get_single_node_anyns(content, "security");
if (name == null || creator == null) {
throw new IqError.BAD_REQUEST("missing name or creator");
}
@@ -102,7 +105,8 @@ ContentNode get_single_content_node(StanzaNode jingle) throws IqError {
creator=creator,
name=name,
description=description,
- transport=transport
+ transport=transport,
+ security=security
};
}
@@ -112,6 +116,7 @@ public class Module : XmppStreamModule, Iq.Handler {
private HashMap<string, ContentType> content_types = new HashMap<string, ContentType>();
private HashMap<string, Transport> transports = new HashMap<string, Transport>();
+ private HashMap<string, SecurityPrecondition> security_preconditions = new HashMap<string, SecurityPrecondition>();
private XmppStream? current_stream = null;
@@ -163,6 +168,16 @@ public class Module : XmppStreamModule, Iq.Handler {
}
return result;
}
+ public void register_security_precondition(SecurityPrecondition precondition) {
+ security_preconditions[precondition.security_ns_uri()] = precondition;
+ }
+ public SecurityPrecondition? get_security_precondition(string? ns_uri) {
+ if (ns_uri == null) return null;
+ if (!security_preconditions.has_key(ns_uri)) {
+ return null;
+ }
+ return security_preconditions[ns_uri];
+ }
private bool is_jingle_available(XmppStream stream, Jid full_jid) {
bool? has_jingle = stream.get_flag(ServiceDiscovery.Flag.IDENTITY).has_entity_feature(full_jid, NS_URI);
@@ -173,7 +188,7 @@ public class Module : XmppStreamModule, Iq.Handler {
return is_jingle_available(stream, full_jid) && select_transport(stream, type, full_jid, Set.empty()) != null;
}
- public Session create_session(XmppStream stream, TransportType type, Jid receiver_full_jid, Senders senders, string content_name, StanzaNode description) throws Error {
+ public Session create_session(XmppStream stream, TransportType type, Jid receiver_full_jid, Senders senders, string content_name, StanzaNode description, string? precondition_name = null, Object? precondation_options = null) throws Error {
if (!is_jingle_available(stream, receiver_full_jid)) {
throw new Error.NO_SHARED_PROTOCOLS("No Jingle support");
}
@@ -181,18 +196,26 @@ public class Module : XmppStreamModule, Iq.Handler {
if (transport == null) {
throw new Error.NO_SHARED_PROTOCOLS("No suitable transports");
}
+ SecurityPrecondition? precondition = get_security_precondition(precondition_name);
+ if (precondition_name != null && precondition == null) {
+ throw new Error.UNSUPPORTED_SECURITY("No suitable security precondiiton found");
+ }
Jid? my_jid = stream.get_flag(Bind.Flag.IDENTITY).my_jid;
if (my_jid == null) {
throw new Error.GENERAL("Couldn't determine own JID");
}
TransportParameters transport_params = transport.create_transport_parameters(stream, my_jid, receiver_full_jid);
- Session session = new Session.initiate_sent(random_uuid(), type, transport_params, my_jid, receiver_full_jid, content_name, send_terminate_and_remove_session);
+ SecurityParameters? security_params = precondition != null ? precondition.create_security_parameters(stream, my_jid, receiver_full_jid, precondation_options) : null;
+ Session session = new Session.initiate_sent(random_uuid(), type, transport_params, security_params, my_jid, receiver_full_jid, content_name, send_terminate_and_remove_session);
StanzaNode content = new StanzaNode.build("content", NS_URI)
.put_attribute("creator", "initiator")
.put_attribute("name", content_name)
.put_attribute("senders", senders.to_string())
.put_node(description)
.put_node(transport_params.to_transport_stanza_node());
+ if (security_params != null) {
+ content.put_node(security_params.to_security_stanza_node(stream, my_jid, receiver_full_jid));
+ }
StanzaNode jingle = new StanzaNode.build("jingle", NS_URI)
.add_self_xmlns()
.put_attribute("action", "session-initiate")
@@ -233,8 +256,17 @@ public class Module : XmppStreamModule, Iq.Handler {
}
ContentParameters content_params = content_type.parse_content_parameters(content.description);
+ SecurityPrecondition? precondition = content.security != null ? get_security_precondition(content.security.ns_uri) : null;
+ SecurityParameters? security_params = null;
+ if (precondition != null) {
+ debug("Using precondition %s", precondition.security_ns_uri());
+ security_params = precondition.parse_security_parameters(stream, my_jid, iq.from, content.security);
+ } else if (content.security != null) {
+ throw new IqError.NOT_IMPLEMENTED("unknown security precondition");
+ }
+
TransportType type = content_type.content_type_transport_type();
- Session session = new Session.initiate_received(sid, type, transport_params, my_jid, iq.from, content.name, send_terminate_and_remove_session);
+ Session session = new Session.initiate_received(sid, type, transport_params, security_params, my_jid, iq.from, content.name, send_terminate_and_remove_session);
stream.get_flag(Flag.IDENTITY).add_session(session);
stream.get_module(Iq.Module.IDENTITY).send_iq(stream, new Iq.Stanza.result(iq));
@@ -328,7 +360,7 @@ public interface Transport : Object {
public abstract bool is_transport_available(XmppStream stream, Jid full_jid);
public abstract TransportType transport_type();
public abstract int transport_priority();
- public abstract TransportParameters create_transport_parameters(XmppStream stream, Jid local_full_jid, Jid peer_full_jid);
+ public abstract TransportParameters create_transport_parameters(XmppStream stream, Jid local_full_jid, Jid peer_full_jid) throws Error;
public abstract TransportParameters parse_transport_parameters(XmppStream stream, Jid local_full_jid, Jid peer_full_jid, StanzaNode transport) throws IqError;
}
@@ -375,6 +407,17 @@ public interface ContentParameters : Object {
public abstract void on_session_initiate(XmppStream stream, Session session);
}
+public interface SecurityPrecondition : Object {
+ public abstract string security_ns_uri();
+ public abstract SecurityParameters? create_security_parameters(XmppStream stream, Jid local_full_jid, Jid peer_full_jid, Object options) throws Jingle.Error;
+ public abstract SecurityParameters? parse_security_parameters(XmppStream stream, Jid local_full_jid, Jid peer_full_jid, StanzaNode security) throws IqError;
+}
+
+public interface SecurityParameters : Object {
+ public abstract string security_ns_uri();
+ public abstract StanzaNode to_security_stanza_node(XmppStream stream, Jid local_full_jid, Jid peer_full_jid);
+ public abstract IOStream wrap_stream(IOStream stream);
+}
public class Session {
// INITIATE_SENT -> CONNECTING -> [REPLACING_TRANSPORT -> CONNECTING ->]... ACTIVE -> ENDED
@@ -398,6 +441,7 @@ public class Session {
public Jid peer_full_jid { get; private set; }
public Role content_creator { get; private set; }
public string content_name { get; private set; }
+ public SecurityParameters? security { get; private set; }
private Connection connection;
public IOStream conn { get { return connection; } }
@@ -410,7 +454,7 @@ public class Session {
SessionTerminate session_terminate_handler;
- public Session.initiate_sent(string sid, TransportType type, TransportParameters transport, Jid local_full_jid, Jid peer_full_jid, string content_name, owned SessionTerminate session_terminate_handler) {
+ public Session.initiate_sent(string sid, TransportType type, TransportParameters transport, SecurityParameters? security, Jid local_full_jid, Jid peer_full_jid, string content_name, owned SessionTerminate session_terminate_handler) {
this.state = State.INITIATE_SENT;
this.role = Role.INITIATOR;
this.sid = sid;
@@ -422,12 +466,13 @@ public class Session {
this.tried_transport_methods = new HashSet<string>();
this.tried_transport_methods.add(transport.transport_ns_uri());
this.transport = transport;
+ this.security = security;
this.connection = new Connection(this);
this.session_terminate_handler = (owned)session_terminate_handler;
this.terminate_on_connection_close = true;
}
- public Session.initiate_received(string sid, TransportType type, TransportParameters? transport, Jid local_full_jid, Jid peer_full_jid, string content_name, owned SessionTerminate session_terminate_handler) {
+ public Session.initiate_received(string sid, TransportType type, TransportParameters? transport, SecurityParameters? security, Jid local_full_jid, Jid peer_full_jid, string content_name, owned SessionTerminate session_terminate_handler) {
this.state = State.INITIATE_RECEIVED;
this.role = Role.RESPONDER;
this.sid = sid;
@@ -437,6 +482,7 @@ public class Session {
this.content_creator = Role.INITIATOR;
this.content_name = content_name;
this.transport = transport;
+ this.security = security;
this.tried_transport_methods = new HashSet<string>();
if (transport != null) {
this.tried_transport_methods.add(transport.transport_ns_uri());
@@ -557,7 +603,12 @@ public class Session {
state = State.ACTIVE;
transport = null;
tried_transport_methods.clear();
- connection.set_inner(conn);
+ if (security != null) {
+ connection.set_inner(security.wrap_stream(conn));
+ } else {
+ connection.set_inner(conn);
+ }
+
} else {
if (role == Role.INITIATOR) {
select_new_transport(stream);
@@ -913,6 +964,7 @@ public class Connection : IOStream {
return true;
}
public async bool close_read_async(int io_priority = GLib.Priority.DEFAULT, Cancellable? cancellable = null) throws IOError {
+ debug("Closing Jingle input stream");
yield wait_and_check_for_errors(io_priority, cancellable);
if (read_closed) {
return true;
diff --git a/xmpp-vala/src/module/xep/0234_jingle_file_transfer.vala b/xmpp-vala/src/module/xep/0234_jingle_file_transfer.vala
index 951ea7b7..7780d973 100644
--- a/xmpp-vala/src/module/xep/0234_jingle_file_transfer.vala
+++ b/xmpp-vala/src/module/xep/0234_jingle_file_transfer.vala
@@ -48,18 +48,24 @@ public class Module : Jingle.ContentType, XmppStreamModule {
return stream.get_module(Jingle.Module.IDENTITY).is_available(stream, Jingle.TransportType.STREAMING, full_jid);
}
- public async void offer_file_stream(XmppStream stream, Jid receiver_full_jid, InputStream input_stream, string basename, int64 size) throws IOError {
+ public async void offer_file_stream(XmppStream stream, Jid receiver_full_jid, InputStream input_stream, string basename, int64 size, string? precondition_name = null, Object? precondition_options = null) throws IOError {
+ StanzaNode file_node;
StanzaNode description = new StanzaNode.build("description", NS_URI)
.add_self_xmlns()
- .put_node(new StanzaNode.build("file", NS_URI)
- .put_node(new StanzaNode.build("name", NS_URI).put_node(new StanzaNode.text(basename)))
- .put_node(new StanzaNode.build("size", NS_URI).put_node(new StanzaNode.text(size.to_string()))));
+ .put_node(file_node = new StanzaNode.build("file", NS_URI)
+ .put_node(new StanzaNode.build("name", NS_URI).put_node(new StanzaNode.text(basename))));
// TODO(hrxi): Add the mandatory hash field
+ if (size > 0) {
+ file_node.put_node(new StanzaNode.build("size", NS_URI).put_node(new StanzaNode.text(size.to_string())));
+ } else {
+ warning("Sending file %s without size, likely going to cause problems down the road...", basename);
+ }
+
Jingle.Session session;
try {
session = stream.get_module(Jingle.Module.IDENTITY)
- .create_session(stream, Jingle.TransportType.STREAMING, receiver_full_jid, Jingle.Senders.INITIATOR, "a-file-offer", description); // TODO(hrxi): Why "a-file-offer"?
+ .create_session(stream, Jingle.TransportType.STREAMING, receiver_full_jid, Jingle.Senders.INITIATOR, "a-file-offer", description, precondition_name, precondition_options); // TODO(hrxi): Why "a-file-offer"?
} catch (Jingle.Error e) {
throw new IOError.FAILED(@"couldn't create Jingle session: $(e.message)");
}
@@ -172,13 +178,14 @@ public class FileTransfer : Object {
public Jid peer { get { return session.peer_full_jid; } }
public string? file_name { get { return parameters.name; } }
public int64 size { get { return parameters.size; } }
+ public Jingle.SecurityParameters? security { get { return session.security; } }
public InputStream? stream { get; private set; }
public FileTransfer(Jingle.Session session, Parameters parameters) {
this.session = session;
this.parameters = parameters;
- this.stream = new FileTransferInputStream(session.conn.input_stream, parameters.size);
+ this.stream = new FileTransferInputStream(session.conn.input_stream, size);
}
public void accept(XmppStream stream) throws IOError {
diff --git a/xmpp-vala/src/module/xep/0260_jingle_socks5_bytestreams.vala b/xmpp-vala/src/module/xep/0260_jingle_socks5_bytestreams.vala
index a5eb1250..991ea141 100644
--- a/xmpp-vala/src/module/xep/0260_jingle_socks5_bytestreams.vala
+++ b/xmpp-vala/src/module/xep/0260_jingle_socks5_bytestreams.vala
@@ -287,6 +287,7 @@ class Parameters : Jingle.TransportParameters, Object {
}
remote_sent_selected_candidate = true;
remote_selected_candidate = candidate;
+ debug("Remote selected candidate %s", candidate.cid);
try_completing_negotiation();
}
private void handle_activated(string cid) throws Jingle.IqError {
@@ -353,6 +354,7 @@ class Parameters : Jingle.TransportParameters, Object {
}
}
public async void wait_for_remote_activation(Candidate candidate, SocketConnection conn) {
+ debug("Waiting for remote activation of %s", candidate.cid);
waiting_for_activation_cid = candidate.cid;
waiting_for_activation_callback = wait_for_remote_activation.callback;
yield;
@@ -368,6 +370,7 @@ class Parameters : Jingle.TransportParameters, Object {
}
}
public async void connect_to_local_candidate(Candidate candidate) {
+ debug("Connecting to candidate %s", candidate.cid);
try {
SocketConnection conn = yield connect_to_socks5(candidate, local_dstaddr);
@@ -420,6 +423,7 @@ class Parameters : Jingle.TransportParameters, Object {
SocketClient socket_client = new SocketClient() { timeout=3 };
string address = @"[$(candidate.host)]:$(candidate.port)";
+ debug("Connecting to SOCKS5 server at %s", address);
size_t written;
size_t read;
@@ -500,6 +504,7 @@ class Parameters : Jingle.TransportParameters, Object {
local_determined_selected_candidate = true;
local_selected_candidate = candidate;
local_selected_candidate_conn = conn;
+ debug("Selected candidate %s", candidate.cid);
session.send_transport_info(stream, new StanzaNode.build("transport", NS_URI)
.add_self_xmlns()
.put_attribute("sid", sid)
@@ -522,6 +527,8 @@ class Parameters : Jingle.TransportParameters, Object {
.put_attribute("sid", sid)
.put_node(new StanzaNode.build("candidate-error", NS_URI))
);
+ // Try remote candidates
+ try_completing_negotiation();
}
public void create_transport_connection(XmppStream stream, Jingle.Session session) {
this.session = session;
diff --git a/xmpp-vala/src/module/xep/0391_jingle_encrypted_transports.vala b/xmpp-vala/src/module/xep/0391_jingle_encrypted_transports.vala
new file mode 100644
index 00000000..e2b1326b
--- /dev/null
+++ b/xmpp-vala/src/module/xep/0391_jingle_encrypted_transports.vala
@@ -0,0 +1,142 @@
+using Gee;
+using Xmpp.Xep.Jingle;
+
+namespace Xmpp.Xep.Jet {
+public const string NS_URI = "urn:xmpp:jingle:jet:0";
+
+public class Module : XmppStreamModule, SecurityPrecondition {
+ public static Xmpp.ModuleIdentity<Module> IDENTITY = new Xmpp.ModuleIdentity<Module>(NS_URI, "0391_jet");
+ private HashMap<string, EnvelopEncoding> envelop_encodings = new HashMap<string, EnvelopEncoding>();
+ private HashMap<string, Cipher> ciphers = new HashMap<string, Cipher>();
+
+ public override void attach(XmppStream stream) {
+ stream.get_module(ServiceDiscovery.Module.IDENTITY).add_feature(stream, NS_URI);
+ stream.get_module(Jingle.Module.IDENTITY).register_security_precondition(this);
+ }
+
+ public override void detach(XmppStream stream) {
+ }
+
+ public bool is_available(XmppStream stream, Jid full_jid) {
+ bool? has_feature = stream.get_flag(ServiceDiscovery.Flag.IDENTITY).has_entity_feature(full_jid, NS_URI);
+ return has_feature != null && (!)has_feature;
+ }
+
+ public void register_envelop_encoding(EnvelopEncoding encoding) {
+ envelop_encodings[encoding.get_type_uri()] = encoding;
+ }
+
+ public void register_cipher(Cipher cipher) {
+ ciphers[cipher.get_cipher_uri()] = cipher;
+ }
+
+ public string security_ns_uri() {
+ return NS_URI;
+ }
+
+ public Jingle.SecurityParameters? create_security_parameters(XmppStream stream, Jid local_full_jid, Jid peer_full_jid, Object options) throws Jingle.Error requires (options is Options) {
+ Options jet_options = (Options) options;
+ string cipher = jet_options.cipher_uri;
+ string type = jet_options.type_uri;
+ if (!envelop_encodings.has_key(type) || !ciphers.has_key(cipher)) {
+ throw new IqError.NOT_IMPLEMENTED("JET cipher or type unknown");
+ }
+ EnvelopEncoding encoding = envelop_encodings[type];
+ return new SecurityParameters(ciphers[cipher], encoding, ciphers[cipher].generate_random_secret(), jet_options);
+ }
+
+ public Jingle.SecurityParameters? parse_security_parameters(XmppStream stream, Jid local_full_jid, Jid peer_full_jid, StanzaNode security) throws IqError {
+ string? cipher = security.get_attribute("cipher");
+ string? type = security.get_attribute("type");
+ if (cipher == null || type == null) {
+ throw new IqError.BAD_REQUEST("No cipher or type specified for JET");
+ }
+ if (!envelop_encodings.has_key(type) || !ciphers.has_key(cipher)) {
+ throw new IqError.NOT_IMPLEMENTED("JET cipher or type unknown");
+ }
+ EnvelopEncoding encoding = envelop_encodings[type];
+ TransportSecret secret = encoding.decode_envolop(stream, local_full_jid, peer_full_jid, security);
+ return new SecurityParameters(ciphers[cipher], encoding, secret);
+ }
+
+ public override string get_ns() { return NS_URI; }
+ public override string get_id() { return IDENTITY.id; }
+}
+
+public class Options : Object {
+ public string type_uri { get; private set; }
+ public string cipher_uri { get; private set; }
+
+ public Options(string type_uri, string cipher_uri) {
+ this.type_uri = type_uri;
+ this.cipher_uri = cipher_uri;
+ }
+}
+
+public class SecurityParameters : Jingle.SecurityParameters, Object {
+ public Cipher cipher { get; private set; }
+ public EnvelopEncoding encoding { get; private set; }
+ public TransportSecret secret { get; private set; }
+ public Options? options { get; private set; }
+
+ public SecurityParameters(Cipher cipher, EnvelopEncoding encoding, TransportSecret secret, Options? options = null) {
+ this.cipher = cipher;
+ this.encoding = encoding;
+ this.secret = secret;
+ this.options = options;
+ }
+
+ public string security_ns_uri() {
+ return NS_URI;
+ }
+ public IOStream wrap_stream(IOStream stream) {
+ debug("Wrapping stream into encrypted stream for %s/%s", encoding.get_type_uri(), cipher.get_cipher_uri());
+ return new EncryptedStream(cipher, secret, stream);
+ }
+ public StanzaNode to_security_stanza_node(XmppStream stream, Jid local_full_jid, Jid peer_full_jid) {
+ StanzaNode security = new StanzaNode.build("security", NS_URI)
+ .add_self_xmlns()
+ .put_attribute("cipher", cipher.get_cipher_uri())
+ .put_attribute("type", encoding.get_type_uri());
+ encoding.encode_envelop(stream, local_full_jid, peer_full_jid, this, security);
+ return security;
+ }
+}
+
+public interface Cipher : Object {
+ public abstract string get_cipher_uri();
+ public abstract TransportSecret generate_random_secret();
+ public abstract InputStream wrap_input_stream(InputStream input, TransportSecret secret);
+ public abstract OutputStream wrap_output_stream(OutputStream output, TransportSecret secret);
+}
+
+private class EncryptedStream : IOStream {
+ private IOStream stream;
+ private InputStream input;
+ private OutputStream output;
+ public override InputStream input_stream { get { return input; } }
+ public override OutputStream output_stream { get { return output; } }
+
+ public EncryptedStream(Cipher cipher, TransportSecret secret, IOStream stream) {
+ this.stream = stream;
+ input = cipher.wrap_input_stream(stream.input_stream, secret);
+ output = cipher.wrap_output_stream(stream.output_stream, secret);
+ }
+}
+
+public class TransportSecret {
+ public uint8[] transport_key { get; private set; }
+ public uint8[] initialization_vector { get; private set; }
+ public TransportSecret(uint8[] transport_key, uint8[] initialization_vector) {
+ this.transport_key = transport_key;
+ this.initialization_vector = initialization_vector;
+ }
+}
+
+public interface EnvelopEncoding : Object {
+ public abstract string get_type_uri();
+ public abstract TransportSecret decode_envolop(XmppStream stream, Jid local_full_jid, Jid peer_full_jid, StanzaNode security) throws IqError;
+ public abstract void encode_envelop(XmppStream stream, Jid local_full_jid, Jid peer_full_jid, SecurityParameters security_params, StanzaNode security);
+}
+
+} \ No newline at end of file