Support PyUSB 1.0, too
[gnuk/gnuk.git] / tool / pageant_proxy_to_gpg.py
1 """
2 pagent_proxy_to_gpg.py - Connect gpg-agent as Pagent
3
4 Copyright (C) 2013 Free Software Initiative of Japan
5 Author: NIIBE Yutaka <gniibe@fsij.org>
6
7 This file is a part of Gnuk, a GnuPG USB Token implementation.
8
9 Gnuk is free software: you can redistribute it and/or modify it
10 under the terms of the GNU General Public License as published by
11 the Free Software Foundation, either version 3 of the License, or
12 (at your option) any later version.
13
14 Gnuk is distributed in the hope that it will be useful, but WITHOUT
15 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
16 or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public
17 License for more details.
18
19 You should have received a copy of the GNU General Public License
20 along with this program.  If not, see <http://www.gnu.org/licenses/>.
21 """
22
23 import os, sys, re, hashlib, binascii
24 from struct import *
25 from gpg_agent import gpg_agent
26 from sexp import sexp
27
28 # Assume it's only OPENPGP.3 key and it's 2048-bit
29
30 def debug(string):
31     print "DEBUG: %s" % string
32     sys.stdout.flush()
33
34 def get_keygrip_list(keyinfo_result):
35     kl_str = keyinfo_result[0:-1] # Chop last newline
36     kl = kl_str.split('\n')
37     # filter by "OPENPGP.3", and only keygrip
38     return [kg.split(' ')[0] for kg in kl if re.search("OPENPGP\\.3", kg)]
39
40 # Connect GPG-Agent, and get list of KEYGRIPs.
41 g = gpg_agent()
42 g.read_line()                   # Greeting message
43
44 g.send_command('KEYINFO --list --data\n')
45 keyinfo_result = g.get_response()
46 keygrip_list = get_keygrip_list(keyinfo_result)
47
48 debug(keygrip_list)
49
50 keylist = []
51 # For each KEYGRIP, get its PUBLIC-KEY.
52 for kg in keygrip_list:
53     g.send_command('READKEY %s\n' % kg)
54     key = sexp(g.get_response())
55     # [ "public-key" [ "rsa" [ "n" MODULUS ] [ "e" EXPONENT] ] ]
56     n = key[1][1][1]
57     e = key[1][2][1]
58     debug(binascii.hexlify(n))
59     debug(binascii.hexlify(e))
60     keylist.append([n, e, kg])
61
62 # FIXME: should handle all keys, not only a single key
63 # FIXME: should support different key size
64 n = keylist[0][0]
65 e = keylist[0][1]
66 keygrip = keylist[0][2]
67
68 ssh_rsa_public_blob = "\x00\x00\x00\x07ssh-rsa" + \
69     "\x00\x00\x00\x03" + e + "\x00\x00\x01\x01" + n
70
71 ssh_key_comment = "key_on_gpg"  # XXX: get login from card for comment?
72
73 import win32con, win32api, win32gui, ctypes, ctypes.wintypes
74
75
76 # For WM_COPYDATA structure
77 class COPYDATA(ctypes.Structure):
78     _fields_ = [ ('dwData', ctypes.wintypes.LPARAM),
79                  ('cbData', ctypes.wintypes.DWORD),
80                  ('lpData', ctypes.c_void_p) ]
81
82 P_COPYDATA = ctypes.POINTER(COPYDATA)
83
84 class SSH_MSG_HEAD(ctypes.BigEndianStructure):
85     _pack_ = 1
86     _fields_ = [ ('msg_len', ctypes.c_uint32),
87                  ('msg_type', ctypes.c_byte) ]
88
89 P_SSH_MSG_HEAD = ctypes.POINTER(SSH_MSG_HEAD)
90
91 class SSH_MSG_ID_ANSWER_HEAD(ctypes.BigEndianStructure):
92     _pack_ = 1
93     _fields_ = [ ('msg_len', ctypes.c_uint32),
94                  ('msg_type', ctypes.c_byte),
95                  ('keys', ctypes.c_uint32)]
96
97 P_SSH_MSG_ID_ANSWER = ctypes.POINTER(SSH_MSG_ID_ANSWER_HEAD)
98
99 class SSH_MSG_SIGN_RESPONSE_HEAD(ctypes.BigEndianStructure):
100     _pack_ = 1
101     _fields_ = [ ('msg_len', ctypes.c_uint32),
102                  ('msg_type', ctypes.c_byte),
103                  ('sig_len', ctypes.c_uint32)]
104
105 P_SSH_MSG_SIGN_RESPONSE = ctypes.POINTER(SSH_MSG_SIGN_RESPONSE_HEAD)
106
107
108 FILE_MAP_ALL_ACCESS=0x000F001F
109
110 class windows_ipc_listener(object):
111     def __init__(self):
112         message_map = { win32con.WM_COPYDATA: self.OnCopyData }
113         wc = win32gui.WNDCLASS()
114         wc.lpfnWndProc = message_map
115         wc.lpszClassName = 'Pageant'
116         hinst = wc.hInstance = win32api.GetModuleHandle(None)
117         classAtom = win32gui.RegisterClass(wc)
118         self.hwnd = win32gui.CreateWindow (
119             classAtom,
120             "Pageant",
121             0,
122             0, 
123             0,
124             win32con.CW_USEDEFAULT, 
125             win32con.CW_USEDEFAULT,
126             0, 
127             0,
128             hinst, 
129             None
130         )
131         debug("created: window=%08x" % self.hwnd)
132
133     def OnCopyData(self, hwnd, msg, wparam, lparam):
134         debug("WM_COPYDATA message")
135         debug("  window=%08x" % hwnd)
136         debug("  msg   =%08x" % msg)
137         debug("  wparam=%08x" % wparam)
138         pCDS = ctypes.cast(lparam, P_COPYDATA)
139         debug("  dwData=%08x" % (pCDS.contents.dwData & 0xffffffff))
140         debug("  len=%d" % pCDS.contents.cbData)
141         mapname = ctypes.string_at(pCDS.contents.lpData)
142         debug("  mapname='%s'" % ctypes.string_at(pCDS.contents.lpData))
143         hMapObject = ctypes.windll.kernel32.OpenFileMappingA(FILE_MAP_ALL_ACCESS, 0, mapname)
144         if hMapObject == 0:
145             debug("error on OpenFileMapping")
146             return 0
147         pBuf = ctypes.windll.kernel32.MapViewOfFile(hMapObject, FILE_MAP_ALL_ACCESS, 0, 0, 0)
148         if pBuf == 0:
149             ctypes.windll.kernel32.CloseHandle(hMapObject)
150             debug("error on MapViewOfFile")
151             return 0
152         pSshMsg = ctypes.cast(pBuf, P_SSH_MSG_HEAD)
153         debug("   ssh_msg_len: %d" % pSshMsg.contents.msg_len)
154         debug("   ssh_msg_type: %d" % pSshMsg.contents.msg_type)
155         if pSshMsg.contents.msg_type == 11: # SSH2_AGENT_REQUEST_IDENTITIES
156             blob_len = len(ssh_rsa_public_blob)
157             cmnt_len = len(ssh_key_comment)
158             pAns = ctypes.cast(pBuf, P_SSH_MSG_ID_ANSWER)
159             pAns.contents.msg_len = 1+4+4+blob_len+4+cmnt_len
160             pAns.contents.msg_type = 12 # SSH2_AGENT_IDENTITIES_ANSWER
161             pAns.contents.keys = 1
162             ctypes.memmove(pBuf+4+1+4, pack('>I', blob_len), 4)
163             ctypes.memmove(pBuf+4+1+4+4, ssh_rsa_public_blob, blob_len)
164             ctypes.memmove(pBuf+4+1+4+4+blob_len, pack('>I', cmnt_len), 4)
165             ctypes.memmove(pBuf+4+1+4+4+blob_len+4, ssh_key_comment, cmnt_len)
166
167             debug("answer is:")
168             debug("   ssh_msg_len: %d" % pSshMsg.contents.msg_len)
169             debug("   ssh_msg_type: %d" % pSshMsg.contents.msg_type)
170         elif pSshMsg.contents.msg_type == 13: # SSH2_AGENT_SIGN_REQUEST
171             req_blob_len = unpack(">I", ctypes.string_at(pBuf+5, 4))[0]
172             req_blob = ctypes.string_at(pBuf+5+4, req_blob_len)
173             req_data_len = unpack(">I", ctypes.string_at(pBuf+5+4+req_blob_len,4))[0]
174             req_data = ctypes.string_at(pBuf+5+4+req_blob_len+4,req_data_len)
175             debug("    blob_len=%d" % req_blob_len)
176             debug("    data_len=%d" % req_data_len)
177             hash = hashlib.sha1(req_data).hexdigest()
178             debug("    hash=%s" % hash)
179             g.send_command('SIGKEY %s\n' % keygrip)
180             g.send_command('SETHASH --hash=sha1 %s\n' % hash)
181             g.send_command('PKSIGN\n')
182             sig = sexp(g.get_response())
183             # [ "sig-val" [ "rsa" [ "s" "xxx" ] ] ]
184             sig = sig[1][1][1]
185             sig = "\x00\x00\x00\x07" + "ssh-rsa" + "\x00\x00\x01\x00" + sig # FIXME: should support different key size
186             siglen = len(sig)
187             debug("sig_len=%d" % siglen)
188             debug("sig=%s" % binascii.hexlify(sig))
189             pRes = ctypes.cast(pBuf, P_SSH_MSG_SIGN_RESPONSE)
190             pRes.contents.msg_len = 1+4+siglen
191             pRes.contents.msg_type = 14 # SSH2_AGENT_SIGN_RESPONSE
192             pRes.contents.sig_len = siglen
193             ctypes.memmove(pBuf+4+1+4, sig, siglen)
194             debug("answer is:")
195             debug("   ssh_msg_len: %d" % pSshMsg.contents.msg_len)
196             debug("   ssh_msg_type: %d" % pSshMsg.contents.msg_type)
197         else:
198             exit(0)
199         ctypes.windll.kernel32.UnmapViewOfFile(pBuf)
200         ctypes.windll.kernel32.CloseHandle(hMapObject)
201         debug("   ssh_msg: done")
202         return 1
203
204 l = windows_ipc_listener()
205 win32gui.PumpMessages()