001/**
002 *
003 * Copyright 2017 Paul Schaub
004 *
005 * Licensed under the Apache License, Version 2.0 (the "License");
006 * you may not use this file except in compliance with the License.
007 * You may obtain a copy of the License at
008 *
009 *     http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.jivesoftware.smackx.omemo.element;
018
019import java.util.HashMap;
020import java.util.Map;
021
022import org.jivesoftware.smack.packet.ExtensionElement;
023import org.jivesoftware.smack.util.StringUtils;
024import org.jivesoftware.smack.util.XmlStringBuilder;
025import org.jivesoftware.smack.util.stringencoder.Base64;
026
027/**
028 * Class that represents an OMEMO Bundle element.
029 *
030 * @author Paul Schaub
031 */
032public abstract class OmemoBundleElement implements ExtensionElement {
033
034    public static final String BUNDLE = "bundle";
035    public static final String SIGNED_PRE_KEY_PUB = "signedPreKeyPublic";
036    public static final String SIGNED_PRE_KEY_ID = "signedPreKeyId";
037    public static final String SIGNED_PRE_KEY_SIG = "signedPreKeySignature";
038    public static final String IDENTITY_KEY = "identityKey";
039    public static final String PRE_KEYS = "prekeys";
040    public static final String PRE_KEY_PUB = "preKeyPublic";
041    public static final String PRE_KEY_ID = "preKeyId";
042
043    private final int signedPreKeyId;
044    private final String signedPreKeyB64;
045    private byte[] signedPreKey;
046    private final String signedPreKeySignatureB64;
047    private byte[] signedPreKeySignature;
048    private final String identityKeyB64;
049    private byte[] identityKey;
050    private final HashMap<Integer, String> preKeysB64;
051    private HashMap<Integer, byte[]> preKeys;
052
053    /**
054     * Constructor to create a Bundle Element from base64 Strings.
055     *
056     * @param signedPreKeyId id
057     * @param signedPreKeyB64 base64 encoded signedPreKey
058     * @param signedPreKeySigB64 base64 encoded signedPreKeySignature
059     * @param identityKeyB64 base64 encoded identityKey
060     * @param preKeysB64 HashMap of base64 encoded preKeys
061     */
062    public OmemoBundleElement(int signedPreKeyId, String signedPreKeyB64, String signedPreKeySigB64, String identityKeyB64, HashMap<Integer, String> preKeysB64) {
063        if (signedPreKeyId < 0) {
064            throw new IllegalArgumentException("signedPreKeyId MUST be greater than or equal to 0.");
065        }
066        this.signedPreKeyId = signedPreKeyId;
067        this.signedPreKeyB64 = StringUtils.requireNotNullNorEmpty(signedPreKeyB64, "signedPreKeyB64 MUST NOT be null nor empty.");
068        this.signedPreKeySignatureB64 = StringUtils.requireNotNullNorEmpty(signedPreKeySigB64, "signedPreKeySigB64 MUST NOT be null nor empty.");
069        this.identityKeyB64 = StringUtils.requireNotNullNorEmpty(identityKeyB64, "identityKeyB64 MUST NOT be null nor empty.");
070
071        if (preKeysB64 == null || preKeysB64.isEmpty()) {
072            throw new IllegalArgumentException("PreKeys MUST NOT be null nor empty.");
073        }
074        this.preKeysB64 = preKeysB64;
075    }
076
077    /**
078     * Constructor to create a Bundle Element from decoded byte arrays.
079     *
080     * @param signedPreKeyId id
081     * @param signedPreKey signedPreKey
082     * @param signedPreKeySig signedPreKeySignature
083     * @param identityKey identityKey
084     * @param preKeys HashMap of preKeys
085     */
086    public OmemoBundleElement(int signedPreKeyId, byte[] signedPreKey, byte[] signedPreKeySig, byte[] identityKey, HashMap<Integer, byte[]> preKeys) {
087        this(signedPreKeyId,
088                signedPreKey != null ? Base64.encodeToString(signedPreKey) : null,
089                signedPreKeySig != null ? Base64.encodeToString(signedPreKeySig) : null,
090                identityKey != null ? Base64.encodeToString(identityKey) : null,
091                base64EncodePreKeys(preKeys));
092        this.signedPreKey = signedPreKey;
093        this.signedPreKeySignature = signedPreKeySig;
094        this.identityKey = identityKey;
095        this.preKeys = preKeys;
096    }
097
098    private static HashMap<Integer, String> base64EncodePreKeys(HashMap<Integer, byte[]> preKeys) {
099        if (preKeys == null) {
100            return null;
101        }
102
103        HashMap<Integer, String> converted = new HashMap<>();
104        for (Integer id : preKeys.keySet()) {
105            converted.put(id, Base64.encodeToString(preKeys.get(id)));
106        }
107        return converted;
108    }
109
110    /**
111     * Return the signedPreKey of the OmemoBundleElement.
112     *
113     * @return signedPreKey as byte array
114     */
115    public byte[] getSignedPreKey() {
116        if (signedPreKey == null) {
117            signedPreKey = Base64.decode(signedPreKeyB64);
118        }
119        return this.signedPreKey.clone();
120    }
121
122    /**
123     * Return the id of the signedPreKey in the bundle.
124     *
125     * @return id of signedPreKey
126     */
127    public int getSignedPreKeyId() {
128        return this.signedPreKeyId;
129    }
130
131    /**
132     * Get the signature of the signedPreKey.
133     *
134     * @return signature as byte array
135     */
136    public byte[] getSignedPreKeySignature() {
137        if (signedPreKeySignature == null) {
138            signedPreKeySignature = Base64.decode(signedPreKeySignatureB64);
139        }
140        return signedPreKeySignature.clone();
141    }
142
143    /**
144     * Return the public identityKey of the bundles owner.
145     * This can be used to check the signedPreKeys signature.
146     * The fingerprint of this key is, what the user has to verify.
147     *
148     * @return public identityKey as byte array
149     */
150    public byte[] getIdentityKey() {
151        if (identityKey == null) {
152            identityKey = Base64.decode(identityKeyB64);
153        }
154        return this.identityKey.clone();
155    }
156
157    /**
158     * Return the HashMap of preKeys in the bundle.
159     * The map uses the preKeys ids as key and the preKeys as value.
160     *
161     * @return preKeys Pre-Keys contained in the bundle
162     */
163    public HashMap<Integer, byte[]> getPreKeys() {
164        if (preKeys == null) {
165            preKeys = new HashMap<>();
166            for (int id : preKeysB64.keySet()) {
167                preKeys.put(id, Base64.decode(preKeysB64.get(id)));
168            }
169        }
170        return this.preKeys;
171    }
172
173    /**
174     * Return a single preKey from the map.
175     *
176     * @param id id of the preKey
177     * @return the preKey
178     */
179    public byte[] getPreKey(int id) {
180        return getPreKeys().get(id);
181    }
182
183    @Override
184    public String getElementName() {
185        return BUNDLE;
186    }
187
188    @Override
189    public XmlStringBuilder toXML(org.jivesoftware.smack.packet.XmlEnvironment enclosingNamespace) {
190        XmlStringBuilder sb = new XmlStringBuilder(this, enclosingNamespace).rightAngleBracket();
191
192        sb.halfOpenElement(SIGNED_PRE_KEY_PUB).attribute(SIGNED_PRE_KEY_ID, signedPreKeyId).rightAngleBracket()
193                .append(signedPreKeyB64).closeElement(SIGNED_PRE_KEY_PUB);
194
195        sb.openElement(SIGNED_PRE_KEY_SIG).append(signedPreKeySignatureB64).closeElement(SIGNED_PRE_KEY_SIG);
196
197        sb.openElement(IDENTITY_KEY).append(identityKeyB64).closeElement(IDENTITY_KEY);
198
199        sb.openElement(PRE_KEYS);
200        for (Map.Entry<Integer, String> p : this.preKeysB64.entrySet()) {
201            sb.halfOpenElement(PRE_KEY_PUB).attribute(PRE_KEY_ID, p.getKey()).rightAngleBracket()
202                    .append(p.getValue()).closeElement(PRE_KEY_PUB);
203        }
204        sb.closeElement(PRE_KEYS);
205
206        sb.closeElement(this);
207        return sb;
208    }
209
210    @Override
211    public String toString() {
212        StringBuilder sb = new StringBuilder("OmemoBundleElement[\n");
213        sb.append(SIGNED_PRE_KEY_PUB).append(' ').append(SIGNED_PRE_KEY_ID).append('=').append(signedPreKeyId)
214                .append(':').append(signedPreKeyB64).append('\n')
215                .append(SIGNED_PRE_KEY_SIG).append(": ").append(signedPreKeySignatureB64).append('\n')
216                .append(IDENTITY_KEY).append(": ").append(identityKeyB64).append('\n')
217                .append(PRE_KEYS).append(" (").append(preKeysB64.size()).append(")\n");
218        for (Map.Entry<Integer, String> e : preKeysB64.entrySet()) {
219            sb.append(PRE_KEY_PUB).append(' ').append(PRE_KEY_ID).append('=').append(e.getKey()).append(": ").append(e.getValue()).append('\n');
220        }
221        sb.append(']');
222        return sb.toString();
223    }
224
225    @Override
226    public boolean equals(Object other) {
227        if (!(other instanceof OmemoBundleElement)) {
228            return false;
229        }
230
231        OmemoBundleElement otherOmemoBundleElement = (OmemoBundleElement) other;
232        return toXML().toString().equals(otherOmemoBundleElement.toXML().toString());
233    }
234
235    @Override
236    public int hashCode() {
237        return toXML().toString().hashCode();
238    }
239}