Search in sources :

Example 1 with Identity

use of org.jivesoftware.smackx.disco.packet.DiscoverInfo.Identity in project Smack by igniterealtime.

the class EntityCapsManager method generateVerificationString.

/**
     * Generates a XEP-115 Verification String
     * 
     * @see <a href="http://xmpp.org/extensions/xep-0115.html#ver">XEP-115
     *      Verification String</a>
     * 
     * @param discoverInfo
     * @param hash
     *            the used hash function, if null, default hash will be used
     * @return The generated verification String or null if the hash is not
     *         supported
     */
protected static CapsVersionAndHash generateVerificationString(DiscoverInfo discoverInfo, String hash) {
    if (hash == null) {
        hash = DEFAULT_HASH;
    }
    // SUPPORTED_HASHES uses the format of MessageDigest, which is uppercase, e.g. "SHA-1" instead of "sha-1"
    MessageDigest md = SUPPORTED_HASHES.get(hash.toUpperCase(Locale.US));
    if (md == null)
        return null;
    // Then transform the hash to lowercase, as this value will be put on the wire within the caps element's hash
    // attribute. I'm not sure if the standard is case insensitive here, but let's assume that even it is, there could
    // be "broken" implementation in the wild, so we *always* transform to lowercase.
    hash = hash.toLowerCase(Locale.US);
    DataForm extendedInfo = DataForm.from(discoverInfo);
    // 1. Initialize an empty string S ('sb' in this method).
    // Use StringBuilder as we don't
    StringBuilder sb = new StringBuilder();
    // need thread-safe StringBuffer
    // 2. Sort the service discovery identities by category and then by
    // type and then by xml:lang
    // (if it exists), formatted as CATEGORY '/' [TYPE] '/' [LANG] '/'
    // [NAME]. Note that each slash is included even if the LANG or
    // NAME is not included (in accordance with XEP-0030, the category and
    // type MUST be included.
    SortedSet<DiscoverInfo.Identity> sortedIdentities = new TreeSet<DiscoverInfo.Identity>();
    for (DiscoverInfo.Identity i : discoverInfo.getIdentities()) sortedIdentities.add(i);
    // followed by the '<' character.
    for (DiscoverInfo.Identity identity : sortedIdentities) {
        sb.append(identity.getCategory());
        sb.append('/');
        sb.append(identity.getType());
        sb.append('/');
        sb.append(identity.getLanguage() == null ? "" : identity.getLanguage());
        sb.append('/');
        sb.append(identity.getName() == null ? "" : identity.getName());
        sb.append('<');
    }
    // 4. Sort the supported service discovery features.
    SortedSet<String> features = new TreeSet<String>();
    for (Feature f : discoverInfo.getFeatures()) features.add(f.getVar());
    // character
    for (String f : features) {
        sb.append(f);
        sb.append('<');
    }
    // see XEP-0115 5.4 step 3.6
    if (extendedInfo != null && extendedInfo.hasHiddenFormTypeField()) {
        synchronized (extendedInfo) {
            // 6. If the service discovery information response includes
            // XEP-0128 data forms, sort the forms by the FORM_TYPE (i.e.,
            // by the XML character data of the <value/> element).
            SortedSet<FormField> fs = new TreeSet<FormField>(new Comparator<FormField>() {

                @Override
                public int compare(FormField f1, FormField f2) {
                    return f1.getVariable().compareTo(f2.getVariable());
                }
            });
            FormField ft = null;
            for (FormField f : extendedInfo.getFields()) {
                if (!f.getVariable().equals("FORM_TYPE")) {
                    fs.add(f);
                } else {
                    ft = f;
                }
            }
            // Add FORM_TYPE values
            if (ft != null) {
                formFieldValuesToCaps(ft.getValues(), sb);
            }
            // followed by the '<' character.
            for (FormField f : fs) {
                sb.append(f.getVariable());
                sb.append('<');
                formFieldValuesToCaps(f.getValues(), sb);
            }
        }
    }
    // 8. Ensure that S is encoded according to the UTF-8 encoding (RFC
    // 3269).
    // 9. Compute the verification string by hashing S using the algorithm
    // specified in the 'hash' attribute (e.g., SHA-1 as defined in RFC
    // 3174).
    // The hashed data MUST be generated with binary output and
    // encoded using Base64 as specified in Section 4 of RFC 4648
    // (note: the Base64 output MUST NOT include whitespace and MUST set
    // padding bits to zero).
    byte[] bytes;
    try {
        bytes = sb.toString().getBytes(StringUtils.UTF8);
    } catch (UnsupportedEncodingException e) {
        throw new AssertionError(e);
    }
    byte[] digest;
    synchronized (md) {
        digest = md.digest(bytes);
    }
    String version = Base64.encodeToString(digest);
    return new CapsVersionAndHash(version, hash);
}
Also used : DiscoverInfo(org.jivesoftware.smackx.disco.packet.DiscoverInfo) UnsupportedEncodingException(java.io.UnsupportedEncodingException) Feature(org.jivesoftware.smackx.disco.packet.DiscoverInfo.Feature) Identity(org.jivesoftware.smackx.disco.packet.DiscoverInfo.Identity) TreeSet(java.util.TreeSet) DataForm(org.jivesoftware.smackx.xdata.packet.DataForm) MessageDigest(java.security.MessageDigest) Identity(org.jivesoftware.smackx.disco.packet.DiscoverInfo.Identity) FormField(org.jivesoftware.smackx.xdata.FormField)

