[PATCH 3/3] git over TLS (gits://) support (part 3)

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



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

[Index of Archives]     [Linux Kernel Development]     [Gcc Help]     [IETF Annouce]     [DCCP]     [Netdev]     [Networking]     [Security]     [V4L]     [Bugtraq]     [Yosemite]     [MIPS Linux]     [ARM Linux]     [Linux Security]     [Linux RAID]     [Linux SCSI]     [Fedora Users]