Wip: Use xep-0285 as replacement for xmlsec
This commit is contained in:
parent
58740c4a69
commit
a2cad37e48
|
@ -21,9 +21,6 @@ dependencies {
|
||||||
api "org.jxmpp:jxmpp-core:1.0.1"
|
api "org.jxmpp:jxmpp-core:1.0.1"
|
||||||
api "org.jxmpp:jxmpp-jid:1.0.1"
|
api "org.jxmpp:jxmpp-jid:1.0.1"
|
||||||
|
|
||||||
api "org.apache.santuario:xmlsec:2.2.0"
|
|
||||||
|
|
||||||
|
|
||||||
testImplementation "org.igniterealtime.smack:smack-java7:$smackJava7Version"
|
testImplementation "org.igniterealtime.smack:smack-java7:$smackJava7Version"
|
||||||
|
|
||||||
// RxJava2
|
// RxJava2
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
package org.jivesoftware.smackx.ikey;
|
package org.jivesoftware.smackx.ikey;
|
||||||
|
|
||||||
import org.apache.xml.security.c14n.CanonicalizationException;
|
import org.apache.xml.security.c14n.CanonicalizationException;
|
||||||
import org.apache.xml.security.c14n.Canonicalizer;
|
|
||||||
import org.apache.xml.security.c14n.InvalidCanonicalizerException;
|
|
||||||
import org.apache.xml.security.parser.XMLParserException;
|
import org.apache.xml.security.parser.XMLParserException;
|
||||||
import org.bouncycastle.openpgp.PGPPublicKeyRing;
|
import org.bouncycastle.openpgp.PGPPublicKeyRing;
|
||||||
import org.jivesoftware.smack.Manager;
|
import org.jivesoftware.smack.Manager;
|
||||||
|
@ -10,13 +8,10 @@ import org.jivesoftware.smack.SmackException;
|
||||||
import org.jivesoftware.smack.XMPPConnection;
|
import org.jivesoftware.smack.XMPPConnection;
|
||||||
import org.jivesoftware.smack.XMPPException;
|
import org.jivesoftware.smack.XMPPException;
|
||||||
import org.jivesoftware.smack.packet.Message;
|
import org.jivesoftware.smack.packet.Message;
|
||||||
import org.jivesoftware.smack.util.Async;
|
|
||||||
import org.jivesoftware.smackx.ikey.element.IkeyElement;
|
import org.jivesoftware.smackx.ikey.element.IkeyElement;
|
||||||
import org.jivesoftware.smackx.ikey.mechanism.IkeySignatureVerificationMechanism;
|
import org.jivesoftware.smackx.ikey.mechanism.IkeySignatureVerificationMechanism;
|
||||||
import org.jivesoftware.smackx.ikey.util.IkeyConstants;
|
import org.jivesoftware.smackx.ikey.util.IkeyConstants;
|
||||||
import org.jivesoftware.smackx.ikey.util.UnsupportedSignatureAlgorithmException;
|
import org.jivesoftware.smackx.ikey.util.UnsupportedSignatureAlgorithmException;
|
||||||
import org.jivesoftware.smackx.ikey.util.canonicalization.ElementCanonicalizer;
|
|
||||||
import org.jivesoftware.smackx.ikey.util.canonicalization.XmlSecElementCanonicalizer;
|
|
||||||
import org.jivesoftware.smackx.ikey_ox.OxIkeySignatureVerificationMechanism;
|
import org.jivesoftware.smackx.ikey_ox.OxIkeySignatureVerificationMechanism;
|
||||||
import org.jivesoftware.smackx.pep.PepEventListener;
|
import org.jivesoftware.smackx.pep.PepEventListener;
|
||||||
import org.jivesoftware.smackx.pep.PepManager;
|
import org.jivesoftware.smackx.pep.PepManager;
|
||||||
|
@ -41,15 +36,9 @@ public final class IkeyManager extends Manager {
|
||||||
private static final Map<XMPPConnection, IkeyManager> INSTANCES = new WeakHashMap<>();
|
private static final Map<XMPPConnection, IkeyManager> INSTANCES = new WeakHashMap<>();
|
||||||
|
|
||||||
private IkeyStore store;
|
private IkeyStore store;
|
||||||
private final ElementCanonicalizer canonicalizer;
|
|
||||||
|
|
||||||
private IkeyManager(XMPPConnection connection) {
|
private IkeyManager(XMPPConnection connection) {
|
||||||
super(connection);
|
super(connection);
|
||||||
try {
|
|
||||||
this.canonicalizer = new XmlSecElementCanonicalizer(Canonicalizer.getInstance(Canonicalizer.ALGO_ID_C14N11_OMIT_COMMENTS));
|
|
||||||
} catch (InvalidCanonicalizerException e) {
|
|
||||||
throw new AssertionError(e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static synchronized IkeyManager getInstanceFor(XMPPConnection connection) {
|
public static synchronized IkeyManager getInstanceFor(XMPPConnection connection) {
|
||||||
|
@ -106,7 +95,7 @@ public final class IkeyManager extends Manager {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void processIkeyElement(EntityBareJid from, IkeyElement element)
|
private void processIkeyElement(EntityBareJid from, IkeyElement element)
|
||||||
throws XMLParserException, IOException, CanonicalizationException, UnsupportedSignatureAlgorithmException {
|
throws IOException, UnsupportedSignatureAlgorithmException {
|
||||||
if (isFromTheFuture(element)) {
|
if (isFromTheFuture(element)) {
|
||||||
LOGGER.log(Level.WARNING, "Received ikey element appears to be from the future: " + element.getSubordinates().getTimestamp());
|
LOGGER.log(Level.WARNING, "Received ikey element appears to be from the future: " + element.getSubordinates().getTimestamp());
|
||||||
return;
|
return;
|
||||||
|
@ -126,9 +115,9 @@ public final class IkeyManager extends Manager {
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean verifyIkeyElement(EntityBareJid from, IkeyElement element)
|
private boolean verifyIkeyElement(EntityBareJid from, IkeyElement element)
|
||||||
throws XMLParserException, IOException, CanonicalizationException, UnsupportedSignatureAlgorithmException {
|
throws IOException, UnsupportedSignatureAlgorithmException {
|
||||||
IkeySignatureVerificationMechanism verificationMechanism = getSignatureVerificationMechanismFor(element);
|
IkeySignatureVerificationMechanism verificationMechanism = getSignatureVerificationMechanismFor(element);
|
||||||
IkeySignatureVerifier verifier = new IkeySignatureVerifier(verificationMechanism, canonicalizer);
|
IkeySignatureVerifier verifier = new IkeySignatureVerifier(verificationMechanism);
|
||||||
return verifier.verify(element, from);
|
return verifier.verify(element, from);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -168,7 +157,7 @@ public final class IkeyManager extends Manager {
|
||||||
public void onPepEvent(EntityBareJid from, IkeyElement event, String id, Message carrierMessage) {
|
public void onPepEvent(EntityBareJid from, IkeyElement event, String id, Message carrierMessage) {
|
||||||
try {
|
try {
|
||||||
processIkeyElement(from, event);
|
processIkeyElement(from, event);
|
||||||
} catch (XMLParserException | CanonicalizationException | IOException | UnsupportedSignatureAlgorithmException e) {
|
} catch (IOException | UnsupportedSignatureAlgorithmException e) {
|
||||||
LOGGER.log(Level.WARNING, "Error:", e);
|
LOGGER.log(Level.WARNING, "Error:", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
package org.jivesoftware.smackx.ikey;
|
package org.jivesoftware.smackx.ikey;
|
||||||
|
|
||||||
import org.apache.xml.security.c14n.CanonicalizationException;
|
|
||||||
import org.apache.xml.security.parser.XMLParserException;
|
|
||||||
import org.bouncycastle.util.encoders.Base64;
|
import org.bouncycastle.util.encoders.Base64;
|
||||||
import org.jivesoftware.smackx.ikey.element.IkeyElement;
|
import org.jivesoftware.smackx.ikey.element.IkeyElement;
|
||||||
import org.jivesoftware.smackx.ikey.mechanism.IkeySignatureVerificationMechanism;
|
import org.jivesoftware.smackx.ikey.mechanism.IkeySignatureVerificationMechanism;
|
||||||
|
@ -21,7 +19,7 @@ public class IkeySignatureVerifier {
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean verify(IkeyElement element, EntityBareJid owner)
|
public boolean verify(IkeyElement element, EntityBareJid owner)
|
||||||
throws XMLParserException, IOException, CanonicalizationException {
|
throws IOException {
|
||||||
throwIfMismatchingMechanism(element);
|
throwIfMismatchingMechanism(element);
|
||||||
throwIfMismatchingOwnerJid(element, owner);
|
throwIfMismatchingOwnerJid(element, owner);
|
||||||
|
|
||||||
|
|
|
@ -22,13 +22,11 @@ public class IkeyElement implements ExtensionElement {
|
||||||
private final IkeyType type;
|
private final IkeyType type;
|
||||||
private final SuperordinateElement superordinate;
|
private final SuperordinateElement superordinate;
|
||||||
private final SubordinateListElement subordinates;
|
private final SubordinateListElement subordinates;
|
||||||
private final ProofElement proof;
|
|
||||||
|
|
||||||
public IkeyElement(IkeyType type, SuperordinateElement superordinate, SubordinateListElement subordinates, ProofElement proof) {
|
public IkeyElement(IkeyType type, SuperordinateElement superordinate, SubordinateListElement subordinates) {
|
||||||
this.type = type;
|
this.type = type;
|
||||||
this.superordinate = superordinate;
|
this.superordinate = superordinate;
|
||||||
this.subordinates = subordinates;
|
this.subordinates = subordinates;
|
||||||
this.proof = proof;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public IkeyType getType() {
|
public IkeyType getType() {
|
||||||
|
@ -43,10 +41,6 @@ public class IkeyElement implements ExtensionElement {
|
||||||
return subordinates;
|
return subordinates;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ProofElement getProof() {
|
|
||||||
return proof;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getNamespace() {
|
public String getNamespace() {
|
||||||
return NAMESPACE;
|
return NAMESPACE;
|
||||||
|
@ -64,7 +58,6 @@ public class IkeyElement implements ExtensionElement {
|
||||||
.rightAngleBracket()
|
.rightAngleBracket()
|
||||||
.append(getSuperordinate())
|
.append(getSuperordinate())
|
||||||
.append(getSubordinates())
|
.append(getSubordinates())
|
||||||
.append(getProof())
|
|
||||||
.closeElement(this);
|
.closeElement(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -75,7 +68,6 @@ public class IkeyElement implements ExtensionElement {
|
||||||
.append(getType())
|
.append(getType())
|
||||||
.append(getSuperordinate())
|
.append(getSuperordinate())
|
||||||
.append(getSubordinates())
|
.append(getSubordinates())
|
||||||
.append(getProof())
|
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -85,7 +77,6 @@ public class IkeyElement implements ExtensionElement {
|
||||||
.append(getElementName(), o.getElementName())
|
.append(getElementName(), o.getElementName())
|
||||||
.append(getType(), o.getType())
|
.append(getType(), o.getType())
|
||||||
.append(getSuperordinate(), o.getSuperordinate())
|
.append(getSuperordinate(), o.getSuperordinate())
|
||||||
.append(getSubordinates(), o.getSubordinates())
|
.append(getSubordinates(), o.getSubordinates()));
|
||||||
.append(getProof(), o.getProof()));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,16 +16,13 @@ public class SubordinateListElement implements NamedElement {
|
||||||
|
|
||||||
public static final String NAMESPACE = IkeyConstants.NAMESPACE;
|
public static final String NAMESPACE = IkeyConstants.NAMESPACE;
|
||||||
public static final String ELEMENT = "subordinates";
|
public static final String ELEMENT = "subordinates";
|
||||||
public static final String ATTR_STAMP = "stamp";
|
|
||||||
public static final String ATTR_JID = "jid";
|
public static final String ATTR_JID = "jid";
|
||||||
|
|
||||||
private final List<SubordinateElement> subordinates;
|
private final List<SubordinateElement> subordinates;
|
||||||
private final EntityBareJid jid;
|
private final EntityBareJid jid;
|
||||||
private final Date timestamp;
|
|
||||||
|
|
||||||
public SubordinateListElement(EntityBareJid jid, Date timestamp, List<SubordinateElement> subordinates) {
|
public SubordinateListElement(EntityBareJid jid, List<SubordinateElement> subordinates) {
|
||||||
this.jid = jid;
|
this.jid = jid;
|
||||||
this.timestamp = timestamp;
|
|
||||||
this.subordinates = Objects.requireNonNullNorEmpty(subordinates, "List of subordinates MUST NOT be null nor empty.");
|
this.subordinates = Objects.requireNonNullNorEmpty(subordinates, "List of subordinates MUST NOT be null nor empty.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -33,10 +30,6 @@ public class SubordinateListElement implements NamedElement {
|
||||||
return jid;
|
return jid;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Date getTimestamp() {
|
|
||||||
return timestamp;
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<SubordinateElement> getSubordinates() {
|
public List<SubordinateElement> getSubordinates() {
|
||||||
return subordinates;
|
return subordinates;
|
||||||
}
|
}
|
||||||
|
@ -50,7 +43,6 @@ public class SubordinateListElement implements NamedElement {
|
||||||
public XmlStringBuilder toXML(XmlEnvironment xmlEnvironment) {
|
public XmlStringBuilder toXML(XmlEnvironment xmlEnvironment) {
|
||||||
return new XmlStringBuilder(this)
|
return new XmlStringBuilder(this)
|
||||||
.attribute(ATTR_JID, getJid())
|
.attribute(ATTR_JID, getJid())
|
||||||
.attribute(ATTR_STAMP, getTimestamp())
|
|
||||||
.rightAngleBracket()
|
.rightAngleBracket()
|
||||||
.append(getSubordinates())
|
.append(getSubordinates())
|
||||||
.closeElement(this);
|
.closeElement(this);
|
||||||
|
@ -61,7 +53,6 @@ public class SubordinateListElement implements NamedElement {
|
||||||
return HashCode.builder()
|
return HashCode.builder()
|
||||||
.append(getElementName())
|
.append(getElementName())
|
||||||
.append(getJid())
|
.append(getJid())
|
||||||
.append(getTimestamp())
|
|
||||||
.append(getSubordinates())
|
.append(getSubordinates())
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
@ -71,7 +62,6 @@ public class SubordinateListElement implements NamedElement {
|
||||||
return EqualsUtil.equals(this, other, (e, o) -> e
|
return EqualsUtil.equals(this, other, (e, o) -> e
|
||||||
.append(getElementName(), o.getElementName())
|
.append(getElementName(), o.getElementName())
|
||||||
.append(getJid(), o.getJid())
|
.append(getJid(), o.getJid())
|
||||||
.append(getTimestamp(), o.getTimestamp())
|
|
||||||
.append(getSubordinates(), o.getSubordinates()));
|
.append(getSubordinates(), o.getSubordinates()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,20 @@
|
||||||
|
package org.jivesoftware.smackx.signed.xep0285;
|
||||||
|
|
||||||
|
import org.bouncycastle.openpgp.PGPSignature;
|
||||||
|
import org.jivesoftware.smackx.signed.xep0285.element.DataElement;
|
||||||
|
import org.jivesoftware.smackx.signed.xep0285.element.PlainElement;
|
||||||
|
import org.jivesoftware.smackx.signed.xep0285.element.SignatureElement;
|
||||||
|
import org.jivesoftware.smackx.signed.xep0285.element.SignedElement;
|
||||||
|
import org.jivesoftware.smackx.signed.xep0285.signing.Signer;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
public class EncapsulatingSignatureManager {
|
||||||
|
|
||||||
|
public static SignedElement createSignedElement(PlainElement plainElement, Signer signer) throws IOException {
|
||||||
|
DataElement dataElement = DataElement.fromPlainElement(plainElement);
|
||||||
|
PGPSignature signature = signer.createSignature(dataElement.getUtf8Bytes());
|
||||||
|
SignatureElement signatureElement = SignatureElement.fromBytes(signer.getAlgorithmName(signature), signature.getEncoded());
|
||||||
|
return new SignedElement(signatureElement, dataElement);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,58 @@
|
||||||
|
package org.jivesoftware.smackx.signed.xep0285.element;
|
||||||
|
|
||||||
|
import org.jivesoftware.smack.packet.NamedElement;
|
||||||
|
import org.jivesoftware.smack.packet.XmlEnvironment;
|
||||||
|
import org.jivesoftware.smack.util.XmlStringBuilder;
|
||||||
|
import org.jivesoftware.smack.util.stringencoder.Base64;
|
||||||
|
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
|
||||||
|
public final class DataElement implements NamedElement {
|
||||||
|
|
||||||
|
public static final String ELEMENT = "data";
|
||||||
|
|
||||||
|
private final String base64Content;
|
||||||
|
private final String utf8Content;
|
||||||
|
|
||||||
|
private DataElement(String utf8Content, String base64Content) {
|
||||||
|
this.utf8Content = utf8Content;
|
||||||
|
this.base64Content = base64Content;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static DataElement fromUtf8Content(String utf8Content) {
|
||||||
|
return new DataElement(utf8Content, Base64.encodeToString(utf8Content.getBytes(StandardCharsets.UTF_8)));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static DataElement fromBase64Content(String base64Content) {
|
||||||
|
return new DataElement(Base64.decodeToString(base64Content), base64Content);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static DataElement fromPlainElement(PlainElement element) {
|
||||||
|
return fromUtf8Content(element.toXML().toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getUtf8Content() {
|
||||||
|
return utf8Content;
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] getUtf8Bytes() {
|
||||||
|
return getUtf8Content().getBytes(StandardCharsets.UTF_8);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getBase64Content() {
|
||||||
|
return base64Content;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getElementName() {
|
||||||
|
return ELEMENT;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public XmlStringBuilder toXML(XmlEnvironment xmlEnvironment) {
|
||||||
|
return new XmlStringBuilder(this)
|
||||||
|
.rightAngleBracket()
|
||||||
|
.append(getBase64Content())
|
||||||
|
.closeElement(this);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,92 @@
|
||||||
|
package org.jivesoftware.smackx.signed.xep0285.element;
|
||||||
|
|
||||||
|
import org.jivesoftware.smack.packet.ExtensionElement;
|
||||||
|
import org.jivesoftware.smack.packet.XmlEnvironment;
|
||||||
|
import org.jivesoftware.smack.util.EqualsUtil;
|
||||||
|
import org.jivesoftware.smack.util.HashCode;
|
||||||
|
import org.jivesoftware.smack.util.XmlStringBuilder;
|
||||||
|
import org.jivesoftware.smack.util.stringencoder.Base64;
|
||||||
|
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
public final class PlainElement implements ExtensionElement {
|
||||||
|
|
||||||
|
public static final String ELEMENT = "plain";
|
||||||
|
public static final String NAMESPACE = "urn:xmpp:signed:0";
|
||||||
|
public static final String ATTR_TIMESTAMP = "timestamp";
|
||||||
|
|
||||||
|
private final String utf8Content;
|
||||||
|
private final String base64Content;
|
||||||
|
private final Date timestamp;
|
||||||
|
|
||||||
|
private PlainElement(String utf8Content, String base64Content, Date timestamp) {
|
||||||
|
this.utf8Content = utf8Content;
|
||||||
|
this.base64Content = base64Content;
|
||||||
|
this.timestamp = timestamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static PlainElement fromExtensionElement(ExtensionElement element) {
|
||||||
|
return fromUtf8String(element.toXML().toString(), new Date());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static PlainElement fromUtf8String(String utf8, Date timestamp) {
|
||||||
|
return new PlainElement(utf8, Base64.encodeToString(utf8.getBytes(StandardCharsets.UTF_8)), timestamp);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static PlainElement fromBase64String(String base64, Date timestamp) {
|
||||||
|
return new PlainElement(Base64.decodeToString(base64), base64, timestamp);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getBase64Content() {
|
||||||
|
return base64Content;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getUtf8Content() {
|
||||||
|
return utf8Content;
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] asUtf8Bytes() {
|
||||||
|
return toXML().toString().getBytes(StandardCharsets.UTF_8);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Date getTimestamp() {
|
||||||
|
return timestamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getNamespace() {
|
||||||
|
return NAMESPACE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getElementName() {
|
||||||
|
return ELEMENT;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public XmlStringBuilder toXML(XmlEnvironment xmlEnvironment) {
|
||||||
|
return new XmlStringBuilder(this)
|
||||||
|
.attribute(ATTR_TIMESTAMP, getTimestamp())
|
||||||
|
.rightAngleBracket()
|
||||||
|
.append(getBase64Content())
|
||||||
|
.closeElement(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object other) {
|
||||||
|
return EqualsUtil.equals(this, other, (e, o) -> e
|
||||||
|
.append(getElementName(), o.getElementName())
|
||||||
|
.append(getBase64Content(), o.getBase64Content())
|
||||||
|
.append(getTimestamp(), o.getTimestamp()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return HashCode.builder()
|
||||||
|
.append(getElementName())
|
||||||
|
.append(getBase64Content())
|
||||||
|
.append(getTimestamp())
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,56 @@
|
||||||
|
package org.jivesoftware.smackx.signed.xep0285.element;
|
||||||
|
|
||||||
|
import org.jivesoftware.smack.packet.NamedElement;
|
||||||
|
import org.jivesoftware.smack.packet.XmlEnvironment;
|
||||||
|
import org.jivesoftware.smack.util.XmlStringBuilder;
|
||||||
|
import org.jivesoftware.smack.util.stringencoder.Base64;
|
||||||
|
|
||||||
|
public final class SignatureElement implements NamedElement {
|
||||||
|
|
||||||
|
public static final String ELEMENT = "signature";
|
||||||
|
public static final String ATTR_ALGORITHM = "algorithm";
|
||||||
|
|
||||||
|
private final String algorithm;
|
||||||
|
private final byte[] bytes;
|
||||||
|
private final String base64Content;
|
||||||
|
|
||||||
|
private SignatureElement(String algorithm, byte[] bytes, String base64Content) {
|
||||||
|
this.algorithm = algorithm;
|
||||||
|
this.bytes = bytes;
|
||||||
|
this.base64Content = base64Content;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static SignatureElement fromBytes(String algorithm, byte[] bytes) {
|
||||||
|
return new SignatureElement(algorithm, bytes, Base64.encodeToString(bytes));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static SignatureElement fromBase64Content(String algorithm, String base64Content) {
|
||||||
|
return new SignatureElement(algorithm, Base64.decode(base64Content), base64Content);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getAlgorithm() {
|
||||||
|
return algorithm;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getBase64Content() {
|
||||||
|
return base64Content;
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] getBytes() {
|
||||||
|
return bytes.clone();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getElementName() {
|
||||||
|
return ELEMENT;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public XmlStringBuilder toXML(XmlEnvironment xmlEnvironment) {
|
||||||
|
return new XmlStringBuilder(this)
|
||||||
|
.attribute(ATTR_ALGORITHM, getAlgorithm())
|
||||||
|
.rightAngleBracket()
|
||||||
|
.append(getBase64Content())
|
||||||
|
.closeElement(this);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,45 @@
|
||||||
|
package org.jivesoftware.smackx.signed.xep0285.element;
|
||||||
|
|
||||||
|
import org.jivesoftware.smack.packet.ExtensionElement;
|
||||||
|
import org.jivesoftware.smack.packet.XmlEnvironment;
|
||||||
|
import org.jivesoftware.smack.util.XmlStringBuilder;
|
||||||
|
|
||||||
|
public class SignedElement implements ExtensionElement {
|
||||||
|
|
||||||
|
public static final String ELEMENT = "signed";
|
||||||
|
|
||||||
|
private final SignatureElement signature;
|
||||||
|
private final DataElement data;
|
||||||
|
|
||||||
|
public SignedElement(SignatureElement signature, DataElement data) {
|
||||||
|
this.signature = signature;
|
||||||
|
this.data = data;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SignatureElement getSignature() {
|
||||||
|
return signature;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DataElement getData() {
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getNamespace() {
|
||||||
|
return PlainElement.NAMESPACE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getElementName() {
|
||||||
|
return ELEMENT;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public XmlStringBuilder toXML(XmlEnvironment xmlEnvironment) {
|
||||||
|
return new XmlStringBuilder(this)
|
||||||
|
.rightAngleBracket()
|
||||||
|
.append(getSignature())
|
||||||
|
.append(getData())
|
||||||
|
.closeElement(this);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
package org.jivesoftware.smackx.signed.xep0285.provider;
|
||||||
|
|
||||||
|
import org.jivesoftware.smack.packet.XmlEnvironment;
|
||||||
|
import org.jivesoftware.smack.parsing.SmackParsingException;
|
||||||
|
import org.jivesoftware.smack.provider.ExtensionElementProvider;
|
||||||
|
import org.jivesoftware.smack.util.ParserUtils;
|
||||||
|
import org.jivesoftware.smack.xml.XmlPullParser;
|
||||||
|
import org.jivesoftware.smack.xml.XmlPullParserException;
|
||||||
|
import org.jivesoftware.smackx.signed.xep0285.element.PlainElement;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
public class PlainElementProvider extends ExtensionElementProvider<PlainElement> {
|
||||||
|
@Override
|
||||||
|
public PlainElement parse(XmlPullParser parser, int initialDepth, XmlEnvironment xmlEnvironment)
|
||||||
|
throws XmlPullParserException, IOException, SmackParsingException {
|
||||||
|
String timestamp = ParserUtils.getRequiredAttribute(parser, PlainElement.ATTR_TIMESTAMP);
|
||||||
|
return PlainElement.fromBase64String(parser.nextText(), ParserUtils.getDateFromXep82String(timestamp));
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,45 @@
|
||||||
|
package org.jivesoftware.smackx.signed.xep0285.provider;
|
||||||
|
|
||||||
|
import org.jivesoftware.smack.packet.XmlEnvironment;
|
||||||
|
import org.jivesoftware.smack.provider.ExtensionElementProvider;
|
||||||
|
import org.jivesoftware.smack.util.Objects;
|
||||||
|
import org.jivesoftware.smack.util.ParserUtils;
|
||||||
|
import org.jivesoftware.smack.xml.XmlPullParser;
|
||||||
|
import org.jivesoftware.smack.xml.XmlPullParserException;
|
||||||
|
import org.jivesoftware.smackx.signed.xep0285.element.DataElement;
|
||||||
|
import org.jivesoftware.smackx.signed.xep0285.element.SignatureElement;
|
||||||
|
import org.jivesoftware.smackx.signed.xep0285.element.SignedElement;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
public class SignedElementProvider extends ExtensionElementProvider<SignedElement> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SignedElement parse(XmlPullParser parser, int initialDepth, XmlEnvironment xmlEnvironment)
|
||||||
|
throws XmlPullParserException, IOException {
|
||||||
|
|
||||||
|
SignatureElement signature = null;
|
||||||
|
DataElement data = null;
|
||||||
|
|
||||||
|
XmlPullParser.TagEvent tag;
|
||||||
|
String name;
|
||||||
|
do {
|
||||||
|
tag = parser.nextTag();
|
||||||
|
name = parser.getName();
|
||||||
|
if (tag == XmlPullParser.TagEvent.START_ELEMENT) {
|
||||||
|
switch (name) {
|
||||||
|
case SignatureElement.ELEMENT:
|
||||||
|
String algorithm = ParserUtils.getRequiredAttribute(parser, SignatureElement.ATTR_ALGORITHM);
|
||||||
|
signature = SignatureElement.fromBase64Content(algorithm, parser.nextText());
|
||||||
|
break;
|
||||||
|
|
||||||
|
case DataElement.ELEMENT:
|
||||||
|
data = DataElement.fromBase64Content(parser.nextText());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} while (parser.getDepth() != initialDepth);
|
||||||
|
|
||||||
|
return new SignedElement(Objects.requireNonNull(signature), Objects.requireNonNull(data));
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,74 @@
|
||||||
|
package org.jivesoftware.smackx.signed.xep0285.signing;
|
||||||
|
|
||||||
|
import org.bouncycastle.openpgp.PGPException;
|
||||||
|
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
||||||
|
import org.bouncycastle.openpgp.PGPSignature;
|
||||||
|
import org.bouncycastle.util.io.Streams;
|
||||||
|
import org.pgpainless.PGPainless;
|
||||||
|
import org.pgpainless.algorithm.HashAlgorithm;
|
||||||
|
import org.pgpainless.algorithm.PublicKeyAlgorithm;
|
||||||
|
import org.pgpainless.decryption_verification.OpenPgpMetadata;
|
||||||
|
import org.pgpainless.encryption_signing.EncryptionStream;
|
||||||
|
import org.pgpainless.key.protection.SecretKeyRingProtector;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
public class OpenPGPSigner implements Signer {
|
||||||
|
|
||||||
|
private final SecretKeyRingProtector protector;
|
||||||
|
private final PGPSecretKeyRing secretKey;
|
||||||
|
|
||||||
|
public OpenPGPSigner(PGPSecretKeyRing secretKey, SecretKeyRingProtector protector) {
|
||||||
|
this.protector = protector;
|
||||||
|
this.secretKey = secretKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PGPSignature createSignature(byte[] data) throws IOException {
|
||||||
|
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||||
|
ByteArrayInputStream inputStream = new ByteArrayInputStream(data);
|
||||||
|
try {
|
||||||
|
EncryptionStream signer = PGPainless.createEncryptor().onOutputStream(outputStream)
|
||||||
|
.doNotEncrypt()
|
||||||
|
.createDetachedSignature()
|
||||||
|
.signWith(protector, secretKey)
|
||||||
|
.noArmor();
|
||||||
|
Streams.pipeAll(inputStream, signer);
|
||||||
|
signer.close();
|
||||||
|
inputStream.close();
|
||||||
|
outputStream.close();
|
||||||
|
OpenPgpMetadata metadata = signer.getResult();
|
||||||
|
return metadata.getSignatures().iterator().next();
|
||||||
|
|
||||||
|
} catch (PGPException e) {
|
||||||
|
throw new IOException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getAlgorithmName(PGPSignature signature) {
|
||||||
|
PublicKeyAlgorithm signAlgo = PublicKeyAlgorithm.fromId(signature.getKeyAlgorithm());
|
||||||
|
|
||||||
|
String signAlgoName;
|
||||||
|
switch (signAlgo) {
|
||||||
|
case RSA_GENERAL:
|
||||||
|
case RSA_ENCRYPT:
|
||||||
|
case RSA_SIGN:
|
||||||
|
signAlgoName = "RSA";
|
||||||
|
break;
|
||||||
|
case DSA:
|
||||||
|
signAlgoName = "DSA";
|
||||||
|
break;
|
||||||
|
case ECDSA:
|
||||||
|
signAlgoName = "ECDSA";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new IllegalArgumentException("Signature was signed with unknown signature algorithm: " + signature.getKeyAlgorithm());
|
||||||
|
}
|
||||||
|
|
||||||
|
signAlgoName += HashAlgorithm.fromId(signature.getHashAlgorithm());
|
||||||
|
return signAlgoName;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
package org.jivesoftware.smackx.signed.xep0285.signing;
|
||||||
|
|
||||||
|
import org.bouncycastle.openpgp.PGPSignature;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
public interface Signer {
|
||||||
|
|
||||||
|
PGPSignature createSignature(byte[] data) throws IOException;
|
||||||
|
|
||||||
|
String getAlgorithmName(PGPSignature signature);
|
||||||
|
}
|
|
@ -3,7 +3,13 @@ package org.jivesoftware.smackx.util;
|
||||||
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
||||||
import org.jivesoftware.smack.SmackConfiguration;
|
import org.jivesoftware.smack.SmackConfiguration;
|
||||||
import org.jivesoftware.smack.util.stringencoder.Base64;
|
import org.jivesoftware.smack.util.stringencoder.Base64;
|
||||||
|
import org.jivesoftware.smack.xml.SmackXmlParser;
|
||||||
|
import org.jivesoftware.smack.xml.XmlPullParser;
|
||||||
|
import org.jivesoftware.smack.xml.XmlPullParserException;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.Reader;
|
||||||
|
import java.io.StringReader;
|
||||||
import java.security.Security;
|
import java.security.Security;
|
||||||
|
|
||||||
public class MercurySmackTestSuite {
|
public class MercurySmackTestSuite {
|
||||||
|
@ -35,4 +41,39 @@ public class MercurySmackTestSuite {
|
||||||
|
|
||||||
Security.addProvider(new BouncyCastleProvider());
|
Security.addProvider(new BouncyCastleProvider());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static XmlPullParser getParser(String string) {
|
||||||
|
return getParser(string, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static XmlPullParser getParser(String string, String startTag) {
|
||||||
|
return getParser(new StringReader(string), startTag);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static XmlPullParser getParser(Reader reader, String startTag) {
|
||||||
|
XmlPullParser parser;
|
||||||
|
try {
|
||||||
|
parser = SmackXmlParser.newXmlParser(reader);
|
||||||
|
if (startTag == null) {
|
||||||
|
while (parser.getEventType() != XmlPullParser.Event.START_ELEMENT) {
|
||||||
|
parser.next();
|
||||||
|
}
|
||||||
|
return parser;
|
||||||
|
}
|
||||||
|
boolean found = false;
|
||||||
|
|
||||||
|
while (!found) {
|
||||||
|
if ((parser.next() == XmlPullParser.Event.START_ELEMENT) && parser.getName().equals(startTag))
|
||||||
|
found = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!found)
|
||||||
|
throw new IllegalArgumentException("Can not find start tag '" + startTag + "'");
|
||||||
|
} catch (XmlPullParserException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
return parser;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -59,38 +59,4 @@ public class IkeyElementTest extends MercurySmackTestSuite {
|
||||||
return new SubordinateListElement(jid, date, Arrays.asList(subordinateElements));
|
return new SubordinateListElement(jid, date, Arrays.asList(subordinateElements));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static XmlPullParser getParser(String string) {
|
|
||||||
return getParser(string, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static XmlPullParser getParser(String string, String startTag) {
|
|
||||||
return getParser(new StringReader(string), startTag);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static XmlPullParser getParser(Reader reader, String startTag) {
|
|
||||||
XmlPullParser parser;
|
|
||||||
try {
|
|
||||||
parser = SmackXmlParser.newXmlParser(reader);
|
|
||||||
if (startTag == null) {
|
|
||||||
while (parser.getEventType() != XmlPullParser.Event.START_ELEMENT) {
|
|
||||||
parser.next();
|
|
||||||
}
|
|
||||||
return parser;
|
|
||||||
}
|
|
||||||
boolean found = false;
|
|
||||||
|
|
||||||
while (!found) {
|
|
||||||
if ((parser.next() == XmlPullParser.Event.START_ELEMENT) && parser.getName().equals(startTag))
|
|
||||||
found = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!found)
|
|
||||||
throw new IllegalArgumentException("Can not find start tag '" + startTag + "'");
|
|
||||||
} catch (XmlPullParserException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
return parser;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,42 @@
|
||||||
|
package org.jivesoftware.smackx.signed.xep0285;
|
||||||
|
|
||||||
|
import org.jivesoftware.smack.parsing.SmackParsingException;
|
||||||
|
import org.jivesoftware.smack.util.ParserUtils;
|
||||||
|
import org.jivesoftware.smack.xml.XmlPullParserException;
|
||||||
|
import org.jivesoftware.smackx.signed.xep0285.element.PlainElement;
|
||||||
|
import org.jivesoftware.smackx.signed.xep0285.provider.PlainElementProvider;
|
||||||
|
import org.jivesoftware.smackx.util.MercurySmackTestSuite;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
|
||||||
|
public class PlainElementTest extends MercurySmackTestSuite {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test() throws XmlPullParserException, IOException, SmackParsingException {
|
||||||
|
String plain = "<message xmlns=\"jabber:client\" " +
|
||||||
|
"from=\"juliet@capulet.net/balcony\" " +
|
||||||
|
"to=\"romeo@montegue.net\" " +
|
||||||
|
"type=\"chat\">" +
|
||||||
|
"<thread>c6373824-a307-40dd-8fe0-bad6e7299ad0</thread>" +
|
||||||
|
"<body>Wherefore art thou, Romeo?</body>" +
|
||||||
|
"</message>";
|
||||||
|
String example2 = "<plain xmlns=\"urn:xmpp:signed:0\" " +
|
||||||
|
"timestamp=\"2010-06-29T02:15:21.012+00:00\">" +
|
||||||
|
"PG1lc3NhZ2UgeG1sbnM9ImphYmJlcjpjbGllbnQiIGZyb209Imp1bGlldEBjYXB" +
|
||||||
|
"1bGV0Lm5ldC9iYWxjb255IiB0bz0icm9tZW9AbW9udGVndWUubmV0IiB0eXBlPS" +
|
||||||
|
"JjaGF0Ij48dGhyZWFkPmM2MzczODI0LWEzMDctNDBkZC04ZmUwLWJhZDZlNzI5O" +
|
||||||
|
"WFkMDwvdGhyZWFkPjxib2R5PldoZXJlZm9yZSBhcnQgdGhvdSwgUm9tZW8/PC9i" +
|
||||||
|
"b2R5PjwvbWVzc2FnZT4=" +
|
||||||
|
"</plain>";
|
||||||
|
|
||||||
|
PlainElement parsed = new PlainElementProvider()
|
||||||
|
.parse(getParser(example2));
|
||||||
|
PlainElement fromPlain = PlainElement.fromUtf8String(plain, ParserUtils.getDateFromXep82String("2010-06-29T02:15:21.012+00:00"));
|
||||||
|
|
||||||
|
assertEquals(parsed, fromPlain);
|
||||||
|
assertEquals(plain, parsed.getUtf8Content());
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,40 @@
|
||||||
|
package org.jivesoftware.smackx.signed.xep0285;
|
||||||
|
|
||||||
|
import org.jivesoftware.smack.parsing.SmackParsingException;
|
||||||
|
import org.jivesoftware.smack.xml.XmlPullParserException;
|
||||||
|
import org.jivesoftware.smackx.signed.xep0285.element.SignedElement;
|
||||||
|
import org.jivesoftware.smackx.signed.xep0285.provider.SignedElementProvider;
|
||||||
|
import org.jivesoftware.smackx.util.MercurySmackTestSuite;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
public class SignedElementTest extends MercurySmackTestSuite {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test() throws XmlPullParserException, IOException, SmackParsingException {
|
||||||
|
String example3 =
|
||||||
|
"<signed xmlns=\"urn:xmpp:signed:0\">" +
|
||||||
|
"<signature algorithm=\"RSA-SHA1\">" +
|
||||||
|
"DxbxIziY1C1Ytcxkj0IFLsfmDLMv96JMlMAQZ7jh49IbsOIPsxI2LyLmqhKH/994UXDJKQLHvLJz" +
|
||||||
|
"gAmw8V2b+zmyZeItJzSmB+HHiLFVXkD2Dd4JfetsafsfIcB7uNWg0gAeiKrTHfFgiyEC/2WxwOj3" +
|
||||||
|
"JUMRyQ9ykEPIzS0GZ/k=" +
|
||||||
|
"</signature>" +
|
||||||
|
"<data>" +
|
||||||
|
"PHBsYWluIHhtbG5zPSJ1cm46eG1wcDpzaWduZWQ6MCIgdGltZXN0YW1wPSIyMDEwLTA2LTI5VDAy" +
|
||||||
|
"OjE1OjIxLjAxMloiPgogIFBHMWxjM05oWjJVZ2VHMXNibk05SW1waFltSmxjanBqYkdsbGJuUWlJ" +
|
||||||
|
"R1p5YjIwOUltcDFiR2xsZEVCallYQgogIDFiR1YwTG01bGRDOWlZV3hqYjI1NUlpQjBiejBpY205" +
|
||||||
|
"dFpXOUFiVzl1ZEdWbmRXVXVibVYwSWlCMGVYQmxQUwogIEpqYUdGMElqNDhkR2h5WldGa1BtTTJN" +
|
||||||
|
"emN6T0RJMExXRXpNRGN0TkRCa1pDMDRabVV3TFdKaFpEWmxOekk1TwogIFdGa01Ed3ZkR2h5WldG" +
|
||||||
|
"a1BqeGliMlI1UGxkb1pYSmxabTl5WlNCaGNuUWdkR2h2ZFN3Z1VtOXRaVzgvUEM5aQogIGIyUjVQ" +
|
||||||
|
"and2YldWemMyRm5aVDQ9CjwvcGxhaW4+Cg==" +
|
||||||
|
"</data>" +
|
||||||
|
"</signed>";
|
||||||
|
|
||||||
|
SignedElement signedElement = new SignedElementProvider()
|
||||||
|
.parse(getParser(example3));
|
||||||
|
|
||||||
|
System.out.println(example3);
|
||||||
|
System.out.println(signedElement.toXML());
|
||||||
|
}
|
||||||
|
}
|
|
@ -1 +1 @@
|
||||||
Subproject commit 38bfe2492149cb095a1376080320e37c71832504
|
Subproject commit 1c822dcaa4d4cb92d8b2d048f49bb69885143d56
|
Loading…
Reference in New Issue