In building the proxy application, it became necessary to provide both application handshake recognition and (relatively) strong user authentication. The first is necessary to prevent a trivial denial of service against the application. The second prevents users from switching identities by trivially modifying the authentication information presented to the application server.
The cryptographic functionality presented in the proxy is rather ad-hoc in nature and can be much improved with the addition of a more secure shared secret holder. For an unprivileged user, and a changing shared secret between application server and proxy, the authentication mechanism ought to be reasonably secure. It is worthy to note that this is my first try at this sort of thing. There may be 'classic' errors in the implementation so please read on with that in mind. In addition, the protocol used is simply an example and is not indicative of any particular protocol in use by any application.
The original protocol design provided the following header:
| Reliable Flag | Version 1 ID | Module Destination | Opcode | Version | Reliable Sequence ID | Protocol-Specific data (12 bytes) | ||
| 1 bit | 2 bits
00=v1 10=v2 |
5 bits | 1 byte | 1 byte | 1 byte | TCP: (unused) | TCP: (unused) | TCP: Data Size |
| 4 bytes | 4 bytes | 4 bytes | ||||||
| UDP: Sequence ID | UDP: Stream Byte Offset
(high word) |
UDP:Stream Byte Offset
(low word) |
||||||
| 4 bytes | 4 bytes | 4 bytes | ||||||
This header will remain unmodified in the final form, but the packet format will be scrutinized to make sure that 'fake' connection attempts are not being attempted.
The data section of the connection is next. The original data structure is as follows:
| Size (in bytes) of the path, including NULL terminator | Path to .amm file | Size (in bytes) of the username | Client User Name | Size (in bytes) of the Client GUID | Client GUID | Size (in bytes) of the User Collab ID Tag | User Collab ID Tag |
| 2 bytes (variable) |
2 bytes (variable) |
character array in unicode, NULL terminated. (variable) |
2 bytes 00 4E (78) |
character array in unicode, NULL terminated. (variable) |
2 bytes (variable) |
character array in ASCII, NULL terminated. (variable) |
All multibyte values are in network byte order.
AMM path and username are both handled as a standard string object by the PacketUtility class.
The AMM path is the part of the amt:// URL following the slash after the server. For example, in "amt://server.domain.com/*default/path/file.amm", the AMM path is "*default/path/file.amm".
The Client GUID is a unicode string in registry format, e.g. "{68A36860-7762-11D3-A7E4-080046024690}". This GUID is unique per Windows user, and per machine (or where ever the user profile is stored). It is stored under HKEY_CURRENT_USER in the Windows registry.
The OPEN packet is forward compatible. Fields may be added to the end of the packet in future versions without causing incompatibility with previous versions. Future versions of the code should always check the length of the packet so as not to read past the end of a packet from a previous version.
Here the problem is obvious - we are allowing the client to tell to tell the server who they are without any kind of checking from the server end. If you see another ID going by on the wire, you can hand modify the self definition in the '.am3' file and instantly have the same privileges as that user.
This is, in general, not a failure of the application. We have extended the use of the product beyond a trusted enterprise user model. What we need to do is provide a check to make sure the credentials provided by the user are the same as those handed out by the central application, and that those credentials are only good for a given period of time.
The general design and implementation of the proxy is as follows:
The first screening of the client connection involves the proper layout of the connection attempt. This involves two types of filtering: (1) Connection timeout to prevent a trivial denial of service by sending thousands of SYN packets to the open socket (2) Connection attempt format checking to see if the data structure presented after the three way handshake is sane. This will not stop a determined attacker (who can read the correct packet format as easily as anyone), but at least the data getting to the inside server is of the correct format.
The timeout limiting can be found in socket definition, while the protocol handshake can be seen in AuthenticateProt.java .
For the second part (verifying user credentials and replay attack prevention), things become slightly more difficult. First we need to add additional fields into the data structure of the initial packet. We do this, instead of modifying the native authentication functionality, so that the modified application is fully backwards compactible with current running products. Adding fields is easy, since the last field in the first packet defines the total length of the presented data. The parsing program is set up to gather information from 10 additional fields of 512 bytes length. Additional fields, or fields of greater length will require a (trivial) modification of the code.
The working data structure is now:
| Size of dB host name | HOST_NAME | size of collab dB dB name | HOST_DB_NAME | size of collab dB assigned id | ASS_ID | size of collab dB tag | DB_TAG | size of digest data | AUTH_TAG |
| 2 bytes (variable) |
2 bytes (variable) |
(variable) | 2 bytes |
(variable) | 2 bytes (variable) |
(variable) | 2 bytes (variable) |
(variable) |
With the AUTH_TAG structure doing the heavy lifting for the authentication. It is, in itself, another data structure looking like:
| ----- | |
| (long) | time |
| (double) | random number |
| (int) | hash length |
| byte[ ] | hash1 (SHA) |
| (int) | hash length |
| byte[ ] | hash2 (SHA) |
The mechanics of a connection are as follows: