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 Map<Integer, String> preKeysB64; 051 private Map<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 Map of base64 encoded preKeys 061 */ 062 public OmemoBundleElement(int signedPreKeyId, String signedPreKeyB64, String signedPreKeySigB64, String identityKeyB64, Map<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 Map of preKeys 085 */ 086 public OmemoBundleElement(int signedPreKeyId, byte[] signedPreKey, byte[] signedPreKeySig, byte[] identityKey, Map<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 Map<Integer, String> base64EncodePreKeys(Map<Integer, byte[]> preKeys) { 099 if (preKeys == null) { 100 return null; 101 } 102 103 Map<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 Map 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 Map<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}