/*
* Copyright 2005 Propero Limited
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or (at
* your option) any later version.
*
* This program 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
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
*/
package org.jopenray.rdp;
import java.io.*;
import org.apache.log4j.Logger;
import org.jopenray.rdp.crypto.*;
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 = Options.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(RdpPacket_Localised data) throws RdesktopException,
IOException, CryptoException {
int tag = 0;
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(RdpPacket_Localised 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 = Options.hostname.getBytes("US-ASCII");
byte[] user = Options.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 (!Options.built_in_licence && Options.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(RdpPacket_Localised data)
throws RdesktopException {
int tokenlen = 0;
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;
RdpPacket_Localised data = null;
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;
RdpPacket_Localised 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(RdpPacket_Localised 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 = null;
/* 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 (!Constants.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
* Options.save_licence
*
* @param data
* Packet containing issued licence
* @throws CryptoException
*/
public void process_issue(RdpPacket_Localised data) throws CryptoException {
int length = 0;
int check = 0;
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;
/*
* data.incrementPosition(2); // in_uint8s(s, 2); // pad
*
* // advance to fourth string length = 0; for (int i = 0; i < 4; i++) {
* data.incrementPosition(length); // in_uint8s(s, length); length =
* data.getLittleEndian32(length); // in_uint32_le(s, length); if
* (!(data.getPosition() + length <= data.getEnd())) return; }
*/
secure.licenceIssued = true;
logger.debug("Server issued Licence");
if (Options.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;
RdpPacket_Localised buffer = secure.init(sec_flags, length);
buffer.set8(LICENCE_TAG_REQUEST);
buffer.set8(2); // version
buffer.setLittleEndian16(length);
buffer.setLittleEndian32(1);
if (Options.built_in_licence && (!Options.load_licence)
&& (!Options.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");
// String home = "/root"; // getenv("HOME");
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(RdpPacket_Localised 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 = Options.licence_path;//home+"/.rdesktop"; String
* filepath = dirpath +"/licence."+Options.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);
}
}