/* silcauth.c Author: Pekka Riikonen Copyright (C) 2001 - 2003 Pekka Riikonen The contents of this file are subject to one of the Licenses specified in the COPYING file; You may not use this file except in compliance with the License. The software distributed under the License is distributed on an "AS IS" basis, in the hope that it will be useful, but WITHOUT WARRANTY OF ANY KIND, either expressed or implied. See the COPYING file for more information. */ /* $Id: silcauth.c,v 1.28.2.4 2003/10/03 08:00:49 priikone Exp $ */ #include "silcincludes.h" #include "silcauth.h" /****************************************************************************** Authentication Payload ******************************************************************************/ /* Authentication Payload structure */ struct SilcAuthPayloadStruct { SilcUInt16 len; SilcUInt16 auth_method; SilcUInt16 random_len; unsigned char *random_data; SilcUInt16 auth_len; unsigned char *auth_data; }; /* Parses and returns Authentication Payload */ SilcAuthPayload silc_auth_payload_parse(const unsigned char *data, SilcUInt32 data_len) { SilcBufferStruct buffer; SilcAuthPayload newp; int ret; SILC_LOG_DEBUG(("Parsing Authentication Payload")); silc_buffer_set(&buffer, (unsigned char *)data, data_len); newp = silc_calloc(1, sizeof(*newp)); if (!newp) return NULL; /* Parse the payload */ ret = silc_buffer_unformat(&buffer, SILC_STR_UI_SHORT(&newp->len), SILC_STR_UI_SHORT(&newp->auth_method), SILC_STR_UI16_NSTRING_ALLOC(&newp->random_data, &newp->random_len), SILC_STR_UI16_NSTRING_ALLOC(&newp->auth_data, &newp->auth_len), SILC_STR_END); if (ret == -1) { silc_free(newp); return NULL; } if (newp->len != buffer.len || newp->random_len + newp->auth_len > buffer.len - 8) { silc_auth_payload_free(newp); return NULL; } /* Authentication data must be provided */ if (newp->auth_len < 1) { silc_auth_payload_free(newp); return NULL; } /* If password authentication, random data must not be set */ if (newp->auth_method == SILC_AUTH_PASSWORD && newp->random_len) { silc_auth_payload_free(newp); return NULL; } /* If public key authentication, random data must be at least 128 bytes */ if (newp->auth_method == SILC_AUTH_PUBLIC_KEY && newp->random_len < 128) { silc_auth_payload_free(newp); return NULL; } return newp; } /* Encodes authentication payload into buffer and returns it */ SilcBuffer silc_auth_payload_encode(SilcAuthMethod method, const unsigned char *random_data, SilcUInt16 random_len, const unsigned char *auth_data, SilcUInt16 auth_len) { SilcBuffer buffer; SilcUInt32 len; unsigned char *autf8 = NULL; SilcUInt32 autf8_len; SILC_LOG_DEBUG(("Encoding Authentication Payload")); /* Passphrase MUST be UTF-8 encoded, encode if it is not */ if (method == SILC_AUTH_PASSWORD && !silc_utf8_valid(auth_data, auth_len)) { autf8_len = silc_utf8_encoded_len(auth_data, auth_len, 0); if (!autf8_len) return NULL; autf8 = silc_calloc(autf8_len, sizeof(*autf8)); auth_len = silc_utf8_encode(auth_data, auth_len, 0, autf8, autf8_len); auth_data = (const unsigned char *)autf8; } len = 2 + 2 + 2 + random_len + 2 + auth_len; buffer = silc_buffer_alloc_size(len); if (!buffer) { silc_free(autf8); return NULL; } silc_buffer_format(buffer, SILC_STR_UI_SHORT(len), SILC_STR_UI_SHORT(method), SILC_STR_UI_SHORT(random_len), SILC_STR_UI_XNSTRING(random_data, random_len), SILC_STR_UI_SHORT(auth_len), SILC_STR_UI_XNSTRING(auth_data, auth_len), SILC_STR_END); silc_free(autf8); return buffer; } /* Frees authentication payload. */ void silc_auth_payload_free(SilcAuthPayload payload) { if (payload) { if (payload->random_data) { memset(payload->random_data, 0, payload->random_len); silc_free(payload->random_data); } if (payload->auth_data) { memset(payload->auth_data, 0, payload->auth_len); silc_free(payload->auth_data); } silc_free(payload); } } /* Get authentication method */ SilcAuthMethod silc_auth_get_method(SilcAuthPayload payload) { return payload->auth_method; } /* Get the public data from the auth payload. */ unsigned char *silc_auth_get_public_data(SilcAuthPayload payload, SilcUInt32 *pubdata_len) { if (pubdata_len) *pubdata_len = (SilcUInt32)payload->random_len; return payload->random_data; } /* Get the authentication data. If this is passphrase it is UTF-8 encoded. */ unsigned char *silc_auth_get_data(SilcAuthPayload payload, SilcUInt32 *auth_len) { if (auth_len) *auth_len = (SilcUInt32)payload->auth_len; return payload->auth_data; } /****************************************************************************** Authentication Routines ******************************************************************************/ /* Encodes the authentication data for hashing and signing as the protocol dictates. */ static unsigned char * silc_auth_public_key_encode_data(SilcPublicKey public_key, const unsigned char *randomdata, SilcUInt32 random_len, const void *id, SilcIdType type, SilcUInt32 *ret_len) { SilcBuffer buf; unsigned char *pk, *id_data, *ret; SilcUInt32 pk_len, id_len; pk = silc_pkcs_public_key_encode(public_key, &pk_len); if (!pk) return NULL; id_data = silc_id_id2str(id, type); if (!id_data) { silc_free(pk); return NULL; } id_len = silc_id_get_len(id, type); buf = silc_buffer_alloc_size(random_len + id_len + pk_len); if (!buf) { silc_free(pk); silc_free(id_data); return NULL; } silc_buffer_format(buf, SILC_STR_UI_XNSTRING(randomdata, random_len), SILC_STR_UI_XNSTRING(id_data, id_len), SILC_STR_UI_XNSTRING(pk, pk_len), SILC_STR_END); ret = silc_buffer_steal(buf, ret_len); silc_buffer_free(buf); silc_free(id_data); silc_free(pk); return ret; } /* Generates Authentication Payload with authentication data. This is used to do public key based authentication. This generates the random data and the actual authentication data. Returns NULL on error. */ SilcBuffer silc_auth_public_key_auth_generate(SilcPublicKey public_key, SilcPrivateKey private_key, SilcRng rng, SilcHash hash, const void *id, SilcIdType type) { unsigned char *randomdata; SilcBuffer buf; /* Get 256 bytes of random data */ if (rng) randomdata = silc_rng_get_rn_data(rng, 256); else randomdata = silc_rng_global_get_rn_data(256); if (!randomdata) return NULL; buf = silc_auth_public_key_auth_generate_wpub(public_key, private_key, randomdata, 256, hash, id, type); memset(randomdata, 0, 256); silc_free(randomdata); return buf; } /* Generates Authentication Payload with authentication data. This is used to do public key based authentication. This generates the random data and the actual authentication data. Returns NULL on error. */ SilcBuffer silc_auth_public_key_auth_generate_wpub(SilcPublicKey public_key, SilcPrivateKey private_key, const unsigned char *pubdata, SilcUInt32 pubdata_len, SilcHash hash, const void *id, SilcIdType type) { unsigned char auth_data[2048 + 1]; SilcUInt32 auth_len; unsigned char *tmp; SilcUInt32 tmp_len; SilcBuffer buf; SilcPKCS pkcs; SILC_LOG_DEBUG(("Generating Authentication Payload with data")); /* Encode the auth data */ tmp = silc_auth_public_key_encode_data(public_key, pubdata, pubdata_len, id, type, &tmp_len); if (!tmp) return NULL; /* Allocate PKCS object */ if (!silc_pkcs_alloc(private_key->name, &pkcs)) { memset(tmp, 0, tmp_len); silc_free(tmp); return NULL; } silc_pkcs_public_key_set(pkcs, public_key); silc_pkcs_private_key_set(pkcs, private_key); /* Compute the hash and the signature. */ if (silc_pkcs_get_key_len(pkcs) / 8 > sizeof(auth_data) - 1 || !silc_pkcs_sign_with_hash(pkcs, hash, tmp, tmp_len, auth_data, &auth_len)) { memset(tmp, 0, tmp_len); silc_free(tmp); silc_pkcs_free(pkcs); return NULL; } /* Encode Authentication Payload */ buf = silc_auth_payload_encode(SILC_AUTH_PUBLIC_KEY, pubdata, pubdata_len, auth_data, auth_len); memset(tmp, 0, tmp_len); memset(auth_data, 0, sizeof(auth_data)); silc_free(tmp); silc_pkcs_free(pkcs); return buf; } /* Verifies the authentication data. Returns TRUE if authentication was successful. */ bool silc_auth_public_key_auth_verify(SilcAuthPayload payload, SilcPublicKey public_key, SilcHash hash, const void *id, SilcIdType type) { unsigned char *tmp; SilcUInt32 tmp_len; SilcPKCS pkcs; SILC_LOG_DEBUG(("Verifying authentication data")); /* Encode auth data */ tmp = silc_auth_public_key_encode_data(public_key, payload->random_data, payload->random_len, id, type, &tmp_len); if (!tmp) { SILC_LOG_DEBUG(("Authentication failed")); return FALSE; } /* Allocate PKCS object */ if (!silc_pkcs_alloc(public_key->name, &pkcs)) { memset(tmp, 0, tmp_len); silc_free(tmp); return FALSE; } silc_pkcs_public_key_set(pkcs, public_key); /* Verify the authentication data */ if (!silc_pkcs_verify_with_hash(pkcs, hash, payload->auth_data, payload->auth_len, tmp, tmp_len)) { memset(tmp, 0, tmp_len); silc_free(tmp); silc_pkcs_free(pkcs); SILC_LOG_DEBUG(("Authentication failed")); return FALSE; } memset(tmp, 0, tmp_len); silc_free(tmp); silc_pkcs_free(pkcs); SILC_LOG_DEBUG(("Authentication successful")); return TRUE; } /* Same as above but the payload is not parsed yet. This will parse it. */ bool silc_auth_public_key_auth_verify_data(const unsigned char *payload, SilcUInt32 payload_len, SilcPublicKey public_key, SilcHash hash, const void *id, SilcIdType type) { SilcAuthPayload auth_payload; int ret; auth_payload = silc_auth_payload_parse(payload, payload_len); if (!auth_payload) { SILC_LOG_DEBUG(("Authentication failed")); return FALSE; } ret = silc_auth_public_key_auth_verify(auth_payload, public_key, hash, id, type); silc_auth_payload_free(auth_payload); return ret; } /* Verifies the authentication data directly from the Authentication Payload. Supports all authentication methods. If the authentication method is passphrase based then the `auth_data' and `auth_data_len' are the passphrase and its length. If the method is public key authentication then the `auth_data' is the SilcPublicKey and the `auth_data_len' is ignored. */ bool silc_auth_verify(SilcAuthPayload payload, SilcAuthMethod auth_method, const void *auth_data, SilcUInt32 auth_data_len, SilcHash hash, const void *id, SilcIdType type) { SILC_LOG_DEBUG(("Verifying authentication")); if (!payload || auth_method != payload->auth_method) return FALSE; switch (payload->auth_method) { case SILC_AUTH_NONE: /* No authentication */ SILC_LOG_DEBUG(("No authentication required")); return TRUE; case SILC_AUTH_PASSWORD: /* Passphrase based authentication. The `pkcs', `hash', `id' and `type' arguments are not needed. */ /* Sanity checks */ if ((payload->auth_len == 0) || !auth_data || payload->auth_len != auth_data_len) break; if (!memcmp(payload->auth_data, auth_data, auth_data_len)) { SILC_LOG_DEBUG(("Passphrase Authentication successful")); return TRUE; } break; case SILC_AUTH_PUBLIC_KEY: /* Public key based authentication */ return silc_auth_public_key_auth_verify(payload, (SilcPublicKey)auth_data, hash, id, type); break; default: break; } SILC_LOG_DEBUG(("Authentication failed")); return FALSE; } /* Same as above but parses the authentication payload before verify. */ bool silc_auth_verify_data(const unsigned char *payload, SilcUInt32 payload_len, SilcAuthMethod auth_method, const void *auth_data, SilcUInt32 auth_data_len, SilcHash hash, const void *id, SilcIdType type) { SilcAuthPayload auth_payload; bool ret; auth_payload = silc_auth_payload_parse(payload, payload_len); if (!auth_payload || (auth_payload->auth_len == 0)) return FALSE; ret = silc_auth_verify(auth_payload, auth_method, auth_data, auth_data_len, hash, id, type); silc_auth_payload_free(auth_payload); return ret; } /****************************************************************************** Key Agreement Payload ******************************************************************************/ /* The Key Agreement protocol structure */ struct SilcKeyAgreementPayloadStruct { SilcUInt16 hostname_len; unsigned char *hostname; SilcUInt32 port; }; /* Parses and returns an allocated Key Agreement payload. */ SilcKeyAgreementPayload silc_key_agreement_payload_parse(const unsigned char *payload, SilcUInt32 payload_len) { SilcBufferStruct buffer; SilcKeyAgreementPayload newp; int ret; SILC_LOG_DEBUG(("Parsing Key Agreement Payload")); silc_buffer_set(&buffer, (unsigned char *)payload, payload_len); newp = silc_calloc(1, sizeof(*newp)); if (!newp) return NULL; /* Parse the payload */ ret = silc_buffer_unformat(&buffer, SILC_STR_UI16_NSTRING_ALLOC(&newp->hostname, &newp->hostname_len), SILC_STR_UI_INT(&newp->port), SILC_STR_END); if (ret == -1 || newp->hostname_len > buffer.len - 6) { silc_free(newp); return NULL; } return newp; } /* Encodes the Key Agreement protocol and returns the encoded buffer */ SilcBuffer silc_key_agreement_payload_encode(const char *hostname, SilcUInt32 port) { SilcBuffer buffer; SilcUInt32 len = hostname ? strlen(hostname) : 0; SILC_LOG_DEBUG(("Encoding Key Agreement Payload")); buffer = silc_buffer_alloc_size(2 + len + 4); if (!buffer) return NULL; silc_buffer_format(buffer, SILC_STR_UI_SHORT(len), SILC_STR_UI_XNSTRING(hostname, len), SILC_STR_UI_INT(port), SILC_STR_END); return buffer; } /* Frees the Key Agreement protocol */ void silc_key_agreement_payload_free(SilcKeyAgreementPayload payload) { if (payload) { silc_free(payload->hostname); silc_free(payload); } } /* Returns the hostname in the payload */ char *silc_key_agreement_get_hostname(SilcKeyAgreementPayload payload) { return payload->hostname; } /* Returns the port in the payload */ SilcUInt32 silc_key_agreement_get_port(SilcKeyAgreementPayload payload) { return payload->port; }