Skip to content

Commit 9c7cf76

Browse files
committed
Authenticate requests between components
Use a randomly generated token to authenticate requests to the server for all endpoints, including WebSockets. The token is passed to the node server via STDIN, which should sufficiently prevent eavesdropping.
1 parent b3f370f commit 9c7cf76

21 files changed

+252
-37
lines changed

Uebersicht/UBAppDelegate.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020

2121
- (void)widgetDirDidChange;
2222
- (void)interactionDidChange;
23+
- (void)enableSecurityDidChange;
2324
- (void)screensChanged:(NSDictionary*)screens;
2425
- (IBAction)showPreferences:(id)sender;
2526
- (IBAction)openWidgetDir:(id)sender;

Uebersicht/UBAppDelegate.m

Lines changed: 91 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,13 @@ @implementation UBAppDelegate {
3333
UBWidgetsStore* widgetsStore;
3434
UBWidgetsController* widgetsController;
3535
BOOL needsRefresh;
36+
NSString *token;
3637
}
3738

3839
@synthesize statusBarMenu;
3940

41+
static const uint kTokenLength256Bits = 32;
42+
4043
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
4144
{
4245
needsRefresh = YES;
@@ -110,33 +113,58 @@ - (void)applicationDidFinishLaunching:(NSNotification *)aNotification
110113
[self listenToWallpaperChanges];
111114
}
112115

113-
- (NSDictionary*)fetchState
116+
- (void)fetchState:(void (^)(NSDictionary*))callback
114117
{
115-
[[UBWebSocket sharedSocket] open:[self serverUrl:@"ws"]];
118+
[[UBWebSocket sharedSocket] open:[self serverUrl:@"ws"]
119+
token:token];
120+
116121
NSURL *urlPath = [[self serverUrl:@"http"] URLByAppendingPathComponent: @"state/"];
117-
NSData *jsonData = [NSData dataWithContentsOfURL:urlPath];
118-
NSError *error = nil;
119-
NSDictionary *dataDictionary = [NSJSONSerialization
120-
JSONObjectWithData: jsonData
121-
options: NSJSONReadingMutableContainers
122-
error: &error
123-
];
124-
if (error) NSLog(@"%@", error);
125-
return dataDictionary;
122+
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:urlPath];
123+
[request setValue:@"Übersicht" forHTTPHeaderField:@"Origin"];
124+
[request setValue:[NSString stringWithFormat:@"token=%@", token] forHTTPHeaderField:@"Cookie"];
125+
NSURLSessionDataTask *t = [[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
126+
if (error) {
127+
NSLog(@"error loading state: %@", error);
128+
dispatch_async(dispatch_get_main_queue(), ^{
129+
callback(@{});
130+
});
131+
return;
132+
}
133+
134+
NSError *jsonError = nil;
135+
NSDictionary *dataDictionary = [NSJSONSerialization
136+
JSONObjectWithData: data
137+
options: NSJSONReadingMutableContainers
138+
error: &jsonError
139+
];
140+
if (jsonError) {
141+
NSLog(@"error parsing state: %@", error);
142+
dispatch_async(dispatch_get_main_queue(), ^{
143+
callback(@{});
144+
});
145+
return;
146+
}
147+
148+
dispatch_async(dispatch_get_main_queue(), ^{
149+
callback(dataDictionary);
150+
});
151+
}];
152+
[t resume];
126153
}
127154

