diff options
author | Damien Miller <djm@cvs.openbsd.org> | 2010-11-21 10:57:08 +0000 |
---|---|---|
committer | Damien Miller <djm@cvs.openbsd.org> | 2010-11-21 10:57:08 +0000 |
commit | f3aacb484d20d0a0fe52a808a7892e5949e554df (patch) | |
tree | 92cd04f2062fcdbd9cf033c67e437f393e731de7 /usr.bin/ssh | |
parent | 8faebe9544a4fc6611d79806140e1a2f47b55e6a (diff) |
Refactor internals of private key loading and saving to work on memory
buffers rather than directly on files. This will make a few things
easier to do in the future; ok markus@
Diffstat (limited to 'usr.bin/ssh')
-rw-r--r-- | usr.bin/ssh/authfile.c | 447 |
1 files changed, 254 insertions, 193 deletions
diff --git a/usr.bin/ssh/authfile.c b/usr.bin/ssh/authfile.c index aa6b3989178..0f9d326008f 100644 --- a/usr.bin/ssh/authfile.c +++ b/usr.bin/ssh/authfile.c @@ -1,4 +1,4 @@ -/* $OpenBSD: authfile.c,v 1.85 2010/10/28 11:22:09 djm Exp $ */ +/* $OpenBSD: authfile.c,v 1.86 2010/11/21 10:57:07 djm Exp $ */ /* * Author: Tatu Ylonen <ylo@cs.hut.fi> * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland @@ -69,19 +69,18 @@ static const char authfile_id_string[] = "SSH PRIVATE KEY FILE FORMAT 1.1\n"; /* - * Saves the authentication (private) key in a file, encrypting it with - * passphrase. The identification of the file (lowest 64 bits of n) will + * 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_save_private_rsa1(Key *key, const char *filename, const char *passphrase, +key_private_rsa1_to_blob(Key *key, Buffer *blob, const char *passphrase, const char *comment) { Buffer buffer, encrypted; u_char buf[100], *cp; - int fd, i, cipher_num; + int i, cipher_num; CipherContext ciphercontext; Cipher *cipher; u_int32_t rnd; @@ -152,157 +151,216 @@ key_save_private_rsa1(Key *key, const char *filename, const char *passphrase, memset(buf, 0, sizeof(buf)); buffer_free(&buffer); - fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0600); - if (fd < 0) { - error("open %s failed: %s.", filename, strerror(errno)); - buffer_free(&encrypted); - return 0; - } - if (atomicio(vwrite, fd, buffer_ptr(&encrypted), - buffer_len(&encrypted)) != buffer_len(&encrypted)) { - error("write to key file %s failed: %s", filename, - strerror(errno)); - buffer_free(&encrypted); - close(fd); - unlink(filename); - return 0; - } - close(fd); + buffer_append(blob, buffer_ptr(&encrypted), buffer_len(&encrypted)); buffer_free(&encrypted); + return 1; } -/* save SSH v2 key in OpenSSL PEM format */ +/* convert SSH v2 key in OpenSSL PEM format */ static int -key_save_private_pem(Key *key, const char *filename, const char *_passphrase, +key_private_pem_to_blob(Key *key, Buffer *blob, const char *_passphrase, const char *comment) { - FILE *fp; - int fd; int success = 0; - int len = strlen(_passphrase); + 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; } - fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0600); - if (fd < 0) { - error("open %s failed: %s.", filename, strerror(errno)); - return 0; - } - fp = fdopen(fd, "w"); - if (fp == NULL) { - error("fdopen %s failed: %s.", filename, strerror(errno)); - close(fd); + 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_DSAPrivateKey(fp, key->dsa, + success = PEM_write_bio_DSAPrivateKey(bio, key->dsa, cipher, passphrase, len, NULL, NULL); break; case KEY_ECDSA: - success = PEM_write_ECPrivateKey(fp, key->ecdsa, + success = PEM_write_bio_ECPrivateKey(bio, key->ecdsa, cipher, passphrase, len, NULL, NULL); break; case KEY_RSA: - success = PEM_write_RSAPrivateKey(fp, key->rsa, + success = PEM_write_bio_RSAPrivateKey(bio, key->rsa, cipher, passphrase, len, NULL, NULL); break; } - fclose(fp); + if (success) { + if ((blen = BIO_get_mem_data(bio, &bptr)) <= 0) + success = 0; + else + buffer_append(blob, bptr, blen); + } + BIO_free(bio); return success; } -int -key_save_private(Key *key, const char *filename, const char *passphrase, +/* Save a key blob to a file */ +static int +key_save_private_blob(Buffer *keybuf, const char *filename) +{ + int fd; + + if ((fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0600)) < 0) { + error("open %s failed: %s.", filename, strerror(errno)); + return 0; + } + if (atomicio(vwrite, fd, buffer_ptr(keybuf), + buffer_len(keybuf)) != buffer_len(keybuf)) { + error("write to key file %s failed: %s", filename, + strerror(errno)); + close(fd); + unlink(filename); + return 0; + } + close(fd); + return 1; +} + +/* Serialise "key" to buffer "blob" */ +static int +key_private_to_blob(Key *key, Buffer *blob, const char *passphrase, const char *comment) { switch (key->type) { case KEY_RSA1: - return key_save_private_rsa1(key, filename, passphrase, - comment); + return key_private_rsa1_to_blob(key, blob, passphrase, comment); case KEY_DSA: case KEY_ECDSA: case KEY_RSA: - return key_save_private_pem(key, filename, passphrase, - comment); + return key_private_pem_to_blob(key, blob, passphrase, comment); default: - break; + error("%s: cannot save key type %d", __func__, key->type); + return 0; } - error("key_save_private: cannot save key type %d", key->type); - return 0; +} + +int +key_save_private(Key *key, const char *filename, const char *passphrase, + const char *comment) +{ + Buffer keyblob; + int success = 0; + + buffer_init(&keyblob); + if (!key_private_to_blob(key, &keyblob, passphrase, comment)) + goto out; + if (!key_save_private_blob(&keyblob, filename)) + goto out; + success = 1; + out: + buffer_free(&keyblob); + return success; } /* - * Loads the public part of the ssh v1 key file. Returns NULL if an error was - * encountered (the file does not exist or is not readable), and the key - * otherwise. + * Parse the public, unencrypted portion of a RSA1 key. */ - static Key * -key_load_public_rsa1(int fd, const char *filename, char **commentp) +key_parse_public_rsa1(Buffer *blob, char **commentp) { - Buffer buffer; Key *pub; - struct stat st; - char *cp; - u_int i; + + /* 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_consume(blob, sizeof(authfile_id_string)); + + /* Skip cipher type and reserved data. */ + (void) buffer_get_char(blob); /* cipher type */ + (void) buffer_get_int(blob); /* reserved */ + + /* Read the public key from the buffer. */ + (void) buffer_get_int(blob); + pub = key_new(KEY_RSA1); + buffer_get_bignum(blob, pub->rsa->n); + buffer_get_bignum(blob, pub->rsa->e); + if (commentp) + *commentp = buffer_get_string(blob, NULL); + /* The encrypted private part is not parsed by this function. */ + buffer_clear(blob); + + return pub; +} + +/* Load the contents of a key file into a buffer */ +static int +key_load_file(int fd, const char *filename, Buffer *blob) +{ size_t len; + u_char *cp; + struct stat st; if (fstat(fd, &st) < 0) { - error("fstat for key file %.200s failed: %.100s", - filename, strerror(errno)); - return NULL; + error("%s: fstat of key file %.200s%sfailed: %.100s", __func__, + filename == NULL ? "" : filename, + filename == NULL ? "" : " ", + strerror(errno)); + close(fd); + return 0; } if (st.st_size > 1*1024*1024) { - error("key file %.200s too large", filename); - return NULL; + error("%s: key file %.200s%stoo large", __func__, + filename == NULL ? "" : filename, + filename == NULL ? "" : " "); + close(fd); + return 0; } len = (size_t)st.st_size; /* truncated */ - buffer_init(&buffer); - cp = buffer_append_space(&buffer, len); + buffer_init(blob); + cp = buffer_append_space(blob, len); if (atomicio(read, fd, cp, len) != len) { - debug("Read from key file %.200s failed: %.100s", filename, + debug("%s: read from key file %.200s%sfailed: %.100s", __func__, + filename == NULL ? "" : filename, + filename == NULL ? "" : " ", strerror(errno)); - buffer_free(&buffer); - return NULL; + buffer_clear(blob); + close(fd); + return 0; } + return 1; +} - /* Check that it is at least big enough to contain the ID string. */ - if (len < sizeof(authfile_id_string)) { - debug3("Not a RSA1 key file %.200s.", filename); +/* + * Loads the public part of the ssh v1 key file. Returns NULL if an error was + * encountered (the file does not exist or is not readable), and the key + * otherwise. + */ +static Key * +key_load_public_rsa1(int fd, const char *filename, char **commentp) +{ + Buffer buffer; + Key *pub; + + buffer_init(&buffer); + if (!key_load_file(fd, filename, &buffer)) { buffer_free(&buffer); return NULL; } - /* - * Make sure it begins with the id string. Consume the id string - * from the buffer. - */ - for (i = 0; i < sizeof(authfile_id_string); i++) - if (buffer_get_char(&buffer) != authfile_id_string[i]) { - debug3("Not a RSA1 key file %.200s.", filename); - buffer_free(&buffer); - return NULL; - } - /* Skip cipher type and reserved data. */ - (void) buffer_get_char(&buffer); /* cipher type */ - (void) buffer_get_int(&buffer); /* reserved */ - - /* Read the public key from the buffer. */ - (void) buffer_get_int(&buffer); - pub = key_new(KEY_RSA1); - buffer_get_bignum(&buffer, pub->rsa->n); - buffer_get_bignum(&buffer, pub->rsa->e); - if (commentp) - *commentp = buffer_get_string(&buffer, NULL); - /* The encrypted private part is not parsed by this function. */ + pub = key_parse_public_rsa1(&buffer, commentp); + if (pub == NULL) + debug3("Could not load \"%s\" as a RSA1 public key", filename); buffer_free(&buffer); return pub; } @@ -325,113 +383,73 @@ key_load_public_type(int type, const char *filename, char **commentp) return NULL; } -/* - * Loads the private key from the file. Returns 0 if an error is encountered - * (file does not exist or is not readable, or passphrase is bad). This - * initializes the private key. - * Assumes we are called under uid of the owner of the file. - */ - static Key * -key_load_private_rsa1(int fd, const char *filename, const char *passphrase, - char **commentp) +key_parse_private_rsa1(Buffer *blob, const char *passphrase, char **commentp) { - u_int i; int check1, check2, cipher_type; - size_t len; - Buffer buffer, decrypted; + Buffer decrypted; u_char *cp; CipherContext ciphercontext; Cipher *cipher; Key *prv = NULL; - struct stat st; - - if (fstat(fd, &st) < 0) { - error("fstat for key file %.200s failed: %.100s", - filename, strerror(errno)); - close(fd); - return NULL; - } - if (st.st_size > 1*1024*1024) { - error("key file %.200s too large", filename); - close(fd); - return (NULL); - } - len = (size_t)st.st_size; /* truncated */ - - buffer_init(&buffer); - cp = buffer_append_space(&buffer, len); - - if (atomicio(read, fd, cp, len) != len) { - debug("Read from key file %.200s failed: %.100s", filename, - strerror(errno)); - buffer_free(&buffer); - close(fd); - return NULL; - } /* Check that it is at least big enough to contain the ID string. */ - if (len < sizeof(authfile_id_string)) { - debug3("Not a RSA1 key file %.200s.", filename); - buffer_free(&buffer); - close(fd); + 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. */ - for (i = 0; i < sizeof(authfile_id_string); i++) - if (buffer_get_char(&buffer) != authfile_id_string[i]) { - debug3("Not a RSA1 key file %.200s.", filename); - buffer_free(&buffer); - close(fd); - return NULL; - } + if (memcmp(buffer_ptr(blob), authfile_id_string, + sizeof(authfile_id_string)) != 0) { + debug3("Incorrect RSA1 identifier"); + return NULL; + } + buffer_consume(blob, sizeof(authfile_id_string)); /* Read cipher type. */ - cipher_type = buffer_get_char(&buffer); - (void) buffer_get_int(&buffer); /* Reserved data. */ + cipher_type = buffer_get_char(blob); + (void) buffer_get_int(blob); /* Reserved data. */ /* Read the public key from the buffer. */ - (void) buffer_get_int(&buffer); + (void) buffer_get_int(blob); prv = key_new_private(KEY_RSA1); - buffer_get_bignum(&buffer, prv->rsa->n); - buffer_get_bignum(&buffer, prv->rsa->e); + buffer_get_bignum(blob, prv->rsa->n); + buffer_get_bignum(blob, prv->rsa->e); if (commentp) - *commentp = buffer_get_string(&buffer, NULL); + *commentp = buffer_get_string(blob, NULL); else - xfree(buffer_get_string(&buffer, NULL)); + (void)buffer_get_string_ptr(blob, NULL); /* Check that it is a supported cipher. */ cipher = cipher_by_number(cipher_type); if (cipher == NULL) { - debug("Unsupported cipher %d used in key file %.200s.", - cipher_type, filename); - buffer_free(&buffer); + debug("Unsupported RSA1 cipher %d", cipher_type); goto fail; } /* Initialize space for decrypted data. */ buffer_init(&decrypted); - cp = buffer_append_space(&decrypted, buffer_len(&buffer)); + cp = buffer_append_space(&decrypted, buffer_len(blob)); /* Rest of the buffer is encrypted. Decrypt it using the passphrase. */ cipher_set_key_string(&ciphercontext, cipher, passphrase, CIPHER_DECRYPT); cipher_crypt(&ciphercontext, cp, - buffer_ptr(&buffer), buffer_len(&buffer)); + buffer_ptr(blob), buffer_len(blob)); cipher_cleanup(&ciphercontext); memset(&ciphercontext, 0, sizeof(ciphercontext)); - buffer_free(&buffer); + buffer_clear(blob); 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 key file %.200s.", - filename); + debug("Bad passphrase supplied for RSA1 key"); /* Bad passphrase. */ buffer_free(&decrypted); goto fail; @@ -450,38 +468,37 @@ key_load_private_rsa1(int fd, const char *filename, const char *passphrase, /* enable blinding */ if (RSA_blinding_on(prv->rsa, NULL) != 1) { - error("key_load_private_rsa1: RSA_blinding_on failed"); + error("%s: RSA_blinding_on failed", __func__); goto fail; } - close(fd); return prv; fail: if (commentp) xfree(*commentp); - close(fd); key_free(prv); return NULL; } -Key * -key_load_private_pem(int fd, int type, const char *passphrase, +static Key * +key_parse_private_pem(Buffer *blob, int type, const char *passphrase, char **commentp) { - FILE *fp; EVP_PKEY *pk = NULL; Key *prv = NULL; char *name = "<no key>"; + BIO *bio; - fp = fdopen(fd, "r"); - if (fp == NULL) { - error("fdopen failed: %s", strerror(errno)); - close(fd); + if ((bio = BIO_new_mem_buf(buffer_ptr(blob), + buffer_len(blob))) == NULL) { + error("%s: BIO_new_mem_buf failed", __func__); return NULL; } - pk = PEM_read_PrivateKey(fp, NULL, NULL, (char *)passphrase); + + pk = PEM_read_bio_PrivateKey(bio, NULL, NULL, (char *)passphrase); + BIO_free(bio); if (pk == NULL) { - debug("PEM_read_PrivateKey failed"); + debug("%s: PEM_read_PrivateKey failed", __func__); (void)ERR_get_error(); } else if (pk->type == EVP_PKEY_RSA && (type == KEY_UNSPEC||type==KEY_RSA)) { @@ -493,7 +510,7 @@ key_load_private_pem(int fd, int type, const char *passphrase, RSA_print_fp(stderr, prv->rsa, 8); #endif if (RSA_blinding_on(prv->rsa, NULL) != 1) { - error("key_load_private_pem: RSA_blinding_on failed"); + error("%s: RSA_blinding_on failed", __func__); key_free(prv); prv = NULL; } @@ -526,10 +543,9 @@ key_load_private_pem(int fd, int type, const char *passphrase, key_dump_ec_key(prv->ecdsa); #endif } else { - error("PEM_read_PrivateKey: mismatch or " - "unknown EVP_PKEY save_type %d", pk->save_type); + error("%s: PEM_read_PrivateKey: mismatch or " + "unknown EVP_PKEY save_type %d", __func__, pk->save_type); } - fclose(fp); if (pk != NULL) EVP_PKEY_free(pk); if (prv != NULL && commentp) @@ -539,6 +555,23 @@ key_load_private_pem(int fd, int type, const char *passphrase, return prv; } +Key * +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; +} + int key_perm_ok(int fd, const char *filename) { @@ -564,11 +597,31 @@ key_perm_ok(int fd, const char *filename) return 1; } +static Key * +key_parse_private_type(Buffer *blob, int type, const char *passphrase, + char **commentp) +{ + switch (type) { + case KEY_RSA1: + return key_parse_private_rsa1(blob, passphrase, commentp); + case KEY_DSA: + case KEY_ECDSA: + case KEY_RSA: + case KEY_UNSPEC: + return key_parse_private_pem(blob, type, passphrase, commentp); + default: + break; + } + return NULL; +} + Key * 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) { @@ -587,22 +640,17 @@ key_load_private_type(int type, const char *filename, const char *passphrase, } if (perm_ok != NULL) *perm_ok = 1; - switch (type) { - case KEY_RSA1: - return key_load_private_rsa1(fd, filename, passphrase, - commentp); - /* closes fd */ - case KEY_DSA: - case KEY_ECDSA: - case KEY_RSA: - case KEY_UNSPEC: - return key_load_private_pem(fd, type, passphrase, commentp); - /* closes fd */ - default: + + buffer_init(&buffer); + if (!key_load_file(fd, filename, &buffer)) { + buffer_free(&buffer); close(fd); - break; + return NULL; } - return NULL; + close(fd); + ret = key_parse_private_type(&buffer, type, passphrase, commentp); + buffer_free(&buffer); + return ret; } Key * @@ -610,6 +658,7 @@ key_load_private(const char *filename, const char *passphrase, char **commentp) { Key *pub, *prv; + Buffer buffer, pubcopy; int fd; fd = open(filename, O_RDONLY); @@ -623,20 +672,32 @@ key_load_private(const char *filename, const char *passphrase, close(fd); return NULL; } - pub = key_load_public_rsa1(fd, filename, commentp); - lseek(fd, (off_t) 0, SEEK_SET); /* rewind */ + + buffer_init(&buffer); + if (!key_load_file(fd, filename, &buffer)) { + buffer_free(&buffer); + close(fd); + return NULL; + } + close(fd); + + buffer_init(&pubcopy); + buffer_append(&pubcopy, buffer_ptr(&buffer), buffer_len(&buffer)); + /* it's a SSH v1 key if the public key part is readable */ + pub = key_parse_public_rsa1(&pubcopy, commentp); + buffer_free(&pubcopy); if (pub == NULL) { - /* closes fd */ - prv = key_load_private_pem(fd, KEY_UNSPEC, passphrase, 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 { - /* it's a SSH v1 key if the public key part is readable */ key_free(pub); - /* closes fd */ - prv = key_load_private_rsa1(fd, filename, passphrase, NULL); + prv = key_parse_private_type(&buffer, KEY_RSA1, passphrase, + commentp); } + buffer_free(&buffer); return prv; } |