2 * Pageant: the PuTTY Authentication Agent.
17 #define IDI_MAINICON 200
18 #define IDI_TRAYICON 201
20 #define WM_XUSER (WM_USER + 0x2000)
21 #define WM_SYSTRAY (WM_XUSER + 6)
22 #define WM_SYSTRAY2 (WM_XUSER + 7)
24 #define AGENT_COPYDATA_ID 0x804e50ba /* random goop */
27 * FIXME: maybe some day we can sort this out ...
29 #define AGENT_MAX_MSGLEN 8192
31 #define IDM_CLOSE 0x0010
32 #define IDM_VIEWKEYS 0x0020
33 #define IDM_ADDKEY 0x0030
34 #define IDM_ABOUT 0x0040
36 #define APPNAME "Pageant"
40 static HINSTANCE instance
;
44 static HMENU systray_menu
;
45 static int already_running
;
47 static tree234
*rsakeys
, *ssh2keys
;
49 static int has_security
;
51 typedef DWORD(WINAPI
* gsi_fn_t
)
52 (HANDLE
, SE_OBJECT_TYPE
, SECURITY_INFORMATION
,
53 PSID
*, PSID
*, PACL
*, PACL
*, PSECURITY_DESCRIPTOR
*);
54 static gsi_fn_t getsecurityinfo
;
58 * Exports from pageantc.c
60 void agent_query(void *in
, int inlen
, void **out
, int *outlen
);
61 int agent_exists(void);
64 * We need this to link with the RSA code, because rsaencrypt()
65 * pads its data with random bytes. Since we only use rsadecrypt()
66 * and the signing functions, which are deterministic, this should
69 * If it _is_ called, there is a _serious_ problem, because it
70 * won't generate true random numbers. So we must scream, panic,
71 * and exit immediately if that should happen.
75 MessageBox(hwnd
, "Internal Error", APPNAME
, MB_OK
| MB_ICONERROR
);
77 /* this line can't be reached but it placates MSVC's warnings :-) */
82 * Blob structure for passing to the asymmetric SSH2 key compare
83 * function, prototyped here.
89 static int cmpkeys_ssh2_asymm(void *av
, void *bv
);
92 * This function is needed to link with the DES code. We need not
93 * have it do anything at all.
95 void logevent(char *msg
)
99 #define GET_32BIT(cp) \
100 (((unsigned long)(unsigned char)(cp)[0] << 24) | \
101 ((unsigned long)(unsigned char)(cp)[1] << 16) | \
102 ((unsigned long)(unsigned char)(cp)[2] << 8) | \
103 ((unsigned long)(unsigned char)(cp)[3]))
105 #define PUT_32BIT(cp, value) { \
106 (cp)[0] = (unsigned char)((value) >> 24); \
107 (cp)[1] = (unsigned char)((value) >> 16); \
108 (cp)[2] = (unsigned char)((value) >> 8); \
109 (cp)[3] = (unsigned char)(value); }
111 #define PASSPHRASE_MAXLEN 512
113 struct PassphraseProcStruct
{
118 static tree234
*passphrases
= NULL
;
121 * After processing a list of filenames, we want to forget the
124 static void forget_passphrases(void)
127 while (count234(passphrases
) > 0) {
128 char *pp
= index234(passphrases
, 0);
129 memset(pp
, 0, strlen(pp
));
130 delpos234(passphrases
, 0);
136 * Dialog-box function for the Licence box.
138 static int CALLBACK
LicenceProc(HWND hwnd
, UINT msg
,
139 WPARAM wParam
, LPARAM lParam
)
145 switch (LOWORD(wParam
)) {
159 * Dialog-box function for the About box.
161 static int CALLBACK
AboutProc(HWND hwnd
, UINT msg
,
162 WPARAM wParam
, LPARAM lParam
)
166 SetDlgItemText(hwnd
, 100, ver
);
169 switch (LOWORD(wParam
)) {
175 EnableWindow(hwnd
, 0);
176 DialogBox(instance
, MAKEINTRESOURCE(214), NULL
, LicenceProc
);
177 EnableWindow(hwnd
, 1);
178 SetActiveWindow(hwnd
);
190 static HWND passphrase_box
;
193 * Dialog-box function for the passphrase box.
195 static int CALLBACK
PassphraseProc(HWND hwnd
, UINT msg
,
196 WPARAM wParam
, LPARAM lParam
)
198 static char *passphrase
= NULL
;
199 struct PassphraseProcStruct
*p
;
203 passphrase_box
= hwnd
;
207 { /* centre the window */
211 hw
= GetDesktopWindow();
212 if (GetWindowRect(hw
, &rs
) && GetWindowRect(hwnd
, &rd
))
214 (rs
.right
+ rs
.left
+ rd
.left
- rd
.right
) / 2,
215 (rs
.bottom
+ rs
.top
+ rd
.top
- rd
.bottom
) / 2,
216 rd
.right
- rd
.left
, rd
.bottom
- rd
.top
, TRUE
);
219 SetForegroundWindow(hwnd
);
220 SetWindowPos(hwnd
, HWND_TOP
, 0, 0, 0, 0,
221 SWP_NOMOVE
| SWP_NOSIZE
| SWP_SHOWWINDOW
);
222 p
= (struct PassphraseProcStruct
*) lParam
;
223 passphrase
= p
->passphrase
;
225 SetDlgItemText(hwnd
, 101, p
->comment
);
227 SetDlgItemText(hwnd
, 102, passphrase
);
230 switch (LOWORD(wParam
)) {
240 case 102: /* edit box */
241 if ((HIWORD(wParam
) == EN_CHANGE
) && passphrase
) {
242 GetDlgItemText(hwnd
, 102, passphrase
,
243 PASSPHRASE_MAXLEN
- 1);
244 passphrase
[PASSPHRASE_MAXLEN
- 1] = '\0';
257 * Warn about the obsolescent key file format.
259 void old_keyfile_warning(void)
261 static const char mbtitle
[] = "PuTTY Key File Warning";
262 static const char message
[] =
263 "You are loading an SSH 2 private key which has an\n"
264 "old version of the file format. This means your key\n"
265 "file is not fully tamperproof. Future versions of\n"
266 "PuTTY may stop supporting this private key format,\n"
267 "so we recommend you convert your key to the new\n"
270 "You can perform this conversion by loading the key\n"
271 "into PuTTYgen and then saving it again.";
273 MessageBox(NULL
, message
, mbtitle
, MB_OK
);
277 * Update the visible key list.
279 static void keylist_update(void)
282 struct ssh2_userkey
*skey
;
286 SendDlgItemMessage(keylist
, 100, LB_RESETCONTENT
, 0, 0);
287 for (i
= 0; NULL
!= (rkey
= index234(rsakeys
, i
)); i
++) {
288 char listentry
[512], *p
;
290 * Replace two spaces in the fingerprint with tabs, for
291 * nice alignment in the box.
293 strcpy(listentry
, "ssh1\t");
294 p
= listentry
+ strlen(listentry
);
295 rsa_fingerprint(p
, sizeof(listentry
) - (p
- listentry
), rkey
);
296 p
= strchr(listentry
, ' ');
299 p
= strchr(listentry
, ' ');
302 SendDlgItemMessage(keylist
, 100, LB_ADDSTRING
,
303 0, (LPARAM
) listentry
);
305 for (i
= 0; NULL
!= (skey
= index234(ssh2keys
, i
)); i
++) {
306 char listentry
[512], *p
;
309 * Replace two spaces in the fingerprint with tabs, for
310 * nice alignment in the box.
312 p
= skey
->alg
->fingerprint(skey
->data
);
313 strncpy(listentry
, p
, sizeof(listentry
));
314 p
= strchr(listentry
, ' ');
317 p
= strchr(listentry
, ' ');
320 len
= strlen(listentry
);
321 if (len
< sizeof(listentry
) - 2) {
322 listentry
[len
] = '\t';
323 strncpy(listentry
+ len
+ 1, skey
->comment
,
324 sizeof(listentry
) - len
- 1);
326 SendDlgItemMessage(keylist
, 100, LB_ADDSTRING
, 0,
329 SendDlgItemMessage(keylist
, 100, LB_SETCURSEL
, (WPARAM
) - 1, 0);
334 * This function loads a key from a file and adds it.
336 static void add_keyfile(char *filename
)
338 char passphrase
[PASSPHRASE_MAXLEN
];
339 struct RSAKey
*rkey
= NULL
;
340 struct ssh2_userkey
*skey
= NULL
;
345 struct PassphraseProcStruct pps
;
349 ver
= keyfile_version(filename
);
351 MessageBox(NULL
, "Couldn't load private key.", APPNAME
,
352 MB_OK
| MB_ICONERROR
);
357 needs_pass
= rsakey_encrypted(filename
, &comment
);
359 needs_pass
= ssh2_userkey_encrypted(filename
, &comment
);
362 rkey
= smalloc(sizeof(*rkey
));
363 pps
.passphrase
= passphrase
;
364 pps
.comment
= comment
;
368 /* try all the remembered passphrases first */
369 char *pp
= index234(passphrases
, attempts
);
371 strcpy(passphrase
, pp
);
375 dlgret
= DialogBoxParam(instance
, MAKEINTRESOURCE(210),
376 NULL
, PassphraseProc
, (LPARAM
) & pps
);
377 passphrase_box
= NULL
;
383 return; /* operation cancelled */
389 ret
= loadrsakey(filename
, rkey
, passphrase
);
391 skey
= ssh2_load_userkey(filename
, passphrase
);
392 if (skey
== SSH2_WRONG_PASSPHRASE
)
402 /* if they typed in an ok passphrase, remember it */
403 if(original_pass
&& ret
) {
404 char *pp
= dupstr(passphrase
);
405 addpos234(passphrases
, pp
, 0);
411 MessageBox(NULL
, "Couldn't load private key.", APPNAME
,
412 MB_OK
| MB_ICONERROR
);
418 if (already_running
) {
419 unsigned char *request
, *response
;
421 int reqlen
, clen
, resplen
;
423 clen
= strlen(rkey
->comment
);
425 reqlen
= 4 + 1 + /* length, message type */
427 ssh1_bignum_length(rkey
->modulus
) +
428 ssh1_bignum_length(rkey
->exponent
) +
429 ssh1_bignum_length(rkey
->private_exponent
) +
430 ssh1_bignum_length(rkey
->iqmp
) +
431 ssh1_bignum_length(rkey
->p
) +
432 ssh1_bignum_length(rkey
->q
) + 4 + clen
/* comment */
435 request
= smalloc(reqlen
);
437 request
[4] = SSH1_AGENTC_ADD_RSA_IDENTITY
;
439 PUT_32BIT(request
+ reqlen
, bignum_bitcount(rkey
->modulus
));
441 reqlen
+= ssh1_write_bignum(request
+ reqlen
, rkey
->modulus
);
442 reqlen
+= ssh1_write_bignum(request
+ reqlen
, rkey
->exponent
);
444 ssh1_write_bignum(request
+ reqlen
,
445 rkey
->private_exponent
);
446 reqlen
+= ssh1_write_bignum(request
+ reqlen
, rkey
->iqmp
);
447 reqlen
+= ssh1_write_bignum(request
+ reqlen
, rkey
->p
);
448 reqlen
+= ssh1_write_bignum(request
+ reqlen
, rkey
->q
);
449 PUT_32BIT(request
+ reqlen
, clen
);
450 memcpy(request
+ reqlen
+ 4, rkey
->comment
, clen
);
452 PUT_32BIT(request
, reqlen
- 4);
454 agent_query(request
, reqlen
, &vresponse
, &resplen
);
455 response
= vresponse
;
456 if (resplen
< 5 || response
[4] != SSH_AGENT_SUCCESS
)
457 MessageBox(NULL
, "The already running Pageant "
458 "refused to add the key.", APPNAME
,
459 MB_OK
| MB_ICONERROR
);
461 if (add234(rsakeys
, rkey
) != rkey
)
462 sfree(rkey
); /* already present, don't waste RAM */
465 if (already_running
) {
466 unsigned char *request
, *response
;
468 int reqlen
, alglen
, clen
, keybloblen
, resplen
;
469 alglen
= strlen(skey
->alg
->name
);
470 clen
= strlen(skey
->comment
);
472 keybloblen
= skey
->alg
->openssh_fmtkey(skey
->data
, NULL
, 0);
474 reqlen
= 4 + 1 + /* length, message type */
475 4 + alglen
+ /* algorithm name */
476 keybloblen
+ /* key data */
477 4 + clen
/* comment */
480 request
= smalloc(reqlen
);
482 request
[4] = SSH2_AGENTC_ADD_IDENTITY
;
484 PUT_32BIT(request
+ reqlen
, alglen
);
486 memcpy(request
+ reqlen
, skey
->alg
->name
, alglen
);
488 reqlen
+= skey
->alg
->openssh_fmtkey(skey
->data
,
491 PUT_32BIT(request
+ reqlen
, clen
);
492 memcpy(request
+ reqlen
+ 4, skey
->comment
, clen
);
493 PUT_32BIT(request
, reqlen
- 4);
496 agent_query(request
, reqlen
, &vresponse
, &resplen
);
497 response
= vresponse
;
498 if (resplen
< 5 || response
[4] != SSH_AGENT_SUCCESS
)
499 MessageBox(NULL
, "The already running Pageant"
500 "refused to add the key.", APPNAME
,
501 MB_OK
| MB_ICONERROR
);
503 if (add234(ssh2keys
, skey
) != skey
) {
504 skey
->alg
->freekey(skey
->data
);
505 sfree(skey
); /* already present, don't waste RAM */
512 * This is the main agent function that answers messages.
514 static void answer_msg(void *msg
)
516 unsigned char *p
= msg
;
517 unsigned char *ret
= msg
;
521 * Get the message type.
527 case SSH1_AGENTC_REQUEST_RSA_IDENTITIES
:
529 * Reply with SSH1_AGENT_RSA_IDENTITIES_ANSWER.
537 * Count up the number and length of keys we hold.
540 for (i
= 0; NULL
!= (key
= index234(rsakeys
, i
)); i
++) {
542 len
+= 4; /* length field */
543 len
+= ssh1_bignum_length(key
->exponent
);
544 len
+= ssh1_bignum_length(key
->modulus
);
545 len
+= 4 + strlen(key
->comment
);
549 * Packet header is the obvious five bytes, plus four
550 * bytes for the key count.
553 if (len
> AGENT_MAX_MSGLEN
)
554 goto failure
; /* aaargh! too much stuff! */
555 PUT_32BIT(ret
, len
- 4);
556 ret
[4] = SSH1_AGENT_RSA_IDENTITIES_ANSWER
;
557 PUT_32BIT(ret
+ 5, nkeys
);
559 for (i
= 0; NULL
!= (key
= index234(rsakeys
, i
)); i
++) {
560 PUT_32BIT(p
, bignum_bitcount(key
->modulus
));
562 p
+= ssh1_write_bignum(p
, key
->exponent
);
563 p
+= ssh1_write_bignum(p
, key
->modulus
);
564 PUT_32BIT(p
, strlen(key
->comment
));
565 memcpy(p
+ 4, key
->comment
, strlen(key
->comment
));
566 p
+= 4 + strlen(key
->comment
);
570 case SSH2_AGENTC_REQUEST_IDENTITIES
:
572 * Reply with SSH2_AGENT_IDENTITIES_ANSWER.
575 struct ssh2_userkey
*key
;
582 * Count up the number and length of keys we hold.
585 for (i
= 0; NULL
!= (key
= index234(ssh2keys
, i
)); i
++) {
587 len
+= 4; /* length field */
588 blob
= key
->alg
->public_blob(key
->data
, &bloblen
);
591 len
+= 4 + strlen(key
->comment
);
595 * Packet header is the obvious five bytes, plus four
596 * bytes for the key count.
599 if (len
> AGENT_MAX_MSGLEN
)
600 goto failure
; /* aaargh! too much stuff! */
601 PUT_32BIT(ret
, len
- 4);
602 ret
[4] = SSH2_AGENT_IDENTITIES_ANSWER
;
603 PUT_32BIT(ret
+ 5, nkeys
);
605 for (i
= 0; NULL
!= (key
= index234(ssh2keys
, i
)); i
++) {
606 blob
= key
->alg
->public_blob(key
->data
, &bloblen
);
607 PUT_32BIT(p
, bloblen
);
609 memcpy(p
, blob
, bloblen
);
612 PUT_32BIT(p
, strlen(key
->comment
));
613 memcpy(p
+ 4, key
->comment
, strlen(key
->comment
));
614 p
+= 4 + strlen(key
->comment
);
618 case SSH1_AGENTC_RSA_CHALLENGE
:
620 * Reply with either SSH1_AGENT_RSA_RESPONSE or
621 * SSH_AGENT_FAILURE, depending on whether we have that key
625 struct RSAKey reqkey
, *key
;
626 Bignum challenge
, response
;
627 unsigned char response_source
[48], response_md5
[16];
628 struct MD5Context md5c
;
632 p
+= ssh1_read_bignum(p
, &reqkey
.exponent
);
633 p
+= ssh1_read_bignum(p
, &reqkey
.modulus
);
634 p
+= ssh1_read_bignum(p
, &challenge
);
635 memcpy(response_source
+ 32, p
, 16);
637 if (GET_32BIT(p
) != 1 ||
638 (key
= find234(rsakeys
, &reqkey
, NULL
)) == NULL
) {
639 freebn(reqkey
.exponent
);
640 freebn(reqkey
.modulus
);
644 response
= rsadecrypt(challenge
, key
);
645 for (i
= 0; i
< 32; i
++)
646 response_source
[i
] = bignum_byte(response
, 31 - i
);
649 MD5Update(&md5c
, response_source
, 48);
650 MD5Final(response_md5
, &md5c
);
651 memset(response_source
, 0, 48); /* burn the evidence */
652 freebn(response
); /* and that evidence */
653 freebn(challenge
); /* yes, and that evidence */
654 freebn(reqkey
.exponent
); /* and free some memory ... */
655 freebn(reqkey
.modulus
); /* ... while we're at it. */
658 * Packet is the obvious five byte header, plus sixteen
662 PUT_32BIT(ret
, len
- 4);
663 ret
[4] = SSH1_AGENT_RSA_RESPONSE
;
664 memcpy(ret
+ 5, response_md5
, 16);
667 case SSH2_AGENTC_SIGN_REQUEST
:
669 * Reply with either SSH2_AGENT_SIGN_RESPONSE or
670 * SSH_AGENT_FAILURE, depending on whether we have that key
674 struct ssh2_userkey
*key
;
676 unsigned char *data
, *signature
;
677 int datalen
, siglen
, len
;
679 b
.len
= GET_32BIT(p
);
683 datalen
= GET_32BIT(p
);
686 key
= find234(ssh2keys
, &b
, cmpkeys_ssh2_asymm
);
689 signature
= key
->alg
->sign(key
->data
, data
, datalen
, &siglen
);
690 len
= 5 + 4 + siglen
;
691 PUT_32BIT(ret
, len
- 4);
692 ret
[4] = SSH2_AGENT_SIGN_RESPONSE
;
693 PUT_32BIT(ret
+ 5, siglen
);
694 memcpy(ret
+ 5 + 4, signature
, siglen
);
698 case SSH1_AGENTC_ADD_RSA_IDENTITY
:
700 * Add to the list and return SSH_AGENT_SUCCESS, or
701 * SSH_AGENT_FAILURE if the key was malformed.
707 key
= smalloc(sizeof(struct RSAKey
));
708 memset(key
, 0, sizeof(struct RSAKey
));
709 p
+= makekey(p
, key
, NULL
, 1);
710 p
+= makeprivate(p
, key
);
711 p
+= ssh1_read_bignum(p
, &key
->iqmp
); /* p^-1 mod q */
712 p
+= ssh1_read_bignum(p
, &key
->p
); /* p */
713 p
+= ssh1_read_bignum(p
, &key
->q
); /* q */
714 commentlen
= GET_32BIT(p
);
715 comment
= smalloc(commentlen
+1);
717 memcpy(comment
, p
+ 4, commentlen
);
718 comment
[commentlen
] = '\0';
719 key
->comment
= comment
;
722 ret
[4] = SSH_AGENT_FAILURE
;
723 if (add234(rsakeys
, key
) == key
) {
725 ret
[4] = SSH_AGENT_SUCCESS
;
732 case SSH2_AGENTC_ADD_IDENTITY
:
734 * Add to the list and return SSH_AGENT_SUCCESS, or
735 * SSH_AGENT_FAILURE if the key was malformed.
738 struct ssh2_userkey
*key
;
743 key
= smalloc(sizeof(struct ssh2_userkey
));
745 alglen
= GET_32BIT(p
);
749 /* Add further algorithm names here. */
750 if (alglen
== 7 && !memcmp(alg
, "ssh-rsa", 7))
752 else if (alglen
== 7 && !memcmp(alg
, "ssh-dss", 7))
760 GET_32BIT((unsigned char *) msg
) - (p
-
761 (unsigned char *) msg
-
763 key
->data
= key
->alg
->openssh_createkey(&p
, &bloblen
);
768 commlen
= GET_32BIT(p
);
771 comment
= smalloc(commlen
+ 1);
773 memcpy(comment
, p
, commlen
);
774 comment
[commlen
] = '\0';
776 key
->comment
= comment
;
779 ret
[4] = SSH_AGENT_FAILURE
;
780 if (add234(ssh2keys
, key
) == key
) {
782 ret
[4] = SSH_AGENT_SUCCESS
;
784 key
->alg
->freekey(key
->data
);
790 case SSH1_AGENTC_REMOVE_RSA_IDENTITY
:
792 * Remove from the list and return SSH_AGENT_SUCCESS, or
793 * perhaps SSH_AGENT_FAILURE if it wasn't in the list to
797 struct RSAKey reqkey
, *key
;
799 p
+= makekey(p
, &reqkey
, NULL
, 0);
800 key
= find234(rsakeys
, &reqkey
, NULL
);
801 freebn(reqkey
.exponent
);
802 freebn(reqkey
.modulus
);
804 ret
[4] = SSH_AGENT_FAILURE
;
806 del234(rsakeys
, key
);
810 ret
[4] = SSH_AGENT_SUCCESS
;
814 case SSH2_AGENTC_REMOVE_IDENTITY
:
816 * Remove from the list and return SSH_AGENT_SUCCESS, or
817 * perhaps SSH_AGENT_FAILURE if it wasn't in the list to
821 struct ssh2_userkey
*key
;
824 b
.len
= GET_32BIT(p
);
828 key
= find234(ssh2keys
, &b
, cmpkeys_ssh2_asymm
);
833 ret
[4] = SSH_AGENT_FAILURE
;
835 del234(ssh2keys
, key
);
837 key
->alg
->freekey(key
->data
);
839 ret
[4] = SSH_AGENT_SUCCESS
;
843 case SSH1_AGENTC_REMOVE_ALL_RSA_IDENTITIES
:
845 * Remove all SSH1 keys. Always returns success.
850 while ((rkey
= index234(rsakeys
, 0)) != NULL
) {
851 del234(rsakeys
, rkey
);
858 ret
[4] = SSH_AGENT_SUCCESS
;
861 case SSH2_AGENTC_REMOVE_ALL_IDENTITIES
:
863 * Remove all SSH2 keys. Always returns success.
866 struct ssh2_userkey
*skey
;
868 while ((skey
= index234(ssh2keys
, 0)) != NULL
) {
869 del234(ssh2keys
, skey
);
870 skey
->alg
->freekey(skey
->data
);
876 ret
[4] = SSH_AGENT_SUCCESS
;
882 * Unrecognised message. Return SSH_AGENT_FAILURE.
885 ret
[4] = SSH_AGENT_FAILURE
;
891 * Key comparison function for the 2-3-4 tree of RSA keys.
893 static int cmpkeys_rsa(void *av
, void *bv
)
895 struct RSAKey
*a
= (struct RSAKey
*) av
;
896 struct RSAKey
*b
= (struct RSAKey
*) bv
;
903 * Compare by length of moduli.
905 alen
= bignum_bitcount(am
);
906 blen
= bignum_bitcount(bm
);
909 else if (alen
< blen
)
912 * Now compare by moduli themselves.
914 alen
= (alen
+ 7) / 8; /* byte count */
917 abyte
= bignum_byte(am
, alen
);
918 bbyte
= bignum_byte(bm
, alen
);
921 else if (abyte
< bbyte
)
931 * Key comparison function for the 2-3-4 tree of SSH2 keys.
933 static int cmpkeys_ssh2(void *av
, void *bv
)
935 struct ssh2_userkey
*a
= (struct ssh2_userkey
*) av
;
936 struct ssh2_userkey
*b
= (struct ssh2_userkey
*) bv
;
939 unsigned char *ablob
, *bblob
;
943 * Compare purely by public blob.
945 ablob
= a
->alg
->public_blob(a
->data
, &alen
);
946 bblob
= b
->alg
->public_blob(b
->data
, &blen
);
949 for (i
= 0; i
< alen
&& i
< blen
; i
++) {
950 if (ablob
[i
] < bblob
[i
]) {
953 } else if (ablob
[i
] > bblob
[i
]) {
958 if (c
== 0 && i
< alen
)
959 c
= +1; /* a is longer */
960 if (c
== 0 && i
< blen
)
961 c
= -1; /* a is longer */
970 * Key comparison function for looking up a blob in the 2-3-4 tree
973 static int cmpkeys_ssh2_asymm(void *av
, void *bv
)
975 struct blob
*a
= (struct blob
*) av
;
976 struct ssh2_userkey
*b
= (struct ssh2_userkey
*) bv
;
979 unsigned char *ablob
, *bblob
;
983 * Compare purely by public blob.
987 bblob
= b
->alg
->public_blob(b
->data
, &blen
);
990 for (i
= 0; i
< alen
&& i
< blen
; i
++) {
991 if (ablob
[i
] < bblob
[i
]) {
994 } else if (ablob
[i
] > bblob
[i
]) {
999 if (c
== 0 && i
< alen
)
1000 c
= +1; /* a is longer */
1001 if (c
== 0 && i
< blen
)
1002 c
= -1; /* a is longer */
1010 * Prompt for a key file to add, and add it.
1012 static void prompt_add_keyfile(void)
1015 char filename
[FILENAME_MAX
];
1016 char *filelist
= smalloc(8192);
1020 memset(&of
, 0, sizeof(of
));
1021 #ifdef OPENFILENAME_SIZE_VERSION_400
1022 of
.lStructSize
= OPENFILENAME_SIZE_VERSION_400
;
1024 of
.lStructSize
= sizeof(of
);
1026 of
.hwndOwner
= hwnd
;
1027 of
.lpstrFilter
= "All Files\0*\0\0\0";
1028 of
.lpstrCustomFilter
= NULL
;
1029 of
.nFilterIndex
= 1;
1030 of
.lpstrFile
= filelist
;
1032 of
.nMaxFile
= FILENAME_MAX
;
1033 of
.lpstrFileTitle
= NULL
;
1034 of
.lpstrInitialDir
= NULL
;
1035 of
.lpstrTitle
= "Select Private Key File";
1036 of
.Flags
= OFN_ALLOWMULTISELECT
| OFN_EXPLORER
;
1037 if (GetOpenFileName(&of
)) {
1038 if(strlen(filelist
) > of
.nFileOffset
)
1039 /* Only one filename returned? */
1040 add_keyfile(filelist
);
1042 /* we are returned a bunch of strings, end to
1043 * end. first string is the directory, the
1044 * rest the filenames. terminated with an
1047 filewalker
= filelist
;
1048 dirlen
= strlen(filewalker
);
1049 if(dirlen
> FILENAME_MAX
- 8) return;
1050 memcpy(filename
, filewalker
, dirlen
);
1052 filewalker
+= dirlen
+ 1;
1053 filename
[dirlen
++] = '\\';
1055 /* then go over names one by one */
1057 n
= strlen(filewalker
) + 1;
1058 /* end of the list */
1061 /* too big, shouldn't happen */
1062 if(n
+ dirlen
> FILENAME_MAX
)
1065 memcpy(filename
+ dirlen
, filewalker
, n
);
1068 add_keyfile(filename
);
1073 forget_passphrases();
1079 * Dialog-box function for the key list box.
1081 static int CALLBACK
KeyListProc(HWND hwnd
, UINT msg
,
1082 WPARAM wParam
, LPARAM lParam
)
1084 struct RSAKey
*rkey
;
1085 struct ssh2_userkey
*skey
;
1090 * Centre the window.
1092 { /* centre the window */
1096 hw
= GetDesktopWindow();
1097 if (GetWindowRect(hw
, &rs
) && GetWindowRect(hwnd
, &rd
))
1099 (rs
.right
+ rs
.left
+ rd
.left
- rd
.right
) / 2,
1100 (rs
.bottom
+ rs
.top
+ rd
.top
- rd
.bottom
) / 2,
1101 rd
.right
- rd
.left
, rd
.bottom
- rd
.top
, TRUE
);
1106 static int tabs
[] = { 35, 60, 210 };
1107 SendDlgItemMessage(hwnd
, 100, LB_SETTABSTOPS
,
1108 sizeof(tabs
) / sizeof(*tabs
),
1114 switch (LOWORD(wParam
)) {
1118 DestroyWindow(hwnd
);
1120 case 101: /* add key */
1121 if (HIWORD(wParam
) == BN_CLICKED
||
1122 HIWORD(wParam
) == BN_DOUBLECLICKED
) {
1123 if (passphrase_box
) {
1124 MessageBeep(MB_ICONERROR
);
1125 SetForegroundWindow(passphrase_box
);
1128 prompt_add_keyfile();
1131 case 102: /* remove key */
1132 if (HIWORD(wParam
) == BN_CLICKED
||
1133 HIWORD(wParam
) == BN_DOUBLECLICKED
) {
1138 /* our counter within the array of selected items */
1141 /* get the number of items selected in the list */
1143 SendDlgItemMessage(hwnd
, 100, LB_GETSELCOUNT
, 0, 0);
1145 /* none selected? that was silly */
1146 if (numSelected
== 0) {
1151 /* get item indices in an array */
1152 selectedArray
= smalloc(numSelected
* sizeof(int));
1153 SendDlgItemMessage(hwnd
, 100, LB_GETSELITEMS
,
1154 numSelected
, (WPARAM
)selectedArray
);
1156 itemNum
= numSelected
- 1;
1157 rCount
= count234(rsakeys
);
1158 sCount
= count234(ssh2keys
);
1160 /* go through the non-rsakeys until we've covered them all,
1161 * and/or we're out of selected items to check. note that
1162 * we go *backwards*, to avoid complications from deleting
1163 * things hence altering the offset of subsequent items
1165 for (i
= sCount
- 1; (itemNum
>= 0) && (i
>= 0); i
--) {
1166 skey
= index234(ssh2keys
, i
);
1168 if (selectedArray
[itemNum
] == rCount
+ i
) {
1169 del234(ssh2keys
, skey
);
1170 skey
->alg
->freekey(skey
->data
);
1176 /* do the same for the rsa keys */
1177 for (i
= rCount
- 1; (itemNum
>= 0) && (i
>= 0); i
--) {
1178 rkey
= index234(rsakeys
, i
);
1180 if(selectedArray
[itemNum
] == i
) {
1181 del234(rsakeys
, rkey
);
1188 sfree(selectedArray
);
1196 DestroyWindow(hwnd
);
1202 /* Set up a system tray icon */
1203 static BOOL
AddTrayIcon(HWND hwnd
)
1206 NOTIFYICONDATA tnid
;
1209 #ifdef NIM_SETVERSION
1211 res
= Shell_NotifyIcon(NIM_SETVERSION
, &tnid
);
1214 tnid
.cbSize
= sizeof(NOTIFYICONDATA
);
1216 tnid
.uID
= 1; /* unique within this systray use */
1217 tnid
.uFlags
= NIF_MESSAGE
| NIF_ICON
| NIF_TIP
;
1218 tnid
.uCallbackMessage
= WM_SYSTRAY
;
1219 tnid
.hIcon
= hicon
= LoadIcon(instance
, MAKEINTRESOURCE(201));
1220 strcpy(tnid
.szTip
, "Pageant (PuTTY authentication agent)");
1222 res
= Shell_NotifyIcon(NIM_ADD
, &tnid
);
1224 if (hicon
) DestroyIcon(hicon
);
1229 static LRESULT CALLBACK
WndProc(HWND hwnd
, UINT message
,
1230 WPARAM wParam
, LPARAM lParam
)
1233 static int menuinprogress
;
1234 static UINT msgTaskbarCreated
= 0;
1238 msgTaskbarCreated
= RegisterWindowMessage(_T("TaskbarCreated"));
1241 if (message
==msgTaskbarCreated
) {
1243 * Explorer has been restarted, so the tray icon will
1251 if (lParam
== WM_RBUTTONUP
) {
1253 GetCursorPos(&cursorpos
);
1254 PostMessage(hwnd
, WM_SYSTRAY2
, cursorpos
.x
, cursorpos
.y
);
1255 } else if (lParam
== WM_LBUTTONDBLCLK
) {
1256 /* Equivalent to IDM_VIEWKEYS. */
1257 PostMessage(hwnd
, WM_COMMAND
, IDM_VIEWKEYS
, 0);
1261 if (!menuinprogress
) {
1263 SetForegroundWindow(hwnd
);
1264 ret
= TrackPopupMenu(systray_menu
,
1265 TPM_RIGHTALIGN
| TPM_BOTTOMALIGN
|
1267 wParam
, lParam
, 0, hwnd
, NULL
);
1273 switch (wParam
& ~0xF) { /* low 4 bits reserved to Windows */
1276 SendMessage(passphrase_box
, WM_CLOSE
, 0, 0);
1277 SendMessage(hwnd
, WM_CLOSE
, 0, 0);
1281 keylist
= CreateDialog(instance
, MAKEINTRESOURCE(211),
1283 ShowWindow(keylist
, SW_SHOWNORMAL
);
1285 * Sometimes the window comes up minimised / hidden
1286 * for no obvious reason. Prevent this.
1288 SetForegroundWindow(keylist
);
1289 SetWindowPos(keylist
, HWND_TOP
, 0, 0, 0, 0,
1290 SWP_NOMOVE
| SWP_NOSIZE
| SWP_SHOWWINDOW
);
1294 if (passphrase_box
) {
1295 MessageBeep(MB_ICONERROR
);
1296 SetForegroundWindow(passphrase_box
);
1299 prompt_add_keyfile();
1303 aboutbox
= CreateDialog(instance
, MAKEINTRESOURCE(213),
1305 ShowWindow(aboutbox
, SW_SHOWNORMAL
);
1307 * Sometimes the window comes up minimised / hidden
1308 * for no obvious reason. Prevent this.
1310 SetForegroundWindow(aboutbox
);
1311 SetWindowPos(aboutbox
, HWND_TOP
, 0, 0, 0, 0,
1312 SWP_NOMOVE
| SWP_NOSIZE
| SWP_SHOWWINDOW
);
1322 COPYDATASTRUCT
*cds
;
1328 PSID mapowner
, procowner
;
1329 PSECURITY_DESCRIPTOR psd1
= NULL
, psd2
= NULL
;
1333 cds
= (COPYDATASTRUCT
*) lParam
;
1334 if (cds
->dwData
!= AGENT_COPYDATA_ID
)
1335 return 0; /* not our message, mate */
1336 mapname
= (char *) cds
->lpData
;
1337 if (mapname
[cds
->cbData
- 1] != '\0')
1338 return 0; /* failure to be ASCIZ! */
1340 debug(("mapname is :%s:\n", mapname
));
1342 filemap
= OpenFileMapping(FILE_MAP_ALL_ACCESS
, FALSE
, mapname
);
1344 debug(("filemap is %p\n", filemap
));
1346 if (filemap
!= NULL
&& filemap
!= INVALID_HANDLE_VALUE
) {
1350 if ((proc
= OpenProcess(MAXIMUM_ALLOWED
, FALSE
,
1351 GetCurrentProcessId())) ==
1354 debug(("couldn't get handle for process\n"));
1358 if (getsecurityinfo(proc
, SE_KERNEL_OBJECT
,
1359 OWNER_SECURITY_INFORMATION
,
1360 &procowner
, NULL
, NULL
, NULL
,
1361 &psd2
) != ERROR_SUCCESS
) {
1363 debug(("couldn't get owner info for process\n"));
1366 return 0; /* unable to get security info */
1369 if ((rc
= getsecurityinfo(filemap
, SE_KERNEL_OBJECT
,
1370 OWNER_SECURITY_INFORMATION
,
1371 &mapowner
, NULL
, NULL
, NULL
,
1372 &psd1
) != ERROR_SUCCESS
)) {
1375 ("couldn't get owner info for filemap: %d\n",
1381 debug(("got security stuff\n"));
1383 if (!EqualSid(mapowner
, procowner
))
1384 return 0; /* security ID mismatch! */
1386 debug(("security stuff matched\n"));
1392 debug(("security APIs not present\n"));
1396 p
= MapViewOfFile(filemap
, FILE_MAP_WRITE
, 0, 0, 0);
1398 debug(("p is %p\n", p
));
1401 for (i
= 0; i
< 5; i
++)
1404 ((unsigned char *) p
)[i
]));}
1410 CloseHandle(filemap
);
1415 return DefWindowProc(hwnd
, message
, wParam
, lParam
);
1419 * Fork and Exec the command in cmdline. [DBW]
1421 void spawn_cmd(char *cmdline
, char * args
, int show
)
1423 if (ShellExecute(NULL
, _T("open"), cmdline
,
1424 args
, NULL
, show
) <= (HINSTANCE
) 32) {
1426 sprintf(sMsg
, _T("Failed to run \"%.100s\", Error: %d"), cmdline
,
1427 (int)GetLastError());
1428 MessageBox(NULL
, sMsg
, APPNAME
, MB_OK
| MB_ICONEXCLAMATION
);
1432 int WINAPI
WinMain(HINSTANCE inst
, HINSTANCE prev
, LPSTR cmdline
, int show
)
1438 char *command
= NULL
;
1442 * Determine whether we're an NT system (should have security
1443 * APIs) or a non-NT system (don't do security).
1445 memset(&osi
, 0, sizeof(OSVERSIONINFO
));
1446 osi
.dwOSVersionInfoSize
= sizeof(OSVERSIONINFO
);
1447 if (GetVersionEx(&osi
) && osi
.dwPlatformId
== VER_PLATFORM_WIN32_NT
) {
1448 has_security
= TRUE
;
1450 has_security
= FALSE
;
1455 * Attempt to get the security API we need.
1457 advapi
= LoadLibrary("ADVAPI32.DLL");
1459 (gsi_fn_t
) GetProcAddress(advapi
, "GetSecurityInfo");
1460 if (!getsecurityinfo
) {
1462 "Unable to access security APIs. Pageant will\n"
1463 "not run, in case it causes a security breach.",
1464 "Pageant Fatal Error", MB_ICONERROR
| MB_OK
);
1469 "This program has been compiled for Win9X and will\n"
1470 "not run on NT, in case it causes a security breach.",
1471 "Pageant Fatal Error", MB_ICONERROR
| MB_OK
);
1480 * Find out if Pageant is already running.
1482 already_running
= FALSE
;
1484 already_running
= TRUE
;
1489 wndclass
.lpfnWndProc
= WndProc
;
1490 wndclass
.cbClsExtra
= 0;
1491 wndclass
.cbWndExtra
= 0;
1492 wndclass
.hInstance
= inst
;
1493 wndclass
.hIcon
= LoadIcon(inst
, MAKEINTRESOURCE(IDI_MAINICON
));
1494 wndclass
.hCursor
= LoadCursor(NULL
, IDC_IBEAM
);
1495 wndclass
.hbrBackground
= GetStockObject(BLACK_BRUSH
);
1496 wndclass
.lpszMenuName
= NULL
;
1497 wndclass
.lpszClassName
= APPNAME
;
1499 RegisterClass(&wndclass
);
1502 hwnd
= keylist
= NULL
;
1504 hwnd
= CreateWindow(APPNAME
, APPNAME
,
1505 WS_OVERLAPPEDWINDOW
| WS_VSCROLL
,
1506 CW_USEDEFAULT
, CW_USEDEFAULT
,
1507 100, 100, NULL
, NULL
, inst
, NULL
);
1509 /* Set up a system tray icon */
1512 systray_menu
= CreatePopupMenu();
1513 /* accelerators used: vkxa */
1514 AppendMenu(systray_menu
, MF_ENABLED
, IDM_VIEWKEYS
,
1516 AppendMenu(systray_menu
, MF_ENABLED
, IDM_ADDKEY
, "Add &Key");
1517 AppendMenu(systray_menu
, MF_ENABLED
, IDM_ABOUT
, "&About");
1518 AppendMenu(systray_menu
, MF_ENABLED
, IDM_CLOSE
, "E&xit");
1520 ShowWindow(hwnd
, SW_HIDE
);
1523 * Initialise storage for RSA keys.
1525 rsakeys
= newtree234(cmpkeys_rsa
);
1526 ssh2keys
= newtree234(cmpkeys_ssh2
);
1531 * Initialise storage for short-term passphrase cache.
1533 passphrases
= newtree234(NULL
);
1536 * Process the command line and add keys as listed on it.
1537 * If we already determined that we need to spawn a program from above we
1538 * need to ignore the first two arguments. [DBW]
1545 while (*p
&& isspace(*p
))
1547 if (*p
&& !isspace(*p
)) {
1548 char *q
= p
, *pp
= p
;
1549 while (*p
&& (inquotes
|| !isspace(*p
))) {
1551 inquotes
= !inquotes
;
1562 if (!strcmp(q
, "-c")) {
1564 * If we see `-c', then the rest of the
1565 * command line should be treated as a
1566 * command to be spawned.
1568 while (*p
&& isspace(*p
))
1581 * Forget any passphrase that we retained while going over
1582 * command line keyfiles.
1584 forget_passphrases();
1588 if (command
[0] == '"')
1589 args
= strchr(++command
, '"');
1591 args
= strchr(command
, ' ');
1594 while(*args
&& isspace(*args
)) args
++;
1596 spawn_cmd(command
, args
, show
);
1600 * If Pageant was already running, we leave now. If we haven't
1601 * even taken any auxiliary action (spawned a command or added
1604 if (already_running
) {
1605 if (!command
&& !added_keys
) {
1606 MessageBox(NULL
, "Pageant is already running", "Pageant Error",
1607 MB_ICONERROR
| MB_OK
);
1610 FreeLibrary(advapi
);
1615 * Main message loop.
1617 while (GetMessage(&msg
, NULL
, 0, 0) == 1) {
1618 TranslateMessage(&msg
);
1619 DispatchMessage(&msg
);
1622 /* Clean up the system tray icon */
1624 NOTIFYICONDATA tnid
;
1626 tnid
.cbSize
= sizeof(NOTIFYICONDATA
);
1630 Shell_NotifyIcon(NIM_DELETE
, &tnid
);
1632 DestroyMenu(systray_menu
);
1636 FreeLibrary(advapi
);