XmppAxolotlMessage.java

  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}