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}