XmppAxolotlMessage.java

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