summaryrefslogtreecommitdiff
path: root/bkucommon/src
diff options
context:
space:
mode:
Diffstat (limited to 'bkucommon/src')
-rw-r--r--bkucommon/src/main/java/at/gv/egiz/bku/slcommands/impl/xsect/STALSignatureMethod.java283
-rw-r--r--bkucommon/src/test/java/at/gv/egiz/bku/slcommands/impl/cms/SignatureTest.java260
2 files changed, 274 insertions, 269 deletions
diff --git a/bkucommon/src/main/java/at/gv/egiz/bku/slcommands/impl/xsect/STALSignatureMethod.java b/bkucommon/src/main/java/at/gv/egiz/bku/slcommands/impl/xsect/STALSignatureMethod.java
index 1a6f6df9..a9eba7e8 100644
--- a/bkucommon/src/main/java/at/gv/egiz/bku/slcommands/impl/xsect/STALSignatureMethod.java
+++ b/bkucommon/src/main/java/at/gv/egiz/bku/slcommands/impl/xsect/STALSignatureMethod.java
@@ -1,139 +1,144 @@
-/*
- * Copyright 2011 by Graz University of Technology, Austria
- * MOCCA has been developed by the E-Government Innovation Center EGIZ, a joint
- * initiative of the Federal Chancellery Austria and Graz University of Technology.
- *
- * Licensed under the EUPL, Version 1.1 or - as soon they will be approved by
- * the European Commission - subsequent versions of the EUPL (the "Licence");
- * You may not use this work except in compliance with the Licence.
- * You may obtain a copy of the Licence at:
- * http://www.osor.eu/eupl/
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the Licence is distributed on an "AS IS" basis,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the Licence for the specific language governing permissions and
- * limitations under the Licence.
- *
- * This product combines work with different licenses. See the "NOTICE" text
- * file for details on the various modules and licenses.
- * The "NOTICE" text file is part of the distribution. Any derivative works
- * that you distribute must include a readable copy of the "NOTICE" text file.
- */
-
-
-
-package at.gv.egiz.bku.slcommands.impl.xsect;
-
-import iaik.xml.crypto.dsig.AbstractSignatureMethodImpl;
-
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.security.InvalidAlgorithmParameterException;
-import java.security.Key;
-import java.security.NoSuchAlgorithmException;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-
-import javax.xml.crypto.XMLCryptoContext;
-import javax.xml.crypto.dsig.XMLSignatureException;
-import javax.xml.crypto.dsig.spec.SignatureMethodParameterSpec;
-
-import at.gv.egiz.bku.slcommands.impl.DataObjectHashDataInput;
-import at.gv.egiz.bku.slexceptions.SLViewerException;
-import at.gv.egiz.bku.utils.StreamUtil;
-import at.gv.egiz.stal.ErrorResponse;
-import at.gv.egiz.stal.HashDataInput;
-import at.gv.egiz.stal.STAL;
-import at.gv.egiz.stal.STALRequest;
-import at.gv.egiz.stal.STALResponse;
-import at.gv.egiz.stal.SignRequest;
-import at.gv.egiz.stal.SignRequest.SignedInfo;
-import at.gv.egiz.stal.SignResponse;
-
-public class STALSignatureMethod extends AbstractSignatureMethodImpl {
-
- /**
- * Creates a new instance of this <code>STALSignatureMethod</code>
- * with the given <code>algorithm</code> and <code>params</code>.
- *
- * @param algorithm the algorithm URI
- * @param params optional algorithm parameters
- * @throws InvalidAlgorithmParameterException if the specified parameters
- * are inappropriate for the requested algorithm
- * @throws NoSuchAlgorithmException if an implementation of the specified
- * algorithm cannot be found
- * @throws NullPointerException if <code>algorithm</code> is <code>null</code>
- */
- public STALSignatureMethod(String algorithm,
- SignatureMethodParameterSpec params)
- throws InvalidAlgorithmParameterException, NoSuchAlgorithmException {
- super(algorithm, params);
- }
-
- @Override
- public byte[] calculateSignatureValue(XMLCryptoContext context, Key key, InputStream message)
- throws XMLSignatureException, IOException {
-
- if (!(key instanceof STALPrivateKey)) {
- throw new XMLSignatureException("STALSignatureMethod expects STALPrivateKey.");
- }
-
- STAL stal = ((STALPrivateKey) key).getStal();
- String keyboxIdentifier = ((STALPrivateKey) key).getKeyboxIdentifier();
- List<DataObject> dataObjects = ((STALPrivateKey) key).getDataObjects();
-
- List<HashDataInput> hashDataInputs = new ArrayList<HashDataInput>();
- for (DataObject dataObject : dataObjects) {
- try {
- dataObject.validateHashDataInput();
- } catch (SLViewerException e) {
- throw new XMLSignatureException(e);
- }
- hashDataInputs.add(new DataObjectHashDataInput(dataObject));
- }
-
- ByteArrayOutputStream m = new ByteArrayOutputStream();
- StreamUtil.copyStream(message, m);
-
- SignRequest signRequest = new SignRequest();
- signRequest.setKeyIdentifier(keyboxIdentifier);
- SignedInfo signedInfo = new SignedInfo();
- signedInfo.setValue(m.toByteArray());
- signRequest.setSignedInfo(signedInfo);
- signRequest.setHashDataInput(hashDataInputs);
-
- List<STALResponse> responses =
- stal.handleRequest(Collections.singletonList((STALRequest) signRequest));
-
- if (responses == null || responses.size() != 1) {
- throw new XMLSignatureException("Failed to access STAL.");
- }
-
- STALResponse response = responses.get(0);
- if (response instanceof SignResponse) {
- return ((SignResponse) response).getSignatureValue();
- } else if (response instanceof ErrorResponse) {
- ErrorResponse err = (ErrorResponse) response;
- STALSignatureException se = new STALSignatureException(err.getErrorCode(), err.getErrorMessage());
- throw new XMLSignatureException(se);
- } else {
- throw new XMLSignatureException("Failed to access STAL.");
- }
-
- }
-
- @Override
- public boolean validateSignatureValue(XMLCryptoContext context, Key key, byte[] value,
- InputStream message) throws XMLSignatureException, IOException {
- throw new XMLSignatureException("The STALSignatureMethod does not support validation.");
- }
-
- @Override
- protected Class<?> getParameterSpecClass() {
- return null;
- }
-
-}
+/*
+ * Copyright 2011 by Graz University of Technology, Austria
+ * MOCCA has been developed by the E-Government Innovation Center EGIZ, a joint
+ * initiative of the Federal Chancellery Austria and Graz University of Technology.
+ *
+ * Licensed under the EUPL, Version 1.1 or - as soon they will be approved by
+ * the European Commission - subsequent versions of the EUPL (the "Licence");
+ * You may not use this work except in compliance with the Licence.
+ * You may obtain a copy of the Licence at:
+ * http://www.osor.eu/eupl/
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the Licence is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the Licence for the specific language governing permissions and
+ * limitations under the Licence.
+ *
+ * This product combines work with different licenses. See the "NOTICE" text
+ * file for details on the various modules and licenses.
+ * The "NOTICE" text file is part of the distribution. Any derivative works
+ * that you distribute must include a readable copy of the "NOTICE" text file.
+ */
+
+package at.gv.egiz.bku.slcommands.impl.xsect;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.Key;
+import java.security.NoSuchAlgorithmException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import javax.xml.crypto.XMLCryptoContext;
+import javax.xml.crypto.dsig.XMLSignatureException;
+import javax.xml.crypto.dsig.spec.SignatureMethodParameterSpec;
+
+import at.gv.egiz.bku.slcommands.impl.DataObjectHashDataInput;
+import at.gv.egiz.bku.slexceptions.SLViewerException;
+import at.gv.egiz.bku.utils.StreamUtil;
+import at.gv.egiz.stal.ErrorResponse;
+import at.gv.egiz.stal.HashDataInput;
+import at.gv.egiz.stal.STAL;
+import at.gv.egiz.stal.STALRequest;
+import at.gv.egiz.stal.STALResponse;
+import at.gv.egiz.stal.SignRequest;
+import at.gv.egiz.stal.SignRequest.SignedInfo;
+import at.gv.egiz.stal.SignResponse;
+import iaik.xml.crypto.dsig.SignatureMethodImpl;
+
+public class STALSignatureMethod extends SignatureMethodImpl {
+
+ /**
+ * Creates a new instance of this <code>STALSignatureMethod</code> with the
+ * given <code>algorithm</code> and <code>params</code>.
+ *
+ * @param algorithm
+ * the algorithm URI
+ * @param params
+ * optional algorithm parameters
+ * @throws InvalidAlgorithmParameterException
+ * if the specified parameters are inappropriate for the requested
+ * algorithm
+ * @throws NoSuchAlgorithmException
+ * if an implementation of the specified algorithm cannot be found
+ * @throws NullPointerException
+ * if <code>algorithm</code> is <code>null</code>
+ */
+ public STALSignatureMethod(String algorithm, SignatureMethodParameterSpec params)
+ throws InvalidAlgorithmParameterException, NoSuchAlgorithmException {
+ super(algorithm, params);
+ }
+
+ @Override
+ public byte[] calculateSignatureValue(XMLCryptoContext context, Key key, InputStream message)
+ throws XMLSignatureException, IOException {
+
+ if (!(key instanceof STALPrivateKey)) {
+ throw new XMLSignatureException("STALSignatureMethod expects STALPrivateKey.");
+ }
+
+ STAL stal = ((STALPrivateKey) key).getStal();
+ String keyboxIdentifier = ((STALPrivateKey) key).getKeyboxIdentifier();
+ List<DataObject> dataObjects = ((STALPrivateKey) key).getDataObjects();
+
+ List<HashDataInput> hashDataInputs = new ArrayList<HashDataInput>();
+ for (DataObject dataObject : dataObjects) {
+ try {
+ dataObject.validateHashDataInput();
+ } catch (SLViewerException e) {
+ throw new XMLSignatureException(e);
+ }
+ hashDataInputs.add(new DataObjectHashDataInput(dataObject));
+ }
+
+ ByteArrayOutputStream m = new ByteArrayOutputStream();
+ StreamUtil.copyStream(message, m);
+
+ SignRequest signRequest = new SignRequest();
+ signRequest.setKeyIdentifier(keyboxIdentifier);
+ SignedInfo signedInfo = new SignedInfo();
+ signedInfo.setValue(m.toByteArray());
+ signRequest.setSignedInfo(signedInfo);
+ signRequest.setHashDataInput(hashDataInputs);
+
+ List<STALResponse> responses = stal.handleRequest(Collections.singletonList((STALRequest) signRequest));
+
+ if (responses == null || responses.size() != 1) {
+ throw new XMLSignatureException("Failed to access STAL.");
+ }
+
+ STALResponse response = responses.get(0);
+ if (response instanceof SignResponse) {
+ return ((SignResponse) response).getSignatureValue();
+ } else if (response instanceof ErrorResponse) {
+ ErrorResponse err = (ErrorResponse) response;
+ STALSignatureException se = new STALSignatureException(err.getErrorCode(), err.getErrorMessage());
+ throw new XMLSignatureException(se);
+ } else {
+ throw new XMLSignatureException("Failed to access STAL.");
+ }
+
+ }
+
+ @Override
+ public boolean validateSignatureValue(XMLCryptoContext context, Key key, byte[] value, InputStream message)
+ throws XMLSignatureException, IOException {
+ throw new XMLSignatureException("The STALSignatureMethod does not support validation.");
+ }
+
+// @Override
+// protected Class<?> getParameterSpecClass() {
+// return null;
+// }
+
+ @Override
+ public boolean validateSignatureValue(Key key, byte[] value, InputStream message) throws XMLSignatureException, IOException {
+ throw new XMLSignatureException("The STALSignatureMethod does not support validation.");
+ }
+
+
+}
diff --git a/bkucommon/src/test/java/at/gv/egiz/bku/slcommands/impl/cms/SignatureTest.java b/bkucommon/src/test/java/at/gv/egiz/bku/slcommands/impl/cms/SignatureTest.java
index 56229b83..f17b0329 100644
--- a/bkucommon/src/test/java/at/gv/egiz/bku/slcommands/impl/cms/SignatureTest.java
+++ b/bkucommon/src/test/java/at/gv/egiz/bku/slcommands/impl/cms/SignatureTest.java
@@ -1,130 +1,130 @@
-package at.gv.egiz.bku.slcommands.impl.cms;
-
-import static org.junit.Assert.*;
-
-import java.io.ByteArrayInputStream;
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.nio.charset.Charset;
-import java.security.MessageDigest;
-import java.util.Date;
-
-import org.junit.BeforeClass;
-import org.junit.Test;
-import org.w3._2000._09.xmldsig_.DigestMethodType;
-
-import at.buergerkarte.namespaces.securitylayer._1_2_3.Base64OptRefContentType;
-import at.buergerkarte.namespaces.securitylayer._1_2_3.CMSDataObjectRequiredMetaType;
-import at.buergerkarte.namespaces.securitylayer._1_2_3.DigestAndRefType;
-import at.buergerkarte.namespaces.securitylayer._1_2_3.MetaInfoType;
-import at.gv.egiz.stal.dummy.DummySTAL;
-import iaik.asn1.ObjectID;
-import iaik.asn1.structures.AlgorithmID;
-import iaik.cms.InvalidSignatureValueException;
-import iaik.cms.SignedData;
-import iaik.cms.SignerInfo;
-import iaik.security.ecc.provider.ECCProvider;
-import iaik.security.provider.IAIK;
-import iaik.x509.X509Certificate;
-
-public class SignatureTest {
-
- private DummySTAL stal = new DummySTAL();
-
- @BeforeClass
- public static void setUpClass() {
- IAIK.addAsProvider();
- ECCProvider.addAsProvider();
- }
-
- @Test
- public void testSignCMSDataObject() throws Exception {
-
- byte[] plaintext = "Plaintext".getBytes(Charset.forName("UTF-8"));
-
- CMSDataObjectRequiredMetaType dataObject = new CMSDataObjectRequiredMetaType();
- Base64OptRefContentType base64OptRefContentType = new Base64OptRefContentType();
- base64OptRefContentType.setBase64Content(plaintext);
- dataObject.setContent(base64OptRefContentType);
- MetaInfoType metaInfoType = new MetaInfoType();
- metaInfoType.setMimeType("text/plain");
- dataObject.setMetaInfo(metaInfoType);
-
- Signature signature = new Signature(dataObject, "detached", stal.getCert(), new Date(), null, true);
- byte[] cmsSignature = signature.sign(stal, "SecureSignatureKeypair");
-
- SignedData signedData = new SignedData(new ByteArrayInputStream(cmsSignature));
- signedData.setContent(plaintext);
- assertEquals(ObjectID.pkcs7_data, signedData.getEncapsulatedContentType());
- SignerInfo[] signerInfos = signedData.getSignerInfos();
- assertEquals(1, signerInfos.length);
- SignerInfo signerInfo = signerInfos[0];
- signedData.verify((X509Certificate) stal.getCert());
- assertEquals(AlgorithmID.sha1, signerInfo.getDigestAlgorithm());
- assertEquals(AlgorithmID.sha1WithRSAEncryption, signerInfo.getSignatureAlgorithm());
-
- System.out.println(AlgorithmID.sha1);
-
- }
-
- @Test
- public void testSignCMSReferenceSha1() throws Exception {
- testSignCMSReference(AlgorithmID.sha1);
- }
-
- //TODO Why doesn't it work this way??
- @Test(expected = InvalidSignatureValueException.class)
- public void testSignCMSReferenceSha256() throws Exception {
- testSignCMSReference(AlgorithmID.sha256);
- }
-
- private void testSignCMSReference(AlgorithmID digestAlgorithmID) throws Exception {
-
- byte[] plaintext = "Plaintext".getBytes(Charset.forName("UTF-8"));
-
- MessageDigest messageDigest = MessageDigest.getInstance(digestAlgorithmID.getImplementationName());
- byte[] digestValue = messageDigest.digest(plaintext);
-
- CMSDataObjectRequiredMetaType dataObject = new CMSDataObjectRequiredMetaType();
- DigestAndRefType digestAndRefType = new DigestAndRefType();
- DigestMethodType digestMethodType = new DigestMethodType();
- digestMethodType.setAlgorithm("URN:OID:" + digestAlgorithmID.getAlgorithm().getID());
- digestAndRefType.setDigestMethod(digestMethodType);
- digestAndRefType.setDigestValue(digestValue);
- dataObject.setDigestAndRef(digestAndRefType);
- MetaInfoType metaInfoType = new MetaInfoType();
- metaInfoType.setMimeType("text/plain");
- dataObject.setMetaInfo(metaInfoType);
-
- Signature signature = new Signature(dataObject, "detached", stal.getCert(), new Date(), null, true);
- byte[] cmsSignature = signature.sign(stal, "SecureSignatureKeypair");
-
- SignedData signedData = new SignedData(new ByteArrayInputStream(cmsSignature));
- signedData.setContent(plaintext);
- assertEquals(ObjectID.pkcs7_data, signedData.getEncapsulatedContentType());
- SignerInfo[] signerInfos = signedData.getSignerInfos();
- assertEquals(1, signerInfos.length);
- SignerInfo signerInfo = signerInfos[0];
- signedData.verify((X509Certificate) stal.getCert());
- assertEquals(digestAlgorithmID, signerInfo.getDigestAlgorithm());
- assertEquals(AlgorithmID.sha1WithRSAEncryption, signerInfo.getSignatureAlgorithm());
-
- }
-
- @Test
- public void test() throws URISyntaxException {
-
- String oid = null;
- URI uri = new URI("URN:OID:1.3.14.3.2.26");
- String scheme = uri.getScheme();
- if ("URN".equalsIgnoreCase(scheme)) {
- String schemeSpecificPart = uri.getSchemeSpecificPart().toLowerCase();
- if (schemeSpecificPart.startsWith("oid:")) {
- oid = schemeSpecificPart.substring(4, schemeSpecificPart.length());
- }
- }
- assertEquals("1.3.14.3.2.26", oid);
-
- }
-
-}
+package at.gv.egiz.bku.slcommands.impl.cms;
+
+import static org.junit.Assert.*;
+
+import java.io.ByteArrayInputStream;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.nio.charset.Charset;
+import java.security.MessageDigest;
+import java.util.Date;
+
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.w3._2000._09.xmldsig_.DigestMethodType;
+
+import at.buergerkarte.namespaces.securitylayer._1_2_3.Base64OptRefContentType;
+import at.buergerkarte.namespaces.securitylayer._1_2_3.CMSDataObjectRequiredMetaType;
+import at.buergerkarte.namespaces.securitylayer._1_2_3.DigestAndRefType;
+import at.buergerkarte.namespaces.securitylayer._1_2_3.MetaInfoType;
+import at.gv.egiz.stal.dummy.DummySTAL;
+import iaik.asn1.ObjectID;
+import iaik.asn1.structures.AlgorithmID;
+import iaik.cms.InvalidSignatureValueException;
+import iaik.cms.SignedData;
+import iaik.cms.SignerInfo;
+import iaik.security.ec.provider.ECCelerate;
+import iaik.security.provider.IAIK;
+import iaik.x509.X509Certificate;
+
+public class SignatureTest {
+
+ private DummySTAL stal = new DummySTAL();
+
+ @BeforeClass
+ public static void setUpClass() {
+ IAIK.addAsProvider();
+ ECCelerate.addAsProvider();
+ }
+
+ @Test
+ public void testSignCMSDataObject() throws Exception {
+
+ byte[] plaintext = "Plaintext".getBytes(Charset.forName("UTF-8"));
+
+ CMSDataObjectRequiredMetaType dataObject = new CMSDataObjectRequiredMetaType();
+ Base64OptRefContentType base64OptRefContentType = new Base64OptRefContentType();
+ base64OptRefContentType.setBase64Content(plaintext);
+ dataObject.setContent(base64OptRefContentType);
+ MetaInfoType metaInfoType = new MetaInfoType();
+ metaInfoType.setMimeType("text/plain");
+ dataObject.setMetaInfo(metaInfoType);
+
+ Signature signature = new Signature(dataObject, "detached", stal.getCert(), new Date(), null, true);
+ byte[] cmsSignature = signature.sign(stal, "SecureSignatureKeypair");
+
+ SignedData signedData = new SignedData(new ByteArrayInputStream(cmsSignature));
+ signedData.setContent(plaintext);
+ assertEquals(ObjectID.pkcs7_data, signedData.getEncapsulatedContentType());
+ SignerInfo[] signerInfos = signedData.getSignerInfos();
+ assertEquals(1, signerInfos.length);
+ SignerInfo signerInfo = signerInfos[0];
+ signedData.verify((X509Certificate) stal.getCert());
+ assertEquals(AlgorithmID.sha1, signerInfo.getDigestAlgorithm());
+ assertEquals(AlgorithmID.sha1WithRSAEncryption, signerInfo.getSignatureAlgorithm());
+
+ System.out.println(AlgorithmID.sha1);
+
+ }
+
+ @Test
+ public void testSignCMSReferenceSha1() throws Exception {
+ testSignCMSReference(AlgorithmID.sha1);
+ }
+
+ //TODO Why doesn't it work this way??
+ @Test(expected = InvalidSignatureValueException.class)
+ public void testSignCMSReferenceSha256() throws Exception {
+ testSignCMSReference(AlgorithmID.sha256);
+ }
+
+ private void testSignCMSReference(AlgorithmID digestAlgorithmID) throws Exception {
+
+ byte[] plaintext = "Plaintext".getBytes(Charset.forName("UTF-8"));
+
+ MessageDigest messageDigest = MessageDigest.getInstance(digestAlgorithmID.getImplementationName());
+ byte[] digestValue = messageDigest.digest(plaintext);
+
+ CMSDataObjectRequiredMetaType dataObject = new CMSDataObjectRequiredMetaType();
+ DigestAndRefType digestAndRefType = new DigestAndRefType();
+ DigestMethodType digestMethodType = new DigestMethodType();
+ digestMethodType.setAlgorithm("URN:OID:" + digestAlgorithmID.getAlgorithm().getID());
+ digestAndRefType.setDigestMethod(digestMethodType);
+ digestAndRefType.setDigestValue(digestValue);
+ dataObject.setDigestAndRef(digestAndRefType);
+ MetaInfoType metaInfoType = new MetaInfoType();
+ metaInfoType.setMimeType("text/plain");
+ dataObject.setMetaInfo(metaInfoType);
+
+ Signature signature = new Signature(dataObject, "detached", stal.getCert(), new Date(), null, true);
+ byte[] cmsSignature = signature.sign(stal, "SecureSignatureKeypair");
+
+ SignedData signedData = new SignedData(new ByteArrayInputStream(cmsSignature));
+ signedData.setContent(plaintext);
+ assertEquals(ObjectID.pkcs7_data, signedData.getEncapsulatedContentType());
+ SignerInfo[] signerInfos = signedData.getSignerInfos();
+ assertEquals(1, signerInfos.length);
+ SignerInfo signerInfo = signerInfos[0];
+ signedData.verify((X509Certificate) stal.getCert());
+ assertEquals(digestAlgorithmID, signerInfo.getDigestAlgorithm());
+ assertEquals(AlgorithmID.sha1WithRSAEncryption, signerInfo.getSignatureAlgorithm());
+
+ }
+
+ @Test
+ public void test() throws URISyntaxException {
+
+ String oid = null;
+ URI uri = new URI("URN:OID:1.3.14.3.2.26");
+ String scheme = uri.getScheme();
+ if ("URN".equalsIgnoreCase(scheme)) {
+ String schemeSpecificPart = uri.getSchemeSpecificPart().toLowerCase();
+ if (schemeSpecificPart.startsWith("oid:")) {
+ oid = schemeSpecificPart.substring(4, schemeSpecificPart.length());
+ }
+ }
+ assertEquals("1.3.14.3.2.26", oid);
+
+ }
+
+}