/* Licence.java
* Component: ProperJavaRDP
*
* Revision: $Revision: 1.1.1.1 $
* Author: $Author: suvarov $
* Date: $Date: 2007/03/08 00:26:26 $
*
* Copyright (c) 2005 Propero Limited
*
* Purpose: Handles request, receipt and processing of
* licences
*/
// Created on 02-Jul-2003
package com.lixia.rdp;
import com.alssoftrd.utils.RDPConnection;
import java.io.*;
import com.lixia.rdp.crypto.*;
import com.lixia.rdp.Package.RdpPackage;
public class Licence {
private Secure secure = null;
Licence(Secure s) {
secure = s;
licence_key = new byte[16];
licence_sign_key = new byte[16];
}
private byte[] licence_key = null;
private byte[] licence_sign_key = null;
private byte[] in_token = null, in_sig = null;
// static Logger logger = Logger.getLogger(Licence.class);
/* constants for the licence negotiation */
private static final int LICENCE_TOKEN_SIZE = 10;
private static final int LICENCE_HWID_SIZE = 20;
private static final int LICENCE_SIGNATURE_SIZE = 16;
/*
private static final int LICENCE_TAG_DEMAND = 0x0201;
private static final int LICENCE_TAG_AUTHREQ = 0x0202;
private static final int LICENCE_TAG_ISSUE = 0x0203;
private static final int LICENCE_TAG_REISSUE = 0x0204; // rdesktop 1.2.0
private static final int LICENCE_TAG_PRESENT = 0x0212; // rdesktop 1.2.0
private static final int LICENCE_TAG_REQUEST = 0x0213;
private static final int LICENCE_TAG_AUTHRESP = 0x0215;
private static final int LICENCE_TAG_RESULT = 0x02ff;
*/
private static final int LICENCE_TAG_DEMAND = 0x01;
private static final int LICENCE_TAG_AUTHREQ = 0x02;
private static final int LICENCE_TAG_ISSUE = 0x03;
private static final int LICENCE_TAG_REISSUE = 0x04;
private static final int LICENCE_TAG_PRESENT = 0x12;
private static final int LICENCE_TAG_REQUEST = 0x13;
private static final int LICENCE_TAG_AUTHRESP = 0x15;
private static final int LICENCE_TAG_RESULT = 0xff;
private static final int LICENCE_TAG_USER = 0x000f;
private static final int LICENCE_TAG_HOST = 0x0010;
public byte[] generate_hwid() throws UnsupportedEncodingException {
byte[] hwid = new byte[LICENCE_HWID_SIZE];
secure.setLittleEndian32(hwid, 2);
byte[] name = RDPConnection.conf.hostname.getBytes("US-ASCII");
if (name.length > LICENCE_HWID_SIZE - 4) {
System.arraycopy(name, 0, hwid, 4, LICENCE_HWID_SIZE - 4);
} else {
System.arraycopy(name, 0, hwid, 4, name.length);
}
return hwid;
}
/**
* Process and handle licence data from a packet
*
* @param data Packet containing licence data
* @throws RdesktopException
* @throws IOException
* @throws CryptoException
*/
public void process(RdpPackage data) throws RdesktopException, IOException, CryptoException {
int tag;
tag = data.get8();
data.incrementPosition(3); // version, length
switch (tag) {
case (LICENCE_TAG_DEMAND):
this.process_demand(data);
break;
case (LICENCE_TAG_AUTHREQ):
this.process_authreq(data);
break;
case (LICENCE_TAG_ISSUE):
this.process_issue(data);
break;
case (LICENCE_TAG_REISSUE):
// logger.debug("Presented licence was accepted!");
break;
case (LICENCE_TAG_RESULT):
break;
default:
// logger.warn("got licence tag: " + tag);
}
}
/**
* Process a demand for a licence. Find a license and transmit to server, or
* request new licence
*
* @param data Packet containing details of licence demand
* @throws UnsupportedEncodingException
* @throws RdesktopException
* @throws IOException
* @throws CryptoException
*/
public void process_demand(RdpPackage data) throws UnsupportedEncodingException, RdesktopException, IOException, CryptoException {
byte[] null_data = new byte[Secure.SEC_MODULUS_SIZE];
byte[] server_random = new byte[Secure.SEC_RANDOM_SIZE];
byte[] host = RDPConnection.conf.hostname.getBytes("US-ASCII");
byte[] user = RDPConnection.conf.username.getBytes("US-ASCII");
/*retrieve the server random */
data.copyToByteArray(server_random, 0, data.getPosition(), server_random.length);
data.incrementPosition(server_random.length);
/* Null client keys are currently used */
this.generate_keys(null_data, server_random, null_data);
if (!RDPConnection.conf.built_in_licence && RDPConnection.conf.load_licence) {
byte[] licence_data = load_licence();
if ((licence_data != null) && (licence_data.length > 0)) {
// logger.debug("licence_data.length = " + licence_data.length);
/* Generate a signature for the HWID buffer */
byte[] hwid = generate_hwid();
byte[] signature = secure.sign(this.licence_sign_key, 16, 16, hwid, hwid.length);
/*now crypt the hwid */
RC4 rc4_licence = new RC4();
byte[] crypt_key = new byte[this.licence_key.length];
byte[] crypt_hwid = new byte[LICENCE_HWID_SIZE];
System.arraycopy(this.licence_key, 0, crypt_key, 0, this.licence_key.length);
rc4_licence.engineInitEncrypt(crypt_key);
rc4_licence.crypt(hwid, 0, LICENCE_HWID_SIZE, crypt_hwid, 0);
present(null_data, null_data, licence_data, licence_data.length, crypt_hwid, signature);
// logger.debug("Presented stored licence to server!");
return;
}
}
this.send_request(null_data, null_data, user, host);
}
/**
* Handle an authorisation request, based on a licence signature (store
* signatures in this Licence object
*
* @param data Packet containing details of request
* @return True if signature is read successfully
* @throws RdesktopException
*/
public boolean parse_authreq(RdpPackage data) throws RdesktopException {
int tokenlen;
data.incrementPosition(6); //unknown
tokenlen = data.getLittleEndian16();
if (tokenlen != LICENCE_TOKEN_SIZE) {
throw new RdesktopException("Wrong Tokenlength!");
}
this.in_token = new byte[tokenlen];
data.copyToByteArray(this.in_token, 0, data.getPosition(), tokenlen);
data.incrementPosition(tokenlen);
this.in_sig = new byte[LICENCE_SIGNATURE_SIZE];
data.copyToByteArray(this.in_sig, 0, data.getPosition(), LICENCE_SIGNATURE_SIZE);
data.incrementPosition(LICENCE_SIGNATURE_SIZE);
if (data.getPosition() == data.getEnd()) {
return true;
} else {
return false;
}
}
/**
* Respond to authorisation request, with token, hwid and signature, send
* response to server
*
* @param token Token data
* @param crypt_hwid HWID for encryption
* @param signature Signature data
* @throws RdesktopException
* @throws IOException
* @throws CryptoException
*/
public void send_authresp(byte[] token, byte[] crypt_hwid, byte[] signature) throws RdesktopException, IOException, CryptoException {
int sec_flags = Secure.SEC_LICENCE_NEG;
int length = 58;
RdpPackage data;
data = secure.init(sec_flags, length + 2);
data.set8(LICENCE_TAG_AUTHRESP);
data.set8(2); // version
data.setLittleEndian16(length);
data.setLittleEndian16(1);
data.setLittleEndian16(LICENCE_TOKEN_SIZE);
data.copyFromByteArray(token, 0, data.getPosition(), LICENCE_TOKEN_SIZE);
data.incrementPosition(LICENCE_TOKEN_SIZE);
data.setLittleEndian16(1);
data.setLittleEndian16(LICENCE_HWID_SIZE);
data.copyFromByteArray(crypt_hwid, 0, data.getPosition(), LICENCE_HWID_SIZE);
data.incrementPosition(LICENCE_HWID_SIZE);
data.copyFromByteArray(signature, 0, data.getPosition(), LICENCE_SIGNATURE_SIZE);
data.incrementPosition(LICENCE_SIGNATURE_SIZE);
data.markEnd();
secure.send(data, sec_flags);
}
/**
* Present a licence to the server
*
* @param client_random
* @param rsa_data
* @param licence_data
* @param licence_size
* @param hwid
* @param signature
* @throws RdesktopException
* @throws IOException
* @throws CryptoException
*/
public void present(byte[] client_random, byte[] rsa_data, byte[] licence_data, int licence_size, byte[] hwid, byte[] signature) throws RdesktopException, IOException, CryptoException {
int sec_flags = Secure.SEC_LICENCE_NEG;
int length = /* rdesktop is 16 not 20, but this must be wrong?! */ 20 + Secure.SEC_RANDOM_SIZE + Secure.SEC_MODULUS_SIZE + Secure.SEC_PADDING_SIZE + licence_size + LICENCE_HWID_SIZE + LICENCE_SIGNATURE_SIZE;
RdpPackage s = secure.init(sec_flags, length + 4);
s.set8(LICENCE_TAG_PRESENT);
s.set8(2); // version
s.setLittleEndian16(length);
s.setLittleEndian32(1);
s.setLittleEndian16(0);
s.setLittleEndian16(0x0201);
s.copyFromByteArray(client_random, 0, s.getPosition(), Secure.SEC_RANDOM_SIZE);
s.incrementPosition(Secure.SEC_RANDOM_SIZE);
s.setLittleEndian16(0);
s.setLittleEndian16((Secure.SEC_MODULUS_SIZE + Secure.SEC_PADDING_SIZE));
s.copyFromByteArray(rsa_data, 0, s.getPosition(), Secure.SEC_MODULUS_SIZE);
s.incrementPosition(Secure.SEC_MODULUS_SIZE);
s.incrementPosition(Secure.SEC_PADDING_SIZE);
s.setLittleEndian16(1);
s.setLittleEndian16(licence_size);
s.copyFromByteArray(licence_data, 0, s.getPosition(), licence_size);
s.incrementPosition(licence_size);
s.setLittleEndian16(1);
s.setLittleEndian16(LICENCE_HWID_SIZE);
s.copyFromByteArray(hwid, 0, s.getPosition(), LICENCE_HWID_SIZE);
s.incrementPosition(LICENCE_HWID_SIZE);
s.copyFromByteArray(signature, 0, s.getPosition(), LICENCE_SIGNATURE_SIZE);
s.incrementPosition(LICENCE_SIGNATURE_SIZE);
s.markEnd();
secure.send(s, sec_flags);
}
/**
* Process an authorisation request
*
* @param data Packet containing request details
* @throws RdesktopException
* @throws UnsupportedEncodingException
* @throws IOException
* @throws CryptoException
*/
public void process_authreq(RdpPackage data) throws RdesktopException, UnsupportedEncodingException, IOException, CryptoException {
byte[] out_token = new byte[LICENCE_TOKEN_SIZE];
byte[] decrypt_token = new byte[LICENCE_TOKEN_SIZE];
byte[] crypt_hwid = new byte[LICENCE_HWID_SIZE];
byte[] sealed_buffer = new byte[LICENCE_TOKEN_SIZE + LICENCE_HWID_SIZE];
byte[] out_sig = new byte[LICENCE_SIGNATURE_SIZE];
RC4 rc4_licence = new RC4();
byte[] crypt_key;
/* parse incoming packet and save encrypted token */
if (parse_authreq(data) != true) {
throw new RdesktopException("Authentication Request was corrupt!");
}
System.arraycopy(this.in_token, 0, out_token, 0, LICENCE_TOKEN_SIZE);
/* decrypt token. It should read TEST in Unicode */
crypt_key = new byte[this.licence_key.length];
System.arraycopy(this.licence_key, 0, crypt_key, 0, this.licence_key.length);
rc4_licence.engineInitDecrypt(crypt_key);
rc4_licence.crypt(this.in_token, 0, LICENCE_TOKEN_SIZE, decrypt_token, 0);
/*construct HWID */
byte[] hwid = this.generate_hwid();
/* generate signature for a buffer of token and HWId */
System.arraycopy(decrypt_token, 0, sealed_buffer, 0, LICENCE_TOKEN_SIZE);
System.arraycopy(hwid, 0, sealed_buffer, LICENCE_TOKEN_SIZE, LICENCE_HWID_SIZE);
out_sig = secure.sign(this.licence_sign_key, 16, 16, sealed_buffer, sealed_buffer.length);
/* deliberately break signature if licencing disabled */
if (!RDPConnection.conf.licence) {
out_sig = new byte[LICENCE_SIGNATURE_SIZE]; // set to 0
}
/*now crypt the hwid */
System.arraycopy(this.licence_key, 0, crypt_key, 0, this.licence_key.length);
rc4_licence.engineInitEncrypt(crypt_key);
rc4_licence.crypt(hwid, 0, LICENCE_HWID_SIZE, crypt_hwid, 0);
this.send_authresp(out_token, crypt_hwid, out_sig);
}
/**
* Handle a licence issued by the server, save to disk if
* RDPConnection.conf.save_licence
*
* @param data Packet containing issued licence
* @throws CryptoException
*/
public void process_issue(RdpPackage data) throws CryptoException {
int length;
int check;
RC4 rc4_licence = new RC4();
byte[] key = new byte[this.licence_key.length];
System.arraycopy(this.licence_key, 0, key, 0, this.licence_key.length);
data.incrementPosition(2); //unknown
length = data.getLittleEndian16();
if (data.getPosition() + length > data.getEnd()) {
return;
}
rc4_licence.engineInitDecrypt(key);
byte[] buffer = new byte[length];
data.copyToByteArray(buffer, 0, data.getPosition(), length);
rc4_licence.crypt(buffer, 0, length, buffer, 0);
data.copyFromByteArray(buffer, 0, data.getPosition(), length);
check = data.getLittleEndian16();
if (check != 0) {
//return;
}
secure.licenceIssued = true;
secure.licenceIssued = true;
// logger.debug("Server issued Licence");
if (RDPConnection.conf.save_licence) {
save_licence(data, length - 2);
}
}
/**
* Send a request for a new licence, or to approve a stored licence
*
* @param client_random
* @param rsa_data
* @param username
* @param hostname
* @throws RdesktopException
* @throws IOException
* @throws CryptoException
*/
public void send_request(byte[] client_random, byte[] rsa_data, byte[] username, byte[] hostname) throws RdesktopException, IOException, CryptoException {
int sec_flags = Secure.SEC_LICENCE_NEG;
int userlen = (username.length == 0 ? 0 : username.length + 1);
int hostlen = (hostname.length == 0 ? 0 : hostname.length + 1);
int length = 128 + userlen + hostlen;
RdpPackage buffer = secure.init(sec_flags, length);
buffer.set8(LICENCE_TAG_REQUEST);
buffer.set8(2); // version
buffer.setLittleEndian16(length);
buffer.setLittleEndian32(1);
if (RDPConnection.conf.built_in_licence && (!RDPConnection.conf.load_licence) && (!RDPConnection.conf.save_licence)) {
// logger.debug("Using built-in Windows Licence");
buffer.setLittleEndian32(0x03010000);
} else {
// logger.debug("Requesting licence");
buffer.setLittleEndian32(0xff010000);
}
buffer.copyFromByteArray(client_random, 0, buffer.getPosition(), Secure.SEC_RANDOM_SIZE);
buffer.incrementPosition(Secure.SEC_RANDOM_SIZE);
buffer.setLittleEndian16(0);
buffer.setLittleEndian16(Secure.SEC_MODULUS_SIZE + Secure.SEC_PADDING_SIZE);
buffer.copyFromByteArray(rsa_data, 0, buffer.getPosition(), Secure.SEC_MODULUS_SIZE);
buffer.incrementPosition(Secure.SEC_MODULUS_SIZE);
buffer.incrementPosition(Secure.SEC_PADDING_SIZE);
buffer.setLittleEndian16(LICENCE_TAG_USER);
buffer.setLittleEndian16(userlen);
if (username.length != 0) {
buffer.copyFromByteArray(username, 0, buffer.getPosition(), userlen - 1);
} else {
buffer.copyFromByteArray(username, 0, buffer.getPosition(), userlen);
}
buffer.incrementPosition(userlen);
buffer.setLittleEndian16(LICENCE_TAG_HOST);
buffer.setLittleEndian16(hostlen);
if (hostname.length != 0) {
buffer.copyFromByteArray(hostname, 0, buffer.getPosition(), hostlen - 1);
} else {
buffer.copyFromByteArray(hostname, 0, buffer.getPosition(), hostlen);
}
buffer.incrementPosition(hostlen);
buffer.markEnd();
secure.send(buffer, sec_flags);
}
/**
* Load a licence from disk
*
* @return Raw byte data for stored licence
*/
byte[] load_licence() {
// logger.debug("load_licence");
return (new LicenceStore_Localised()).load_licence();
}
/**
* Save a licence to disk
*
* @param data Packet containing licence data
* @param length Length of licence
*/
void save_licence(RdpPackage data, int length) {
// logger.debug("save_licence");
int len;
int startpos = data.getPosition();
data.incrementPosition(2); // Skip first two bytes
/* Skip three strings */
for (int i = 0; i < 3; i++) {
len = data.getLittleEndian32();
data.incrementPosition(len);
/* Make sure that we won't be past the end of data after
* reading the next length value
*/
if (data.getPosition() + 4 - startpos > length) {
// logger.warn("Error in parsing licence key.");
return;
}
}
len = data.getLittleEndian32();
// logger.debug("save_licence: len=" + len);
if (data.getPosition() + len - startpos > length) {
// logger.warn("Error in parsing licence key.");
return;
}
byte[] databytes = new byte[len];
data.copyToByteArray(databytes, 0, data.getPosition(), len);
new LicenceStore_Localised().save_licence(databytes);
/*
String dirpath = RDPConnection.conf.licence_path;//home+"/.rdesktop";
String filepath = dirpath +"/licence."+RDPConnection.conf.hostname;
File file = new File(dirpath);
file.mkdir();
try{
FileOutputStream fd = new FileOutputStream(filepath);
// write to the licence file
byte[] databytes = new byte[len];
data.copyToByteArray(databytes,0,data.getPosition(),len);
fd.write(databytes);
fd.close();
logger.info("Stored licence at " + filepath);
}
catch(FileNotFoundException e){logger.info("save_licence: file path not valid!");}
catch(IOException e){logger.warn("IOException in save_licence");}
*/
}
/**
* Generate a set of encryption keys
*
* @param client_key Array in which to store client key
* @param server_key Array in which to store server key
* @param client_rsa Array in which to store RSA data
* @throws CryptoException
*/
public void generate_keys(byte[] client_key, byte[] server_key, byte[] client_rsa) throws CryptoException {
byte[] session_key = new byte[48];
byte[] temp_hash = new byte[48];
temp_hash = secure.hash48(client_rsa, client_key, server_key, 65);
session_key = secure.hash48(temp_hash, server_key, client_key, 65);
System.arraycopy(session_key, 0, this.licence_sign_key, 0, 16);
this.licence_key = secure.hash16(session_key, client_key, server_key, 16);
}
}