/* sig-check.c - Check signatures
* Copyright (C) 1998-2003, 2007-2008, 2010, 2012 Free Software
* Foundation, Inc.
*
* Author: Timo Schulz
*
* This file is part of OpenCDK.
*
* The OpenCDK library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* as published by the Free Software Foundation; either version 3 of
* the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see
*
*/
#ifdef HAVE_CONFIG_H
#include
#endif
#include
#include
#include "opencdk.h"
#include "main.h"
#include "packet.h"
/* Hash all multi precision integers of the key PK with the given
message digest context MD. */
static int
hash_mpibuf (cdk_pubkey_t pk, digest_hd_st * md, int usefpr)
{
byte buf[MAX_MPI_BYTES]; /* FIXME: do not use hardcoded length. */
size_t nbytes;
size_t i, npkey;
int err;
/* We have to differ between two modes for v3 keys. To form the
fingerprint, we hash the MPI values without the length prefix.
But if we calculate the hash for verifying/signing we use all data. */
npkey = cdk_pk_get_npkey (pk->pubkey_algo);
for (i = 0; i < npkey; i++)
{
nbytes = MAX_MPI_BYTES;
err = _gnutls_mpi_print_pgp (pk->mpi[i], buf, &nbytes);
if (err < 0)
{
gnutls_assert ();
return map_gnutls_error (err);
}
if (!usefpr || pk->version == 4)
_gnutls_hash (md, buf, nbytes);
else /* without the prefix. */
_gnutls_hash (md, buf + 2, nbytes - 2);
}
return 0;
}
/* Hash an entire public key PK with the given message digest context
MD. The @usefpr param is only valid for version 3 keys because of
the different way to calculate the fingerprint. */
cdk_error_t
_cdk_hash_pubkey (cdk_pubkey_t pk, digest_hd_st * md, int usefpr)
{
byte buf[12];
size_t i, n, npkey;
if (!pk || !md)
return CDK_Inv_Value;
if (usefpr && pk->version < 4 && is_RSA (pk->pubkey_algo))
return hash_mpibuf (pk, md, 1);
/* The version 4 public key packet does not have the 2 octets for
the expiration date. */
n = pk->version < 4 ? 8 : 6;
npkey = cdk_pk_get_npkey (pk->pubkey_algo);
for (i = 0; i < npkey; i++)
n = n + (_gnutls_mpi_get_nbits (pk->mpi[i]) + 7) / 8 + 2;
i = 0;
buf[i++] = 0x99;
buf[i++] = n >> 8;
buf[i++] = n >> 0;
buf[i++] = pk->version;
buf[i++] = pk->timestamp >> 24;
buf[i++] = pk->timestamp >> 16;
buf[i++] = pk->timestamp >> 8;
buf[i++] = pk->timestamp >> 0;
if (pk->version < 4)
{
u16 a = 0;
/* Convert the expiration date into days. */
if (pk->expiredate)
a = (u16) ((pk->expiredate - pk->timestamp) / 86400L);
buf[i++] = a >> 8;
buf[i++] = a;
}
buf[i++] = pk->pubkey_algo;
_gnutls_hash (md, buf, i);
return hash_mpibuf (pk, md, 0);
}
/* Hash the user ID @uid with the given message digest @md.
Use openpgp mode if @is_v4 is 1. */
cdk_error_t
_cdk_hash_userid (cdk_pkt_userid_t uid, int is_v4, digest_hd_st * md)
{
const byte *data;
byte buf[5];
u32 dlen;
if (!uid || !md)
return CDK_Inv_Value;
if (!is_v4)
{
_gnutls_hash (md, (byte *) uid->name, uid->len);
return 0;
}
dlen = uid->attrib_img ? uid->attrib_len : uid->len;
data = uid->attrib_img ? uid->attrib_img : (byte *) uid->name;
buf[0] = uid->attrib_img ? 0xD1 : 0xB4;
buf[1] = dlen >> 24;
buf[2] = dlen >> 16;
buf[3] = dlen >> 8;
buf[4] = dlen >> 0;
_gnutls_hash (md, buf, 5);
_gnutls_hash (md, data, dlen);
return 0;
}
/* Hash all parts of the signature which are needed to derive
the correct message digest to verify the sig. */
cdk_error_t
_cdk_hash_sig_data (cdk_pkt_signature_t sig, digest_hd_st * md)
{
byte buf[4];
byte tmp;
if (!sig || !md)
return CDK_Inv_Value;
if (sig->version == 4)
_gnutls_hash (md, &sig->version, 1);
_gnutls_hash (md, &sig->sig_class, 1);
if (sig->version < 4)
{
buf[0] = sig->timestamp >> 24;
buf[1] = sig->timestamp >> 16;
buf[2] = sig->timestamp >> 8;
buf[3] = sig->timestamp >> 0;
_gnutls_hash (md, buf, 4);
}
else
{
size_t n;
tmp = _cdk_pub_algo_to_pgp (sig->pubkey_algo);
_gnutls_hash (md, &tmp, 1);
tmp = _gnutls_hash_algo_to_pgp (sig->digest_algo);
_gnutls_hash (md, &tmp, 1);
if (sig->hashed != NULL)
{
byte *p = _cdk_subpkt_get_array (sig->hashed, 0, &n);
assert (p != NULL);
buf[0] = n >> 8;
buf[1] = n >> 0;
_gnutls_hash (md, buf, 2);
_gnutls_hash (md, p, n);
cdk_free (p);
sig->hashed_size = n;
n = sig->hashed_size + 6;
}
else
{
tmp = 0x00;
_gnutls_hash (md, &tmp, 1);
_gnutls_hash (md, &tmp, 1);
n = 6;
}
_gnutls_hash (md, &sig->version, 1);
tmp = 0xff;
_gnutls_hash (md, &tmp, 1);
buf[0] = n >> 24;
buf[1] = n >> 16;
buf[2] = n >> 8;
buf[3] = n >> 0;
_gnutls_hash (md, buf, 4);
}
return 0;
}
/* Cache the signature result and store it inside the sig. */
static void
cache_sig_result (cdk_pkt_signature_t sig, int res)
{
sig->flags.checked = 0;
sig->flags.valid = 0;
if (res == 0)
{
sig->flags.checked = 1;
sig->flags.valid = 1;
}
else if (res == CDK_Bad_Sig)
{
sig->flags.checked = 1;
sig->flags.valid = 0;
}
}
/* Check the given signature @sig with the public key @pk.
Use the digest handle @digest. */
cdk_error_t
_cdk_sig_check (cdk_pubkey_t pk, cdk_pkt_signature_t sig,
digest_hd_st * digest, int *r_expired)
{
cdk_error_t rc;
byte md[MAX_DIGEST_LEN];
time_t cur_time = (u32) gnutls_time (NULL);
if (!pk || !sig || !digest)
{
gnutls_assert ();
return CDK_Inv_Value;
}
if (sig->flags.checked)
return sig->flags.valid ? 0 : CDK_Bad_Sig;
if (!KEY_CAN_SIGN (pk->pubkey_algo))
return CDK_Inv_Algo;
if (pk->timestamp > sig->timestamp || pk->timestamp > cur_time)
return CDK_Time_Conflict;
if (r_expired && pk->expiredate
&& (pk->expiredate + pk->timestamp) > cur_time)
*r_expired = 1;
_cdk_hash_sig_data (sig, digest);
_gnutls_hash_output (digest, md);
if (md[0] != sig->digest_start[0] || md[1] != sig->digest_start[1])
{
gnutls_assert ();
return CDK_Chksum_Error;
}
rc = cdk_pk_verify (pk, sig, md);
cache_sig_result (sig, rc);
return rc;
}
/* Check the given key signature.
@knode is the key node and @snode the signature node. */
cdk_error_t
_cdk_pk_check_sig (cdk_keydb_hd_t keydb,
cdk_kbnode_t knode, cdk_kbnode_t snode, int *is_selfsig,
char **ret_uid)
{
digest_hd_st md;
int err;
cdk_pubkey_t pk;
cdk_pkt_signature_t sig;
cdk_kbnode_t node;
cdk_error_t rc = 0;
int is_expired;
if (!knode || !snode)
{
gnutls_assert ();
return CDK_Inv_Value;
}
if (is_selfsig)
*is_selfsig = 0;
if ((knode->pkt->pkttype != CDK_PKT_PUBLIC_KEY &&
knode->pkt->pkttype != CDK_PKT_PUBLIC_SUBKEY) ||
snode->pkt->pkttype != CDK_PKT_SIGNATURE)
{
gnutls_assert ();
return CDK_Inv_Value;
}
pk = knode->pkt->pkt.public_key;
sig = snode->pkt->pkt.signature;
err = _gnutls_hash_init (&md, sig->digest_algo);
if (err < 0)
{
gnutls_assert ();
return map_gnutls_error (err);
}
is_expired = 0;
if (sig->sig_class == 0x20)
{ /* key revocation */
cdk_kbnode_hash (knode, &md, 0, 0, 0);
rc = _cdk_sig_check (pk, sig, &md, &is_expired);
}
else if (sig->sig_class == 0x28)
{ /* subkey revocation */
node = cdk_kbnode_find_prev (knode, snode, CDK_PKT_PUBLIC_SUBKEY);
if (!node)
{ /* no subkey for subkey revocation packet */
gnutls_assert ();
rc = CDK_Error_No_Key;
goto fail;
}
cdk_kbnode_hash (knode, &md, 0, 0, 0);
cdk_kbnode_hash (node, &md, 0, 0, 0);
rc = _cdk_sig_check (pk, sig, &md, &is_expired);
}
else if (sig->sig_class == 0x18 || sig->sig_class == 0x19)
{ /* primary/secondary key binding */
node = cdk_kbnode_find_prev (knode, snode, CDK_PKT_PUBLIC_SUBKEY);
if (!node)
{ /* no subkey for subkey binding packet */
gnutls_assert ();
rc = CDK_Error_No_Key;
goto fail;
}
cdk_kbnode_hash (knode, &md, 0, 0, 0);
cdk_kbnode_hash (node, &md, 0, 0, 0);
rc = _cdk_sig_check (pk, sig, &md, &is_expired);
}
else if (sig->sig_class == 0x1F)
{ /* direct key signature */
cdk_kbnode_hash (knode, &md, 0, 0, 0);
rc = _cdk_sig_check (pk, sig, &md, &is_expired);
}
else
{ /* all other classes */
cdk_pkt_userid_t uid;
node = cdk_kbnode_find_prev (knode, snode, CDK_PKT_USER_ID);
if (!node)
{ /* no user ID for key signature packet */
gnutls_assert ();
rc = CDK_Error_No_Key;
goto fail;
}
uid = node->pkt->pkt.user_id;
if (ret_uid)
{
*ret_uid = uid->name;
}
cdk_kbnode_hash (knode, &md, 0, 0, 0);
cdk_kbnode_hash (node, &md, sig->version == 4, 0, 0);
if (pk->keyid[0] == sig->keyid[0] && pk->keyid[1] == sig->keyid[1])
{
rc = _cdk_sig_check (pk, sig, &md, &is_expired);
if (is_selfsig)
*is_selfsig = 1;
}
else if (keydb != NULL)
{
cdk_pubkey_t sig_pk;
rc = cdk_keydb_get_pk (keydb, sig->keyid, &sig_pk);
if (!rc)
rc = _cdk_sig_check (sig_pk, sig, &md, &is_expired);
cdk_pk_release (sig_pk);
}
}
fail:
_gnutls_hash_deinit (&md, NULL);
return rc;
}
struct verify_uid
{
const char *name;
int nsigs;
struct verify_uid *next;
};
static int
uid_list_add_sig (struct verify_uid **list, const char *uid,
unsigned int flag)
{
if (*list == NULL)
{
*list = cdk_calloc (1, sizeof (struct verify_uid));
if (*list == NULL)
return CDK_Out_Of_Core;
(*list)->name = uid;
if (flag != 0)
(*list)->nsigs++;
}
else
{
struct verify_uid *p, *prev_p = NULL;
int found = 0;
p = *list;
while (p != NULL)
{
if (strcmp (uid, p->name) == 0)
{
found = 1;
break;
}
prev_p = p;
p = p->next;
}
if (found == 0)
{ /* not found add to the last */
prev_p->next = cdk_calloc (1, sizeof (struct verify_uid));
if (prev_p->next == NULL)
return CDK_Out_Of_Core;
prev_p->next->name = uid;
if (flag != 0)
prev_p->next->nsigs++;
}
else
{ /* found... increase sigs */
if (flag != 0)
p->nsigs++;
}
}
return CDK_Success;
}
static void
uid_list_free (struct verify_uid *list)
{
struct verify_uid *p, *p1;
p = list;
while (p != NULL)
{
p1 = p->next;
cdk_free (p);
p = p1;
}
}
/* returns non (0) if all UIDs in the list have at least one
* signature. If the list is empty or no signatures are present
* a (0) value is returned.
*/
static int
uid_list_all_signed (struct verify_uid *list)
{
struct verify_uid *p;
if (list == NULL)
return 0;
p = list;
while (p != NULL)
{
if (p->nsigs == 0)
{
return 0;
}
p = p->next;
}
return 1; /* all signed */
}
/**
* cdk_pk_check_sigs:
* @key: the public key
* @hd: an optinal key database handle
* @r_status: variable to store the status of the key
*
* Check all signatures. When no key is available for checking, the
* sigstat is marked as 'NOKEY'. The @r_status contains the key flags
* which are or-ed or (0) when there are no flags.
**/
cdk_error_t
cdk_pk_check_sigs (cdk_kbnode_t key, cdk_keydb_hd_t keydb, int *r_status)
{
cdk_pkt_signature_t sig;
cdk_kbnode_t node;
cdk_error_t rc;
u32 keyid;
int key_status, is_selfsig = 0;
struct verify_uid *uid_list = NULL;
char *uid_name;
if (!key || !r_status)
{
gnutls_assert ();
return CDK_Inv_Value;
}
*r_status = 0;
node = cdk_kbnode_find (key, CDK_PKT_PUBLIC_KEY);
if (!node)
{
gnutls_assert ();
return CDK_Error_No_Key;
}
key_status = 0;
/* Continue with the signature check but adjust the
key status flags accordingly. */
if (node->pkt->pkt.public_key->is_revoked)
key_status |= CDK_KEY_REVOKED;
if (node->pkt->pkt.public_key->has_expired)
key_status |= CDK_KEY_EXPIRED;
rc = 0;
keyid = cdk_pk_get_keyid (node->pkt->pkt.public_key, NULL);
for (node = key; node; node = node->next)
{
if (node->pkt->pkttype != CDK_PKT_SIGNATURE)
continue;
sig = node->pkt->pkt.signature;
rc = _cdk_pk_check_sig (keydb, key, node, &is_selfsig, &uid_name);
if (rc && rc != CDK_Error_No_Key)
{
/* It might be possible that a single signature has been
corrupted, thus we do not consider it a problem when
one ore more signatures are bad. But at least the self
signature has to be valid. */
if (is_selfsig)
{
key_status |= CDK_KEY_INVALID;
break;
}
}
_cdk_log_debug ("signature %s: signer %08X keyid %08X\n",
rc == CDK_Bad_Sig ? "BAD" : "good",
(unsigned int) sig->keyid[1], (unsigned int) keyid);
if (IS_UID_SIG (sig) && uid_name != NULL)
{
/* add every uid in the uid list. Only consider valid:
* - verification was ok
* - not a selfsig
*/
rc =
uid_list_add_sig (&uid_list, uid_name,
(rc == CDK_Success && is_selfsig == 0) ? 1 : 0);
if (rc != CDK_Success)
{
gnutls_assert ();
goto exit;
}
}
}
if (uid_list_all_signed (uid_list) == 0)
key_status |= CDK_KEY_NOSIGNER;
*r_status = key_status;
if (rc == CDK_Error_No_Key)
rc = 0;
exit:
uid_list_free (uid_list);
return rc;
}
/**
* cdk_pk_check_self_sig:
* @key: the key node
* @r_status: output the status of the key.
*
* A convenient function to make sure the key is valid.
* Valid means the self signature is ok.
**/
cdk_error_t
cdk_pk_check_self_sig (cdk_kbnode_t key, int *r_status)
{
cdk_pkt_signature_t sig;
cdk_kbnode_t node;
cdk_error_t rc;
u32 keyid[2], sigid[2];
int is_selfsig, sig_ok;
cdk_kbnode_t p, ctx = NULL;
cdk_packet_t pkt;
if (!key || !r_status)
return CDK_Inv_Value;
cdk_pk_get_keyid (key->pkt->pkt.public_key, keyid);
while ((p = cdk_kbnode_walk (key, &ctx, 0)))
{
pkt = cdk_kbnode_get_packet (p);
if (pkt->pkttype != CDK_PKT_PUBLIC_SUBKEY
&& pkt->pkttype != CDK_PKT_PUBLIC_KEY)
continue;
/* FIXME: we should set expire/revoke here also but callers
expect CDK_KEY_VALID=0 if the key is okay. */
sig_ok = 0;
for (node = p; node; node = node->next)
{
if (node->pkt->pkttype != CDK_PKT_SIGNATURE)
continue;
sig = node->pkt->pkt.signature;
cdk_sig_get_keyid (sig, sigid);
if (sigid[0] != keyid[0] || sigid[1] != keyid[1])
continue;
/* FIXME: Now we check all self signatures. */
rc = _cdk_pk_check_sig (NULL, p, node, &is_selfsig, NULL);
if (rc)
{
*r_status = CDK_KEY_INVALID;
return rc;
}
else /* For each valid self sig we increase this counter. */
sig_ok++;
}
/* A key without a self signature is not valid. At least one
* signature for the given key has to be found.
*/
if (!sig_ok)
{
*r_status = CDK_KEY_INVALID;
return CDK_General_Error;
}
}
/* No flags indicate a valid key. */
*r_status = CDK_KEY_VALID;
return 0;
}