Example 2 with Identity

use of org.jivesoftware.smackx.disco.packet.DiscoverInfo.Identity in project Smack by igniterealtime.

the class EntityCapsManager method generateVerificationString.

/**
 * Generates a XEP-115 Verification String
 *
 * @see <a href="http://xmpp.org/extensions/xep-0115.html#ver">XEP-115
 *      Verification String</a>
 *
 * @param discoverInfo TODO javadoc me please
 * @param hash TODO javadoc me please
 *            the used hash function, if null, default hash will be used
 * @return The generated verification String or null if the hash is not
 *         supported
 */
static CapsVersionAndHash generateVerificationString(DiscoverInfoView discoverInfo, String hash) {
    if (hash == null) {
        hash = DEFAULT_HASH;
    }
    // SUPPORTED_HASHES uses the format of MessageDigest, which is uppercase, e.g. "SHA-1" instead of "sha-1"
    MessageDigest md = SUPPORTED_HASHES.get(hash.toUpperCase(Locale.US));
    if (md == null)
        return null;
    // Then transform the hash to lowercase, as this value will be put on the wire within the caps element's hash
    // attribute. I'm not sure if the standard is case insensitive here, but let's assume that even it is, there could
    // be "broken" implementation in the wild, so we *always* transform to lowercase.
    hash = hash.toLowerCase(Locale.US);
    // 1. Initialize an empty string S ('sb' in this method).
    // Use StringBuilder as we don't
    StringBuilder sb = new StringBuilder();
    // need thread-safe StringBuffer
    // 2. Sort the service discovery identities by category and then by
    // type and then by xml:lang
    // (if it exists), formatted as CATEGORY '/' [TYPE] '/' [LANG] '/'
    // [NAME]. Note that each slash is included even if the LANG or
    // NAME is not included (in accordance with XEP-0030, the category and
    // type MUST be included.
    SortedSet<DiscoverInfo.Identity> sortedIdentities = new TreeSet<>();
    sortedIdentities.addAll(discoverInfo.getIdentities());
    // followed by the '<' character.
    for (DiscoverInfo.Identity identity : sortedIdentities) {
        sb.append(identity.getCategory());
        sb.append('/');
        sb.append(identity.getType());
        sb.append('/');
        sb.append(identity.getLanguage() == null ? "" : identity.getLanguage());
        sb.append('/');
        sb.append(identity.getName() == null ? "" : identity.getName());
        sb.append('<');
    }
    // 4. Sort the supported service discovery features.
    SortedSet<String> features = new TreeSet<>();
    for (Feature f : discoverInfo.getFeatures()) features.add(f.getVar());
    // character
    for (String f : features) {
        sb.append(f);
        sb.append('<');
    }
    List<DataForm> extendedInfos = discoverInfo.getExtensions(DataForm.class);
    for (DataForm extendedInfo : extendedInfos) {
        if (!extendedInfo.hasHiddenFormTypeField()) {
            // See XEP-0115 5.4 step 3.f
            continue;
        }
        // 6. If the service discovery information response includes
        // XEP-0128 data forms, sort the forms by the FORM_TYPE (i.e.,
        // by the XML character data of the <value/> element).
        SortedSet<FormField> fs = new TreeSet<>(new Comparator<FormField>() {

            @Override
            public int compare(FormField f1, FormField f2) {
                return f1.getFieldName().compareTo(f2.getFieldName());
            }
        });
        for (FormField f : extendedInfo.getFields()) {
            if (!f.getFieldName().equals("FORM_TYPE")) {
                fs.add(f);
            }
        }
        // Add FORM_TYPE values
        formFieldValuesToCaps(Collections.singletonList(extendedInfo.getFormType()), sb);
        // followed by the '<' character.
        for (FormField f : fs) {
            sb.append(f.getFieldName());
            sb.append('<');
            formFieldValuesToCaps(f.getRawValueCharSequences(), sb);
        }
    }
    // 8. Ensure that S is encoded according to the UTF-8 encoding (RFC
    // 3269).
    // 9. Compute the verification string by hashing S using the algorithm
    // specified in the 'hash' attribute (e.g., SHA-1 as defined in RFC
    // 3174).
    // The hashed data MUST be generated with binary output and
    // encoded using Base64 as specified in Section 4 of RFC 4648
    // (note: the Base64 output MUST NOT include whitespace and MUST set
    // padding bits to zero).
    byte[] bytes = sb.toString().getBytes(StandardCharsets.UTF_8);
    byte[] digest;
    synchronized (md) {
        digest = md.digest(bytes);
    }
    String version = Base64.encodeToString(digest);
    return new CapsVersionAndHash(version, hash);
}
Also used : DiscoverInfo(org.jivesoftware.smackx.disco.packet.DiscoverInfo) Feature(org.jivesoftware.smackx.disco.packet.DiscoverInfo.Feature) Identity(org.jivesoftware.smackx.disco.packet.DiscoverInfo.Identity) TreeSet(java.util.TreeSet) DataForm(org.jivesoftware.smackx.xdata.packet.DataForm) MessageDigest(java.security.MessageDigest) Identity(org.jivesoftware.smackx.disco.packet.DiscoverInfo.Identity) FormField(org.jivesoftware.smackx.xdata.FormField)

