This repository has been archived by the owner on Nov 6, 2021. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 47
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
add an HTTP Strict Transport Security (RFC6797) implementation
It wasn't until I was about 90% finished with all of this that I realized that WebKit already has its own HSTS implementation behind the scenes that caches entries and has a preload list of a few hundred domains. Oh well. Since we can't manipulate that list, continue with our own and preload it with Chromium's preload list (which Mozilla also appears to use), which currently has 1427 hosts and is freely licensed.
- Loading branch information
Showing
16 changed files
with
7,655 additions
and
49 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
#import <UIKit/UIKit.h> | ||
#import <XCTest/XCTest.h> | ||
#import <OCMock/OCMock.h> | ||
|
||
#import "HSTSCache.h" | ||
|
||
#define TRACE_HSTS | ||
|
||
@interface HSTSCache_Tests : XCTestCase | ||
@end | ||
|
||
@implementation HSTSCache_Tests | ||
|
||
HSTSCache *hstsCache; | ||
|
||
- (void)setUp { | ||
[super setUp]; | ||
|
||
hstsCache = [[HSTSCache alloc] init]; | ||
} | ||
|
||
- (void)testParseHSTSHeader { | ||
[hstsCache parseHSTSHeader:@"max-age=12345; includeSubDomains" forHost:@"example.com"]; | ||
|
||
NSDictionary *params = [hstsCache objectForKey:@"example.com"]; | ||
XCTAssertNotNil(params); | ||
XCTAssertNotNil([params objectForKey:HSTS_KEY_ALLOW_SUBDOMAINS]); | ||
XCTAssertNotNil([params objectForKey:HSTS_KEY_EXPIRATION]); | ||
|
||
XCTAssertTrue([(NSDate *)[params objectForKey:HSTS_KEY_EXPIRATION] timeIntervalSince1970] - [[NSDate date] timeIntervalSince1970] >= 12340); | ||
} | ||
|
||
- (void)testIgnoreIPAddresses { | ||
[hstsCache parseHSTSHeader:@"max-age=12345; includeSubDomains" forHost:@"127.0.0.1"]; | ||
|
||
NSDictionary *params = [hstsCache objectForKey:@"127.0.0.1"]; | ||
XCTAssertNil(params); | ||
} | ||
|
||
- (void)testParseUpdatedHSTSHeader { | ||
[hstsCache parseHSTSHeader:@"max-age=12345; includeSubDomains" forHost:@"example.com"]; | ||
|
||
NSDictionary *params = [hstsCache objectForKey:@"example.com"]; | ||
XCTAssertNotNil(params); | ||
XCTAssertNotNil([params objectForKey:HSTS_KEY_ALLOW_SUBDOMAINS]); | ||
|
||
/* now a new request presents without includeSubDomains */ | ||
[hstsCache parseHSTSHeader:@"max-age=12345" forHost:@"example.com"]; | ||
|
||
params = [hstsCache objectForKey:@"example.com"]; | ||
XCTAssertNotNil(params); | ||
XCTAssertNil([params objectForKey:HSTS_KEY_ALLOW_SUBDOMAINS]); | ||
} | ||
|
||
- (void)testParseEFFHSTSHeader { | ||
/* weirdo header that eff sends (to cover old spec?) */ | ||
[hstsCache parseHSTSHeader:@"max-age=31536000; includeSubdomains, max-age=31536000; includeSubdomains" forHost:@"www.EFF.org"]; | ||
|
||
NSDictionary *params = [hstsCache objectForKey:@"www.eff.org"]; | ||
XCTAssertNotNil(params); | ||
XCTAssertNotNil([params objectForKey:HSTS_KEY_ALLOW_SUBDOMAINS]); | ||
XCTAssertNotNil([params objectForKey:HSTS_KEY_EXPIRATION]); | ||
} | ||
|
||
- (void)testURLRewriting { | ||
[hstsCache parseHSTSHeader:@"max-age=31536000; includeSubdomains, max-age=31536000; includeSubdomains" forHost:@"www.EFF.org"]; | ||
|
||
NSURL *output = [hstsCache rewrittenURI:[NSURL URLWithString:@"http://www.eff.org/test"]]; | ||
XCTAssertTrue([[output absoluteString] isEqualToString:@"https://www.eff.org/test"]); | ||
|
||
/* we didn't see the header for "eff.org", so subdomains have to be of www */ | ||
output = [hstsCache rewrittenURI:[NSURL URLWithString:@"http://subdomain.eff.org/test"]]; | ||
XCTAssertFalse([[output absoluteString] isEqualToString:@"https://subdomain.eff.org/test"]); | ||
|
||
output = [hstsCache rewrittenURI:[NSURL URLWithString:@"http://subdomain.www.eff.org/test"]]; | ||
XCTAssertTrue([[output absoluteString] isEqualToString:@"https://subdomain.www.eff.org/test"]); | ||
|
||
output = [hstsCache rewrittenURI:[NSURL URLWithString:@"http://www.eff.org:1234/?what#hi"]]; | ||
XCTAssertTrue([[output absoluteString] isEqualToString:@"https://www.eff.org:1234/?what#hi"]); | ||
|
||
output = [hstsCache rewrittenURI:[NSURL URLWithString:@"http://www.eff.org:80/?what#hi"]]; | ||
XCTAssertTrue([[output absoluteString] isEqualToString:@"https://www.eff.org/?what#hi"]); | ||
} | ||
|
||
- (void)testExpiring { | ||
[hstsCache parseHSTSHeader:@"max-age=2; includeSubDomains" forHost:@"example.com"]; | ||
|
||
NSURL *output = [hstsCache rewrittenURI:[NSURL URLWithString:@"http://www.example.com/"]]; | ||
XCTAssertTrue([[output absoluteString] isEqualToString:@"https://www.example.com/"]); | ||
|
||
NSDate *timeoutDate = [NSDate dateWithTimeIntervalSinceNow:4]; | ||
|
||
do { | ||
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:timeoutDate]; | ||
if ([timeoutDate timeIntervalSinceNow] < 0) | ||
break; | ||
} while (TRUE); | ||
|
||
/* expired */ | ||
output = [hstsCache rewrittenURI:[NSURL URLWithString:@"http://www.example.com/"]]; | ||
XCTAssertTrue([[output absoluteString] isEqualToString:@"http://www.example.com/"]); | ||
} | ||
|
||
@end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
#import <Foundation/Foundation.h> | ||
|
||
#define HSTS_HEADER @"Strict-Transport-Security" | ||
#define HSTS_KEY_EXPIRATION @"expiration" | ||
#define HSTS_KEY_ALLOW_SUBDOMAINS @"allowSubdomains" | ||
#define HSTS_KEY_PRELOADED @"preloaded" | ||
|
||
/* subclassing NSMutableDictionary is not easy, so we have to use composition */ | ||
|
||
@interface HSTSCache : NSObject | ||
{ | ||
NSMutableDictionary *_dict; | ||
} | ||
|
||
@property NSMutableDictionary *dict; | ||
|
||
+ (HSTSCache *)retrieve; | ||
|
||
- (void)persist; | ||
- (NSURL *)rewrittenURI:(NSURL *)URL; | ||
- (void)parseHSTSHeader:(NSString *)header forHost:(NSString *)host; | ||
|
||
/* NSMutableDictionary composition pass-throughs */ | ||
- (id)objectForKey:(id)aKey; | ||
- (BOOL)writeToFile:(NSString *)path atomically:(BOOL)useAuxiliaryFile; | ||
- (void)setValue:(id)value forKey:(NSString *)key; | ||
- (void)removeObjectForKey:(id)aKey; | ||
- (NSArray *)allKeys; | ||
|
||
@end |
Oops, something went wrong.