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}