Skip to content

Commit 5a43805

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 270a7d8 commit 5a43805

21 files changed

+256
-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: 95 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,60 @@ - (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+
if (preferences.enableSecurity) {
125+
[request setValue:[NSString stringWithFormat:@"token=%@", token] forHTTPHeaderField:@"Cookie"];
126+
}
127+
NSURLSessionDataTask *t = [[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
128+
if (error) {
129+
NSLog(@"error loading state: %@", error);
130+
dispatch_async(dispatch_get_main_queue(), ^{
131+
callback(@{});
132+
});
133+
return;
134+
}
135+
136+
NSError *jsonError = nil;
137+
NSDictionary *dataDictionary = [NSJSONSerialization
138+
JSONObjectWithData: data
139+
options: NSJSONReadingMutableContainers
140+
error: &jsonError
141+
];
142+
if (jsonError) {
143+
NSLog(@"error parsing state: %@", error);
144+
dispatch_async(dispatch_get_main_queue(), ^{
145+
callback(@{});
146+
});
147+
return;
148+
}
149+
150+
dispatch_async(dispatch_get_main_queue(), ^{
151+
callback(dataDictionary);
152+
});
153+
}];
154+
[t resume];
126155
}
127156

128157
- (void)startUp
129158
{
130159

131160
NSLog(@"starting server task");
132-
161+
133162
void (^handleData)(NSString*) = ^(NSString* output) {
134163
// note that these might be called several times
135164
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-
165+
[self fetchState:^(NSDictionary* state) {
166+
[self->widgetsStore reset: state];
167+
// this will trigger a render
168+
[self->screensController syncScreens:self];
169+
}];
140170
} else if ([output rangeOfString:@"EADDRINUSE"].location != NSNotFound) {
141171
self->portOffset++;
142172
}
@@ -216,15 +246,30 @@ - (NSTask*)launchWidgetServer:(NSString*)widgetPath
216246
onData:(void (^)(NSString*))dataHandler
217247
onExit:(void (^)(NSTask*))exitHandler
218248
{
249+
token = generateToken(kTokenLength256Bits);
250+
219251
NSBundle* bundle = [NSBundle mainBundle];
220252
NSString* nodePath = [bundle pathForResource:@"localnode" ofType:nil];
221253
NSString* serverPath = [bundle pathForResource:@"server" ofType:@"js"];
222-
BOOL loginShell = [[NSUserDefaults standardUserDefaults]
223-
boolForKey:@"loginShell"
224-
];
225254

226255
NSTask *task = [[NSTask alloc] init];
227256

257+
NSPipe *inPipe = [NSPipe pipe];
258+
NSFileHandle *fh = [inPipe fileHandleForWriting];
259+
NSMutableDictionary *secrets = [[NSMutableDictionary alloc] init];
260+
if (preferences.enableSecurity) {
261+
secrets[@"token"] = token;
262+
}
263+
NSError *jsonErr = NULL;
264+
NSData *stdinData = [NSJSONSerialization dataWithJSONObject:secrets options:0 error:&jsonErr];
265+
if (!stdinData) {
266+
NSLog(@"[FATAL] %@", jsonErr);
267+
return NULL;
268+
}
269+
[fh writeData:stdinData];
270+
[fh closeFile];
271+
[task setStandardInput:inPipe];
272+
228273
[task setStandardOutput:[NSPipe pipe]];
229274
[task.standardOutput fileHandleForReading].readabilityHandler = ^(NSFileHandle *handle) {
230275
NSData *output = [handle availableData];
@@ -252,7 +297,8 @@ - (NSTask*)launchWidgetServer:(NSString*)widgetPath
252297
@"-d", widgetPath,
253298
@"-p", [NSString stringWithFormat:@"%d", PORT + portOffset],
254299
@"-s", [[self getPreferencesDir] path],
255-
loginShell ? @"--login-shell" : @""
300+
!preferences.enableSecurity ? @"--disable-token" : @"",
301+
preferences.loginShell ? @"--login-shell" : @""
256302
]];
257303

258304
[task launch];
@@ -294,6 +340,7 @@ - (void)screensChanged:(NSDictionary*)screens
294340
[windowsController
295341
updateWindows:screens
296342
baseUrl: [self serverUrl: @"http"]
343+
token:token
297344
interactionEnabled: preferences.enableInteraction
298345
forceRefresh: needsRefresh
299346
];
@@ -323,6 +370,11 @@ - (void)interactionDidChange
323370
[screensController syncScreens:self];
324371
}
325372

373+
- (void)enableSecurityDidChange
374+
{
375+
[self shutdown:true];
376+
}
377+
326378
- (IBAction)showPreferences:(id)sender
327379
{
328380
[preferences showWindow:nil];
@@ -453,6 +505,29 @@ void wallpaperSettingsChanged(
453505
}
454506
}
455507

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

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)