diff --git a/common/errors.h b/common/errors.h index d968d70c8..42842afe7 100644 --- a/common/errors.h +++ b/common/errors.h @@ -92,6 +92,8 @@ enum { GNUPG_Card_Not_Present = 63, GNUPG_No_PKCS15_App = 64, GNUPG_Not_Confirmed = 65, + GNUPG_Configuration_Error = 66, + GNUPG_No_Policy_Match = 67, }; /* Status codes - fixme: should go into another file */ diff --git a/sm/ChangeLog b/sm/ChangeLog index 9f9aaa38f..167e88316 100644 --- a/sm/ChangeLog +++ b/sm/ChangeLog @@ -1,3 +1,11 @@ +2002-02-19 Werner Koch + + * certpath.c (check_cert_policy): New. + (gpgsm_validate_path): And call it from here. + * gpgsm.c (main): New options --policy-file, + --disable-policy-checks and --enable-policy-checks. + * gpgsm.h (opt): Added policy_file, no_policy_checks. + 2002-02-18 Werner Koch * certpath.c (gpgsm_validate_path): Ask the agent to add the diff --git a/sm/certchain.c b/sm/certchain.c index e76ff6c2f..0dac59acf 100644 --- a/sm/certchain.c +++ b/sm/certchain.c @@ -84,6 +84,114 @@ allowed_ca (KsbaCert cert, int *pathlen) return 0; } + +static int +check_cert_policy (KsbaCert cert) +{ + KsbaError err; + char *policies; + FILE *fp; + int any_critical; + + err = ksba_cert_get_cert_policies (cert, &policies); + if (err == KSBA_No_Data) + return 0; /* no policy given */ + if (err) + return map_ksba_err (err); + + /* STRING is a line delimited list of certifiate policies as stored + in the certificate. The line itself is colon delimted where the + first field is the OID of the policy and the second field either + N or C for normal or critical extension */ + + /* The check is very minimal but won't give false positives */ + any_critical = !!strstr (policies, ":C"); + + if (!opt.policy_file) + { + xfree (policies); + if (any_critical) + { + log_error ("critical marked policy without configured policies\n"); + return GNUPG_No_Policy_Match; + } + return 0; + } + + fp = fopen (opt.policy_file, "r"); + if (!fp) + { + log_error ("failed to open `%s': %s\n", + opt.policy_file, strerror (errno)); + xfree (policies); + return GNUPG_Configuration_Error; + } + + for (;;) + { + int c; + char *p, line[256]; + char *haystack, *allowed; + + /* read line */ + do + { + if (!fgets (line, DIM(line)-1, fp) ) + { + xfree (policies); + if (feof (fp)) + { + fclose (fp); + log_error (_("certificate policy not allowed\n")); + /* with no critical policies this is only a warning */ + return any_critical? GNUPG_No_Policy_Match : 0; + } + fclose (fp); + return GNUPG_Read_Error; + } + + if (!*line || line[strlen(line)-1] != '\n') + { + /* eat until end of line */ + while ( (c=getc (fp)) != EOF && c != '\n') + ; + fclose (fp); + xfree (policies); + return *line? GNUPG_Line_Too_Long: GNUPG_Incomplete_Line; + } + + /* Allow for empty lines and spaces */ + for (p=line; spacep (p); p++) + ; + } + while (!*p || *p == '\n' || *p == '#'); + + /* parse line */ + for (allowed=line; spacep (allowed); allowed++) + ; + p = strpbrk (allowed, " :\n"); + if (!*p || p == allowed) + { + fclose (fp); + xfree (policies); + return GNUPG_Configuration_Error; + } + *p = 0; /* strip the rest of the line */ + /* See whether we find ALLOWED (which is an OID) in POLICIES */ + for (haystack=policies; (p=strstr (haystack, allowed)); haystack = p+1) + { + if ( !(p == policies || p[-1] == '\n') ) + continue; /* does not match the begin of a line */ + if (p[strlen (allowed)] != ':') + continue; /* the length does not match */ + /* Yep - it does match so return okay */ + fclose (fp); + xfree (policies); + return 0; + } + } +} + /* Return the next certificate up in the chain starting at START. Returns -1 when there are no more certificates. */ int @@ -216,7 +324,14 @@ gpgsm_validate_path (KsbaCert cert) rc = unknown_criticals (subject_cert); if (rc) goto leave; - + + if (!opt.no_policy_check) + { + rc = check_cert_policy (subject_cert); + if (rc) + goto leave; + } + if (!opt.no_crl_check) { rc = gpgsm_dirmngr_isvalid (subject_cert); @@ -360,9 +475,10 @@ gpgsm_validate_path (KsbaCert cert) issuer_cert = NULL; } + if (opt.no_policy_check) + log_info ("policies not checked due to --disable-policy-checks option\n"); if (opt.no_crl_check) - log_info ("CRL was not checked due to --no-crl-cechk option\n"); - + log_info ("CRLs not checked due to --disable-crl-checks option\n"); leave: xfree (issuer); diff --git a/sm/certpath.c b/sm/certpath.c index e76ff6c2f..0dac59acf 100644 --- a/sm/certpath.c +++ b/sm/certpath.c @@ -84,6 +84,114 @@ allowed_ca (KsbaCert cert, int *pathlen) return 0; } + +static int +check_cert_policy (KsbaCert cert) +{ + KsbaError err; + char *policies; + FILE *fp; + int any_critical; + + err = ksba_cert_get_cert_policies (cert, &policies); + if (err == KSBA_No_Data) + return 0; /* no policy given */ + if (err) + return map_ksba_err (err); + + /* STRING is a line delimited list of certifiate policies as stored + in the certificate. The line itself is colon delimted where the + first field is the OID of the policy and the second field either + N or C for normal or critical extension */ + + /* The check is very minimal but won't give false positives */ + any_critical = !!strstr (policies, ":C"); + + if (!opt.policy_file) + { + xfree (policies); + if (any_critical) + { + log_error ("critical marked policy without configured policies\n"); + return GNUPG_No_Policy_Match; + } + return 0; + } + + fp = fopen (opt.policy_file, "r"); + if (!fp) + { + log_error ("failed to open `%s': %s\n", + opt.policy_file, strerror (errno)); + xfree (policies); + return GNUPG_Configuration_Error; + } + + for (;;) + { + int c; + char *p, line[256]; + char *haystack, *allowed; + + /* read line */ + do + { + if (!fgets (line, DIM(line)-1, fp) ) + { + xfree (policies); + if (feof (fp)) + { + fclose (fp); + log_error (_("certificate policy not allowed\n")); + /* with no critical policies this is only a warning */ + return any_critical? GNUPG_No_Policy_Match : 0; + } + fclose (fp); + return GNUPG_Read_Error; + } + + if (!*line || line[strlen(line)-1] != '\n') + { + /* eat until end of line */ + while ( (c=getc (fp)) != EOF && c != '\n') + ; + fclose (fp); + xfree (policies); + return *line? GNUPG_Line_Too_Long: GNUPG_Incomplete_Line; + } + + /* Allow for empty lines and spaces */ + for (p=line; spacep (p); p++) + ; + } + while (!*p || *p == '\n' || *p == '#'); + + /* parse line */ + for (allowed=line; spacep (allowed); allowed++) + ; + p = strpbrk (allowed, " :\n"); + if (!*p || p == allowed) + { + fclose (fp); + xfree (policies); + return GNUPG_Configuration_Error; + } + *p = 0; /* strip the rest of the line */ + /* See whether we find ALLOWED (which is an OID) in POLICIES */ + for (haystack=policies; (p=strstr (haystack, allowed)); haystack = p+1) + { + if ( !(p == policies || p[-1] == '\n') ) + continue; /* does not match the begin of a line */ + if (p[strlen (allowed)] != ':') + continue; /* the length does not match */ + /* Yep - it does match so return okay */ + fclose (fp); + xfree (policies); + return 0; + } + } +} + /* Return the next certificate up in the chain starting at START. Returns -1 when there are no more certificates. */ int @@ -216,7 +324,14 @@ gpgsm_validate_path (KsbaCert cert) rc = unknown_criticals (subject_cert); if (rc) goto leave; - + + if (!opt.no_policy_check) + { + rc = check_cert_policy (subject_cert); + if (rc) + goto leave; + } + if (!opt.no_crl_check) { rc = gpgsm_dirmngr_isvalid (subject_cert); @@ -360,9 +475,10 @@ gpgsm_validate_path (KsbaCert cert) issuer_cert = NULL; } + if (opt.no_policy_check) + log_info ("policies not checked due to --disable-policy-checks option\n"); if (opt.no_crl_check) - log_info ("CRL was not checked due to --no-crl-cechk option\n"); - + log_info ("CRLs not checked due to --disable-crl-checks option\n"); leave: xfree (issuer); diff --git a/sm/gpgsm.c b/sm/gpgsm.c index cc86f053c..9927357ec 100644 --- a/sm/gpgsm.c +++ b/sm/gpgsm.c @@ -100,10 +100,9 @@ enum cmd_and_opt_values { oEnableCRLChecks, oIncludeCerts, - - - - + oPolicyFile, + oDisablePolicyChecks, + oEnablePolicyChecks, @@ -242,6 +241,12 @@ static ARGPARSE_OPTS opts[] = { { oIncludeCerts, "include-certs", 1, N_("|N|number of certificates to include") }, + { oPolicyFile, "policy-file", 2, + N_("|FILE|take policy information from FILE") }, + + { oDisablePolicyChecks, "disable-policy-checks", 0, + N_("do not check certificate policies")}, + { oEnablePolicyChecks, "enable-policy-checks", 0, "@"}, #if 0 { oDefRecipient, "default-recipient" ,2, @@ -654,6 +659,8 @@ main ( int argc, char **argv) /* set the default option file */ if (default_config ) configname = make_filename (opt.homedir, "gpgsm.conf", NULL); + /* cet the default policy file */ + opt.policy_file = make_filename (opt.homedir, "policies.txt", NULL); argc = orig_argc; argv = orig_argv; @@ -759,6 +766,22 @@ main ( int argc, char **argv) case oIncludeCerts: ctrl.include_certs = pargs.r.ret_int; break; + case oPolicyFile: + xfree (opt.policy_file); + if (*pargs.r.ret_str) + opt.policy_file = xstrdup (pargs.r.ret_str); + else + opt.policy_file = NULL; + break; + + case oDisablePolicyChecks: + opt.no_policy_check = 1; + break; + case oEnablePolicyChecks: + opt.no_policy_check = 0; + break; + + case oOutput: opt.outfile = pargs.r.ret_str; break; diff --git a/sm/gpgsm.h b/sm/gpgsm.h index fdb68441e..ab2d3660d 100644 --- a/sm/gpgsm.h +++ b/sm/gpgsm.h @@ -66,7 +66,10 @@ struct { int ignore_time_conflict; /* Ignore certain time conflicts */ - int no_crl_check; /* Don't do a CRL check */ + int no_crl_check; /* Don't do a CRL check */ + + char *policy_file; /* full pathname of policy file */ + int no_policy_check; /* ignore certificate policies */ } opt; @@ -208,6 +211,7 @@ int gpgsm_agent_pkdecrypt (const char *keygrip, int gpgsm_agent_genkey (KsbaConstSexp keyparms, KsbaSexp *r_pubkey); int gpgsm_agent_istrusted (KsbaCert cert); int gpgsm_agent_havekey (const char *hexkeygrip); +int gpgsm_agent_marktrusted (KsbaCert cert); /*-- call-dirmngr.c --*/ int gpgsm_dirmngr_isvalid (KsbaCert cert);