128155
- (void)startUp
129156
{
130157

131158
NSLog(@"starting server task");
132-
159+
133160
void (^handleData)(NSString*) = ^(NSString* output) {
134161
// note that these might be called several times
135162
if ([output rangeOfString:@"server started"].location != NSNotFound) {
136-
[self->widgetsStore reset: [self fetchState]];
137-
// this will trigger a render
138-
[self->screensController syncScreens:self];
139-
163+
[self fetchState:^(NSDictionary* state) {
164+
[self->widgetsStore reset: state];
165+
// this will trigger a render
166+
[self->screensController syncScreens:self];
167+
}];
140168
} else if ([output rangeOfString:@"EADDRINUSE"].location != NSNotFound) {
141169
self->portOffset++;
142170
}
@@ -216,15 +244,28 @@ - (NSTask*)launchWidgetServer:(NSString*)widgetPath
216244
onData:(void (^)(NSString*))dataHandler
217245
onExit:(void (^)(NSTask*))exitHandler
218246
{
247+
token = generateToken(kTokenLength256Bits);
248+
219249
NSBundle* bundle = [NSBundle mainBundle];
220250
NSString* nodePath = [bundle pathForResource:@"localnode" ofType:nil];
221251
NSString* serverPath = [bundle pathForResource:@"server" ofType:@"js"];
222-
BOOL loginShell = [[NSUserDefaults standardUserDefaults]
223-
boolForKey:@"loginShell"
224-
];
225252

226253
NSTask *task = [[NSTask alloc] init];
227254

255+
NSPipe *inPipe = [NSPipe pipe];
256+
NSFileHandle *fh = [inPipe fileHandleForWriting];
257+
NSMutableDictionary *secrets = [[NSMutableDictionary alloc] init];
258+
secrets[@"token"] = token;
259+
NSError *jsonErr = NULL;
260+
NSData *stdinData = [NSJSONSerialization dataWithJSONObject:secrets options:0 error:&jsonErr];
261+
if (!stdinData) {
262+
NSLog(@"[FATAL] %@", jsonErr);
263+
return NULL;
264+
}
265+
[fh writeData:stdinData];
266+
[fh closeFile];
267+
[task setStandardInput:inPipe];
268+
228269
[task setStandardOutput:[NSPipe pipe]];
229270
[task.standardOutput fileHandleForReading].readabilityHandler = ^(NSFileHandle *handle) {
230271
NSData *output = [handle availableData];
@@ -252,7 +293,8 @@ - (NSTask*)launchWidgetServer:(NSString*)widgetPath
252293
@"-d", widgetPath,
253294
@"-p", [NSString stringWithFormat:@"%d", PORT + portOffset],
254295
@"-s", [[self getPreferencesDir] path],
255-
loginShell ? @"--login-shell" : @""
296+
!preferences.enableSecurity ? @"--disable-token" : @"",
297+
preferences.loginShell ? @"--login-shell" : @""
256298
]];
257299

258300
[task launch];
@@ -294,6 +336,7 @@ - (void)screensChanged:(NSDictionary*)screens
294336
[windowsController
295337
updateWindows:screens
296338
baseUrl: [self serverUrl: @"http"]
339+
token:token
297340
interactionEnabled: preferences.enableInteraction
298341
forceRefresh: needsRefresh
299342
];
@@ -323,6 +366,11 @@ - (void)interactionDidChange
323366
[screensController syncScreens:self];
324367
}
325368

369+
- (void)enableSecurityDidChange
370+
{
371+
[self shutdown:true];
372+
}
373+
326374
- (IBAction)showPreferences:(id)sender
327375
{
328376
[preferences showWindow:nil];
@@ -453,6 +501,29 @@ void wallpaperSettingsChanged(
453501
}
454502
}
455503

504+
/*!
505+
@function generateToken
506+
507+
@abstract
508+
Returns a base64-encoded @p NSString* of specified number of random bytes.
509+
510+
@param length
511+
A reasonably large, non-zero number representing the length in bytes.
512+
For example, a value of 32 would generate a 256-bit token.
513+
514+
@result Returns @p NSString* on success; panics on failure.
515+
*/
516+
NSString* generateToken(uint length) {
517+
UInt8 buf[length];
518+
519+
int error = SecRandomCopyBytes(kSecRandomDefault, length, &buf);
520+
if (error != errSecSuccess) {
521+
panic("failed to generate token");
522+
}
523+
524+
return [[NSData dataWithBytes:buf length:length] base64EncodedStringWithOptions:0];
525+
}
526+
456527
#
457528
# pragma mark script support
458529
#

Uebersicht/UBPreferencesController.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
@property NSURL* widgetDir;
2121
@property BOOL loginShell;
2222
@property BOOL enableInteraction;
23+
@property BOOL enableSecurity;
2324

2425
- (IBAction)showFilePicker:(id)sender;
2526

Uebersicht/UBPreferencesController.m

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ @implementation UBPreferencesController {
1616
LSSharedFileListRef loginItems;
1717
}
1818

19+
static NSString * const kDefaultsEnableSecurity = @"enableSecurity";
20+
1921
@synthesize filePicker;
2022

2123
- (id)initWithWindowNibName:(NSString *)windowNibName
@@ -26,7 +28,8 @@ - (id)initWithWindowNibName:(NSString *)windowNibName
2628
NSData* defaultWidgetDir = [self ensureDefaultsWidgetDir];
2729
NSDictionary *appDefaults = @{
2830
@"widgetDirectory": defaultWidgetDir,
29-
@"enableInteraction": @YES
31+
@"enableInteraction": @YES,
32+
kDefaultsEnableSecurity: @YES,
3033
};
3134
[[NSUserDefaults standardUserDefaults] registerDefaults:appDefaults];
3235

@@ -196,6 +199,23 @@ - (void)setEnableInteraction:(BOOL)enabled
196199
[(UBAppDelegate *)[NSApp delegate] interactionDidChange];
197200
}
198201

202+
#
203+
#pragma mark Security
204+
#
205+
206+
- (BOOL)enableSecurity
207+
{
208+
NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults];
209+
return [defaults boolForKey:kDefaultsEnableSecurity];
210+
}
211+
212+
- (void)setEnableSecurity:(BOOL)enabled
213+
{
214+
NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults];
215+
[defaults setBool:enabled forKey:kDefaultsEnableSecurity];
216+
[(UBAppDelegate *)[NSApp delegate] enableSecurityDidChange];
217+
}
218+
199219
#
200220
#pragma mark Startup
201221
#

