GAppProxyPG uses RSA and AES to secure GAppProxy. The communication between the client and server is encrypted by AES.
-
Python: >= 2.7: Google App Engine has switched to Python 2.7, so do we.
-
Pycrypto: >= 2.6: Pycrypto 2.5 added PKCS#1 RSA encryption and signature schemes which been used by GappProxyPG. Since App Engine only provide Pycrypto 2.3 and 2.6, the version 2.6 is choosen. Binary package for windows can be found here
-
Generate RSA keypairs for server and client, run
local/rsa_keygen.py
will generate two new RSA public/private keypairs, and copy the server's public key to
local/
, client's public key tofetchserver/
-
Upload to App Engine, run
$ path_to_app_engine_sdk/appcfg update path_to_fetchserver
Please refer to App Engine Document for appcfg options.
-
edit
local/proxy.conf
, thefetch_server
is the address of your-appid.appspot.com
The AES key negotiation try to simulate TLS 1.2 as specified in RFC 5246.
The client and the fetch server have each other's RSA public key, which they use to negotiate a MasterSecret, then the key for AES and HMAC.
Because client and server know each other's public key, and the encryption method are pre-determined, the login takes only one step.
The clients use HTTP POST method to send the hello message to login.py.
The client generate a 48 bytes long PreMasterSecret, together with a Client Random.
Structure of Client Random
:
struct {
uint32 timestamp;
opaque random(28);
}
The format of Client Hello
message:
struct {
RSA Encrypt Object (UserName | PreMasterSecret | Client Random);
RSA Signature Signature of above message;
}
Upon receive the client hello message, the server first decrypt it by server's RSA private key, then the cleartext is partitioned into username, PreMasterSecret, and Client Random. The server then lookup the user's public key, use it to verify the RSA signature of client hello message.
The server then generate the Server Random
, which is in the same format like
Client Random
; a random session id also generated as this stage.
Use a PseudoRandom Function(PRF) defined in RFC 5246, the server calculate MasterSecret as:
PRF(PreMasterSecret, 'master secret', Client Random + Server Random)[48]
Then create the key block:
PRF(MasterSecret, 'key expansion', Client Random + Server Random)
The key block contains 4 keys:
- client AES write key
- client HMAC write key
- server AES write key
- server HMAC write key
A timestamp is attached to the key block, then it is stored into AppEngine memcache. Every time the fetchserver retrieve the key block from the memcache, it read the timestamp to verify the key block is not expired. A expired key block will make fetchserver send a custom HTTP 521 error to the client.
The server create Server Finish message as:
Server_Random + Session_id + verify_data
Where verify_data
is generated by PRF:
PRF(MasterSecret, 'server finished', HASH(Session id + Client_Random + Server_Random + PreMasterSecret))
The final server finish message is:
struct {
RSA Encrypt Object (Server_Random + Session_id + verify_data);
RSA Signature Signature of above;
}
After receive the ServerFinish message, client decrypt it using own private key, then verify the signature by Server's public key.
Client partition the cleartext into ServerRandom
, Session Id
, and
verify_data
. Client generate the MasterSecret
, key block
, and
verify_data
using the exact same PRF like server does. Client also verify the
verfify_data
hasn't been changed.
Client then partition the key block
into client and server AES/HMAC keys.
The connection is encrypted with AES, the message integrity is protected by HMAC(Keyed-Hashing for Message Authentication). The client generate the request as:
struct {
Session ID XOR(Session Id, Obfuscate Key);
Random[4] Obfuscate Key;
AES OBJ AES(
Random[16] Request Id;
URLENCODE Plain Request;
);
}
Because the server uses session id to identify the client's AES/HMAC key, the
same Session ID
is prefixed to every request. After apply XOR with a random
Obfuscate key
, the beginning of every request will looks differently to a
third-party observer. The server recover the session id after apply XOR, then
use it to look up key block in memcache. After that, decrypt the client request,
fetch the content use AppEngine urlfetch service, then encapsulate the result in
an AES object and send back to client. A random generate Request ID
is
inserted into every request, the client will verify this id to prevent replay
attack.