diff --git a/NEWS b/NEWS index ae91562f5..ac2723890 100644 --- a/NEWS +++ b/NEWS @@ -1,6 +1,8 @@ Noteworthy changes in the current test release ---------------------------------------------- + * Add an experimental feature to do unattended key generation. + * The user is now asked for the reason of revocation as required by the new OpenPGP draft. diff --git a/doc/ChangeLog b/doc/ChangeLog index 06008a3aa..bdd535e5c 100644 --- a/doc/ChangeLog +++ b/doc/ChangeLog @@ -1,3 +1,7 @@ +2000-03-09 15:01:51 Werner Koch (wk@habibti.openit.de) + + * DETAILS: Ad a short blurb about unattended key generation. + Wed Feb 9 15:33:44 CET 2000 Werner Koch * gpg.sgml: Describe --ignore-time-conflict. diff --git a/doc/DETAILS b/doc/DETAILS index 0ab83ecdf..37475c6b5 100644 --- a/doc/DETAILS +++ b/doc/DETAILS @@ -231,6 +231,107 @@ Key generation Crypto '97 proceedings p. 260. +Unattended key generation +========================= +There is an experimental feature which allows for unattended +generation of keys controlled by a parameter file. +This feature is not very well tested and does only make sense for some +very special applications. Please don't complain if we decide to chnage +the behaviour of this command. + +To use this feature, you use --gen-key together with --batch and feed the +parameters either form stdin or from a file given on the commandline. +The format of this file is as follows: + o Text only, line length is limited to about 1000 chars. + o You must use UTF-8 encoding to specifiy non-ascii characters. + o Empty lines are ignored + o Leading and trailing spaces are ignored + o A hash sign as the first non white space character indicates a comment line + o Control statements are indicated by a leading percent sign, the + arguments are separated by white space from the keyword. + o Parameters are specified by a keyword, followed by a colon. Arguments + are speparated by white space. + o The first parameter must be "Key-Type", control statements + may be placed anywhere. + o Key generation takes place when either the end of the parameter file + is reached, the next "Key-Type" parameter is encountered or at the + controlstatement "%commit" + o Control staements: + %echo + Print + %dry-run + Suppress actual key generation (useful for syntax checking) + %commit + Perform the key generation. An implicit commit is done + at the next "Key-Type" parameter. + %pubring + %secring + Do not write the key to the default or commandline given + keyring but to . This must be given before the first + commit to take place, duplicate specification of the same filename + is ignored, the last filename before a commit is used. + The filename is used until a new filename is used (at commit points) + and all keys are written to that file. If a new filename is given, + this file is created (and overwrites an existing one). + Both control statements must be given. + o The order of the parameters does not matter except for "Key-Type" + which must be the first parameter. The paramtyers are only for the + generated keyblock and paramters from previous key generations are not + used. Some syntactically checks may be performed. + The currently defined parameters are: + Key-Type: | + Starts a new parameter block by giving the type of the + primary key. The algorithm must be capable of signing. + This is a required parameter. + Key-Length: + Length of the key in bits. Default is 1024 + Subkey-Type: | + This generates a secondary key. Currently only one subkey + can be handled. + Subkey-Length: + Length of the subkey in bits. Default is 1024. + Passphrase: + If you want to specify a passphrase for the secret key, + enter it here. Default is not to use any passphrase. + Name-Real: + Name-Comment: + Name-Email: + The 3 parts of a key. Remember to use UTF-8 here. + If you don't give any of them, no user ID is created. + Expire-Date: |([d|w|m|y]) + Set the expiration date for the key (and the subkey). It + may either be entered in ISO date format (2000-08-15) or as + number of days, weeks, month or years. Without a letter days + are assumed. + +Here is an example: +$ cat >foo < +ssb 1024g/8F70E2C0 2000-03-09 + + Layout of the TrustDB ===================== diff --git a/doc/HACKING b/doc/HACKING index 6f4c9ffd8..2f4de27d3 100644 --- a/doc/HACKING +++ b/doc/HACKING @@ -10,12 +10,12 @@ CVS Access ========== Anonymous read-only CVS access is available: - cvs -z6 -d :pserver:anonymous@ftp.guug.de:/home/koch/cvs login + cvs -z6 -d :pserver:anonymous@cvs.guug.de:/home/koch/cvs login use the password "anonymous". To check out the the complete archive use: - cvs -z6 -d :pserver:anonymous@ftp.guug.de:/home/koch/cvs checkout gnupg + cvs -z6 -d :pserver:anonymous@cvs.guug.de:/home/koch/cvs checkout gnupg This service is provided to help you in hunting bugs and not to deliver stable snapshots; it may happen that it even does not compile, so please diff --git a/doc/gpg.sgml b/doc/gpg.sgml index c20bfb173..51a0f28b3 100644 --- a/doc/gpg.sgml +++ b/doc/gpg.sgml @@ -235,8 +235,13 @@ useful for debugging. --gen-key -Generate a new key pair. This command can only be -used interactive. +Generate a new key pair. This command is normally only used +interactive. + + +There is an experimental feature which allows to create keys +in batch mode. See the file doc/DETAILS +in the source distribution on how to use this. diff --git a/g10/ChangeLog b/g10/ChangeLog index 3b21ace0c..c8d90540f 100644 --- a/g10/ChangeLog +++ b/g10/ChangeLog @@ -1,3 +1,11 @@ +2000-03-09 12:53:09 Werner Koch (wk@habibti.openit.de) + + * keygen.c (ask_expire_interval): Movede parsig to ... + (parse_expire_string): ... this new function. And some new control + commands. + (proc_parameter_file): Add expire date parsing. + (do_generate_keypair): Allow the use of specified output files. + 2000-03-08 10:38:38 Werner Koch (wk@habibti.openit.de) * keygen.c (ask_algo): Removed is_v4 return value and the commented diff --git a/g10/export.c b/g10/export.c index 106caa7d7..be72e89d8 100644 --- a/g10/export.c +++ b/g10/export.c @@ -201,7 +201,7 @@ do_export_stream( IOBUF out, STRLIST users, int secret, int onlyrfc, int *any ) if( secret == 2 && node->pkt->pkttype == PKT_SECRET_KEY ) { /* we don't want to export the secret parts of the - * primary key, this is doen by using GNU protection mode 1001 + * primary key, this is done by using GNU protection mode 1001 */ int save_mode = node->pkt->pkt.secret_key->protect.s2k.mode; node->pkt->pkt.secret_key->protect.s2k.mode = 1001; diff --git a/g10/g10.c b/g10/g10.c index 38cd4bbec..f5eb0dd14 100644 --- a/g10/g10.c +++ b/g10/g10.c @@ -1,5 +1,5 @@ /* g10.c - The GnuPG utility (main for gpg) - * Copyright (C) 1998, 1999 Free Software Foundation, Inc. + * Copyright (C) 1998, 1999, 2000 Free Software Foundation, Inc. * * This file is part of GnuPG. * diff --git a/g10/keygen.c b/g10/keygen.c index 2ed5a70e4..ead51cbc2 100644 --- a/g10/keygen.c +++ b/g10/keygen.c @@ -1,5 +1,5 @@ /* keygen.c - generate a key pair - * Copyright (C) 1998, 1999 Free Software Foundation, Inc. + * Copyright (C) 1998, 1999, 2000 Free Software Foundation, Inc. * * This file is part of GnuPG. * @@ -59,12 +59,33 @@ struct para_data_s { union { DEK *dek; STRING2KEY *s2k; + u32 expire; char value[1]; } u; }; +struct output_control_s { + int lnr; + int dryrun; + int use_files; + struct { + char *fname; + char *newfname; + IOBUF stream; + armor_filter_context_t afx; + } pub; + struct { + char *fname; + char *newfname; + IOBUF stream; + armor_filter_context_t afx; + } sec; +}; -static void do_generate_keypair( struct para_data_s *para ); + +static void do_generate_keypair( struct para_data_s *para, + struct output_control_s *outctrl ); +static int write_keyblock( IOBUF out, KBNODE node ); static void @@ -537,6 +558,41 @@ ask_keysize( int algo ) } +/**************** + * Parse an expire string and return it's value in days. + * Returns -1 on error. + */ +static int +parse_expire_string( const char *string ) +{ + int mult; + u32 abs_date=0; + u32 curtime = make_timestamp(); + int valid_days; + + if( !*string ) + valid_days = 0; + else if( (abs_date = scan_isodatestr(string)) && abs_date > curtime ) { + /* This calculation is not perfectly okay because we + * are later going to simply multiply by 86400 and don't + * correct for leapseconds. A solution would be to change + * the whole implemenation to work with dates and not intervals + * which are required for v3 keys. + */ + valid_days = abs_date/86400-curtime/86400+1; + } + else if( (mult=check_valid_days(string)) ) { + valid_days = atoi(string) * mult; + if( valid_days < 0 || valid_days > 39447 ) + valid_days = 0; + } + else { + valid_days = -1; + } + return valid_days; +} + + static u32 ask_expire_interval(void) { @@ -556,32 +612,14 @@ ask_expire_interval(void) answer = NULL; for(;;) { - int mult; - u32 abs_date=0; - u32 curtime=0;; + u32 curtime=make_timestamp(); m_free(answer); answer = cpr_get("keygen.valid",_("Key is valid for? (0) ")); cpr_kill_prompt(); trim_spaces(answer); - curtime = make_timestamp(); - if( !*answer ) - valid_days = 0; - else if( (abs_date = scan_isodatestr(answer)) && abs_date > curtime ) { - /* This calculation is not perfectly okay because we - * are later going to simply multiply by 86400 and don't - * correct for leapseconds. A solution would be to change - * the whole implemenation to work with dates and not intervals - * which are required for v3 keys. - */ - valid_days = abs_date/86400-curtime/86400+1; - } - else if( (mult=check_valid_days(answer)) ) { - valid_days = atoi(answer) * mult; - if( valid_days < 0 || valid_days > 39447 ) - valid_days = 0; - } - else { + valid_days = parse_expire_string( answer ); + if( valid_days < 0 ) { tty_printf(_("invalid value\n")); continue; } @@ -924,6 +962,9 @@ get_parameter_u32( struct para_data_s *para, enum para_name key ) if( !r ) return 0; + if( r->key == pKEYEXPIRE || r->key == pSUBKEYEXPIRE ) + return r->u.expire; + return (unsigned int)strtoul( r->u.value, NULL, 10 ); } @@ -949,7 +990,8 @@ get_parameter_s2k( struct para_data_s *para, enum para_name key ) static int -proc_parameter_file( struct para_data_s *para, const char *fname ) +proc_parameter_file( struct para_data_s *para, const char *fname, + struct output_control_s *outctrl ) { struct para_data_s *r; const char *s1, *s2, *s3; @@ -994,6 +1036,7 @@ proc_parameter_file( struct para_data_s *para, const char *fname ) } } + /* make DEK and S2K from the Passphrase */ r = get_parameter( para, pPASSPHRASE ); if( r && *r->u.value ) { /* we have a plain text passphrase - create a DEK from it. @@ -1022,7 +1065,31 @@ proc_parameter_file( struct para_data_s *para, const char *fname ) r->next = para; para = r; } - do_generate_keypair( para ); + + /* make KEYEXPIRE from Expire-Date */ + r = get_parameter( para, pEXPIREDATE ); + if( r && *r->u.value ) { + i = parse_expire_string( r->u.value ); + if( i < 0 ) { + log_error("%s:%d: invalid expire date\n", fname, r->lnr ); + return -1; + } + r->u.expire = i * 86400L; + r->key = pKEYEXPIRE; /* change hat entry */ + /* also set it for the subkey */ + r = m_alloc_clear( sizeof *r + 20 ); + r->key = pSUBKEYEXPIRE; + r->u.expire = i * 86400L; + r->next = para; + para = r; + } + + if( !!outctrl->pub.newfname ^ !!outctrl->sec.newfname ) { + log_error("%s:%d: only one ring name is set\n", fname, outctrl->lnr ); + return -1; + } + + do_generate_keypair( para, outctrl ); return 0; } @@ -1055,6 +1122,9 @@ read_parameter_file( const char *fname ) const char *err = NULL; struct para_data_s *para, *r; int i; + struct output_control_s outctrl; + + memset( &outctrl, 0, sizeof( outctrl ) ); if( !fname || !*fname || !strcmp(fname,"-") ) { fp = stdin; @@ -1085,8 +1155,46 @@ read_parameter_file( const char *fname ) continue; keyword = p; if( *keyword == '%' ) { - /* for now these are all comments but in future they may be used - * to control certain aspects */ + for( ; !isspace(*p); p++ ) + ; + if( *p ) + *p++ = 0; + for( ; isspace(*p); p++ ) + ; + value = p; + trim_trailing_ws( value, strlen(value) ); + if( !stricmp( keyword, "%echo" ) ) + log_info("%s\n", value ); + else if( !stricmp( keyword, "%dry-run" ) ) + outctrl.dryrun = 1; + else if( !stricmp( keyword, "%commit" ) ) { + outctrl.lnr = lnr; + proc_parameter_file( para, fname, &outctrl ); + release_parameter_list( para ); + para = NULL; + } + else if( !stricmp( keyword, "%pubring" ) ) { + if( outctrl.pub.fname && !strcmp( outctrl.pub.fname, value ) ) + ; /* still the same file - ignore it */ + else { + m_free( outctrl.pub.newfname ); + outctrl.pub.newfname = m_strdup( value ); + outctrl.use_files = 1; + } + } + else if( !stricmp( keyword, "%secring" ) ) { + if( outctrl.sec.fname && !strcmp( outctrl.sec.fname, value ) ) + ; /* still the same file - ignore it */ + else { + m_free( outctrl.sec.newfname ); + outctrl.sec.newfname = m_strdup( value ); + outctrl.use_files = 1; + } + } + else + log_info("skipping control `%s' (%s)\n", keyword, value ); + + continue; } @@ -1095,7 +1203,8 @@ read_parameter_file( const char *fname ) err = "missing colon"; break; } - *p++ = 0; + if( *p ) + *p++ = 0; for( ; isspace(*p); p++ ) ; if( !*p ) { @@ -1119,7 +1228,8 @@ read_parameter_file( const char *fname ) } if( keywords[i].key == pKEYTYPE && para ) { - proc_parameter_file( para, fname ); + outctrl.lnr = lnr; + proc_parameter_file( para, fname, &outctrl ); release_parameter_list( para ); para = NULL; } @@ -1146,8 +1256,19 @@ read_parameter_file( const char *fname ) log_error("%s:%d: read error: %s\n", fname, lnr, strerror(errno) ); } else if( para ) { - proc_parameter_file( para, fname ); + outctrl.lnr = lnr; + proc_parameter_file( para, fname, &outctrl ); } + + if( outctrl.use_files ) { /* close open streams */ + iobuf_close( outctrl.pub.stream ); + iobuf_close( outctrl.sec.stream ); + m_free( outctrl.pub.fname ); + m_free( outctrl.pub.newfname ); + m_free( outctrl.sec.fname ); + m_free( outctrl.sec.newfname ); + } + release_parameter_list( para ); if( strcmp( fname, "-" ) ) fclose(fp); @@ -1170,6 +1291,9 @@ generate_keypair( const char *fname ) u32 expire; struct para_data_s *para = NULL; struct para_data_s *r; + struct output_control_s outctrl; + + memset( &outctrl, 0, sizeof( outctrl ) ); if( opt.batch ) { read_parameter_file( fname ); @@ -1216,16 +1340,14 @@ generate_keypair( const char *fname ) expire = ask_expire_interval(); r = m_alloc_clear( sizeof *r + 20 ); r->key = pKEYEXPIRE; - sprintf( r->u.value, "%lu", (ulong)expire ); + r->u.expire = expire; + r->next = para; + para = r; + r = m_alloc_clear( sizeof *r + 20 ); + r->key = pSUBKEYEXPIRE; + r->u.expire = expire; r->next = para; para = r; - if( both ) { - r = m_alloc_clear( sizeof *r + 20 ); - r->key = pSUBKEYEXPIRE; - sprintf( r->u.value, "%lu", (ulong)expire ); - r->next = para; - para = r; - } uid = ask_user_id(0); if( !uid ) { @@ -1253,13 +1375,14 @@ generate_keypair( const char *fname ) para = r; } - proc_parameter_file( para, "[internal]" ); + proc_parameter_file( para, "[internal]", &outctrl ); release_parameter_list( para ); } static void -do_generate_keypair( struct para_data_s *para ) +do_generate_keypair( struct para_data_s *para, + struct output_control_s *outctrl ) { char *pub_fname = NULL; char *sec_fname = NULL; @@ -1269,12 +1392,68 @@ do_generate_keypair( struct para_data_s *para ) const char *s; int rc; - /* check whether we are allowed to write to the keyrings */ - pub_fname = make_filename(opt.homedir, "pubring.gpg", NULL ); - sec_fname = make_filename(opt.homedir, "secring.gpg", NULL ); + if( outctrl->dryrun ) { + log_info("dry-run mode - key generation skipped\n"); + return; + } + + + if( outctrl->use_files ) { + if( outctrl->pub.newfname ) { + iobuf_close(outctrl->pub.stream); + outctrl->pub.stream = NULL; + m_free( outctrl->pub.fname ); + outctrl->pub.fname = outctrl->pub.newfname; + outctrl->pub.newfname = NULL; + + outctrl->pub.stream = iobuf_create( outctrl->pub.fname ); + if( !outctrl->pub.stream ) { + log_error("can't create `%s': %s\n", outctrl->pub.newfname, + strerror(errno) ); + return; + } + if( opt.armor ) { + outctrl->pub.afx.what = 1; + iobuf_push_filter( outctrl->pub.stream, armor_filter, + &outctrl->pub.afx ); + } + } + if( outctrl->sec.newfname ) { + iobuf_close(outctrl->sec.stream); + outctrl->sec.stream = NULL; + m_free( outctrl->sec.fname ); + outctrl->sec.fname = outctrl->sec.newfname; + outctrl->sec.newfname = NULL; + + outctrl->sec.stream = iobuf_create( outctrl->sec.fname ); + if( !outctrl->sec.stream ) { + log_error("can't create `%s': %s\n", outctrl->sec.newfname, + strerror(errno) ); + return; + } + if( opt.armor ) { + outctrl->sec.afx.what = 5; + iobuf_push_filter( outctrl->sec.stream, armor_filter, + &outctrl->sec.afx ); + } + } + pub_fname = outctrl->pub.fname; /* only for info output */ + sec_fname = outctrl->sec.fname; /* only for info output */ + assert( outctrl->pub.stream ); + assert( outctrl->sec.stream ); + } + else { + /* check whether we are allowed to write to the keyrings */ + /* It is probably wrong to use the default names here + * but becuase I never gpt any complaints, we better leave + * it as it is. */ + pub_fname = make_filename(opt.homedir, "pubring.gpg", NULL ); + sec_fname = make_filename(opt.homedir, "secring.gpg", NULL ); + } + if( opt.verbose ) { - log_info(_("writing public certificate to `%s'\n"), pub_fname ); - log_info(_("writing secret certificate to `%s'\n"), sec_fname ); + log_info(_("writing public key to `%s'\n"), pub_fname ); + log_info(_("writing secret key to `%s'\n"), sec_fname ); } /* we create the packets as a tree of kbnodes. Because the structure @@ -1317,7 +1496,18 @@ do_generate_keypair( struct para_data_s *para ) } - if( !rc ) { + if( !rc && outctrl->use_files ) { /* direct write to specified files */ + rc = write_keyblock( outctrl->pub.stream, pub_root ); + if( rc ) + log_error("can't write public key: %s\n", g10_errstr(rc) ); + if( !rc ) { + rc = write_keyblock( outctrl->sec.stream, sec_root ); + if( rc ) + log_error("can't write secret key: %s\n", g10_errstr(rc) ); + } + + } + else if( !rc ) { /* write to the standard keyrings */ KBPOS pub_kbpos; KBPOS sec_kbpos; int rc1 = -1; @@ -1377,10 +1567,9 @@ do_generate_keypair( struct para_data_s *para ) unlock_keyblock( &sec_kbpos ); } - if( rc ) { if( opt.batch ) - log_error(_("Key generation failed: %s\n"), g10_errstr(rc) ); + log_error("key generation failed: %s\n", g10_errstr(rc) ); else tty_printf(_("Key generation failed: %s\n"), g10_errstr(rc) ); } @@ -1388,8 +1577,10 @@ do_generate_keypair( struct para_data_s *para ) release_kbnode( sec_root ); if( sk ) /* the unprotected secret key */ free_secret_key(sk); - m_free(pub_fname); - m_free(sec_fname); + if( !outctrl->use_files ) { + m_free(pub_fname); + m_free(sec_fname); + } } @@ -1491,3 +1682,20 @@ generate_subkeypair( KBNODE pub_keyblock, KBNODE sec_keyblock ) return okay; } +/**************** + * Write a keyblock to an output stream + */ +static int +write_keyblock( IOBUF out, KBNODE node ) +{ + for( ; node ; node = node->next ) { + int rc = build_packet( out, node->pkt ); + if( rc ) { + log_error("build_packet(%d) failed: %s\n", + node->pkt->pkttype, g10_errstr(rc) ); + return G10ERR_WRITE_FILE; + } + } + return 0; +} + diff --git a/util/ChangeLog b/util/ChangeLog index 4fdc43f7e..4106b075d 100644 --- a/util/ChangeLog +++ b/util/ChangeLog @@ -1,3 +1,7 @@ +2000-03-09 14:04:22 Werner Koch (wk@habibti.openit.de) + + * argparse.c (default_strusage): Changed year of default copyright. + Tue Mar 7 18:45:31 CET 2000 Werner Koch * secmem.c (lock_pool): No more warning for QNX. By Sam Roberts. diff --git a/util/argparse.c b/util/argparse.c index c6f405f62..929d62a79 100644 --- a/util/argparse.c +++ b/util/argparse.c @@ -892,7 +892,7 @@ default_strusage( int level ) switch( level ) { case 11: p = "foo"; break; case 13: p = "0.0"; break; - case 14: p = "Copyright (C) 1999 Free Software Foundation, Inc."; break; + case 14: p = "Copyright (C) 2000 Free Software Foundation, Inc."; break; case 15: p = "This program comes with ABSOLUTELY NO WARRANTY.\n" "This is free software, and you are welcome to redistribute it\n"