An optional secure channel can be created for authentication to ensure credentials are encrypted

when sent to the server.

To implement a secure authentication channel:

- Create a key pair using the com.threerings.presents.tool.KeyPairGen tool.  You will need to
distribute the public key with your client, and the private key with the server.

- On your server during initialization, use PresentsConnectionManager.setPrivateKey to set the
private key.  It can take the key string gerenated by KeyPairGen.  This will return true if the
key was sucessfully set and the server supports the encryption necessary.

- On your client before authenticating, use Client.setPublicKey to set the public key.  As with
setPrivateKey, it will return true if the client supports the encryption necessary.

That's it!  You should now be authenticating over a secure encrypted channel.  The server can 
still accept unsecured authentication attempts (for the purpose of telling the client it needs a
new version to get the server's public key).  A failure to decrypt the client credientials on the
server will return a new "m.failed_to_secure" authentication code.

Handshake process:
- The client generates a random 128-bit key, and encodes it with the public key using a 32-bit
salt (PublicKeyCredentials).  This is sent to the server as a SecureRequest.
- The server decrypts the i128-bit key using its private key and verifies it against the salt.  If
verification fails, a failed secure response is returned and the client will authenticate over a
clear channel.  If verification succeeds, the server generates a random 128-bit AES key and
encodes it with the 128-bit key sent from the client.  This is sent back to the client as a
SecureResponse.
- The client will decode the AES key sent from the server (using the random key it generated at
the start of the handshake).  Using the AES key, the client will encrypt their credentials using
an AESAuthRequest and send it to the server.
- The server can now decrypt the credentials from the client and pass the AESAuthRequest to the
configured authenticators to complete authentication.  If the server fails to decrypt the
credentials a "m.failed_to_secure" authentication code is returned to the client.



git-svn-id: svn+ssh://src.earth.threerings.net/narya/trunk@6477 542714f4-19e9-0310-aa3c-eee0fc999fb1
This commit is contained in:
Mark Johnson
2011-02-04 19:38:53 +00:00
parent 540f84194d
commit ae6c134546
11 changed files with 886 additions and 8 deletions
@@ -0,0 +1,134 @@
//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2011 Three Rings Design, Inc., All Rights Reserved
// http://code.google.com/p/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.presents.net;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.security.GeneralSecurityException;
import javax.crypto.Cipher;
import com.threerings.io.ObjectInputStream;
import com.threerings.io.ObjectOutputStream;
import com.threerings.presents.util.SecureUtil;
/**
* Sends an AES encrypted auth request to the server. It assumes that
* {@link SecureUtil#ciphersSupported} has succeeded.
*/
public class AESAuthRequest extends AuthRequest
{
/**
* Zero argument constructor used when unserializing an instance.
*/
public AESAuthRequest ()
{
super();
}
/**
* Constructs a auth request with the supplied credentials and client version information.
*/
public AESAuthRequest (byte[] key, Credentials creds, String version, String[] bootGroups)
{
super(null, version, bootGroups);
_clearCreds = creds;
_key = key;
}
@Override // documentation inherited
public Credentials getCredentials ()
{
return _clearCreds;
}
@Override // documentation inherited
public String toString ()
{
return "[type=AESAREQ, msgid=" + messageId + ", creds=" + _clearCreds +
", version=" + _version + "]";
}
/**
* Decrypts the request after transmission.
*/
public void decrypt (byte[] key)
throws IOException, ClassNotFoundException
{
if (_clearCreds != null) {
return;
}
_key = key;
try {
_contents = SecureUtil.getAESCipher(Cipher.DECRYPT_MODE, _key).doFinal(_contents);
} catch (GeneralSecurityException gse) {
throw new IOException("Failed to decrypt credentials", gse);
}
ByteArrayInputStream byteIn = new ByteArrayInputStream(_contents);
ObjectInputStream cipherIn = new ObjectInputStream(byteIn);
_clearCreds = (Credentials)cipherIn.readObject();
}
/**
* A customized AES encrypting write object.
*/
public void writeObject (ObjectOutputStream out)
throws IOException
{
out.defaultWriteObject();
ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
ObjectOutputStream oOut = new ObjectOutputStream(byteOut);
oOut.writeObject(_clearCreds);
try {
byte[] encrypted =
SecureUtil.getAESCipher(Cipher.ENCRYPT_MODE, _key).doFinal(byteOut.toByteArray());
out.writeInt(encrypted.length);
out.write(encrypted);
} catch (GeneralSecurityException gse) {
throw new IOException("Failed to encrypt credentials", gse);
}
}
/**
* Read in our encrypted contents.
*/
public void readObject (ObjectInputStream in)
throws IOException, ClassNotFoundException
{
in.defaultReadObject();
_contents = new byte[in.readInt()];
in.read(_contents);
}
/** Our encryption key. */
protected transient byte[] _key;
/** Our encrypted contents. */
protected transient byte[] _contents;
/** Our unencrypted credentials. */
protected transient Credentials _clearCreds;
}
@@ -0,0 +1,86 @@
//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2011 Three Rings Design, Inc., All Rights Reserved
// http://code.google.com/p/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.presents.net;
import java.security.PrivateKey;
import java.security.PublicKey;
import javax.crypto.Cipher;
import com.threerings.presents.util.SecureUtil;
/**
* Credentials based on a public key encrypted secret.
*/
public class PublicKeyCredentials extends Credentials
{
/**
* No-arg constructor.
*/
public PublicKeyCredentials ()
{
}
/**
* Create a public key credential.
*/
public PublicKeyCredentials (PublicKey key)
{
_secret = SecureUtil.createRandomKey(16);
_salt = SecureUtil.createRandomKey(4);
_encodedSecret = SecureUtil.encryptBytes(key, _secret, _salt);
}
/**
* Returns the secret.
*/
public byte[] getSecret ()
{
return _secret;
}
/**
* Decodes the secret.
*/
public byte[] getSecret (PrivateKey key)
{
if (_secret == null) {
_secret = SecureUtil.decryptBytes(key, _encodedSecret, _salt);
}
return _secret;
}
@Override // documentation inherited
public String getDatagramSecret ()
{
return new String(_encodedSecret);
}
/** Our transmitted key. */
protected byte[] _encodedSecret;
/** Our verification salt. */
protected byte[] _salt;
/** Our secret key. */
protected transient byte[] _secret;
}
@@ -0,0 +1,57 @@
//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2011 Three Rings Design, Inc., All Rights Reserved
// http://code.google.com/p/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.presents.net;
import java.security.PrivateKey;
import java.security.PublicKey;
import javax.crypto.Cipher;
/**
* Used to create a secure channel to the server.
*/
public class SecureRequest extends AuthRequest
{
/**
* Zero argument constructor used when unserializing an instance.
*/
public SecureRequest ()
{
super();
}
/**
* Constructs a auth request with the supplied credentials and client version information.
*/
public SecureRequest (PublicKeyCredentials creds, String version)
{
super(creds, version, new String[0]);
}
/**
* Returns the secret from the credentials.
*/
public byte[] getSecret (PrivateKey key)
{
return ((PublicKeyCredentials)_creds).getSecret(key);
}
}
@@ -0,0 +1,84 @@
//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2011 Three Rings Design, Inc., All Rights Reserved
// http://code.google.com/p/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.presents.net;
import java.security.PrivateKey;
import com.samskivert.util.StringUtil;
import com.threerings.presents.data.AuthCodes;
import com.threerings.presents.util.SecureUtil;
/**
* Used to indicate a authentication response based on a SecureRequest.
*/
public class SecureResponse extends AuthResponse
implements AuthCodes
{
/**
* Zero argument constructor used when unserializing an instance.
*/
public SecureResponse ()
{
super();
}
/**
* Creates a secure response with the response code.
*/
public SecureResponse (String code)
{
_data = new AuthResponseData();
_data.code = code;
}
/**
* Encodes the server secret in the response data, or sets the failed state.
*
* @return the server secret if successfully encoded, or null.
*/
public byte[] createSecret (PublicKeyCredentials pkcred, PrivateKey key, int length)
{
_data = new AuthResponseData();
byte[] clientSecret = pkcred.getSecret(key);
if (clientSecret == null) {
_data.code = FAILED_TO_SECURE;
return null;
}
byte[] secret = SecureUtil.createRandomKey(length);
_data.code = StringUtil.hexlate(SecureUtil.xorBytes(secret, clientSecret));
return secret;
}
/**
* Creates a request based on the secure response.
*/
public AuthRequest createAuthRequest (
PublicKeyCredentials pkcreds, Credentials creds, String version, String[] bootGroups)
{
if (_data.code.equals(FAILED_TO_SECURE)) {
return new AuthRequest(creds, version, bootGroups);
}
byte[] secret = SecureUtil.xorBytes(StringUtil.unhexlate(_data.code), pkcreds.getSecret());
return new AESAuthRequest(secret, creds, version, bootGroups);
}
}