|
SecureChatRooms
This page is part of the AimSecureIm documentation. Secure Chat RoomsSecure chat rooms are implemented using the AES symmetric encryption algorithm. When you send a message in a Secure Chat Room, it is signed and encrypted with AES, then stored in a custom ASN.1 structure containing a PKCS7 encrypted data block. Symmetric encryption algorithms like AES use a single key to encrypt and decrypt; no public certificates or private keys are used. Anyone who has the key can participate in the chat room. To invite a user to a Secure Chat Room, you give him the key. Creating a chat roomTo create a secure chat room, you must generate an AES key. To do this in Java, you can use code like the following: // we assume these are defined
SecureRandom srandom; // this could be a new SecureRandom()
// 2.16.840.1.101.3.4.1.42 is AES256-CBC
KeyGenerator kg = KeyGenerator.getInstance("2.16.840.1.101.3.4.1.42");
kg.init(srandom);
SecretKey key = kg.generateKey();You can then join the chat room normally, but with a content type of "application/pkcs7-mime" instead of the usual "text/x-aolrtf". (The key is never sent to the server; it was only generated for use later.) Inviting a buddyTo invite a buddy to a chat room, you send a normal buddy invitation rendezvous ICBM but with two extra TLV's: Reading the security block of an incoming invitationStored in securityBlock is a PKCS7 signed data block whose data can be extracted and verified as described in Pkcs7SignedData. After extracting the signed content, it should look like the following:
chatInfo is normally the same miniature chat room information block sent in the 0x2711 service data TLV of the chat invitation structure. This block should probably be used instead of the 0x2711 block, however, in the case that they are different. len contains the length of the data block. data contains what appears to be a custom ASN.1 structure which contains a PKCS7 encrypted data structure. OpenSSL's ASN.1 parser describes it as follows: 0:d=0 hl=4 l= 313 cons: SEQUENCE
4:d=1 hl=4 l= 298 cons: SEQUENCE
8:d=2 hl=2 l= 1 prim: INTEGER :00
11:d=2 hl=3 l= 146 cons: SEQUENCE
14:d=3 hl=3 l= 140 cons: SEQUENCE
17:d=4 hl=2 l= 11 cons: SET
19:d=5 hl=2 l= 9 cons: SEQUENCE
21:d=6 hl=2 l= 3 prim: OBJECT :countryName
26:d=6 hl=2 l= 2 prim: PRINTABLESTRING :AB
30:d=4 hl=2 l= 18 cons: SET
32:d=5 hl=2 l= 16 cons: SEQUENCE
34:d=6 hl=2 l= 3 prim: OBJECT :stateOrProvinceName
39:d=6 hl=2 l= 9 prim: PRINTABLESTRING :Statename
50:d=4 hl=2 l= 17 cons: SET
52:d=5 hl=2 l= 15 cons: SEQUENCE
54:d=6 hl=2 l= 3 prim: OBJECT :localityName
59:d=6 hl=2 l= 8 prim: PRINTABLESTRING :Locality
69:d=4 hl=2 l= 21 cons: SET
71:d=5 hl=2 l= 19 cons: SEQUENCE
73:d=6 hl=2 l= 3 prim: OBJECT :organizationName
78:d=6 hl=2 l= 12 prim: PRINTABLESTRING :Organization
92:d=4 hl=2 l= 16 cons: SET
94:d=5 hl=2 l= 14 cons: SEQUENCE
96:d=6 hl=2 l= 3 prim: OBJECT :organizationalUnitName
101:d=6 hl=2 l= 7 prim: PRINTABLESTRING :OrgUnit
110:d=4 hl=2 l= 19 cons: SET
112:d=5 hl=2 l= 17 cons: SEQUENCE
114:d=6 hl=2 l= 3 prim: OBJECT :commonName
119:d=6 hl=2 l= 10 prim: PRINTABLESTRING :CommonName
131:d=4 hl=2 l= 24 cons: SET
133:d=5 hl=2 l= 22 cons: SEQUENCE
135:d=6 hl=2 l= 9 prim: OBJECT :emailAddress
146:d=6 hl=2 l= 9 prim: IA5STRING :yo@yo.com
157:d=3 hl=2 l= 1 prim: INTEGER :01
160:d=2 hl=2 l= 13 cons: SEQUENCE
162:d=3 hl=2 l= 9 prim: OBJECT :rsaEncryption
173:d=3 hl=2 l= 0 prim: NULL
175:d=2 hl=3 l= 128 prim: OCTET STRING
306:d=1 hl=2 l= 9 prim: OBJECT :aes-256-cbcThe structure contains the distinguished name (DN) of the receiver as well as the chat room's AES key, encrypted using RSA. The root sequence of the ASN.1 object contains another sequence (at index 0). That inner sequence is a KeyTransRecipientInfo object. To extract the key transport recipient information object from the entire ASN.1 block in Java, you could use code like the following: // we assume these are imported import org.bouncycastle.asn1.ASN1InputStream; import org.bouncycastle.asn1.ASN1Sequence; import org.bouncycastle.asn1.cms.KeyTransRecipientInfo; // we assume these are defined byte[] data; // the data block containing the ASN.1 structure ByteArrayInputStream bin = new ByteArrayInputStream(data); ASN1InputStream ain = new ASN1InputStream(bin); ASN1Sequence root = (ASN1Sequence) ain.readObject(); ASN1Sequence seq = (ASN1Sequence) root.getObjectAt(0); KeyTransRecipientInfo ktr = KeyTransRecipientInfo.getInstance(seq); We now need to extract and decrypt the chat room key data, which is stored inside ktr. The chat room key was encrypted using our public certificate, so we need to use our private key to decrypt it. // we assume these are imported import org.bouncycastle.asn1.ASN1Sequence; import org.bouncycastle.asn1.cms.KeyTransRecipientInfo; // we assume these are defined ASN1Sequence root; // from above code sample KeyTransRecipientInfo ktr; // from above code sample RSAPrivateKey privateKey; // your private key // extract the object identifier describing what encryption this key is for // (this should be 2.16.840.1.101.3.4.1.42) String keyoid = ((DERObjectIdentifier) root.getObjectAt(1)).getId(); // extract the object identifier describing what encryption method the key // data is encrypted with (this should be 1.2.840.113549.1.7.1) String encoid = ktr.getKeyEncryptionAlgorithm().getObjectId().getId(); // initialize the decryption cipher Cipher cipher = Cipher.getInstance(encoid, "BC"); cipher.init(Cipher.DECRYPT_MODE, privateKey); // decrypt the chat room key data with our private key byte[] result = cipher.doFinal(ktr.getEncryptedKey().getOctets()); SecretKey roomKey = new SecretKeySpec(result, keyoid); We now know that roomKey is the key with which messages sent in the chat room are encrypted, and we know that keyoid is the algorithm used. (The algorithm will normally be 2.16.840.1.101.3.4.1.42, which is AES256-CBC.) Sending a secure chat room invitationBuilding the securityBlock is a somewhat tedious process. First, you need to encrypt the chat room key with the receiver's public X.509 certificate. // we assume these are defined
SecretKey key; // the chat room key
X509Certificate receiverCert; // the receiver's public certificate
byte[] keyData = key.getEncoded();
Cipher c = Cipher.getInstance("1.2.840.113549.1.1.1", "BC");
c.init(Cipher.ENCRYPT_MODE, cert);
byte[] encryptedKey = c.doFinal(keyData);encryptedKey now contains the encrypted chat room key. Next we need to place it in a key transport recipient information object: // we assume these are imported
import org.bouncycastle.asn1.cms.IssuerAndSerialNumber;
import org.bouncycastle.asn1.cms.KeyTransRecipientInfo;
import org.bouncycastle.asn1.cms.RecipientIdentifier;
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
import org.bouncycastle.asn1.x509.X509Name;
import org.bouncycastle.asn1.DEROctetString;
// we assume these are defined
X509Certificate receiverCert; // the receiver's certificate
byte[] encryptedKey; // the encrypted key data generated above
X509Name xname = new X509Name(receiverCert.getSubjectDN().getName());
IssuerAndSerialNumber ias = new IssuerAndSerialNumber(xname,
receiverCert.getSerialNumber());
KeyTransRecipientInfo ktr = new KeyTransRecipientInfo(
new RecipientIdentifier(ias),
new AlgorithmIdentifier("1.2.840.113549.1.1.1"),
new DEROctetString(encryptedKey));Next, we have to create the custom ASN.1 structure. It contains a sequence which contains another sequence which contains the key transport recipient information object and the key cipher object ID. // we assume these are imported
import org.bouncycastle.asn1.ASN1EncodableVector;
import org.bouncycastle.asn1.ASN1OutputStream;
import org.bouncycastle.asn1.DERObjectIdentifier;
import org.bouncycastle.asn1.DERSequence;
// we assume this is defined
KeyTransRecipientInfo ktr; // the key transport recipient information object, created above
ASN1EncodableVector vec = new ASN1EncodableVector();
vec.add(ktr);
vec.add(new DERObjectIdentifier("2.16.840.1.101.3.4.1.42"));
ByteArrayOutputStream bout = new ByteArrayOutputStream();
ASN1OutputStream aout = new ASN1OutputStream(bout);
aout.writeObject(new DERSequence(vec));
aout.close();
byte[] asn1data = bout.toByteArray();Now asn1data contains the entire custom ASN.1 structure (DER-encoded). You should already have a MiniChatRoomInfo object for the chat room to which you're inviting your buddy. Either it was sent to you in your invitation message or you received it from the server when creating the room. After concatenating the MiniChatRoomInfo, the length of asn1data, and then asn1data itself (see structure description above for details) into a single byte array, you need to sign the data and place it in a PKCS7 signed data block (as described in Pkcs7SignedData). The PKCS7 signed data block you generate can then be sent in the chat invitation rendezvous ICBM in the 0x2711 TLV. It should be about 800 bytes long. Encrypted chat messagesSecure Chat messages are encrypted with AES, wrapped in what appears to be custom ASN.1 format, and then signed in a PKCS7 signed data block. An encrypted chat message is sent in the same way as a normal chat message, except that its content type (TLV 0x0004) is "application/pkcs7-mime" instead of "text/x-aolrtf". Decoding an encrypted chat messageThe structure contains a BER sequence which contains a the OID of the PKCS7 encrypted data structure followed by a BER tagged object. The BER tagged object contains a BER sequence which is an encrypted data block. To decode the encrypted chat message in Java, you could use code like the following: // we assume these are imported import org.bouncycastle.asn1.ASN1InputStream; import org.bouncycastle.asn1.BERTaggedObject; import org.bouncycastle.asn1.cms.EncryptedContentInfo; import org.bouncycastle.asn1.cms.EncryptedData; // we assume these are defined byte[] encryptedMsgData; // the encrypted message block ByteArrayInputStream bin = new ByteArrayInputStream(encryptedMsgData); ASN1InputStream ain = new ASN1InputStream(bin); ASN1Sequence seq = (ASN1Sequence) ain.readObject(); BERTaggedObject bert = (BERTaggedObject) seq.getObjectAt(1); ASN1Sequence seq2 = (ASN1Sequence) bert.getObject(); EncryptedData encd = new EncryptedData(seq2); EncryptedContentInfo enci = encd.getEncryptedContentInfo(); AlgorithmIdentifier alg = enci.getContentEncryptionAlgorithm(); byte[] encryptedData = enci.getEncryptedContent().getOctets(); Now alg is an algorithm identifier that describes the encryption algorithm (normally 2.16.840.1.101.3.4.1.42, which is AES256-CBC), and encryptedData is the encrypted data. Next we need to decrypt the data: // extract the initialization vector from the algorithm
// identifier object
byte[] iv = ((ASN1OctetString) alg.getParameters()).getOctets();
Cipher c = Cipher.getInstance(alg.getObjectId().getId(), "BC");
c.init(Cipher.DECRYPT_MODE, getChatKey(chat),
new IvParameterSpec(iv));
bye[] decrypted = c.doFinal(encryptedData);Now decrypted contains the decrypted data. It should look like this: Content-Type: application/pkcs7-mime; charset=us-ascii Content-Language: en Content-Transfer-Encoding: binary [binary garbage] That binary garbage is a PKCS7 signed data structure, whose data can be extracted and verified as described at Pkcs7SignedData. The signed data is in the same format as that of the signed data sent in SecureInstantMessages: Content-Type: text/x-aolrtf; charset=us-ascii Content-Language: en Content-Transfer-Encoding: binary <HTML><BODY BGCOLOR="#ffffff"><FONT FACE="Verdana" LANG="0" SIZE=2>hey</FONT></BODY></HTML> Simple string parsing can then extract the message from the HTTP-style stream using the charset specified (it won't always be "us-ascii"). Encoding an encrypted chat messageTo encode a message for a Secure Chat Room, it must first be wrapped in a PKCS7 signed data object with the same HTTP-style headers used in SecureInstantMessages. Code like the following will create the data that needs to be signed: // we assume these are defined
String msg; // the message to be sent
ByteArrayOutputStream bout = new ByteArrayOutputStream();
OutputStreamWriter osw = new OutputStreamWriter(bout, "US-ASCII");
osw.write("Content-Transfer-Encoding: binary\r\n"
+ "Content-Type: text/x-aolrtf; charset=us-ascii\r\n"
+ "Content-Language: en\r\n"
+ "\r\n");
osw.flush();
bout.write(msg.getBytes("US-ASCII"));
byte[] dataToSign = bout.toByteArray();''NOTE:'' US-ASCII is used in these examples to simplify the example code. Your code should accommodate for different charsets if the given text cannot be encoded in US-ASCII. Information about charsets may be placed here later. The data then needs to be signed as described in Pkcs7SignedData. That signed data block then needs to be encrypted with AES. // we assume these are defined
Random random;
SecretKey chatkey; // the chat room's key
byte[] dataToEncrypt; // the PKCS7 signed data object data
// (that is, in DER-encoded form)
byte[] iv = new byte[16];
random.nextBytes(iv);
// 2.16.840.1.101.3.4.1.42 is AES256-CBC
Cipher c = Cipher.getInstance("2.16.840.1.101.3.4.1.42", "BC");
c.init(Cipher.ENCRYPT_MODE, chatKey, new IvParameterSpec(iv));
byte[] encryptedData = c.doFinal(dataToEncrypt);Next, you need to create a PKCS7 encrypted data object containing the encrypted data (as a BER octet string) as well as information about the encryption method: // we assume these are imported
import org.bouncycastle.asn1.cms.EncryptedContentInfo;
import org.bouncycastle.asn1.cms.EncryptedData;
// we assume these are defined
byte[] iv; // the initialization vector generated above
byte[] encryptedData; // the encrypted data made above
EncryptedContentInfo eci = new EncryptedContentInfo(
new DERObjectIdentifier("1.2.840.113549.1.7.1"),
new AlgorithmIdentifier(
new DERObjectIdentifier("2.16.840.1.101.3.4.1.42"),
new DEROctetString(iv)),
new BERConstructedOctetString(encryptedData));
EncryptedData ed = new EncryptedData(eci, null);Next you need to build the custom ASN.1 structure. It consists of a BER sequence which contains two elements: the OID of the PKCS7 encrypted data structure and a BER tagged object containing the encrypted content object. // we assume these are imported
import org.bouncycastle.asn1.BERSequence;
import org.bouncycastle.asn1.BERTaggedObject;
import org.bouncycastle.asn1.DERObjectIdentifier;
import org.bouncycastle.asn1.ASN1OutputStream;
import org.bouncycastle.asn1.ASN1EncodableVector;
// we assume these are defined
EncryptedData ed; // the encrypted data object created above
BERTaggedObject bert = new BERTaggedObject(0, ed.getDERObject());
DERObjectIdentifier rootid
= new DERObjectIdentifier("1.2.840.113549.1.7.6");
ASN1EncodableVector vec = new ASN1EncodableVector();
vec.add(rootid);
vec.add(bert);
ByteArrayOutputStream fout = new ByteArrayOutputStream();
ASN1OutputStream out = new ASN1OutputStream(fout);
out.writeObject(new BERSequence(vec));
out.close();
byte[] asn1data = fout.toByteArray();Finally, asn1data contains the data you can send in the chat message TLV. It should be around 850 bytes, or more depending on the length of the message. The content type of the chat message should be "application/pkcs7-mime". |
Sign in to add a comment