Improve key entry code

This commit is contained in:
Richard Frith-Macdonald 2018-03-02 11:04:25 +00:00
parent 8c2e4d44ce
commit 5967901af8
8 changed files with 793 additions and 553 deletions

View file

@ -1,3 +1,14 @@
2018-03-02 Richard Frith-Macdonald <rfm@gnu.org>
* Control.m:
* EcControl.m:
* EcProcess.h:
* EcProcess.m:
Allow EcControlKey to be an MD5 digest of the expected key, so we can
easily tell if the expected key was entered correctly.
Expose method to request entry of a hexadecimal key, with checking
against an MD5 digest of the expected value.
2018-02-23 Richard Frith-Macdonald <rfm@gnu.org>
* AlarmTool.m: Add default values for arguments, suitable for sending

194
Control.m
View file

@ -32,144 +32,12 @@
#import "EcProcess.h"
#include <unistd.h>
#include <termios.h>
#include <stdio.h>
static size_t
trim(char *str)
{
size_t len = 0;
char *frontp = str - 1;
char *endp = NULL;
if (NULL == str || '\0' == str[0])
{
return 0;
}
len = strlen(str);
endp = str + len;
while (isspace(*(++frontp)))
;
while (isspace(*(--endp)) && endp != frontp)
;
if (str + len - 1 != endp)
{
*(endp + 1) = '\0';
}
else if (frontp != str && endp == frontp)
{
*str = '\0';
}
if (frontp != str)
{
endp = str;
while (*frontp)
{
*endp++ = *frontp++;
}
*endp = '\0';
}
return endp - str;
}
static BOOL
getkey(NSString **key)
{
FILE *stream;
struct termios old;
struct termios new;
char *one = NULL;
char *two = NULL;
#define MINKEY 16
/* Open the terminal
*/
if ((stream = fopen("/dev/tty", "r+")) == NULL)
{
return NO;
}
/* Turn echoing off
*/
if (tcgetattr(fileno(stream), &old) != 0)
{
fclose(stream);
return NO;
}
new = old;
new.c_lflag &= ~ECHO;
if (tcsetattr (fileno(stream), TCSAFLUSH, &new) != 0)
{
fclose(stream);
return NO;
}
while (NULL == one || NULL == two)
{
int olen = 0;
int tlen = 0;
while (olen < MINKEY)
{
size_t len = 0;
fprintf(stream, "\nPlease enter EcControlKey: ");
if (one != NULL) { free(one); one = NULL; }
olen = getline(&one, &len, stream);
if (olen < 0)
{
if (one != NULL) { free(one); one = NULL; }
fclose(stream);
return NO;
}
olen = trim(one);
if (olen < MINKEY)
{
fprintf(stream, "\nKey must be at least %u characters\n", MINKEY);
}
}
while (0 == tlen)
{
size_t len = 0;
fprintf(stream, "\nPlease re-enter to confirm: ");
if (two != NULL) { free(two); two = NULL; }
tlen = getline(&two, &len, stream);
if (tlen < 0)
{
if (one != NULL) { free(one); one = NULL; }
if (two != NULL) { free(two); two = NULL; }
fclose(stream);
return NO;
}
tlen = trim(two);
}
if (strcmp(one, two) != 0)
{
free(one); one = NULL;
free(two); two = NULL;
fprintf(stream, "\nKeys do not match, please try again.");
}
}
/* Restore terminal. */
(void) tcsetattr(fileno(stream), TCSAFLUSH, &old);
*key = [NSString stringWithUTF8String: one];
free(one);
free(two);
fprintf(stream, "\nEcControlKey accepted.\n");
fclose(stream);
return YES;
}
#if !defined(EC_DEFAULTS_PREFIX)
#define EC_DEFAULTS_PREFIX nil
#endif
#if !defined(EC_BASE_CLASS)
#define EC_BASE_CLASS EcControl
@ -203,6 +71,7 @@ main(int argc, char *argv[])
NSString *path = [[NSBundle mainBundle] executablePath];
NSAutoreleasePool *inner = nil;
BOOL done = NO;
NSUInteger index = NSNotFound;
int status = 0;
NSTask *t;
@ -210,20 +79,43 @@ main(int argc, char *argv[])
if ([pArgs containsObject: @"--Watcher"] == NO)
{
/* In the top level task ... set flags to create a subtask
* to act as a watcher for other tasks, and once that has
* been created, exit to leave it running as a daemon.
*/
if ([[NSUserDefaults standardUserDefaults] boolForKey:
@"EcControlKey"] == YES)
NSUserDefaults *defs;
NSString *str;
defs = [NSUserDefaults standardUserDefaults];
str = [defs stringForKey: @"EcControlKey"];
if ([str length] == 32 || [str boolValue] == YES)
{
if (getkey(&key) == NO)
NSData *digest = nil;
if ([str length] == 32)
{
/* Check that the 32 character value is hex digits,
* in which case it should be the MD5 digest of the
* actual key to be entered.
*/
digest = AUTORELEASE([[NSData alloc]
initWithHexadecimalRepresentation: str]);
if ([digest length] != 16)
{
NSLog(@"Bad values specified in EcControlKey... abort");
exit(1);
}
}
key = [EcProcess ecGetKey: "EcControlKey"
size: 32
md5: digest];
if (nil == key)
{
NSLog(@"Failed to read EcControlKey from terminal ... abort");
exit(1);
}
}
/* In the top level task ... set flags to create a subtask
* to act as a watcher for other tasks, and once that has
* been created, exit to leave it running as a daemon.
*/
[args addObject: @"--Watcher"];
t = [NSTask new];
NS_DURING
@ -267,8 +159,26 @@ main(int argc, char *argv[])
/* Set args to tell subtask task not to make itself a daemon
*/
if (EC_DEFAULTS_PREFIX != nil)
{
NSString *str;
str = [NSString stringWithFormat: @"-%@Daemon", EC_DEFAULTS_PREFIX];
index = [args indexOfObject: str];
}
if (NSNotFound == index)
{
index = [args indexOfObject: @"-Daemon"];
}
if (NSNotFound != index)
{
[args replaceObjectAtIndex: index + 1 withObject: @"NO"];
}
else
{
[args addObject: @"-Daemon"];
[args addObject: @"NO"];
}
/* Set args to tell task it is being watched.
*/

View file

@ -2758,6 +2758,7 @@ static NSString* cmdWord(NSArray* a, unsigned int pos)
NSMutableDictionary *root;
NSEnumerator *rootEnum;
id hostKey;
NSString *digest = nil;
if ([conf isKindOfClass: [NSDictionary class]] == NO)
{
@ -2775,6 +2776,50 @@ static NSString* cmdWord(NSArray* a, unsigned int pos)
* applications/classes levels of the configuration.
*/
conf = [self recursiveInclude: conf];
/* Get the EcControlKey from the generic area of the configuration.
* If present, this should be an MD5 digest of the actual key.
*/
if ([[conf objectForKey: @"*"] isKindOfClass: [NSDictionary class]]
&& [[[conf objectForKey: @"*"] objectForKey: @"*"]
isKindOfClass: [NSDictionary class]])
{
digest = [[[conf objectForKey: @"*"] objectForKey: @"*"]
objectForKey: @"EcControlKey"];
}
if ([controlKey length] > 0 && nil == digest)
{
ASSIGN(configFailed,
@"EcControlKey supplied on startup but not in Control.plist");
[[self cmdLogFile: logname] printf: @"%@", configFailed];
return NO;
}
if ([controlKey length] == 0 && digest != nil)
{
ASSIGN(configFailed,
@"EcControlKey configured but no value supplied on startup");
[[self cmdLogFile: logname] printf: @"%@", configFailed];
return NO;
}
if (digest != nil)
{
NSData *key;
NSData *md5;
NSString *hex;
key = [[NSData alloc] initWithHexadecimalRepresentation: controlKey];
md5 = [key md5Digest];
RELEASE(key);
hex = [md5 hexadecimalRepresentation];
if (NO == [digest isEqual: hex])
{
ASSIGN(configFailed,
@"EcControlKey is not the MD5 digest of value from startup");
[[self cmdLogFile: logname] printf: @"%@", configFailed];
return NO;
}
}
[conf writeToFile: @"/tmp/Control.cnf" atomically: YES];
[mgr changeFileAttributes: [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithInt: 0666], NSFilePosixPermissions,

View file

@ -182,6 +182,7 @@ typedef enum {
* Useful functions -
*/
extern void cmdSetHome(NSString *home);
extern NSString* cmdDataDir();
extern NSString* cmdLogsDir(NSString *date);
extern NSString* cmdLogKey(EcLogType t);
extern NSString* cmdLogName();
@ -408,6 +409,23 @@ extern NSString* cmdVersion(NSString *ver);
*/
@interface EcProcess : NSObject <CmdClient,EcAlarmDestination>
/** This method is provided to prompt for an encryptionkey using the
* specified key name and read in a value from the terminal.<br />
* The entered value must be an even numbered sequence of hexadecimal
* digits, each pair representing one byte of the key.<br />
* The key length (number of bytes) must be the specified size, a value
* between 16 and 128, which is exactly half the number of hexadecimal
* digits that must be entered.<br />
* If the digest is not supplied, the user will be required to
* enter the value twice (and the two values must match) for
* confirmation.<br />
* If the digest is supplied, the md5 digest of the entered key must
* match it (but the user does not need to enter the value twice).
*/
+ (NSString*) ecGetKey: (const char*)name
size: (unsigned)size
md5: (NSData*)digest;
/** Provides initial configuration.
* This method is used by -init and its return value is passed to
* -initWithDefaults: method.<br />

File diff suppressed because it is too large Load diff

View file

@ -94,6 +94,9 @@
/* Define to 1 if you have <sys/wait.h> that is POSIX.1 compatible. */
#undef HAVE_SYS_WAIT_H
/* Define to 1 if you have the <termios.h> header file. */
#undef HAVE_TERMIOS_H
/* Define to 1 if you have the <unistd.h> header file. */
#undef HAVE_UNISTD_H

2
configure vendored
View file

@ -3581,7 +3581,7 @@ fi
done
for ac_header in arpa/inet.h arpa/telnet.h netinet/in.h netdb.h pwd.h string.h fcntl.h sys/fcntl.h sys/file.h sys/resource.h sys/time.h sys/types.h sys/socket.h sys/signal.h stdlib.h unistd.h
for ac_header in arpa/inet.h arpa/telnet.h netinet/in.h netdb.h pwd.h string.h fcntl.h sys/fcntl.h sys/file.h sys/resource.h sys/time.h sys/types.h sys/socket.h sys/signal.h stdlib.h unistd.h termios.h
do :
as_ac_Header=`$as_echo "ac_cv_header_$ac_header" | $as_tr_sh`
ac_fn_c_check_header_mongrel "$LINENO" "$ac_header" "$as_ac_Header" "$ac_includes_default"

View file

@ -35,7 +35,7 @@ AC_TYPE_SIGNAL
AC_HEADER_STDC
AC_HEADER_TIME
AC_HEADER_SYS_WAIT
AC_CHECK_HEADERS(arpa/inet.h arpa/telnet.h netinet/in.h netdb.h pwd.h string.h fcntl.h sys/fcntl.h sys/file.h sys/resource.h sys/time.h sys/types.h sys/socket.h sys/signal.h stdlib.h unistd.h)
AC_CHECK_HEADERS(arpa/inet.h arpa/telnet.h netinet/in.h netdb.h pwd.h string.h fcntl.h sys/fcntl.h sys/file.h sys/resource.h sys/time.h sys/types.h sys/socket.h sys/signal.h stdlib.h unistd.h termios.h)
AC_TYPE_GETGROUPS
AC_TYPE_SIGNAL