Index: src/XMPPConnection.h ================================================================== --- src/XMPPConnection.h +++ src/XMPPConnection.h @@ -29,10 +29,11 @@ @class XMPPMessage; @class XMPPPresence; @class XMPPAuthenticator; @class XMPPRoster; @class XMPPRosterItem; +@class SSLSocket; @protocol XMPPConnectionDelegate #ifndef XMPP_CONNECTION_M #endif @@ -62,14 +63,15 @@ @interface XMPPConnection: OFObject #ifdef OF_HAVE_OPTONAL_PROTOCOLS #endif { - OFTCPSocket *sock; + SSLSocket *sock; OFXMLParser *parser, *oldParser; OFXMLElementBuilder *elementBuilder, *oldElementBuilder; - OFString *username, *password, *server, *domain, *resource; + OFString *username, *password, *server, *resource; + OFString *domain, *domainToASCII; XMPPJID *JID; uint16_t port; id delegate; XMPPAuthenticator *authModule; BOOL needsSession; @@ -98,10 +100,16 @@ /** * Connects to the XMPP service. */ - (void)connect; +/** + * Checks the certificate presented by the server. + * Throws SSLInvalidCertificateException on failure. + */ +- (void)checkCertificate; + /** * Starts a loop handling incomming data. */ - (void)handleConnection; @@ -182,9 +190,10 @@ - (void)XMPP_handleFeatures: (OFXMLElement*)element; - (void)XMPP_sendResourceBind; - (void)XMPP_handleResourceBind: (XMPPIQ*)iq; - (void)XMPP_sendSession; - (void)XMPP_handleSession: (XMPPIQ*)iq; +- (OFString*)XMPP_IDNAToASCII: (OFString*)domain; @end @interface OFObject (XMPPConnectionDelegate) @end Index: src/XMPPConnection.m ================================================================== --- src/XMPPConnection.m +++ src/XMPPConnection.m @@ -31,10 +31,12 @@ #include #include #import +#import +#import #import "XMPPConnection.h" #import "XMPPSRVLookup.h" #import "XMPPSCRAMAuth.h" #import "XMPPPLAINAuth.h" @@ -146,27 +148,11 @@ } - (void)setServer: (OFString*)server_ { OFString *old = server; - char *srv; - Idna_rc rc; - - if ((rc = idna_to_ascii_8z([server_ UTF8String], - &srv, IDNA_USE_STD3_ASCII_RULES)) != IDNA_SUCCESS) - @throw [XMPPIDNATranslationFailedException - exceptionWithClass: isa - connection: self - operation: @"ToASCII" - string: server_]; - - @try { - server = [[OFString alloc] initWithUTF8String: srv]; - } @finally { - free(srv); - } - + server = [self XMPP_IDNAToASCII: server_]; [old release]; } - (OFString*)server { @@ -173,11 +159,12 @@ return [[server copy] autorelease]; } - (void)setDomain: (OFString*)domain_ { - OFString *old = domain; + OFString *oldDomain = domain; + OFString *oldDomainToASCII = domainToASCII; char *srv; Stringprep_rc rc; if ((rc = stringprep_profile([domain_ UTF8String], &srv, "Nameprep", 0)) != STRINGPREP_OK) @@ -190,12 +177,14 @@ @try { domain = [[OFString alloc] initWithUTF8String: srv]; } @finally { free(srv); } + [oldDomain release]; - [old release]; + domainToASCII = [self XMPP_IDNAToASCII: domain]; + [oldDomainToASCII release]; } - (OFString*)domain { return [[domain copy] autorelease]; @@ -233,33 +222,15 @@ { OFAutoreleasePool *pool = [[OFAutoreleasePool alloc] init]; XMPPSRVEntry *candidate = nil; XMPPSRVLookup *SRVLookup = nil; OFEnumerator *enumerator; - OFString *domainToASCII; - char *cDomainToASCII; - Idna_rc rc; if (server) [sock connectToHost: server port: port]; else { - if ((rc = idna_to_ascii_8z([domain UTF8String], &cDomainToASCII, - IDNA_USE_STD3_ASCII_RULES)) != IDNA_SUCCESS) - @throw [XMPPIDNATranslationFailedException - exceptionWithClass: isa - connection: self - operation: @"ToASCII" - string: domain]; - - @try { - domainToASCII = [OFString - stringWithUTF8String: cDomainToASCII]; - } @finally { - free(cDomainToASCII); - } - @try { SRVLookup = [XMPPSRVLookup lookupWithDomain: domainToASCII]; } @catch (id e) { } @@ -340,10 +311,38 @@ - (BOOL)encrypted { return encrypted; } + +- (void)checkCertificate +{ + X509Certificate *cert; + OFDictionary *SANs; + BOOL serviceSpecific = NO; + + [sock verifyPeerCertificate]; + cert = [sock peerCertificate]; + SANs = [cert subjectAlternativeName]; + + if ([[SANs objectForKey: @"otherName"] + objectForKey: OID_SRVName] || + [SANs objectForKey: @"dNSName"] || + [SANs objectForKey: @"uniformResourceIdentifier"]) + serviceSpecific = YES; + + if ([cert hasSRVNameMatchingDomain: domainToASCII + service: @"xmpp-client"] || + [cert hasDNSNameMatchingDomain: domainToASCII]) + return; + + if (serviceSpecific || + ![cert hasCommonNameMatchingDomain: domainToASCII]) + @throw [SSLInvalidCertificateException + exceptionWithClass: isa + reason: @"No matching identifier"]; +} - (void)sendStanza: (OFXMLElement*)element { of_log(@"Out: %@", element); [sock writeString: [element XMLString]]; @@ -874,10 +873,33 @@ wasBoundToJID: JID]; [sessionID release]; sessionID = nil; } + +- (OFString*)XMPP_IDNAToASCII: (OFString*)domain_ +{ + OFString *ret; + char *cDomain; + Idna_rc rc; + + if ((rc = idna_to_ascii_8z([domain_ UTF8String], + &cDomain, IDNA_USE_STD3_ASCII_RULES)) != IDNA_SUCCESS) + @throw [XMPPIDNATranslationFailedException + exceptionWithClass: isa + connection: self + operation: @"ToASCII" + string: domain_]; + + @try { + ret = [[OFString alloc] initWithUTF8String: cDomain]; + } @finally { + free(cDomain); + } + + return ret; +} - (XMPPJID*)JID { return [[JID copy] autorelease]; } Index: tests/test.m ================================================================== --- tests/test.m +++ tests/test.m @@ -22,10 +22,11 @@ */ #include #import +#import #import "XMPPConnection.h" #import "XMPPJID.h" #import "XMPPStanza.h" #import "XMPPIQ.h" @@ -133,10 +134,25 @@ [pres addPriority: 10]; [pres addStatus: @"ObjXMPP test is working!"]; [conn sendStanza: pres]; } + +- (void)connectionDidUpgradeToTLS: (XMPPConnection*)conn +{ + @try { + [conn checkCertificate]; + } @catch (SSLInvalidCertificateException *e) { + OFString *answer; + [of_stdout writeString: @"Couldn't verify certificate: "]; + [of_stdout writeFormat: @"%@\n", e]; + [of_stdout writeString: @"Do you want to continue [y/N]? "]; + answer = [of_stdin readLine]; + if (![answer hasPrefix: @"y"]) + @throw e; + } +} - (void)connection: (XMPPConnection*)conn didReceiveRosterItem: (XMPPRosterItem*)rosterItem { of_log(@"Got roster push: %@", rosterItem);