Signed-off-by: Ilari Liusvaara <ilari.liusvaara@xxxxxxxxxxx> --- git-over-tls/pem.c | 362 ++++++++++++++++++++++++ git-over-tls/pem.h | 16 + git-over-tls/pem_decrypt.c | 203 ++++++++++++++ git-over-tls/pem_decrypt.h | 9 + git-over-tls/ssh.c | 635 +++++++++++++++++++++++++++++++++++++++++++ git-over-tls/ssh.h | 18 ++ git-over-tls/ssh_localkey.c | 435 +++++++++++++++++++++++++++++ git-over-tls/ssh_localkey.h | 18 ++ 8 files changed, 1696 insertions(+), 0 deletions(-) create mode 100644 git-over-tls/pem.c create mode 100644 git-over-tls/pem.h create mode 100644 git-over-tls/pem_decrypt.c create mode 100644 git-over-tls/pem_decrypt.h create mode 100644 git-over-tls/ssh.c create mode 100644 git-over-tls/ssh.h create mode 100644 git-over-tls/ssh_localkey.c create mode 100644 git-over-tls/ssh_localkey.h diff --git a/git-over-tls/pem.c b/git-over-tls/pem.c new file mode 100644 index 0000000..3e4f366 --- /dev/null +++ b/git-over-tls/pem.c @@ -0,0 +1,362 @@ +/* + * Copyright (C) Ilari Liusvaara 2010 + * + * This code is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ +#include "pem.h" +#include "pem_decrypt.h" +#include "base64.h" +#include <string.h> +#ifdef USE_COMPAT_H +#include "compat.h" +#else +#include "git-compat-util.h" +#endif + +void dump_blob(const char *name, const unsigned char *blob, size_t bloblen); + +static const unsigned char *strstr_n(const unsigned char *haystack, + const unsigned char *needle, size_t haystack_size) +{ + size_t maxpos, i; + size_t needle_size; + needle_size = strlen((const char*)needle); + if (haystack_size < needle_size) + return NULL; + maxpos = haystack_size - needle_size; + for (i = 0; i < maxpos; i++) + if (!strncmp((const char*)(haystack + i), + (const char*)needle, needle_size)) + return haystack + i; + return NULL; +} + +static const unsigned char *strchrs_n(const unsigned char *str, + const char *chars, size_t str_len) +{ + while (str_len > 0) { + if (strchr(chars, (char)str[0])) + return str; + str++; + str_len--; + } + return NULL; +} + +const unsigned char *find_base64_start(const unsigned char *start, + const unsigned char *end, char **dek_info) +{ + const unsigned char *startaddr; + const unsigned char *dekline; + size_t remaining = end - start; + startaddr = base64_blob_start(start, &remaining); + if (!startaddr) { + error("Malformed PEM: No BASE64 section found"); + return NULL; + } + dekline = strstr_n(start, (const unsigned char*)"DEK-Info: ", + startaddr - start); + if (!dekline) + *dek_info = NULL; + else { + const unsigned char *term; + const unsigned char *dekstart = dekline + 10; + while (dekstart < startaddr && (*dekstart == ' ' || + *dekstart == '\t')) + dekstart++; + + term = strchrs_n(dekstart, "\r\n", startaddr - dekstart); + if(!term) { + error("Malformed PEM: No BASE64 section found"); + return NULL; + } + *dek_info = xstrndup((const char*)dekstart, term - dekstart); + } + return startaddr; +} + +size_t decode_asn1_length_tag(const unsigned char **file, size_t *left) +{ + if (*left < 1) + goto eof; + + if (**file < 0x80) { + (*left)--; + (*file)++; + return (*file)[-1]; + } else if (**file == 0x80) { + error("Malformed PEM: Bad length tag"); + *file = NULL; + return 0; + } else if (**file == 0x81) { + if (*left < 2) + goto eof; + *left -= 2; + (*file) += 2; + return (*file)[-1]; + } else if (**file == 0x82) { + if (*left < 3) + goto eof; + *left -= 3; + (*file) += 3; + return (((size_t)(*file)[-2]) << 8) | + ((size_t)(*file)[-1]); + } else if (**file == 0x83) { + if (*left < 4) + goto eof; + *left -= 4; + (*file) += 4; + return (((size_t)(*file)[-3]) << 16) | + (((size_t)(*file)[-2]) << 8) | + ((size_t)(*file)[-1]); + } else { + error("Unreadable PEM: Size too large"); + *file = NULL; + return 0; + } +eof: + error("Malformed PEM: Expected length tag, got EOF"); + *file = NULL; + return 0; +} + +size_t asn1_integers_size(const unsigned char *seq, size_t seq_len, + int low, int high, size_t *k2size) +{ + int component = 0; + size_t size = 0; + for (component = 0; component < high; component++) { + size_t clen = 0; + if (seq_len < 1) { + error("Malformed PEM: Expected tag, got EOF"); + return 0; + } + if (*seq != 0x02) { + error("Malformed PEM: Expected INTEGER"); + return 0; + } + seq++; + seq_len--; + clen = decode_asn1_length_tag(&seq, &seq_len); + if (!seq) + return 0; + if (seq_len < clen) { + error("Malformed PEM: Truncated integer"); + return 0; + } + seq += clen; + seq_len -= clen; + if (component == 2 && k2size) + *k2size = (4 + clen); + if (component >= low) { + size += (4 + clen); + } + } + return size; +} + +unsigned char *handle_asn1_sequence(const unsigned char *seq, size_t seq_len, + size_t *keysize, int keytype) +{ + int low; + int high; + size_t kslow; + size_t toffset = 0; + size_t foffset = 0; + int component = 0; + size_t offset = 0; + size_t k2size = 0; + unsigned char *ret = NULL; + + if (keytype == 1) { + low = 1; + high = 4; + kslow = 11; + } else if (keytype == 2) { + low = 1; + high = 6; + kslow = 11; + } else { + error("Unknown PEM key type"); + return NULL; + } + *keysize = kslow + asn1_integers_size(seq, seq_len, low, high, + &k2size); + if (*keysize <= kslow) + return NULL; + ret = xmalloc(*keysize); + + if (keytype == 1) { + ret[0] = 0; + ret[1] = 0; + ret[2] = 0; + ret[3] = 7; + memcpy(ret + 4, "ssh-rsa", 7); + offset = 11; + } else if (keytype == 2) { + ret[0] = 0; + ret[1] = 0; + ret[2] = 0; + ret[3] = 7; + memcpy(ret + 4, "ssh-dss", 7); + offset = 11; + } + + /* + * Hack warning: The e and n are in "wrong" order in PEM + * RSA keys, so swap those. + */ + for (component = 0; component < high; component++) { + size_t clen = 0; + seq++; + seq_len--; + clen = decode_asn1_length_tag(&seq, &seq_len); + if(component >= low) { + if (keytype == 1 && component == 1) { + toffset = offset; + foffset = offset + 4 + clen; + offset += k2size; + } + ret[offset + 0] = (unsigned char)(clen >> 24); + ret[offset + 1] = (unsigned char)(clen >> 16); + ret[offset + 2] = (unsigned char)(clen >> 8); + ret[offset + 3] = (unsigned char)(clen); + memcpy(ret + offset + 4, seq, clen); + offset += (4 + clen); + if (keytype == 1 && component == 1) + offset = toffset; + if (keytype == 1 && component == 2) + offset = foffset + k2size; + } + seq += clen; + seq_len -= clen; + } + + return ret; +} + + +#define RSA_START_MARKER "-----BEGIN RSA PRIVATE KEY-----" +#define DSA_START_MARKER "-----BEGIN DSA PRIVATE KEY-----" +#define START_MARKER_LEN 31 +#define RSA_END_MARKER "-----END RSA PRIVATE KEY-----" +#define DSA_END_MARKER "-----END DSA PRIVATE KEY-----" + +unsigned char *pem_decode(const unsigned char *file, size_t filesize, + const char *keyname, size_t *keysize) +{ + int type = 0; + const unsigned char *start; + const unsigned char *end; + unsigned char *binary; + unsigned char *ret; + const unsigned char *binary_c; + size_t binary_c_size; + size_t binary_size; + size_t length; + char *dek_info; + + while(filesize > 0 && (*file == '\n' || *file == '\r')) { + filesize--; + file++; + } + + if (filesize < 64) + return NULL; + + if (!strncmp((const char*)file, RSA_START_MARKER "\r", + START_MARKER_LEN + 1)) { + start = file + START_MARKER_LEN + 1; + type = 1; + } else if (!strncmp((const char*)file, + DSA_START_MARKER "\r", START_MARKER_LEN + 1)) { + start = file + START_MARKER_LEN + 1; + type = 2; + } else if (!strncmp((const char*)file, + RSA_START_MARKER "\n", START_MARKER_LEN + 1)) { + start = file + START_MARKER_LEN + 1; + type = 1; + } else if (!strncmp((const char*)file, + DSA_START_MARKER "\n", START_MARKER_LEN + 1)) { + start = file + START_MARKER_LEN + 1; + type = 2; + } else { + return NULL; + } + + if(type == 1) { + end = strstr_n(file, (const unsigned char*) + "\r" RSA_END_MARKER, filesize); + if (end) + goto ok; + end = strstr_n(file, (const unsigned char*) + "\n" RSA_END_MARKER, filesize); + if (end) + goto ok; + error("Malformed PEM: No end marker (type 1)"); + return NULL; + } else if(type == 2) { + end = strstr_n(file, (const unsigned char*) + "\r" DSA_END_MARKER, filesize); + if (end) + goto ok; + end = strstr_n(file, (const unsigned char*) + "\n" DSA_END_MARKER, filesize); + if (end) + goto ok; + error("Malformed PEM: No end marker (type 2)"); + return NULL; + } else { + error("Unknown PEM key type"); + return NULL; + } +ok: + start = find_base64_start(start, end, &dek_info); + binary = decode_base64_chunk(start, end - start, &binary_size); + if (!binary) { + free(dek_info); + return NULL; + } + if (dek_info) { + unsigned char *decrypted; + size_t decrypted_len; + decrypted = decrypt_key(binary, binary_size, dek_info, + keyname, &decrypted_len); + free(dek_info); + free(binary); + if (!decrypted) + return NULL; + binary = decrypted; + binary_size = decrypted_len; + } + if (binary_size < 1) + goto invalid_seq; + if (binary[0] != 0x30) { + error("Malformed PEM: First element must be SEQUENCE"); + goto error_decoded; + } + binary_c = binary + 1; + binary_c_size = binary_size -1; + length = decode_asn1_length_tag(&binary_c, &binary_c_size); + if (!binary_c) { + error("Malformed PEM: Bad length for SEQUENCE"); + goto error_decoded; + } + if (length < binary_c_size) { + error("Malformed PEM: Garbage after end"); + goto error_decoded; + } else if (length > binary_c_size) { + error("Malformed PEM: SEQUENCE overflows file"); + goto error_decoded; + } + ret = handle_asn1_sequence(binary_c, binary_c_size, keysize, type); + free(binary); + return ret; +invalid_seq: + error("Malformed PEM: No valid sequence"); +error_decoded: + free(binary); + return NULL; +} diff --git a/git-over-tls/pem.h b/git-over-tls/pem.h new file mode 100644 index 0000000..df202e5 --- /dev/null +++ b/git-over-tls/pem.h @@ -0,0 +1,16 @@ +/* + * Copyright (C) Ilari Liusvaara 2010 + * + * This code is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ +#ifndef _pem__h__included__ +#define _pem__h__included__ + +#include <stdlib.h> + +unsigned char *pem_decode(const unsigned char *file, size_t filesize, + const char* keyname, size_t *keysize); + +#endif diff --git a/git-over-tls/pem_decrypt.c b/git-over-tls/pem_decrypt.c new file mode 100644 index 0000000..46effcf --- /dev/null +++ b/git-over-tls/pem_decrypt.c @@ -0,0 +1,203 @@ +#include "pem_decrypt.h" +#include "srp_askpass.h" +#include <string.h> +#include <gcrypt.h> +#ifdef USE_COMPAT_H +#include "compat.h" +#else +#include "git-compat-util.h" +#endif + +#define BUFFERSIZE 16384 +#define IVSIZE 128 + +unsigned char hexdecode(char c) +{ + switch(c) { + case '0': return 0; + case '1': return 1; + case '2': return 2; + case '3': return 3; + case '4': return 4; + case '5': return 5; + case '6': return 6; + case '7': return 7; + case '8': return 8; + case '9': return 9; + case 'a': return 10; + case 'b': return 11; + case 'c': return 12; + case 'd': return 13; + case 'e': return 14; + case 'f': return 15; + case 'A': return 10; + case 'B': return 11; + case 'C': return 12; + case 'D': return 13; + case 'E': return 14; + case 'F': return 15; + default: die("Bad hexadecimal character"); + } + return 0; +} + +unsigned char hexpairdecode(const char* hexpair) +{ + return hexdecode(hexpair[0]) * 16 + hexdecode(hexpair[1]); +} + +void decode_iv(unsigned char *iv, const char *dek_tail, size_t correct_len) +{ + int i; + if (strchr(dek_tail, ' ') != dek_tail + 2 * correct_len && + strlen(dek_tail) != 2 * correct_len) + die("IV field in key encryption wrong length"); + + for (i = 0; i < correct_len; i++) + iv[i] = hexpairdecode(dek_tail + 2 * i); +} + +#define MD5LEN 16 +#define SALTLEN 8 + +void derive_key(unsigned char *key, size_t keylen, const unsigned char *iv, + const char *passphrase) +{ + size_t passphrase_len; + int derived; + unsigned char* md5 = NULL; + unsigned char md5copy[MD5LEN]; + gcry_md_hd_t handle; + int i; + + if (gcry_md_open(&handle, GCRY_MD_MD5, 0)) + die("Can't gcry_md_open(..., GCRY_MD_MD5, ...)."); + + passphrase_len = strlen(passphrase); + for (derived = 0; derived < keylen;) { + gcry_md_reset(handle); + + if (md5) + gcry_md_write(handle, md5copy, MD5LEN); + gcry_md_write(handle, passphrase, passphrase_len); + gcry_md_write(handle, iv, SALTLEN); + + md5 = gcry_md_read(handle, 0); + memcpy(md5copy, md5, MD5LEN); + for (i = 0; i < 16 && derived < keylen; i++) { + key[derived++] = md5[i]; + } + } + gcry_md_close(handle); +} + +unsigned char *decrypt_key(const unsigned char *cipher, size_t cipher_size, + const char *dek_info, const char *keyname, size_t *decrypted_len) +{ + char *passphrase; + static char *stored_passphrase = NULL; + static char lastkeyname[BUFFERSIZE]; + unsigned char iv[IVSIZE]; + unsigned char key[IVSIZE]; + size_t ivlen; + size_t keylen; + int algo; + int mode; + size_t i; + int cipher_inited = 0; + gcry_cipher_hd_t cipherh; + unsigned char *ret = NULL; + + if (strlen(keyname) + 1 > BUFFERSIZE) + die("Keyname insanely long"); + + if (stored_passphrase && !strcmp(keyname, lastkeyname)) { + passphrase = stored_passphrase; + goto reuse_passphrase; + } + + passphrase = get_ssh_password(keyname); + stored_passphrase = passphrase; +reuse_passphrase: + if (!prefixcmp(dek_info, "DES-EDE3-CBC,")) { + /* 3DES-CBC */ + ivlen = 8; + keylen = 24; + algo = GCRY_CIPHER_3DES; + mode = GCRY_CIPHER_MODE_CBC; + } else if (!prefixcmp(dek_info, "DES-CBC,")) { + /* DES-CBC (INSECURE!!!) */ + ivlen = 8; + keylen = 8; + algo = GCRY_CIPHER_DES; + mode = GCRY_CIPHER_MODE_CBC; + } else if (!prefixcmp(dek_info, "AES-128-CBC,")) { + /* AES-128-CBC */ + ivlen = 16; + keylen = 16; + algo = GCRY_CIPHER_AES128; + mode = GCRY_CIPHER_MODE_CBC; + } else if (!prefixcmp(dek_info, "AES-192-CBC,")) { + /* AES-192-CBC */ + ivlen = 16; + keylen = 24; + algo = GCRY_CIPHER_AES192; + mode = GCRY_CIPHER_MODE_CBC; + } else if (!prefixcmp(dek_info, "AES-256-CBC,")) { + /* AES-256-CBC */ + ivlen = 16; + keylen = 32; + algo = GCRY_CIPHER_AES256; + mode = GCRY_CIPHER_MODE_CBC; + } else { + error("Key encryption type not supported (%s)", dek_info); + goto error_exit; + } + decode_iv(iv, strchr(dek_info, ',') + 1, ivlen); + derive_key(key, keylen, iv, passphrase); + + if (gcry_cipher_open(&cipherh, algo, mode, 0)) { + error("gcry_cipher_open() failed (DEK=%s; unsupported algorithm?)", dek_info); + goto error_exit; + } + cipher_inited = 1; + + if (gcry_cipher_setkey(cipherh, key, keylen)) { + error("gcry_cipher_setkey() failed (DEK=%s)", dek_info); + goto error_exit; + } + + if (gcry_cipher_setiv(cipherh, iv, ivlen)) { + error("gcry_cipher_setiv() failed (DEK=%s)", dek_info); + goto error_exit; + } + + ret = xmalloc(cipher_size); + if (gcry_cipher_decrypt(cipherh, ret, cipher_size, cipher, + cipher_size)) { + error("gcry_cipher_decrypt() failed (DEK=%s, wrong passphrase?)", dek_info); + goto error_exit; + } + + *decrypted_len = cipher_size - ret[cipher_size - 1]; + for(i = *decrypted_len; i < cipher_size; i++) { + if(ret[i] != ret[cipher_size - 1]) { + error("Incorrect passphrase or key corrupt"); + goto error_exit; + } + } + + strcpy(lastkeyname, keyname); + stored_passphrase = passphrase; + gcry_cipher_close(cipherh); + return ret; +error_exit: + if (cipher_inited) + gcry_cipher_close(cipherh); + if (stored_passphrase) + free(stored_passphrase); + if (ret) + free(ret); + stored_passphrase = NULL; + return NULL; +} diff --git a/git-over-tls/pem_decrypt.h b/git-over-tls/pem_decrypt.h new file mode 100644 index 0000000..ab132bf --- /dev/null +++ b/git-over-tls/pem_decrypt.h @@ -0,0 +1,9 @@ +#ifndef _pem_decrypt__h__included__ +#define _pem_decrypt__h__included__ + +#include <stdlib.h> + +unsigned char *decrypt_key(const unsigned char *cipher, size_t cipher_size, + const char *dek_info, const char *keyname, size_t *decrypted_len); + +#endif diff --git a/git-over-tls/ssh.c b/git-over-tls/ssh.c new file mode 100644 index 0000000..1d7426f --- /dev/null +++ b/git-over-tls/ssh.c @@ -0,0 +1,635 @@ +/* + * Copyright (C) Ilari Liusvaara 2010 + * + * This code is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ +#include "ssh.h" +#include "ssh_localkey.h" +#include "home.h" +#include "base64.h" +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <sys/un.h> +#include <sys/socket.h> +#include <sys/types.h> +#include <unistd.h> +#include <gcrypt.h> +#ifdef USE_COMPAT_H +#include "compat.h" +#else +#include "git-compat-util.h" +#endif +#include <stdlib.h> + +#define SSH2_AGENTC_SIGN_REQUEST 13 +#define SSH_AGENT_FAILURE 5 +#define SSH_AGENT_SUCCESS 6 +#define SSH2_AGENT_SIGN_RESPONSE 14 + +#define MAX_REPLY_SIZE 1048576 + +/* Decode uint32. */ +static unsigned long decode_uint32(const unsigned char *ptr) +{ + unsigned long v = 0; + v |= ((unsigned long)ptr[0] << 24); + v |= ((unsigned long)ptr[1] << 16); + v |= ((unsigned long)ptr[2] << 8); + v |= ((unsigned long)ptr[3]); + return v; +} + +/* + * Extract actual signature from ssh-agent reply. The return value should be + * freed. The reply has the component lengths present. + */ +static unsigned char *extract_actual_key(const unsigned char *keyreply, + size_t keyreply_len, const char *expected_type, size_t *key_len) +{ + unsigned long rlen; + + if (keyreply_len < 1) { + error("Malformed reply from SSH agent: No packet type present"); + return NULL; + } + if (keyreply[0] == SSH_AGENT_FAILURE) { + error("SSH agent failed to sign the challenge"); + return NULL; + } + if (keyreply[0] != SSH2_AGENT_SIGN_RESPONSE) { + error("SSH agent is confused: Returned packet type %u when type %u was expected", + keyreply[0], SSH2_AGENT_SIGN_RESPONSE); + return NULL; + } + if (keyreply_len < 5) { + error("Malformed signature reply from SSH agent: Signature length incomplete"); + return NULL; + } + rlen = decode_uint32(keyreply + 1); + + if (keyreply_len < 5 + rlen) { + error("Malformed signature reply from SSH agent: Signature incomplete"); + return NULL; + } + /* Strip packet header. */ + keyreply += 5; + keyreply_len = rlen; + + if (keyreply_len < 4) { + error("Malformed signature reply from SSH agent: Keytype header incomplete"); + return NULL; + } + rlen = decode_uint32(keyreply); + if (keyreply_len < 4 + rlen) { + error("Malformed signature reply from SSH agent: Keytype incomplete"); + return NULL; + } + if (rlen != strlen(expected_type) || memcmp(keyreply + 4, + expected_type, rlen)) { + error("SSH agent returned signature of wrong type: Expected '%s', got '%.*s'", + expected_type, (int)rlen, keyreply + 4); + return NULL; + } + /* Strip key type. */ + keyreply += 4 + rlen; + keyreply_len -= 4 + rlen; + + if (keyreply_len < 4) { + error("Malformed signature reply from SSH agent: Signature blob length incomplete"); + return NULL; + } + rlen = decode_uint32(keyreply); + if (keyreply_len < 4 + rlen) { + error("Malformed signature reply from SSH agent: Signature blob incomplete"); + return NULL; + } + + /* Extract the signature blob. */ + keyreply += 4; + keyreply_len = rlen; + + if (!strcmp(expected_type, "ssh-rsa")) { + unsigned char *ret; + + ret = xmalloc(keyreply_len + 5); + if (keyreply[0] > 128) { + /* Zero-pad to avoid negative value. */ + encode_uint32(ret, keyreply_len + 1); + ret[4] = 0; + memcpy(ret + 5, keyreply, keyreply_len); + *key_len = 5 + keyreply_len; + } else { + encode_uint32(ret, keyreply_len); + memcpy(ret + 4, keyreply, keyreply_len); + *key_len = 4 + keyreply_len; + } + return ret; + } else if (!strcmp(expected_type, "ssh-dss")) { + unsigned char *ret; + unsigned offset = 0; + + ret = xmalloc(keyreply_len + 10); + if (keyreply_len % 2) { + error("Malformed DSA signature, length is odd."); + free(ret); + return NULL; + } + + /* Encode r. */ + if (keyreply[0] > 128) { + /* Zero-pad to avoid negative value. */ + encode_uint32(ret, keyreply_len / 2 + 1); + ret[offset + 4] = 0; + offset += 5; + } else { + encode_uint32(ret, keyreply_len / 2); + offset += 4; + } + memcpy(ret + offset, keyreply, keyreply_len / 2); + offset += keyreply_len / 2; + + /* Encode s. */ + if (keyreply[keyreply_len / 2] > 128) { + /* Zero-pad to avoid negative value. */ + encode_uint32(ret + offset, keyreply_len / 2 + 1); + ret[offset + 4] = 0; + offset += 5; + } else { + encode_uint32(ret + offset, keyreply_len / 2); + offset += 4; + } + memcpy(ret + offset, keyreply + keyreply_len / 2, + keyreply_len / 2); + offset += keyreply_len / 2; + + *key_len = offset; + return ret; + } else { + error("Unknown signature type '%s'", expected_type); + return NULL; + } + + return NULL; +} + + +/* Extract SecSH public key. */ +static unsigned char *extract_secsh_pubkey(const unsigned char *file, + size_t file_len, size_t *key_len) +{ + const unsigned char *file2; + size_t file_len2; + + /* Skip to begin line. */ + if (*file == '\r' || *file == '\n') { + /* Skip the empty lines. */ + file = next_line(file, &file_len); + if (!file) { + error("Malformed SecSH public key (no header)"); + return NULL; + } + } + file = next_line(file, &file_len); + if (!file) { + error("Malformed SecSH public key (no header)"); + return NULL; + } + + file = base64_blob_start(file, &file_len); + file2 = file; + file_len2 = file_len; + + if (file_len2 == 29 && !memcmp(file2, + "---- END SSH2 PUBLIC KEY ----", 29)) { + error("Malformed SecSH public key (no encoded blob)"); + return NULL; + } + if (!memcmp(file2, "---- END SSH2 PUBLIC KEY ----\r", 30)) { + error("Malformed SecSH public key (no encoded blob)"); + return NULL; + } + if (!memcmp(file2, "---- END SSH2 PUBLIC KEY ----\n", 30)) { + error("Malformed SecSH public key (no encoded blob)"); + return NULL; + } + while(1) { + file2 = next_line(file2, &file_len2); + if (!file2) { + error("Malformed SecSH public key (no trailer)"); + return NULL; + } + if (file_len2 == 29 && !memcmp(file2, + "---- END SSH2 PUBLIC KEY ----", 29)) { + file_len = file2 - file; + break; + } + if (!memcmp(file2, "---- END SSH2 PUBLIC KEY ----\r", 30)) { + file_len = file2 - file; + break; + } + if (!memcmp(file, "---- END SSH2 PUBLIC KEY ----\n", 30)) { + file_len = file2 - file; + break; + } + } + return decode_base64_chunk(file, file_len, key_len); +} + +/* Is SecSH format key? */ +static int is_secsh_pubkey(const unsigned char *file, size_t file_len) +{ + if (file_len < 32) + return 0; + if (!memcmp(file, "---- BEGIN SSH2 PUBLIC KEY ----\r", 32)) + return 1; + if (!memcmp(file, "---- BEGIN SSH2 PUBLIC KEY ----\n", 32)) + return 1; + file = next_line(file, &file_len); + if (file_len < 32) + return 0; + if (!memcmp(file, "---- BEGIN SSH2 PUBLIC KEY ----\r", 32)) + return 1; + if (!memcmp(file, "---- BEGIN SSH2 PUBLIC KEY ----\n", 32)) + return 1; + return 0; +} + +/* Extract OpenSSH public key. */ +static unsigned char *extract_openssh_pubkey(const unsigned char *file, + size_t file_len, size_t *key_len) +{ + const unsigned char *base; + const unsigned char *sep2; + size_t blob_size; + base = (const unsigned char*)strchr((const char*)file, ' '); + if (!base) { + error("Malformed OpenSSH pubkey file"); + return NULL; + } + base++; + + sep2 = (const unsigned char*)strchr((const char*)base, ' '); + if (!sep2) + blob_size = strlen((const char*)base); + else + blob_size = sep2 - base; + + return decode_base64_chunk(base, blob_size, key_len); +} + +/* Is OpenSSH format key? */ +static int is_openssh_pubkey(const unsigned char *file, size_t file_len) +{ + if (file_len < 8) + return 0; + if (!memcmp(file, "ssh-rsa ", 8)) + return 1; + if (!memcmp(file, "ssh-dss ", 8)) + return 1; + return 0; +} + +/* + * Extract actual key blob from encoded key. The return value should be + * freed. The reply has the component lengths present. + */ +unsigned char *extract_key_from_file(const unsigned char *file, + size_t file_len, size_t *key_len) +{ + if (is_openssh_pubkey(file, file_len)) + return extract_openssh_pubkey(file, file_len, key_len); + if (is_secsh_pubkey(file, file_len)) + return extract_secsh_pubkey(file, file_len, key_len); + error("Unknown public key file format"); + return NULL; +} + +void dump_blob(const char *name, const unsigned char *blob, size_t bloblen) +{ + + size_t i; + fprintf(stderr, "------- START %s (%u bytes) ------\n", name, (unsigned)bloblen); + for(i = 0; i < bloblen; i++) { + fprintf(stderr, "%02X ", blob[i]); + if (i % 16 == 15) + fprintf(stderr, "\n"); + } + if (i % 16) + fprintf(stderr, "\n"); + fprintf(stderr, "-------- END %s -------\n", name); + +} + +int write_to_agent(int fd, const unsigned char *ptr, size_t size) +{ + while (size > 0) { + ssize_t r; + r = write(fd, ptr, size); + if (r > 0) { + ptr += r; + size -= r; + } else if (r == 0) { + error("Connection to ssh-agent unexpectedly lost"); + return -1; + } + else if (errno == EINTR || errno == EAGAIN) + continue; + else { + error("Error writing to ssh-agent: %s", + strerror(errno)); + return -1; + } + } + return 0; +} + +int read_in_agent(int fd, unsigned char *ptr, size_t size) +{ + while (size > 0) { + ssize_t r; + r = read(fd, ptr, size); + if (r > 0) { + ptr += r; + size -= r; + } else if (r == 0) { + error("Connection to ssh-agent unexpectedly lost"); + return -1; + } + else if (errno == EINTR || errno == EAGAIN) + continue; + else { + error("Error reading from ssh-agent: %s", + strerror(errno)); + return -1; + } + } + return 0; +} + +int write_packet_to_agent(int fd, const unsigned char *payload, size_t len) +{ + unsigned char tmp[4]; + encode_uint32(tmp, len); + if (write_to_agent(fd, tmp, 4) < 0) + return -1; + if (write_to_agent(fd, payload, len) < 0) + return -1; + return 0; +} + +unsigned char *read_packet_from_agent(int fd, size_t *len) +{ + unsigned char *ret; + unsigned char tmp[4]; + if (read_in_agent(fd, tmp, 4) < 0) + return NULL; + *len = decode_uint32(tmp); + if (*len > MAX_REPLY_SIZE) { + error("Reply from SSH agent too large (size %zu, maximum allowed %zu)", + *len, (size_t)MAX_REPLY_SIZE); + return NULL; + } + ret = xmalloc(*len); + if (read_in_agent(fd, ret, *len) < 0) { + free(ret); + return NULL; + } + return ret; +} + +static unsigned char *agent_do_io_cycle(int fd, const unsigned char *payload, + size_t payload_len, size_t *reply_len) +{ + if (write_packet_to_agent(fd, payload, payload_len) < 0) + return NULL; + return read_packet_from_agent(fd, reply_len); +} + +int ssh_agent_sock = -1; + +int init_ssh_agent() +{ + const char *agentpath; + struct sockaddr_un addr; + + if (ssh_agent_sock >= 0) + return 0; + if (ssh_agent_sock < -1) + return -1; + + agentpath = getenv("SSH_AUTH_SOCK"); + if (!agentpath) { + ssh_agent_sock = -2; + return -1; + } + + addr.sun_family = AF_UNIX; + strcpy(addr.sun_path, agentpath); + + ssh_agent_sock = socket(AF_UNIX, SOCK_STREAM, 0); + if (ssh_agent_sock < 0) { + error("Can't create socket: %s", + strerror(errno)); + ssh_agent_sock = -2; + return -1; + } + + if (connect(ssh_agent_sock, (struct sockaddr*)&addr, sizeof(addr)) < 0) { + error("Can't connect to ssh-agent(%s): %s", agentpath, + strerror(errno)); + close(ssh_agent_sock); + ssh_agent_sock = -2; + return -1; + } + return 0; +} + +unsigned char *sign_using_ssh_agent(const char *keyname, + const unsigned char *kblob, size_t kblob_size, + const unsigned char *challenge, size_t challenge_size, + size_t *signature_length, const char *type) +{ + unsigned char *request; + unsigned char *reply; + size_t reply_length; + size_t request_length; + unsigned char *signblocks = NULL; + + /* 43 = 1 (type) + 8 (2 lengths) + 4 (flags) */ + request = xmalloc(13 + kblob_size + challenge_size); + request_length = 13 + kblob_size + challenge_size; + + request[0] = SSH2_AGENTC_SIGN_REQUEST; + encode_uint32(request + 1, kblob_size); + memcpy(request + 5, kblob, kblob_size); + encode_uint32(request + 5 + kblob_size, challenge_size); + memcpy(request + 9 + kblob_size, challenge, challenge_size); + encode_uint32(request + 9 + kblob_size + challenge_size, 0); + + if (init_ssh_agent() < 0) + goto out_request; + + reply = agent_do_io_cycle(ssh_agent_sock, request, request_length, + &reply_length); + if (!reply) + goto out_request; + + signblocks = extract_actual_key(reply, reply_length, type, + signature_length); + /* extract_actual_key can fail, but we don't print any error for that. */ + + free(reply); +out_request: + free(request); + return signblocks; +} + +static const char *check_blob(const unsigned char* blob, size_t blob_size) +{ + size_t offset = 0; + unsigned round = 0; + unsigned rounds = 1; + const char *type = NULL; + + for (round = 0; round < rounds; round++) { + unsigned long len; + if (offset + 4 < offset || blob_size < offset + 4) { + error("Component length header incomplete (component=%u/%u, blob_size=%zu, offset=%zu", + round + 1, rounds, blob_size, offset); + return NULL; + } + len = decode_uint32(blob + offset); + if (offset + len + 4 < offset + 4 || blob_size < offset + len + 4) { + error("Component incomplete (component=%u/%u, blob_size=%zu, offset=%zu, len=%lu)", + round + 1, rounds, blob_size, offset, len); + return NULL; + } + if (round == 0 && len == 7 && !memcmp(blob + offset + 4, + "ssh-rsa", 7)) { + rounds = 3; + type = "ssh-rsa"; + } else if (round == 0 && len == 7 && !memcmp(blob + offset + 4, + "ssh-dss", 7)) { + rounds = 5; + type = "ssh-dss"; + } else if (round == 0) { + error("Read key has unknown key type (neither ssh-rsa nor ssh-dss)"); + return NULL; + } + offset += len + 4; + } + if (offset != blob_size) { + error("Garbage after end of key"); + return NULL; + } + return type; +} + +#define MAXPATH 4096 +#define MAXKEY 65536 +#define DATABLOCK 32 +#define HASHALGO GCRY_MD_SHA256 +#define HASHLEN 32 + +void do_ssh_preauth(const char *ssh_user) +{ + send_ssh_authentication(NULL, ssh_user); +} + +void send_ssh_authentication(struct user *dispatcher, const char *ssh_user) +{ + unsigned char *x = NULL; + size_t i; + FILE* filp; + char *path; + char pathtemplate[MAXPATH]; + unsigned char key[MAXKEY]; + size_t keysize; + unsigned char block[DATABLOCK] = {0}; + + unsigned char *signature = NULL; + size_t signature_length; + const char *type; + + if (strlen(ssh_user) > MAXPATH - 32) + die("Key name too long"); + sprintf(pathtemplate, "~/.ssh/%s.pub", ssh_user); + path = expand_path(pathtemplate); + + if (!x) { + /* Read the pubkey file. */ + filp = fopen(path, "r"); + if (!filp) { + error("Can't open '%s'", path); + goto out_readfile; + } + keysize = fread(key, 1, MAXKEY, filp); + if (ferror(filp)) { + error("Can't read '%s'", path); + goto out_readfile; + } + if (!feof(filp)) { + error("Keyfile '%s' too large", path); + goto out_readfile; + } + fclose(filp); + + x = extract_key_from_file(key, keysize, &i); + } +out_readfile: + if (!x) + die("Can't read any public key named '%s'", + ssh_user); + + type = check_blob(x, i); + if (!type) + die("Malformed public key for '%s'", ssh_user); + + if (dispatcher) { + int s; + gnutls_session_t session; + unsigned char hash[HASHLEN]; + const gnutls_datum_t *cert; + unsigned int lsize; + session = user_get_tls(dispatcher); + if (!session) + die("SSH auth requires TLS"); + cert = gnutls_certificate_get_peers(session, &lsize); + if (!cert) + die("Unable to get server certificate (server didn't send one?)"); + + gcry_md_hash_buffer(HASHALGO, hash, cert->data, cert->size); + s = gnutls_prf(session, 14, "ssh-key-verify", 0, HASHLEN, + (const char*)hash, DATABLOCK, (char*)block); + if (s < 0) + die("Can't compute challenge: %s", + gnutls_strerror(s)); + } + if (!signature) + signature = sign_using_ssh_agent(ssh_user, + x, i, block, DATABLOCK, &signature_length, + type); + if (!signature) + signature = sign_using_local_key(ssh_user, + x, i, block, DATABLOCK, &signature_length, + type); + if (!signature) + die("Can't sign using key '%s'", ssh_user); + + if (dispatcher) { + struct cbuffer* in; + char tmp[5]; + in = user_get_red_in(dispatcher); + sprintf(tmp, "%04x", (unsigned)(8 + i + signature_length)); + cbuffer_write(in, (const unsigned char*)tmp, 4); + cbuffer_write(in, (const unsigned char*)"ssh ", 4); + cbuffer_write(in, x, i); + cbuffer_write(in, signature, signature_length); + } + + free(signature); + free(x); +} diff --git a/git-over-tls/ssh.h b/git-over-tls/ssh.h new file mode 100644 index 0000000..3ee5daf --- /dev/null +++ b/git-over-tls/ssh.h @@ -0,0 +1,18 @@ +/* + * Copyright (C) Ilari Liusvaara 2010 + * + * This code is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ +#ifndef _ssh__h__included__ +#define _ssh__h__included__ + +#include "user.h" + +void do_ssh_preauth(const char *ssh_user); +void send_ssh_authentication(struct user *dispatcher, const char *ssh_user); +unsigned char *extract_key_from_file(const unsigned char *file, + size_t file_len, size_t *key_len); + +#endif diff --git a/git-over-tls/ssh_localkey.c b/git-over-tls/ssh_localkey.c new file mode 100644 index 0000000..f27ee9b --- /dev/null +++ b/git-over-tls/ssh_localkey.c @@ -0,0 +1,435 @@ +/* + * Copyright (C) Ilari Liusvaara 2010 + * + * This code is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ +#include <stdint.h> +#include <stdlib.h> +#include <string.h> +#include "pem.h" +#include "base64.h" +#include "home.h" +#include <gcrypt.h> +#ifdef USE_COMPAT_H +#include "compat.h" +#else +#include "git-compat-util.h" +#endif + +static int validate_key(const unsigned char *key, size_t keysize, + size_t *offsets, size_t *lengths, int components) +{ + size_t offset = 0; + int c; + + for(c = 0; c < components; c++) { + size_t len = 0; + if (offset + 4 < offset || offset + 4 > keysize) + return -1; + offsets[c] = offset + 4; + len |= ((size_t)key[offset] << 24); + len |= ((size_t)key[offset + 1] << 16); + len |= ((size_t)key[offset + 2] << 8); + len |= ((size_t)key[offset + 3]); + lengths[c] = len; + if (offset + 4 + len < offset + 4 || + offset + 4 + len > keysize) + return -1; + offset += (4 + len); + } + if (offset != keysize) + return -1; + return 0; +} + +typedef void* voidptr; + +static unsigned char *sign_rsa(const unsigned char *key, size_t keysize, + const unsigned char *challenge, size_t challenge_size, + size_t *signature_size) +{ + unsigned char *ret = NULL; + size_t offsets[4]; + size_t lengths[4]; + gcry_mpi_t rsa_e; + gcry_mpi_t rsa_n; + gcry_mpi_t rsa_d; + gcry_error_t err; + gcry_sexp_t _key; + gcry_sexp_t tosign; + gcry_sexp_t signature; + gcry_sexp_t sval; + gcry_sexp_t rsas_s; + const unsigned char *rsa_s; + size_t rsa_s_len; + int hashalgo = GCRY_MD_SHA256; + const char *algo = "sha256"; + int hashlen = 32; + unsigned char hashbuffer[128]; + voidptr params[3]; + if (validate_key(key, keysize, offsets, lengths, 4) < 0) { + error("Bad RSA private key"); + return NULL; + } + err = gcry_mpi_scan(&rsa_e, GCRYMPI_FMT_STD, key + offsets[1], + lengths[1], NULL); + if (err) { + error("Can't read RSA e: %s/%s", gcry_strsource(err), + gcry_strerror(err)); + goto out; + } + err = gcry_mpi_scan(&rsa_n, GCRYMPI_FMT_STD, key + offsets[2], + lengths[2], NULL); + if (err) { + error("Can't read RSA n: %s/%s", gcry_strsource(err), + gcry_strerror(err)); + goto out_e; + } + err = gcry_mpi_scan(&rsa_d, GCRYMPI_FMT_STD, key + offsets[3], + lengths[3], NULL); + if (err) { + error("Can't read RSA d: %s/%s", gcry_strsource(err), + gcry_strerror(err)); + goto out_n; + } + params[0] = &rsa_n; + params[1] = &rsa_e; + params[2] = &rsa_d; + err = gcry_sexp_build_array(&_key, NULL, "(private-key (rsa (n %m) (e %m) (d %m)))", + params); + if (err) { + error("Can't build key: %s/%s", gcry_strsource(err), + gcry_strerror(err)); + goto out_d; + } + + gcry_md_hash_buffer(hashalgo, hashbuffer, challenge, + challenge_size); + + err = gcry_sexp_build(&tosign, NULL, "(data (flags pkcs1) (hash %s %b))", + algo, hashlen, hashbuffer); + if (err) { + error("Can't build data to sign: %s/%s", gcry_strsource(err), + gcry_strerror(err)); + goto out_key; + } + + err = gcry_pk_sign(&signature, tosign, _key); + if (err) { + error("Can't sign data: %s/%s", gcry_strsource(err), + gcry_strerror(err)); + goto out_tosign; + } + + sval = gcry_sexp_nth(signature, 1); + if(!sval) { + error("DSA signature output faulty"); + goto out_signature; + } + rsas_s = gcry_sexp_nth(sval, 1); + if(!rsas_s) { + error("RSA signature output faulty (no s)"); + goto out_sval; + } + rsa_s = (const unsigned char*)gcry_sexp_nth_data(rsas_s, 1, &rsa_s_len); + if(!rsa_s) { + error("RSA signature output faulty (no real s)"); + goto out_s; + } + + *signature_size = 4 + rsa_s_len; + ret = xmalloc(*signature_size); + encode_uint32(ret, rsa_s_len); + memcpy(ret + 4, rsa_s, rsa_s_len); + +out_s: + gcry_sexp_release(rsas_s); +out_sval: + gcry_sexp_release(sval); +out_signature: + gcry_sexp_release(signature); +out_tosign: + gcry_sexp_release(tosign); +out_key: + gcry_sexp_release(_key); +out_d: + gcry_mpi_release(rsa_d); +out_n: + gcry_mpi_release(rsa_n); +out_e: + gcry_mpi_release(rsa_e); +out: + return ret; +} + +static unsigned char *sign_dsa(const unsigned char *key, size_t keysize, + const unsigned char *challenge, size_t challenge_size, + size_t *signature_size) +{ + unsigned char *ret = NULL; + size_t offsets[6]; + size_t lengths[6]; + gcry_mpi_t dsa_p; + gcry_mpi_t dsa_q; + gcry_mpi_t dsa_g; + gcry_mpi_t dsa_y; + gcry_mpi_t dsa_x; + gcry_mpi_t dsa_e; + int hashalgo; + const char *algo; + int hashlen; + unsigned char hashbuffer[128]; + voidptr params[5]; + gcry_sexp_t _key; + gcry_sexp_t tosign; + gcry_sexp_t signature; + gcry_sexp_t sval; + gcry_sexp_t dsas_r; + gcry_sexp_t dsas_s; + const unsigned char *dsa_r; + size_t dsa_r_len; + const unsigned char *dsa_s; + size_t dsa_s_len; + gcry_error_t err; + int sbits; + + if (validate_key(key, keysize, offsets, lengths, 6) < 0) { + error("Bad DSA private key"); + return NULL; + } + + err = gcry_mpi_scan(&dsa_p, GCRYMPI_FMT_STD, key + offsets[1], + lengths[1], NULL); + if (err) { + error("Can't read DSA p: %s/%s", gcry_strsource(err), + gcry_strerror(err)); + goto out; + } + err = gcry_mpi_scan(&dsa_q, GCRYMPI_FMT_STD, key + offsets[2], + lengths[2], NULL); + if (err) { + error("Can't read DSA q: %s/%s", gcry_strsource(err), + gcry_strerror(err)); + goto out_p; + } + err = gcry_mpi_scan(&dsa_g, GCRYMPI_FMT_STD, key + offsets[3], + lengths[3], NULL); + if (err) { + error("Can't read DSA g: %s/%s", gcry_strsource(err), + gcry_strerror(err)); + goto out_q; + } + err = gcry_mpi_scan(&dsa_y, GCRYMPI_FMT_STD, key + offsets[4], + lengths[4], NULL); + if (err) { + error("Can't read DSA y: %s/%s", gcry_strsource(err), + gcry_strerror(err)); + goto out_g; + } + err = gcry_mpi_scan(&dsa_x, GCRYMPI_FMT_STD, key + offsets[5], + lengths[5], NULL); + if (err) { + error("Can't read DSA x: %s/%s", gcry_strsource(err), + gcry_strerror(err)); + goto out_y; + } + + params[0] = &dsa_p; + params[1] = &dsa_q; + params[2] = &dsa_g; + params[3] = &dsa_y; + params[4] = &dsa_x; + err = gcry_sexp_build_array(&_key, NULL, "(private-key (dsa (p %m) (q %m) (g %m) (y %m) (x %m)))", + params); + if (err) { + error("Can't build key: %s/%s", gcry_strsource(err), + gcry_strerror(err)); + goto out_x; + } + + sbits = gcry_mpi_get_nbits(dsa_q); + if(sbits <= 160) { + algo = "sha1"; + hashalgo = GCRY_MD_SHA1; + hashlen = 20; + } else if(sbits <= 224) { + algo = "sha224"; + hashalgo = GCRY_MD_SHA224; + hashlen = 28; + } else if(sbits <= 256) { + algo = "sha256"; + hashalgo = GCRY_MD_SHA256; + hashlen = 32; + } else if(sbits <= 384) { + algo = "sha384"; + hashalgo = GCRY_MD_SHA384; + hashlen = 48; + } else if(sbits <= 512) { + algo = "sha512"; + hashalgo = GCRY_MD_SHA512; + hashlen = 64; + } else { + error("DSA parameter q out of range (2^512 or larger)"); + goto out_key; + } + + gcry_md_hash_buffer(hashalgo, hashbuffer, challenge, + challenge_size); + err = gcry_mpi_scan(&dsa_e, GCRYMPI_FMT_USG, hashbuffer, + hashlen, NULL); + if (err) { + error("Can't read DSA e: %s/%s", gcry_strsource(err), + gcry_strerror(err)); + goto out_key; + } + + err = gcry_sexp_build(&tosign, NULL, "(%m)", + dsa_e); + if (err) { + error("Can't build data to sign: %s/%s", gcry_strsource(err), + gcry_strerror(err)); + goto out_e; + } + + err = gcry_pk_sign(&signature, tosign, _key); + if (err) { + error("Can't sign data: %s/%s", gcry_strsource(err), + gcry_strerror(err)); + goto out_tosign; + } + + sval = gcry_sexp_nth(signature, 1); + if(!sval) { + error("DSA signature output faulty (no sval)"); + goto out_signature; + } + dsas_r = gcry_sexp_nth(sval, 1); + if(!dsas_r) { + error("DSA signature output faulty (no r)"); + goto out_sval; + } + dsa_r = (const unsigned char*)gcry_sexp_nth_data(dsas_r, 1, &dsa_r_len); + if(!dsa_r) { + error("DSA signature output faulty (no real r)"); + goto out_r; + } + dsas_s = gcry_sexp_nth(sval, 2); + if(!dsas_s) { + error("DSA signature output faulty (no s)"); + goto out_r; + } + dsa_s = (const unsigned char*)gcry_sexp_nth_data(dsas_s, 1, &dsa_s_len); + if(!dsa_s) { + error("DSA signature output faulty (no real s)"); + goto out_s; + } + + *signature_size = 8 + dsa_r_len + dsa_s_len; + ret = xmalloc(*signature_size); + encode_uint32(ret, dsa_r_len); + memcpy(ret + 4, dsa_r, dsa_r_len); + encode_uint32(ret + 4 + dsa_r_len, dsa_s_len); + memcpy(ret + 8 + dsa_r_len, dsa_s, dsa_s_len); + +out_s: + gcry_sexp_release(dsas_s); +out_r: + gcry_sexp_release(dsas_r); +out_sval: + gcry_sexp_release(sval); +out_signature: + gcry_sexp_release(signature); +out_tosign: + gcry_sexp_release(tosign); +out_e: + gcry_mpi_release(dsa_e); +out_key: + gcry_sexp_release(_key); +out_x: + gcry_mpi_release(dsa_x); +out_y: + gcry_mpi_release(dsa_y); +out_g: + gcry_mpi_release(dsa_g); +out_q: + gcry_mpi_release(dsa_q); +out_p: + gcry_mpi_release(dsa_p); +out: + return ret; +} + +#define MAXPATH 4096 +#define MAXKEY 65536 + +unsigned char *sign_using_local_key(const char *keyname, + const unsigned char *kblob, size_t kblob_size, + const unsigned char *challenge, size_t challenge_size, + size_t *signature_length, const char *type) +{ + FILE* filp; + unsigned char *ret = NULL; + + char *path; + char pathtemplate[MAXPATH]; + unsigned char key[MAXKEY]; + size_t keysize; + int readkey_flag = 0; + + unsigned char *x; + size_t i; + + if (strlen(keyname) > MAXPATH - 32) + die("Key name too long"); + sprintf(pathtemplate, "~/.ssh/%s", keyname); + path = expand_path(pathtemplate); + + /* Read the privkey file. */ + filp = fopen(path, "r"); + if (!filp) { + error("Can't open '%s'", path); + goto out_readfile; + } + keysize = fread(key, 1, MAXKEY, filp); + if (ferror(filp)) { + error("Can't read '%s'", path); + goto out_readfile_handle; + } + if (!feof(filp)) { + error("Keyfile '%s' too large", path); + goto out_readfile_handle; + } + readkey_flag = 1; +out_readfile_handle: + fclose(filp); +out_readfile: + free(path); + if (!readkey_flag) { + error("Can't read any private key named '%s'", keyname); + return NULL; + } + + x = pem_decode(key, keysize, keyname, &i); + if (!x) { + error("Can't decode key '%s'", keyname); + return NULL; + } + if (kblob_size > i || memcmp(kblob, x, kblob_size)) { + free(x); + error("Pubic and private keys for '%s' are inconsistent", + keyname); + return NULL; + } + if (!strcmp(type, "ssh-rsa")) + ret = sign_rsa(x, i, challenge, challenge_size, + signature_length); + else if (!strcmp(type, "ssh-dss")) + ret = sign_dsa(x, i, challenge, challenge_size, + signature_length); + else + error("Key '%s' is of unknown type", keyname); + free(x); + return ret; +} diff --git a/git-over-tls/ssh_localkey.h b/git-over-tls/ssh_localkey.h new file mode 100644 index 0000000..223b97f --- /dev/null +++ b/git-over-tls/ssh_localkey.h @@ -0,0 +1,18 @@ +/* + * Copyright (C) Ilari Liusvaara 2010 + * + * This code is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ +#ifndef _ssh_localkey__h__include__ +#define _ssh_localkey__h__include__ + +#include <stdlib.h> + +unsigned char *sign_using_local_key(const char *keyname, + const unsigned char *kblob, size_t kblob_size, + const unsigned char *challenge, size_t challenge_size, + size_t *signature_length, const char *type); + +#endif -- 1.7.1.rc2.10.g714149 -- To unsubscribe from this list: send the line "unsubscribe git" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html