1165 lines
31 KiB
C
1165 lines
31 KiB
C
/* $OpenBSD: ike_auth.c,v 1.118 2020/07/07 17:33:40 tobhe Exp $ */
|
|
/* $EOM: ike_auth.c,v 1.59 2000/11/21 00:21:31 angelos Exp $ */
|
|
|
|
/*
|
|
* Copyright (c) 1998, 1999, 2000, 2001 Niklas Hallqvist. All rights reserved.
|
|
* Copyright (c) 1999 Niels Provos. All rights reserved.
|
|
* Copyright (c) 1999 Angelos D. Keromytis. All rights reserved.
|
|
* Copyright (c) 2000, 2001, 2003 Håkan Olsson. All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
|
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
|
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
|
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
|
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
|
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
|
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
*/
|
|
|
|
/*
|
|
* This code was written under funding by Ericsson Radio Systems.
|
|
*/
|
|
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
#include <netinet/in.h>
|
|
#include <arpa/inet.h>
|
|
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
#include <regex.h>
|
|
#include <keynote.h>
|
|
#include <policy.h>
|
|
|
|
#include "cert.h"
|
|
#include "conf.h"
|
|
#include "constants.h"
|
|
#if defined (USE_DNSSEC)
|
|
#include "dnssec.h"
|
|
#endif
|
|
#include "exchange.h"
|
|
#include "hash.h"
|
|
#include "ike_auth.h"
|
|
#include "ipsec.h"
|
|
#include "ipsec_doi.h"
|
|
#include "libcrypto.h"
|
|
#include "log.h"
|
|
#include "message.h"
|
|
#include "monitor.h"
|
|
#include "prf.h"
|
|
#include "transport.h"
|
|
#include "util.h"
|
|
#include "key.h"
|
|
#include "x509.h"
|
|
|
|
#ifdef notyet
|
|
static u_int8_t *enc_gen_skeyid(struct exchange *, size_t *);
|
|
#endif
|
|
static u_int8_t *pre_shared_gen_skeyid(struct exchange *, size_t *);
|
|
|
|
static int pre_shared_decode_hash(struct message *);
|
|
static int pre_shared_encode_hash(struct message *);
|
|
|
|
static u_int8_t *sig_gen_skeyid(struct exchange *, size_t *);
|
|
static int rsa_sig_decode_hash(struct message *);
|
|
static int rsa_sig_encode_hash(struct message *);
|
|
|
|
static int get_raw_key_from_file(int, u_int8_t *, size_t, RSA **);
|
|
|
|
static int ike_auth_hash(struct exchange *, u_int8_t *);
|
|
|
|
static struct ike_auth ike_auth[] = {
|
|
{
|
|
IKE_AUTH_PRE_SHARED, pre_shared_gen_skeyid,
|
|
pre_shared_decode_hash,
|
|
pre_shared_encode_hash
|
|
},
|
|
#ifdef notdef
|
|
{
|
|
IKE_AUTH_DSS, sig_gen_skeyid,
|
|
pre_shared_decode_hash,
|
|
pre_shared_encode_hash
|
|
},
|
|
#endif
|
|
{
|
|
IKE_AUTH_RSA_SIG, sig_gen_skeyid,
|
|
rsa_sig_decode_hash,
|
|
rsa_sig_encode_hash
|
|
},
|
|
#ifdef notdef
|
|
{
|
|
IKE_AUTH_RSA_ENC, enc_gen_skeyid,
|
|
pre_shared_decode_hash,
|
|
pre_shared_encode_hash
|
|
},
|
|
{
|
|
IKE_AUTH_RSA_ENC_REV, enc_gen_skeyid,
|
|
pre_shared_decode_hash,
|
|
pre_shared_encode_hash
|
|
},
|
|
#endif
|
|
};
|
|
|
|
struct ike_auth *
|
|
ike_auth_get(u_int16_t id)
|
|
{
|
|
size_t i;
|
|
|
|
for (i = 0; i < sizeof ike_auth / sizeof ike_auth[0]; i++)
|
|
if (id == ike_auth[i].id)
|
|
return &ike_auth[i];
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Find and decode the configured key (pre-shared or public) for the
|
|
* peer denoted by ID. Stash the len in KEYLEN.
|
|
*/
|
|
static void *
|
|
ike_auth_get_key(int type, char *id, char *local_id, size_t *keylen)
|
|
{
|
|
char *key, *buf;
|
|
char *keyfile, *privkeyfile;
|
|
FILE *keyfp;
|
|
RSA *rsakey;
|
|
size_t fsize, pkflen;
|
|
int fd;
|
|
|
|
switch (type) {
|
|
case IKE_AUTH_PRE_SHARED:
|
|
/* Get the pre-shared key for our peer. */
|
|
key = conf_get_str(id, "Authentication");
|
|
if (!key && local_id)
|
|
key = conf_get_str(local_id, "Authentication");
|
|
|
|
if (!key) {
|
|
log_print("ike_auth_get_key: "
|
|
"no key found for peer \"%s\" or local ID \"%s\"",
|
|
id, local_id ? local_id : "<none>");
|
|
return 0;
|
|
}
|
|
/* If the key starts with 0x it is in hex format. */
|
|
if (strncasecmp(key, "0x", 2) == 0) {
|
|
*keylen = (strlen(key) - 1) / 2;
|
|
buf = malloc(*keylen);
|
|
if (!buf) {
|
|
log_error("ike_auth_get_key: malloc (%lu) "
|
|
"failed", (unsigned long)*keylen);
|
|
return 0;
|
|
}
|
|
if (hex2raw(key + 2, (unsigned char *)buf, *keylen)) {
|
|
free(buf);
|
|
log_print("ike_auth_get_key: invalid hex key "
|
|
"%s", key);
|
|
return 0;
|
|
}
|
|
key = buf;
|
|
} else {
|
|
buf = key;
|
|
key = strdup(buf);
|
|
if (!key) {
|
|
log_error("ike_auth_get_key: strdup() failed");
|
|
return 0;
|
|
}
|
|
*keylen = strlen(key);
|
|
}
|
|
break;
|
|
|
|
case IKE_AUTH_RSA_SIG:
|
|
if (local_id && (keyfile = conf_get_str("KeyNote",
|
|
"Credential-directory")) != 0) {
|
|
struct stat sb;
|
|
struct keynote_deckey dc;
|
|
char *privkeyfile, *buf2;
|
|
size_t size;
|
|
|
|
if (asprintf(&privkeyfile, "%s/%s/%s", keyfile,
|
|
local_id, PRIVATE_KEY_FILE) == -1) {
|
|
log_print("ike_auth_get_key: failed to asprintf()");
|
|
return 0;
|
|
}
|
|
keyfile = privkeyfile;
|
|
|
|
fd = monitor_open(keyfile, O_RDONLY, 0);
|
|
if (fd < 0) {
|
|
free(keyfile);
|
|
goto ignorekeynote;
|
|
}
|
|
|
|
if (fstat(fd, &sb) == -1) {
|
|
log_print("ike_auth_get_key: fstat failed");
|
|
free(keyfile);
|
|
close(fd);
|
|
return 0;
|
|
}
|
|
size = (size_t)sb.st_size;
|
|
|
|
buf = calloc(size + 1, sizeof(char));
|
|
if (!buf) {
|
|
log_print("ike_auth_get_key: failed allocating"
|
|
" %lu bytes", (unsigned long)size + 1);
|
|
free(keyfile);
|
|
close(fd);
|
|
return 0;
|
|
}
|
|
if (read(fd, buf, size) != (ssize_t)size) {
|
|
free(buf);
|
|
log_print("ike_auth_get_key: "
|
|
"failed reading %lu bytes from \"%s\"",
|
|
(unsigned long)size, keyfile);
|
|
free(keyfile);
|
|
close(fd);
|
|
return 0;
|
|
}
|
|
close(fd);
|
|
|
|
/* Parse private key string */
|
|
buf2 = kn_get_string(buf);
|
|
free(buf);
|
|
|
|
if (!buf2 || kn_decode_key(&dc, buf2,
|
|
KEYNOTE_PRIVATE_KEY) == -1) {
|
|
free(buf2);
|
|
log_print("ike_auth_get_key: failed decoding "
|
|
"key in \"%s\"", keyfile);
|
|
free(keyfile);
|
|
return 0;
|
|
}
|
|
free(buf2);
|
|
|
|
if (dc.dec_algorithm != KEYNOTE_ALGORITHM_RSA) {
|
|
log_print("ike_auth_get_key: wrong algorithm "
|
|
"type %d in \"%s\"", dc.dec_algorithm,
|
|
keyfile);
|
|
free(keyfile);
|
|
kn_free_key(&dc);
|
|
return 0;
|
|
}
|
|
free(keyfile);
|
|
return dc.dec_key;
|
|
}
|
|
ignorekeynote:
|
|
/* Otherwise, try X.509 */
|
|
|
|
privkeyfile = keyfile = NULL;
|
|
fd = -1;
|
|
|
|
if (local_id) {
|
|
/* Look in Private-key-directory. */
|
|
keyfile = conf_get_str("X509-certificates",
|
|
"Private-key-directory");
|
|
pkflen = strlen(keyfile) + strlen(local_id) + sizeof "/";
|
|
privkeyfile = calloc(pkflen, sizeof(char));
|
|
if (!privkeyfile) {
|
|
log_print("ike_auth_get_key: failed to "
|
|
"allocate %lu bytes", (unsigned long)pkflen);
|
|
return 0;
|
|
}
|
|
|
|
snprintf(privkeyfile, pkflen, "%s/%s", keyfile,
|
|
local_id);
|
|
keyfile = privkeyfile;
|
|
|
|
fd = monitor_open(keyfile, O_RDONLY, 0);
|
|
if (fd == -1 && errno != ENOENT) {
|
|
log_print("ike_auth_get_key: failed opening "
|
|
"\"%s\"", keyfile);
|
|
free(privkeyfile);
|
|
privkeyfile = NULL;
|
|
keyfile = NULL;
|
|
}
|
|
}
|
|
|
|
if (fd == -1) {
|
|
/* No key found, try default key. */
|
|
keyfile = conf_get_str("X509-certificates",
|
|
"Private-key");
|
|
|
|
fd = monitor_open(keyfile, O_RDONLY, 0);
|
|
if (fd == -1) {
|
|
log_print("ike_auth_get_key: failed opening "
|
|
"\"%s\"", keyfile);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
if (check_file_secrecy_fd(fd, keyfile, &fsize)) {
|
|
free(privkeyfile);
|
|
close(fd);
|
|
return 0;
|
|
}
|
|
|
|
if ((keyfp = fdopen(fd, "r")) == NULL) {
|
|
log_print("ike_auth_get_key: fdopen failed");
|
|
free(privkeyfile);
|
|
close(fd);
|
|
return 0;
|
|
}
|
|
#if SSLEAY_VERSION_NUMBER >= 0x00904100L
|
|
rsakey = PEM_read_RSAPrivateKey(keyfp, NULL, NULL, NULL);
|
|
#else
|
|
rsakey = PEM_read_RSAPrivateKey(keyfp, NULL, NULL);
|
|
#endif
|
|
fclose(keyfp);
|
|
|
|
free(privkeyfile);
|
|
|
|
if (!rsakey) {
|
|
log_print("ike_auth_get_key: "
|
|
"PEM_read_bio_RSAPrivateKey failed");
|
|
return 0;
|
|
}
|
|
return rsakey;
|
|
|
|
default:
|
|
log_print("ike_auth_get_key: unknown key type %d", type);
|
|
return 0;
|
|
}
|
|
|
|
return key;
|
|
}
|
|
|
|
static u_int8_t *
|
|
pre_shared_gen_skeyid(struct exchange *exchange, size_t *sz)
|
|
{
|
|
struct prf *prf;
|
|
struct ipsec_exch *ie = exchange->data;
|
|
u_int8_t *skeyid, *buf = 0;
|
|
unsigned char *key;
|
|
size_t keylen;
|
|
|
|
/*
|
|
* If we're the responder and have the initiator's ID (which is the
|
|
* case in Aggressive mode), try to find the preshared key in the
|
|
* section of the initiator's Phase 1 ID. This allows us to do
|
|
* mobile user support with preshared keys.
|
|
*/
|
|
if (!exchange->initiator && exchange->id_i) {
|
|
switch (exchange->id_i[0]) {
|
|
case IPSEC_ID_IPV4_ADDR:
|
|
case IPSEC_ID_IPV6_ADDR:
|
|
util_ntoa((char **) &buf,
|
|
exchange->id_i[0] == IPSEC_ID_IPV4_ADDR ? AF_INET :
|
|
AF_INET6, exchange->id_i + ISAKMP_ID_DATA_OFF -
|
|
ISAKMP_GEN_SZ);
|
|
if (!buf)
|
|
return 0;
|
|
break;
|
|
|
|
case IPSEC_ID_FQDN:
|
|
case IPSEC_ID_USER_FQDN:
|
|
buf = calloc(exchange->id_i_len - ISAKMP_ID_DATA_OFF +
|
|
ISAKMP_GEN_SZ + 1, sizeof(char));
|
|
if (!buf) {
|
|
log_print("pre_shared_gen_skeyid: malloc (%lu"
|
|
") failed",
|
|
(unsigned long)exchange->id_i_len -
|
|
ISAKMP_ID_DATA_OFF + ISAKMP_GEN_SZ + 1);
|
|
return 0;
|
|
}
|
|
memcpy(buf,
|
|
exchange->id_i + ISAKMP_ID_DATA_OFF -
|
|
ISAKMP_GEN_SZ,
|
|
exchange->id_i_len - ISAKMP_ID_DATA_OFF +
|
|
ISAKMP_GEN_SZ);
|
|
break;
|
|
|
|
/* XXX Support more ID types ? */
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
/*
|
|
* Get the pre-shared key for our peer. This will work even if the key
|
|
* has been passed to us through a mechanism like PFKEYv2.
|
|
*/
|
|
key = ike_auth_get_key(IKE_AUTH_PRE_SHARED, exchange->name,
|
|
(char *)buf, &keylen);
|
|
free(buf);
|
|
|
|
/* Fail if no key could be found. */
|
|
if (!key)
|
|
return 0;
|
|
|
|
/* Store the secret key for later policy processing. */
|
|
exchange->recv_key = calloc(keylen + 1, sizeof(char));
|
|
exchange->recv_keytype = ISAKMP_KEY_PASSPHRASE;
|
|
if (!exchange->recv_key) {
|
|
log_error("pre_shared_gen_skeyid: malloc (%lu) failed",
|
|
(unsigned long)keylen);
|
|
free(key);
|
|
return 0;
|
|
}
|
|
memcpy(exchange->recv_key, key, keylen);
|
|
exchange->recv_certtype = ISAKMP_CERTENC_NONE;
|
|
free(key);
|
|
|
|
prf = prf_alloc(ie->prf_type, ie->hash->type, exchange->recv_key,
|
|
keylen);
|
|
if (!prf)
|
|
return 0;
|
|
|
|
*sz = prf->blocksize;
|
|
skeyid = malloc(*sz);
|
|
if (!skeyid) {
|
|
log_error("pre_shared_gen_skeyid: malloc (%lu) failed",
|
|
(unsigned long)*sz);
|
|
prf_free(prf);
|
|
return 0;
|
|
}
|
|
prf->Init(prf->prfctx);
|
|
prf->Update(prf->prfctx, exchange->nonce_i, exchange->nonce_i_len);
|
|
prf->Update(prf->prfctx, exchange->nonce_r, exchange->nonce_r_len);
|
|
prf->Final(skeyid, prf->prfctx);
|
|
prf_free(prf);
|
|
return skeyid;
|
|
}
|
|
|
|
/* Both DSS & RSA signature authentication use this algorithm. */
|
|
static u_int8_t *
|
|
sig_gen_skeyid(struct exchange *exchange, size_t *sz)
|
|
{
|
|
struct prf *prf;
|
|
struct ipsec_exch *ie = exchange->data;
|
|
u_int8_t *skeyid;
|
|
unsigned char *key;
|
|
|
|
key = malloc(exchange->nonce_i_len + exchange->nonce_r_len);
|
|
if (!key)
|
|
return 0;
|
|
memcpy(key, exchange->nonce_i, exchange->nonce_i_len);
|
|
memcpy(key + exchange->nonce_i_len, exchange->nonce_r,
|
|
exchange->nonce_r_len);
|
|
|
|
LOG_DBG((LOG_NEGOTIATION, 80, "sig_gen_skeyid: PRF type %d, hash %d",
|
|
ie->prf_type, ie->hash->type));
|
|
LOG_DBG_BUF((LOG_NEGOTIATION, 80,
|
|
"sig_gen_skeyid: SKEYID initialized with",
|
|
(u_int8_t *)key, exchange->nonce_i_len + exchange->nonce_r_len));
|
|
|
|
prf = prf_alloc(ie->prf_type, ie->hash->type, key,
|
|
exchange->nonce_i_len + exchange->nonce_r_len);
|
|
free(key);
|
|
if (!prf)
|
|
return 0;
|
|
|
|
*sz = prf->blocksize;
|
|
skeyid = malloc(*sz);
|
|
if (!skeyid) {
|
|
log_error("sig_gen_skeyid: malloc (%lu) failed",
|
|
(unsigned long)*sz);
|
|
prf_free(prf);
|
|
return 0;
|
|
}
|
|
LOG_DBG((LOG_NEGOTIATION, 80, "sig_gen_skeyid: g^xy length %lu",
|
|
(unsigned long)ie->g_xy_len));
|
|
LOG_DBG_BUF((LOG_NEGOTIATION, 80,
|
|
"sig_gen_skeyid: SKEYID fed with g^xy", ie->g_xy, ie->g_xy_len));
|
|
|
|
prf->Init(prf->prfctx);
|
|
prf->Update(prf->prfctx, ie->g_xy, ie->g_xy_len);
|
|
prf->Final(skeyid, prf->prfctx);
|
|
prf_free(prf);
|
|
return skeyid;
|
|
}
|
|
|
|
#ifdef notdef
|
|
/*
|
|
* Both standard and revised RSA encryption authentication use this SKEYID
|
|
* computation.
|
|
*/
|
|
static u_int8_t *
|
|
enc_gen_skeyid(struct exchange *exchange, size_t *sz)
|
|
{
|
|
struct prf *prf;
|
|
struct ipsec_exch *ie = exchange->data;
|
|
struct hash *hash = ie->hash;
|
|
u_int8_t *skeyid;
|
|
|
|
hash->Init(hash->ctx);
|
|
hash->Update(hash->ctx, exchange->nonce_i, exchange->nonce_i_len);
|
|
hash->Update(hash->ctx, exchange->nonce_r, exchange->nonce_r_len);
|
|
hash->Final(hash->digest, hash->ctx);
|
|
prf = prf_alloc(ie->prf_type, hash->type, hash->digest, *sz);
|
|
if (!prf)
|
|
return 0;
|
|
|
|
*sz = prf->blocksize;
|
|
skeyid = malloc(*sz);
|
|
if (!skeyid) {
|
|
log_error("enc_gen_skeyid: malloc (%d) failed", *sz);
|
|
prf_free(prf);
|
|
return 0;
|
|
}
|
|
prf->Init(prf->prfctx);
|
|
prf->Update(prf->prfctx, exchange->cookies, ISAKMP_HDR_COOKIES_LEN);
|
|
prf->Final(skeyid, prf->prfctx);
|
|
prf_free(prf);
|
|
return skeyid;
|
|
}
|
|
#endif /* notdef */
|
|
|
|
static int
|
|
pre_shared_decode_hash(struct message *msg)
|
|
{
|
|
struct exchange *exchange = msg->exchange;
|
|
struct ipsec_exch *ie = exchange->data;
|
|
struct payload *payload;
|
|
size_t hashsize = ie->hash->hashsize;
|
|
char header[80];
|
|
int initiator = exchange->initiator;
|
|
u_int8_t **hash_p;
|
|
|
|
/* Choose the right fields to fill-in. */
|
|
hash_p = initiator ? &ie->hash_r : &ie->hash_i;
|
|
|
|
payload = payload_first(msg, ISAKMP_PAYLOAD_HASH);
|
|
if (!payload) {
|
|
log_print("pre_shared_decode_hash: no HASH payload found");
|
|
return -1;
|
|
}
|
|
/* Check that the hash is of the correct size. */
|
|
if (GET_ISAKMP_GEN_LENGTH(payload->p) - ISAKMP_GEN_SZ != hashsize)
|
|
return -1;
|
|
|
|
/* XXX Need this hash be in the SA? */
|
|
*hash_p = malloc(hashsize);
|
|
if (!*hash_p) {
|
|
log_error("pre_shared_decode_hash: malloc (%lu) failed",
|
|
(unsigned long)hashsize);
|
|
return -1;
|
|
}
|
|
memcpy(*hash_p, payload->p + ISAKMP_HASH_DATA_OFF, hashsize);
|
|
snprintf(header, sizeof header, "pre_shared_decode_hash: HASH_%c",
|
|
initiator ? 'R' : 'I');
|
|
LOG_DBG_BUF((LOG_MISC, 80, header, *hash_p, hashsize));
|
|
|
|
payload->flags |= PL_MARK;
|
|
return 0;
|
|
}
|
|
|
|
/* Decrypt the HASH in SIG, we already need a parsed ID payload. */
|
|
static int
|
|
rsa_sig_decode_hash(struct message *msg)
|
|
{
|
|
struct cert_handler *handler;
|
|
struct exchange *exchange = msg->exchange;
|
|
struct ipsec_exch *ie = exchange->data;
|
|
struct payload *p;
|
|
void *cert = 0;
|
|
u_int8_t *rawcert = 0, **hash_p, **id_cert, *id;
|
|
u_int32_t rawcertlen, *id_cert_len;
|
|
RSA *key = 0;
|
|
size_t hashsize = ie->hash->hashsize, id_len;
|
|
char header[80];
|
|
int len, initiator = exchange->initiator;
|
|
int found = 0, n, i, id_found;
|
|
#if defined (USE_DNSSEC)
|
|
u_int8_t *rawkey = 0;
|
|
u_int32_t rawkeylen;
|
|
#endif
|
|
|
|
/* Choose the right fields to fill-in. */
|
|
hash_p = initiator ? &ie->hash_r : &ie->hash_i;
|
|
id = initiator ? exchange->id_r : exchange->id_i;
|
|
id_len = initiator ? exchange->id_r_len : exchange->id_i_len;
|
|
|
|
if (!id || id_len == 0) {
|
|
log_print("rsa_sig_decode_hash: ID is missing");
|
|
return -1;
|
|
}
|
|
/*
|
|
* XXX Assume we should use the same kind of certification as the
|
|
* remote... moreover, just use the first CERT payload to decide what
|
|
* to use.
|
|
*/
|
|
p = payload_first(msg, ISAKMP_PAYLOAD_CERT);
|
|
if (!p)
|
|
handler = cert_get(ISAKMP_CERTENC_KEYNOTE);
|
|
else
|
|
handler = cert_get(GET_ISAKMP_CERT_ENCODING(p->p));
|
|
if (!handler) {
|
|
log_print("rsa_sig_decode_hash: cert_get (%d) failed",
|
|
p ? GET_ISAKMP_CERT_ENCODING(p->p) : -1);
|
|
return -1;
|
|
}
|
|
/*
|
|
* We need the policy session initialized now, so we can add
|
|
* credentials etc.
|
|
*/
|
|
exchange->policy_id = kn_init();
|
|
if (exchange->policy_id == -1) {
|
|
log_print("rsa_sig_decode_hash: failed to initialize policy "
|
|
"session");
|
|
return -1;
|
|
}
|
|
|
|
/* Obtain a certificate from our certificate storage. */
|
|
if (handler->cert_obtain(id, id_len, 0, &rawcert, &rawcertlen)) {
|
|
if (handler->id == ISAKMP_CERTENC_X509_SIG) {
|
|
cert = handler->cert_get(rawcert, rawcertlen);
|
|
if (!cert)
|
|
LOG_DBG((LOG_CRYPTO, 50, "rsa_sig_decode_hash:"
|
|
" certificate malformed"));
|
|
else {
|
|
if (!handler->cert_get_key(cert, &key)) {
|
|
log_print("rsa_sig_decode_hash: "
|
|
"decoding certificate failed");
|
|
handler->cert_free(cert);
|
|
} else {
|
|
found++;
|
|
LOG_DBG((LOG_CRYPTO, 40,
|
|
"rsa_sig_decode_hash: using cert "
|
|
"of type %d", handler->id));
|
|
exchange->recv_cert = cert;
|
|
exchange->recv_certtype = handler->id;
|
|
x509_generate_kn(exchange->policy_id,
|
|
cert);
|
|
}
|
|
}
|
|
} else if (handler->id == ISAKMP_CERTENC_KEYNOTE)
|
|
handler->cert_insert(exchange->policy_id, rawcert);
|
|
free(rawcert);
|
|
}
|
|
/*
|
|
* Walk over potential CERT payloads in this message.
|
|
* XXX I believe this is the wrong spot for this. CERTs can appear
|
|
* anytime.
|
|
*/
|
|
TAILQ_FOREACH(p, &msg->payload[ISAKMP_PAYLOAD_CERT], link) {
|
|
p->flags |= PL_MARK;
|
|
|
|
/*
|
|
* When we have found a key, just walk over the rest, marking
|
|
* them.
|
|
*/
|
|
if (found)
|
|
continue;
|
|
|
|
handler = cert_get(GET_ISAKMP_CERT_ENCODING(p->p));
|
|
if (!handler) {
|
|
LOG_DBG((LOG_MISC, 30, "rsa_sig_decode_hash: "
|
|
"no handler for %s CERT encoding",
|
|
constant_name(isakmp_certenc_cst,
|
|
GET_ISAKMP_CERT_ENCODING(p->p))));
|
|
continue;
|
|
}
|
|
cert = handler->cert_get(p->p + ISAKMP_CERT_DATA_OFF,
|
|
GET_ISAKMP_GEN_LENGTH(p->p) - ISAKMP_CERT_DATA_OFF);
|
|
if (!cert) {
|
|
log_print("rsa_sig_decode_hash: "
|
|
"can not get data from CERT");
|
|
continue;
|
|
}
|
|
if (!handler->cert_validate(cert)) {
|
|
handler->cert_free(cert);
|
|
log_print("rsa_sig_decode_hash: received CERT can't "
|
|
"be validated");
|
|
continue;
|
|
}
|
|
if (GET_ISAKMP_CERT_ENCODING(p->p) ==
|
|
ISAKMP_CERTENC_X509_SIG) {
|
|
if (!handler->cert_get_subjects(cert, &n, &id_cert,
|
|
&id_cert_len)) {
|
|
handler->cert_free(cert);
|
|
log_print("rsa_sig_decode_hash: can not get "
|
|
"subject from CERT");
|
|
continue;
|
|
}
|
|
id_found = 0;
|
|
for (i = 0; i < n; i++)
|
|
if (id_cert_len[i] == id_len &&
|
|
id[0] == id_cert[i][0] &&
|
|
memcmp(id + 4, id_cert[i] + 4, id_len - 4)
|
|
== 0) {
|
|
id_found++;
|
|
break;
|
|
}
|
|
if (!id_found) {
|
|
handler->cert_free(cert);
|
|
log_print("rsa_sig_decode_hash: no CERT "
|
|
"subject match the ID");
|
|
free(id_cert);
|
|
continue;
|
|
}
|
|
cert_free_subjects(n, id_cert, id_cert_len);
|
|
}
|
|
if (!handler->cert_get_key(cert, &key)) {
|
|
handler->cert_free(cert);
|
|
log_print("rsa_sig_decode_hash: decoding payload CERT "
|
|
"failed");
|
|
continue;
|
|
}
|
|
/* We validated the cert, cache it for later use. */
|
|
handler->cert_insert(exchange->policy_id, cert);
|
|
|
|
exchange->recv_cert = cert;
|
|
exchange->recv_certtype = GET_ISAKMP_CERT_ENCODING(p->p);
|
|
|
|
if (exchange->recv_certtype == ISAKMP_CERTENC_KEYNOTE) {
|
|
struct keynote_deckey dc;
|
|
char *pp;
|
|
|
|
dc.dec_algorithm = KEYNOTE_ALGORITHM_RSA;
|
|
dc.dec_key = key;
|
|
|
|
pp = kn_encode_key(&dc, INTERNAL_ENC_PKCS1,
|
|
ENCODING_HEX, KEYNOTE_PUBLIC_KEY);
|
|
if (pp == NULL) {
|
|
kn_free_key(&dc);
|
|
log_print("rsa_sig_decode_hash: failed to "
|
|
"ASCII-encode key");
|
|
return -1;
|
|
}
|
|
if (asprintf(&exchange->keynote_key, "rsa-hex:%s",
|
|
pp) == -1) {
|
|
free(pp);
|
|
kn_free_key(&dc);
|
|
log_print("rsa_sig_decode_hash: failed to asprintf()");
|
|
return -1;
|
|
}
|
|
free(pp);
|
|
}
|
|
found++;
|
|
}
|
|
|
|
#if defined (USE_DNSSEC)
|
|
/*
|
|
* If no certificate provided a key, try to find a validated DNSSEC
|
|
* KEY.
|
|
*/
|
|
if (!found) {
|
|
rawkey = dns_get_key(IKE_AUTH_RSA_SIG, msg, &rawkeylen);
|
|
|
|
/* We need to convert 'void *rawkey' into 'RSA *key'. */
|
|
if (dns_RSA_dns_to_x509(rawkey, rawkeylen, &key) == 0)
|
|
found++;
|
|
else
|
|
log_print("rsa_sig_decode_hash: KEY to RSA key "
|
|
"conversion failed");
|
|
|
|
free(rawkey);
|
|
}
|
|
#endif /* USE_DNSSEC */
|
|
|
|
/* If we still have not found a key, try to read it from a file. */
|
|
if (!found)
|
|
if (get_raw_key_from_file(IKE_AUTH_RSA_SIG, id, id_len, &key)
|
|
!= -1)
|
|
found++;
|
|
|
|
if (!found) {
|
|
log_print("rsa_sig_decode_hash: no public key found");
|
|
return -1;
|
|
}
|
|
p = payload_first(msg, ISAKMP_PAYLOAD_SIG);
|
|
if (!p) {
|
|
log_print("rsa_sig_decode_hash: missing signature payload");
|
|
RSA_free(key);
|
|
return -1;
|
|
}
|
|
/* Check that the sig is of the correct size. */
|
|
len = GET_ISAKMP_GEN_LENGTH(p->p) - ISAKMP_SIG_SZ;
|
|
if (len != RSA_size(key)) {
|
|
RSA_free(key);
|
|
log_print("rsa_sig_decode_hash: "
|
|
"SIG payload length does not match public key");
|
|
return -1;
|
|
}
|
|
*hash_p = malloc(len);
|
|
if (!*hash_p) {
|
|
RSA_free(key);
|
|
log_error("rsa_sig_decode_hash: malloc (%d) failed", len);
|
|
return -1;
|
|
}
|
|
len = RSA_public_decrypt(len, p->p + ISAKMP_SIG_DATA_OFF, *hash_p, key,
|
|
RSA_PKCS1_PADDING);
|
|
if (len == -1) {
|
|
RSA_free(key);
|
|
log_print("rsa_sig_decode_hash: RSA_public_decrypt () failed");
|
|
return -1;
|
|
}
|
|
/* Store key for later use */
|
|
exchange->recv_key = key;
|
|
exchange->recv_keytype = ISAKMP_KEY_RSA;
|
|
|
|
if (len != (int)hashsize) {
|
|
free(*hash_p);
|
|
*hash_p = 0;
|
|
log_print("rsa_sig_decode_hash: len %lu != hashsize %lu",
|
|
(unsigned long)len, (unsigned long)hashsize);
|
|
return -1;
|
|
}
|
|
snprintf(header, sizeof header, "rsa_sig_decode_hash: HASH_%c",
|
|
initiator ? 'R' : 'I');
|
|
LOG_DBG_BUF((LOG_MISC, 80, header, *hash_p, hashsize));
|
|
|
|
p->flags |= PL_MARK;
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
pre_shared_encode_hash(struct message *msg)
|
|
{
|
|
struct exchange *exchange = msg->exchange;
|
|
struct ipsec_exch *ie = exchange->data;
|
|
size_t hashsize = ie->hash->hashsize;
|
|
char header[80];
|
|
int initiator = exchange->initiator;
|
|
u_int8_t *buf;
|
|
|
|
buf = ipsec_add_hash_payload(msg, hashsize);
|
|
if (!buf)
|
|
return -1;
|
|
|
|
if (ike_auth_hash(exchange, buf + ISAKMP_HASH_DATA_OFF) == -1)
|
|
return -1;
|
|
|
|
snprintf(header, sizeof header, "pre_shared_encode_hash: HASH_%c",
|
|
initiator ? 'I' : 'R');
|
|
LOG_DBG_BUF((LOG_MISC, 80, header, buf + ISAKMP_HASH_DATA_OFF,
|
|
hashsize));
|
|
return 0;
|
|
}
|
|
|
|
/* Encrypt the HASH into a SIG type. */
|
|
static int
|
|
rsa_sig_encode_hash(struct message *msg)
|
|
{
|
|
struct exchange *exchange = msg->exchange;
|
|
struct ipsec_exch *ie = exchange->data;
|
|
size_t hashsize = ie->hash->hashsize, id_len;
|
|
struct cert_handler *handler;
|
|
char header[80];
|
|
int initiator = exchange->initiator, idtype;
|
|
u_int8_t *buf, *data, *buf2, *id;
|
|
u_int32_t datalen;
|
|
int32_t sigsize;
|
|
void *sent_key;
|
|
|
|
id = initiator ? exchange->id_i : exchange->id_r;
|
|
id_len = initiator ? exchange->id_i_len : exchange->id_r_len;
|
|
|
|
/* We may have been provided these by the kernel */
|
|
buf = (u_int8_t *)conf_get_str(exchange->name, "Credentials");
|
|
if (buf && (idtype = conf_get_num(exchange->name, "Credential_Type",
|
|
-1)) != -1) {
|
|
exchange->sent_certtype = idtype;
|
|
handler = cert_get(idtype);
|
|
if (!handler) {
|
|
log_print("rsa_sig_encode_hash: cert_get (%d) failed",
|
|
idtype);
|
|
return -1;
|
|
}
|
|
exchange->sent_cert =
|
|
handler->cert_from_printable((char *)buf);
|
|
if (!exchange->sent_cert) {
|
|
log_print("rsa_sig_encode_hash: failed to retrieve "
|
|
"certificate");
|
|
return -1;
|
|
}
|
|
handler->cert_serialize(exchange->sent_cert, &data, &datalen);
|
|
if (!data) {
|
|
log_print("rsa_sig_encode_hash: cert serialization "
|
|
"failed");
|
|
return -1;
|
|
}
|
|
goto aftercert; /* Skip all the certificate discovery */
|
|
}
|
|
/* XXX This needs to be configurable. */
|
|
idtype = ISAKMP_CERTENC_KEYNOTE;
|
|
|
|
/* Find a certificate with subjectAltName = id. */
|
|
handler = cert_get(idtype);
|
|
if (!handler) {
|
|
idtype = ISAKMP_CERTENC_X509_SIG;
|
|
handler = cert_get(idtype);
|
|
if (!handler) {
|
|
log_print("rsa_sig_encode_hash: cert_get(%d) failed",
|
|
idtype);
|
|
return -1;
|
|
}
|
|
}
|
|
if (handler->cert_obtain(id, id_len, 0, &data, &datalen) == 0) {
|
|
if (idtype == ISAKMP_CERTENC_KEYNOTE) {
|
|
idtype = ISAKMP_CERTENC_X509_SIG;
|
|
handler = cert_get(idtype);
|
|
if (!handler) {
|
|
log_print("rsa_sig_encode_hash: cert_get(%d) "
|
|
"failed", idtype);
|
|
return -1;
|
|
}
|
|
if (handler->cert_obtain(id, id_len, 0, &data,
|
|
&datalen) == 0) {
|
|
LOG_DBG((LOG_MISC, 10, "rsa_sig_encode_hash: "
|
|
"no certificate to send for id %s",
|
|
ipsec_id_string(id, id_len)));
|
|
goto skipcert;
|
|
}
|
|
} else {
|
|
LOG_DBG((LOG_MISC, 10,
|
|
"rsa_sig_encode_hash: no certificate to send"
|
|
" for id %s", ipsec_id_string(id, id_len)));
|
|
goto skipcert;
|
|
}
|
|
}
|
|
/* Let's store the certificate we are going to use */
|
|
exchange->sent_certtype = idtype;
|
|
exchange->sent_cert = handler->cert_get(data, datalen);
|
|
if (!exchange->sent_cert) {
|
|
free(data);
|
|
log_print("rsa_sig_encode_hash: failed to get certificate "
|
|
"from wire encoding");
|
|
return -1;
|
|
}
|
|
aftercert:
|
|
|
|
buf = realloc(data, ISAKMP_CERT_SZ + datalen);
|
|
if (!buf) {
|
|
log_error("rsa_sig_encode_hash: realloc (%p, %d) failed", data,
|
|
ISAKMP_CERT_SZ + datalen);
|
|
free(data);
|
|
return -1;
|
|
}
|
|
memmove(buf + ISAKMP_CERT_SZ, buf, datalen);
|
|
SET_ISAKMP_CERT_ENCODING(buf, idtype);
|
|
if (message_add_payload(msg, ISAKMP_PAYLOAD_CERT, buf,
|
|
ISAKMP_CERT_SZ + datalen, 1)) {
|
|
free(buf);
|
|
return -1;
|
|
}
|
|
skipcert:
|
|
|
|
/* Again, we may have these from the kernel */
|
|
buf = (u_int8_t *)conf_get_str(exchange->name, "PKAuthentication");
|
|
if (buf) {
|
|
key_from_printable(ISAKMP_KEY_RSA, ISAKMP_KEYTYPE_PRIVATE,
|
|
(char *)buf, &data, &datalen);
|
|
if (!data) {
|
|
log_print("rsa_sig_encode_hash: badly formatted RSA "
|
|
"private key");
|
|
return 0;
|
|
}
|
|
sent_key = key_internalize(ISAKMP_KEY_RSA,
|
|
ISAKMP_KEYTYPE_PRIVATE, data, datalen);
|
|
if (!sent_key) {
|
|
log_print("rsa_sig_encode_hash: bad RSA private key "
|
|
"from dynamic SA acquisition subsystem");
|
|
return 0;
|
|
}
|
|
} else {
|
|
/* Try through the regular means. */
|
|
switch (id[ISAKMP_ID_TYPE_OFF - ISAKMP_GEN_SZ]) {
|
|
case IPSEC_ID_IPV4_ADDR:
|
|
case IPSEC_ID_IPV6_ADDR:
|
|
util_ntoa((char **)&buf2,
|
|
id[ISAKMP_ID_TYPE_OFF - ISAKMP_GEN_SZ] ==
|
|
IPSEC_ID_IPV4_ADDR ? AF_INET : AF_INET6,
|
|
id + ISAKMP_ID_DATA_OFF - ISAKMP_GEN_SZ);
|
|
if (!buf2)
|
|
return 0;
|
|
break;
|
|
|
|
case IPSEC_ID_FQDN:
|
|
case IPSEC_ID_USER_FQDN:
|
|
buf2 = calloc(id_len - ISAKMP_ID_DATA_OFF +
|
|
ISAKMP_GEN_SZ + 1, sizeof(char));
|
|
if (!buf2) {
|
|
log_print("rsa_sig_encode_hash: malloc (%lu) "
|
|
"failed", (unsigned long)id_len -
|
|
ISAKMP_ID_DATA_OFF + ISAKMP_GEN_SZ + 1);
|
|
return 0;
|
|
}
|
|
memcpy(buf2, id + ISAKMP_ID_DATA_OFF - ISAKMP_GEN_SZ,
|
|
id_len - ISAKMP_ID_DATA_OFF + ISAKMP_GEN_SZ);
|
|
break;
|
|
|
|
/* XXX Support more ID types? */
|
|
default:
|
|
buf2 = 0;
|
|
return 0;
|
|
}
|
|
|
|
sent_key = ike_auth_get_key(IKE_AUTH_RSA_SIG, exchange->name,
|
|
(char *)buf2, 0);
|
|
free(buf2);
|
|
|
|
/* Did we find a key? */
|
|
if (!sent_key) {
|
|
log_print("rsa_sig_encode_hash: "
|
|
"could not get private key");
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
/* Enable RSA blinding. */
|
|
if (RSA_blinding_on(sent_key, NULL) != 1) {
|
|
log_error("rsa_sig_encode_hash: RSA_blinding_on () failed.");
|
|
RSA_free(sent_key);
|
|
return -1;
|
|
}
|
|
/* XXX hashsize is not necessarily prf->blocksize. */
|
|
buf = malloc(hashsize);
|
|
if (!buf) {
|
|
log_error("rsa_sig_encode_hash: malloc (%lu) failed",
|
|
(unsigned long)hashsize);
|
|
RSA_free(sent_key);
|
|
return -1;
|
|
}
|
|
if (ike_auth_hash(exchange, buf) == -1) {
|
|
free(buf);
|
|
RSA_free(sent_key);
|
|
return -1;
|
|
}
|
|
snprintf(header, sizeof header, "rsa_sig_encode_hash: HASH_%c",
|
|
initiator ? 'I' : 'R');
|
|
LOG_DBG_BUF((LOG_MISC, 80, header, buf, hashsize));
|
|
|
|
data = malloc(RSA_size(sent_key));
|
|
if (!data) {
|
|
log_error("rsa_sig_encode_hash: malloc (%d) failed",
|
|
RSA_size(sent_key));
|
|
free(buf);
|
|
RSA_free(sent_key);
|
|
return -1;
|
|
}
|
|
sigsize = RSA_private_encrypt(hashsize, buf, data, sent_key,
|
|
RSA_PKCS1_PADDING);
|
|
if (sigsize == -1) {
|
|
log_print("rsa_sig_encode_hash: "
|
|
"RSA_private_encrypt () failed");
|
|
free(data);
|
|
free(buf);
|
|
RSA_free(sent_key);
|
|
return -1;
|
|
}
|
|
datalen = (u_int32_t) sigsize;
|
|
|
|
free(buf);
|
|
RSA_free(sent_key);
|
|
|
|
buf = realloc(data, ISAKMP_SIG_SZ + datalen);
|
|
if (!buf) {
|
|
log_error("rsa_sig_encode_hash: realloc (%p, %d) failed", data,
|
|
ISAKMP_SIG_SZ + datalen);
|
|
free(data);
|
|
return -1;
|
|
}
|
|
memmove(buf + ISAKMP_SIG_SZ, buf, datalen);
|
|
|
|
snprintf(header, sizeof header, "rsa_sig_encode_hash: SIG_%c",
|
|
initiator ? 'I' : 'R');
|
|
LOG_DBG_BUF((LOG_MISC, 80, header, buf + ISAKMP_SIG_DATA_OFF,
|
|
datalen));
|
|
if (message_add_payload(msg, ISAKMP_PAYLOAD_SIG, buf,
|
|
ISAKMP_SIG_SZ + datalen, 1)) {
|
|
free(buf);
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
ike_auth_hash(struct exchange *exchange, u_int8_t *buf)
|
|
{
|
|
struct ipsec_exch *ie = exchange->data;
|
|
struct prf *prf;
|
|
struct hash *hash = ie->hash;
|
|
int initiator = exchange->initiator;
|
|
u_int8_t *id;
|
|
size_t id_len;
|
|
|
|
/* Choose the right fields to fill-in. */
|
|
id = initiator ? exchange->id_i : exchange->id_r;
|
|
id_len = initiator ? exchange->id_i_len : exchange->id_r_len;
|
|
|
|
/* Allocate the prf and start calculating our HASH. */
|
|
prf = prf_alloc(ie->prf_type, hash->type, ie->skeyid, ie->skeyid_len);
|
|
if (!prf)
|
|
return -1;
|
|
|
|
prf->Init(prf->prfctx);
|
|
prf->Update(prf->prfctx, initiator ? ie->g_xi : ie->g_xr, ie->g_x_len);
|
|
prf->Update(prf->prfctx, initiator ? ie->g_xr : ie->g_xi, ie->g_x_len);
|
|
prf->Update(prf->prfctx, exchange->cookies +
|
|
(initiator ? ISAKMP_HDR_ICOOKIE_OFF : ISAKMP_HDR_RCOOKIE_OFF),
|
|
ISAKMP_HDR_ICOOKIE_LEN);
|
|
prf->Update(prf->prfctx, exchange->cookies +
|
|
(initiator ? ISAKMP_HDR_RCOOKIE_OFF : ISAKMP_HDR_ICOOKIE_OFF),
|
|
ISAKMP_HDR_ICOOKIE_LEN);
|
|
prf->Update(prf->prfctx, ie->sa_i_b, ie->sa_i_b_len);
|
|
prf->Update(prf->prfctx, id, id_len);
|
|
prf->Final(buf, prf->prfctx);
|
|
prf_free(prf);
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
get_raw_key_from_file(int type, u_int8_t *id, size_t id_len, RSA **rsa)
|
|
{
|
|
char filename[FILENAME_MAX];
|
|
char *fstr;
|
|
FILE *keyfp;
|
|
|
|
if (type != IKE_AUTH_RSA_SIG) { /* XXX More types? */
|
|
LOG_DBG((LOG_NEGOTIATION, 20, "get_raw_key_from_file: "
|
|
"invalid auth type %d\n", type));
|
|
return -1;
|
|
}
|
|
*rsa = 0;
|
|
|
|
fstr = conf_get_str("General", "Pubkey-directory");
|
|
if (!fstr)
|
|
fstr = CONF_DFLT_PUBKEY_DIR;
|
|
|
|
if (snprintf(filename, sizeof filename, "%s/", fstr) >
|
|
(int)sizeof filename - 1)
|
|
return -1;
|
|
|
|
fstr = ipsec_id_string(id, id_len);
|
|
if (!fstr) {
|
|
LOG_DBG((LOG_NEGOTIATION, 50, "get_raw_key_from_file: "
|
|
"ipsec_id_string failed"));
|
|
return -1;
|
|
}
|
|
strlcat(filename, fstr, sizeof filename - strlen(filename));
|
|
free(fstr);
|
|
|
|
/* If the file does not exist, fail silently. */
|
|
keyfp = monitor_fopen(filename, "r");
|
|
if (keyfp) {
|
|
*rsa = PEM_read_RSA_PUBKEY(keyfp, NULL, NULL, NULL);
|
|
if (!*rsa) {
|
|
rewind(keyfp);
|
|
*rsa = PEM_read_RSAPublicKey(keyfp, NULL, NULL, NULL);
|
|
}
|
|
if (!*rsa)
|
|
log_print("get_raw_key_from_file: failed to get "
|
|
"public key %s", filename);
|
|
fclose(keyfp);
|
|
} else if (errno != ENOENT) {
|
|
log_error("get_raw_key_from_file: monitor_fopen "
|
|
"(\"%s\", \"r\") failed", filename);
|
|
return -1;
|
|
} else
|
|
LOG_DBG((LOG_NEGOTIATION, 50,
|
|
"get_raw_key_from_file: file %s not found", filename));
|
|
|
|
return (*rsa ? 0 : -1);
|
|
}
|