Example 3 with Identity

use of org.jivesoftware.smackx.disco.packet.DiscoverInfo.Identity in project Smack by igniterealtime.

the class EntityCapsManager method updateLocalEntityCaps.

/**
 * Updates the local user Entity Caps information with the data provided
 *
 * If we are connected and there was already a presence send, another
 * presence is send to inform others about your new Entity Caps node string.
 */
private void updateLocalEntityCaps(DiscoverInfo synthesizedDiscoveryInfo) {
    XMPPConnection connection = connection();
    DiscoverInfoBuilder discoverInfoBuilder = synthesizedDiscoveryInfo.asBuilder("synthesized-disco-info-result");
    // getLocalNodeVer() will return a result only after currentCapsVersion is set. Therefore
    // set it first and then call getLocalNodeVer()
    currentCapsVersion = generateVerificationString(discoverInfoBuilder);
    final String localNodeVer = getLocalNodeVer();
    discoverInfoBuilder.setNode(localNodeVer);
    final DiscoverInfo discoverInfo = discoverInfoBuilder.build();
    addDiscoverInfoByNode(localNodeVer, discoverInfo);
    if (lastLocalCapsVersions.size() > 10) {
        CapsVersionAndHash oldCapsVersion = lastLocalCapsVersions.poll();
        sdm.removeNodeInformationProvider(entityNode + '#' + oldCapsVersion.version);
    }
    lastLocalCapsVersions.add(currentCapsVersion);
    if (connection != null)
        JID_TO_NODEVER_CACHE.put(connection.getUser(), new NodeVerHash(entityNode, currentCapsVersion));
    final List<Identity> identities = new LinkedList<>(ServiceDiscoveryManager.getInstanceFor(connection).getIdentities());
    sdm.setNodeInformationProvider(localNodeVer, new AbstractNodeInformationProvider() {

        List<String> features = sdm.getFeatures();

        List<DataForm> packetExtensions = sdm.getExtendedInfo();

        @Override
        public List<String> getNodeFeatures() {
            return features;
        }

        @Override
        public List<Identity> getNodeIdentities() {
            return identities;
        }

        @Override
        public List<DataForm> getNodePacketExtensions() {
            return packetExtensions;
        }
    });
}
Also used : DiscoverInfo(org.jivesoftware.smackx.disco.packet.DiscoverInfo) AbstractNodeInformationProvider(org.jivesoftware.smackx.disco.AbstractNodeInformationProvider) DiscoverInfoBuilder(org.jivesoftware.smackx.disco.packet.DiscoverInfoBuilder) XMPPConnection(org.jivesoftware.smack.XMPPConnection) LinkedList(java.util.LinkedList) DataForm(org.jivesoftware.smackx.xdata.packet.DataForm) List(java.util.List) LinkedList(java.util.LinkedList) Identity(org.jivesoftware.smackx.disco.packet.DiscoverInfo.Identity)

