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		public AxolotlService.XmppAxolotlSession getSession() {
 80			return session;
 81		}
 82
 83	}
 84
 85	public XmppAxolotlMessage(Contact contact, Element axolotlMessage) {
 86		this.contact = contact;
 87		this.sourceDeviceId = Integer.parseInt(axolotlMessage.getAttribute("id"));
 88		this.headers = new HashSet<>();
 89		for(Element child:axolotlMessage.getChildren()) {
 90			switch(child.getName()) {
 91				case "header":
 92					headers.add(new XmppAxolotlMessageHeader(child));
 93					break;
 94				case "message":
 95					iv = Base64.decode(child.getAttribute("iv"),Base64.DEFAULT);
 96					ciphertext = Base64.decode(child.getContent(),Base64.DEFAULT);
 97					break;
 98				default:
 99					break;
100			}
101		}
102	}
103
104	public XmppAxolotlMessage(Contact contact, int sourceDeviceId, String plaintext) {
105		this.contact = contact;
106		this.sourceDeviceId = sourceDeviceId;
107		this.headers = new HashSet<>();
108		this.encrypt(plaintext);
109	}
110
111	private void encrypt(String plaintext) {
112		try {
113			KeyGenerator generator = KeyGenerator.getInstance("AES");
114			generator.init(128);
115			SecretKey secretKey = generator.generateKey();
116			Cipher cipher = Cipher.getInstance("AES/CTR/NoPadding");
117			cipher.init(Cipher.ENCRYPT_MODE, secretKey);
118			this.innerKey = secretKey.getEncoded();
119			this.iv = cipher.getIV();
120			this.ciphertext = cipher.doFinal(plaintext.getBytes());
121		} catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException
122				| IllegalBlockSizeException | BadPaddingException e) {
123
124		}
125	}
126
127	public Contact getContact() {
128		return this.contact;
129	}
130
131	public int getSenderDeviceId() {
132		return sourceDeviceId;
133	}
134
135	public byte[] getCiphertext() {
136		return ciphertext;
137	}
138
139	public Set<XmppAxolotlMessageHeader> getHeaders() {
140		return headers;
141	}
142
143	public void addHeader(XmppAxolotlMessageHeader header) {
144		headers.add(header);
145	}
146
147	public byte[] getInnerKey(){
148		return innerKey;
149	}
150
151	public byte[] getIV() {
152		return this.iv;
153	}
154
155	public Element toXml() {
156		// TODO: generate outer XML, add in header XML
157		Element message= new Element("axolotl_message", AxolotlService.PEP_PREFIX);
158		message.setAttribute("id", sourceDeviceId);
159		for(XmppAxolotlMessageHeader header: headers) {
160			message.addChild(header.toXml());
161		}
162		Element payload = message.addChild("message");
163		payload.setAttribute("iv",Base64.encodeToString(iv, Base64.DEFAULT));
164		payload.setContent(Base64.encodeToString(ciphertext,Base64.DEFAULT));
165		return message;
166	}
167
168
169	public XmppAxolotlPlaintextMessage decrypt(AxolotlService.XmppAxolotlSession session, byte[] key) {
170		XmppAxolotlPlaintextMessage plaintextMessage = null;
171		try {
172
173			Cipher cipher = Cipher.getInstance("AES/CTR/NoPadding");
174			SecretKeySpec keySpec = new SecretKeySpec(key, "AES");
175			IvParameterSpec ivSpec = new IvParameterSpec(iv);
176
177			cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec);
178
179			String plaintext = new String(cipher.doFinal(ciphertext));
180			plaintextMessage = new XmppAxolotlPlaintextMessage(session, plaintext);
181
182		} catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException
183				| InvalidAlgorithmParameterException | IllegalBlockSizeException
184				| BadPaddingException e) {
185			throw new AssertionError(e);
186		}
187		return plaintextMessage;
188	}
189}