diff --git a/Extensions/OMEMO/NSXMLElement+OMEMO.h b/Extensions/OMEMO/NSXMLElement+OMEMO.h index 3bb2afac1d..6cd6f37563 100644 --- a/Extensions/OMEMO/NSXMLElement+OMEMO.h +++ b/Extensions/OMEMO/NSXMLElement+OMEMO.h @@ -55,10 +55,9 @@ NS_ASSUME_NONNULL_BEGIN /** Extracts device list from PEP element */ -- (nullable NSArray*)omemo_deviceListFromItems:(OMEMOModuleNamespace)ns; +- (nullable NSArray*)omemo_deviceListFromItems; /** Extracts device list from PEP iq respnse */ -- (nullable NSArray*)omemo_deviceListFromIqResponse:(OMEMOModuleNamespace)ns; - +- (nullable NSArray*)omemo_deviceListFromIqResponse; @end NS_ASSUME_NONNULL_END diff --git a/Extensions/OMEMO/NSXMLElement+OMEMO.m b/Extensions/OMEMO/NSXMLElement+OMEMO.m index 84e4ea7f30..e58ff48f57 100644 --- a/Extensions/OMEMO/NSXMLElement+OMEMO.m +++ b/Extensions/OMEMO/NSXMLElement+OMEMO.m @@ -110,27 +110,27 @@ + (NSXMLElement*) omemo_keyTransportElementWithKeyData:(NSArray*) } /* - - - - - - - - - - + + + + + + + + + + */ -- (nullable NSArray*)omemo_deviceListFromIqResponse:(OMEMOModuleNamespace)ns { +- (nullable NSArray*)omemo_deviceListFromIqResponse { NSXMLElement *pubsub = [self elementForName:@"pubsub" xmlns:XMLNS_PUBSUB]; NSXMLElement *items = [pubsub elementForName:@"items"]; - return [items omemo_deviceListFromItems:ns]; + return [items omemo_deviceListFromItems]; } -- (nullable NSArray*)omemo_deviceListFromItems:(OMEMOModuleNamespace)ns { - if ([[self attributeStringValueForName:@"node"] isEqualToString:[OMEMOModule xmlnsOMEMODeviceList:ns]]) { - NSXMLElement * devicesList = [[self elementForName:@"item"] elementForName:@"list" xmlns:[OMEMOModule xmlnsOMEMO:ns]]; +- (nullable NSArray*)omemo_deviceListFromItems { + if ([[self attributeStringValueForName:@"node"] isEqualToString:[OMEMOModule xmlnsOMEMODeviceList]]) { + NSXMLElement * devicesList = [[self elementForName:@"item"] elementForName:@"devices" xmlns:[OMEMOModule xmlnsOMEMO]]; if (devicesList) { NSArray *children = [devicesList children]; NSMutableArray *result = [[NSMutableArray alloc] initWithCapacity:children.count]; diff --git a/Extensions/OMEMO/OMEMOModule.h b/Extensions/OMEMO/OMEMOModule.h index 18376a90c5..5464a9e6d8 100644 --- a/Extensions/OMEMO/OMEMOModule.h +++ b/Extensions/OMEMO/OMEMOModule.h @@ -137,11 +137,12 @@ typedef NS_ENUM(NSUInteger, OMEMOModuleNamespace) { #pragma mark Namespace methods -+ (NSString*) xmlnsOMEMO:(OMEMOModuleNamespace)ns; -+ (NSString*) xmlnsOMEMODeviceList:(OMEMOModuleNamespace)ns; -+ (NSString*) xmlnsOMEMODeviceListNotify:(OMEMOModuleNamespace)ns; -+ (NSString*) xmlnsOMEMOBundles:(OMEMOModuleNamespace)ns; -+ (NSString*) xmlnsOMEMOBundles:(OMEMOModuleNamespace)ns deviceId:(uint32_t)deviceId; ++ (NSString*) xmlnsOMEMO; ++ (NSString*) xmlnsOMEMODeviceList; ++ (NSString*) xmlnsOMEMODeviceListNotify; ++ (NSString*) xmlnsOMEMOBundles; + ++ (NSString*) xmlnsOMEMO:(OMEMOModuleNamespace)ns __attribute__((deprecated("Use xmlnsOMEMO instead"))); @end diff --git a/Extensions/OMEMO/OMEMOModule.m b/Extensions/OMEMO/OMEMOModule.m index 6bab077676..64da1da056 100644 --- a/Extensions/OMEMO/OMEMOModule.m +++ b/Extensions/OMEMO/OMEMOModule.m @@ -88,7 +88,7 @@ - (void) publishDeviceIds:(NSArray*)deviceIds elementId:(nullable NSS __weak id weakMulticast = multicastDelegate; [self performBlock:^{ NSString *eid = [self fixElementId:elementId]; - XMPPIQ *iq = [XMPPIQ omemo_iqPublishDeviceIds:deviceIds elementId:eid xmlNamespace:self.xmlNamespace]; + XMPPIQ *iq = [XMPPIQ omemo_iqPublishDeviceIds:deviceIds elementId:eid]; [self.tracker addElement:iq block:^(XMPPIQ *responseIq, id info) { __typeof__(self) strongSelf = weakSelf; if (!strongSelf) { return; } @@ -119,7 +119,7 @@ - (void) fetchDeviceIdsForJID:(XMPPJID*)jid return; } - NSArray *devices = [responseIq omemo_deviceListFromIqResponse:self.xmlNamespace]; + NSArray *devices = [responseIq omemo_deviceListFromIqResponse]; if (!devices) { devices = @[]; XMPPLogWarn(@"Missing devices from element: %@ %@", info.element, responseIq); @@ -141,7 +141,7 @@ - (void) fetchDeviceIdsForJID:(nonnull XMPPJID*)jid completion:(void (^_Nonnull)(XMPPIQ *responseIq, id info))completion { [self performBlock:^{ NSString *eid = [self fixElementId:elementId]; - XMPPIQ *iq = [XMPPIQ omemo_iqFetchDeviceIdsForJID:jid elementId:eid xmlNamespace:self.xmlNamespace]; + XMPPIQ *iq = [XMPPIQ omemo_iqFetchDeviceIdsForJID:jid elementId:eid]; [self.tracker addElement:iq block:completion timeout:30]; [self->xmppStream sendElement:iq]; }]; @@ -155,7 +155,7 @@ - (void) publishBundle:(OMEMOBundle*)bundle __weak id weakMulticast = multicastDelegate; [self performBlock:^{ NSString *eid = [self fixElementId:elementId]; - XMPPIQ *iq = [XMPPIQ omemo_iqPublishBundle:bundle elementId:eid xmlNamespace:self.xmlNamespace]; + XMPPIQ *iq = [XMPPIQ omemo_iqPublishBundle:bundle elementId:eid]; [self.tracker addElement:iq block:^(XMPPIQ *responseIq, id info) { __typeof__(self) strongSelf = weakSelf; if (!strongSelf) { return; } @@ -181,7 +181,7 @@ - (void) fetchBundleForDeviceId:(uint32_t)deviceId __weak id weakMulticast = multicastDelegate; [self performBlock:^{ NSString *eid = [self fixElementId:elementId]; - XMPPIQ *iq = [XMPPIQ omemo_iqFetchBundleForDeviceId:deviceId jid:jid.bareJID elementId:eid xmlNamespace:self.xmlNamespace]; + XMPPIQ *iq = [XMPPIQ omemo_iqFetchBundleForDeviceId:deviceId jid:jid.bareJID elementId:eid]; [self.tracker addElement:iq block:^(XMPPIQ *responseIq, id info) { __typeof__(self) strongSelf = weakSelf; if (!strongSelf) { return; } @@ -191,7 +191,7 @@ - (void) fetchBundleForDeviceId:(uint32_t)deviceId [weakMulticast omemo:strongSelf failedToFetchBundleForDeviceId:deviceId fromJID:jid errorIq:responseIq outgoingIq:iq]; return; } - OMEMOBundle *bundle = [responseIq omemo_bundle:strongSelf.xmlNamespace]; + OMEMOBundle *bundle = [responseIq omemo_bundle]; if (bundle) { [weakMulticast omemo:strongSelf fetchedBundle:bundle fromJID:jid responseIq:responseIq outgoingIq:iq]; } else { @@ -218,7 +218,7 @@ - (void) removeDeviceIds:(NSArray*)deviceIds elementId:(NSString *)el return; } - NSArray *devices = [responseIq omemo_deviceListFromIqResponse:strongSelf.xmlNamespace]; + NSArray *devices = [responseIq omemo_deviceListFromIqResponse]; NSIndexSet *indexSet = [devices indexesOfObjectsPassingTest:^BOOL(NSNumber * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { return [deviceIds containsObject:obj]; }]; @@ -277,7 +277,7 @@ - (void) receiveMessage:(XMPPMessage*)message forJID:(XMPPJID*)forJID isIncoming return; } // Check for incoming device list updates - NSArray *deviceIds = [message omemo_deviceListFromPEPUpdate:self.xmlNamespace]; + NSArray *deviceIds = [message omemo_deviceListFromPEPUpdate]; XMPPJID *bareJID = forJID.bareJID; if (deviceIds && message == originalMessage) { [multicastDelegate omemo:self deviceListUpdate:deviceIds fromJID:bareJID incomingElement:message]; @@ -301,37 +301,28 @@ - (void) receiveMessage:(XMPPMessage*)message forJID:(XMPPJID*)forJID isIncoming #pragma mark Namespace methods -+ (NSString*) xmlnsOMEMO:(OMEMOModuleNamespace)ns { - if (ns == OMEMOModuleNamespaceOMEMO) { - return @"urn:xmpp:omemo:0"; - } else { // OMEMOModuleNamespaceConversationsLegacy - return @"eu.siacs.conversations.axolotl"; - } ++ (NSString*) xmlnsOMEMO { + return @"urn:xmpp:omemo:2"; } -+ (NSString*) xmlnsOMEMODeviceList:(OMEMOModuleNamespace)ns { - NSString *xmlns = [self xmlnsOMEMO:ns]; - if (ns == OMEMOModuleNamespaceOMEMO) { - return [NSString stringWithFormat:@"%@:devicelist", xmlns]; - } else { // OMEMOModuleNamespaceConversationsLegacy - return [NSString stringWithFormat:@"%@.devicelist", xmlns]; - } + ++ (NSString*) xmlnsOMEMODeviceList { + return [NSString stringWithFormat:@"%@:devices", [self xmlnsOMEMO]]; } -+ (NSString*) xmlnsOMEMODeviceListNotify:(OMEMOModuleNamespace)ns { - return [NSString stringWithFormat:@"%@+notify", [self xmlnsOMEMODeviceList:ns]]; + ++ (NSString*) xmlnsOMEMODeviceListNotify { + return [NSString stringWithFormat:@"%@+notify", [self xmlnsOMEMODeviceList]]; +} + ++ (NSString*) xmlnsOMEMOBundles { + return [NSString stringWithFormat:@"%@:bundles", [self xmlnsOMEMO]]; } -+ (NSString*) xmlnsOMEMOBundles:(OMEMOModuleNamespace)ns { - NSString *xmlns = [self xmlnsOMEMO:ns]; + ++ (NSString*) xmlnsOMEMO:(OMEMOModuleNamespace)ns { if (ns == OMEMOModuleNamespaceOMEMO) { - xmlns = [NSString stringWithFormat:@"%@:bundles", xmlns]; + return @"urn:xmpp:omemo:0"; } else { // OMEMOModuleNamespaceConversationsLegacy - xmlns = [NSString stringWithFormat:@"%@.bundles", xmlns]; + return @"eu.siacs.conversations.axolotl"; } - NSParameterAssert(xmlns != nil); - return xmlns; -} - -+ (NSString*) xmlnsOMEMOBundles:(OMEMOModuleNamespace)ns deviceId:(uint32_t)deviceId { - return [NSString stringWithFormat:@"%@:%d", [self xmlnsOMEMOBundles:ns], (int)deviceId]; } #pragma mark XMPPStreamDelegate methods @@ -395,7 +386,7 @@ - (BOOL)xmppStream:(XMPPStream *)sender didReceiveIQ:(XMPPIQ *)iq { #pragma mark XMPPCapabilitiesDelegate methods - (NSArray*) myFeaturesForXMPPCapabilities:(XMPPCapabilities *)sender { - return @[[[self class] xmlnsOMEMODeviceList:self.xmlNamespace], [[self class] xmlnsOMEMODeviceListNotify:self.xmlNamespace]]; + return @[[[self class] xmlnsOMEMODeviceList], [[self class] xmlnsOMEMODeviceListNotify]]; } #pragma mark Utility diff --git a/Extensions/OMEMO/XMPPIQ+OMEMO.h b/Extensions/OMEMO/XMPPIQ+OMEMO.h index 87d41dd8bd..f9acbba612 100644 --- a/Extensions/OMEMO/XMPPIQ+OMEMO.h +++ b/Extensions/OMEMO/XMPPIQ+OMEMO.h @@ -19,27 +19,23 @@ NS_ASSUME_NONNULL_BEGIN /** iq stanza for manually fetching deviceIds list. This should be handled automatically by PEP. */ + (XMPPIQ*) omemo_iqFetchDeviceIdsForJID:(XMPPJID*)jid - elementId:(nullable NSString*)elementId - xmlNamespace:(OMEMOModuleNamespace)xmlNamespace; + elementId:(nullable NSString*)elementId; /** iq stanza for publishing your device ids. The Device IDs are integers between 1 and 2^31 - 1 */ + (XMPPIQ*) omemo_iqPublishDeviceIds:(NSArray*)deviceIds - elementId:(nullable NSString*)elementId - xmlNamespace:(OMEMOModuleNamespace)xmlNamespace; + elementId:(nullable NSString*)elementId; /** iq stanza for publishing bundle for device */ + (XMPPIQ*) omemo_iqPublishBundle:(OMEMOBundle*)bundle - elementId:(nullable NSString*)elementId - xmlNamespace:(OMEMOModuleNamespace)xmlNamespace; + elementId:(nullable NSString*)elementId; /** iq stanza for fetching remote bundle */ + (XMPPIQ*) omemo_iqFetchBundleForDeviceId:(uint32_t)deviceId jid:(XMPPJID*)jid - elementId:(nullable NSString*)elementId - xmlNamespace:(OMEMOModuleNamespace)xmlNamespace; + elementId:(nullable NSString*)elementId; /** Serialize bundle from IQ */ -- (nullable OMEMOBundle*) omemo_bundle:(OMEMOModuleNamespace)ns; +- (nullable OMEMOBundle*) omemo_bundle; @end NS_ASSUME_NONNULL_END diff --git a/Extensions/OMEMO/XMPPIQ+OMEMO.m b/Extensions/OMEMO/XMPPIQ+OMEMO.m index 2288bc3eb0..e505bf80ef 100644 --- a/Extensions/OMEMO/XMPPIQ+OMEMO.m +++ b/Extensions/OMEMO/XMPPIQ+OMEMO.m @@ -18,17 +18,19 @@ @implementation XMPPIQ (OMEMO) /** - - - - - + https://xmpp.org/extensions/xep-0384.html#example-9 + + + + + + + */ + (XMPPIQ*) omemo_iqFetchDeviceIdsForJID:(XMPPJID*)jid - elementId:(nullable NSString*)elementId - xmlNamespace:(OMEMOModuleNamespace)xmlNamespace { + elementId:(nullable NSString*)elementId { NSXMLElement *items = [NSXMLElement elementWithName:@"items"]; - [items addAttributeWithName:@"node" stringValue:[OMEMOModule xmlnsOMEMODeviceList:xmlNamespace]]; + [items addAttributeWithName:@"node" stringValue:[OMEMOModule xmlnsOMEMODeviceList]]; NSXMLElement *pubsub = [NSXMLElement elementWithName:@"pubsub" xmlns:XMLNS_PUBSUB]; [pubsub addChild:items]; @@ -38,219 +40,186 @@ + (XMPPIQ*) omemo_iqFetchDeviceIdsForJID:(XMPPJID*)jid } -/** - - - - - - - - - - - - - - - http://jabber.org/protocol/pubsub#publish-options - - - 1 - - - open - - - - - +/** + https://xmpp.org/extensions/xep-0384.html#example-2 + + + + + + + + + + + + + + + + http://jabber.org/protocol/pubsub#publish-options + + + open + + + + + + */ -+ (XMPPIQ*) omemo_iqPublishDeviceIds:(NSArray*)deviceIds elementId:(nullable NSString*)elementId xmlNamespace:(OMEMOModuleNamespace)xmlNamespace { - NSXMLElement *listElement = [NSXMLElement elementWithName:@"list" xmlns:[OMEMOModule xmlnsOMEMO:xmlNamespace]]; - [deviceIds enumerateObjectsUsingBlock:^(NSNumber * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { - NSXMLElement *device = [NSXMLElement elementWithName:@"device"]; - [device addAttributeWithName:@"id" numberValue:obj]; - [listElement addChild:device]; - }]; ++ (XMPPIQ*) omemo_iqPublishDeviceIds:(NSArray*)deviceIds elementId:(nullable NSString*)elementId { - NSXMLElement *item = [NSXMLElement elementWithName:@"item"]; - [item addChild:listElement]; + XMPPIQ *iq = [XMPPIQ iqWithType:@"set" elementID:elementId]; + + NSXMLElement *pubsub = [NSXMLElement elementWithName:@"pubsub" xmlns:XMLNS_PUBSUB]; + [iq addChild:pubsub]; NSXMLElement *publish = [NSXMLElement elementWithName:@"publish"]; - [publish addAttributeWithName:@"node" stringValue:[OMEMOModule xmlnsOMEMODeviceList:xmlNamespace]]; + [publish addAttributeWithName:@"node" stringValue:[OMEMOModule xmlnsOMEMODeviceList]]; + [pubsub addChild:publish]; + + NSXMLElement *item = [NSXMLElement elementWithName:@"item"]; + [item addAttributeWithName:@"id" stringValue:@"current"]; [publish addChild:item]; - NSXMLElement *pubsub = [NSXMLElement elementWithName:@"pubsub" xmlns:XMLNS_PUBSUB]; - [pubsub addChild:publish]; + NSXMLElement *devices = [NSXMLElement elementWithName:@"devices" xmlns:[OMEMOModule xmlnsOMEMO]]; + [item addChild:devices]; + + [deviceIds enumerateObjectsUsingBlock:^(NSNumber * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { + NSXMLElement *device = [NSXMLElement elementWithName:@"device"]; + [device addAttributeWithName:@"id" numberValue:obj]; + [devices addChild:device]; + }]; + + NSXMLElement *publishOptions = [NSXMLElement elementWithName:@"publish-options"]; + [pubsub addChild:publishOptions]; NSXMLElement *x = [NSXMLElement elementWithName:@"x" xmlns:@"jabber:x:data"]; [x addAttributeWithName:@"type" stringValue:@"submit"]; + [publishOptions addChild:x]; NSXMLElement *formTypeField = [NSXMLElement elementWithName:@"field"]; [formTypeField addAttributeWithName:@"var" stringValue:@"FORM_TYPE"]; [formTypeField addAttributeWithName:@"type" stringValue:@"hidden"]; [formTypeField addChild:[NSXMLElement elementWithName:@"value" stringValue:XMLNS_PUBSUB_PUBLISH_OPTIONS]]; - [x addChild:formTypeField]; - NSXMLElement *persistanceField = [NSXMLElement elementWithName:@"field"]; - [persistanceField addAttributeWithName:@"var" stringValue:@"pubsub#persist_items"]; - [persistanceField addChild:[NSXMLElement elementWithName:@"value" objectValue:@"1"]]; - - [x addChild:persistanceField]; - NSXMLElement *accessModelField = [NSXMLElement elementWithName:@"field"]; [accessModelField addAttributeWithName:@"var" stringValue:@"pubsub#access_model"]; - [accessModelField addChild:[NSXMLElement elementWithName:@"value" objectValue:@"open"]]; - + [accessModelField addChild:[NSXMLElement elementWithName:@"value" stringValue:@"open"]]; [x addChild:accessModelField]; - NSXMLElement *publishOptions = [NSXMLElement elementWithName:@"publish-options"]; - [publishOptions addChild:x]; - - [pubsub addChild:publishOptions]; - - XMPPIQ *iq = [XMPPIQ iqWithType:@"set" elementID:elementId]; - [iq addChild:pubsub]; - return iq; } -/** iq stanza for publishing bundle for device +/** iq stanza for publishing bundle for device - - - - - - - BASE64ENCODED... - - - BASE64ENCODED... - - - BASE64ENCODED... - - - - BASE64ENCODED... - - - BASE64ENCODED... - - - BASE64ENCODED... - - - - - - - - - - http://jabber.org/protocol/pubsub#publish-options - - - 1 - - - open - - - - - + https://xmpp.org/extensions/xep-0384.html#example-3 + https://xmpp.org/extensions/xep-0384.html#example-4 - open access model node copied over to example-3 listing + + + + + + + b64/encoded/data + b64/encoded/data + b64/encoded/data + + b64/encoded/data + b64/encoded/data + + b64/encoded/data + + + + + + + + http://jabber.org/protocol/pubsub#publish-options + + + max + + + open + + + + + */ + (XMPPIQ*) omemo_iqPublishBundle:(OMEMOBundle*)bundle - elementId:(nullable NSString*)elementId - xmlNamespace:(OMEMOModuleNamespace)xmlNamespace { - NSXMLElement *signedPreKeyElement = nil; + elementId:(nullable NSString*)elementId { + XMPPIQ *iq = [XMPPIQ iqWithType:@"set" elementID:elementId]; + + NSXMLElement *pubsub = [NSXMLElement elementWithName:@"pubsub" xmlns:XMLNS_PUBSUB]; + [iq addChild:pubsub]; + + NSXMLElement *publish = [NSXMLElement elementWithName:@"publish"]; + NSString *nodeName = [OMEMOModule xmlnsOMEMOBundles]; + [publish addAttributeWithName:@"node" stringValue:nodeName]; + [pubsub addChild:publish]; + + NSXMLElement *itemElement = [NSXMLElement elementWithName:@"item"]; + NSString *deviceId = [NSString stringWithFormat:@"%u", bundle.deviceId]; + [itemElement addAttributeWithName:@"id" stringValue:deviceId]; + [publish addChild:itemElement]; + + NSXMLElement *bundleElement = [XMPPElement elementWithName:@"bundle" xmlns:[OMEMOModule xmlnsOMEMO]]; + [itemElement addChild:bundleElement]; + if (bundle.signedPreKey.publicKey) { - signedPreKeyElement = [NSXMLElement elementWithName:@"signedPreKeyPublic" stringValue:[bundle.signedPreKey.publicKey base64EncodedStringWithOptions:0]]; - [signedPreKeyElement addAttributeWithName:@"signedPreKeyId" unsignedIntegerValue:bundle.signedPreKey.preKeyId]; + NSXMLElement *signedPreKeyElement = [NSXMLElement elementWithName:@"spk" stringValue:[bundle.signedPreKey.publicKey base64EncodedStringWithOptions:0]]; + [signedPreKeyElement addAttributeWithName:@"id" unsignedIntegerValue:bundle.signedPreKey.preKeyId]; + [bundleElement addChild:signedPreKeyElement]; } - NSXMLElement *signedPreKeySignatureElement = nil; + if (bundle.signedPreKey.signature) { - signedPreKeySignatureElement = [NSXMLElement elementWithName:@"signedPreKeySignature" stringValue:[bundle.signedPreKey.signature base64EncodedStringWithOptions:0]]; + NSXMLElement *signedPreKeySignatureElement = [NSXMLElement elementWithName:@"spks" stringValue:[bundle.signedPreKey.signature base64EncodedStringWithOptions:0]]; + [bundleElement addChild:signedPreKeySignatureElement]; } - NSXMLElement *identityKeyElement = nil; + if (bundle.identityKey) { - identityKeyElement = [NSXMLElement elementWithName:@"identityKey" stringValue:[bundle.identityKey base64EncodedStringWithOptions:0]]; + NSXMLElement *identityKeyElement = [NSXMLElement elementWithName:@"ik" stringValue:[bundle.identityKey base64EncodedStringWithOptions:0]]; + [bundleElement addChild:identityKeyElement]; } + NSXMLElement *preKeysElement = [NSXMLElement elementWithName:@"prekeys"]; + [bundleElement addChild:preKeysElement]; + [bundle.preKeys enumerateObjectsUsingBlock:^(OMEMOPreKey * _Nonnull preKey, NSUInteger idx, BOOL * _Nonnull stop) { - NSXMLElement *preKeyElement = [NSXMLElement elementWithName:@"preKeyPublic" stringValue:[preKey.publicKey base64EncodedStringWithOptions:0]]; - [preKeyElement addAttributeWithName:@"preKeyId" unsignedIntegerValue:preKey.preKeyId]; + NSXMLElement *preKeyElement = [NSXMLElement elementWithName:@"pk" stringValue:[preKey.publicKey base64EncodedStringWithOptions:0]]; + [preKeyElement addAttributeWithName:@"id" unsignedIntegerValue:preKey.preKeyId]; [preKeysElement addChild:preKeyElement]; }]; - NSXMLElement *bundleElement = [XMPPElement elementWithName:@"bundle" xmlns:[OMEMOModule xmlnsOMEMO:xmlNamespace]]; - if (signedPreKeyElement) { - [bundleElement addChild:signedPreKeyElement]; - } - if (signedPreKeySignatureElement) { - [bundleElement addChild:signedPreKeySignatureElement]; - } - if (identityKeyElement) { - [bundleElement addChild:identityKeyElement]; - } - [bundleElement addChild:preKeysElement]; - NSXMLElement *itemElement = [NSXMLElement elementWithName:@"item"]; - [itemElement addChild:bundleElement]; - NSXMLElement *publish = [NSXMLElement elementWithName:@"publish"]; - NSString *nodeName = [OMEMOModule xmlnsOMEMOBundles:xmlNamespace deviceId:bundle.deviceId]; - [publish addAttributeWithName:@"node" stringValue:nodeName]; - [publish addChild:itemElement]; - - NSXMLElement *pubsub = [NSXMLElement elementWithName:@"pubsub" xmlns:XMLNS_PUBSUB]; - [pubsub addChild:publish]; + NSXMLElement *publishOptions = [NSXMLElement elementWithName:@"publish-options"]; + [pubsub addChild:publishOptions]; NSXMLElement *x = [NSXMLElement elementWithName:@"x" xmlns:@"jabber:x:data"]; [x addAttributeWithName:@"type" stringValue:@"submit"]; + [publishOptions addChild:x]; NSXMLElement *formTypeField = [NSXMLElement elementWithName:@"field"]; [formTypeField addAttributeWithName:@"var" stringValue:@"FORM_TYPE"]; [formTypeField addAttributeWithName:@"type" stringValue:@"hidden"]; [formTypeField addChild:[NSXMLElement elementWithName:@"value" stringValue:XMLNS_PUBSUB_PUBLISH_OPTIONS]]; - [x addChild:formTypeField]; - NSXMLElement *persistanceField = [NSXMLElement elementWithName:@"field"]; - [persistanceField addAttributeWithName:@"var" stringValue:@"pubsub#persist_items"]; - [persistanceField addChild:[NSXMLElement elementWithName:@"value" objectValue:@"1"]]; - - [x addChild:persistanceField]; + NSXMLElement *maxItemsField = [NSXMLElement elementWithName:@"field"]; + [maxItemsField addAttributeWithName:@"var" stringValue:@"pubsub#max_items"]; + [maxItemsField addChild:[NSXMLElement elementWithName:@"value" stringValue:@"max"]]; + [x addChild:maxItemsField]; NSXMLElement *accessModelField = [NSXMLElement elementWithName:@"field"]; [accessModelField addAttributeWithName:@"var" stringValue:@"pubsub#access_model"]; - [accessModelField addChild:[NSXMLElement elementWithName:@"value" objectValue:@"open"]]; - + [accessModelField addChild:[NSXMLElement elementWithName:@"value" stringValue:@"open"]]; [x addChild:accessModelField]; - NSXMLElement *publishOptions = [NSXMLElement elementWithName:@"publish-options"]; - [publishOptions addChild:x]; - - [pubsub addChild:publishOptions]; - - XMPPIQ *iq = [XMPPIQ iqWithType:@"set" elementID:elementId]; - [iq addChild:pubsub]; - return iq; -} - - -+ (XMPPIQ *) omemo_iqFetchNode:(NSString *)node to:(XMPPJID *)toJID elementId:(nullable NSString*)elementId { - XMPPIQ *iq = [XMPPIQ iqWithType:@"get" to:toJID elementID:elementId]; - NSXMLElement *pubsub = [NSXMLElement elementWithName:@"pubsub" xmlns:XMLNS_PUBSUB]; - NSXMLElement *itemsElement = [NSXMLElement elementWithName:@"items"]; - [itemsElement addAttributeWithName:@"node" stringValue:node]; - - [pubsub addChild:itemsElement]; - [iq addChild:pubsub]; - return iq; } @@ -269,68 +238,87 @@ + (XMPPIQ *) omemo_iqDeleteNode:(NSString *)node elementId:(nullable NSString *) * iq stanza for fetching remote bundle - - - - + from='romeo@montague.lit' + to='juliet@capulet.lit' + id='fetch1'> + + + + + + */ + (XMPPIQ*) omemo_iqFetchBundleForDeviceId:(uint32_t)deviceId jid:(XMPPJID*)jid - elementId:(nullable NSString*)elementId - xmlNamespace:(OMEMOModuleNamespace)xmlNamespace { - NSString *nodeName = [OMEMOModule xmlnsOMEMOBundles:xmlNamespace deviceId:deviceId]; - return [self omemo_iqFetchNode:nodeName to:jid elementId:elementId]; + elementId:(nullable NSString*)elementId { + XMPPIQ *iq = [XMPPIQ iqWithType:@"get" to:jid elementID:elementId]; + + NSXMLElement *pubsub = [NSXMLElement elementWithName:@"pubsub" xmlns:XMLNS_PUBSUB]; + [iq addChild:pubsub]; + + NSXMLElement *itemsElement = [NSXMLElement elementWithName:@"items"]; + [itemsElement addAttributeWithName:@"node" stringValue:[OMEMOModule xmlnsOMEMOBundles]]; + [pubsub addChild:itemsElement]; + + NSXMLElement *itemElement = [NSXMLElement elementWithName:@"item"]; + [itemElement addAttributeWithName:@"id" stringValue:[NSString stringWithFormat:@"%u", deviceId]]; + [itemsElement addChild:itemElement]; + + return iq; } -- (nullable OMEMOBundle*) omemo_bundle:(OMEMOModuleNamespace)ns { +- (nullable OMEMOBundle*) omemo_bundle { NSXMLElement *pubsub = [self elementForName:@"pubsub" xmlns:XMLNS_PUBSUB]; if (!pubsub) { return nil; } + NSXMLElement *items = [pubsub elementForName:@"items"]; // If !items, this is a bundle and used for testing if (!items) { items = [pubsub elementForName:@"publish"]; } if (!items) { return nil; } + NSString *node = [items attributeForName:@"node"].stringValue; if (!node) { return nil; } - if (![node containsString:[OMEMOModule xmlnsOMEMOBundles:ns]]) { + if (![node isEqualToString:[OMEMOModule xmlnsOMEMOBundles]]) { return nil; } - NSString *separator = [[OMEMOModule xmlnsOMEMOBundles:ns] stringByAppendingString:@":"]; - NSArray *components = [node componentsSeparatedByString:separator]; - NSString *deviceIdString = [components lastObject]; - uint32_t deviceId = (uint32_t)[deviceIdString integerValue]; NSXMLElement *itemElement = [items elementForName:@"item"]; if (!itemElement) { return nil; } - NSXMLElement *bundleElement = [itemElement elementForName:@"bundle" xmlns:[OMEMOModule xmlnsOMEMO:ns]]; + NSString *deviceIdString = [itemElement attributeStringValueForName:@"id"]; + uint32_t deviceId = (uint32_t)[deviceIdString integerValue]; + + NSXMLElement *bundleElement = [itemElement elementForName:@"bundle" xmlns:[OMEMOModule xmlnsOMEMO]]; if (!bundleElement) { return nil; } - NSXMLElement *signedPreKeyElement = [bundleElement elementForName:@"signedPreKeyPublic"]; + + NSXMLElement *signedPreKeyElement = [bundleElement elementForName:@"spk"]; if (!signedPreKeyElement) { return nil; } - uint32_t signedPreKeyId = [signedPreKeyElement attributeUInt32ValueForName:@"signedPreKeyId"]; + uint32_t signedPreKeyId = [signedPreKeyElement attributeUInt32ValueForName:@"id"]; NSString *signedPreKeyPublicBase64 = [signedPreKeyElement stringValue]; if (!signedPreKeyPublicBase64) { return nil; } NSData *signedPreKeyPublic = [[NSData alloc] initWithBase64EncodedString:signedPreKeyPublicBase64 options:NSDataBase64DecodingIgnoreUnknownCharacters]; if (!signedPreKeyPublic) { return nil; } - NSString *signedPreKeySignatureBase64 = [[bundleElement elementForName:@"signedPreKeySignature"] stringValue]; + + NSString *signedPreKeySignatureBase64 = [[bundleElement elementForName:@"spks"] stringValue]; if (!signedPreKeySignatureBase64) { return nil; } NSData *signedPreKeySignature = [[NSData alloc] initWithBase64EncodedString:signedPreKeySignatureBase64 options:NSDataBase64DecodingIgnoreUnknownCharacters]; if (!signedPreKeySignature) { return nil; } - NSString *identityKeyBase64 = [[bundleElement elementForName:@"identityKey"] stringValue]; + + NSString *identityKeyBase64 = [[bundleElement elementForName:@"ik"] stringValue]; if (!identityKeyBase64) { return nil; } NSData *identityKey = [[NSData alloc] initWithBase64EncodedString:identityKeyBase64 options:NSDataBase64DecodingIgnoreUnknownCharacters]; if (!identityKey) { return nil; } + NSXMLElement *preKeysElement = [bundleElement elementForName:@"prekeys"]; if (!preKeysElement) { return nil; } - NSArray *preKeyElements = [preKeysElement elementsForName:@"preKeyPublic"]; + + NSArray *preKeyElements = [preKeysElement elementsForName:@"pk"]; NSMutableArray *preKeys = [NSMutableArray arrayWithCapacity:preKeyElements.count]; [preKeyElements enumerateObjectsUsingBlock:^(NSXMLElement * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { - uint32_t preKeyId = [obj attributeUInt32ValueForName:@"preKeyId"]; + uint32_t preKeyId = [obj attributeUInt32ValueForName:@"id"]; NSString *b64 = [obj stringValue]; NSData *data = nil; if (b64) { @@ -341,6 +329,7 @@ - (nullable OMEMOBundle*) omemo_bundle:(OMEMOModuleNamespace)ns { [preKeys addObject:preKey]; } }]; + OMEMOSignedPreKey *signedPreKey = [[OMEMOSignedPreKey alloc] initWithPreKeyId:signedPreKeyId publicKey:signedPreKeyPublic signature:signedPreKeySignature]; OMEMOBundle *bundle = [[OMEMOBundle alloc] initWithDeviceId:deviceId identityKey:identityKey signedPreKey:signedPreKey preKeys:preKeys]; return bundle; diff --git a/Extensions/OMEMO/XMPPMessage+OMEMO.h b/Extensions/OMEMO/XMPPMessage+OMEMO.h index c652ebd348..f08165aba3 100644 --- a/Extensions/OMEMO/XMPPMessage+OMEMO.h +++ b/Extensions/OMEMO/XMPPMessage+OMEMO.h @@ -18,7 +18,7 @@ NS_ASSUME_NONNULL_BEGIN @interface XMPPMessage (OMEMO) /** Extracts device list from PEP update */ -- (nullable NSArray*)omemo_deviceListFromPEPUpdate:(OMEMOModuleNamespace)ns; +- (nullable NSArray*)omemo_deviceListFromPEPUpdate; /** In order to send a chat message, its first has to be encrypted. The client MUST use fresh, randomly generated key/IV pairs with AES-128 in Galois/Counter Mode (GCM). For each intended recipient device, i.e. both own devices as well as devices associated with the contact, this key is encrypted using the corresponding long-standing axolotl session. Each encrypted payload key is tagged with the recipient device's ID. This is all serialized into a MessageElement. diff --git a/Extensions/OMEMO/XMPPMessage+OMEMO.m b/Extensions/OMEMO/XMPPMessage+OMEMO.m index 45c0c53457..32cf9c98ab 100644 --- a/Extensions/OMEMO/XMPPMessage+OMEMO.m +++ b/Extensions/OMEMO/XMPPMessage+OMEMO.m @@ -19,13 +19,13 @@ @implementation XMPPMessage (OMEMO) -- (nullable NSArray*)omemo_deviceListFromPEPUpdate:(OMEMOModuleNamespace)ns +- (nullable NSArray*)omemo_deviceListFromPEPUpdate { NSXMLElement *event = [self elementForName:@"event" xmlns:XMLNS_PUBSUB_EVENT]; if (!event) { return nil; } NSXMLElement * itemsList = [event elementForName:@"items"]; if (!itemsList) { return nil; } - return [itemsList omemo_deviceListFromItems:ns]; + return [itemsList omemo_deviceListFromItems]; } /** diff --git a/Extensions/XEP-0066/XMPPMessage+XEP_0066.m b/Extensions/XEP-0066/XMPPMessage+XEP_0066.m index 13e78db538..91a87fcce4 100644 --- a/Extensions/XEP-0066/XMPPMessage+XEP_0066.m +++ b/Extensions/XEP-0066/XMPPMessage+XEP_0066.m @@ -14,9 +14,9 @@ - (void)addOutOfBandURL:(NSURL *)URL desc:(NSString *)desc { NSXMLElement *outOfBand = [NSXMLElement elementWithName:NAME_OUT_OF_BAND xmlns:XMLNS_OUT_OF_BAND]; - if([[URL path] length]) + if([[URL absoluteString] length]) { - NSXMLElement *URLElement = [NSXMLElement elementWithName:@"url" stringValue:[URL path]]; + NSXMLElement *URLElement = [NSXMLElement elementWithName:@"url" stringValue:[URL absoluteString]]; [outOfBand addChild:URLElement]; } diff --git a/Extensions/XEP-0077/XMPPRegistration.h b/Extensions/XEP-0077/XMPPRegistration.h index 6233b7d1b4..3e5b44e6d1 100644 --- a/Extensions/XEP-0077/XMPPRegistration.h +++ b/Extensions/XEP-0077/XMPPRegistration.h @@ -15,6 +15,8 @@ NS_ASSUME_NONNULL_BEGIN XMPPIDTracker *xmppIDTracker; } +- (BOOL)registerWithFields:(NSDictionary *)fields; + /** * This method will attempt to change the current user's password to the new one provided. The * user *MUST* be authenticated for this to work successfully. @@ -54,6 +56,9 @@ NS_ASSUME_NONNULL_BEGIN @protocol XMPPRegistrationDelegate @optional +- (void)registrationSuccesful:(XMPPRegistration *)sender; +- (void)registrationFailed:(XMPPRegistration *)sender withError:(nullable NSError *)error; + /** * Implement this method when calling [regInstance changePassword:]. It will be invoked * if the request for changing the user's password is successfully executed and receives a diff --git a/Extensions/XEP-0077/XMPPRegistration.m b/Extensions/XEP-0077/XMPPRegistration.m index 9367a75a65..bca39c8d2a 100644 --- a/Extensions/XEP-0077/XMPPRegistration.m +++ b/Extensions/XEP-0077/XMPPRegistration.m @@ -28,6 +28,44 @@ - (void)willDeactivate #pragma mark Public API //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +- (BOOL)registerWithFields:(NSDictionary *)fields +{ + if ([xmppStream isAuthenticated]) + return NO; + + dispatch_block_t block = ^{ + @autoreleasepool { + NSString *toStr = self->xmppStream.myJID.domain; + NSXMLElement *query = [NSXMLElement elementWithName:@"query" xmlns:@"jabber:iq:register"]; + + for (NSString *fieldName in fields) { + NSXMLElement *field = [NSXMLElement elementWithName:fieldName + stringValue:fields[fieldName]]; + [query addChild:field]; + } + + XMPPIQ *iq = [XMPPIQ iqWithType:@"set" + to:[XMPPJID jidWithString:toStr] + elementID:[self->xmppStream generateUUID] + child:query]; + + [self->xmppIDTracker addID:[iq elementID] + target:self + selector:@selector(handleRegistrationQueryIQ:withInfo:) + timeout:60]; + + [self->xmppStream sendElement:iq]; + } + }; + + if (dispatch_get_specific(moduleQueueTag)) + block(); + else + dispatch_async(moduleQueue, block); + + return YES; +} + /** * This method provides functionality of XEP-0077 3.3 User Changes Password. * @@ -137,6 +175,44 @@ - (BOOL)cancelRegistrationUsingPassword:(NSString *)password #pragma mark - XMPPIDTracker //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +- (void)handleRegistrationQueryIQ:(XMPPIQ *)iq withInfo:(XMPPBasicTrackingInfo *)info +{ + dispatch_block_t block = ^{ + @autoreleasepool { + NSXMLElement *errorElem = [iq elementForName:@"error"]; + + if (errorElem) { + NSString *errMsg = [[errorElem children] componentsJoinedByString:@", "]; + NSInteger errCode = [errorElem attributeIntegerValueForName:@"code" + withDefaultValue:-1]; + NSDictionary *errInfo = @{NSLocalizedDescriptionKey : errMsg}; + NSError *err = [NSError errorWithDomain:XMPPRegistrationErrorDomain + code:errCode + userInfo:errInfo]; + + [self->multicastDelegate registrationFailed:self + withError:err]; + return; + } + + NSString *type = [iq type]; + + if ([type isEqualToString:@"result"]) { + [self->multicastDelegate registrationSuccesful:self]; + } else { + // this should be impossible to reach, but just for safety's sake... + [self->multicastDelegate registrationFailed:self + withError:nil]; + } + } + }; + + if (dispatch_get_specific(moduleQueueTag)) + block(); + else + dispatch_async(moduleQueue, block); +} + /** * This method handles the response received (or not received) after calling changePassword. */ diff --git a/Extensions/XEP-0136/CoreDataStorage/XMPPMessageArchivingCoreDataStorage.m b/Extensions/XEP-0136/CoreDataStorage/XMPPMessageArchivingCoreDataStorage.m index d4b801e475..de644e946c 100644 --- a/Extensions/XEP-0136/CoreDataStorage/XMPPMessageArchivingCoreDataStorage.m +++ b/Extensions/XEP-0136/CoreDataStorage/XMPPMessageArchivingCoreDataStorage.m @@ -128,6 +128,11 @@ - (void)willInsertMessage:(XMPPMessageArchiving_Message_CoreDataObject *)message // Override hook } +- (void)didInsertMessage:(XMPPMessageArchiving_Message_CoreDataObject *)message +{ + // Override hook +} + - (void)didUpdateMessage:(XMPPMessageArchiving_Message_CoreDataObject *)message { // Override hook @@ -148,6 +153,12 @@ - (void)didUpdateContact:(XMPPMessageArchiving_Contact_CoreDataObject *)contact // Override hook } +- (BOOL)shouldInsertContactForMessage:(XMPPMessageArchiving_Message_CoreDataObject *)message +{ + // Override hook + return YES; +} + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// #pragma mark Private API //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -482,6 +493,7 @@ - (void)archiveMessage:(XMPPMessage *)message outgoing:(BOOL)isOutgoing xmppStre [archivedMessage willInsertObject]; // Override hook [self willInsertMessage:archivedMessage]; // Override hook [moc insertObject:archivedMessage]; + [self didInsertMessage:archivedMessage]; // Override hook } else { @@ -500,7 +512,7 @@ - (void)archiveMessage:(XMPPMessage *)message outgoing:(BOOL)isOutgoing xmppStre XMPPMessageArchiving_Contact_CoreDataObject *contact = [self contactForMessage:archivedMessage]; XMPPLogVerbose(@"Previous contact: %@", contact); - if (contact == nil) + if (contact == nil && [self shouldInsertContactForMessage:archivedMessage]) { contact = (XMPPMessageArchiving_Contact_CoreDataObject *) [[NSManagedObject alloc] initWithEntity:[self contactEntity:moc] @@ -508,6 +520,11 @@ - (void)archiveMessage:(XMPPMessage *)message outgoing:(BOOL)isOutgoing xmppStre didCreateNewContact = YES; } + + if (contact == nil) { + XMPPLogVerbose(@"No contact for archived message"); + return; + } contact.streamBareJidStr = archivedMessage.streamBareJidStr; contact.bareJid = archivedMessage.bareJid; diff --git a/Extensions/XEP-0136/XMPPMessageArchiving.h b/Extensions/XEP-0136/XMPPMessageArchiving.h index 716bc58daa..de00f9ea45 100644 --- a/Extensions/XEP-0136/XMPPMessageArchiving.h +++ b/Extensions/XEP-0136/XMPPMessageArchiving.h @@ -9,7 +9,7 @@ * This class provides support for storing message history. * The functionality is formalized in XEP-0136. **/ -@interface XMPPMessageArchiving : XMPPModule +@interface XMPPMessageArchiving : XMPPModule { @protected diff --git a/Extensions/XEP-0363/XMPPHTTPFileUpload.m b/Extensions/XEP-0363/XMPPHTTPFileUpload.m index 6c82170e72..fc7a05a261 100644 --- a/Extensions/XEP-0363/XMPPHTTPFileUpload.m +++ b/Extensions/XEP-0363/XMPPHTTPFileUpload.m @@ -19,7 +19,7 @@ static const int xmppLogLevel = XMPP_LOG_LEVEL_WARN; #endif -NSString *const XMPPHTTPFileUploadNamespace = @"urn:xmpp:http:upload"; +NSString *const XMPPHTTPFileUploadNamespace = @"urn:xmpp:http:upload:0"; NSString *const XMPPHTTPFileUploadErrorDomain = @"XMPPHTTPFileUploadErrorDomain"; NSString* StringForXMPPHTTPFileUploadErrorCode(XMPPHTTPFileUploadErrorCode errorCode) { @@ -129,11 +129,10 @@ - (void)requestSlotFromService:(XMPPJID*)serviceJID // - // - // my_juliet.png - // 23456 - // image/jpeg - // + // // NSString *iqID = [XMPPStream generateUUID]; @@ -142,11 +141,11 @@ - (void)requestSlotFromService:(XMPPJID*)serviceJID XMPPElement *request = [XMPPElement elementWithName:@"request"]; [request setXmlns:XMPPHTTPFileUploadNamespace]; if (filename) { - [request addChild:[XMPPElement elementWithName:@"filename" stringValue:filename]]; + [request addAttributeWithName:@"filename" stringValue:filename]; } - [request addChild:[XMPPElement elementWithName:@"size" numberValue:[NSNumber numberWithUnsignedInteger:size]]]; + [request addAttributeWithName:@"size" unsignedIntegerValue:size]; if (contentType) { - [request addChild:[XMPPElement elementWithName:@"content-type" stringValue:contentType]]; + [request addAttributeWithName:@"content-type" stringValue:contentType]; } [iq addChild:request]; diff --git a/Extensions/XMPPMUCLight/XMPPRoomLight.h b/Extensions/XMPPMUCLight/XMPPRoomLight.h index bc763b9150..1dd4402ea3 100644 --- a/Extensions/XMPPMUCLight/XMPPRoomLight.h +++ b/Extensions/XMPPMUCLight/XMPPRoomLight.h @@ -56,6 +56,7 @@ @optional - (void)xmppRoomLight:(nonnull XMPPRoomLight *)sender didReceiveMessage:(nonnull XMPPMessage *)message; +- (void)xmppRoomLight:(nonnull XMPPRoomLight *)sender willSendIQElementWithID:(nonnull NSString *)iqID; - (void)xmppRoomLight:(nonnull XMPPRoomLight *)sender didCreateRoomLight:(nonnull XMPPIQ *)iq; - (void)xmppRoomLight:(nonnull XMPPRoomLight *)sender didFailToCreateRoomLight:(nonnull XMPPIQ *)iq; diff --git a/Extensions/XMPPMUCLight/XMPPRoomLight.m b/Extensions/XMPPMUCLight/XMPPRoomLight.m index 2bed21a9e8..c7af0796fb 100644 --- a/Extensions/XMPPMUCLight/XMPPRoomLight.m +++ b/Extensions/XMPPMUCLight/XMPPRoomLight.m @@ -290,6 +290,7 @@ - (void)createRoomLightWithMembersJID:(nullable NSArray *) members{ selector:@selector(handleCreateRoomLight:withInfo:) timeout:60.0]; + [multicastDelegate xmppRoomLight:self willSendIQElementWithID:iqID]; [self->xmppStream sendElement:iq]; }}; @@ -339,6 +340,7 @@ - (void)leaveRoomLight{ selector:@selector(handleLeaveRoomLight:withInfo:) timeout:60.0]; + [multicastDelegate xmppRoomLight:self willSendIQElementWithID:iqID]; [self->xmppStream sendElement:iq]; }}; @@ -389,6 +391,8 @@ - (void)addUsers:(nonnull NSArray *)users{ target:self selector:@selector(handleAddUsers:withInfo:) timeout:60.0]; + + [multicastDelegate xmppRoomLight:self willSendIQElementWithID:iqID]; [self->xmppStream sendElement:iq]; }}; @@ -431,6 +435,7 @@ - (void)fetchMembersList{ selector:@selector(handleFetchMembersListResponse:withInfo:) timeout:60.0]; + [multicastDelegate xmppRoomLight:self willSendIQElementWithID:iqID]; [self->xmppStream sendElement:iq]; }}; @@ -481,6 +486,7 @@ - (void)destroyRoom { selector:@selector(handleDestroyRoom:withInfo:) timeout:60.0]; + [multicastDelegate xmppRoomLight:self willSendIQElementWithID:iqID]; [self->xmppStream sendElement:iq]; }}; @@ -561,7 +567,8 @@ - (void)changeAffiliations:(nonnull NSArray *)members{ selector:@selector(handleChangeAffiliations:withInfo:) timeout:60.0]; - [self->xmppStream sendElement:iq]; + [multicastDelegate xmppRoomLight:self willSendIQElementWithID:iqID]; + [self->xmppStream sendElement:iq]; }}; if (dispatch_get_specific(moduleQueueTag)) @@ -602,7 +609,8 @@ - (void)getConfiguration { selector:@selector(handleGetConfiguration:withInfo:) timeout:60.0]; - [self->xmppStream sendElement:iq]; + [multicastDelegate xmppRoomLight:self willSendIQElementWithID:iqID]; + [self->xmppStream sendElement:iq]; }}; if (dispatch_get_specific(moduleQueueTag)) @@ -654,7 +662,8 @@ - (void)setConfiguration:(nonnull NSArray *)configs{ selector:@selector(handleSetConfiguration:withInfo:) timeout:60.0]; - [self->xmppStream sendElement:iq]; + [multicastDelegate xmppRoomLight:self willSendIQElementWithID:iqID]; + [self->xmppStream sendElement:iq]; }}; if (dispatch_get_specific(moduleQueueTag)) diff --git a/Swift/XEP-0420/XMLElement+XEP_0420.swift b/Swift/XEP-0420/XMLElement+XEP_0420.swift new file mode 100644 index 0000000000..c01d5a10f9 --- /dev/null +++ b/Swift/XEP-0420/XMLElement+XEP_0420.swift @@ -0,0 +1,169 @@ +// +// XMLElement+XEP_0420.swift +// XMPPFramework +// +// Created by Piotr Wegrzynek on 16/07/2025. +// + +#if canImport(XMPPFramework) +import XMPPFramework +#endif + +extension XMLElement { + // https://xmpp.org/extensions/xep-0420.html#example-5 + public static func makeStanzaContentEncryptionEnvelope(sensitiveElements: [XMLElement]) -> XMLElement { + // In order to send an encrypted message without leaking extension elements, the sender prepares the message by placing the sensitive extension elements inside a element and that inside an element. + let envelope = XMLElement(name: "envelope", xmlns: "urn:xmpp:sce:1") + let content = XMLElement(name: "content") + for sensitiveElement in sensitiveElements { + guard sensitiveElement.name != nil, sensitiveElement.xmlns != nil else { + // Elements in the element MUST be identified using an element name and namespace. + assertionFailure("Encountered element without name or namespace in element") + continue + } + content.addChild(sensitiveElement) + } + envelope.addChild(content) + return envelope + } + + /// - Note: Applications that rely on server processed elements not mentioned in the XEP need to apply their own element filtering on top of what unpacking does. + public func filteredStanzaContentEncryptionEnvelopeContent() -> [XMLElement] { + guard isStanzaContentEncryptionEnvelope, let contentChildren = element(forName: "content")?.children else { return [] } + // After verifying the integrity of the element, the recipient needs to make sure that no server-processed elements are found inside of it + return contentChildren.compactMap { + $0 as? XMLElement + } .filter { + !$0.isServerProcessed + } + } +} + +// In order to prevent certain attacks, different affix elements MAY be added as direct child elements of the element. +extension XMLElement { + // Prevent known ciphertext and message length correlation attacks. + public func addStanzaContentEncryptionRandomPaddingAffix() { + addChild(XMLElement(name: "rpad", stringValue: StanzaContentEncryptionPaddingGenerator.randomPadding())) + } + + // Prevent replay attacks using old messages. + public func addStanzaContentEncryptionTimestampAffix(with date: Date) { + let affix = XMLElement(name: "time") + affix.addAttribute(withName: "stamp", stringValue: date.xmppDateTimeString) + addChild(affix) + } + + // Prevent spoofing of the recipient. + public func addStanzaContentEncryptionRecipientAffix(with jid: XMPPJID) { + let affix = XMLElement(name: "to") + affix.addAttribute(withName: "jid", stringValue: jid.bare) + addChild(affix) + } + + // Prevent spoofing of the sender. + public func addStanzaContentEncryptionSenderAffix(with jid: XMPPJID) { + let affix = XMLElement(name: "from") + affix.addAttribute(withName: "jid", stringValue: jid.bare) + addChild(affix) + } +} + +extension XMLElement { + // Receiving clients MUST check whether the difference between the timestamp and the sending time derived from the stanza itself lays within a reasonable margin. + // The client SHOULD use the content of the timestamp element when displaying the send date of the message + public func verifyStanzaContentEncryptionTimestamp(expecting expectedDate: Date, withMargin verificationMargin: TimeInterval = 10) -> Bool { + guard let affix = stanzaContentEncryptionAffix(named: "time"), + let stamp = affix.attributeStringValue(forName: "stamp"), + let actualDate = Date.from(xmppDateTimeString: stamp) else { + return false + } + return abs(actualDate.timeIntervalSince(expectedDate)) <= verificationMargin + } + + // Receiving clients MUST check if the JID matches the to attribute of the enclosing stanza and otherwise alert the user/reject the message + public func verifyStanzaContentEncryptionRecipient(expecting expectedJID: XMPPJID) -> Bool { + guard let affix = stanzaContentEncryptionAffix(named: "to"), + let jid = affix.attributeStringValue(forName: "jid"), + let actualJID = XMPPJID(string: jid) else { + return false + } + return actualJID.isEqual(to: expectedJID, options: .bare) + } + + // Receiving clients MUST check if the value matches the from attribute of the enclosing stanza and otherwise alert the user/reject the message + public func verifyStanzaContentEncryptionSender(expecting expectedJID: XMPPJID) -> Bool { + guard let affix = stanzaContentEncryptionAffix(named: "from"), + let jid = affix.attributeStringValue(forName: "jid"), + let actualJID = XMPPJID(string: jid) else { + return false + } + return actualJID.isEqual(to: expectedJID, options: .bare) + } + + private func stanzaContentEncryptionAffix(named affixName: String) -> XMLElement? { + // XML schema for the extension is undefined as of specification version 0.4.1 + // This implementation requires each verified affix element to appear exactly once in an envelope + let matchingAffixes = elements(forName: affixName) + guard matchingAffixes.count == 1, let affix = matchingAffixes.first else { + return nil + } + return affix + } +} + +extension XMLElement { + var isStanzaContentEncryptionEnvelope: Bool { + name == "envelope" && xmlns == "urn:xmpp:sce:1" + } + + // There are certain extension elements which are required to be available to the server in order to do message routing and processing + // Additionally there are some elements that MUST be filtered by the server. + // Allowing for those elements to be included in, and parsed from the encrypted payload would allow a malicious client to perform a number of attacks. + // Contrary to this, other elements are considered sensitive and MUST NOT be available in plaintext outside the element. + var isServerProcessed: Bool { + // Message Processing Hints are addressed to the server and MUST therefore be accessible in plaintext. + if xmlns == "urn:xmpp:hints" { + return true + } + // Sending clients MUST NOT include Stanza-ID elements inside the element, as this would prevent the server from filtering it. + if xmlns == XMPPStanzaIdXmlns, [XMPPStanzaIdElementName, XMPPOriginIdElementName].contains(name) { + return true + } + // The server MUST be able to access the and
elements in order to do message routing, so they MUST NOT be encrypted. + if xmlns == "http://jabber.org/protocol/address" { + return true + } + // The server needs to be able to provide stanza error information + if name == "error", ["jabber:client", "jabber:server"].contains(xmlns) { + return true + } + return false + } +} + +private struct StanzaContentEncryptionPaddingGenerator: Sequence, IteratorProtocol { + static func randomPadding() -> String { + String(StanzaContentEncryptionPaddingGenerator()) + } + + private static let alphabet: [Character] = { + let printableASCIICharacterCodes = 33...126 + let xmlUnsafeCharacters = CharacterSet(charactersIn: #""'<>&"#) + return printableASCIICharacterCodes.compactMap { code in + guard let scalar = UnicodeScalar(code), !xmlUnsafeCharacters.contains(scalar) else { + return nil + } + return Character(scalar) + } + }() + + private var remaining = Int.random(in: 0...200) + + mutating func next() -> Character? { + guard remaining > 0, let randomCharacter = Self.alphabet.randomElement() else { + return nil + } + remaining -= 1 + return randomCharacter + } +} diff --git a/Swift/XEP-0420/XMPPStanzaContentEncryption.swift b/Swift/XEP-0420/XMPPStanzaContentEncryption.swift new file mode 100644 index 0000000000..fe2416aef1 --- /dev/null +++ b/Swift/XEP-0420/XMPPStanzaContentEncryption.swift @@ -0,0 +1,94 @@ +// +// XMPPStanzaContentEncryption.swift +// XMPPFramework +// +// Created by Piotr Wegrzynek on 18/07/2025. +// + +#if canImport(XMPPFramework) +import XMPPFramework +#endif + +@objc public protocol XMPPStanzaContentEncryptionDelegate: NSObjectProtocol { + @objc optional func xmppStanzaContentEncryption(_ encryption: XMPPStanzaContentEncryption, didReceiveEncryptedMessage encryptedMessage: XMPPMessage) +} + +extension GCDMulticastDelegate: XMPPStanzaContentEncryptionDelegate {} + +/// A module implementing XMPP stanza content encryption specification as defined in [XEP-0420 version 0.4.1](https://xmpp.org/extensions/attic/xep-0420-0.4.1.html). +open class XMPPStanzaContentEncryption: XMPPModule { + // Incoming messags are considered encrypted if they contain immediate children in this namespace + private let encryptedElementsNamespace: String + + public init(encryptedElementsNamespace: String, dispatchQueue: DispatchQueue? = nil) { + self.encryptedElementsNamespace = encryptedElementsNamespace + super.init(dispatchQueue: dispatchQueue) + } +} + +// Override hooks +extension XMPPStanzaContentEncryption { + + /// - Note: Applications that rely on server processed elements not mentioned in the XEP will need to override this logic. + @objc open func shouldIgnoreElementOutsideEnvelope(_ elementOutsideEnvelope: XMLElement) -> Bool { + elementOutsideEnvelope.xmlns != encryptedElementsNamespace && !elementOutsideEnvelope.isServerProcessed + } +} + +extension XMPPStanzaContentEncryption: XMPPStreamDelegate { + public func xmppStream(_ sender: XMPPStream, willSend message: XMPPMessage) -> XMPPMessage? { + // Unencrypted elements are NOT ALLOWED as child elements of the stanza and MUST be dropped. + assert(message.element(forName: "envelope") == nil, "Encountered unencrypted child element in outgoing message") + message.removeElements(forName: "envelope") + + return message + } + + public func xmppStream(_ sender: XMPPStream, willReceive message: XMPPMessage) -> XMPPMessage? { + // Furthermore the receiving client MUST ignore any extension elements considered as sensitive which are found outside of the element, especially as direct unencrypted child elements of the enclosing stanza. + message.removeElementsRecursive(withPredicate: shouldIgnoreElementOutsideEnvelope(_:)) + return message + } + + public func xmppStream(_ sender: XMPPStream, didReceive message: XMPPMessage) { + if !message.elements(forXmlns: encryptedElementsNamespace).isEmpty { + // The recipient of the message decrypts its encrypted payload. + multicast.invoke(ofType: XMPPStanzaContentEncryptionDelegate.self) { multicast in + // This should eventually lead to XMPPStanzaContentEncryptionProfileDelegate callback invocation + multicast.xmppStanzaContentEncryption!(self, didReceiveEncryptedMessage: message) + } + } + } +} + +extension XMPPStanzaContentEncryption: XMPPStanzaContentEncryptionProfileDelegate { + public func xmppStanzaContentEncryptionProfile(_ profile: XMPPStanzaContentEncryptionProfile, didPrepareEncryptedElement encryptedElement: XMLElement, for message: XMPPMessage) { + guard let xmppStream else { + assertionFailure("Stream not ready to send") + return + } + + // The result is appended to the message. + message.addChild(encryptedElement) + + // Since the outer message element does not contain a element the sender appends an unencrypted hint as specified in Message Processing Hints (XEP-0334) [7]. + message.addStorageHint(.store) + + // The message can then be sent to the recipient. + xmppStream.send(message) + } +} + +private extension XMLElement { + func removeElementsRecursive(withPredicate shouldBeRemoved: (XMLElement) -> Bool) { + guard let childrenIndices = children?.indices else { return } + for childIndex in childrenIndices.reversed() { + guard let element = child(at: UInt(childIndex)) as? XMLElement else { continue } + if shouldBeRemoved(element) { + removeChild(at: UInt(childIndex)) + } else { + element.removeElementsRecursive(withPredicate: shouldBeRemoved) + } + } + } +} diff --git a/Swift/XEP-0420/XMPPStanzaContentEncryptionProfile.swift b/Swift/XEP-0420/XMPPStanzaContentEncryptionProfile.swift new file mode 100644 index 0000000000..c653c8148c --- /dev/null +++ b/Swift/XEP-0420/XMPPStanzaContentEncryptionProfile.swift @@ -0,0 +1,96 @@ +// +// XMPPStanzaContentEncryptionProfile.swift +// XMPPFramework +// +// Created by Piotr Wegrzynek on 12/05/2026. +// + +#if canImport(XMPPFramework) +import XMPPFramework +#endif + +@objc public protocol XMPPStanzaContentEncryptionProfileDelegate: NSObjectProtocol { + @objc optional func xmppStanzaContentEncryptionProfile(_ profile: XMPPStanzaContentEncryptionProfile, + didPrepareEncryptedElement encryptedElement: XMLElement, + for message: XMPPMessage) + @objc optional func xmppStanzaContentEncryptionProfile(_ profile: XMPPStanzaContentEncryptionProfile, + didFailToPrepareEncryptedElementFor message: XMPPMessage) + @objc optional func xmppStanzaContentEncryptionProfile(_ profile: XMPPStanzaContentEncryptionProfile, + didDecryptEnvelopeElement envelopeElement: XMLElement, + from message: XMPPMessage) + @objc optional func xmppStanzaContentEncryptionProfile(_ profile: XMPPStanzaContentEncryptionProfile, + didFailToHandleEncryptedElementFrom message: XMPPMessage) +} + +extension GCDMulticastDelegate: XMPPStanzaContentEncryptionProfileDelegate {} + +open class XMPPStanzaContentEncryptionProfile: NSObject { + private let multicast = GCDMulticastDelegate() + + public func prepareEncryptedElement(withEmbeddedEnvelope envelopeElement: XMLElement, forOutgoingMessage outgoingMessage: XMPPMessage) { + // Depending on the encryption-specific SCE-profile, some affix elements are added as child elements of the element. + let finalEnvelope = addAffixElemenets(to: envelopeElement, for: outgoingMessage) + + // The element is then serialized into XML and encrypted using the SCE-specific profile of the encryption mechanism in place. + encryptEnvelopeXML(finalEnvelope.xmlString, for: outgoingMessage) { encrypted in + self.multicast.invoke(ofType: XMPPStanzaContentEncryptionProfileDelegate.self) { multicast in + if let encrypted { + multicast.xmppStanzaContentEncryptionProfile!(self, didPrepareEncryptedElement: encrypted, for: outgoingMessage) + } else { + multicast.xmppStanzaContentEncryptionProfile!(self, didFailToPrepareEncryptedElementFor: outgoingMessage) + } + } + } + } + + public func handleEncryptedElement(fromIncomingMessage incomingMessage: XMPPMessage) { + decryptEnvelopeXML(from: incomingMessage) { envelopeXML in + self.multicast.invoke(ofType: XMPPStanzaContentEncryptionProfileDelegate.self) { multicast in + guard let envelopeXML, + // The recipient MUST verify that the decrypted element contains valid XML before processing it any further. Invalid XML must be rejected. + let decryptedEnvelope = try? XMLElement(xmlString: envelopeXML), decryptedEnvelope.isStanzaContentEncryptionEnvelope, + // Depending on the affix profiles specified by the used encryption protocol, the affix elements are verified to prevent certain attacks from taking place. + self.verifyAffixElements(in: decryptedEnvelope, from: incomingMessage) + else { + multicast.xmppStanzaContentEncryptionProfile!(self, didFailToHandleEncryptedElementFrom: incomingMessage) + return + } + + // The result is the element containing the element and the affix elements as direct child elements. + multicast.xmppStanzaContentEncryptionProfile!(self, didDecryptEnvelopeElement: decryptedEnvelope, from: incomingMessage) + + // The following is not implemented as it contradicts section 11. Implementation Notes, which calls to handle encrypted elements explicitly: + // As a last step, the original unencrypted stanza is recreated by replacing the element of the stanza with the elements inside of the element. + } + } + } +} + +// Override hooks +extension XMPPStanzaContentEncryptionProfile { + @objc open func add(_ delegate: XMPPStanzaContentEncryptionProfileDelegate, delegateQueue: dispatch_queue_t) { + multicast.add(delegate, delegateQueue: delegateQueue) + } + + @objc open func addAffixElemenets(to envelope: XMLElement, for message: XMPPMessage) -> XMLElement { + envelope + } + + @objc open func encryptEnvelopeXML(_ envelopeXML: String, for message: XMPPMessage, completion: @escaping (XMLElement?) -> Void) { + completion(nil) + } + + @objc open func decryptEnvelopeXML(from message: XMPPMessage, completion: @escaping (String?) -> Void) { + completion(nil) + } + + @objc open func verifyAffixElements(in envelope: XMLElement, from message: XMPPMessage) -> Bool { + false + } +} + +extension XMPPStanzaContentEncryptionProfile: XMPPStanzaContentEncryptionDelegate { + public func xmppStanzaContentEncryption(_ encryption: XMPPStanzaContentEncryption, didReceiveEncryptedMessage encryptedMessage: XMPPMessage) { + handleEncryptedElement(fromIncomingMessage: encryptedMessage) + } +} diff --git a/Xcode/Testing-Shared/OMEMOElementTests.m b/Xcode/Testing-Shared/OMEMOElementTests.m index ead7e9f23c..8377c072a8 100644 --- a/Xcode/Testing-Shared/OMEMOElementTests.m +++ b/Xcode/Testing-Shared/OMEMOElementTests.m @@ -35,36 +35,33 @@ - (void)tearDown { - (void)testDeviceIdSerialization { NSArray *deviceIds = @[@(12345), @(4223), @(31415)]; - XMPPIQ *iq = [XMPPIQ omemo_iqPublishDeviceIds:deviceIds elementId:@"announce1" xmlNamespace:self.ns]; + XMPPIQ *iq = [XMPPIQ omemo_iqPublishDeviceIds:deviceIds elementId:@"announce1"]; NSString *iqString = [iq XMLString]; - NSString *expectedString = [NSString stringWithFormat:@" \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - http://jabber.org/protocol/pubsub#publish-options \ - \ - \ - 1 \ - \ - \ - open \ - \ - \ - \ - \ - \ - ", [OMEMOModule xmlnsOMEMODeviceList:self.ns], [OMEMOModule xmlnsOMEMO:self.ns]]; + NSString *expectedString = @"" + "" + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " http://jabber.org/protocol/pubsub#publish-options" + " " + " " + " open" + " " + " " + " " + " " + "" + ""; NSError *error = nil; NSXMLElement *outputIQ = [[NSXMLElement alloc] initWithXMLString:iqString error:&error]; XCTAssertNil(error); @@ -75,39 +72,39 @@ - (void)testDeviceIdSerialization { } - (void) testPublishDeviceBundle { - NSString *expectedString = [NSString stringWithFormat:@" \ - \ - \ - \ - \ - \ - c2lnbmVkUHJlS2V5UHVibGlj \ - c2lnbmVkUHJlS2V5U2lnbmF0dXJl \ - aWRlbnRpdHlLZXk= \ - \ - cHJlS2V5MQ== \ - cHJlS2V5Mg== \ - cHJlS2V5Mw== \ - \ - \ - \ - \ - \ - \ - \ - http://jabber.org/protocol/pubsub#publish-options \ - \ - \ - 1 \ - \ - \ - open \ - \ - \ - \ - \ - \ - ", [OMEMOModule xmlnsOMEMOBundles:self.ns], [OMEMOModule xmlnsOMEMO:self.ns]]; + NSString *expectedString = @"" + "" + " " + " " + " " + " " + " c2lnbmVkUHJlS2V5UHVibGlj" + " c2lnbmVkUHJlS2V5U2lnbmF0dXJl" + " aWRlbnRpdHlLZXk=" + " " + " cHJlS2V5MQ==" + " cHJlS2V5Mg==" + " cHJlS2V5Mw==" + " " + " " + " " + " " + " " + " " + " " + " http://jabber.org/protocol/pubsub#publish-options" + " " + " " + " max" + " " + " " + " open" + " " + " " + " " + " " + "" + ""; NSError *error = nil; NSXMLElement *expectedXML = [[NSXMLElement alloc] initWithXMLString:expectedString error:&error]; XCTAssertNotNil(expectedXML); @@ -130,7 +127,7 @@ - (void) testPublishDeviceBundle { ]; OMEMOSignedPreKey *signedPreKey = [[OMEMOSignedPreKey alloc] initWithPreKeyId:1 publicKey:signedPreKeyPublicData signature:signedPreKeySignatureData]; OMEMOBundle *bundle = [[OMEMOBundle alloc] initWithDeviceId:31415 identityKey:identityKeyData signedPreKey:signedPreKey preKeys:preKeys]; - XMPPIQ *iq = [XMPPIQ omemo_iqPublishBundle:bundle elementId:@"announce2" xmlNamespace:self.ns]; + XMPPIQ *iq = [XMPPIQ omemo_iqPublishBundle:bundle elementId:@"announce2"]; XCTAssertEqualObjects([iq XMLStringWithOptions:NSXMLNodePrettyPrint], [expectedXML XMLStringWithOptions:NSXMLNodePrettyPrint]); } @@ -147,20 +144,20 @@ - (void) testPublishDeviceBundle { */ - (void) testFetchBundleForDeviceId { - NSString *expected = [NSString stringWithFormat:@" \ - \ - \ - \ - \ - \ - ", [OMEMOModule xmlnsOMEMOBundles:self.ns]]; + NSString *expected = @"" + "" + " " + " " + " " + " " + " " + "" + ""; NSError *error = nil; NSXMLElement *expectedElement = [[NSXMLElement alloc] initWithXMLString:expected error:&error]; XCTAssertNil(error); XCTAssertNotNil(expectedElement); - XMPPIQ *iq = [XMPPIQ omemo_iqFetchBundleForDeviceId:31415 jid:[XMPPJID jidWithString:@"juliet@capulet.lit"] elementId:@"fetch1" xmlNamespace:self.ns]; + XMPPIQ *iq = [XMPPIQ omemo_iqFetchBundleForDeviceId:31415 jid:[XMPPJID jidWithString:@"juliet@capulet.lit"] elementId:@"fetch1"]; XCTAssertEqualObjects([iq XMLStringWithOptions:NSXMLNodePrettyPrint], [expectedElement XMLStringWithOptions:NSXMLNodePrettyPrint]); } @@ -255,39 +252,42 @@ - (void) testKeyTransportElement { */ - (void) testBundleParsing { - NSString *expectedString = [NSString stringWithFormat:@" \ - \ - \ - \ - \ - \ - c2lnbmVkUHJlS2V5UHVibGlj \ - c2lnbmVkUHJlS2V5U2lnbmF0dXJl \ - aWRlbnRpdHlLZXk= \ - \ - cHJlS2V5MQ== \ - cHJlS2V5Mg== \ - cHJlS2V5Mw== \ - \ - \ - \ - \ - \ - \ - \ - http://jabber.org/protocol/pubsub#publish-options \ - \ - \ - 1 \ - \ - \ - open \ - \ - \ - \ - \ - \ - ",[OMEMOModule xmlnsOMEMOBundles:self.ns], [OMEMOModule xmlnsOMEMO:self.ns]]; + NSString *expectedString = @"" + "" + " " + " " + " " + " " + " c2lnbmVkUHJlS2V5UHVibGlj" + " c2lnbmVkUHJlS2V5U2lnbmF0dXJl" + " aWRlbnRpdHlLZXk=" + " " + " cHJlS2V5MQ==" + " cHJlS2V5Mg==" + " cHJlS2V5Mw==" + " " + " " + " " + " " + " " + " " + " " + " http://jabber.org/protocol/pubsub#publish-options" + " " + " " + " max" + " " + " " + " open" + " " + " " + " " + " " + "" + ""; NSError *error = nil; NSXMLElement *expectedXML = [[NSXMLElement alloc] initWithXMLString:expectedString error:&error]; XCTAssertNotNil(expectedXML); @@ -310,32 +310,33 @@ - (void) testBundleParsing { ]; OMEMOSignedPreKey *signedPreKey = [[OMEMOSignedPreKey alloc] initWithPreKeyId:1 publicKey:signedPreKeyPublicData signature:signedPreKeySignatureData]; OMEMOBundle *bundle = [[OMEMOBundle alloc] initWithDeviceId:31415 identityKey:identityKeyData signedPreKey:signedPreKey preKeys:preKeys]; - XMPPIQ *iq = [XMPPIQ omemo_iqPublishBundle:bundle elementId:@"announce2" xmlNamespace:self.ns]; + XMPPIQ *iq = [XMPPIQ omemo_iqPublishBundle:bundle elementId:@"announce2"]; XCTAssertEqualObjects([iq XMLStringWithOptions:NSXMLNodePrettyPrint], [expectedXML XMLStringWithOptions:NSXMLNodePrettyPrint]); - OMEMOBundle *expectedBundle = [[XMPPIQ iqFromElement:expectedXML] omemo_bundle:self.ns]; - OMEMOBundle *bundle2 = [iq omemo_bundle:self.ns]; + OMEMOBundle *expectedBundle = [[XMPPIQ iqFromElement:expectedXML] omemo_bundle]; + OMEMOBundle *bundle2 = [iq omemo_bundle]; - XMPPIQ *expectedIQ = [XMPPIQ omemo_iqPublishBundle:expectedBundle elementId:@"eid" xmlNamespace:self.ns]; - XMPPIQ *bundle2iq = [XMPPIQ omemo_iqPublishBundle:bundle2 elementId:@"eid" xmlNamespace:self.ns]; + XMPPIQ *expectedIQ = [XMPPIQ omemo_iqPublishBundle:expectedBundle elementId:@"eid"]; + XMPPIQ *bundle2iq = [XMPPIQ omemo_iqPublishBundle:bundle2 elementId:@"eid"]; XCTAssertEqualObjects([expectedIQ XMLStringWithOptions:NSXMLNodePrettyPrint], [bundle2iq XMLStringWithOptions:NSXMLNodePrettyPrint]); } - (void) testFetchDeviceList { - NSString *expected = [NSString stringWithFormat:@" \ - \ - \ - \ - \ - \ - ",[OMEMOModule xmlnsOMEMODeviceList:self.ns]]; + NSString *expected = @"" + "" + " " + " " + " " + "" + ""; NSError *error = nil; NSXMLElement *expXml = [[NSXMLElement alloc] initWithXMLString:expected error:&error]; XCTAssertNil(error); XCTAssertNotNil(expXml); XMPPJID *jid = [XMPPJID jidWithString:@"juliet@capulet.lit"]; - XMPPIQ *iq = [XMPPIQ omemo_iqFetchDeviceIdsForJID:jid elementId:@"fetch1" xmlNamespace:self.ns]; + XMPPIQ *iq = [XMPPIQ omemo_iqFetchDeviceIdsForJID:jid elementId:@"fetch1"]; XMPPIQ *expIq = [XMPPIQ iqFromElement:expXml]; XCTAssertEqualObjects([expIq type], [iq type]); XCTAssertEqualObjects([expIq to], [iq to]); diff --git a/Xcode/Testing-Shared/OMEMOModuleTests.m b/Xcode/Testing-Shared/OMEMOModuleTests.m index 45609866b3..15c2b4fe04 100644 --- a/Xcode/Testing-Shared/OMEMOModuleTests.m +++ b/Xcode/Testing-Shared/OMEMOModuleTests.m @@ -60,19 +60,18 @@ - (void)testFetchDeviceIds { XMPPJID *testJID = [XMPPJID jidWithString:@"test@example.com"]; OMEMOModuleNamespace ns = self.omemoModule.xmlNamespace; - NSString *items = [NSString stringWithFormat:@" \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - ", [OMEMOModule xmlnsOMEMODeviceList:ns], [OMEMOModule xmlnsOMEMO:ns]]; - + NSString *items = @"" + "" + " " + " " + " " + " " + " " + " " + " " + " " + "" + ""; NSError *error = nil; NSXMLElement *pubsub = [[NSXMLElement alloc] initWithXMLString:items error:&error]; XCTAssertNil(error); @@ -270,23 +269,20 @@ - (void) testReceiveMessage { - (void) testDeviceListUpdate { OMEMOModuleNamespace ns = self.omemoModule.xmlNamespace; - NSString *incoming = [NSString stringWithFormat:@" \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - ", [OMEMOModule xmlnsOMEMODeviceList:ns], [OMEMOModule xmlnsOMEMO:ns]]; + NSString *incoming = @"" + "" + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + "" + ""; NSXMLElement *element = [[NSXMLElement alloc] initWithXMLString:incoming error:nil]; self.expectation = [self expectationWithDescription:@"testDeviceListUpdate"]; XCTAssertNotNil(element); @@ -437,24 +433,24 @@ - (XMPPIQ*) iq_testIQFromJID:(XMPPJID*)fromJID eid:(NSString*)eid type:(NSString - (NSXMLElement*)innerBundleElement { OMEMOModuleNamespace ns = self.omemoModule.xmlNamespace; - NSString *expectedString = [NSString stringWithFormat:@" \ - \ - \ - \ - \ - c2lnbmVkUHJlS2V5UHVibGlj \ - c2lnbmVkUHJlS2V5U2lnbmF0dXJl \ - aWRlbnRpdHlLZXk= \ - \ - cHJlS2V5MQ== \ - cHJlS2V5Mg== \ - cHJlS2V5Mw== \ - \ - \ - \ - \ - \ - ", [OMEMOModule xmlnsOMEMOBundles:ns], [OMEMOModule xmlnsOMEMO:ns]]; + NSString *expectedString = @"" + "" + " " + " " + " " + " c2lnbmVkUHJlS2V5UHVibGlj" + " c2lnbmVkUHJlS2V5U2lnbmF0dXJl" + " aWRlbnRpdHlLZXk=" + " " + " cHJlS2V5MQ==" + " cHJlS2V5Mg==" + " cHJlS2V5Mw==" + " " + " " + " " + " " + "" + ""; NSXMLElement *element = [[NSXMLElement alloc] initWithXMLString:expectedString error:nil]; XCTAssertNotNil(element); return element; @@ -462,7 +458,7 @@ - (NSXMLElement*)innerBundleElement { - (OMEMOBundle*) bundle { OMEMOModuleNamespace ns = self.omemoModule.xmlNamespace; - OMEMOBundle *bundle = [[self iq_SetBundleWithEid:@"announce1"] omemo_bundle:ns]; + OMEMOBundle *bundle = [[self iq_SetBundleWithEid:@"announce1"] omemo_bundle]; XCTAssertNotNil(bundle); return bundle; } diff --git a/Xcode/Testing-Shared/OMEMOTestStorage.m b/Xcode/Testing-Shared/OMEMOTestStorage.m index e76f8a6640..0f3ba6f865 100644 --- a/Xcode/Testing-Shared/OMEMOTestStorage.m +++ b/Xcode/Testing-Shared/OMEMOTestStorage.m @@ -99,24 +99,24 @@ + (XMPPIQ*) iq_testIQFromJID:(XMPPJID*)fromJID eid:(NSString*)eid type:(NSString + (NSXMLElement*)innerBundleElement:(OMEMOModuleNamespace)ns { - NSString *expectedString = [NSString stringWithFormat:@" \ - \ - \ - \ - \ - c2lnbmVkUHJlS2V5UHVibGlj \ - c2lnbmVkUHJlS2V5U2lnbmF0dXJl \ - aWRlbnRpdHlLZXk= \ - \ - cHJlS2V5MQ== \ - cHJlS2V5Mg== \ - cHJlS2V5Mw== \ - \ - \ - \ - \ - \ - ", [OMEMOModule xmlnsOMEMODeviceList:ns], [OMEMOModule xmlnsOMEMO:ns]]; + NSString *expectedString = @"" + "" + " " + " " + " " + " c2lnbmVkUHJlS2V5UHVibGlj=" + " c2lnbmVkUHJlS2V5U2lnbmF0dXJl==" + " aWRlbnRpdHlLZXk=" + " " + " cHJlS2V5MQ==" + " cHJlS2V5Mg==" + " cHJlS2V5Mw==" + " " + " " + " " + " " + "" + ""; NSXMLElement *element = [[NSXMLElement alloc] initWithXMLString:expectedString error:nil]; return element; @@ -124,7 +124,7 @@ + (NSXMLElement*)innerBundleElement:(OMEMOModuleNamespace)ns { + (OMEMOBundle*) testBundle:(OMEMOModuleNamespace)ns { [self innerBundleElement:ns]; - OMEMOBundle *bundle = [[self iq_SetBundleWithEid:@"announce1" xmlNamespace:ns] omemo_bundle:ns]; + OMEMOBundle *bundle = [[self iq_SetBundleWithEid:@"announce1" xmlNamespace:ns] omemo_bundle]; return bundle; } diff --git a/Xcode/Testing-Shared/XMPPHTTPFileUploadTests.m b/Xcode/Testing-Shared/XMPPHTTPFileUploadTests.m index 4fe9ead148..4ae7eb4c3d 100644 --- a/Xcode/Testing-Shared/XMPPHTTPFileUploadTests.m +++ b/Xcode/Testing-Shared/XMPPHTTPFileUploadTests.m @@ -100,10 +100,7 @@ - (void) testRequestSlot { NSMutableString *s = [NSMutableString string]; [s appendString:@""]; - [s appendString:@" "]; - [s appendString:@" my_juliet.png"]; - [s appendString:@" 23456"]; - [s appendString:@" image/jpeg"]; + [s appendString:@" "]; [s appendString:@" "]; [s appendString:@""]; @@ -122,12 +119,12 @@ - (void) testRequestSlot { NSXMLElement *sentRequest = [sentIQ childElement]; NSXMLElement *request = [iq childElement]; - XCTAssertEqualObjects(sentRequest.xmlns, @"urn:xmpp:http:upload"); + XCTAssertEqualObjects(sentRequest.xmlns, @"urn:xmpp:http:upload:0"); XCTAssertEqualObjects(sentRequest.xmlns, request.xmlns); - NSString *filename = [sentRequest elementForName:@"filename"].stringValue; - NSString *size = [sentRequest elementForName:@"size"].stringValue; - NSString *contentType = [sentRequest elementForName:@"content-type"].stringValue; + NSString *filename = [sentRequest attributeStringValueForName:@"filename"]; + NSString *size = [sentRequest attributeStringValueForName:@"size"]; + NSString *contentType = [sentRequest attributeStringValueForName:@"content-type"]; XCTAssertEqualObjects(filename, @"my_juliet.png"); XCTAssertEqualObjects(size, @"23456");