Example 4 with Identity

use of org.jivesoftware.smackx.disco.packet.DiscoverInfo.Identity in project Smack by igniterealtime.

the class Socks5ByteStreamManagerTest method shouldFailIfTargetUsesInvalidSocks5Proxy.

/**
 * Invoking {@link Socks5BytestreamManager#establishSession(org.jxmpp.jid.Jid, String)} should fail if the
 * proxy used by target is invalid.
 *
 * @throws InterruptedException if the calling thread was interrupted.
 * @throws SmackException if Smack detected an exceptional situation.
 * @throws XMPPException if an XMPP protocol error was received.
 * @throws IOException if an I/O error occurred.
 */
@Test
public void shouldFailIfTargetUsesInvalidSocks5Proxy() throws SmackException, InterruptedException, IOException, XMPPException {
    final Protocol protocol = new Protocol();
    final XMPPConnection connection = ConnectionUtils.createMockedConnection(protocol, initiatorJID);
    final String sessionID = "session_id_shouldFailIfTargetUsesInvalidSocks5Proxy";
    // get Socks5ByteStreamManager for connection
    Socks5BytestreamManager byteStreamManager = Socks5BytestreamManager.getBytestreamManager(connection);
    // TODO: It appears that it is not required to disable the local stream host for this unit test.
    byteStreamManager.setAnnounceLocalStreamHost(false);
    /**
     * create responses in the order they should be queried specified by the XEP-0065
     * specification
     */
    // build discover info that supports the SOCKS5 feature
    DiscoverInfoBuilder discoverInfo = Socks5PacketUtils.createDiscoverInfo(targetJID, initiatorJID);
    discoverInfo.addFeature(Bytestream.NAMESPACE);
    // return that SOCKS5 is supported if target is queried
    protocol.addResponse(discoverInfo.build(), Verification.correspondingSenderReceiver, Verification.requestTypeGET);
    // build discover items containing a proxy item
    DiscoverItems discoverItems = Socks5PacketUtils.createDiscoverItems(xmppServer, initiatorJID);
    Item item = new Item(proxyJID);
    discoverItems.addItem(item);
    // return the proxy item if XMPP server is queried
    protocol.addResponse(discoverItems, Verification.correspondingSenderReceiver, Verification.requestTypeGET);
    // build discover info for proxy containing information about being a SOCKS5 proxy
    DiscoverInfoBuilder proxyInfo = Socks5PacketUtils.createDiscoverInfo(proxyJID, initiatorJID);
    Identity identity = new Identity("proxy", proxyJID.toString(), "bytestreams");
    proxyInfo.addIdentity(identity);
    // return the socks5 bytestream proxy identity if proxy is queried
    protocol.addResponse(proxyInfo.build(), Verification.correspondingSenderReceiver, Verification.requestTypeGET);
    // build a socks5 stream host info containing the address and the port of the
    // proxy
    Bytestream streamHostInfo = Socks5PacketUtils.createBytestreamResponse(proxyJID, initiatorJID);
    streamHostInfo.addStreamHost(proxyJID, proxyAddress, 7778);
    // return stream host info if it is queried
    protocol.addResponse(streamHostInfo, Verification.correspondingSenderReceiver, Verification.requestTypeGET);
    // build used stream host response with unknown proxy
    Bytestream streamHostUsedPacket = Socks5PacketUtils.createBytestreamResponse(targetJID, initiatorJID);
    streamHostUsedPacket.setSessionID(sessionID);
    streamHostUsedPacket.setUsedHost(JidCreate.from("invalid.proxy"));
    // return used stream host info as response to the bytestream initiation
    protocol.addResponse(streamHostUsedPacket, Verification.correspondingSenderReceiver, Verification.requestTypeSET);
    SmackException e = assertThrows(SmackException.class, () -> {
        // start SOCKS5 Bytestream
        byteStreamManager.establishSession(targetJID, sessionID);
    });
    protocol.verifyAll();
    assertTrue(e.getMessage().contains("Remote user responded with unknown host"));
}
Also used : Item(org.jivesoftware.smackx.disco.packet.DiscoverItems.Item) Bytestream(org.jivesoftware.smackx.bytestreams.socks5.packet.Bytestream) DiscoverInfoBuilder(org.jivesoftware.smackx.disco.packet.DiscoverInfoBuilder) SmackException(org.jivesoftware.smack.SmackException) DiscoverItems(org.jivesoftware.smackx.disco.packet.DiscoverItems) XMPPConnection(org.jivesoftware.smack.XMPPConnection) Protocol(org.jivesoftware.util.Protocol) Identity(org.jivesoftware.smackx.disco.packet.DiscoverInfo.Identity) Test(org.junit.jupiter.api.Test)

