You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
simplesshd/dropbear/gensignkey.c

196 lines
4.4 KiB

#include "includes.h"
#include "dbutil.h"
#include "buffer.h"
#include "ecdsa.h"
#include "genrsa.h"
#include "gendss.h"
#include "gened25519.h"
#include "signkey.h"
#include "dbrandom.h"
/* Returns DROPBEAR_SUCCESS or DROPBEAR_FAILURE */
static int buf_writefile(buffer * buf, const char * filename, int skip_exist) {
int ret = DROPBEAR_FAILURE;
int fd = -1;
fd = open(filename, O_RDWR | O_CREAT | O_EXCL, S_IRUSR | S_IWUSR);
if (fd < 0) {
/* If generating keys on connection (skip_exist) it's OK to get EEXIST
- we probably just lost a race with another connection to generate the key */
if (skip_exist && errno == EEXIST) {
ret = DROPBEAR_SUCCESS;
} else {
dropbear_log(LOG_ERR, "Couldn't create new file %s: %s",
filename, strerror(errno));
}
goto out;
}
/* write the file now */
while (buf->pos != buf->len) {
int len = write(fd, buf_getptr(buf, buf->len - buf->pos),
buf->len - buf->pos);
if (len == -1 && errno == EINTR) {
continue;
}
if (len <= 0) {
dropbear_log(LOG_ERR, "Failed writing file %s: %s",
filename, strerror(errno));
goto out;
}
buf_incrpos(buf, len);
}
ret = DROPBEAR_SUCCESS;
out:
if (fd >= 0) {
if (fsync(fd) != 0) {
dropbear_log(LOG_ERR, "fsync of %s failed: %s", filename, strerror(errno));
}
m_close(fd);
}
return ret;
}
/* returns 0 on failure */
static int get_default_bits(enum signkey_type keytype)
{
switch (keytype) {
#if DROPBEAR_RSA
case DROPBEAR_SIGNKEY_RSA:
return DROPBEAR_DEFAULT_RSA_SIZE;
#endif
#if DROPBEAR_DSS
case DROPBEAR_SIGNKEY_DSS:
/* DSS for SSH only defines 1024 bits */
return 1024;
#endif
#if DROPBEAR_ECDSA
case DROPBEAR_SIGNKEY_ECDSA_KEYGEN:
return ECDSA_DEFAULT_SIZE;
case DROPBEAR_SIGNKEY_ECDSA_NISTP521:
return 521;
case DROPBEAR_SIGNKEY_ECDSA_NISTP384:
return 384;
case DROPBEAR_SIGNKEY_ECDSA_NISTP256:
return 256;
#endif
#if DROPBEAR_ED25519
case DROPBEAR_SIGNKEY_ED25519:
return 256;
#endif
default:
return 0;
}
}
int signkey_generate_get_bits(enum signkey_type keytype, int bits) {
if (bits == 0)
{
bits = get_default_bits(keytype);
}
return bits;
}
/* if skip_exist is set it will silently return if the key file exists */
int signkey_generate(enum signkey_type keytype, int bits, const char* filename, int skip_exist)
{
sign_key * key = NULL;
buffer *buf = NULL;
char *fn_temp = NULL;
int ret = DROPBEAR_FAILURE;
bits = signkey_generate_get_bits(keytype, bits);
/* now we can generate the key */
key = new_sign_key();
seedrandom();
switch(keytype) {
#if DROPBEAR_RSA
case DROPBEAR_SIGNKEY_RSA:
key->rsakey = gen_rsa_priv_key(bits);
break;
#endif
#if DROPBEAR_DSS
case DROPBEAR_SIGNKEY_DSS:
key->dsskey = gen_dss_priv_key(bits);
break;
#endif
#if DROPBEAR_ECDSA
case DROPBEAR_SIGNKEY_ECDSA_KEYGEN:
case DROPBEAR_SIGNKEY_ECDSA_NISTP521:
case DROPBEAR_SIGNKEY_ECDSA_NISTP384:
case DROPBEAR_SIGNKEY_ECDSA_NISTP256:
{
ecc_key *ecckey = gen_ecdsa_priv_key(bits);
keytype = ecdsa_signkey_type(ecckey);
*signkey_key_ptr(key, keytype) = ecckey;
}
break;
#endif
#if DROPBEAR_ED25519
case DROPBEAR_SIGNKEY_ED25519:
key->ed25519key = gen_ed25519_priv_key(bits);
break;
#endif
default:
dropbear_exit("Internal error");
}
seedrandom();
buf = buf_new(MAX_PRIVKEY_SIZE);
buf_put_priv_key(buf, key, keytype);
sign_key_free(key);
key = NULL;
buf_setpos(buf, 0);
fn_temp = m_malloc(strlen(filename) + 30);
snprintf(fn_temp, strlen(filename)+30, "%s.tmp%d", filename, getpid());
ret = buf_writefile(buf, fn_temp, 0);
if (ret == DROPBEAR_FAILURE) {
goto out;
}
if (link(fn_temp, filename) < 0) {
/* If generating keys on connection (skipexist) it's OK to get EEXIST
- we probably just lost a race with another connection to generate the key */
if ((!(skip_exist && errno == EEXIST)) &&
(rename(fn_temp, filename) < 0)) {
if (errno == EPERM || errno == EACCES) {
/* Non-atomic fallback when hard-links not allowed or unsupported */
buf_setpos(buf, 0);
ret = buf_writefile(buf, filename, skip_exist);
} else {
dropbear_log(LOG_ERR, "Failed moving key file to %s: %s", filename,
strerror(errno));
ret = DROPBEAR_FAILURE;
}
goto out;
}
}
/* ensure directory update is flushed to disk, otherwise we can end up
with zero-byte hostkey files if the power goes off */
fsync_parent_dir(filename);
out:
if (buf) {
buf_burn(buf);
buf_free(buf);
}
if (fn_temp) {
unlink(fn_temp);
m_free(fn_temp);
}
return ret;
}