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