Example 5 with Identity

use of org.jivesoftware.smackx.disco.packet.DiscoverInfo.Identity in project Smack by igniterealtime.

the class Socks5ByteStreamManagerTest method shouldBlacklistNonSocks5Proxies.

/**
 * Invoking {@link Socks5BytestreamManager#establishSession(org.jxmpp.jid.Jid, String)} should fail if no
 * SOCKS5 proxy can be found. If it turns out that a proxy is not a SOCKS5 proxy it should not
 * be queried again.
 * @throws InterruptedException if the calling thread was interrupted.
 * @throws SmackException if Smack detected an exceptional situation.
 * @throws XMPPException if an XMPP protocol error was received.
 * @throws IOException if an I/O error occurred.
 */
@Test
public void shouldBlacklistNonSocks5Proxies() throws SmackException, InterruptedException, IOException, XMPPException {
    final Protocol protocol = new Protocol();
    final XMPPConnection connection = ConnectionUtils.createMockedConnection(protocol, initiatorJID);
    final String sessionID = "session_id_shouldBlacklistNonSocks5Proxies";
    // get Socks5ByteStreamManager for connection
    Socks5BytestreamManager byteStreamManager = Socks5BytestreamManager.getBytestreamManager(connection);
    byteStreamManager.setAnnounceLocalStreamHost(false);
    /**
     * create responses in the order they should be queried specified by the XEP-0065
     * specification
     */
    // build discover info that supports the SOCKS5 feature
    DiscoverInfoBuilder discoverInfoBuilder = Socks5PacketUtils.createDiscoverInfo(targetJID, initiatorJID);
    discoverInfoBuilder.addFeature(Bytestream.NAMESPACE);
    DiscoverInfo discoverInfo = discoverInfoBuilder.build();
    // return that SOCKS5 is supported if target is queried
    protocol.addResponse(discoverInfo, Verification.correspondingSenderReceiver, Verification.requestTypeGET);
    // build discover items containing a proxy item
    DiscoverItems discoverItems = Socks5PacketUtils.createDiscoverItems(xmppServer, initiatorJID);
    Item item = new Item(proxyJID);
    discoverItems.addItem(item);
    // return the proxy item if XMPP server is queried
    protocol.addResponse(discoverItems, Verification.correspondingSenderReceiver, Verification.requestTypeGET);
    // build discover info for proxy containing information about NOT being a Socks5
    // proxy
    DiscoverInfoBuilder proxyInfo = Socks5PacketUtils.createDiscoverInfo(proxyJID, initiatorJID);
    Identity identity = new Identity("noproxy", proxyJID.toString(), "bytestreams");
    proxyInfo.addIdentity(identity);
    // return the proxy identity if proxy is queried
    protocol.addResponse(proxyInfo.build(), Verification.correspondingSenderReceiver, Verification.requestTypeGET);
    SmackException e = assertThrows(SmackException.class, () -> {
        // start SOCKS5 Bytestream
        byteStreamManager.establishSession(targetJID, sessionID);
        fail("exception should be thrown");
    });
    protocol.verifyAll();
    assertTrue(e.getMessage().contains("no SOCKS5 proxies available"));
    /* retry to establish SOCKS5 Bytestream */
    // add responses for service discovery again
    protocol.addResponse(discoverInfo, Verification.correspondingSenderReceiver, Verification.requestTypeGET);
    protocol.addResponse(discoverItems, Verification.correspondingSenderReceiver, Verification.requestTypeGET);
    e = assertThrows(SmackException.class, () -> {
        // start SOCKS5 Bytestream
        byteStreamManager.establishSession(targetJID, sessionID);
    });
    /*
         * #verifyAll() tests if the number of requests and responses corresponds and should
         * fail if the invalid proxy is queried again
         */
    protocol.verifyAll();
    assertTrue(e.getMessage().contains("no SOCKS5 proxies available"));
}
Also used : DiscoverInfo(org.jivesoftware.smackx.disco.packet.DiscoverInfo) Item(org.jivesoftware.smackx.disco.packet.DiscoverItems.Item) DiscoverInfoBuilder(org.jivesoftware.smackx.disco.packet.DiscoverInfoBuilder) SmackException(org.jivesoftware.smack.SmackException) DiscoverItems(org.jivesoftware.smackx.disco.packet.DiscoverItems) XMPPConnection(org.jivesoftware.smack.XMPPConnection) Protocol(org.jivesoftware.util.Protocol) Identity(org.jivesoftware.smackx.disco.packet.DiscoverInfo.Identity) Test(org.junit.jupiter.api.Test)