Uebersicht/UBPreferencesController.xib

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,12 @@
1717
<window title="Preferences" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" restorable="NO" releasedWhenClosed="NO" visibleAtLaunch="NO" animationBehavior="default" id="1">
1818
<windowStyleMask key="styleMask" titled="YES" closable="YES" miniaturizable="YES" resizable="YES"/>
1919
<windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
20-
<rect key="contentRect" x="472" y="636" width="580" height="236"/>
20+
<rect key="contentRect" x="472" y="636" width="580" height="300"/>
2121
<rect key="screenRect" x="0.0" y="0.0" width="2560" height="1415"/>
22-
<value key="minSize" type="size" width="580" height="236"/>
23-
<value key="maxSize" type="size" width="580" height="236"/>
22+
<value key="minSize" type="size" width="580" height="300"/>
23+
<value key="maxSize" type="size" width="580" height="300"/>
2424
<view key="contentView" autoresizesSubviews="NO" id="2">
25-
<rect key="frame" x="0.0" y="0.0" width="580" height="236"/>
25+
<rect key="frame" x="0.0" y="0.0" width="580" height="300"/>
2626
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
2727
<subviews>
2828
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="5">
@@ -124,6 +124,15 @@
124124
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
125125
</textFieldCell>
126126
</textField>
127+
<textField verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" fixedFrame="YES" preferredMaxLayoutWidth="268" translatesAutoresizingMaskIntoConstraints="NO" id="BRa-2G-OzE">
128+
<rect key="frame" x="239" y="217" width="330" height="42"/>
129+
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
130+
<textFieldCell key="cell" controlSize="small" sendsActionOnEndEditing="YES" title="CAUTION! Disabling this will allow any local user or process to execute commands under your account." id="W8m-Vx-Tdo">
131+
<font key="font" metaFont="label" size="11"/>
132+
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
133+
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
134+
</textFieldCell>
135+
</textField>
127136
<textField verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" fixedFrame="YES" preferredMaxLayoutWidth="268" translatesAutoresizingMaskIntoConstraints="NO" id="VVJ-Cl-FSt">
128137
<rect key="frame" x="239" y="88" width="312" height="42"/>
129138
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
@@ -133,6 +142,26 @@
133142
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
134143
</textFieldCell>
135144
</textField>
145+
<button fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="4Cm-6a-MW9">
146+
<rect key="frame" x="220" y="265" width="182" height="18"/>
147+
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
148+
<buttonCell key="cell" type="check" title="Enable security measures" bezelStyle="regularSquare" imagePosition="left" state="on" inset="2" id="6iu-kH-BSa">
149+
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
150+
<font key="font" metaFont="system"/>
151+
</buttonCell>
152+
<connections>
153+
<binding destination="-2" name="value" keyPath="enableSecurity" id="OTS-dV-otR"/>
154+
</connections>
155+
</button>
156+
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="HZc-Ku-Lxb">
157+
<rect key="frame" x="158" y="267" width="58" height="16"/>
158+
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
159+
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Security:" id="7yt-3i-Laz">
160+
<font key="font" metaFont="system"/>
161+
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
162+
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
163+
</textFieldCell>
164+
</textField>
136165
</subviews>
137166
</view>
138167
<connections>

Uebersicht/UBWebSocket.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@
1212
@interface UBWebSocket : NSObject <SRWebSocketDelegate>
1313

1414
+ (id)sharedSocket;
15-
- (void)open:(NSURL*)aUrl;
15+
- (void)open:(NSURL*)aUrl
16+
token:(NSString*)aToken;
1617
- (void)close;
1718
- (void)send:(id)message;
1819
- (void)listen:(void (^)(id))listener;

Uebersicht/UBWebSocket.m

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ @implementation UBWebSocket {
1313
NSMutableArray* queuedMessages;
1414
SRWebSocket* ws;
1515
NSURL* url;
16+
NSString *token;
1617
}
1718

1819

@@ -49,16 +50,26 @@ - (void)listen:(void (^)(id))listener
4950
}
5051

5152
- (void)open:(NSURL*)aUrl
53+
token:(NSString*)aToken
5254
{
5355
if (ws) {
5456
return;
5557
}
5658

59+
token = aToken;
5760
url = aUrl;
5861
NSMutableURLRequest* request = [NSMutableURLRequest requestWithURL:url];
5962
[request setValue:@"Übersicht" forHTTPHeaderField:@"Origin"];
6063
ws = [[SRWebSocket alloc] initWithURLRequest: request];
6164
ws.delegate = self;
65+
ws.requestCookies = @[
66+
[NSHTTPCookie cookieWithProperties:@{
67+
NSHTTPCookieDomain: url.host,
68+
NSHTTPCookiePath: @"/",
69+
NSHTTPCookieName: @"token",
70+
NSHTTPCookieValue: token,
71+
}],
72+
];
6273
[ws open];
6374
}
6475

@@ -76,7 +87,8 @@ - (void)reopen
7687
{
7788
[self close];
7889
if (url) {
79-
[self open:url];
90+
[self open:url
91+
token:token];
8092
}
8193
}
8294

Uebersicht/UBWebViewController.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515

1616
- (id)initWithFrame:(NSRect)frame;
1717
- (void)load:(NSURL*)url;
18+
- (void)setToken:(NSString*)token;
1819
- (void)reload;
1920
- (void)redraw;
2021
- (void)destroy;

0 commit comments

Comments
 (0)