diff --git a/src/XMPPConnection.h b/src/XMPPConnection.h index fcc9baf..a976a7d 100644 --- a/src/XMPPConnection.h +++ b/src/XMPPConnection.h @@ -31,6 +31,7 @@ @class XMPPAuthenticator; @class XMPPRoster; @class XMPPRosterItem; +@class SSLSocket; @protocol XMPPConnectionDelegate #ifndef XMPP_CONNECTION_M @@ -64,10 +65,11 @@ #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; @@ -100,6 +102,12 @@ */ - (void)connect; +/** + * Checks the certificate presented by the server. + * Throws SSLInvalidCertificateException on failure. + */ +- (void)checkCertificate; + /** * Starts a loop handling incomming data. */ @@ -184,6 +192,7 @@ - (void)XMPP_handleResourceBind: (XMPPIQ*)iq; - (void)XMPP_sendSession; - (void)XMPP_handleSession: (XMPPIQ*)iq; +- (OFString*)XMPP_IDNAToASCII: (OFString*)domain; @end @interface OFObject (XMPPConnectionDelegate) diff --git a/src/XMPPConnection.m b/src/XMPPConnection.m index ba2d42f..fb83309 100644 --- a/src/XMPPConnection.m +++ b/src/XMPPConnection.m @@ -33,6 +33,8 @@ #include #import +#import +#import #import "XMPPConnection.h" #import "XMPPSRVLookup.h" @@ -148,23 +150,7 @@ - (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]; } @@ -175,7 +161,8 @@ - (void)setDomain: (OFString*)domain_ { - OFString *old = domain; + OFString *oldDomain = domain; + OFString *oldDomainToASCII = domainToASCII; char *srv; Stringprep_rc rc; @@ -192,8 +179,10 @@ } @finally { free(srv); } + [oldDomain release]; - [old release]; + domainToASCII = [self XMPP_IDNAToASCII: domain]; + [oldDomainToASCII release]; } - (OFString*)domain @@ -235,29 +224,11 @@ 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]; @@ -343,6 +314,34 @@ 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); @@ -877,6 +876,29 @@ 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]; diff --git a/tests/test.m b/tests/test.m index aae4b94..74c36e7 100644 --- a/tests/test.m +++ b/tests/test.m @@ -24,6 +24,7 @@ #include #import +#import #import "XMPPConnection.h" #import "XMPPJID.h" @@ -136,6 +137,21 @@ OF_APPLICATION_DELEGATE(AppDelegate) [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 {