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