diff options
Diffstat (limited to 'libs/libssh2/src/pem.c')
-rw-r--r-- | libs/libssh2/src/pem.c | 658 |
1 files changed, 618 insertions, 40 deletions
diff --git a/libs/libssh2/src/pem.c b/libs/libssh2/src/pem.c index 9f51bba3b5..53f58c2ef1 100644 --- a/libs/libssh2/src/pem.c +++ b/libs/libssh2/src/pem.c @@ -43,23 +43,23 @@ readline(char *line, int line_size, FILE * fp) { size_t len; - if (!line) { + if(!line) { return -1; } - if (!fgets(line, line_size, fp)) { + if(!fgets(line, line_size, fp)) { return -1; } - if (*line) { + if(*line) { len = strlen(line); - if (len > 0 && line[len - 1] == '\n') { + if(len > 0 && line[len - 1] == '\n') { line[len - 1] = '\0'; } } - if (*line) { + if(*line) { len = strlen(line); - if (len > 0 && line[len - 1] == '\r') { + if(len > 0 && line[len - 1] == '\r') { line[len - 1] = '\0'; } } @@ -76,14 +76,14 @@ readline_memory(char *line, size_t line_size, off = *filedata_offset; - for (len = 0; off + len < filedata_len && len < line_size; len++) { - if (filedata[off + len] == '\n' || + for(len = 0; off + len < filedata_len && len < line_size - 1; len++) { + if(filedata[off + len] == '\n' || filedata[off + len] == '\r') { break; } } - if (len) { + if(len) { memcpy(line, filedata + off, len); *filedata_offset += len; } @@ -96,36 +96,86 @@ readline_memory(char *line, size_t line_size, #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)) { + if(readline(line, LINE_SIZE, fp)) { return -1; } } - while (strcmp(line, headerbegin) != 0); + while(strcmp(line, headerbegin) != 0); - *line = '\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) { + if(*line) { char *tmp; size_t linelen; linelen = strlen(line); tmp = LIBSSH2_REALLOC(session, b64data, b64datalen + linelen); - if (!tmp) { + if(!tmp) { ret = -1; goto out; } @@ -136,25 +186,102 @@ _libssh2_pem_parse(LIBSSH2_SESSION * session, *line = '\0'; - if (readline(line, LINE_SIZE, fp)) { + if(readline(line, LINE_SIZE, fp)) { ret = -1; goto out; } - } while (strcmp(line, headerend) != 0); + } while(strcmp(line, headerend) != 0); - if (!b64data) { + if(!b64data) { return -1; } - if (libssh2_base64_decode(session, (char**) data, datalen, + 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) { + if(b64data) { + _libssh2_explicit_zero(b64data, b64datalen); LIBSSH2_FREE(session, b64data); } return ret; @@ -176,22 +303,22 @@ _libssh2_pem_parse_memory(LIBSSH2_SESSION * session, do { *line = '\0'; - if (readline_memory(line, LINE_SIZE, filedata, filedata_len, &off)) { + if(readline_memory(line, LINE_SIZE, filedata, filedata_len, &off)) { return -1; } } - while (strcmp(line, headerbegin) != 0); + while(strcmp(line, headerbegin) != 0); *line = '\0'; do { - if (*line) { + if(*line) { char *tmp; size_t linelen; linelen = strlen(line); tmp = LIBSSH2_REALLOC(session, b64data, b64datalen + linelen); - if (!tmp) { + if(!tmp) { ret = -1; goto out; } @@ -202,17 +329,17 @@ _libssh2_pem_parse_memory(LIBSSH2_SESSION * session, *line = '\0'; - if (readline_memory(line, LINE_SIZE, filedata, filedata_len, &off)) { + if(readline_memory(line, LINE_SIZE, filedata, filedata_len, &off)) { ret = -1; goto out; } - } while (strcmp(line, headerend) != 0); + } while(strcmp(line, headerend) != 0); - if (!b64data) { + if(!b64data) { return -1; } - if (libssh2_base64_decode(session, (char**) data, datalen, + if(libssh2_base64_decode(session, (char **) data, datalen, b64data, b64datalen)) { ret = -1; goto out; @@ -220,12 +347,462 @@ _libssh2_pem_parse_memory(LIBSSH2_SESSION * session, ret = 0; out: - if (b64data) { + 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) { + 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 -1; + } + + do { + + *line = '\0'; + + if(off >= filedata_len) { + return -1; + } + + 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 = -1; + goto out; + } + memcpy(tmp + b64datalen, line, linelen); + b64data = tmp; + b64datalen += linelen; + } + + *line = '\0'; + + if(off >= filedata_len) { + ret = -1; + 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 -1; + } + + 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) @@ -233,27 +810,28 @@ read_asn1_length(const unsigned char *data, unsigned int lenlen; int nextpos; - if (datalen < 1) { + if(datalen < 1) { return -1; } *len = data[0]; - if (*len >= 0x80) { + if(*len >= 0x80) { lenlen = *len & 0x7F; *len = data[1]; - if (1 + lenlen > datalen) { + if(1 + lenlen > datalen) { return -1; } - if (lenlen > 1) { + if(lenlen > 1) { *len <<= 8; *len |= data[2]; } - } else { + } + else { lenlen = 0; } nextpos = 1 + lenlen; - if (lenlen > 2 || 1 + lenlen + *len > datalen) { + if(lenlen > 2 || 1 + lenlen + *len > datalen) { return -1; } @@ -266,11 +844,11 @@ _libssh2_pem_decode_sequence(unsigned char **data, unsigned int *datalen) unsigned int len; int lenlen; - if (*datalen < 1) { + if(*datalen < 1) { return -1; } - if ((*data)[0] != '\x30') { + if((*data)[0] != '\x30') { return -1; } @@ -278,7 +856,7 @@ _libssh2_pem_decode_sequence(unsigned char **data, unsigned int *datalen) (*datalen)--; lenlen = read_asn1_length(*data, *datalen, &len); - if (lenlen < 0 || lenlen + len != *datalen) { + if(lenlen < 0 || lenlen + len != *datalen) { return -1; } @@ -295,11 +873,11 @@ _libssh2_pem_decode_integer(unsigned char **data, unsigned int *datalen, unsigned int len; int lenlen; - if (*datalen < 1) { + if(*datalen < 1) { return -1; } - if ((*data)[0] != '\x02') { + if((*data)[0] != '\x02') { return -1; } @@ -307,7 +885,7 @@ _libssh2_pem_decode_integer(unsigned char **data, unsigned int *datalen, (*datalen)--; lenlen = read_asn1_length(*data, *datalen, &len); - if (lenlen < 0 || lenlen + len > *datalen) { + if(lenlen < 0 || lenlen + len > *datalen) { return -1; } |