From 8426ca288713e3cae764c8fff3a3df715b691a9c Mon Sep 17 00:00:00 2001 From: Greg Neagle Date: Tue, 20 Jun 2017 06:51:36 -0700 Subject: [PATCH 1/9] Drop support for 10.6-era ShadowHash file; add 10.8+ ShadowHashData and AuthenticationAuthority to user account plist --- CreateUserPkg.xcodeproj/project.pbxproj | 23 ++++++++-------- CreateUserPkg/CUPDocument.h | 3 +++ CreateUserPkg/CUPDocument.m | 35 +++++++++++++++++++++++++ CreateUserPkg/create_package.py | 17 +++--------- 4 files changed, 54 insertions(+), 24 deletions(-) diff --git a/CreateUserPkg.xcodeproj/project.pbxproj b/CreateUserPkg.xcodeproj/project.pbxproj index 73d175b..ff98203 100644 --- a/CreateUserPkg.xcodeproj/project.pbxproj +++ b/CreateUserPkg.xcodeproj/project.pbxproj @@ -24,6 +24,7 @@ 6605E696159B964D0048362A /* CUPUserNameFormatter.m in Sources */ = {isa = PBXBuildFile; fileRef = 6605E692159B964D0048362A /* CUPUserNameFormatter.m */; }; 6605E697159B964D0048362A /* CUPUUIDFormatter.m in Sources */ = {isa = PBXBuildFile; fileRef = 6605E694159B964D0048362A /* CUPUUIDFormatter.m */; }; 66B221D8159C4C30003FDF4E /* CreateUserPkg.icns in Resources */ = {isa = PBXBuildFile; fileRef = 66B221D7159C4C30003FDF4E /* CreateUserPkg.icns */; }; + C09EAB201EF8EDFE005A8859 /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C09EAB1F1EF8EDFE005A8859 /* Security.framework */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ @@ -58,6 +59,7 @@ 6605E693159B964D0048362A /* CUPUUIDFormatter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CUPUUIDFormatter.h; sourceTree = ""; }; 6605E694159B964D0048362A /* CUPUUIDFormatter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CUPUUIDFormatter.m; sourceTree = ""; }; 66B221D7159C4C30003FDF4E /* CreateUserPkg.icns */ = {isa = PBXFileReference; lastKnownFileType = image.icns; path = CreateUserPkg.icns; sourceTree = ""; }; + C09EAB1F1EF8EDFE005A8859 /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = ../../../../System/Library/Frameworks/Security.framework; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -65,6 +67,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + C09EAB201EF8EDFE005A8859 /* Security.framework in Frameworks */, 0596D17D159AF27800B898B4 /* Cocoa.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -102,6 +105,7 @@ 0596D17E159AF27800B898B4 /* Other Frameworks */ = { isa = PBXGroup; children = ( + C09EAB1F1EF8EDFE005A8859 /* Security.framework */, 0596D17F159AF27800B898B4 /* AppKit.framework */, 0596D180159AF27800B898B4 /* CoreData.framework */, 0596D181159AF27800B898B4 /* Foundation.framework */, @@ -180,11 +184,6 @@ CLASSPREFIX = CUP; LastUpgradeCheck = 0730; ORGANIZATIONNAME = "University of Gothenburg"; - TargetAttributes = { - 0596D177159AF27800B898B4 = { - DevelopmentTeam = 5KQ3D3FG5H; - }; - }; }; buildConfigurationList = 0596D172159AF27800B898B4 /* Build configuration list for PBXProject "CreateUserPkg" */; compatibilityVersion = "Xcode 3.2"; @@ -377,13 +376,14 @@ buildSettings = { CLANG_ENABLE_OBJC_ARC = YES; CODE_SIGN_ENTITLEMENTS = CreateUserPkg/CreateUserPkg.entitlements; - CODE_SIGN_IDENTITY = "Mac Developer"; - "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Mac Developer"; + CODE_SIGN_IDENTITY = ""; + "CODE_SIGN_IDENTITY[sdk=macosx*]" = ""; COMBINE_HIDPI_IMAGES = YES; + DEVELOPMENT_TEAM = ""; GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = "CreateUserPkg/CreateUserPkg-Prefix.pch"; INFOPLIST_FILE = "CreateUserPkg/CreateUserPkg-Info.plist"; - MACOSX_DEPLOYMENT_TARGET = 10.7; + MACOSX_DEPLOYMENT_TARGET = 10.8; PRODUCT_BUNDLE_IDENTIFIER = "se.gu.it.${PRODUCT_NAME:rfc1034identifier}"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE = ""; @@ -396,13 +396,14 @@ buildSettings = { CLANG_ENABLE_OBJC_ARC = YES; CODE_SIGN_ENTITLEMENTS = CreateUserPkg/CreateUserPkg.entitlements; - CODE_SIGN_IDENTITY = "Mac Developer"; - "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Mac Developer"; + CODE_SIGN_IDENTITY = ""; + "CODE_SIGN_IDENTITY[sdk=macosx*]" = ""; COMBINE_HIDPI_IMAGES = YES; + DEVELOPMENT_TEAM = ""; GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = "CreateUserPkg/CreateUserPkg-Prefix.pch"; INFOPLIST_FILE = "CreateUserPkg/CreateUserPkg-Info.plist"; - MACOSX_DEPLOYMENT_TARGET = 10.7; + MACOSX_DEPLOYMENT_TARGET = 10.8; PRODUCT_BUNDLE_IDENTIFIER = "se.gu.it.${PRODUCT_NAME:rfc1034identifier}"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE = ""; diff --git a/CreateUserPkg/CUPDocument.h b/CreateUserPkg/CUPDocument.h index d7ca738..f5615ec 100644 --- a/CreateUserPkg/CUPDocument.h +++ b/CreateUserPkg/CUPDocument.h @@ -29,6 +29,7 @@ NSTextField *__unsafe_unretained _version; NSMutableString *_shadowHash; + NSData *_shadowHashData; NSData *_kcPassword; NSMutableDictionary *_docState; } @@ -50,6 +51,7 @@ @property (unsafe_unretained) IBOutlet NSTextField *version; @property (strong) NSMutableString *shadowHash; +@property (strong) NSData *shadowHashData; @property (strong) NSData *kcPassword; @property (strong) NSMutableDictionary *docState; @@ -57,6 +59,7 @@ - (IBAction)didLeaveAccountName:(id)sender; - (IBAction)didLeavePassword:(id)sender; +- (void)calculateShadowHashData:(NSString *)pwd; - (void)calculateShadowHash:(NSString *)pwd; - (void)calculateKCPassword:(NSString *)pwd; - (void)setTextField:(NSTextField *)field withKey:(NSString *)key; diff --git a/CreateUserPkg/CUPDocument.m b/CreateUserPkg/CUPDocument.m index 4550cc3..a26264d 100644 --- a/CreateUserPkg/CUPDocument.m +++ b/CreateUserPkg/CUPDocument.m @@ -7,6 +7,8 @@ // #import +#import +#import #import "CUPDocument.h" #import "CUPUIDFormatter.h" #import "CUPUserNameFormatter.h" @@ -31,6 +33,7 @@ @implementation CUPDocument @synthesize version = _version; @synthesize shadowHash = _shadowHash; +@synthesize shadowHashData = _shadowHashData; @synthesize kcPassword = _kcPassword; @synthesize docState = _docState; @@ -99,8 +102,38 @@ - (IBAction)didLeavePassword:(id)sender { } } + +- (void)calculateShadowHashData:(NSString *)pwd +{ + unsigned char salt[32]; + int status = SecRandomCopyBytes(kSecRandomDefault, 32, salt); + unsigned char key[128]; + unsigned int rounds = 400000; + CCKeyDerivationPBKDF(kCCPBKDF2, + [pwd UTF8String], + (CC_LONG)[pwd lengthOfBytesUsingEncoding:NSUTF8StringEncoding], + salt, 32, + kCCPRFHmacAlgSHA512, rounds, key, 128); + NSDictionary *dictionary = @{ + @"SALTED-SHA512-PBKDF2" : @{ + @"entropy" : [NSData dataWithBytes: key length: 128], + @"iterations" : [NSNumber numberWithUnsignedInt: rounds], + @"salt" : [NSData dataWithBytes: salt length: 32] + } + }; + NSError * _Nullable __autoreleasing * error = NULL; + NSData *plistData = [NSPropertyListSerialization dataWithPropertyList: dictionary + format: NSPropertyListBinaryFormat_v1_0 + options: 0 + error: error]; + self.shadowHashData = plistData; + //NSLog(@"ShadowHashData: %@", plistData); +} + + - (void)calculateShadowHash:(NSString *)pwd { + [self calculateShadowHashData:pwd]; CC_SHA1_CTX ctx; unsigned char salted_sha1_hash[24]; union _salt { @@ -296,6 +329,7 @@ - (BOOL)validateDocumentAndUpdateState [self calculateShadowHash:[self.password stringValue]]; } (self.docState)[@"shadowHash"] = [NSString stringWithString:self.shadowHash]; + (self.docState)[@"shadowHashData"] = self.shadowHashData; } if ([self.automaticLogin state] == NSOnState) { if ([self.kcPassword length] == 0) { @@ -374,6 +408,7 @@ - (BOOL)readFromData:(NSData *)data ofType:(NSString *)typeName error:(NSError * [self setDocStateKey:@"packageID" fromDict:document]; [self setDocStateKey:@"version" fromDict:document]; [self setDocStateKey:@"shadowHash" fromDict:document]; + [self setDocStateKey:@"shadowHashData" fromDict:document]; [self setDocStateKey:@"imageData" fromDict:document]; [self setDocStateKey:@"imagePath" fromDict:document]; [self setDocStateKey:@"kcPassword" fromDict:document]; diff --git a/CreateUserPkg/create_package.py b/CreateUserPkg/create_package.py index 23c4894..8bdb9b5 100755 --- a/CreateUserPkg/create_package.py +++ b/CreateUserPkg/create_package.py @@ -21,7 +21,7 @@ REQUIRED_KEYS = set(( u"fullName", u"accountName", - u"shadowHash", + u"shadowHashData", u"userID", u"isAdmin", u"homeDirectory", @@ -219,7 +219,8 @@ def main(argv): # Create a dictionary with user attributes. user_plist = dict() - user_plist[u"authentication_authority"] = [u";ShadowHash;"] + user_plist[u"authentication_authority"] = [u";ShadowHash;HASHLIST:"] + user_plist[u"ShadowHashData"] = [input_data[u"shadowHashData"]] user_plist[u"generateduid"] = [input_data[u"uuid"]] user_plist[u"gid"] = [u"20"] user_plist[u"home"] = [input_data[u"homeDirectory"]] @@ -251,7 +252,7 @@ def main(argv): kcpassword = None is_admin = input_data.get(u"isAdmin", False) - # Create a package with the plist for our user and a shadow hash file. + # Create a package with the plist for our user. tmp_path = tempfile.mkdtemp() try: # Create a root for the package. @@ -260,7 +261,6 @@ def main(argv): # Create package structure inside root. os.makedirs(os.path.join(pkg_root_path, "private/var/db/dslocal/nodes"), 0755) os.makedirs(os.path.join(pkg_root_path, "private/var/db/dslocal/nodes/Default/users"), 0700) - os.makedirs(os.path.join(pkg_root_path, "private/var/db/shadow/hash"), 0700) if kcpassword: os.makedirs(os.path.join(pkg_root_path, "private/etc"), 0755) # Save user plist. @@ -270,15 +270,6 @@ def main(argv): user_plist_name) plistlib.writePlist(user_plist, user_plist_path) os.chmod(user_plist_path, 0600) - # Save shadow hash. - shadow_hash_name = input_data[u"uuid"] - shadow_hash_path = os.path.join(pkg_root_path, - "private/var/db/shadow/hash", - shadow_hash_name) - f = open(shadow_hash_path, "w") - f.write(shadow_hash) - f.close() - os.chmod(shadow_hash_path, 0600) # Save kcpassword. if kcpassword: kcpassword_path = os.path.join(pkg_root_path, "private/etc/kcpassword") From 509205378c994bee1ab1a0c85dec1e793953cb7d Mon Sep 17 00:00:00 2001 From: Greg Neagle Date: Tue, 20 Jun 2017 08:18:59 -0700 Subject: [PATCH 2/9] Calculate appropriate iterations instead of hard-coding a value --- CreateUserPkg/CUPDocument.m | 49 +++++++++++++++++++++---------------- 1 file changed, 28 insertions(+), 21 deletions(-) diff --git a/CreateUserPkg/CUPDocument.m b/CreateUserPkg/CUPDocument.m index a26264d..3b431bb 100644 --- a/CreateUserPkg/CUPDocument.m +++ b/CreateUserPkg/CUPDocument.m @@ -107,27 +107,34 @@ - (void)calculateShadowHashData:(NSString *)pwd { unsigned char salt[32]; int status = SecRandomCopyBytes(kSecRandomDefault, 32, salt); - unsigned char key[128]; - unsigned int rounds = 400000; - CCKeyDerivationPBKDF(kCCPBKDF2, - [pwd UTF8String], - (CC_LONG)[pwd lengthOfBytesUsingEncoding:NSUTF8StringEncoding], - salt, 32, - kCCPRFHmacAlgSHA512, rounds, key, 128); - NSDictionary *dictionary = @{ - @"SALTED-SHA512-PBKDF2" : @{ - @"entropy" : [NSData dataWithBytes: key length: 128], - @"iterations" : [NSNumber numberWithUnsignedInt: rounds], - @"salt" : [NSData dataWithBytes: salt length: 32] - } - }; - NSError * _Nullable __autoreleasing * error = NULL; - NSData *plistData = [NSPropertyListSerialization dataWithPropertyList: dictionary - format: NSPropertyListBinaryFormat_v1_0 - options: 0 - error: error]; - self.shadowHashData = plistData; - //NSLog(@"ShadowHashData: %@", plistData); + if (status == 0) { + unsigned char key[128]; + // calculate the number of iterations to use + unsigned int rounds = CCCalibratePBKDF(kCCPBKDF2, + [pwd lengthOfBytesUsingEncoding:NSUTF8StringEncoding], + 32, kCCPRFHmacAlgSHA512, 128, 100); + // derive our SALTED-SHA512-PBKDF2 key + CCKeyDerivationPBKDF(kCCPBKDF2, + [pwd UTF8String], + (CC_LONG)[pwd lengthOfBytesUsingEncoding:NSUTF8StringEncoding], + salt, 32, + kCCPRFHmacAlgSHA512, rounds, key, 128); + // Make a dictionary containing the needed fields + NSDictionary *dictionary = @{ + @"SALTED-SHA512-PBKDF2" : @{ + @"entropy" : [NSData dataWithBytes: key length: 128], + @"iterations" : [NSNumber numberWithUnsignedInt: rounds], + @"salt" : [NSData dataWithBytes: salt length: 32] + } + }; + // convert to binary plist data + NSError *error = NULL; + NSData *plistData = [NSPropertyListSerialization dataWithPropertyList: dictionary + format: NSPropertyListBinaryFormat_v1_0 + options: 0 + error: &error]; + self.shadowHashData = plistData; + } } From 9fd8648124321f118ba3f3f72b21eb9664008589 Mon Sep 17 00:00:00 2001 From: Greg Neagle Date: Tue, 20 Jun 2017 08:27:19 -0700 Subject: [PATCH 3/9] Update README.markdown --- README.markdown | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/README.markdown b/README.markdown index 1e94feb..5460f2a 100644 --- a/README.markdown +++ b/README.markdown @@ -1,24 +1,13 @@ -This project is no longer maintained -==================================== - -Please see the [blog post](https://magervalp.github.io/2016/12/07/createuserpkg-up-for-adoption.html). 10.13 doesn't appear to support SHA1 passwords anymore, and this project needs to be updated to create PBKDF2 passwords (see Security Notes below). - -Download -======== - -Download CreateUserPkg from the project homepage at [http://magervalp.github.com/CreateUserPkg/](http://magervalp.github.com/CreateUserPkg/) - - Overview ======== -This utility creates packages that create local user accounts when installed. The packages can create users on 10.5+, and they are compatible with all workflows that can install standard installer pkgs. For the details on how the packages work, see Greg Neagle's article in the [May 2012 issue of MacTech](http://www.mactech.com/issue-TOCs-2012). +This utility creates packages that create local user accounts when installed. The packages can create users on 10.8+, and they are compatible with all workflows that can install standard installer pkgs. For the details on how the packages work, see Greg Neagle's article in the [May 2012 issue of MacTech](http://www.mactech.com/issue-TOCs-2012). Security Notes -------------- -Packages created using this utility encrypts the password as a salted SHA1 hash, which is how 10.5 and 10.6 normally stores it. Using a dictionary based attack they are reasonably easy to crack on modern machines, so make sure you pick a good, strong password. In 10.7 and up this is converted to PBKDF2 upon first login, which is much harder to crack, but the SHA1 hash can still be extracted from the package. +Packages created using this utility install a user account plist with ShadowHashData contaning a "SALTED-SHA512-PBKDF2" key derived from the provided password. This is the standard mechanism for storing user authentication data in macOS 10.8+. If you enable automatic login the password is stored in a kcpassword file, which is merely obfuscated and not encrypted - extracting the password (no matter how strong) is trivial. From ef4d0fa95ef4d1669ffd23c36a8b4fd0e29f2607 Mon Sep 17 00:00:00 2001 From: Greg Neagle Date: Tue, 20 Jun 2017 09:13:21 -0700 Subject: [PATCH 4/9] Replace some magic numbers with #defined constants --- CreateUserPkg/CUPDocument.h | 4 ++++ CreateUserPkg/CUPDocument.m | 16 ++++++++-------- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/CreateUserPkg/CUPDocument.h b/CreateUserPkg/CUPDocument.h index f5615ec..bb2a620 100644 --- a/CreateUserPkg/CUPDocument.h +++ b/CreateUserPkg/CUPDocument.h @@ -72,6 +72,10 @@ #define SALTED_SHA1_OFFSET (64 + 40 + 64) #define SHADOW_HASH_LEN 1240 +#define PBKDF2_SALT_LEN 32 +#define PBKDF2_ENTROPY_LEN 128 + + #define CUP_PASSWORD_PLACEHOLDER @"SEIPHATSXSTX$D418JMPC000" enum { diff --git a/CreateUserPkg/CUPDocument.m b/CreateUserPkg/CUPDocument.m index 3b431bb..0946c57 100644 --- a/CreateUserPkg/CUPDocument.m +++ b/CreateUserPkg/CUPDocument.m @@ -105,26 +105,26 @@ - (IBAction)didLeavePassword:(id)sender { - (void)calculateShadowHashData:(NSString *)pwd { - unsigned char salt[32]; - int status = SecRandomCopyBytes(kSecRandomDefault, 32, salt); + unsigned char salt[PBKDF2_SALT_LEN]; + int status = SecRandomCopyBytes(kSecRandomDefault, PBKDF2_SALT_LEN, salt); if (status == 0) { - unsigned char key[128]; + unsigned char key[PBKDF2_ENTROPY_LEN]; // calculate the number of iterations to use unsigned int rounds = CCCalibratePBKDF(kCCPBKDF2, [pwd lengthOfBytesUsingEncoding:NSUTF8StringEncoding], - 32, kCCPRFHmacAlgSHA512, 128, 100); + PBKDF2_SALT_LEN, kCCPRFHmacAlgSHA512, PBKDF2_ENTROPY_LEN, 100); // derive our SALTED-SHA512-PBKDF2 key CCKeyDerivationPBKDF(kCCPBKDF2, [pwd UTF8String], (CC_LONG)[pwd lengthOfBytesUsingEncoding:NSUTF8StringEncoding], - salt, 32, - kCCPRFHmacAlgSHA512, rounds, key, 128); + salt, PBKDF2_SALT_LEN, + kCCPRFHmacAlgSHA512, rounds, key, PBKDF2_ENTROPY_LEN); // Make a dictionary containing the needed fields NSDictionary *dictionary = @{ @"SALTED-SHA512-PBKDF2" : @{ - @"entropy" : [NSData dataWithBytes: key length: 128], + @"entropy" : [NSData dataWithBytes: key length: PBKDF2_ENTROPY_LEN], @"iterations" : [NSNumber numberWithUnsignedInt: rounds], - @"salt" : [NSData dataWithBytes: salt length: 32] + @"salt" : [NSData dataWithBytes: salt length: PBKDF2_SALT_LEN] } }; // convert to binary plist data From a313d7f207ccea3dfd597181b9129aa40288b3fa Mon Sep 17 00:00:00 2001 From: Greg Neagle Date: Tue, 20 Jun 2017 10:51:36 -0700 Subject: [PATCH 5/9] Read created pkgs with ShadowHashData --- CreateUserPkg/read_package.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/CreateUserPkg/read_package.py b/CreateUserPkg/read_package.py index fb0b884..3a42eb1 100755 --- a/CreateUserPkg/read_package.py +++ b/CreateUserPkg/read_package.py @@ -73,10 +73,10 @@ def main(argv): et = ElementTree.parse(pkginfo_path) pkg_info = et.getroot() # Read the shadow hash. - shadow_hash_path = os.path.join(payload_path, "private/var/db/shadow/hash", user[u"generateduid"][0]) - f = open(shadow_hash_path) - shadow_hash = f.read() - f.close() + #shadow_hash_path = os.path.join(payload_path, "private/var/db/shadow/hash", user[u"generateduid"][0]) + #f = open(shadow_hash_path) + #shadow_hash = f.read() + #f.close() # Read kcpassword. kcpassword_path = os.path.join(payload_path, "private/etc/kcpassword") try: @@ -107,7 +107,8 @@ def main(argv): u"uuid": user[u"generateduid"][0], u"packageID": pkg_info.get("identifier"), u"version": pkg_info.get("version"), - u"shadowHash": shadow_hash, + u"shadowHashData": user[u"ShadowHashData"][0], + u"shadowHash": u"", } if u"picture" in user and len(user[u"picture"]): output_data[u"imagePath"] = user[u"picture"][0] From 5270cd704fecc9d17da9b9daf0eaf5e1054ee035 Mon Sep 17 00:00:00 2001 From: Greg Neagle Date: Tue, 20 Jun 2017 11:22:31 -0700 Subject: [PATCH 6/9] Open older CUP-generated pkgs by leaving password empty. --- CreateUserPkg/CUPDocument.m | 7 +++++-- CreateUserPkg/read_package.py | 3 ++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/CreateUserPkg/CUPDocument.m b/CreateUserPkg/CUPDocument.m index 0946c57..1d37872 100644 --- a/CreateUserPkg/CUPDocument.m +++ b/CreateUserPkg/CUPDocument.m @@ -414,8 +414,11 @@ - (BOOL)readFromData:(NSData *)data ofType:(NSString *)typeName error:(NSError * [self setDocStateKey:@"uuid" fromDict:document]; [self setDocStateKey:@"packageID" fromDict:document]; [self setDocStateKey:@"version" fromDict:document]; - [self setDocStateKey:@"shadowHash" fromDict:document]; - [self setDocStateKey:@"shadowHashData" fromDict:document]; + if (document[@"shadowHashData"] != nil) { + // only set these if we read ShadowHashData from the package + [self setDocStateKey:@"shadowHash" fromDict:document]; + [self setDocStateKey:@"shadowHashData" fromDict:document]; + } [self setDocStateKey:@"imageData" fromDict:document]; [self setDocStateKey:@"imagePath" fromDict:document]; [self setDocStateKey:@"kcPassword" fromDict:document]; diff --git a/CreateUserPkg/read_package.py b/CreateUserPkg/read_package.py index 3a42eb1..af97a20 100755 --- a/CreateUserPkg/read_package.py +++ b/CreateUserPkg/read_package.py @@ -107,9 +107,10 @@ def main(argv): u"uuid": user[u"generateduid"][0], u"packageID": pkg_info.get("identifier"), u"version": pkg_info.get("version"), - u"shadowHashData": user[u"ShadowHashData"][0], u"shadowHash": u"", } + if u"ShadowHashData" in user: + output_data[u"shadowHashData"] = user[u"ShadowHashData"][0] if u"picture" in user and len(user[u"picture"]): output_data[u"imagePath"] = user[u"picture"][0] if u"jpegphoto" in user and len(user[u"jpegphoto"]): From 9f0a4f8291e783275171da2e8523faaa7ac1422a Mon Sep 17 00:00:00 2001 From: Greg Neagle Date: Tue, 20 Jun 2017 11:37:26 -0700 Subject: [PATCH 7/9] Remove ShadowHash code --- CreateUserPkg/CUPDocument.h | 6 ---- CreateUserPkg/CUPDocument.m | 49 +++++---------------------------- CreateUserPkg/create_package.py | 3 +- CreateUserPkg/read_package.py | 1 - 4 files changed, 8 insertions(+), 51 deletions(-) diff --git a/CreateUserPkg/CUPDocument.h b/CreateUserPkg/CUPDocument.h index bb2a620..c78a212 100644 --- a/CreateUserPkg/CUPDocument.h +++ b/CreateUserPkg/CUPDocument.h @@ -28,7 +28,6 @@ NSTextField *__unsafe_unretained _packageID; NSTextField *__unsafe_unretained _version; - NSMutableString *_shadowHash; NSData *_shadowHashData; NSData *_kcPassword; NSMutableDictionary *_docState; @@ -50,7 +49,6 @@ @property (unsafe_unretained) IBOutlet NSTextField *packageID; @property (unsafe_unretained) IBOutlet NSTextField *version; -@property (strong) NSMutableString *shadowHash; @property (strong) NSData *shadowHashData; @property (strong) NSData *kcPassword; @property (strong) NSMutableDictionary *docState; @@ -60,7 +58,6 @@ - (IBAction)didLeavePassword:(id)sender; - (void)calculateShadowHashData:(NSString *)pwd; -- (void)calculateShadowHash:(NSString *)pwd; - (void)calculateKCPassword:(NSString *)pwd; - (void)setTextField:(NSTextField *)field withKey:(NSString *)key; - (void)setDocStateKey:(NSString *)key fromDict:(NSDictionary *)dict; @@ -68,9 +65,6 @@ - (BOOL)validateDocumentAndUpdateState; - (NSDictionary *)newDictFromScript:(NSString *)path withArgs:(NSDictionary *)argDict error:(NSError **)outError; -#define SALTED_SHA1_LEN 48 -#define SALTED_SHA1_OFFSET (64 + 40 + 64) -#define SHADOW_HASH_LEN 1240 #define PBKDF2_SALT_LEN 32 #define PBKDF2_ENTROPY_LEN 128 diff --git a/CreateUserPkg/CUPDocument.m b/CreateUserPkg/CUPDocument.m index 1d37872..b923b9a 100644 --- a/CreateUserPkg/CUPDocument.m +++ b/CreateUserPkg/CUPDocument.m @@ -31,8 +31,6 @@ @implementation CUPDocument @synthesize packageID = _packageID; @synthesize version = _version; - -@synthesize shadowHash = _shadowHash; @synthesize shadowHashData = _shadowHashData; @synthesize kcPassword = _kcPassword; @synthesize docState = _docState; @@ -43,7 +41,7 @@ - (instancetype)init self = [super init]; if (self) { _docState = [[NSMutableDictionary alloc] init]; - _shadowHash = [[NSMutableString alloc] initWithString:@""]; + _shadowHashData = nil; _kcPassword = nil; } return self; @@ -94,7 +92,7 @@ - (IBAction)didLeavePassword:(id)sender { if ([[self.password stringValue] length] != 0) { if ([[self.password stringValue] isEqualToString:[self.verifyPassword stringValue]]) { if ([[self.password stringValue] isEqualToString:CUP_PASSWORD_PLACEHOLDER] == NO) { - [self calculateShadowHash:[self.password stringValue]]; + [self calculateShadowHashData:[self.password stringValue]]; [self calculateKCPassword:[self.password stringValue]]; [self.automaticLogin setEnabled:YES]; } @@ -138,37 +136,6 @@ - (void)calculateShadowHashData:(NSString *)pwd } -- (void)calculateShadowHash:(NSString *)pwd -{ - [self calculateShadowHashData:pwd]; - CC_SHA1_CTX ctx; - unsigned char salted_sha1_hash[24]; - union _salt { - unsigned char bytes[4]; - u_int32_t value; - } *salt = (union _salt *)&salted_sha1_hash[0]; - unsigned char *hash = &salted_sha1_hash[4]; - - // Calculate salted sha1 hash. - CC_SHA1_Init(&ctx); - salt->value = arc4random(); - CC_SHA1_Update(&ctx, salt->bytes, sizeof(salt->bytes)); - CC_SHA1_Update(&ctx, [pwd UTF8String], (CC_LONG)[pwd lengthOfBytesUsingEncoding:NSUTF8StringEncoding]); - CC_SHA1_Final(hash, &ctx); - - // Generate new shadow hash. - [self.shadowHash setString:@""]; - [self.shadowHash appendFormat:@"%0168X", 0]; - assert([self.shadowHash length] == SALTED_SHA1_OFFSET); - for (int i = 0; i < sizeof(salted_sha1_hash); i++) { - [self.shadowHash appendFormat:@"%02X", salted_sha1_hash[i]]; - } - while ([self.shadowHash length] < SHADOW_HASH_LEN) { - [self.shadowHash appendFormat:@"%064X", 0]; - } - assert([self.shadowHash length] == SHADOW_HASH_LEN); -} - #define KCKEY_LEN 11 const char kcPasswordKey[KCKEY_LEN] = {0x7D, 0x89, 0x52, 0x23, 0xD2, 0xBC, 0xDD, 0xEA, 0xA3, 0xB9, 0x1F}; @@ -230,7 +197,7 @@ - (void)windowControllerDidLoadNib:(NSWindowController *)aController UPDATE_TEXT_FIELD(uuid); UPDATE_TEXT_FIELD(packageID); UPDATE_TEXT_FIELD(version); - if ((self.docState)[@"shadowHash"] != nil) { + if ((self.docState)[@"shadowHashData"] != nil) { [self.password setStringValue:CUP_PASSWORD_PLACEHOLDER]; [self.verifyPassword setStringValue:CUP_PASSWORD_PLACEHOLDER]; } @@ -331,12 +298,11 @@ - (BOOL)validateDocumentAndUpdateState SET_DOC_STATE(packageID); SET_DOC_STATE(version); if ([[self.password stringValue] isEqualToString:CUP_PASSWORD_PLACEHOLDER] == NO) { - if ([self.shadowHash length] == 0) { + if (self.shadowHashData == nil) { NSLog(@"shadowHash is empty, calculating new hash"); - [self calculateShadowHash:[self.password stringValue]]; + [self calculateShadowHashData:[self.password stringValue]]; } - (self.docState)[@"shadowHash"] = [NSString stringWithString:self.shadowHash]; - (self.docState)[@"shadowHashData"] = self.shadowHashData; + (self.docState)[@"shadowHashData"] = [NSData dataWithData:self.shadowHashData]; } if ([self.automaticLogin state] == NSOnState) { if ([self.kcPassword length] == 0) { @@ -415,8 +381,7 @@ - (BOOL)readFromData:(NSData *)data ofType:(NSString *)typeName error:(NSError * [self setDocStateKey:@"packageID" fromDict:document]; [self setDocStateKey:@"version" fromDict:document]; if (document[@"shadowHashData"] != nil) { - // only set these if we read ShadowHashData from the package - [self setDocStateKey:@"shadowHash" fromDict:document]; + // only set this if we read ShadowHashData from the package [self setDocStateKey:@"shadowHashData" fromDict:document]; } [self setDocStateKey:@"imageData" fromDict:document]; diff --git a/CreateUserPkg/create_package.py b/CreateUserPkg/create_package.py index 8bdb9b5..c94ac0e 100755 --- a/CreateUserPkg/create_package.py +++ b/CreateUserPkg/create_package.py @@ -240,12 +240,11 @@ def main(argv): if u"imageData" in input_data: user_plist[u"jpegphoto"] = [input_data[u"imageData"]] - # Get name, version, package ID, shadow hash, and kcpassword. + # Get name, version, package ID, and kcpassword. utf8_username = input_data[u"accountName"].encode("utf-8") pkg_version = input_data[u"version"] pkg_name = "create_%s-%s" % (utf8_username, pkg_version) pkg_id = input_data[u"packageID"] - shadow_hash = input_data[u"shadowHash"] if u"kcPassword" in input_data: kcpassword = input_data[u"kcPassword"].data else: diff --git a/CreateUserPkg/read_package.py b/CreateUserPkg/read_package.py index af97a20..65578b2 100755 --- a/CreateUserPkg/read_package.py +++ b/CreateUserPkg/read_package.py @@ -107,7 +107,6 @@ def main(argv): u"uuid": user[u"generateduid"][0], u"packageID": pkg_info.get("identifier"), u"version": pkg_info.get("version"), - u"shadowHash": u"", } if u"ShadowHashData" in user: output_data[u"shadowHashData"] = user[u"ShadowHashData"][0] From 6cfb3d7c6d4b0fb2696891586c0ddde0db5b8972 Mon Sep 17 00:00:00 2001 From: Greg Neagle Date: Tue, 20 Jun 2017 11:37:52 -0700 Subject: [PATCH 8/9] Bump version to 1.5; maybe it should be 2.0? --- CreateUserPkg/CreateUserPkg-Info.plist | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CreateUserPkg/CreateUserPkg-Info.plist b/CreateUserPkg/CreateUserPkg-Info.plist index fcda3c3..0d2893b 100644 --- a/CreateUserPkg/CreateUserPkg-Info.plist +++ b/CreateUserPkg/CreateUserPkg-Info.plist @@ -38,7 +38,7 @@ CFBundlePackageType APPL CFBundleShortVersionString - 1.2.5 + 1.5 CFBundleSignature ???? CFBundleVersion From b2f2ae7ae36c184f50accb68c92e878affa9b8de Mon Sep 17 00:00:00 2001 From: tburgin Date: Tue, 20 Jun 2017 18:51:04 -0400 Subject: [PATCH 9/9] Clean up --- CreateUserPkg.xcodeproj/project.pbxproj | 18 +- CreateUserPkg/CUPAppDelegate.h | 13 - CreateUserPkg/CUPAppDelegate.m | 19 - CreateUserPkg/CUPBestHostFinder.h | 20 - CreateUserPkg/CUPBestHostFinder.m | 58 - CreateUserPkg/CUPDocument.h | 69 +- CreateUserPkg/CUPDocument.m | 780 +++++----- CreateUserPkg/CUPImageSelector.h | 9 +- CreateUserPkg/CUPImageSelector.m | 123 +- CreateUserPkg/CUPKeys.h | 16 + CreateUserPkg/CUPKeys.m | 76 + CreateUserPkg/CUPUIDFormatter.h | 1 - CreateUserPkg/CUPUIDFormatter.m | 29 +- CreateUserPkg/CUPUUIDFormatter.h | 5 +- CreateUserPkg/CUPUUIDFormatter.m | 66 +- CreateUserPkg/CUPUserNameFormatter.h | 5 +- CreateUserPkg/CUPUserNameFormatter.m | 85 +- CreateUserPkg/en.lproj/CUPDocument.xib | 1786 ++++------------------- CreateUserPkg/en.lproj/MainMenu.xib | 14 +- CreateUserPkg/main.m | 5 +- 20 files changed, 915 insertions(+), 2282 deletions(-) delete mode 100644 CreateUserPkg/CUPAppDelegate.h delete mode 100644 CreateUserPkg/CUPAppDelegate.m delete mode 100644 CreateUserPkg/CUPBestHostFinder.h delete mode 100644 CreateUserPkg/CUPBestHostFinder.m create mode 100644 CreateUserPkg/CUPKeys.h create mode 100644 CreateUserPkg/CUPKeys.m diff --git a/CreateUserPkg.xcodeproj/project.pbxproj b/CreateUserPkg.xcodeproj/project.pbxproj index ff98203..8f96579 100644 --- a/CreateUserPkg.xcodeproj/project.pbxproj +++ b/CreateUserPkg.xcodeproj/project.pbxproj @@ -18,13 +18,12 @@ 0596D196159AF27800B898B4 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 0596D194159AF27800B898B4 /* MainMenu.xib */; }; 05CF2A17159DE745004F36C8 /* CUPImageSelector.m in Sources */ = {isa = PBXBuildFile; fileRef = 05CF2A16159DE745004F36C8 /* CUPImageSelector.m */; }; 05CF2A1A159DEDE4004F36C8 /* dropimagehere.png in Resources */ = {isa = PBXBuildFile; fileRef = 05CF2A19159DEDE4004F36C8 /* dropimagehere.png */; }; - 6605E68A159B86480048362A /* CUPBestHostFinder.m in Sources */ = {isa = PBXBuildFile; fileRef = 6605E689159B86480048362A /* CUPBestHostFinder.m */; }; - 6605E68E159B90070048362A /* CUPAppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 6605E68D159B90070048362A /* CUPAppDelegate.m */; }; 6605E695159B964D0048362A /* CUPUIDFormatter.m in Sources */ = {isa = PBXBuildFile; fileRef = 6605E690159B964D0048362A /* CUPUIDFormatter.m */; }; 6605E696159B964D0048362A /* CUPUserNameFormatter.m in Sources */ = {isa = PBXBuildFile; fileRef = 6605E692159B964D0048362A /* CUPUserNameFormatter.m */; }; 6605E697159B964D0048362A /* CUPUUIDFormatter.m in Sources */ = {isa = PBXBuildFile; fileRef = 6605E694159B964D0048362A /* CUPUUIDFormatter.m */; }; 66B221D8159C4C30003FDF4E /* CreateUserPkg.icns in Resources */ = {isa = PBXBuildFile; fileRef = 66B221D7159C4C30003FDF4E /* CreateUserPkg.icns */; }; C09EAB201EF8EDFE005A8859 /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C09EAB1F1EF8EDFE005A8859 /* Security.framework */; }; + C7D85C7E1EF9BCEF00936FD0 /* CUPKeys.m in Sources */ = {isa = PBXBuildFile; fileRef = C7D85C7D1EF9BCEF00936FD0 /* CUPKeys.m */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ @@ -48,10 +47,6 @@ 05CF2A15159DE745004F36C8 /* CUPImageSelector.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CUPImageSelector.h; sourceTree = ""; }; 05CF2A16159DE745004F36C8 /* CUPImageSelector.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CUPImageSelector.m; sourceTree = ""; }; 05CF2A19159DEDE4004F36C8 /* dropimagehere.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = dropimagehere.png; sourceTree = ""; }; - 6605E688159B86480048362A /* CUPBestHostFinder.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CUPBestHostFinder.h; sourceTree = ""; }; - 6605E689159B86480048362A /* CUPBestHostFinder.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CUPBestHostFinder.m; sourceTree = ""; }; - 6605E68C159B90070048362A /* CUPAppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CUPAppDelegate.h; sourceTree = ""; }; - 6605E68D159B90070048362A /* CUPAppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CUPAppDelegate.m; sourceTree = ""; }; 6605E68F159B964D0048362A /* CUPUIDFormatter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CUPUIDFormatter.h; sourceTree = ""; }; 6605E690159B964D0048362A /* CUPUIDFormatter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CUPUIDFormatter.m; sourceTree = ""; }; 6605E691159B964D0048362A /* CUPUserNameFormatter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CUPUserNameFormatter.h; sourceTree = ""; }; @@ -60,6 +55,8 @@ 6605E694159B964D0048362A /* CUPUUIDFormatter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CUPUUIDFormatter.m; sourceTree = ""; }; 66B221D7159C4C30003FDF4E /* CreateUserPkg.icns */ = {isa = PBXFileReference; lastKnownFileType = image.icns; path = CreateUserPkg.icns; sourceTree = ""; }; C09EAB1F1EF8EDFE005A8859 /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = ../../../../System/Library/Frameworks/Security.framework; sourceTree = ""; }; + C7D85C7C1EF9BCEF00936FD0 /* CUPKeys.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CUPKeys.h; sourceTree = ""; }; + C7D85C7D1EF9BCEF00936FD0 /* CUPKeys.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CUPKeys.m; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -117,15 +114,13 @@ isa = PBXGroup; children = ( 0596D19C159AF2A200B898B4 /* CreateUserPkg.entitlements */, - 6605E68C159B90070048362A /* CUPAppDelegate.h */, - 6605E68D159B90070048362A /* CUPAppDelegate.m */, - 6605E688159B86480048362A /* CUPBestHostFinder.h */, - 6605E689159B86480048362A /* CUPBestHostFinder.m */, 05CF2A15159DE745004F36C8 /* CUPImageSelector.h */, 05CF2A16159DE745004F36C8 /* CUPImageSelector.m */, 0596D18E159AF27800B898B4 /* CUPDocument.h */, 0596D18F159AF27800B898B4 /* CUPDocument.m */, 0596D191159AF27800B898B4 /* CUPDocument.xib */, + C7D85C7C1EF9BCEF00936FD0 /* CUPKeys.h */, + C7D85C7D1EF9BCEF00936FD0 /* CUPKeys.m */, 6605E68F159B964D0048362A /* CUPUIDFormatter.h */, 6605E690159B964D0048362A /* CUPUIDFormatter.m */, 6605E691159B964D0048362A /* CUPUserNameFormatter.h */, @@ -258,10 +253,9 @@ files = ( 0596D189159AF27800B898B4 /* main.m in Sources */, 0596D190159AF27800B898B4 /* CUPDocument.m in Sources */, - 6605E68A159B86480048362A /* CUPBestHostFinder.m in Sources */, - 6605E68E159B90070048362A /* CUPAppDelegate.m in Sources */, 6605E695159B964D0048362A /* CUPUIDFormatter.m in Sources */, 6605E696159B964D0048362A /* CUPUserNameFormatter.m in Sources */, + C7D85C7E1EF9BCEF00936FD0 /* CUPKeys.m in Sources */, 6605E697159B964D0048362A /* CUPUUIDFormatter.m in Sources */, 05CF2A17159DE745004F36C8 /* CUPImageSelector.m in Sources */, ); diff --git a/CreateUserPkg/CUPAppDelegate.h b/CreateUserPkg/CUPAppDelegate.h deleted file mode 100644 index 6fa7fe8..0000000 --- a/CreateUserPkg/CUPAppDelegate.h +++ /dev/null @@ -1,13 +0,0 @@ -// -// CUPAppDelegate.h -// CreateUserPkg -// -// Created by Per Olofsson on 2012-06-27. -// Copyright (c) 2012 University of Gothenburg. All rights reserved. -// - -#import - -@interface CUPAppDelegate : NSObject - -@end diff --git a/CreateUserPkg/CUPAppDelegate.m b/CreateUserPkg/CUPAppDelegate.m deleted file mode 100644 index 0a5feb6..0000000 --- a/CreateUserPkg/CUPAppDelegate.m +++ /dev/null @@ -1,19 +0,0 @@ -// -// CUPAppDelegate.m -// CreateUserPkg -// -// Created by Per Olofsson on 2012-06-27. -// Copyright (c) 2012 University of Gothenburg. All rights reserved. -// - -#import "CUPAppDelegate.h" -#import "CUPBestHostFinder.h" - -@implementation CUPAppDelegate - -- (void)applicationDidFinishLaunching:(NSNotification *)aNotification -{ - [CUPBestHostFinder bestHostFinder]; -} - -@end diff --git a/CreateUserPkg/CUPBestHostFinder.h b/CreateUserPkg/CUPBestHostFinder.h deleted file mode 100644 index 935177a..0000000 --- a/CreateUserPkg/CUPBestHostFinder.h +++ /dev/null @@ -1,20 +0,0 @@ -// -// CUPBestHostFinder.h -// CreateUserPkg -// -// Created by Per Olofsson on 2012-06-27. -// Copyright (c) 2012 University of Gothenburg. All rights reserved. -// - -#import - -@interface CUPBestHostFinder : NSObject { - NSArray *_bestHost; -} - -@property (atomic, strong) NSArray *bestHost; - -+ (CUPBestHostFinder *)bestHostFinder; -- (void)findBestHost; - -@end diff --git a/CreateUserPkg/CUPBestHostFinder.m b/CreateUserPkg/CUPBestHostFinder.m deleted file mode 100644 index a11a3c3..0000000 --- a/CreateUserPkg/CUPBestHostFinder.m +++ /dev/null @@ -1,58 +0,0 @@ -// -// CUPBestHostFinder.m -// CreateUserPkg -// -// Created by Per Olofsson on 2012-06-27. -// Copyright (c) 2012 University of Gothenburg. All rights reserved. -// - -#import "CUPBestHostFinder.h" - -@implementation CUPBestHostFinder - -@synthesize bestHost = _bestHost; - -- (instancetype)init -{ - self = [super init]; - if (self) { - self.bestHost = @[@"host", @"example", @"com"]; - } - return self; -} - - -+ (CUPBestHostFinder *)bestHostFinder -{ - static CUPBestHostFinder *sharedSingleton; - @synchronized(self) { - if (!sharedSingleton) { - sharedSingleton = [[CUPBestHostFinder alloc] init]; - [sharedSingleton performSelectorInBackground:@selector(findBestHost) withObject:nil]; - } - return sharedSingleton; - } -} - -- (void)findBestHost -{ - // Try to find a full domain name for the current host. - - // Iterate over all host names. - for (NSString *name in [[NSHost currentHost] names]) { - // Split host name by ".". - NSArray *splitHost = [name componentsSeparatedByString:@"."]; - // Don't bother with unqualified host names. - if ([splitHost count] > 1) { - // Ignore .local. - if (! [[splitHost lastObject] isEqualToString:@"local"]) { - // Whatever we found, it's probably better than what we have. - self.bestHost = splitHost; - // Return as soon as we've got a result. - return; - } - } - } -} - -@end diff --git a/CreateUserPkg/CUPDocument.h b/CreateUserPkg/CUPDocument.h index c78a212..8f814db 100644 --- a/CreateUserPkg/CUPDocument.h +++ b/CreateUserPkg/CUPDocument.h @@ -7,74 +7,7 @@ // #import -#import "CUPBestHostFinder.h" -#import "CUPImageSelector.h" - -@interface CUPDocument : NSDocument { - NSWindow *__unsafe_unretained docWindow; - - CUPImageSelector *__unsafe_unretained _image; - NSTextField *__unsafe_unretained _fullName; - NSTextField *__unsafe_unretained _accountName; - NSSecureTextField *__unsafe_unretained _password; - NSSecureTextField *__unsafe_unretained _verifyPassword; - NSTextField *__unsafe_unretained _userID; - NSPopUpButton *__unsafe_unretained _accountType; - NSTextField *__unsafe_unretained _homeDirectory; - NSTextField *__unsafe_unretained _uuid; - NSButton *__unsafe_unretained _automaticLogin; - - NSTextField *__unsafe_unretained _packageID; - NSTextField *__unsafe_unretained _version; - - NSData *_shadowHashData; - NSData *_kcPassword; - NSMutableDictionary *_docState; -} - -@property (unsafe_unretained) IBOutlet NSWindow *docWindow; - -@property (unsafe_unretained) IBOutlet NSTextField *fullName; -@property (unsafe_unretained) IBOutlet NSTextField *accountName; -@property (unsafe_unretained) IBOutlet CUPImageSelector *image; -@property (unsafe_unretained) IBOutlet NSSecureTextField *password; -@property (unsafe_unretained) IBOutlet NSSecureTextField *verifyPassword; -@property (unsafe_unretained) IBOutlet NSTextField *userID; -@property (unsafe_unretained) IBOutlet NSPopUpButton *accountType; -@property (unsafe_unretained) IBOutlet NSTextField *homeDirectory; -@property (unsafe_unretained) IBOutlet NSTextField *uuid; -@property (unsafe_unretained) IBOutlet NSButton *automaticLogin; - -@property (unsafe_unretained) IBOutlet NSTextField *packageID; -@property (unsafe_unretained) IBOutlet NSTextField *version; - -@property (strong) NSData *shadowHashData; -@property (strong) NSData *kcPassword; -@property (strong) NSMutableDictionary *docState; - -- (IBAction)didLeaveFullName:(id)sender; -- (IBAction)didLeaveAccountName:(id)sender; -- (IBAction)didLeavePassword:(id)sender; - -- (void)calculateShadowHashData:(NSString *)pwd; -- (void)calculateKCPassword:(NSString *)pwd; -- (void)setTextField:(NSTextField *)field withKey:(NSString *)key; -- (void)setDocStateKey:(NSString *)key fromDict:(NSDictionary *)dict; -- (BOOL)validateField:(NSControl *)field validator:(BOOL (^)(NSControl *))valFunc errorMsg:(NSString *)msg; -- (BOOL)validateDocumentAndUpdateState; -- (NSDictionary *)newDictFromScript:(NSString *)path withArgs:(NSDictionary *)argDict error:(NSError **)outError; - - -#define PBKDF2_SALT_LEN 32 -#define PBKDF2_ENTROPY_LEN 128 - - -#define CUP_PASSWORD_PLACEHOLDER @"SEIPHATSXSTX$D418JMPC000" - -enum { - ACCOUNT_TYPE_STANDARD = 0, - ACCOUNT_TYPE_ADMIN -}; +@interface CUPDocument : NSDocument @end diff --git a/CreateUserPkg/CUPDocument.m b/CreateUserPkg/CUPDocument.m index b923b9a..9f03b30 100644 --- a/CreateUserPkg/CUPDocument.m +++ b/CreateUserPkg/CUPDocument.m @@ -6,470 +6,450 @@ // Copyright (c) 2012 University of Gothenburg. All rights reserved. // -#import -#import -#import #import "CUPDocument.h" + +#import + +#import "CUPKeys.h" +#import "CUPImageSelector.h" #import "CUPUIDFormatter.h" -#import "CUPUserNameFormatter.h" #import "CUPUUIDFormatter.h" +#import "CUPUserNameFormatter.h" + +#define CUP_PASSWORD_PLACEHOLDER @"SEIPHATSXSTX$D418JMPC000" + +enum { + ACCOUNT_TYPE_STANDARD = 0, + ACCOUNT_TYPE_ADMIN +}; + +@interface CUPDocument () + +@property(weak) IBOutlet NSWindow *docWindow; + +@property(weak) IBOutlet NSTextField *fullName; +@property(weak) IBOutlet NSTextField *accountName; +@property(weak) IBOutlet CUPImageSelector *image; +@property(weak) IBOutlet NSSecureTextField *password; +@property(weak) IBOutlet NSSecureTextField *verifyPassword; +@property(weak) IBOutlet NSTextField *userID; +@property(weak) IBOutlet NSPopUpButton *accountType; +@property(weak) IBOutlet NSTextField *homeDirectory; +@property(weak) IBOutlet NSTextField *uuid; +@property(weak) IBOutlet NSButton *automaticLogin; + +@property(weak) IBOutlet NSTextField *packageID; +@property(weak) IBOutlet NSTextField *version; + +@property NSData *shadowHashData; +@property NSData *kcPasswordData; +@property NSMutableDictionary *docState; +@property(readonly) NSArray *bestHost; + +- (IBAction)didLeaveFullName:(id)sender; +- (IBAction)didLeaveAccountName:(id)sender; +- (IBAction)didLeavePassword:(id)sender; + +@end @implementation CUPDocument -@synthesize docWindow; - -@synthesize fullName = _fullName; -@synthesize accountName = _accountName; -@synthesize image = _image; -@synthesize password = _password; -@synthesize verifyPassword = _verifyPassword; -@synthesize userID = _userID; -@synthesize accountType = _accountType; -@synthesize homeDirectory = _homeDirectory; -@synthesize uuid = _uuid; -@synthesize automaticLogin = _automaticLogin; - -@synthesize packageID = _packageID; -@synthesize version = _version; -@synthesize shadowHashData = _shadowHashData; -@synthesize kcPassword = _kcPassword; -@synthesize docState = _docState; - - -- (instancetype)init -{ - self = [super init]; - if (self) { - _docState = [[NSMutableDictionary alloc] init]; - _shadowHashData = nil; - _kcPassword = nil; - } - return self; +- (instancetype)init { + self = [super init]; + if (self) { + _docState = [[NSMutableDictionary alloc] init]; + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{ + [self bestHost]; + }); + } + return self; } +- (NSArray *)bestHost { + // Cache names for the lifetime of the process + static NSArray *names; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + names = [[NSHost currentHost] names]; + }); + + // Try to find a full domain name for the current host. + // Iterate over all host names. + for (NSString *name in names) { + // Split host name by ".". + NSArray *splitHost = [name componentsSeparatedByString:@"."]; + // Don't bother with unqualified host names. + if ([splitHost count] > 1) { + // Ignore .local. + if (![[splitHost lastObject] isEqualToString:@"local"]) { + // Whatever we found, it's probably better than what we have. + return splitHost; + } + } + } -- (NSError *)cocoaError:(NSInteger)code withReason:(NSString *)reason -{ - return [NSError errorWithDomain:NSCocoaErrorDomain code:code userInfo:@{NSLocalizedFailureReasonErrorKey: reason}]; + return @[@"host", @"example", @"com"]; } -- (NSString *)windowNibName -{ - return @"CUPDocument"; +- (NSError *)cocoaError:(NSInteger)code withReason:(NSString *)reason { + return [NSError errorWithDomain:NSCocoaErrorDomain + code:code + userInfo:@{ NSLocalizedFailureReasonErrorKey: reason }]; } -- (void)showHelp:(id)sender -{ - [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:@"https://github.com/MagerValp/CreateUserPkg#createuserpkg"]]; +- (NSString *)windowNibName { + return @"CUPDocument"; +} + +- (void)showHelp:(id)sender { + NSString *url = @"https://github.com/MagerValp/CreateUserPkg#createuserpkg"; + [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:url]]; } - (IBAction)didLeaveFullName:(id)sender { - // If the user has entered a fullName convert the full name to lower case, strip - // illegal chars, and use that to fill the accountName. - if ([[self.accountName stringValue] length] == 0 && [[self.fullName stringValue] length] != 0) { - NSString *lcString = [[self.fullName stringValue] lowercaseString]; - NSData *asciiData = [lcString dataUsingEncoding:NSASCIIStringEncoding allowLossyConversion:YES]; - NSString *asciiString = [[NSString alloc] initWithData:asciiData encoding:NSASCIIStringEncoding]; - [self.accountName setStringValue:asciiString]; - } + // If the user has entered a fullName convert the full name to lower case, strip + // illegal chars, and use that to fill the accountName. + if (!self.accountName.stringValue.length && self.fullName.stringValue.length) { + NSString *lc = self.fullName.stringValue.lowercaseString; + NSData *data = [lc dataUsingEncoding:NSASCIIStringEncoding allowLossyConversion:YES]; + NSString *asciiString = [[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding]; + self.accountName.stringValue = asciiString; + } } - (IBAction)didLeaveAccountName:(id)sender { - // If the user has entered an accountName fill homeDirectory and packageID. - if ([[self.homeDirectory stringValue] length] == 0 && [[self.accountName stringValue] length] != 0) { - [self.homeDirectory setStringValue:[NSString stringWithFormat:@"/Users/%@", [self.accountName stringValue]]]; - } - if ([[self.packageID stringValue] length] == 0 && [[self.accountName stringValue] length] != 0) { - NSMutableArray *host = [NSMutableArray arrayWithArray:[[CUPBestHostFinder bestHostFinder] bestHost]]; - [host removeObjectAtIndex:0]; - - NSString *reverseDomain = [[[host reverseObjectEnumerator] allObjects] componentsJoinedByString:@"."]; - [self.packageID setStringValue:[NSString stringWithFormat:@"%@.create_%@.pkg", reverseDomain, [self.accountName stringValue]]]; - } + // If the user has entered an accountName fill homeDirectory and packageID. + if (!self.homeDirectory.stringValue.length && self.accountName.stringValue.length) { + self.homeDirectory.stringValue = + [NSString stringWithFormat:@"/Users/%@", self.accountName.stringValue]; + } + if (!self.packageID.stringValue.length && self.accountName.stringValue.length) { + NSMutableArray *host = [self.bestHost reverseObjectEnumerator].allObjects.mutableCopy; + [host removeLastObject]; + NSString *reverseDomain = [host componentsJoinedByString:@"."]; + self.packageID.stringValue = [NSString stringWithFormat:@"%@.create_%@.pkg", + reverseDomain, self.accountName.stringValue]; + } } - (IBAction)didLeavePassword:(id)sender { - if ([[self.password stringValue] length] != 0) { - if ([[self.password stringValue] isEqualToString:[self.verifyPassword stringValue]]) { - if ([[self.password stringValue] isEqualToString:CUP_PASSWORD_PLACEHOLDER] == NO) { - [self calculateShadowHashData:[self.password stringValue]]; - [self calculateKCPassword:[self.password stringValue]]; - [self.automaticLogin setEnabled:YES]; - } - } + if (self.password.stringValue.length) { + if ([self.password.stringValue isEqualToString:self.verifyPassword.stringValue]) { + if (![self.password.stringValue isEqualToString:CUP_PASSWORD_PLACEHOLDER]) { + self.shadowHashData = [CUPKeys calculateShadowHashData:[self.password stringValue]]; + self.kcPasswordData = [CUPKeys calculateKCPasswordData:[self.password stringValue]]; + [self.automaticLogin setEnabled:YES]; + } } + } } - -- (void)calculateShadowHashData:(NSString *)pwd -{ - unsigned char salt[PBKDF2_SALT_LEN]; - int status = SecRandomCopyBytes(kSecRandomDefault, PBKDF2_SALT_LEN, salt); - if (status == 0) { - unsigned char key[PBKDF2_ENTROPY_LEN]; - // calculate the number of iterations to use - unsigned int rounds = CCCalibratePBKDF(kCCPBKDF2, - [pwd lengthOfBytesUsingEncoding:NSUTF8StringEncoding], - PBKDF2_SALT_LEN, kCCPRFHmacAlgSHA512, PBKDF2_ENTROPY_LEN, 100); - // derive our SALTED-SHA512-PBKDF2 key - CCKeyDerivationPBKDF(kCCPBKDF2, - [pwd UTF8String], - (CC_LONG)[pwd lengthOfBytesUsingEncoding:NSUTF8StringEncoding], - salt, PBKDF2_SALT_LEN, - kCCPRFHmacAlgSHA512, rounds, key, PBKDF2_ENTROPY_LEN); - // Make a dictionary containing the needed fields - NSDictionary *dictionary = @{ - @"SALTED-SHA512-PBKDF2" : @{ - @"entropy" : [NSData dataWithBytes: key length: PBKDF2_ENTROPY_LEN], - @"iterations" : [NSNumber numberWithUnsignedInt: rounds], - @"salt" : [NSData dataWithBytes: salt length: PBKDF2_SALT_LEN] - } - }; - // convert to binary plist data - NSError *error = NULL; - NSData *plistData = [NSPropertyListSerialization dataWithPropertyList: dictionary - format: NSPropertyListBinaryFormat_v1_0 - options: 0 - error: &error]; - self.shadowHashData = plistData; - } +- (void)setTextField:(NSTextField *)field withKey:(NSString *)key { + NSString *value = self.docState[key]; + if (value) [field setStringValue:value]; } +#define UPDATE_TEXT_FIELD(FIELD) [self setTextField:self.FIELD withKey:@#FIELD] -#define KCKEY_LEN 11 -const char kcPasswordKey[KCKEY_LEN] = {0x7D, 0x89, 0x52, 0x23, 0xD2, 0xBC, 0xDD, 0xEA, 0xA3, 0xB9, 0x1F}; - -- (void)calculateKCPassword:(NSString *)pwd -{ - int i; - NSUInteger pwdlen = [pwd lengthOfBytesUsingEncoding:NSUTF8StringEncoding]; - // kcpassword stores the password plus null terminator, rounded up to the nearest 12 bytes. - NSUInteger rounded12len = ((pwdlen + 12) / 12) * 12; - unsigned char *kcbuf = malloc(rounded12len); - - // Get UTF-8 encoded data and xor it with the key. - const unsigned char *pwdbuf = [[pwd dataUsingEncoding:NSUTF8StringEncoding] bytes]; - for (i = 0; i < pwdlen; i++) { - kcbuf[i] = pwdbuf[i] ^ kcPasswordKey[i % KCKEY_LEN]; - } - kcbuf[i] = kcPasswordKey[i % KCKEY_LEN]; - - self.kcPassword = [NSData dataWithBytes:kcbuf length:rounded12len]; - - free(kcbuf); -} +- (void)windowControllerDidLoadNib:(NSWindowController *)aController { + [super windowControllerDidLoadNib:aController]; -- (void)setTextField:(NSTextField *)field withKey:(NSString *)key -{ - NSString *value = (self.docState)[key]; - if (key != nil && value != nil) { - [field setStringValue:value]; - } -} + // Initialize form with default values. + self.accountName.formatter = [[CUPUserNameFormatter alloc] init]; -#define UPDATE_TEXT_FIELD(FIELD) [self setTextField:self.FIELD withKey:@#FIELD] + self.userID.formatter = [[CUPUIDFormatter alloc] init]; + self.userID.stringValue = @"899"; -- (void)windowControllerDidLoadNib:(NSWindowController *)aController -{ - [super windowControllerDidLoadNib:aController]; - - // Initialize form with default values. - CUPUIDFormatter *uidFormatter = [[CUPUIDFormatter alloc] init]; - [self.userID setFormatter:uidFormatter]; - [self.userID setStringValue:@"899"]; - CUPUserNameFormatter *userNameFormatter = [[CUPUserNameFormatter alloc] init]; - [self.accountName setFormatter:userNameFormatter]; - CUPUUIDFormatter *uuidFormatter = [[CUPUUIDFormatter alloc] init]; - [self.uuid setFormatter:uuidFormatter]; - CFUUIDRef theUUID = CFUUIDCreate(NULL); - NSString *uuidString = (NSString *)CFBridgingRelease(CFUUIDCreateString(NULL, theUUID)); - [self.uuid setStringValue:uuidString]; - CFRelease(theUUID); - [self.version setStringValue:@"1.0"]; - - [self.fullName becomeFirstResponder]; - - // Load values from document state. - UPDATE_TEXT_FIELD(fullName); - UPDATE_TEXT_FIELD(accountName); - UPDATE_TEXT_FIELD(userID); - UPDATE_TEXT_FIELD(homeDirectory); - UPDATE_TEXT_FIELD(uuid); - UPDATE_TEXT_FIELD(packageID); - UPDATE_TEXT_FIELD(version); - if ((self.docState)[@"shadowHashData"] != nil) { - [self.password setStringValue:CUP_PASSWORD_PLACEHOLDER]; - [self.verifyPassword setStringValue:CUP_PASSWORD_PLACEHOLDER]; - } - [self.automaticLogin setEnabled:YES]; - if ((self.docState)[@"kcPassword"] != nil) { - [self.automaticLogin setState:NSOnState]; - } else { - [self.automaticLogin setState:NSOffState]; - if ([[self.password stringValue] isEqualToString:CUP_PASSWORD_PLACEHOLDER]) { - [self.automaticLogin setEnabled:NO]; - } - } - self.image.imageData = (self.docState)[@"imageData"]; - self.image.imagePath = (self.docState)[@"imagePath"]; - [self.image displayImageData]; - if ((self.docState)[@"isAdmin"] != nil) { - [self.accountType selectItemAtIndex:[(self.docState)[@"isAdmin"] boolValue] ? ACCOUNT_TYPE_ADMIN : ACCOUNT_TYPE_STANDARD]; - } else { - [self.accountType selectItemAtIndex:ACCOUNT_TYPE_ADMIN]; + self.uuid.formatter = [[CUPUUIDFormatter alloc] init]; + self.uuid.stringValue = [NSUUID UUID].UUIDString; + + self.version.stringValue = @"1.0"; + + [self.fullName becomeFirstResponder]; + + // Load values from document state. + UPDATE_TEXT_FIELD(fullName); + UPDATE_TEXT_FIELD(accountName); + UPDATE_TEXT_FIELD(userID); + UPDATE_TEXT_FIELD(homeDirectory); + UPDATE_TEXT_FIELD(uuid); + UPDATE_TEXT_FIELD(packageID); + UPDATE_TEXT_FIELD(version); + + if (self.docState[@"shadowHashData"]) { + self.password.stringValue = CUP_PASSWORD_PLACEHOLDER; + self.verifyPassword.stringValue = CUP_PASSWORD_PLACEHOLDER; + } + + self.automaticLogin.enabled = YES; + + if (self.docState[@"kcPassword"]) { + self.automaticLogin.state = NSOnState; + } else { + self.automaticLogin.state = NSOffState; + if ([self.password.stringValue isEqualToString:CUP_PASSWORD_PLACEHOLDER]) { + self.automaticLogin.enabled = NO; } + } + + self.image.imageData = self.docState[@"imageData"]; + self.image.imagePath = self.docState[@"imagePath"]; + [self.image displayImageData]; + + if (self.docState[@"isAdmin"]) { + [self.accountType selectItemAtIndex: + [self.docState[@"isAdmin"] boolValue] ? ACCOUNT_TYPE_ADMIN : ACCOUNT_TYPE_STANDARD]; + } else { + [self.accountType selectItemAtIndex:ACCOUNT_TYPE_ADMIN]; + } } -+ (BOOL)autosavesInPlace -{ - return NO; ++ (BOOL)autosavesInPlace { + return NO; } -- (BOOL)isDocumentEdited -{ - return NO; +- (BOOL)isDocumentEdited { + return NO; } -- (NSString *)displayName -{ - // This is used to provide a default name for the save dialog. - // If accountName and version are set, return create_NAME-VER instead of Untitled. - NSString *orgDisplayName = [super displayName]; - if ([[orgDisplayName lowercaseString] hasPrefix:@"untitled"]) { - if ([[self.accountName stringValue] length] > 0 && [[self.version stringValue] length] > 0) { - return [NSString stringWithFormat:@"create_%@-%@", [self.accountName stringValue], [self.version stringValue]]; - } +- (NSString *)displayName { + // This is used to provide a default name for the save dialog. + // If accountName and version are set, return create_NAME-VER instead of Untitled. + NSString *orgDisplayName = [super displayName]; + if ([orgDisplayName.lowercaseString hasPrefix:@"untitled"]) { + if (self.accountName.stringValue.length && self.version.stringValue.length) { + return [NSString stringWithFormat:@"create_%@-%@", + self.accountName.stringValue, self.version.stringValue]; } - return orgDisplayName; + } + return orgDisplayName; } -- (BOOL)validateField:(NSControl *)field validator:(BOOL (^)(NSControl *))valFunc errorMsg:(NSString *)msg -{ - if (valFunc(field) == NO) { - [field becomeFirstResponder]; - NSRunCriticalAlertPanel(msg, @"", @"OK", nil, nil); - return NO; - } - return YES; +- (BOOL)validateField:(NSControl *)field + validator:(BOOL (^)(NSControl *))valFunc + errorMsg:(NSString *)msg { + if (valFunc(field) == NO) { + [field becomeFirstResponder]; + NSRunCriticalAlertPanel(msg, @"", @"OK", nil, nil); + return NO; + } + return YES; } #define VALIDATE(...) if ((__VA_ARGS__) == NO) {return NO;} #define SET_DOC_STATE(KEY) [self.docState setObject:[[self KEY] stringValue] forKey:@#KEY] -- (BOOL)validateDocumentAndUpdateState -{ - BOOL (^validateNotEmpty)(NSControl *) = ^(NSControl *field){ - return (BOOL)([[field stringValue] length] != 0); - }; - BOOL (^validateSameAsPassword)(NSControl *) = ^(NSControl *field){ - return [[field stringValue] isEqualToString:[self.password stringValue]]; - }; - BOOL (^validateHomeDir)(NSControl *) = ^(NSControl *field){ - return (BOOL)([[self.homeDirectory stringValue] length] != 0 && [[self.homeDirectory stringValue] characterAtIndex:0] == L'/'); - }; - BOOL (^validateUUID)(NSControl *) = ^(NSControl *field){ - return (BOOL)([[field stringValue] length] == 36); - }; - - VALIDATE([self validateField:self.fullName validator:validateNotEmpty errorMsg:@"Please enter a full name"]); - VALIDATE([self validateField:self.accountName validator:validateNotEmpty errorMsg:@"Please enter an account name"]); - if ([[self.password stringValue] length] == 0) { - if (NSRunAlertPanel(@"Are you sure you want to create a user with an empty password?", - @"Anyone will be able to log in, even remotely, without being asked for a password.", - @"Cancel", - @"Use Empty Password", - nil) == NSAlertDefaultReturn) { - [self.password becomeFirstResponder]; - return NO; - } - } - VALIDATE([self validateField:self.verifyPassword validator:validateSameAsPassword errorMsg:@"Password verification doesn't match"]); - VALIDATE([self validateField:self.userID validator:validateNotEmpty errorMsg:@"Please enter a user ID"]); - VALIDATE([self validateField:self.homeDirectory validator:validateHomeDir errorMsg:@"Please enter a home directory path"]); - VALIDATE([self validateField:self.uuid validator:validateUUID errorMsg:@"Please enter a valid UUID"]); - VALIDATE([self validateField:self.packageID validator:validateNotEmpty errorMsg:@"Please enter a package ID"]); - VALIDATE([self validateField:self.version validator:validateNotEmpty errorMsg:@"Please enter a version"]); - - SET_DOC_STATE(fullName); - SET_DOC_STATE(accountName); - SET_DOC_STATE(userID); - SET_DOC_STATE(homeDirectory); - SET_DOC_STATE(uuid); - SET_DOC_STATE(packageID); - SET_DOC_STATE(version); - if ([[self.password stringValue] isEqualToString:CUP_PASSWORD_PLACEHOLDER] == NO) { - if (self.shadowHashData == nil) { - NSLog(@"shadowHash is empty, calculating new hash"); - [self calculateShadowHashData:[self.password stringValue]]; - } - (self.docState)[@"shadowHashData"] = [NSData dataWithData:self.shadowHashData]; +- (BOOL)validateDocumentAndUpdateState { + BOOL (^validateNotEmpty)(NSControl *) = ^(NSControl *field) { + return (BOOL)(field.stringValue.length); + }; + BOOL (^validateSameAsPassword)(NSControl *) = ^(NSControl *field) { + return [field.stringValue isEqualToString:self.password.stringValue]; + }; + BOOL (^validateHomeDir)(NSControl *) = ^(NSControl *field) { + return (BOOL)(self.homeDirectory.stringValue.length && + [self.homeDirectory.stringValue characterAtIndex:0] == L'/'); + }; + BOOL (^validateUUID)(NSControl *) = ^(NSControl *field){ + return (BOOL)(field.stringValue.length == 36); + }; + + VALIDATE([self validateField:self.fullName + validator:validateNotEmpty + errorMsg:@"Please enter a full name"]); + VALIDATE([self validateField:self.accountName + validator:validateNotEmpty + errorMsg:@"Please enter an account name"]); + if (!self.password.stringValue.length) { + if (NSRunAlertPanel(@"Are you sure you want to create a user with an empty password?", + @"Anyone will be able to log in, even remotely, without being asked for a password.", + @"Cancel", + @"Use Empty Password", + nil) == NSAlertDefaultReturn) { + [self.password becomeFirstResponder]; + return NO; } - if ([self.automaticLogin state] == NSOnState) { - if ([self.kcPassword length] == 0) { - NSLog(@"kcPassword is empty, calculating new hash"); - [self calculateKCPassword:[self.password stringValue]]; - } - (self.docState)[@"kcPassword"] = self.kcPassword; + } + VALIDATE([self validateField:self.verifyPassword + validator:validateSameAsPassword + errorMsg:@"Password verification doesn't match"]); + VALIDATE([self validateField:self.userID + validator:validateNotEmpty + errorMsg:@"Please enter a user ID"]); + VALIDATE([self validateField:self.homeDirectory + validator:validateHomeDir + errorMsg:@"Please enter a home directory path"]); + VALIDATE([self validateField:self.uuid + validator:validateUUID + errorMsg:@"Please enter a valid UUID"]); + VALIDATE([self validateField:self.packageID + validator:validateNotEmpty + errorMsg:@"Please enter a package ID"]); + VALIDATE([self validateField:self.version + validator:validateNotEmpty + errorMsg:@"Please enter a version"]); + + SET_DOC_STATE(fullName); + SET_DOC_STATE(accountName); + SET_DOC_STATE(userID); + SET_DOC_STATE(homeDirectory); + SET_DOC_STATE(uuid); + SET_DOC_STATE(packageID); + SET_DOC_STATE(version); + + if (![self.password.stringValue isEqualToString:CUP_PASSWORD_PLACEHOLDER]) { + if (!self.shadowHashData) { + NSLog(@"shadowHash is empty, calculating new hash"); + self.shadowHashData = [CUPKeys calculateShadowHashData:[self.password stringValue]]; } - if (self.image.imageData != nil) { - (self.docState)[@"imageData"] = self.image.imageData; + self.docState[@"shadowHashData"] = self.shadowHashData; + } + if (self.automaticLogin.state == NSOnState) { + if (!self.kcPasswordData.length) { + NSLog(@"kcPassword is empty, calculating new hash"); + self.kcPasswordData = [CUPKeys calculateKCPasswordData:[self.password stringValue]]; } - if (self.image.imagePath != nil) { - (self.docState)[@"imagePath"] = self.image.imagePath; - } - (self.docState)[@"isAdmin"] = [NSNumber numberWithBool:([self.accountType indexOfSelectedItem] == ACCOUNT_TYPE_ADMIN)]; - - return YES; + self.docState[@"kcPassword"] = self.kcPasswordData; + } + if (self.image.imageData) { + self.docState[@"imageData"] = self.image.imageData; + } + if (self.image.imagePath) { + self.docState[@"imagePath"] = self.image.imagePath; + } + self.docState[@"isAdmin"] = + [NSNumber numberWithBool:([self.accountType indexOfSelectedItem] == ACCOUNT_TYPE_ADMIN)]; + + return YES; } -- (void)saveDocument:(id)sender -{ - if ([self validateDocumentAndUpdateState]) { - [super saveDocument:sender]; - } +- (void)saveDocument:(id)sender { + if ([self validateDocumentAndUpdateState]) { + [super saveDocument:sender]; + } } -- (void)saveDocumentAs:(id)sender -{ - if ([self validateDocumentAndUpdateState]) { - [super saveDocumentAs:sender]; - } +- (void)saveDocumentAs:(id)sender { + if ([self validateDocumentAndUpdateState]) { + [super saveDocumentAs:sender]; + } } -- (NSData *)dataOfType:(NSString *)typeName error:(NSError **)outError -{ - NSData *data; - - NSDictionary *result = [self newDictFromScript:[[NSBundle mainBundle] pathForResource:@"create_package" ofType:@"py"] withArgs:self.docState error:outError]; - if (result == nil) { - return nil; - } - data = result[@"data"]; - return data; +- (NSData *)dataOfType:(NSString *)typeName error:(NSError **)outError { + NSString *path = [[NSBundle mainBundle] pathForResource:@"create_package" ofType:@"py"]; + NSDictionary *result = [self newDictFromScript:path withArgs:self.docState error:outError]; + return result[@"data"]; } -- (void)setDocStateKey:(NSString *)key fromDict:(NSDictionary *)dict -{ - NSString *value = dict[key]; - if (value != nil) { - (self.docState)[key] = value; - } +- (void)setDocStateKey:(NSString *)key fromDict:(NSDictionary *)dict { + NSString *value = dict[key]; + if (value) self.docState[key] = value; } -- (BOOL)readFromData:(NSData *)data ofType:(NSString *)typeName error:(NSError **)outError -{ - NSDictionary *document; - NSString *tmp_plist = [NSTemporaryDirectory() stringByAppendingFormat:@"%08x.pkg", arc4random()]; - NSDictionary *args = @{@"input": tmp_plist}; - - [data writeToFile:tmp_plist atomically:NO]; - - document = [self newDictFromScript:[[NSBundle mainBundle] pathForResource:@"read_package" ofType:@"py"] withArgs:args error:outError]; - [[NSFileManager defaultManager] removeItemAtPath:tmp_plist error:nil]; - if (document == nil) { - NSLog(@"Script returned nil"); - return NO; - } - - [self.docState removeAllObjects]; - [self setDocStateKey:@"fullName" fromDict:document]; - [self setDocStateKey:@"accountName" fromDict:document]; - [self setDocStateKey:@"userID" fromDict:document]; - [self setDocStateKey:@"isAdmin" fromDict:document]; - [self setDocStateKey:@"homeDirectory" fromDict:document]; - [self setDocStateKey:@"uuid" fromDict:document]; - [self setDocStateKey:@"packageID" fromDict:document]; - [self setDocStateKey:@"version" fromDict:document]; - if (document[@"shadowHashData"] != nil) { - // only set this if we read ShadowHashData from the package - [self setDocStateKey:@"shadowHashData" fromDict:document]; - } - [self setDocStateKey:@"imageData" fromDict:document]; - [self setDocStateKey:@"imagePath" fromDict:document]; - [self setDocStateKey:@"kcPassword" fromDict:document]; - - self.kcPassword = document[@"kcPassword"]; - - return YES; +- (BOOL)readFromData:(NSData *)data ofType:(NSString *)typeName error:(NSError **)outError { + NSString *tmpPlist = [NSTemporaryDirectory() stringByAppendingFormat:@"%08x.pkg", arc4random()]; + NSDictionary *args = @{ @"input" : tmpPlist }; + + [data writeToFile:tmpPlist atomically:NO]; + + NSString *path = [[NSBundle mainBundle] pathForResource:@"read_package" ofType:@"py"]; + NSDictionary *document = [self newDictFromScript:path withArgs:args error:outError]; + [[NSFileManager defaultManager] removeItemAtPath:tmpPlist error:NULL]; + if (!document) { + NSLog(@"Script returned nil"); + return NO; + } + + [self.docState removeAllObjects]; + [self setDocStateKey:@"fullName" fromDict:document]; + [self setDocStateKey:@"accountName" fromDict:document]; + [self setDocStateKey:@"userID" fromDict:document]; + [self setDocStateKey:@"isAdmin" fromDict:document]; + [self setDocStateKey:@"homeDirectory" fromDict:document]; + [self setDocStateKey:@"uuid" fromDict:document]; + [self setDocStateKey:@"packageID" fromDict:document]; + [self setDocStateKey:@"version" fromDict:document]; + if (document[@"shadowHashData"]) { + // only set this if we read ShadowHashData from the package + [self setDocStateKey:@"shadowHashData" fromDict:document]; + } + [self setDocStateKey:@"imageData" fromDict:document]; + [self setDocStateKey:@"imagePath" fromDict:document]; + [self setDocStateKey:@"kcPassword" fromDict:document]; + + self.kcPasswordData = document[@"kcPassword"]; + + return YES; } -- (NSDictionary *)newDictFromScript:(NSString *)path withArgs:(NSDictionary *)argDict error:(NSError **)outError -{ - NSTask *task; - NSPipe *stdoutPipe; - NSPipe *stderrPipe; - NSPipe *stdinPipe; - NSFileHandle *stdoutFile; - NSFileHandle *stderrFile; - NSFileHandle *stdinFile; - NSData *stdoutData; - NSData *stderrData; - NSData *stdinData; - NSDictionary *result; - NSString *errorMsg = nil; - - // Create a task to execute the script. - task = [[NSTask alloc] init]; - [task setLaunchPath:path]; - //[task setArguments:args]; - - // Set up stdout/err/in pipes and get the file handles. - stdoutPipe = [NSPipe pipe]; - stderrPipe = [NSPipe pipe]; - stdinPipe = [NSPipe pipe]; - [task setStandardOutput:stdoutPipe]; - [task setStandardError:stderrPipe]; - [task setStandardInput:stdinPipe]; - stdoutFile = [stdoutPipe fileHandleForReading]; - stderrFile = [stderrPipe fileHandleForReading]; - stdinFile = [stdinPipe fileHandleForWriting]; - - // Execute and collect output. - [task launch]; - - stdinData = [NSPropertyListSerialization dataFromPropertyList:argDict format:NSPropertyListXMLFormat_v1_0 errorDescription:&errorMsg]; - if (stdinData == nil) { - if (outError != NULL) { - *outError = [self cocoaError:NSFileReadUnknownError withReason:[NSString stringWithFormat:@"Couldn't create input for %@: %@", [path lastPathComponent], errorMsg]]; - } +- (NSDictionary *)newDictFromScript:(NSString *)path + withArgs:(NSDictionary *)argDict + error:(NSError **)outError { + // Create a task to execute the script. + NSTask *task = [[NSTask alloc] init]; + task.launchPath = path; + task.standardOutput = [NSPipe pipe]; + task.standardError = [NSPipe pipe]; + task.standardInput = [NSPipe pipe]; + + // Execute and collect output. + [task launch]; + + NSError *error; + NSData *inData = [NSPropertyListSerialization dataWithPropertyList:argDict + format:NSPropertyListXMLFormat_v1_0 + options:0 + error:&error]; + if (!inData) { + if (outError) { + *outError = [self cocoaError:NSFileReadUnknownError + withReason:[NSString stringWithFormat:@"Couldn't create input for %@: %@", + [path lastPathComponent], error.description]]; + } + } + [[task.standardInput fileHandleForWriting] writeData:inData]; + [[task.standardInput fileHandleForWriting] closeFile]; + + [task waitUntilExit]; + NSData *stdoutData = [[task.standardOutput fileHandleForReading] readDataToEndOfFile]; + NSData *stderrData = [[task.standardError fileHandleForReading] readDataToEndOfFile]; + int terminationStatus = [task terminationStatus]; + + if (stderrData.length) { + NSLog(@"%@ stderr: %@", path, [[NSString alloc] initWithData:stderrData + encoding:NSUTF8StringEncoding]); + } + + if (terminationStatus == 0) { + NSDictionary *result = [NSPropertyListSerialization propertyListWithData:stdoutData + options:0 + format:NULL + error:&error]; + if (!result) { + if (stdoutData.length) { + NSLog(@"%@ stdout: %@", path, [[NSString alloc] initWithData:stdoutData + encoding:NSUTF8StringEncoding]); + } + if (outError) { + *outError = [self cocoaError:NSFileReadUnknownError + withReason:[NSString stringWithFormat:@"Couldn't read output from %@: %@", + [path lastPathComponent], error.description]]; + } + return nil; } - [stdinFile writeData:stdinData]; - [stdinFile closeFile]; - stdoutData = [stdoutFile readDataToEndOfFile]; - stderrData = [stderrFile readDataToEndOfFile]; - - [task waitUntilExit]; - int terminationStatus = [task terminationStatus]; - - - if ([stderrData length] != 0) { - NSLog(@"%@ stderr: %@", path, [[NSString alloc] initWithData:stderrData encoding:NSUTF8StringEncoding]); + return result; + } else { + if (stdoutData.length) { + NSLog(@"%@ stdout: %@", path, [[NSString alloc] initWithData:stdoutData + encoding:NSUTF8StringEncoding]); } - - if (terminationStatus == 0) { - result = [NSPropertyListSerialization propertyListFromData:stdoutData - mutabilityOption:0 - format:NULL - errorDescription:&errorMsg]; - if (result == nil) { - if ([stdoutData length] != 0) { - NSLog(@"%@ stdout: %@", path, [[NSString alloc] initWithData:stdoutData encoding:NSUTF8StringEncoding]); - } - if (outError != NULL) { - *outError = [self cocoaError:NSFileReadUnknownError withReason:[NSString stringWithFormat:@"Couldn't read output from %@: %@", [path lastPathComponent], errorMsg]]; - } - return nil; - } - return result; - } else { - if ([stdoutData length] != 0) { - NSLog(@"%@ stdout: %@", path, [[NSString alloc] initWithData:stdoutData encoding:NSUTF8StringEncoding]); - } - if (outError != NULL) { - *outError = [self cocoaError:NSFileReadUnknownError withReason:[NSString stringWithFormat:@"%@ exited with return code %d", [path lastPathComponent], terminationStatus]]; - } - return nil; + if (outError) { + *outError = [self cocoaError:NSFileReadUnknownError + withReason:[NSString stringWithFormat:@"%@ exited with return code %d", + [path lastPathComponent], terminationStatus]]; } + return nil; + } } @end diff --git a/CreateUserPkg/CUPImageSelector.h b/CreateUserPkg/CUPImageSelector.h index 9ec8c05..8615161 100644 --- a/CreateUserPkg/CUPImageSelector.h +++ b/CreateUserPkg/CUPImageSelector.h @@ -8,13 +8,10 @@ #import -@interface CUPImageSelector : NSImageView { - NSData *_imageData; - NSString *_imagePath; -} +@interface CUPImageSelector : NSImageView -@property (strong) NSData *imageData; -@property (strong) NSString *imagePath; +@property(copy) NSData *imageData; +@property(copy) NSString *imagePath; - (BOOL)saveJpegData:(NSData *)data; - (void)saveUserPicturesPath:(NSURL *)url; diff --git a/CreateUserPkg/CUPImageSelector.m b/CreateUserPkg/CUPImageSelector.m index e6ad105..e0fc207 100644 --- a/CreateUserPkg/CUPImageSelector.m +++ b/CreateUserPkg/CUPImageSelector.m @@ -10,90 +10,69 @@ @implementation CUPImageSelector -@synthesize imageData = _imageData; -@synthesize imagePath = _imagePath; - - -- (instancetype)initWithFrame:(NSRect)frame -{ - self = [super initWithFrame:frame]; - if (self) { - [self registerForDraggedTypes:@[NSTIFFPboardType, NSURLPboardType]]; - self.imageData = nil; - self.imagePath = nil; - } - - return self; +- (instancetype)initWithFrame:(NSRect)frame { + self = [super initWithFrame:frame]; + if (self) { + [self registerForDraggedTypes:@[ NSTIFFPboardType, NSURLPboardType ]]; + } + return self; } -- (BOOL)saveJpegData:(NSData *)data -{ - NSBitmapImageRep *imgrep = [NSBitmapImageRep imageRepWithData:data]; - if (imgrep == nil) { - return NO; - } - self.imageData = [imgrep representationUsingType:NSJPEGFileType properties:@{}]; - if (self.imageData == nil) { - return NO; - } - return YES; +- (BOOL)saveJpegData:(NSData *)data { + NSBitmapImageRep *imgrep = [NSBitmapImageRep imageRepWithData:data]; + if (!imgrep) return NO; + self.imageData = [imgrep representationUsingType:NSJPEGFileType properties:@{}]; + if (!self.imageData) return NO; + return YES; } -- (void)saveUserPicturesPath:(NSURL *)url -{ - if (url != nil) { - if ([url isFileURL] == YES) { - NSString *path = [url path]; - if ([path hasPrefix:@"/Library/User Pictures/"]) { - self.imagePath = path; - } - } +- (void)saveUserPicturesPath:(NSURL *)url { + if (url) { + if ([url isFileURL]) { + NSString *path = [url path]; + if ([path hasPrefix:@"/Library/User Pictures/"]) { + self.imagePath = path; + } } + } } -- (void)displayImageData -{ - if (self.imageData) { - NSImage *image = [[NSImage alloc] initWithData:self.imageData]; - [self setImage:image]; - } - [self setNeedsDisplay:YES]; +- (void)displayImageData { + if (self.imageData) { + NSImage *image = [[NSImage alloc] initWithData:self.imageData]; + [self setImage:image]; + } + [self setNeedsDisplay:YES]; } -- (NSDragOperation)draggingEntered:(id )sender -{ - if ((NSDragOperationGeneric & [sender draggingSourceOperationMask]) == NSDragOperationGeneric) { - return NSDragOperationGeneric; - } else { - return NSDragOperationNone; - } +- (NSDragOperation)draggingEntered:(id)sender { + if ((NSDragOperationGeneric & [sender draggingSourceOperationMask]) == NSDragOperationGeneric) { + return NSDragOperationGeneric; + } else { + return NSDragOperationNone; + } } -- (BOOL)performDragOperation:(id )sender -{ - NSPasteboard *pboard = [sender draggingPasteboard]; - NSString *droppedType = [pboard availableTypeFromArray:@[NSTIFFPboardType, NSURLPboardType]]; - - if ([droppedType isEqualToString:NSTIFFPboardType]) { - NSData *droppedData = [pboard dataForType:droppedType]; - if ([self saveJpegData:droppedData] == NO) { - return NO; - } - [self displayImageData]; - self.imagePath = nil; - } else if ([droppedType isEqualToString:NSURLPboardType]) { - NSURL *droppedURL = [NSURL URLFromPasteboard:pboard]; - if ([self saveJpegData:[NSData dataWithContentsOfURL:droppedURL]] == NO) { - return NO; - } - [self saveUserPicturesPath:droppedURL]; - } else { - return NO; - } - - [self setNeedsDisplay:YES]; - return YES; +- (BOOL)performDragOperation:(id )sender { + NSPasteboard *pboard = [sender draggingPasteboard]; + NSString *droppedType = [pboard availableTypeFromArray:@[ NSTIFFPboardType, NSURLPboardType ]]; + + if ([droppedType isEqualToString:NSTIFFPboardType]) { + NSData *droppedData = [pboard dataForType:droppedType]; + if (![self saveJpegData:droppedData]) return NO; + [self displayImageData]; + self.imagePath = nil; + } else if ([droppedType isEqualToString:NSURLPboardType]) { + NSURL *droppedURL = [NSURL URLFromPasteboard:pboard]; + if (![self saveJpegData:[NSData dataWithContentsOfURL:droppedURL]]) return NO; + [self saveUserPicturesPath:droppedURL]; + } else { + return NO; + } + + [self setNeedsDisplay:YES]; + return YES; } @end diff --git a/CreateUserPkg/CUPKeys.h b/CreateUserPkg/CUPKeys.h new file mode 100644 index 0000000..c410082 --- /dev/null +++ b/CreateUserPkg/CUPKeys.h @@ -0,0 +1,16 @@ +// +// CUPKeys.h +// CreateUserPkg +// +// Created by Tom Burgin on 6/20/17. +// Copyright © 2017 University of Gothenburg. All rights reserved. +// + +#import + +@interface CUPKeys : NSObject + ++ (NSData *)calculateShadowHashData:(NSString *)pwd; ++ (NSData *)calculateKCPasswordData:(NSString *)pwd; + +@end diff --git a/CreateUserPkg/CUPKeys.m b/CreateUserPkg/CUPKeys.m new file mode 100644 index 0000000..126c625 --- /dev/null +++ b/CreateUserPkg/CUPKeys.m @@ -0,0 +1,76 @@ +// +// CUPKeys.m +// CreateUserPkg +// +// Created by Tom Burgin on 6/20/17. +// Copyright © 2017 University of Gothenburg. All rights reserved. +// + +#import "CUPKeys.h" + +#import +#import + +#define PBKDF2_SALT_LEN 32 +#define PBKDF2_ENTROPY_LEN 128 +#define KCKEY_LEN 11 +const char kcPasswordKey[KCKEY_LEN] = + {0x7D, 0x89, 0x52, 0x23, 0xD2, 0xBC, 0xDD, 0xEA, 0xA3, 0xB9, 0x1F}; + +@implementation CUPKeys + ++ (NSData *)calculateShadowHashData:(NSString *)pwd { + unsigned char salt[PBKDF2_SALT_LEN]; + int status = SecRandomCopyBytes(kSecRandomDefault, PBKDF2_SALT_LEN, salt); + if (status == 0) { + unsigned char key[PBKDF2_ENTROPY_LEN]; + // calculate the number of iterations to use + unsigned int rounds = CCCalibratePBKDF(kCCPBKDF2, + [pwd lengthOfBytesUsingEncoding:NSUTF8StringEncoding], + PBKDF2_SALT_LEN, kCCPRFHmacAlgSHA512, PBKDF2_ENTROPY_LEN, 100); + // derive our SALTED-SHA512-PBKDF2 key + CCKeyDerivationPBKDF(kCCPBKDF2, + [pwd UTF8String], + (CC_LONG)[pwd lengthOfBytesUsingEncoding:NSUTF8StringEncoding], + salt, PBKDF2_SALT_LEN, + kCCPRFHmacAlgSHA512, rounds, key, PBKDF2_ENTROPY_LEN); + // Make a dictionary containing the needed fields + NSDictionary *dictionary = @{ + @"SALTED-SHA512-PBKDF2" : @{ + @"entropy" : [NSData dataWithBytes: key length: PBKDF2_ENTROPY_LEN], + @"iterations" : [NSNumber numberWithUnsignedInt: rounds], + @"salt" : [NSData dataWithBytes: salt length: PBKDF2_SALT_LEN] + } + }; + // convert to binary plist data + NSError *error = NULL; + NSData *plistData = [NSPropertyListSerialization dataWithPropertyList: dictionary + format: NSPropertyListBinaryFormat_v1_0 + options: 0 + error: &error]; + return plistData; + } + return nil; +} + ++ (NSData *)calculateKCPasswordData:(NSString *)pwd { + int i; + NSUInteger pwdlen = [pwd lengthOfBytesUsingEncoding:NSUTF8StringEncoding]; + // kcpassword stores the password plus null terminator, rounded up to the nearest 12 bytes. + NSUInteger rounded12len = ((pwdlen + 12) / 12) * 12; + unsigned char *kcbuf = malloc(rounded12len); + + // Get UTF-8 encoded data and xor it with the key. + const unsigned char *pwdbuf = [[pwd dataUsingEncoding:NSUTF8StringEncoding] bytes]; + for (i = 0; i < pwdlen; i++) { + kcbuf[i] = pwdbuf[i] ^ kcPasswordKey[i % KCKEY_LEN]; + } + kcbuf[i] = kcPasswordKey[i % KCKEY_LEN]; + + free(kcbuf); + + return [NSData dataWithBytes:kcbuf length:rounded12len]; +} + + +@end diff --git a/CreateUserPkg/CUPUIDFormatter.h b/CreateUserPkg/CUPUIDFormatter.h index 187c079..d8c9f35 100644 --- a/CreateUserPkg/CUPUIDFormatter.h +++ b/CreateUserPkg/CUPUIDFormatter.h @@ -9,5 +9,4 @@ #import @interface CUPUIDFormatter : NSNumberFormatter - @end diff --git a/CreateUserPkg/CUPUIDFormatter.m b/CreateUserPkg/CUPUIDFormatter.m index 1b70856..f9763d6 100644 --- a/CreateUserPkg/CUPUIDFormatter.m +++ b/CreateUserPkg/CUPUIDFormatter.m @@ -12,22 +12,19 @@ @implementation CUPUIDFormatter - (BOOL)isPartialStringValid:(NSString *)partialString newEditingString:(NSString **)newString - errorDescription:(NSString **)error -{ - NSRange inRange; - NSCharacterSet *allowedChars = [[NSCharacterSet characterSetWithCharactersInString:@"0123456789"] invertedSet]; - inRange = [partialString rangeOfCharacterFromSet:allowedChars]; - - if(inRange.location != NSNotFound) - { - *error = @"Only numbers are allowed."; - NSBeep(); - return NO; - } - - *newString = partialString; - return YES; -} + errorDescription:(NSString **)error { + NSCharacterSet *allowedChars = + [NSCharacterSet characterSetWithCharactersInString:@"0123456789"].invertedSet; + NSRange inRange = [partialString rangeOfCharacterFromSet:allowedChars]; + + if (inRange.location != NSNotFound) { + *error = @"Only numbers are allowed."; + NSBeep(); + return NO; + } + *newString = partialString; + return YES; +} @end diff --git a/CreateUserPkg/CUPUUIDFormatter.h b/CreateUserPkg/CUPUUIDFormatter.h index c77de23..a05033a 100644 --- a/CreateUserPkg/CUPUUIDFormatter.h +++ b/CreateUserPkg/CUPUUIDFormatter.h @@ -8,8 +8,5 @@ #import -@interface CUPUUIDFormatter : NSFormatter { - NSMutableCharacterSet *controlSet; -} - +@interface CUPUUIDFormatter : NSFormatter @end diff --git a/CreateUserPkg/CUPUUIDFormatter.m b/CreateUserPkg/CUPUUIDFormatter.m index dfbd51b..0519c99 100644 --- a/CreateUserPkg/CUPUUIDFormatter.m +++ b/CreateUserPkg/CUPUUIDFormatter.m @@ -8,52 +8,52 @@ #import "CUPUUIDFormatter.h" +@interface CUPUUIDFormatter () +@property NSMutableCharacterSet *controlSet; +@end + @implementation CUPUUIDFormatter -- (instancetype)init -{ - self = [super init]; - if (self) { - controlSet = [[NSMutableCharacterSet alloc] init]; - - NSRange upperCaseAlphaRange; - upperCaseAlphaRange.location = (unsigned int)'A'; - upperCaseAlphaRange.length = 6; - [controlSet addCharactersInRange:upperCaseAlphaRange]; - - NSRange digitsRange; - digitsRange.location = (unsigned int)'0'; - digitsRange.length = 10; - [controlSet addCharactersInRange:digitsRange]; - - [controlSet addCharactersInString:@"-"]; - - [controlSet invert]; - } - return self; +- (instancetype)init { + self = [super init]; + if (self) { + _controlSet = [[NSMutableCharacterSet alloc] init]; + + NSRange upperCaseAlphaRange; + upperCaseAlphaRange.location = (unsigned int)'A'; + upperCaseAlphaRange.length = 6; + [_controlSet addCharactersInRange:upperCaseAlphaRange]; + + NSRange digitsRange; + digitsRange.location = (unsigned int)'0'; + digitsRange.length = 10; + [_controlSet addCharactersInRange:digitsRange]; + [_controlSet addCharactersInString:@"-"]; + [_controlSet invert]; + } + return self; } - (NSString *)stringForObjectValue:(id)anObject { - if (![anObject isKindOfClass:[NSString class]]) { - return nil; - } - return anObject; + if (![anObject isKindOfClass:[NSString class]]) { + return nil; + } + return anObject; } - (BOOL)getObjectValue:(id *)obj forString:(NSString *)string errorDescription:(NSString **)error { - NSString *trimmedReplacement = [[string componentsSeparatedByCharactersInSet:controlSet] componentsJoinedByString:@""]; - *obj = trimmedReplacement; - return YES; + NSString *trimmedReplacement = + [[string componentsSeparatedByCharactersInSet:self.controlSet] componentsJoinedByString:@""]; + *obj = trimmedReplacement; + return YES; } - (BOOL)isPartialStringValid:(NSString *)partialString newEditingString:(NSString **)newString - errorDescription:(NSString **)error -{ - NSRange inRange = [[partialString uppercaseString] rangeOfCharacterFromSet:controlSet]; + errorDescription:(NSString **)error { + NSRange inRange = [[partialString uppercaseString] rangeOfCharacterFromSet:self.controlSet]; - if(inRange.location != NSNotFound) - { + if(inRange.location != NSNotFound) { *error = @"Illegal value."; NSBeep(); return NO; diff --git a/CreateUserPkg/CUPUserNameFormatter.h b/CreateUserPkg/CUPUserNameFormatter.h index aa74794..02e2978 100644 --- a/CreateUserPkg/CUPUserNameFormatter.h +++ b/CreateUserPkg/CUPUserNameFormatter.h @@ -8,8 +8,5 @@ #import -@interface CUPUserNameFormatter : NSFormatter { - NSMutableCharacterSet *controlSet; -} - +@interface CUPUserNameFormatter : NSFormatter @end diff --git a/CreateUserPkg/CUPUserNameFormatter.m b/CreateUserPkg/CUPUserNameFormatter.m index c6de2ff..a366590 100644 --- a/CreateUserPkg/CUPUserNameFormatter.m +++ b/CreateUserPkg/CUPUserNameFormatter.m @@ -8,59 +8,60 @@ #import "CUPUserNameFormatter.h" +@interface CUPUserNameFormatter () +@property NSMutableCharacterSet *controlSet; +@end + @implementation CUPUserNameFormatter -- (instancetype)init -{ - self = [super init]; - if (self) { - controlSet = [[NSMutableCharacterSet alloc] init]; - - NSRange lowerCaseAlphaRange; - lowerCaseAlphaRange.location = (unsigned int)'a'; - lowerCaseAlphaRange.length = 26; - [controlSet addCharactersInRange:lowerCaseAlphaRange]; - - NSRange digitsRange; - digitsRange.location = (unsigned int)'0'; - digitsRange.length = 10; - [controlSet addCharactersInRange:digitsRange]; - - [controlSet addCharactersInString:@"-._"]; - - [controlSet invert]; - } - return self; +- (instancetype)init { + self = [super init]; + if (self) { + _controlSet = [[NSMutableCharacterSet alloc] init]; + + NSRange lowerCaseAlphaRange; + lowerCaseAlphaRange.location = (unsigned int)'a'; + lowerCaseAlphaRange.length = 26; + [_controlSet addCharactersInRange:lowerCaseAlphaRange]; + + NSRange digitsRange; + digitsRange.location = (unsigned int)'0'; + digitsRange.length = 10; + [_controlSet addCharactersInRange:digitsRange]; + [_controlSet addCharactersInString:@"-._"]; + [_controlSet invert]; + } + return self; } - (NSString *)stringForObjectValue:(id)anObject { - if (![anObject isKindOfClass:[NSString class]]) { - return nil; - } - return anObject; + if (![anObject isKindOfClass:[NSString class]]) { + return nil; + } + return anObject; } -- (BOOL)getObjectValue:(id *)obj forString:(NSString *)string errorDescription:(NSString **)error { - NSString *trimmedReplacement = [[string componentsSeparatedByCharactersInSet:controlSet] componentsJoinedByString:@""]; - *obj = trimmedReplacement; - return YES; +- (BOOL)getObjectValue:(id *)obj + forString:(NSString *)string + errorDescription:(NSString **)error { + NSString *trimmedReplacement = + [[string componentsSeparatedByCharactersInSet:self.controlSet] componentsJoinedByString:@""]; + *obj = trimmedReplacement; + return YES; } - (BOOL)isPartialStringValid:(NSString *)partialString newEditingString:(NSString **)newString - errorDescription:(NSString **)error -{ - NSRange inRange = [partialString rangeOfCharacterFromSet:controlSet]; - - if(inRange.location != NSNotFound) - { - *error = @"Illegal value."; - NSBeep(); - return NO; - } - - *newString = partialString; - return YES; + errorDescription:(NSString **)error { + NSRange inRange = [partialString rangeOfCharacterFromSet:self.controlSet]; + if(inRange.location != NSNotFound) { + *error = @"Illegal value."; + NSBeep(); + return NO; + } + + *newString = partialString; + return YES; } @end diff --git a/CreateUserPkg/en.lproj/CUPDocument.xib b/CreateUserPkg/en.lproj/CUPDocument.xib index f1eba3e..c5fe2c5 100644 --- a/CreateUserPkg/en.lproj/CUPDocument.xib +++ b/CreateUserPkg/en.lproj/CUPDocument.xib @@ -1,1505 +1,283 @@ - - - 1070 - 12A269 - 2549 - 1187 - 624.00 - - com.apple.InterfaceBuilder.CocoaPlugin - 2549 - - - NSBox - NSButton - NSButtonCell - NSCustomObject - NSImageCell - NSImageView - NSMenu - NSMenuItem - NSPopUpButton - NSPopUpButtonCell - NSTextField - NSTextFieldCell - NSView - NSWindowTemplate - - - com.apple.InterfaceBuilder.CocoaPlugin - - - PluginDependencyRecalculationVersion - - - - - CUPDocument - - - FirstResponder - - - 15 - 2 - {{1030, 450}, {534, 459}} - 1886912512 - Window - NSWindow - View - - {534, 459} - {534, 459} - - - 256 - - - - 12 - - - - 274 - - - - 268 - {{143, 312}, {357, 22}} - - - - _NS:9 - YES - - -1804599231 - 272630784 - - - LucidaGrande - 13 - 1044 - - _NS:9 - - YES - - 6 - System - textBackgroundColor - - 3 - MQA - - - - 6 - System - textColor - - 3 - MAA - - - - NO - - - - 268 - {{31, 314}, {107, 17}} - - - - _NS:1505 - YES - - 68157504 - 71304192 - Full Name: - - _NS:1505 - - - 6 - System - controlColor - - 3 - MC42NjY2NjY2NjY3AA - - - - 6 - System - controlTextColor - - - - NO - - - - 268 - {{143, 270}, {229, 22}} - - - - _NS:9 - YES - - -1804599231 - 272630784 - - - _NS:9 - - YES - - - - NO - - - - 268 - {{143, 228}, {229, 22}} - - - - _NS:9 - YES - - -1804599231 - 272630784 - - - _NS:9 - - YES - - - - NO - - - - 268 - {{143, 186}, {229, 22}} - - - - _NS:9 - YES - - -1804599231 - 272630784 - - - _NS:9 - - YES - - - - NO - - - - 268 - {{31, 272}, {107, 17}} - - - - _NS:1505 - YES - - 68157504 - 71304192 - Account name: - - _NS:1505 - - - - - NO - - - - 268 - {{31, 230}, {107, 17}} - - - - _NS:1505 - YES - - 68157504 - 71304192 - Password: - - _NS:1505 - - - - - NO - - - - 268 - {{31, 188}, {107, 17}} - - - - _NS:1505 - YES - - 68157504 - 71304192 - Verify: - - _NS:1505 - - - - - NO - - - - 268 - {{143, 144}, {70, 22}} - - - - _NS:9 - YES - - -1804599231 - 272630784 - - - _NS:9 - - YES - - - - NO - - - - 268 - {{31, 146}, {107, 17}} - - - - _NS:1505 - YES - - 68157504 - 71304192 - User ID: - - _NS:1505 - - - - - NO - - - - 268 - {{218, 146}, {96, 17}} - - - - _NS:1505 - YES - - 68157504 - 71304192 - Account type: - - _NS:1505 - - - - - NO - - - - 268 - {{143, 102}, {357, 22}} - - - - _NS:9 - YES - - -1804599231 - 272630784 - - - _NS:9 - - YES - - - - NO - - - - 268 - {{31, 104}, {107, 17}} - - - - _NS:1505 - YES - - 68157504 - 71304192 - Home directory: - - _NS:1505 - - - - - NO - - - - 268 - {{143, 60}, {357, 22}} - - - - _NS:9 - YES - - -1804599231 - 272630784 - - - _NS:9 - - YES - - - - NO - - - - 268 - {{31, 62}, {107, 17}} - - - - _NS:1505 - YES - - 68157504 - 71304192 - UUID: - - _NS:1505 - - - - - NO - - - - 268 - - Apple PDF pasteboard type - Apple PICT pasteboard type - Apple PNG pasteboard type - NSFilenamesPboardType - NeXT Encapsulated PostScript v1.2 pasteboard type - NeXT TIFF v4.0 pasteboard type - - {{391, 183}, {112, 112}} - - - - _NS:9 - YES - - 134217728 - 33554432 - - NSImage - dropimagehere - - _NS:9 - 0 - 0 - 2 - NO - - NO - YES - - - - 268 - {{196, 19}, {123, 18}} - - - - _NS:9 - YES - - 67108864 - 268435456 - Automatic login - - _NS:9 - - 1211912448 - 2 - - NSImage - NSSwitch - - - NSSwitch - - - - 200 - 25 - - NO - - - - 268 - {{316, 141}, {187, 26}} - - - - _NS:9 - YES - - -2076180416 - 2048 - - _NS:9 - - 109199360 - 129 - - - 400 - 75 - - - Administrator - - 1048576 - 2147483647 - 1 - - NSImage - NSMenuCheckmark - - - NSImage - NSMenuMixedState - - _popUpItemAction: - - - YES - - OtherViews - - - - Standard - - 1048576 - 2147483647 - - - _popUpItemAction: - - - - - - - 1 - 1 - YES - YES - 2 - - NO - - - {{1, 1}, {518, 348}} - - - - _NS:11 - - - {{7, 98}, {520, 364}} - - - - _NS:9 - {250, 250} - {0, 0} - - 67108864 - 0 - - - LucidaGrande - 11 - 3100 - - - - 3 - MCAwLjgwMDAwMDAxMTkAA - - - - 1 - 0 - 2 - NO - - - - 268 - {{400, 13}, {124, 32}} - - - _NS:9 - YES - - 67108864 - 134217728 - Save Package - - _NS:9 - - -2038284288 - 129 - - - 200 - 25 - - NO - - - - 268 - {{101, 61}, {285, 22}} - - - - _NS:9 - YES - - -1804599231 - 272630784 - - - _NS:9 - - YES - - - - NO - - - - 268 - {{7, 63}, {92, 17}} - - - - _NS:1505 - YES - - 68157504 - 71304192 - Package ID: - - _NS:1505 - - - - - NO - - - - 268 - {{458, 61}, {60, 22}} - - - - _NS:9 - YES - - -1804599231 - 272630784 - - - _NS:9 - - YES - - - - NO - - - - 268 - {{391, 63}, {62, 17}} - - - - _NS:1505 - YES - - 68157504 - 71304192 - Version: - - _NS:1505 - - - - - NO - - - {534, 459} - - - - - {{0, 0}, {1680, 1028}} - {534, 481} - {534, 481} - YES - - - NSApplication - - - - - - - window - - - - 18 - - - - fullName - - - - 100239 - - - - accountName - - - - 100240 - - - - password - - - - 100241 - - - - verifyPassword - - - - 100242 - - - - userID - - - - 100243 - - - - homeDirectory - - - - 100245 - - - - uuid - - - - 100246 - - - - packageID - - - - 100247 - - - - version - - - - 100248 - - - - docWindow - - - - 100252 - - - - didLeaveFullName: - - - - 100253 - - - - didLeaveAccountName: - - - - 100254 - - - - didLeavePassword: - - - - 100255 - - - - didLeavePassword: - - - - 100256 - - - - image - - - - 100260 - - - - automaticLogin - - - - 100273 - - - - accountType - - - - 100280 - - - - saveDocumentAs: - - - - 100257 - - - - delegate - - - - 17 - - - - - - 0 - - - - - - -2 - - - File's Owner - - - -1 - - - First Responder - - - 5 - - - - - - Window - - - 6 - - - - - - - - - - - - - -3 - - - Application - - - 100028 - - - - - - - - - - - - - - - - - - - - - - - - - 100029 - - - - - - - - 100030 - - - - - - - - 100031 - - - - - - - - 100032 - - - - - - - - 100033 - - - - - - - - 100035 - - - - - 100036 - - - - - 100037 - - - - - 100038 - - - - - 100039 - - - - - 100040 - - - - - - - - 100041 - - - - - - - - 100042 - - - - - - - - 100043 - - - - - - - - 100044 - - - - - - - - 100045 - - - - - - - - 100046 - - - - - - - - 100047 - - - - - - - - 100049 - - - - - - - - 100050 - - - - - - - - 100051 - - - - - - - - 100052 - - - - - - - - 100053 - - - - - - - - 100054 - - - - - - - - 100055 - - - - - - - - 100056 - - - - - 100057 - - - - - 100058 - - - - - 100059 - - - - - 100060 - - - - - 100061 - - - - - 100062 - - - - - 100064 - - - - - 100065 - - - - - 100066 - - - - - 100067 - - - - - 100068 - - - - - 100069 - - - - - 100070 - - - - - 100071 - - - - - 100258 - - - - - - - - 100259 - - - - - 100271 - - - - - - - - 100272 - - - - - 100274 - - - - - - - - 100275 - - - - - - - - 100276 - - - - - - - - - 100277 - - - - - 100278 - - - - - - - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - NSSecureTextField - com.apple.InterfaceBuilder.CocoaPlugin - NSSecureTextField - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - CUPImageSelector - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - - - com.apple.InterfaceBuilder.CocoaPlugin - {{133, 170}, {507, 413}} - com.apple.InterfaceBuilder.CocoaPlugin - - - - - - 100280 - - - - - CUPDocument - NSDocument - - id - id - id - - - - didLeaveAccountName: - id - - - didLeaveFullName: - id - - - didLeavePassword: - id - - - - NSTextField - NSPopUpButton - NSButton - NSWindow - NSTextField - NSTextField - CUPImageSelector - NSTextField - NSSecureTextField - NSTextField - NSTextField - NSSecureTextField - NSTextField - - - - accountName - NSTextField - - - accountType - NSPopUpButton - - - automaticLogin - NSButton - - - docWindow - NSWindow - - - fullName - NSTextField - - - homeDirectory - NSTextField - - - image - CUPImageSelector - - - packageID - NSTextField - - - password - NSSecureTextField - - - userID - NSTextField - - - uuid - NSTextField - - - verifyPassword - NSSecureTextField - - - version - NSTextField - - - - IBProjectSource - ./Classes/CUPDocument.h - - - - CUPImageSelector - NSImageView - - IBProjectSource - ./Classes/CUPImageSelector.h - - - - NSDocument - - id - id - id - id - id - id - - - - printDocument: - id - - - revertDocumentToSaved: - id - - - runPageLayout: - id - - - saveDocument: - id - - - saveDocumentAs: - id - - - saveDocumentTo: - id - - - - IBProjectSource - ./Classes/NSDocument.h - - - - - 0 - IBCocoaFramework - - com.apple.InterfaceBuilder.CocoaPlugin.macosx - - - YES - 3 - - {11, 11} - {10, 3} - {15, 15} - {64, 64} - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/CreateUserPkg/en.lproj/MainMenu.xib b/CreateUserPkg/en.lproj/MainMenu.xib index ceb47c7..94a6408 100644 --- a/CreateUserPkg/en.lproj/MainMenu.xib +++ b/CreateUserPkg/en.lproj/MainMenu.xib @@ -1,13 +1,13 @@ - - + + - - + + - + @@ -188,6 +188,6 @@ - + - \ No newline at end of file + diff --git a/CreateUserPkg/main.m b/CreateUserPkg/main.m index 99872f6..bc6eb00 100644 --- a/CreateUserPkg/main.m +++ b/CreateUserPkg/main.m @@ -8,7 +8,6 @@ #import -int main(int argc, char *argv[]) -{ - return NSApplicationMain(argc, (const char **)argv); +int main(int argc, char *argv[]) { + return NSApplicationMain(argc, (const char **)argv); }