version 1.106, 2014/04/29 18:01:49 |
version 1.107, 2014/06/24 01:13:21 |
|
|
/* $OpenBSD$ */ |
/* $OpenBSD$ */ |
/* |
/* |
* Author: Tatu Ylonen <ylo@cs.hut.fi> |
|
* Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland |
|
* All rights reserved |
|
* This file contains functions for reading and writing identity files, and |
|
* for reading the passphrase from the user. |
|
* |
|
* As far as I am concerned, the code I have written for this software |
|
* can be used freely for any purpose. Any derived versions of this |
|
* software must be clearly marked as such, and if the derived work is |
|
* incompatible with the protocol description in the RFC file, it must be |
|
* called by a name other than "ssh" or "Secure Shell". |
|
* |
|
* |
|
* Copyright (c) 2000, 2013 Markus Friedl. All rights reserved. |
* Copyright (c) 2000, 2013 Markus Friedl. All rights reserved. |
* |
* |
* Redistribution and use in source and binary forms, with or without |
* Redistribution and use in source and binary forms, with or without |
|
|
#include <sys/param.h> |
#include <sys/param.h> |
#include <sys/uio.h> |
#include <sys/uio.h> |
|
|
#ifdef WITH_OPENSSL |
|
#include <openssl/err.h> |
|
#include <openssl/evp.h> |
|
#include <openssl/pem.h> |
|
#endif |
|
|
|
#include "crypto_api.h" |
|
|
|
#include <errno.h> |
#include <errno.h> |
#include <fcntl.h> |
#include <fcntl.h> |
#include <stdio.h> |
#include <stdio.h> |
|
|
#include <string.h> |
#include <string.h> |
#include <unistd.h> |
#include <unistd.h> |
|
|
#include <util.h> |
|
|
|
#include "xmalloc.h" |
|
#include "cipher.h" |
#include "cipher.h" |
#include "buffer.h" |
|
#include "key.h" |
#include "key.h" |
#include "ssh.h" |
#include "ssh.h" |
#include "log.h" |
#include "log.h" |
|
|
#include "rsa.h" |
#include "rsa.h" |
#include "misc.h" |
#include "misc.h" |
#include "atomicio.h" |
#include "atomicio.h" |
#include "uuencode.h" |
#include "sshbuf.h" |
|
#include "ssherr.h" |
|
|
/* openssh private key file format */ |
|
#define MARK_BEGIN "-----BEGIN OPENSSH PRIVATE KEY-----\n" |
|
#define MARK_END "-----END OPENSSH PRIVATE KEY-----\n" |
|
#define KDFNAME "bcrypt" |
|
#define AUTH_MAGIC "openssh-key-v1" |
|
#define SALT_LEN 16 |
|
#define DEFAULT_CIPHERNAME "aes256-cbc" |
|
#define DEFAULT_ROUNDS 16 |
|
|
|
#define MAX_KEY_FILE_SIZE (1024 * 1024) |
#define MAX_KEY_FILE_SIZE (1024 * 1024) |
|
|
/* Version identification string for SSH v1 identity files. */ |
|
static const char authfile_id_string[] = |
|
"SSH PRIVATE KEY FILE FORMAT 1.1\n"; |
|
|
|
static int |
|
key_private_to_blob2(Key *prv, Buffer *blob, const char *passphrase, |
|
const char *comment, const char *ciphername, int rounds) |
|
{ |
|
u_char *key, *cp, salt[SALT_LEN]; |
|
size_t keylen, ivlen, blocksize, authlen; |
|
u_int len, check; |
|
int i, n; |
|
const Cipher *c; |
|
Buffer encoded, b, kdf; |
|
CipherContext ctx; |
|
const char *kdfname = KDFNAME; |
|
|
|
if (rounds <= 0) |
|
rounds = DEFAULT_ROUNDS; |
|
if (passphrase == NULL || !strlen(passphrase)) { |
|
ciphername = "none"; |
|
kdfname = "none"; |
|
} else if (ciphername == NULL) |
|
ciphername = DEFAULT_CIPHERNAME; |
|
else if (cipher_number(ciphername) != SSH_CIPHER_SSH2) |
|
fatal("invalid cipher"); |
|
|
|
if ((c = cipher_by_name(ciphername)) == NULL) |
|
fatal("unknown cipher name"); |
|
buffer_init(&kdf); |
|
blocksize = cipher_blocksize(c); |
|
keylen = cipher_keylen(c); |
|
ivlen = cipher_ivlen(c); |
|
authlen = cipher_authlen(c); |
|
key = xcalloc(1, keylen + ivlen); |
|
if (strcmp(kdfname, "none") != 0) { |
|
arc4random_buf(salt, SALT_LEN); |
|
if (bcrypt_pbkdf(passphrase, strlen(passphrase), |
|
salt, SALT_LEN, key, keylen + ivlen, rounds) < 0) |
|
fatal("bcrypt_pbkdf failed"); |
|
buffer_put_string(&kdf, salt, SALT_LEN); |
|
buffer_put_int(&kdf, rounds); |
|
} |
|
cipher_init(&ctx, c, key, keylen, key + keylen , ivlen, 1); |
|
explicit_bzero(key, keylen + ivlen); |
|
free(key); |
|
|
|
buffer_init(&encoded); |
|
buffer_append(&encoded, AUTH_MAGIC, sizeof(AUTH_MAGIC)); |
|
buffer_put_cstring(&encoded, ciphername); |
|
buffer_put_cstring(&encoded, kdfname); |
|
buffer_put_string(&encoded, buffer_ptr(&kdf), buffer_len(&kdf)); |
|
buffer_put_int(&encoded, 1); /* number of keys */ |
|
key_to_blob(prv, &cp, &len); /* public key */ |
|
buffer_put_string(&encoded, cp, len); |
|
|
|
explicit_bzero(cp, len); |
|
free(cp); |
|
|
|
buffer_free(&kdf); |
|
|
|
/* set up the buffer that will be encrypted */ |
|
buffer_init(&b); |
|
|
|
/* Random check bytes */ |
|
check = arc4random(); |
|
buffer_put_int(&b, check); |
|
buffer_put_int(&b, check); |
|
|
|
/* append private key and comment*/ |
|
key_private_serialize(prv, &b); |
|
buffer_put_cstring(&b, comment); |
|
|
|
/* padding */ |
|
i = 0; |
|
while (buffer_len(&b) % blocksize) |
|
buffer_put_char(&b, ++i & 0xff); |
|
|
|
/* length */ |
|
buffer_put_int(&encoded, buffer_len(&b)); |
|
|
|
/* encrypt */ |
|
cp = buffer_append_space(&encoded, buffer_len(&b) + authlen); |
|
if (cipher_crypt(&ctx, 0, cp, buffer_ptr(&b), buffer_len(&b), 0, |
|
authlen) != 0) |
|
fatal("%s: cipher_crypt failed", __func__); |
|
buffer_free(&b); |
|
cipher_cleanup(&ctx); |
|
|
|
/* uuencode */ |
|
len = 2 * buffer_len(&encoded); |
|
cp = xmalloc(len); |
|
n = uuencode(buffer_ptr(&encoded), buffer_len(&encoded), |
|
(char *)cp, len); |
|
if (n < 0) |
|
fatal("%s: uuencode", __func__); |
|
|
|
buffer_clear(blob); |
|
buffer_append(blob, MARK_BEGIN, sizeof(MARK_BEGIN) - 1); |
|
for (i = 0; i < n; i++) { |
|
buffer_put_char(blob, cp[i]); |
|
if (i % 70 == 69) |
|
buffer_put_char(blob, '\n'); |
|
} |
|
if (i % 70 != 69) |
|
buffer_put_char(blob, '\n'); |
|
buffer_append(blob, MARK_END, sizeof(MARK_END) - 1); |
|
free(cp); |
|
|
|
return buffer_len(blob); |
|
} |
|
|
|
static Key * |
|
key_parse_private2(Buffer *blob, int type, const char *passphrase, |
|
char **commentp) |
|
{ |
|
u_char *key = NULL, *cp, *salt = NULL, pad, last; |
|
char *comment = NULL, *ciphername = NULL, *kdfname = NULL; |
|
const u_char *kdfp; |
|
u_int keylen = 0, ivlen, blocksize, slen, klen, len, rounds, nkeys; |
|
u_int check1, check2, m1len, m2len; |
|
size_t authlen; |
|
const Cipher *c; |
|
Buffer b, encoded, copy, kdf; |
|
CipherContext ctx; |
|
Key *k = NULL; |
|
int dlen, ret, i; |
|
|
|
buffer_init(&b); |
|
buffer_init(&kdf); |
|
buffer_init(&encoded); |
|
buffer_init(©); |
|
|
|
/* uudecode */ |
|
m1len = sizeof(MARK_BEGIN) - 1; |
|
m2len = sizeof(MARK_END) - 1; |
|
cp = buffer_ptr(blob); |
|
len = buffer_len(blob); |
|
if (len < m1len || memcmp(cp, MARK_BEGIN, m1len)) { |
|
debug("%s: missing begin marker", __func__); |
|
goto out; |
|
} |
|
cp += m1len; |
|
len -= m1len; |
|
while (len) { |
|
if (*cp != '\n' && *cp != '\r') |
|
buffer_put_char(&encoded, *cp); |
|
last = *cp; |
|
len--; |
|
cp++; |
|
if (last == '\n') { |
|
if (len >= m2len && !memcmp(cp, MARK_END, m2len)) { |
|
buffer_put_char(&encoded, '\0'); |
|
break; |
|
} |
|
} |
|
} |
|
if (!len) { |
|
debug("%s: no end marker", __func__); |
|
goto out; |
|
} |
|
len = buffer_len(&encoded); |
|
if ((cp = buffer_append_space(©, len)) == NULL) { |
|
error("%s: buffer_append_space", __func__); |
|
goto out; |
|
} |
|
if ((dlen = uudecode(buffer_ptr(&encoded), cp, len)) < 0) { |
|
error("%s: uudecode failed", __func__); |
|
goto out; |
|
} |
|
if ((u_int)dlen > len) { |
|
error("%s: crazy uudecode length %d > %u", __func__, dlen, len); |
|
goto out; |
|
} |
|
buffer_consume_end(©, len - dlen); |
|
if (buffer_len(©) < sizeof(AUTH_MAGIC) || |
|
memcmp(buffer_ptr(©), AUTH_MAGIC, sizeof(AUTH_MAGIC))) { |
|
error("%s: bad magic", __func__); |
|
goto out; |
|
} |
|
buffer_consume(©, sizeof(AUTH_MAGIC)); |
|
|
|
ciphername = buffer_get_cstring_ret(©, NULL); |
|
if (ciphername == NULL || |
|
(c = cipher_by_name(ciphername)) == NULL) { |
|
error("%s: unknown cipher name", __func__); |
|
goto out; |
|
} |
|
if ((passphrase == NULL || !strlen(passphrase)) && |
|
strcmp(ciphername, "none") != 0) { |
|
/* passphrase required */ |
|
goto out; |
|
} |
|
kdfname = buffer_get_cstring_ret(©, NULL); |
|
if (kdfname == NULL || |
|
(strcmp(kdfname, "none") != 0 && strcmp(kdfname, "bcrypt") != 0)) { |
|
error("%s: unknown kdf name", __func__); |
|
goto out; |
|
} |
|
if (!strcmp(kdfname, "none") && strcmp(ciphername, "none") != 0) { |
|
error("%s: cipher %s requires kdf", __func__, ciphername); |
|
goto out; |
|
} |
|
/* kdf options */ |
|
kdfp = buffer_get_string_ptr_ret(©, &klen); |
|
if (kdfp == NULL) { |
|
error("%s: kdf options not set", __func__); |
|
goto out; |
|
} |
|
if (klen > 0) { |
|
if ((cp = buffer_append_space(&kdf, klen)) == NULL) { |
|
error("%s: kdf alloc failed", __func__); |
|
goto out; |
|
} |
|
memcpy(cp, kdfp, klen); |
|
} |
|
/* number of keys */ |
|
if (buffer_get_int_ret(&nkeys, ©) < 0) { |
|
error("%s: key counter missing", __func__); |
|
goto out; |
|
} |
|
if (nkeys != 1) { |
|
error("%s: only one key supported", __func__); |
|
goto out; |
|
} |
|
/* pubkey */ |
|
if ((cp = buffer_get_string_ret(©, &len)) == NULL) { |
|
error("%s: pubkey not found", __func__); |
|
goto out; |
|
} |
|
free(cp); /* XXX check pubkey against decrypted private key */ |
|
|
|
/* size of encrypted key blob */ |
|
len = buffer_get_int(©); |
|
blocksize = cipher_blocksize(c); |
|
authlen = cipher_authlen(c); |
|
if (len < blocksize) { |
|
error("%s: encrypted data too small", __func__); |
|
goto out; |
|
} |
|
if (len % blocksize) { |
|
error("%s: length not multiple of blocksize", __func__); |
|
goto out; |
|
} |
|
|
|
/* setup key */ |
|
keylen = cipher_keylen(c); |
|
ivlen = cipher_ivlen(c); |
|
key = xcalloc(1, keylen + ivlen); |
|
if (!strcmp(kdfname, "bcrypt")) { |
|
if ((salt = buffer_get_string_ret(&kdf, &slen)) == NULL) { |
|
error("%s: salt not set", __func__); |
|
goto out; |
|
} |
|
if (buffer_get_int_ret(&rounds, &kdf) < 0) { |
|
error("%s: rounds not set", __func__); |
|
goto out; |
|
} |
|
if (bcrypt_pbkdf(passphrase, strlen(passphrase), salt, slen, |
|
key, keylen + ivlen, rounds) < 0) { |
|
error("%s: bcrypt_pbkdf failed", __func__); |
|
goto out; |
|
} |
|
} |
|
|
|
cp = buffer_append_space(&b, len); |
|
cipher_init(&ctx, c, key, keylen, key + keylen, ivlen, 0); |
|
ret = cipher_crypt(&ctx, 0, cp, buffer_ptr(©), len, 0, authlen); |
|
cipher_cleanup(&ctx); |
|
buffer_consume(©, len); |
|
|
|
/* fail silently on decryption errors */ |
|
if (ret != 0) { |
|
debug("%s: decrypt failed", __func__); |
|
goto out; |
|
} |
|
|
|
if (buffer_len(©) != 0) { |
|
error("%s: key blob has trailing data (len = %u)", __func__, |
|
buffer_len(©)); |
|
goto out; |
|
} |
|
|
|
/* check bytes */ |
|
if (buffer_get_int_ret(&check1, &b) < 0 || |
|
buffer_get_int_ret(&check2, &b) < 0) { |
|
error("check bytes missing"); |
|
goto out; |
|
} |
|
if (check1 != check2) { |
|
debug("%s: decrypt failed: 0x%08x != 0x%08x", __func__, |
|
check1, check2); |
|
goto out; |
|
} |
|
|
|
k = key_private_deserialize(&b); |
|
|
|
/* comment */ |
|
comment = buffer_get_cstring_ret(&b, NULL); |
|
|
|
i = 0; |
|
while (buffer_len(&b)) { |
|
if (buffer_get_char_ret(&pad, &b) == -1 || |
|
pad != (++i & 0xff)) { |
|
error("%s: bad padding", __func__); |
|
key_free(k); |
|
k = NULL; |
|
goto out; |
|
} |
|
} |
|
|
|
if (k && commentp) { |
|
*commentp = comment; |
|
comment = NULL; |
|
} |
|
|
|
/* XXX decode pubkey and check against private */ |
|
out: |
|
free(ciphername); |
|
free(kdfname); |
|
free(salt); |
|
free(comment); |
|
if (key) |
|
explicit_bzero(key, keylen + ivlen); |
|
free(key); |
|
buffer_free(&encoded); |
|
buffer_free(©); |
|
buffer_free(&kdf); |
|
buffer_free(&b); |
|
return k; |
|
} |
|
|
|
#ifdef WITH_SSH1 |
|
/* |
|
* Serialises the authentication (private) key to a blob, encrypting it with |
|
* passphrase. The identification of the blob (lowest 64 bits of n) will |
|
* precede the key to provide identification of the key without needing a |
|
* passphrase. |
|
*/ |
|
static int |
|
key_private_rsa1_to_blob(Key *key, Buffer *blob, const char *passphrase, |
|
const char *comment) |
|
{ |
|
Buffer buffer, encrypted; |
|
u_char buf[100], *cp; |
|
int i, cipher_num; |
|
CipherContext ciphercontext; |
|
const Cipher *cipher; |
|
u_int32_t rnd; |
|
|
|
/* |
|
* If the passphrase is empty, use SSH_CIPHER_NONE to ease converting |
|
* to another cipher; otherwise use SSH_AUTHFILE_CIPHER. |
|
*/ |
|
cipher_num = (strcmp(passphrase, "") == 0) ? |
|
SSH_CIPHER_NONE : SSH_AUTHFILE_CIPHER; |
|
if ((cipher = cipher_by_number(cipher_num)) == NULL) |
|
fatal("save_private_key_rsa: bad cipher"); |
|
|
|
/* This buffer is used to built the secret part of the private key. */ |
|
buffer_init(&buffer); |
|
|
|
/* Put checkbytes for checking passphrase validity. */ |
|
rnd = arc4random(); |
|
buf[0] = rnd & 0xff; |
|
buf[1] = (rnd >> 8) & 0xff; |
|
buf[2] = buf[0]; |
|
buf[3] = buf[1]; |
|
buffer_append(&buffer, buf, 4); |
|
|
|
/* |
|
* Store the private key (n and e will not be stored because they |
|
* will be stored in plain text, and storing them also in encrypted |
|
* format would just give known plaintext). |
|
*/ |
|
buffer_put_bignum(&buffer, key->rsa->d); |
|
buffer_put_bignum(&buffer, key->rsa->iqmp); |
|
buffer_put_bignum(&buffer, key->rsa->q); /* reverse from SSL p */ |
|
buffer_put_bignum(&buffer, key->rsa->p); /* reverse from SSL q */ |
|
|
|
/* Pad the part to be encrypted until its size is a multiple of 8. */ |
|
while (buffer_len(&buffer) % 8 != 0) |
|
buffer_put_char(&buffer, 0); |
|
|
|
/* This buffer will be used to contain the data in the file. */ |
|
buffer_init(&encrypted); |
|
|
|
/* First store keyfile id string. */ |
|
for (i = 0; authfile_id_string[i]; i++) |
|
buffer_put_char(&encrypted, authfile_id_string[i]); |
|
buffer_put_char(&encrypted, 0); |
|
|
|
/* Store cipher type. */ |
|
buffer_put_char(&encrypted, cipher_num); |
|
buffer_put_int(&encrypted, 0); /* For future extension */ |
|
|
|
/* Store public key. This will be in plain text. */ |
|
buffer_put_int(&encrypted, BN_num_bits(key->rsa->n)); |
|
buffer_put_bignum(&encrypted, key->rsa->n); |
|
buffer_put_bignum(&encrypted, key->rsa->e); |
|
buffer_put_cstring(&encrypted, comment); |
|
|
|
/* Allocate space for the private part of the key in the buffer. */ |
|
cp = buffer_append_space(&encrypted, buffer_len(&buffer)); |
|
|
|
cipher_set_key_string(&ciphercontext, cipher, passphrase, |
|
CIPHER_ENCRYPT); |
|
if (cipher_crypt(&ciphercontext, 0, cp, |
|
buffer_ptr(&buffer), buffer_len(&buffer), 0, 0) != 0) |
|
fatal("%s: cipher_crypt failed", __func__); |
|
cipher_cleanup(&ciphercontext); |
|
explicit_bzero(&ciphercontext, sizeof(ciphercontext)); |
|
|
|
/* Destroy temporary data. */ |
|
explicit_bzero(buf, sizeof(buf)); |
|
buffer_free(&buffer); |
|
|
|
buffer_append(blob, buffer_ptr(&encrypted), buffer_len(&encrypted)); |
|
buffer_free(&encrypted); |
|
|
|
return 1; |
|
} |
|
#endif |
|
|
|
#ifdef WITH_OPENSSL |
|
/* convert SSH v2 key in OpenSSL PEM format */ |
|
static int |
|
key_private_pem_to_blob(Key *key, Buffer *blob, const char *_passphrase, |
|
const char *comment) |
|
{ |
|
int success = 0; |
|
int blen, len = strlen(_passphrase); |
|
u_char *passphrase = (len > 0) ? (u_char *)_passphrase : NULL; |
|
const EVP_CIPHER *cipher = (len > 0) ? EVP_aes_128_cbc() : NULL; |
|
const u_char *bptr; |
|
BIO *bio; |
|
|
|
if (len > 0 && len <= 4) { |
|
error("passphrase too short: have %d bytes, need > 4", len); |
|
return 0; |
|
} |
|
if ((bio = BIO_new(BIO_s_mem())) == NULL) { |
|
error("%s: BIO_new failed", __func__); |
|
return 0; |
|
} |
|
switch (key->type) { |
|
case KEY_DSA: |
|
success = PEM_write_bio_DSAPrivateKey(bio, key->dsa, |
|
cipher, passphrase, len, NULL, NULL); |
|
break; |
|
case KEY_ECDSA: |
|
success = PEM_write_bio_ECPrivateKey(bio, key->ecdsa, |
|
cipher, passphrase, len, NULL, NULL); |
|
break; |
|
case KEY_RSA: |
|
success = PEM_write_bio_RSAPrivateKey(bio, key->rsa, |
|
cipher, passphrase, len, NULL, NULL); |
|
break; |
|
} |
|
if (success) { |
|
if ((blen = BIO_get_mem_data(bio, &bptr)) <= 0) |
|
success = 0; |
|
else |
|
buffer_append(blob, bptr, blen); |
|
} |
|
BIO_free(bio); |
|
return success; |
|
} |
|
#endif |
|
|
|
/* Save a key blob to a file */ |
/* Save a key blob to a file */ |
static int |
static int |
key_save_private_blob(Buffer *keybuf, const char *filename) |
sshkey_save_private_blob(struct sshbuf *keybuf, const char *filename) |
{ |
{ |
int fd; |
int fd, oerrno; |
|
|
if ((fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0600)) < 0) { |
if ((fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0600)) < 0) |
error("open %s failed: %s.", filename, strerror(errno)); |
return SSH_ERR_SYSTEM_ERROR; |
return 0; |
if (atomicio(vwrite, fd, (u_char *)sshbuf_ptr(keybuf), |
} |
sshbuf_len(keybuf)) != sshbuf_len(keybuf)) { |
if (atomicio(vwrite, fd, buffer_ptr(keybuf), |
oerrno = errno; |
buffer_len(keybuf)) != buffer_len(keybuf)) { |
|
error("write to key file %s failed: %s", filename, |
|
strerror(errno)); |
|
close(fd); |
close(fd); |
unlink(filename); |
unlink(filename); |
return 0; |
errno = oerrno; |
|
return SSH_ERR_SYSTEM_ERROR; |
} |
} |
close(fd); |
close(fd); |
return 1; |
return 0; |
} |
} |
|
|
/* Serialise "key" to buffer "blob" */ |
|
static int |
|
key_private_to_blob(Key *key, Buffer *blob, const char *passphrase, |
|
const char *comment, int force_new_format, const char *new_format_cipher, |
|
int new_format_rounds) |
|
{ |
|
switch (key->type) { |
|
#ifdef WITH_SSH1 |
|
case KEY_RSA1: |
|
return key_private_rsa1_to_blob(key, blob, passphrase, comment); |
|
#endif |
|
#ifdef WITH_OPENSSL |
|
case KEY_DSA: |
|
case KEY_ECDSA: |
|
case KEY_RSA: |
|
if (force_new_format) { |
|
return key_private_to_blob2(key, blob, passphrase, |
|
comment, new_format_cipher, new_format_rounds); |
|
} |
|
return key_private_pem_to_blob(key, blob, passphrase, comment); |
|
#endif |
|
case KEY_ED25519: |
|
return key_private_to_blob2(key, blob, passphrase, |
|
comment, new_format_cipher, new_format_rounds); |
|
default: |
|
error("%s: cannot save key type %d", __func__, key->type); |
|
return 0; |
|
} |
|
} |
|
|
|
int |
int |
key_save_private(Key *key, const char *filename, const char *passphrase, |
sshkey_save_private(struct sshkey *key, const char *filename, |
const char *comment, int force_new_format, const char *new_format_cipher, |
const char *passphrase, const char *comment, |
int new_format_rounds) |
int force_new_format, const char *new_format_cipher, int new_format_rounds) |
{ |
{ |
Buffer keyblob; |
struct sshbuf *keyblob = NULL; |
int success = 0; |
int r; |
|
|
buffer_init(&keyblob); |
if ((keyblob = sshbuf_new()) == NULL) |
if (!key_private_to_blob(key, &keyblob, passphrase, comment, |
return SSH_ERR_ALLOC_FAIL; |
force_new_format, new_format_cipher, new_format_rounds)) |
if ((r = sshkey_private_to_fileblob(key, keyblob, passphrase, comment, |
|
force_new_format, new_format_cipher, new_format_rounds)) != 0) |
goto out; |
goto out; |
if (!key_save_private_blob(&keyblob, filename)) |
if ((r = sshkey_save_private_blob(keyblob, filename)) != 0) |
goto out; |
goto out; |
success = 1; |
r = 0; |
out: |
out: |
buffer_free(&keyblob); |
sshbuf_free(keyblob); |
return success; |
return r; |
} |
} |
|
|
#ifdef WITH_SSH1 |
|
/* |
|
* Parse the public, unencrypted portion of a RSA1 key. |
|
*/ |
|
static Key * |
|
key_parse_public_rsa1(Buffer *blob, char **commentp) |
|
{ |
|
Key *pub; |
|
Buffer copy; |
|
|
|
/* Check that it is at least big enough to contain the ID string. */ |
|
if (buffer_len(blob) < sizeof(authfile_id_string)) { |
|
debug3("Truncated RSA1 identifier"); |
|
return NULL; |
|
} |
|
|
|
/* |
|
* Make sure it begins with the id string. Consume the id string |
|
* from the buffer. |
|
*/ |
|
if (memcmp(buffer_ptr(blob), authfile_id_string, |
|
sizeof(authfile_id_string)) != 0) { |
|
debug3("Incorrect RSA1 identifier"); |
|
return NULL; |
|
} |
|
buffer_init(©); |
|
buffer_append(©, buffer_ptr(blob), buffer_len(blob)); |
|
buffer_consume(©, sizeof(authfile_id_string)); |
|
|
|
/* Skip cipher type and reserved data. */ |
|
(void) buffer_get_char(©); /* cipher type */ |
|
(void) buffer_get_int(©); /* reserved */ |
|
|
|
/* Read the public key from the buffer. */ |
|
(void) buffer_get_int(©); |
|
pub = key_new(KEY_RSA1); |
|
buffer_get_bignum(©, pub->rsa->n); |
|
buffer_get_bignum(©, pub->rsa->e); |
|
if (commentp) |
|
*commentp = buffer_get_string(©, NULL); |
|
/* The encrypted private part is not parsed by this function. */ |
|
buffer_free(©); |
|
|
|
return pub; |
|
} |
|
#endif |
|
|
|
/* Load a key from a fd into a buffer */ |
/* Load a key from a fd into a buffer */ |
int |
int |
key_load_file(int fd, const char *filename, Buffer *blob) |
sshkey_load_file(int fd, const char *filename, struct sshbuf *blob) |
{ |
{ |
u_char buf[1024]; |
u_char buf[1024]; |
size_t len; |
size_t len; |
struct stat st; |
struct stat st; |
|
int r; |
|
|
if (fstat(fd, &st) < 0) { |
if (fstat(fd, &st) < 0) |
error("%s: fstat of key file %.200s%sfailed: %.100s", __func__, |
return SSH_ERR_SYSTEM_ERROR; |
filename == NULL ? "" : filename, |
|
filename == NULL ? "" : " ", |
|
strerror(errno)); |
|
return 0; |
|
} |
|
if ((st.st_mode & (S_IFSOCK|S_IFCHR|S_IFIFO)) == 0 && |
if ((st.st_mode & (S_IFSOCK|S_IFCHR|S_IFIFO)) == 0 && |
st.st_size > MAX_KEY_FILE_SIZE) { |
st.st_size > MAX_KEY_FILE_SIZE) |
toobig: |
return SSH_ERR_INVALID_FORMAT; |
error("%s: key file %.200s%stoo large", __func__, |
|
filename == NULL ? "" : filename, |
|
filename == NULL ? "" : " "); |
|
return 0; |
|
} |
|
buffer_clear(blob); |
|
for (;;) { |
for (;;) { |
if ((len = atomicio(read, fd, buf, sizeof(buf))) == 0) { |
if ((len = atomicio(read, fd, buf, sizeof(buf))) == 0) { |
if (errno == EPIPE) |
if (errno == EPIPE) |
break; |
break; |
debug("%s: read from key file %.200s%sfailed: %.100s", |
r = SSH_ERR_SYSTEM_ERROR; |
__func__, filename == NULL ? "" : filename, |
goto out; |
filename == NULL ? "" : " ", strerror(errno)); |
|
buffer_clear(blob); |
|
explicit_bzero(buf, sizeof(buf)); |
|
return 0; |
|
} |
} |
buffer_append(blob, buf, len); |
if ((r = sshbuf_put(blob, buf, len)) != 0) |
if (buffer_len(blob) > MAX_KEY_FILE_SIZE) { |
goto out; |
buffer_clear(blob); |
if (sshbuf_len(blob) > MAX_KEY_FILE_SIZE) { |
explicit_bzero(buf, sizeof(buf)); |
r = SSH_ERR_INVALID_FORMAT; |
goto toobig; |
goto out; |
} |
} |
} |
} |
explicit_bzero(buf, sizeof(buf)); |
|
if ((st.st_mode & (S_IFSOCK|S_IFCHR|S_IFIFO)) == 0 && |
if ((st.st_mode & (S_IFSOCK|S_IFCHR|S_IFIFO)) == 0 && |
st.st_size != buffer_len(blob)) { |
st.st_size != (off_t)sshbuf_len(blob)) { |
debug("%s: key file %.200s%schanged size while reading", |
r = SSH_ERR_FILE_CHANGED; |
__func__, filename == NULL ? "" : filename, |
goto out; |
filename == NULL ? "" : " "); |
|
buffer_clear(blob); |
|
return 0; |
|
} |
} |
|
r = 0; |
|
|
return 1; |
out: |
|
explicit_bzero(buf, sizeof(buf)); |
|
if (r != 0) |
|
sshbuf_reset(blob); |
|
return r; |
} |
} |
|
|
#ifdef WITH_SSH1 |
#ifdef WITH_SSH1 |
|
|
* encountered (the file does not exist or is not readable), and the key |
* encountered (the file does not exist or is not readable), and the key |
* otherwise. |
* otherwise. |
*/ |
*/ |
static Key * |
static int |
key_load_public_rsa1(int fd, const char *filename, char **commentp) |
sshkey_load_public_rsa1(int fd, const char *filename, |
|
struct sshkey **keyp, char **commentp) |
{ |
{ |
Buffer buffer; |
struct sshbuf *b = NULL; |
Key *pub; |
int r; |
|
|
buffer_init(&buffer); |
*keyp = NULL; |
if (!key_load_file(fd, filename, &buffer)) { |
if (commentp != NULL) |
buffer_free(&buffer); |
*commentp = NULL; |
return NULL; |
|
} |
|
|
|
pub = key_parse_public_rsa1(&buffer, commentp); |
if ((b = sshbuf_new()) == NULL) |
if (pub == NULL) |
return SSH_ERR_ALLOC_FAIL; |
debug3("Could not load \"%s\" as a RSA1 public key", filename); |
if ((r = sshkey_load_file(fd, filename, b)) != 0) |
buffer_free(&buffer); |
goto out; |
return pub; |
if ((r = sshkey_parse_public_rsa1_fileblob(b, keyp, commentp)) != 0) |
|
goto out; |
|
r = 0; |
|
out: |
|
sshbuf_free(b); |
|
return r; |
} |
} |
|
#endif /* WITH_SSH1 */ |
|
|
/* load public key from private-key file, works only for SSH v1 */ |
#ifdef WITH_OPENSSL |
Key * |
/* XXX Deprecate? */ |
key_load_public_type(int type, const char *filename, char **commentp) |
int |
|
sshkey_load_private_pem(int fd, int type, const char *passphrase, |
|
struct sshkey **keyp, char **commentp) |
{ |
{ |
Key *pub; |
struct sshbuf *buffer = NULL; |
int fd; |
int r; |
|
|
if (type == KEY_RSA1) { |
*keyp = NULL; |
fd = open(filename, O_RDONLY); |
|
if (fd < 0) |
|
return NULL; |
|
pub = key_load_public_rsa1(fd, filename, commentp); |
|
close(fd); |
|
return pub; |
|
} |
|
return NULL; |
|
} |
|
|
|
static Key * |
|
key_parse_private_rsa1(Buffer *blob, const char *passphrase, char **commentp) |
|
{ |
|
int check1, check2, cipher_type; |
|
Buffer decrypted; |
|
u_char *cp; |
|
CipherContext ciphercontext; |
|
const Cipher *cipher; |
|
Key *prv = NULL; |
|
Buffer copy; |
|
|
|
/* Check that it is at least big enough to contain the ID string. */ |
|
if (buffer_len(blob) < sizeof(authfile_id_string)) { |
|
debug3("Truncated RSA1 identifier"); |
|
return NULL; |
|
} |
|
|
|
/* |
|
* Make sure it begins with the id string. Consume the id string |
|
* from the buffer. |
|
*/ |
|
if (memcmp(buffer_ptr(blob), authfile_id_string, |
|
sizeof(authfile_id_string)) != 0) { |
|
debug3("Incorrect RSA1 identifier"); |
|
return NULL; |
|
} |
|
buffer_init(©); |
|
buffer_append(©, buffer_ptr(blob), buffer_len(blob)); |
|
buffer_consume(©, sizeof(authfile_id_string)); |
|
|
|
/* Read cipher type. */ |
|
cipher_type = buffer_get_char(©); |
|
(void) buffer_get_int(©); /* Reserved data. */ |
|
|
|
/* Read the public key from the buffer. */ |
|
(void) buffer_get_int(©); |
|
prv = key_new_private(KEY_RSA1); |
|
|
|
buffer_get_bignum(©, prv->rsa->n); |
|
buffer_get_bignum(©, prv->rsa->e); |
|
if (commentp) |
|
*commentp = buffer_get_string(©, NULL); |
|
else |
|
(void)buffer_get_string_ptr(©, NULL); |
|
|
|
/* Check that it is a supported cipher. */ |
|
cipher = cipher_by_number(cipher_type); |
|
if (cipher == NULL) { |
|
debug("Unsupported RSA1 cipher %d", cipher_type); |
|
buffer_free(©); |
|
goto fail; |
|
} |
|
/* Initialize space for decrypted data. */ |
|
buffer_init(&decrypted); |
|
cp = buffer_append_space(&decrypted, buffer_len(©)); |
|
|
|
/* Rest of the buffer is encrypted. Decrypt it using the passphrase. */ |
|
cipher_set_key_string(&ciphercontext, cipher, passphrase, |
|
CIPHER_DECRYPT); |
|
if (cipher_crypt(&ciphercontext, 0, cp, |
|
buffer_ptr(©), buffer_len(©), 0, 0) != 0) |
|
fatal("%s: cipher_crypt failed", __func__); |
|
cipher_cleanup(&ciphercontext); |
|
explicit_bzero(&ciphercontext, sizeof(ciphercontext)); |
|
buffer_free(©); |
|
|
|
check1 = buffer_get_char(&decrypted); |
|
check2 = buffer_get_char(&decrypted); |
|
if (check1 != buffer_get_char(&decrypted) || |
|
check2 != buffer_get_char(&decrypted)) { |
|
if (strcmp(passphrase, "") != 0) |
|
debug("Bad passphrase supplied for RSA1 key"); |
|
/* Bad passphrase. */ |
|
buffer_free(&decrypted); |
|
goto fail; |
|
} |
|
/* Read the rest of the private key. */ |
|
buffer_get_bignum(&decrypted, prv->rsa->d); |
|
buffer_get_bignum(&decrypted, prv->rsa->iqmp); /* u */ |
|
/* in SSL and SSH v1 p and q are exchanged */ |
|
buffer_get_bignum(&decrypted, prv->rsa->q); /* p */ |
|
buffer_get_bignum(&decrypted, prv->rsa->p); /* q */ |
|
|
|
/* calculate p-1 and q-1 */ |
|
rsa_generate_additional_parameters(prv->rsa); |
|
|
|
buffer_free(&decrypted); |
|
|
|
/* enable blinding */ |
|
if (RSA_blinding_on(prv->rsa, NULL) != 1) { |
|
error("%s: RSA_blinding_on failed", __func__); |
|
goto fail; |
|
} |
|
return prv; |
|
|
|
fail: |
|
if (commentp != NULL) |
if (commentp != NULL) |
free(*commentp); |
*commentp = NULL; |
key_free(prv); |
|
return NULL; |
|
} |
|
#endif |
|
|
|
#ifdef WITH_OPENSSL |
if ((buffer = sshbuf_new()) == NULL) |
static Key * |
return SSH_ERR_ALLOC_FAIL; |
key_parse_private_pem(Buffer *blob, int type, const char *passphrase, |
if ((r = sshkey_load_file(fd, NULL, buffer)) != 0) |
char **commentp) |
goto out; |
{ |
if ((r = sshkey_parse_private_pem_fileblob(buffer, type, passphrase, |
EVP_PKEY *pk = NULL; |
keyp, commentp)) != 0) |
Key *prv = NULL; |
goto out; |
char *name = "<no key>"; |
r = 0; |
BIO *bio; |
out: |
|
sshbuf_free(buffer); |
if ((bio = BIO_new_mem_buf(buffer_ptr(blob), |
return r; |
buffer_len(blob))) == NULL) { |
|
error("%s: BIO_new_mem_buf failed", __func__); |
|
return NULL; |
|
} |
|
|
|
pk = PEM_read_bio_PrivateKey(bio, NULL, NULL, (char *)passphrase); |
|
BIO_free(bio); |
|
if (pk == NULL) { |
|
debug("%s: PEM_read_PrivateKey failed", __func__); |
|
(void)ERR_get_error(); |
|
} else if (pk->type == EVP_PKEY_RSA && |
|
(type == KEY_UNSPEC||type==KEY_RSA)) { |
|
prv = key_new(KEY_UNSPEC); |
|
prv->rsa = EVP_PKEY_get1_RSA(pk); |
|
prv->type = KEY_RSA; |
|
name = "rsa w/o comment"; |
|
#ifdef DEBUG_PK |
|
RSA_print_fp(stderr, prv->rsa, 8); |
|
#endif |
|
if (RSA_blinding_on(prv->rsa, NULL) != 1) { |
|
error("%s: RSA_blinding_on failed", __func__); |
|
key_free(prv); |
|
prv = NULL; |
|
} |
|
} else if (pk->type == EVP_PKEY_DSA && |
|
(type == KEY_UNSPEC||type==KEY_DSA)) { |
|
prv = key_new(KEY_UNSPEC); |
|
prv->dsa = EVP_PKEY_get1_DSA(pk); |
|
prv->type = KEY_DSA; |
|
name = "dsa w/o comment"; |
|
#ifdef DEBUG_PK |
|
DSA_print_fp(stderr, prv->dsa, 8); |
|
#endif |
|
} else if (pk->type == EVP_PKEY_EC && |
|
(type == KEY_UNSPEC||type==KEY_ECDSA)) { |
|
prv = key_new(KEY_UNSPEC); |
|
prv->ecdsa = EVP_PKEY_get1_EC_KEY(pk); |
|
prv->type = KEY_ECDSA; |
|
if ((prv->ecdsa_nid = key_ecdsa_key_to_nid(prv->ecdsa)) == -1 || |
|
key_curve_nid_to_name(prv->ecdsa_nid) == NULL || |
|
key_ec_validate_public(EC_KEY_get0_group(prv->ecdsa), |
|
EC_KEY_get0_public_key(prv->ecdsa)) != 0 || |
|
key_ec_validate_private(prv->ecdsa) != 0) { |
|
error("%s: bad ECDSA key", __func__); |
|
key_free(prv); |
|
prv = NULL; |
|
} |
|
name = "ecdsa w/o comment"; |
|
#ifdef DEBUG_PK |
|
if (prv != NULL && prv->ecdsa != NULL) |
|
key_dump_ec_key(prv->ecdsa); |
|
#endif |
|
} else { |
|
error("%s: PEM_read_PrivateKey: mismatch or " |
|
"unknown EVP_PKEY save_type %d", __func__, pk->save_type); |
|
} |
|
if (pk != NULL) |
|
EVP_PKEY_free(pk); |
|
if (prv != NULL && commentp) |
|
*commentp = xstrdup(name); |
|
debug("read PEM private key done: type %s", |
|
prv ? key_type(prv) : "<unknown>"); |
|
return prv; |
|
} |
} |
|
#endif /* WITH_OPENSSL */ |
|
|
Key * |
/* XXX remove error() calls from here? */ |
key_load_private_pem(int fd, int type, const char *passphrase, |
|
char **commentp) |
|
{ |
|
Buffer buffer; |
|
Key *prv; |
|
|
|
buffer_init(&buffer); |
|
if (!key_load_file(fd, NULL, &buffer)) { |
|
buffer_free(&buffer); |
|
return NULL; |
|
} |
|
prv = key_parse_private_pem(&buffer, type, passphrase, commentp); |
|
buffer_free(&buffer); |
|
return prv; |
|
} |
|
#endif |
|
|
|
int |
int |
key_perm_ok(int fd, const char *filename) |
sshkey_perm_ok(int fd, const char *filename) |
{ |
{ |
struct stat st; |
struct stat st; |
|
|
if (fstat(fd, &st) < 0) |
if (fstat(fd, &st) < 0) |
return 0; |
return SSH_ERR_SYSTEM_ERROR; |
/* |
/* |
* if a key owned by the user is accessed, then we check the |
* if a key owned by the user is accessed, then we check the |
* permissions of the file. if the key owned by a different user, |
* permissions of the file. if the key owned by a different user, |
|
|
(u_int)st.st_mode & 0777, filename); |
(u_int)st.st_mode & 0777, filename); |
error("It is recommended that your private key files are NOT accessible by others."); |
error("It is recommended that your private key files are NOT accessible by others."); |
error("This private key will be ignored."); |
error("This private key will be ignored."); |
return 0; |
return SSH_ERR_KEY_BAD_PERMISSIONS; |
} |
} |
return 1; |
return 0; |
} |
} |
|
|
static Key * |
/* XXX kill perm_ok now that we have SSH_ERR_KEY_BAD_PERMISSIONS? */ |
key_parse_private_type(Buffer *blob, int type, const char *passphrase, |
int |
char **commentp) |
sshkey_load_private_type(int type, const char *filename, const char *passphrase, |
|
struct sshkey **keyp, char **commentp, int *perm_ok) |
{ |
{ |
Key *k; |
int fd, r; |
|
struct sshbuf *buffer = NULL; |
|
|
switch (type) { |
*keyp = NULL; |
#ifdef WITH_SSH1 |
if (commentp != NULL) |
case KEY_RSA1: |
*commentp = NULL; |
return key_parse_private_rsa1(blob, passphrase, commentp); |
|
#endif |
|
#ifdef WITH_OPENSSL |
|
case KEY_DSA: |
|
case KEY_ECDSA: |
|
case KEY_RSA: |
|
return key_parse_private_pem(blob, type, passphrase, commentp); |
|
#endif |
|
case KEY_ED25519: |
|
return key_parse_private2(blob, type, passphrase, commentp); |
|
case KEY_UNSPEC: |
|
if ((k = key_parse_private2(blob, type, passphrase, commentp))) |
|
return k; |
|
#ifdef WITH_OPENSSL |
|
return key_parse_private_pem(blob, type, passphrase, commentp); |
|
#endif |
|
default: |
|
error("%s: cannot parse key type %d", __func__, type); |
|
break; |
|
} |
|
return NULL; |
|
} |
|
|
|
Key * |
if ((fd = open(filename, O_RDONLY)) < 0) { |
key_load_private_type(int type, const char *filename, const char *passphrase, |
|
char **commentp, int *perm_ok) |
|
{ |
|
int fd; |
|
Key *ret; |
|
Buffer buffer; |
|
|
|
fd = open(filename, O_RDONLY); |
|
if (fd < 0) { |
|
debug("could not open key file '%s': %s", filename, |
|
strerror(errno)); |
|
if (perm_ok != NULL) |
if (perm_ok != NULL) |
*perm_ok = 0; |
*perm_ok = 0; |
return NULL; |
return SSH_ERR_SYSTEM_ERROR; |
} |
} |
if (!key_perm_ok(fd, filename)) { |
if (sshkey_perm_ok(fd, filename) != 0) { |
if (perm_ok != NULL) |
if (perm_ok != NULL) |
*perm_ok = 0; |
*perm_ok = 0; |
error("bad permissions: ignore key: %s", filename); |
r = SSH_ERR_KEY_BAD_PERMISSIONS; |
close(fd); |
goto out; |
return NULL; |
|
} |
} |
if (perm_ok != NULL) |
if (perm_ok != NULL) |
*perm_ok = 1; |
*perm_ok = 1; |
|
|
buffer_init(&buffer); |
if ((buffer = sshbuf_new()) == NULL) { |
if (!key_load_file(fd, filename, &buffer)) { |
r = SSH_ERR_ALLOC_FAIL; |
buffer_free(&buffer); |
goto out; |
close(fd); |
|
return NULL; |
|
} |
} |
|
if ((r = sshkey_load_file(fd, filename, buffer)) != 0) |
|
goto out; |
|
if ((r = sshkey_parse_private_fileblob_type(buffer, type, passphrase, |
|
keyp, commentp)) != 0) |
|
goto out; |
|
r = 0; |
|
out: |
close(fd); |
close(fd); |
ret = key_parse_private_type(&buffer, type, passphrase, commentp); |
if (buffer != NULL) |
buffer_free(&buffer); |
sshbuf_free(buffer); |
return ret; |
return r; |
} |
} |
|
|
Key * |
/* XXX this is almost identical to sshkey_load_private_type() */ |
key_parse_private(Buffer *buffer, const char *filename, |
int |
const char *passphrase, char **commentp) |
sshkey_load_private(const char *filename, const char *passphrase, |
|
struct sshkey **keyp, char **commentp) |
{ |
{ |
#ifdef WITH_SSH1 |
struct sshbuf *buffer = NULL; |
Key *pub, *prv; |
int r, fd; |
|
|
/* it's a SSH v1 key if the public key part is readable */ |
*keyp = NULL; |
pub = key_parse_public_rsa1(buffer, commentp); |
if (commentp != NULL) |
if (pub == NULL) { |
*commentp = NULL; |
prv = key_parse_private_type(buffer, KEY_UNSPEC, |
|
passphrase, NULL); |
|
/* use the filename as a comment for PEM */ |
|
if (commentp && prv) |
|
*commentp = xstrdup(filename); |
|
} else { |
|
key_free(pub); |
|
/* key_parse_public_rsa1() has already loaded the comment */ |
|
prv = key_parse_private_type(buffer, KEY_RSA1, passphrase, |
|
NULL); |
|
} |
|
return prv; |
|
#else |
|
return key_parse_private_type(buffer, KEY_UNSPEC, |
|
passphrase, commentp); |
|
#endif |
|
} |
|
|
|
Key * |
if ((fd = open(filename, O_RDONLY)) < 0) |
key_load_private(const char *filename, const char *passphrase, |
return SSH_ERR_SYSTEM_ERROR; |
char **commentp) |
if (sshkey_perm_ok(fd, filename) != 0) { |
{ |
r = SSH_ERR_KEY_BAD_PERMISSIONS; |
Key *prv; |
goto out; |
Buffer buffer; |
|
int fd; |
|
|
|
fd = open(filename, O_RDONLY); |
|
if (fd < 0) { |
|
debug("could not open key file '%s': %s", filename, |
|
strerror(errno)); |
|
return NULL; |
|
} |
} |
if (!key_perm_ok(fd, filename)) { |
|
error("bad permissions: ignore key: %s", filename); |
|
close(fd); |
|
return NULL; |
|
} |
|
|
|
buffer_init(&buffer); |
if ((buffer = sshbuf_new()) == NULL) { |
if (!key_load_file(fd, filename, &buffer)) { |
r = SSH_ERR_ALLOC_FAIL; |
buffer_free(&buffer); |
goto out; |
close(fd); |
|
return NULL; |
|
} |
} |
|
if ((r = sshkey_load_file(fd, filename, buffer)) != 0 || |
|
(r = sshkey_parse_private_fileblob(buffer, passphrase, filename, |
|
keyp, commentp)) != 0) |
|
goto out; |
|
r = 0; |
|
out: |
close(fd); |
close(fd); |
|
if (buffer != NULL) |
prv = key_parse_private(&buffer, filename, passphrase, commentp); |
sshbuf_free(buffer); |
buffer_free(&buffer); |
return r; |
return prv; |
|
} |
} |
|
|
static int |
static int |
key_try_load_public(Key *k, const char *filename, char **commentp) |
sshkey_try_load_public(struct sshkey *k, const char *filename, char **commentp) |
{ |
{ |
FILE *f; |
FILE *f; |
char line[SSH_MAX_PUBKEY_BYTES]; |
char line[SSH_MAX_PUBKEY_BYTES]; |
char *cp; |
char *cp; |
u_long linenum = 0; |
u_long linenum = 0; |
|
int r; |
|
|
f = fopen(filename, "r"); |
if (commentp != NULL) |
if (f != NULL) { |
*commentp = NULL; |
while (read_keyfile_line(f, filename, line, sizeof(line), |
if ((f = fopen(filename, "r")) == NULL) |
&linenum) != -1) { |
return SSH_ERR_SYSTEM_ERROR; |
cp = line; |
while (read_keyfile_line(f, filename, line, sizeof(line), |
switch (*cp) { |
&linenum) != -1) { |
case '#': |
cp = line; |
case '\n': |
switch (*cp) { |
case '\0': |
case '#': |
continue; |
case '\n': |
} |
case '\0': |
/* Abort loading if this looks like a private key */ |
continue; |
if (strncmp(cp, "-----BEGIN", 10) == 0) |
} |
break; |
/* Abort loading if this looks like a private key */ |
/* Skip leading whitespace. */ |
if (strncmp(cp, "-----BEGIN", 10) == 0 || |
for (; *cp && (*cp == ' ' || *cp == '\t'); cp++) |
strcmp(cp, "SSH PRIVATE KEY FILE") == 0) |
; |
break; |
if (*cp) { |
/* Skip leading whitespace. */ |
if (key_read(k, &cp) == 1) { |
for (; *cp && (*cp == ' ' || *cp == '\t'); cp++) |
cp[strcspn(cp, "\r\n")] = '\0'; |
; |
if (commentp) { |
if (*cp) { |
*commentp = xstrdup(*cp ? |
if ((r = sshkey_read(k, &cp)) == 0) { |
cp : filename); |
cp[strcspn(cp, "\r\n")] = '\0'; |
} |
if (commentp) { |
fclose(f); |
*commentp = strdup(*cp ? |
return 1; |
cp : filename); |
|
if (*commentp == NULL) |
|
r = SSH_ERR_ALLOC_FAIL; |
} |
} |
|
fclose(f); |
|
return r; |
} |
} |
} |
} |
fclose(f); |
|
} |
} |
return 0; |
fclose(f); |
|
return SSH_ERR_INVALID_FORMAT; |
} |
} |
|
|
/* load public key from ssh v1 private or any pubkey file */ |
/* load public key from ssh v1 private or any pubkey file */ |
Key * |
int |
key_load_public(const char *filename, char **commentp) |
sshkey_load_public(const char *filename, struct sshkey **keyp, char **commentp) |
{ |
{ |
Key *pub; |
struct sshkey *pub = NULL; |
char file[MAXPATHLEN]; |
char file[MAXPATHLEN]; |
|
int r, fd; |
|
|
|
if (keyp != NULL) |
|
*keyp = NULL; |
|
if (commentp != NULL) |
|
*commentp = NULL; |
|
|
|
if ((fd = open(filename, O_RDONLY)) < 0) |
|
goto skip; |
#ifdef WITH_SSH1 |
#ifdef WITH_SSH1 |
/* try rsa1 private key */ |
/* try rsa1 private key */ |
pub = key_load_public_type(KEY_RSA1, filename, commentp); |
r = sshkey_load_public_rsa1(fd, filename, keyp, commentp); |
if (pub != NULL) |
close(fd); |
return pub; |
switch (r) { |
|
case SSH_ERR_INTERNAL_ERROR: |
|
case SSH_ERR_ALLOC_FAIL: |
|
case SSH_ERR_INVALID_ARGUMENT: |
|
case SSH_ERR_SYSTEM_ERROR: |
|
case 0: |
|
return r; |
|
} |
|
#endif /* WITH_SSH1 */ |
|
|
|
/* try ssh2 public key */ |
|
if ((pub = sshkey_new(KEY_UNSPEC)) == NULL) |
|
return SSH_ERR_ALLOC_FAIL; |
|
if ((r = sshkey_try_load_public(pub, filename, commentp)) == 0) { |
|
if (keyp != NULL) |
|
*keyp = pub; |
|
return 0; |
|
} |
|
sshkey_free(pub); |
|
|
|
#ifdef WITH_SSH1 |
/* try rsa1 public key */ |
/* try rsa1 public key */ |
pub = key_new(KEY_RSA1); |
if ((pub = sshkey_new(KEY_RSA1)) == NULL) |
if (key_try_load_public(pub, filename, commentp) == 1) |
return SSH_ERR_ALLOC_FAIL; |
return pub; |
if ((r = sshkey_try_load_public(pub, filename, commentp)) == 0) { |
key_free(pub); |
if (keyp != NULL) |
#endif |
*keyp = pub; |
|
return 0; |
|
} |
|
sshkey_free(pub); |
|
#endif /* WITH_SSH1 */ |
|
|
/* try ssh2 public key */ |
skip: |
pub = key_new(KEY_UNSPEC); |
/* try .pub suffix */ |
if (key_try_load_public(pub, filename, commentp) == 1) |
if ((pub = sshkey_new(KEY_UNSPEC)) == NULL) |
return pub; |
return SSH_ERR_ALLOC_FAIL; |
|
r = SSH_ERR_ALLOC_FAIL; /* in case strlcpy or strlcat fail */ |
if ((strlcpy(file, filename, sizeof file) < sizeof(file)) && |
if ((strlcpy(file, filename, sizeof file) < sizeof(file)) && |
(strlcat(file, ".pub", sizeof file) < sizeof(file)) && |
(strlcat(file, ".pub", sizeof file) < sizeof(file)) && |
(key_try_load_public(pub, file, commentp) == 1)) |
(r = sshkey_try_load_public(pub, file, commentp)) == 0) { |
return pub; |
if (keyp != NULL) |
key_free(pub); |
*keyp = pub; |
return NULL; |
return 0; |
|
} |
|
sshkey_free(pub); |
|
return r; |
} |
} |
|
|
/* Load the certificate associated with the named private key */ |
/* Load the certificate associated with the named private key */ |
Key * |
int |
key_load_cert(const char *filename) |
sshkey_load_cert(const char *filename, struct sshkey **keyp) |
{ |
{ |
Key *pub; |
struct sshkey *pub = NULL; |
char *file; |
char *file = NULL; |
|
int r = SSH_ERR_INTERNAL_ERROR; |
|
|
pub = key_new(KEY_UNSPEC); |
*keyp = NULL; |
xasprintf(&file, "%s-cert.pub", filename); |
|
if (key_try_load_public(pub, file, NULL) == 1) { |
if (asprintf(&file, "%s-cert.pub", filename) == -1) |
free(file); |
return SSH_ERR_ALLOC_FAIL; |
return pub; |
|
|
if ((pub = sshkey_new(KEY_UNSPEC)) == NULL) { |
|
goto out; |
} |
} |
free(file); |
if ((r = sshkey_try_load_public(pub, file, NULL)) != 0) |
key_free(pub); |
goto out; |
return NULL; |
|
|
*keyp = pub; |
|
pub = NULL; |
|
r = 0; |
|
|
|
out: |
|
if (file != NULL) |
|
free(file); |
|
if (pub != NULL) |
|
sshkey_free(pub); |
|
return r; |
} |
} |
|
|
/* Load private key and certificate */ |
/* Load private key and certificate */ |
Key * |
int |
key_load_private_cert(int type, const char *filename, const char *passphrase, |
sshkey_load_private_cert(int type, const char *filename, const char *passphrase, |
int *perm_ok) |
struct sshkey **keyp, int *perm_ok) |
{ |
{ |
Key *key, *pub; |
struct sshkey *key = NULL, *cert = NULL; |
|
int r; |
|
|
|
*keyp = NULL; |
|
|
switch (type) { |
switch (type) { |
#ifdef WITH_OPENSSL |
#ifdef WITH_OPENSSL |
case KEY_RSA: |
case KEY_RSA: |
case KEY_DSA: |
case KEY_DSA: |
case KEY_ECDSA: |
case KEY_ECDSA: |
#endif |
|
case KEY_ED25519: |
case KEY_ED25519: |
|
#endif /* WITH_OPENSSL */ |
|
case KEY_UNSPEC: |
break; |
break; |
default: |
default: |
error("%s: unsupported key type", __func__); |
return SSH_ERR_KEY_TYPE_UNKNOWN; |
return NULL; |
|
} |
} |
|
|
if ((key = key_load_private_type(type, filename, |
if ((r = sshkey_load_private_type(type, filename, |
passphrase, NULL, perm_ok)) == NULL) |
passphrase, &key, NULL, perm_ok)) != 0 || |
return NULL; |
(r = sshkey_load_cert(filename, &cert)) != 0) |
|
goto out; |
|
|
if ((pub = key_load_cert(filename)) == NULL) { |
|
key_free(key); |
|
return NULL; |
|
} |
|
|
|
/* Make sure the private key matches the certificate */ |
/* Make sure the private key matches the certificate */ |
if (key_equal_public(key, pub) == 0) { |
if (sshkey_equal_public(key, cert) == 0) { |
error("%s: certificate does not match private key %s", |
r = SSH_ERR_KEY_CERT_MISMATCH; |
__func__, filename); |
goto out; |
} else if (key_to_certified(key, key_cert_is_legacy(pub)) != 0) { |
|
error("%s: key_to_certified failed", __func__); |
|
} else { |
|
key_cert_copy(pub, key); |
|
key_free(pub); |
|
return key; |
|
} |
} |
|
|
key_free(key); |
if ((r = sshkey_to_certified(key, sshkey_cert_is_legacy(cert))) != 0 || |
key_free(pub); |
(r = sshkey_cert_copy(cert, key)) != 0) |
return NULL; |
goto out; |
|
r = 0; |
|
*keyp = key; |
|
key = NULL; |
|
out: |
|
if (key != NULL) |
|
sshkey_free(key); |
|
if (cert != NULL) |
|
sshkey_free(cert); |
|
return r; |
} |
} |
|
|
/* |
/* |
* Returns 1 if the specified "key" is listed in the file "filename", |
* Returns success if the specified "key" is listed in the file "filename", |
* 0 if the key is not listed or -1 on error. |
* SSH_ERR_KEY_NOT_FOUND: if the key is not listed or another error. |
* If strict_type is set then the key type must match exactly, |
* If strict_type is set then the key type must match exactly, |
* otherwise a comparison that ignores certficiate data is performed. |
* otherwise a comparison that ignores certficiate data is performed. |
*/ |
*/ |
int |
int |
key_in_file(Key *key, const char *filename, int strict_type) |
sshkey_in_file(struct sshkey *key, const char *filename, int strict_type) |
{ |
{ |
FILE *f; |
FILE *f; |
char line[SSH_MAX_PUBKEY_BYTES]; |
char line[SSH_MAX_PUBKEY_BYTES]; |
char *cp; |
char *cp; |
u_long linenum = 0; |
u_long linenum = 0; |
int ret = 0; |
int r = 0; |
Key *pub; |
struct sshkey *pub = NULL; |
int (*key_compare)(const Key *, const Key *) = strict_type ? |
int (*sshkey_compare)(const struct sshkey *, const struct sshkey *) = |
key_equal : key_equal_public; |
strict_type ? sshkey_equal : sshkey_equal_public; |
|
|
if ((f = fopen(filename, "r")) == NULL) { |
if ((f = fopen(filename, "r")) == NULL) { |
if (errno == ENOENT) { |
if (errno == ENOENT) |
debug("%s: keyfile \"%s\" missing", __func__, filename); |
return SSH_ERR_KEY_NOT_FOUND; |
return 0; |
else |
} else { |
return SSH_ERR_SYSTEM_ERROR; |
error("%s: could not open keyfile \"%s\": %s", __func__, |
|
filename, strerror(errno)); |
|
return -1; |
|
} |
|
} |
} |
|
|
while (read_keyfile_line(f, filename, line, sizeof(line), |
while (read_keyfile_line(f, filename, line, sizeof(line), |
&linenum) != -1) { |
&linenum) != -1) { |
cp = line; |
cp = line; |
|
|
/* Skip leading whitespace. */ |
/* Skip leading whitespace. */ |
|
|
continue; |
continue; |
} |
} |
|
|
pub = key_new(KEY_UNSPEC); |
if ((pub = sshkey_new(KEY_UNSPEC)) == NULL) { |
if (key_read(pub, &cp) != 1) { |
r = SSH_ERR_ALLOC_FAIL; |
key_free(pub); |
goto out; |
continue; |
|
} |
} |
if (key_compare(key, pub)) { |
if ((r = sshkey_read(pub, &cp)) != 0) |
ret = 1; |
goto out; |
key_free(pub); |
if (sshkey_compare(key, pub)) { |
break; |
r = 0; |
|
goto out; |
} |
} |
key_free(pub); |
sshkey_free(pub); |
|
pub = NULL; |
} |
} |
|
r = SSH_ERR_KEY_NOT_FOUND; |
|
out: |
|
if (pub != NULL) |
|
sshkey_free(pub); |
fclose(f); |
fclose(f); |
return ret; |
return r; |
} |
} |
|
|