XmppAxolotlMessage.java

  1package eu.siacs.conversations.crypto.axolotl;
  2
  3import android.support.annotation.Nullable;
  4import android.util.Base64;
  5
  6import java.security.InvalidAlgorithmParameterException;
  7import java.security.InvalidKeyException;
  8import java.security.NoSuchAlgorithmException;
  9import java.util.HashSet;
 10import java.util.Set;
 11
 12import javax.crypto.BadPaddingException;
 13import javax.crypto.Cipher;
 14import javax.crypto.IllegalBlockSizeException;
 15import javax.crypto.KeyGenerator;
 16import javax.crypto.NoSuchPaddingException;
 17import javax.crypto.SecretKey;
 18import javax.crypto.spec.IvParameterSpec;
 19import javax.crypto.spec.SecretKeySpec;
 20
 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		private final String fingerprint;
 71
 72		public XmppAxolotlPlaintextMessage(AxolotlService.XmppAxolotlSession session, String plaintext, String fingerprint) {
 73			this.session = session;
 74			this.plaintext = plaintext;
 75			this.fingerprint = fingerprint;
 76		}
 77
 78		public String getPlaintext() {
 79			return plaintext;
 80		}
 81
 82		public AxolotlService.XmppAxolotlSession getSession() {
 83			return session;
 84		}
 85
 86		public String getFingerprint() {
 87			return fingerprint;
 88		}
 89	}
 90
 91	public XmppAxolotlMessage(Jid from, Element axolotlMessage) {
 92		this.from = from;
 93		this.sourceDeviceId = Integer.parseInt(axolotlMessage.getAttribute("id"));
 94		this.headers = new HashSet<>();
 95		for(Element child:axolotlMessage.getChildren()) {
 96			switch(child.getName()) {
 97				case "header":
 98					headers.add(new XmppAxolotlMessageHeader(child));
 99					break;
100				case "message":
101					iv = Base64.decode(child.getAttribute("iv"),Base64.DEFAULT);
102					ciphertext = Base64.decode(child.getContent(),Base64.DEFAULT);
103					break;
104				default:
105					break;
106			}
107		}
108	}
109
110	public XmppAxolotlMessage(Jid from, int sourceDeviceId, String plaintext) {
111		this.from = from;
112		this.sourceDeviceId = sourceDeviceId;
113		this.headers = new HashSet<>();
114		this.encrypt(plaintext);
115	}
116
117	private void encrypt(String plaintext) {
118		try {
119			KeyGenerator generator = KeyGenerator.getInstance("AES");
120			generator.init(128);
121			SecretKey secretKey = generator.generateKey();
122			Cipher cipher = Cipher.getInstance("AES/CTR/NoPadding");
123			cipher.init(Cipher.ENCRYPT_MODE, secretKey);
124			this.innerKey = secretKey.getEncoded();
125			this.iv = cipher.getIV();
126			this.ciphertext = cipher.doFinal(plaintext.getBytes());
127		} catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException
128				| IllegalBlockSizeException | BadPaddingException e) {
129
130		}
131	}
132
133	public Jid getFrom() {
134		return this.from;
135	}
136
137	public int getSenderDeviceId() {
138		return sourceDeviceId;
139	}
140
141	public byte[] getCiphertext() {
142		return ciphertext;
143	}
144
145	public Set<XmppAxolotlMessageHeader> getHeaders() {
146		return headers;
147	}
148
149	public void addHeader(@Nullable XmppAxolotlMessageHeader header) {
150		if (header != null) {
151			headers.add(header);
152		}
153	}
154
155	public byte[] getInnerKey(){
156		return innerKey;
157	}
158
159	public byte[] getIV() {
160		return this.iv;
161	}
162
163	public Element toXml() {
164		// TODO: generate outer XML, add in header XML
165		Element message= new Element("axolotl_message", AxolotlService.PEP_PREFIX);
166		message.setAttribute("id", sourceDeviceId);
167		for(XmppAxolotlMessageHeader header: headers) {
168			message.addChild(header.toXml());
169		}
170		Element payload = message.addChild("message");
171		payload.setAttribute("iv",Base64.encodeToString(iv, Base64.DEFAULT));
172		payload.setContent(Base64.encodeToString(ciphertext,Base64.DEFAULT));
173		return message;
174	}
175
176
177	public XmppAxolotlPlaintextMessage decrypt(AxolotlService.XmppAxolotlSession session, byte[] key, String fingerprint) {
178		XmppAxolotlPlaintextMessage plaintextMessage = null;
179		try {
180
181			Cipher cipher = Cipher.getInstance("AES/CTR/NoPadding");
182			SecretKeySpec keySpec = new SecretKeySpec(key, "AES");
183			IvParameterSpec ivSpec = new IvParameterSpec(iv);
184
185			cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec);
186
187			String plaintext = new String(cipher.doFinal(ciphertext));
188			plaintextMessage = new XmppAxolotlPlaintextMessage(session, plaintext, fingerprint);
189
190		} catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException
191				| InvalidAlgorithmParameterException | IllegalBlockSizeException
192				| BadPaddingException e) {
193			throw new AssertionError(e);
194		}
195		return plaintextMessage;
196	}
197}