XmppAxolotlMessage.java

  1package eu.siacs.conversations.crypto.axolotl;
  2
  3import android.util.Base64;
  4
  5import java.security.InvalidAlgorithmParameterException;
  6import java.security.NoSuchAlgorithmException;
  7import java.security.InvalidKeyException;
  8import java.util.HashSet;
  9import java.util.Set;
 10
 11import javax.crypto.BadPaddingException;
 12import javax.crypto.Cipher;
 13import javax.crypto.IllegalBlockSizeException;
 14import javax.crypto.KeyGenerator;
 15import javax.crypto.NoSuchPaddingException;
 16import javax.crypto.SecretKey;
 17import javax.crypto.spec.IvParameterSpec;
 18import javax.crypto.spec.SecretKeySpec;
 19
 20import eu.siacs.conversations.entities.Contact;
 21import eu.siacs.conversations.xml.Element;
 22
 23public class XmppAxolotlMessage {
 24	private byte[] innerKey;
 25	private byte[] ciphertext;
 26	private byte[] iv;
 27	private final Set<XmppAxolotlMessageHeader> headers;
 28	private final Contact contact;
 29	private final int sourceDeviceId;
 30
 31	public static class XmppAxolotlMessageHeader {
 32		private final int recipientDeviceId;
 33		private final byte[] content;
 34
 35		public XmppAxolotlMessageHeader(int deviceId, byte[] content) {
 36			this.recipientDeviceId = deviceId;
 37			this.content = content;
 38		}
 39
 40		public XmppAxolotlMessageHeader(Element header) {
 41			if("header".equals(header.getName())) {
 42				this.recipientDeviceId = Integer.parseInt(header.getAttribute("rid"));
 43				this.content = Base64.decode(header.getContent(),Base64.DEFAULT);
 44			} else {
 45				throw new IllegalArgumentException("Argument not a <header> Element!");
 46			}
 47		}
 48
 49		public int getRecipientDeviceId() {
 50			return recipientDeviceId;
 51		}
 52
 53		public byte[] getContents() {
 54			return content;
 55		}
 56
 57		public Element toXml() {
 58			Element headerElement = new Element("header");
 59			// TODO: generate XML
 60			headerElement.setAttribute("rid", getRecipientDeviceId());
 61			headerElement.setContent(Base64.encodeToString(getContents(), Base64.DEFAULT));
 62			return headerElement;
 63		}
 64	}
 65
 66	public static class XmppAxolotlPlaintextMessage {
 67		private final AxolotlService.XmppAxolotlSession session;
 68		private final String plaintext;
 69
 70		public XmppAxolotlPlaintextMessage(AxolotlService.XmppAxolotlSession session, String plaintext) {
 71			this.session = session;
 72			this.plaintext = plaintext;
 73		}
 74
 75		public String getPlaintext() {
 76			return plaintext;
 77		}
 78	}
 79
 80	public XmppAxolotlMessage(Contact contact, Element axolotlMessage) {
 81		this.contact = contact;
 82		this.sourceDeviceId = Integer.parseInt(axolotlMessage.getAttribute("id"));
 83		this.headers = new HashSet<>();
 84		for(Element child:axolotlMessage.getChildren()) {
 85			switch(child.getName()) {
 86				case "header":
 87					headers.add(new XmppAxolotlMessageHeader(child));
 88					break;
 89				case "message":
 90					iv = Base64.decode(child.getAttribute("iv"),Base64.DEFAULT);
 91					ciphertext = Base64.decode(child.getContent(),Base64.DEFAULT);
 92					break;
 93				default:
 94					break;
 95			}
 96		}
 97	}
 98
 99	public XmppAxolotlMessage(Contact contact, int sourceDeviceId, String plaintext) {
100		this.contact = contact;
101		this.sourceDeviceId = sourceDeviceId;
102		this.headers = new HashSet<>();
103		this.encrypt(plaintext);
104	}
105
106	private void encrypt(String plaintext) {
107		try {
108			KeyGenerator generator = KeyGenerator.getInstance("AES");
109			generator.init(128);
110			SecretKey secretKey = generator.generateKey();
111			Cipher cipher = Cipher.getInstance("AES/CTR/NoPadding");
112			cipher.init(Cipher.ENCRYPT_MODE, secretKey);
113			this.innerKey = secretKey.getEncoded();
114			this.iv = cipher.getIV();
115			this.ciphertext = cipher.doFinal(plaintext.getBytes());
116		} catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException
117				| IllegalBlockSizeException | BadPaddingException e) {
118
119		}
120	}
121
122	public Contact getContact() {
123		return this.contact;
124	}
125
126	public int getSenderDeviceId() {
127		return sourceDeviceId;
128	}
129
130	public byte[] getCiphertext() {
131		return ciphertext;
132	}
133
134	public Set<XmppAxolotlMessageHeader> getHeaders() {
135		return headers;
136	}
137
138	public void addHeader(XmppAxolotlMessageHeader header) {
139		headers.add(header);
140	}
141
142	public byte[] getInnerKey(){
143		return innerKey;
144	}
145
146	public byte[] getIV() {
147		return this.iv;
148	}
149
150	public Element toXml() {
151		// TODO: generate outer XML, add in header XML
152		Element message= new Element("axolotl_message", AxolotlService.PEP_PREFIX);
153		message.setAttribute("id", sourceDeviceId);
154		for(XmppAxolotlMessageHeader header: headers) {
155			message.addChild(header.toXml());
156		}
157		Element payload = message.addChild("message");
158		payload.setAttribute("iv",Base64.encodeToString(iv, Base64.DEFAULT));
159		payload.setContent(Base64.encodeToString(ciphertext,Base64.DEFAULT));
160		return message;
161	}
162
163
164	public XmppAxolotlPlaintextMessage decrypt(AxolotlService.XmppAxolotlSession session, byte[] key) {
165		XmppAxolotlPlaintextMessage plaintextMessage = null;
166		try {
167
168			Cipher cipher = Cipher.getInstance("AES/CTR/NoPadding");
169			SecretKeySpec keySpec = new SecretKeySpec(key, "AES");
170			IvParameterSpec ivSpec = new IvParameterSpec(iv);
171
172			cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec);
173
174			String plaintext = new String(cipher.doFinal(ciphertext));
175			plaintextMessage = new XmppAxolotlPlaintextMessage(session, plaintext);
176
177		} catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException
178				| InvalidAlgorithmParameterException | IllegalBlockSizeException
179				| BadPaddingException e) {
180			throw new AssertionError(e);
181		}
182		return plaintextMessage;
183	}
184}