Aggregations

Identity (org.jivesoftware.smackx.disco.packet.DiscoverInfo.Identity)15 DiscoverInfoBuilder (org.jivesoftware.smackx.disco.packet.DiscoverInfoBuilder)10 DiscoverInfo (org.jivesoftware.smackx.disco.packet.DiscoverInfo)9 XMPPConnection (org.jivesoftware.smack.XMPPConnection)8 DiscoverItems (org.jivesoftware.smackx.disco.packet.DiscoverItems)8 Item (org.jivesoftware.smackx.disco.packet.DiscoverItems.Item)8 Test (org.junit.jupiter.api.Test)8 Bytestream (org.jivesoftware.smackx.bytestreams.socks5.packet.Bytestream)6 Protocol (org.jivesoftware.util.Protocol)6 ErrorIQ (org.jivesoftware.smack.packet.ErrorIQ)4 IQ (org.jivesoftware.smack.packet.IQ)4 SmackException (org.jivesoftware.smack.SmackException)3 ThreadedDummyConnection (org.jivesoftware.smack.ThreadedDummyConnection)3 XMPPErrorException (org.jivesoftware.smack.XMPPException.XMPPErrorException)3 DataForm (org.jivesoftware.smackx.xdata.packet.DataForm)3 MessageDigest (java.security.MessageDigest)2 LinkedList (java.util.LinkedList)2 List (java.util.List)2 TreeSet (java.util.TreeSet)2 StanzaError (org.jivesoftware.smack.packet.StanzaError)2