| /* Copyright (C) 2007 The Written Word, Inc. |
| * Copyright (C) 2008, Simon Josefsson |
| * All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, |
| * with or without modification, are permitted provided |
| * that the following conditions are met: |
| * |
| * Redistributions of source code must retain the above |
| * copyright notice, this list of conditions and the |
| * following disclaimer. |
| * |
| * 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. |
| * |
| * Neither the name of the copyright holder nor the names |
| * of any other contributors may be used to endorse or |
| * promote products derived from this software without |
| * specific prior written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND |
| * CONTRIBUTORS "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 COPYRIGHT OWNER OR |
| * CONTRIBUTORS 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. |
| */ |
| |
| #include "libssh2_priv.h" |
| |
| static int |
| readline(char *line, int line_size, FILE * fp) |
| { |
| size_t len; |
| |
| if(!line) { |
| return -1; |
| } |
| if(!fgets(line, line_size, fp)) { |
| return -1; |
| } |
| |
| if(*line) { |
| len = strlen(line); |
| if(len > 0 && line[len - 1] == '\n') { |
| line[len - 1] = '\0'; |
| } |
| } |
| |
| if(*line) { |
| len = strlen(line); |
| if(len > 0 && line[len - 1] == '\r') { |
| line[len - 1] = '\0'; |
| } |
| } |
| |
| return 0; |
| } |
| |
| static int |
| readline_memory(char *line, size_t line_size, |
| const char *filedata, size_t filedata_len, |
| size_t *filedata_offset) |
| { |
| size_t off, len; |
| |
| off = *filedata_offset; |
| |
| for(len = 0; off + len < filedata_len && len < line_size - 1; len++) { |
| if(filedata[off + len] == '\n' || |
| filedata[off + len] == '\r') { |
| break; |
| } |
| } |
| |
| if(len) { |
| memcpy(line, filedata + off, len); |
| *filedata_offset += len; |
| } |
| |
| line[len] = '\0'; |
| *filedata_offset += 1; |
| |
| return 0; |
| } |
| |
| #define LINE_SIZE 128 |
| |
| static const char *crypt_annotation = "Proc-Type: 4,ENCRYPTED"; |
| |
| static unsigned char hex_decode(char digit) |
| { |
| return (digit >= 'A') ? 0xA + (digit - 'A') : (digit - '0'); |
| } |
| |
| int |
| _libssh2_pem_parse(LIBSSH2_SESSION * session, |
| const char *headerbegin, |
| const char *headerend, |
| const unsigned char *passphrase, |
| FILE * fp, unsigned char **data, unsigned int *datalen) |
| { |
| char line[LINE_SIZE]; |
| unsigned char iv[LINE_SIZE]; |
| char *b64data = NULL; |
| unsigned int b64datalen = 0; |
| int ret; |
| const LIBSSH2_CRYPT_METHOD *method = NULL; |
| |
| do { |
| *line = '\0'; |
| |
| if(readline(line, LINE_SIZE, fp)) { |
| return -1; |
| } |
| } |
| while(strcmp(line, headerbegin) != 0); |
| |
| if(readline(line, LINE_SIZE, fp)) { |
| return -1; |
| } |
| |
| if(passphrase && |
| memcmp(line, crypt_annotation, strlen(crypt_annotation)) == 0) { |
| const LIBSSH2_CRYPT_METHOD **all_methods, *cur_method; |
| int i; |
| |
| if(readline(line, LINE_SIZE, fp)) { |
| ret = -1; |
| goto out; |
| } |
| |
| all_methods = libssh2_crypt_methods(); |
| while((cur_method = *all_methods++)) { |
| if(*cur_method->pem_annotation && |
| memcmp(line, cur_method->pem_annotation, |
| strlen(cur_method->pem_annotation)) == 0) { |
| method = cur_method; |
| memcpy(iv, line + strlen(method->pem_annotation) + 1, |
| 2*method->iv_len); |
| } |
| } |
| |
| /* None of the available crypt methods were able to decrypt the key */ |
| if(method == NULL) |
| return -1; |
| |
| /* Decode IV from hex */ |
| for(i = 0; i < method->iv_len; ++i) { |
| iv[i] = hex_decode(iv[2*i]) << 4; |
| iv[i] |= hex_decode(iv[2*i + 1]); |
| } |
| |
| /* skip to the next line */ |
| if(readline(line, LINE_SIZE, fp)) { |
| ret = -1; |
| goto out; |
| } |
| } |
| |
| do { |
| if(*line) { |
| char *tmp; |
| size_t linelen; |
| |
| linelen = strlen(line); |
| tmp = LIBSSH2_REALLOC(session, b64data, b64datalen + linelen); |
| if(!tmp) { |
| _libssh2_error(session, LIBSSH2_ERROR_ALLOC, |
| "Unable to allocate memory for PEM parsing"); |
| ret = -1; |
| goto out; |
| } |
| memcpy(tmp + b64datalen, line, linelen); |
| b64data = tmp; |
| b64datalen += linelen; |
| } |
| |
| *line = '\0'; |
| |
| if(readline(line, LINE_SIZE, fp)) { |
| ret = -1; |
| goto out; |
| } |
| } while(strcmp(line, headerend) != 0); |
| |
| if(!b64data) { |
| return -1; |
| } |
| |
| if(libssh2_base64_decode(session, (char **) data, datalen, |
| b64data, b64datalen)) { |
| ret = -1; |
| goto out; |
| } |
| |
| if(method) { |
| /* Set up decryption */ |
| int free_iv = 0, free_secret = 0, len_decrypted = 0, padding = 0; |
| int blocksize = method->blocksize; |
| void *abstract; |
| unsigned char secret[2*MD5_DIGEST_LENGTH]; |
| libssh2_md5_ctx fingerprint_ctx; |
| |
| /* Perform key derivation (PBKDF1/MD5) */ |
| if(!libssh2_md5_init(&fingerprint_ctx)) { |
| ret = -1; |
| goto out; |
| } |
| libssh2_md5_update(fingerprint_ctx, passphrase, |
| strlen((char *)passphrase)); |
| libssh2_md5_update(fingerprint_ctx, iv, 8); |
| libssh2_md5_final(fingerprint_ctx, secret); |
| if(method->secret_len > MD5_DIGEST_LENGTH) { |
| if(!libssh2_md5_init(&fingerprint_ctx)) { |
| ret = -1; |
| goto out; |
| } |
| libssh2_md5_update(fingerprint_ctx, secret, MD5_DIGEST_LENGTH); |
| libssh2_md5_update(fingerprint_ctx, passphrase, |
| strlen((char *)passphrase)); |
| libssh2_md5_update(fingerprint_ctx, iv, 8); |
| libssh2_md5_final(fingerprint_ctx, secret + MD5_DIGEST_LENGTH); |
| } |
| |
| /* Initialize the decryption */ |
| if(method->init(session, method, iv, &free_iv, secret, |
| &free_secret, 0, &abstract)) { |
| _libssh2_explicit_zero((char *)secret, sizeof(secret)); |
| LIBSSH2_FREE(session, data); |
| ret = -1; |
| goto out; |
| } |
| |
| if(free_secret) { |
| _libssh2_explicit_zero((char *)secret, sizeof(secret)); |
| } |
| |
| /* Do the actual decryption */ |
| if((*datalen % blocksize) != 0) { |
| _libssh2_explicit_zero((char *)secret, sizeof(secret)); |
| method->dtor(session, &abstract); |
| _libssh2_explicit_zero(*data, *datalen); |
| LIBSSH2_FREE(session, *data); |
| ret = -1; |
| goto out; |
| } |
| |
| while(len_decrypted <= (int)*datalen - blocksize) { |
| if(method->crypt(session, *data + len_decrypted, blocksize, |
| &abstract)) { |
| ret = LIBSSH2_ERROR_DECRYPT; |
| _libssh2_explicit_zero((char *)secret, sizeof(secret)); |
| method->dtor(session, &abstract); |
| _libssh2_explicit_zero(*data, *datalen); |
| LIBSSH2_FREE(session, *data); |
| goto out; |
| } |
| |
| len_decrypted += blocksize; |
| } |
| |
| /* Account for padding */ |
| padding = (*data)[*datalen - 1]; |
| memset(&(*data)[*datalen-padding], 0, padding); |
| *datalen -= padding; |
| |
| /* Clean up */ |
| _libssh2_explicit_zero((char *)secret, sizeof(secret)); |
| method->dtor(session, &abstract); |
| } |
| |
| ret = 0; |
| out: |
| if(b64data) { |
| _libssh2_explicit_zero(b64data, b64datalen); |
| LIBSSH2_FREE(session, b64data); |
| } |
| return ret; |
| } |
| |
| int |
| _libssh2_pem_parse_memory(LIBSSH2_SESSION * session, |
| const char *headerbegin, |
| const char *headerend, |
| const char *filedata, size_t filedata_len, |
| unsigned char **data, unsigned int *datalen) |
| { |
| char line[LINE_SIZE]; |
| char *b64data = NULL; |
| unsigned int b64datalen = 0; |
| size_t off = 0; |
| int ret; |
| |
| do { |
| *line = '\0'; |
| |
| if(readline_memory(line, LINE_SIZE, filedata, filedata_len, &off)) { |
| return -1; |
| } |
| } |
| while(strcmp(line, headerbegin) != 0); |
| |
| *line = '\0'; |
| |
| do { |
| if(*line) { |
| char *tmp; |
| size_t linelen; |
| |
| linelen = strlen(line); |
| tmp = LIBSSH2_REALLOC(session, b64data, b64datalen + linelen); |
| if(!tmp) { |
| _libssh2_error(session, LIBSSH2_ERROR_ALLOC, |
| "Unable to allocate memory for PEM parsing"); |
| ret = -1; |
| goto out; |
| } |
| memcpy(tmp + b64datalen, line, linelen); |
| b64data = tmp; |
| b64datalen += linelen; |
| } |
| |
| *line = '\0'; |
| |
| if(readline_memory(line, LINE_SIZE, filedata, filedata_len, &off)) { |
| ret = -1; |
| goto out; |
| } |
| } while(strcmp(line, headerend) != 0); |
| |
| if(!b64data) { |
| return -1; |
| } |
| |
| if(libssh2_base64_decode(session, (char **) data, datalen, |
| b64data, b64datalen)) { |
| ret = -1; |
| goto out; |
| } |
| |
| ret = 0; |
| out: |
| if(b64data) { |
| _libssh2_explicit_zero(b64data, b64datalen); |
| LIBSSH2_FREE(session, b64data); |
| } |
| return ret; |
| } |
| |
| /* OpenSSH formatted keys */ |
| #define AUTH_MAGIC "openssh-key-v1" |
| #define OPENSSH_HEADER_BEGIN "-----BEGIN OPENSSH PRIVATE KEY-----" |
| #define OPENSSH_HEADER_END "-----END OPENSSH PRIVATE KEY-----" |
| |
| static int |
| _libssh2_openssh_pem_parse_data(LIBSSH2_SESSION * session, |
| const unsigned char *passphrase, |
| const char *b64data, size_t b64datalen, |
| struct string_buf **decrypted_buf) |
| { |
| const LIBSSH2_CRYPT_METHOD *method = NULL; |
| struct string_buf decoded, decrypted, kdf_buf; |
| unsigned char *ciphername = NULL; |
| unsigned char *kdfname = NULL; |
| unsigned char *kdf = NULL; |
| unsigned char *buf = NULL; |
| unsigned char *salt = NULL; |
| uint32_t nkeys, check1, check2; |
| uint32_t rounds = 0; |
| unsigned char *key = NULL; |
| unsigned char *key_part = NULL; |
| unsigned char *iv_part = NULL; |
| unsigned char *f = NULL; |
| unsigned int f_len = 0; |
| int ret = 0, keylen = 0, ivlen = 0, total_len = 0; |
| size_t kdf_len = 0, tmp_len = 0, salt_len = 0; |
| |
| if(decrypted_buf) |
| *decrypted_buf = NULL; |
| |
| /* decode file */ |
| if(libssh2_base64_decode(session, (char **)&f, &f_len, |
| b64data, b64datalen)) { |
| ret = -1; |
| goto out; |
| } |
| |
| /* Parse the file */ |
| decoded.data = (unsigned char *)f; |
| decoded.dataptr = (unsigned char *)f; |
| decoded.len = f_len; |
| |
| if(decoded.len < strlen(AUTH_MAGIC)) { |
| ret = _libssh2_error(session, LIBSSH2_ERROR_PROTO, "key too short"); |
| goto out; |
| } |
| |
| if(strncmp((char *) decoded.dataptr, AUTH_MAGIC, |
| strlen(AUTH_MAGIC)) != 0) { |
| ret = _libssh2_error(session, LIBSSH2_ERROR_PROTO, |
| "key auth magic mismatch"); |
| goto out; |
| } |
| |
| decoded.dataptr += strlen(AUTH_MAGIC) + 1; |
| |
| if(_libssh2_get_string(&decoded, &ciphername, &tmp_len) || |
| tmp_len == 0) { |
| ret = _libssh2_error(session, LIBSSH2_ERROR_PROTO, |
| "ciphername is missing"); |
| goto out; |
| } |
| |
| if(_libssh2_get_string(&decoded, &kdfname, &tmp_len) || |
| tmp_len == 0) { |
| ret = _libssh2_error(session, LIBSSH2_ERROR_PROTO, |
| "kdfname is missing"); |
| goto out; |
| } |
| |
| if(_libssh2_get_string(&decoded, &kdf, &kdf_len)) { |
| ret = _libssh2_error(session, LIBSSH2_ERROR_PROTO, |
| "kdf is missing"); |
| goto out; |
| } |
| else { |
| kdf_buf.data = kdf; |
| kdf_buf.dataptr = kdf; |
| kdf_buf.len = kdf_len; |
| } |
| |
| if((passphrase == NULL || strlen((const char *)passphrase) == 0) && |
| strcmp((const char *)ciphername, "none") != 0) { |
| /* passphrase required */ |
| ret = LIBSSH2_ERROR_KEYFILE_AUTH_FAILED; |
| goto out; |
| } |
| |
| if(strcmp((const char *)kdfname, "none") != 0 && |
| strcmp((const char *)kdfname, "bcrypt") != 0) { |
| ret = _libssh2_error(session, LIBSSH2_ERROR_PROTO, |
| "unknown cipher"); |
| goto out; |
| } |
| |
| if(!strcmp((const char *)kdfname, "none") && |
| strcmp((const char *)ciphername, "none") != 0) { |
| ret =_libssh2_error(session, LIBSSH2_ERROR_PROTO, |
| "invalid format"); |
| goto out; |
| } |
| |
| if(_libssh2_get_u32(&decoded, &nkeys) != 0 || nkeys != 1) { |
| ret = _libssh2_error(session, LIBSSH2_ERROR_PROTO, |
| "Multiple keys are unsupported"); |
| goto out; |
| } |
| |
| /* unencrypted public key */ |
| |
| if(_libssh2_get_string(&decoded, &buf, &tmp_len) || tmp_len == 0) { |
| ret = _libssh2_error(session, LIBSSH2_ERROR_PROTO, |
| "Invalid private key; " |
| "expect embedded public key"); |
| goto out; |
| } |
| |
| if(_libssh2_get_string(&decoded, &buf, &tmp_len) || tmp_len == 0) { |
| ret = _libssh2_error(session, LIBSSH2_ERROR_PROTO, |
| "Private key data not found"); |
| goto out; |
| } |
| |
| /* decode encrypted private key */ |
| decrypted.data = decrypted.dataptr = buf; |
| decrypted.len = tmp_len; |
| |
| if(ciphername && strcmp((const char *)ciphername, "none") != 0) { |
| const LIBSSH2_CRYPT_METHOD **all_methods, *cur_method; |
| |
| all_methods = libssh2_crypt_methods(); |
| while((cur_method = *all_methods++)) { |
| if(*cur_method->name && |
| memcmp(ciphername, cur_method->name, |
| strlen(cur_method->name)) == 0) { |
| method = cur_method; |
| } |
| } |
| |
| /* None of the available crypt methods were able to decrypt the key */ |
| |
| if(method == NULL) { |
| ret = _libssh2_error(session, LIBSSH2_ERROR_PROTO, |
| "No supported cipher found"); |
| goto out; |
| } |
| } |
| |
| if(method) { |
| int free_iv = 0, free_secret = 0, len_decrypted = 0; |
| int blocksize; |
| void *abstract = NULL; |
| |
| keylen = method->secret_len; |
| ivlen = method->iv_len; |
| total_len = keylen + ivlen; |
| |
| key = LIBSSH2_CALLOC(session, total_len); |
| if(key == NULL) { |
| ret = _libssh2_error(session, LIBSSH2_ERROR_PROTO, |
| "Could not alloc key"); |
| goto out; |
| } |
| |
| if(strcmp((const char *)kdfname, "bcrypt") == 0 && |
| passphrase != NULL) { |
| if((_libssh2_get_string(&kdf_buf, &salt, &salt_len)) || |
| (_libssh2_get_u32(&kdf_buf, &rounds) != 0) ) { |
| ret = _libssh2_error(session, LIBSSH2_ERROR_PROTO, |
| "kdf contains unexpected values"); |
| LIBSSH2_FREE(session, key); |
| goto out; |
| } |
| |
| if(_libssh2_bcrypt_pbkdf((const char *)passphrase, |
| strlen((const char *)passphrase), |
| salt, salt_len, key, |
| keylen + ivlen, rounds) < 0) { |
| ret = _libssh2_error(session, LIBSSH2_ERROR_DECRYPT, |
| "invalid format"); |
| LIBSSH2_FREE(session, key); |
| goto out; |
| } |
| } |
| else { |
| ret = _libssh2_error(session, LIBSSH2_ERROR_KEYFILE_AUTH_FAILED, |
| "bcrypted without passphrase"); |
| LIBSSH2_FREE(session, key); |
| goto out; |
| } |
| |
| /* Set up decryption */ |
| blocksize = method->blocksize; |
| |
| key_part = LIBSSH2_CALLOC(session, keylen); |
| if(key_part == NULL) { |
| ret = _libssh2_error(session, LIBSSH2_ERROR_PROTO, |
| "Could not alloc key part"); |
| goto out; |
| } |
| |
| iv_part = LIBSSH2_CALLOC(session, ivlen); |
| if(iv_part == NULL) { |
| ret = _libssh2_error(session, LIBSSH2_ERROR_PROTO, |
| "Could not alloc iv part"); |
| goto out; |
| } |
| |
| memcpy(key_part, key, keylen); |
| memcpy(iv_part, key + keylen, ivlen); |
| |
| /* Initialize the decryption */ |
| if(method->init(session, method, iv_part, &free_iv, key_part, |
| &free_secret, 0, &abstract)) { |
| ret = LIBSSH2_ERROR_DECRYPT; |
| goto out; |
| } |
| |
| /* Do the actual decryption */ |
| if((decrypted.len % blocksize) != 0) { |
| method->dtor(session, &abstract); |
| ret = LIBSSH2_ERROR_DECRYPT; |
| goto out; |
| } |
| |
| while((size_t)len_decrypted <= decrypted.len - blocksize) { |
| if(method->crypt(session, decrypted.data + len_decrypted, |
| blocksize, |
| &abstract)) { |
| ret = LIBSSH2_ERROR_DECRYPT; |
| method->dtor(session, &abstract); |
| goto out; |
| } |
| |
| len_decrypted += blocksize; |
| } |
| |
| /* No padding */ |
| |
| method->dtor(session, &abstract); |
| } |
| |
| /* Check random bytes match */ |
| |
| if(_libssh2_get_u32(&decrypted, &check1) != 0 || |
| _libssh2_get_u32(&decrypted, &check2) != 0 || |
| check1 != check2) { |
| _libssh2_error(session, LIBSSH2_ERROR_PROTO, |
| "Private key unpack failed (correct password?)"); |
| ret = LIBSSH2_ERROR_KEYFILE_AUTH_FAILED; |
| goto out; |
| } |
| |
| if(decrypted_buf != NULL) { |
| /* copy data to out-going buffer */ |
| struct string_buf *out_buf = _libssh2_string_buf_new(session); |
| if(!out_buf) { |
| ret = _libssh2_error(session, LIBSSH2_ERROR_ALLOC, |
| "Unable to allocate memory for " |
| "decrypted struct"); |
| goto out; |
| } |
| |
| out_buf->data = LIBSSH2_CALLOC(session, decrypted.len); |
| if(out_buf->data == NULL) { |
| ret = _libssh2_error(session, LIBSSH2_ERROR_ALLOC, |
| "Unable to allocate memory for " |
| "decrypted struct"); |
| _libssh2_string_buf_free(session, out_buf); |
| goto out; |
| } |
| memcpy(out_buf->data, decrypted.data, decrypted.len); |
| out_buf->dataptr = out_buf->data + |
| (decrypted.dataptr - decrypted.data); |
| out_buf->len = decrypted.len; |
| |
| *decrypted_buf = out_buf; |
| } |
| |
| out: |
| |
| /* Clean up */ |
| if(key) { |
| _libssh2_explicit_zero(key, total_len); |
| LIBSSH2_FREE(session, key); |
| } |
| if(key_part) { |
| _libssh2_explicit_zero(key_part, keylen); |
| LIBSSH2_FREE(session, key_part); |
| } |
| if(iv_part) { |
| _libssh2_explicit_zero(iv_part, ivlen); |
| LIBSSH2_FREE(session, iv_part); |
| } |
| if(f) { |
| _libssh2_explicit_zero(f, f_len); |
| LIBSSH2_FREE(session, f); |
| } |
| |
| return ret; |
| } |
| |
| int |
| _libssh2_openssh_pem_parse(LIBSSH2_SESSION * session, |
| const unsigned char *passphrase, |
| FILE * fp, struct string_buf **decrypted_buf) |
| { |
| char line[LINE_SIZE]; |
| char *b64data = NULL; |
| unsigned int b64datalen = 0; |
| int ret = 0; |
| |
| /* read file */ |
| |
| do { |
| *line = '\0'; |
| |
| if(readline(line, LINE_SIZE, fp)) { |
| return -1; |
| } |
| } |
| while(strcmp(line, OPENSSH_HEADER_BEGIN) != 0); |
| |
| if(readline(line, LINE_SIZE, fp)) { |
| return -1; |
| } |
| |
| do { |
| if(*line) { |
| char *tmp; |
| size_t linelen; |
| |
| linelen = strlen(line); |
| tmp = LIBSSH2_REALLOC(session, b64data, b64datalen + linelen); |
| if(!tmp) { |
| _libssh2_error(session, LIBSSH2_ERROR_ALLOC, |
| "Unable to allocate memory for PEM parsing"); |
| ret = -1; |
| goto out; |
| } |
| memcpy(tmp + b64datalen, line, linelen); |
| b64data = tmp; |
| b64datalen += linelen; |
| } |
| |
| *line = '\0'; |
| |
| if(readline(line, LINE_SIZE, fp)) { |
| ret = -1; |
| goto out; |
| } |
| } while(strcmp(line, OPENSSH_HEADER_END) != 0); |
| |
| if(!b64data) { |
| return -1; |
| } |
| |
| ret = _libssh2_openssh_pem_parse_data(session, |
| passphrase, |
| (const char *)b64data, |
| (size_t)b64datalen, |
| decrypted_buf); |
| |
| if(b64data) { |
| _libssh2_explicit_zero(b64data, b64datalen); |
| LIBSSH2_FREE(session, b64data); |
| } |
| |
| out: |
| |
| return ret; |
| } |
| |
| int |
| _libssh2_openssh_pem_parse_memory(LIBSSH2_SESSION * session, |
| const unsigned char *passphrase, |
| const char *filedata, size_t filedata_len, |
| struct string_buf **decrypted_buf) |
| { |
| char line[LINE_SIZE]; |
| char *b64data = NULL; |
| unsigned int b64datalen = 0; |
| size_t off = 0; |
| int ret; |
| |
| if(filedata == NULL || filedata_len <= 0) |
| return _libssh2_error(session, LIBSSH2_ERROR_PROTO, |
| "Error parsing PEM: filedata missing"); |
| |
| do { |
| |
| *line = '\0'; |
| |
| if(off >= filedata_len) |
| return _libssh2_error(session, LIBSSH2_ERROR_PROTO, |
| "Error parsing PEM: offset out of bounds"); |
| |
| if(readline_memory(line, LINE_SIZE, filedata, filedata_len, &off)) { |
| return -1; |
| } |
| } |
| while(strcmp(line, OPENSSH_HEADER_BEGIN) != 0); |
| |
| *line = '\0'; |
| |
| do { |
| if (*line) { |
| char *tmp; |
| size_t linelen; |
| |
| linelen = strlen(line); |
| tmp = LIBSSH2_REALLOC(session, b64data, b64datalen + linelen); |
| if(!tmp) { |
| ret = _libssh2_error(session, LIBSSH2_ERROR_ALLOC, |
| "Unable to allocate memory for " |
| "PEM parsing"); |
| goto out; |
| } |
| memcpy(tmp + b64datalen, line, linelen); |
| b64data = tmp; |
| b64datalen += linelen; |
| } |
| |
| *line = '\0'; |
| |
| if(off >= filedata_len) { |
| ret = _libssh2_error(session, LIBSSH2_ERROR_PROTO, |
| "Error parsing PEM: offset out of bounds"); |
| goto out; |
| } |
| |
| if(readline_memory(line, LINE_SIZE, filedata, filedata_len, &off)) { |
| ret = -1; |
| goto out; |
| } |
| } while(strcmp(line, OPENSSH_HEADER_END) != 0); |
| |
| if(!b64data) |
| return _libssh2_error(session, LIBSSH2_ERROR_PROTO, |
| "Error parsing PEM: base 64 data missing"); |
| |
| ret = _libssh2_openssh_pem_parse_data(session, passphrase, b64data, |
| b64datalen, decrypted_buf); |
| |
| out: |
| if(b64data) { |
| _libssh2_explicit_zero(b64data, b64datalen); |
| LIBSSH2_FREE(session, b64data); |
| } |
| return ret; |
| |
| } |
| |
| static int |
| read_asn1_length(const unsigned char *data, |
| unsigned int datalen, unsigned int *len) |
| { |
| unsigned int lenlen; |
| int nextpos; |
| |
| if(datalen < 1) { |
| return -1; |
| } |
| *len = data[0]; |
| |
| if(*len >= 0x80) { |
| lenlen = *len & 0x7F; |
| *len = data[1]; |
| if(1 + lenlen > datalen) { |
| return -1; |
| } |
| if(lenlen > 1) { |
| *len <<= 8; |
| *len |= data[2]; |
| } |
| } |
| else { |
| lenlen = 0; |
| } |
| |
| nextpos = 1 + lenlen; |
| if(lenlen > 2 || 1 + lenlen + *len > datalen) { |
| return -1; |
| } |
| |
| return nextpos; |
| } |
| |
| int |
| _libssh2_pem_decode_sequence(unsigned char **data, unsigned int *datalen) |
| { |
| unsigned int len; |
| int lenlen; |
| |
| if(*datalen < 1) { |
| return -1; |
| } |
| |
| if((*data)[0] != '\x30') { |
| return -1; |
| } |
| |
| (*data)++; |
| (*datalen)--; |
| |
| lenlen = read_asn1_length(*data, *datalen, &len); |
| if(lenlen < 0 || lenlen + len != *datalen) { |
| return -1; |
| } |
| |
| *data += lenlen; |
| *datalen -= lenlen; |
| |
| return 0; |
| } |
| |
| int |
| _libssh2_pem_decode_integer(unsigned char **data, unsigned int *datalen, |
| unsigned char **i, unsigned int *ilen) |
| { |
| unsigned int len; |
| int lenlen; |
| |
| if(*datalen < 1) { |
| return -1; |
| } |
| |
| if((*data)[0] != '\x02') { |
| return -1; |
| } |
| |
| (*data)++; |
| (*datalen)--; |
| |
| lenlen = read_asn1_length(*data, *datalen, &len); |
| if(lenlen < 0 || lenlen + len > *datalen) { |
| return -1; |
| } |
| |
| *data += lenlen; |
| *datalen -= lenlen; |
| |
| *i = *data; |
| *ilen = len; |
| |
| *data += len; |
| *datalen -= len; |
| |
| return 0; |
| } |