Skip to content

Commit

Permalink
Add test to exemplify usage of a custom JCA provider that delegates t…
Browse files Browse the repository at this point in the history
…he signature operation to an external system
  • Loading branch information
luisgoncalves committed Jul 4, 2024
1 parent 216d3ec commit 8683296
Show file tree
Hide file tree
Showing 4 changed files with 327 additions and 0 deletions.
Binary file added src/test/cert/gva/raizaccv1_der_16.crl
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
/*
* XAdES4j - A Java library for generation and verification of XAdES signatures.
* Copyright (C) 2024 Luis Goncalves.
*
* XAdES4j is free software; you can redistribute it and/or modify it under
* the terms of the GNU Lesser General Public License as published by the Free
* Software Foundation; either version 3 of the License, or any later version.
*
* XAdES4j is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
* details.
*
* You should have received a copy of the GNU Lesser General Public License along
* with XAdES4j. If not, see <http://www.gnu.org/licenses/>.
*/

package xades4j.production;

import org.apache.xml.security.utils.Constants;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import xades4j.algorithms.EnvelopedSignatureTransform;
import xades4j.properties.DataObjectDesc;
import xades4j.providers.CertificateValidationProvider;
import xades4j.providers.KeyingDataProvider;
import xades4j.verification.SignatureSpecificVerificationOptions;
import xades4j.verification.XAdESForm;
import xades4j.verification.XAdESVerificationResult;
import xades4j.verification.XadesVerificationProfile;
import xades4j.verification.XadesVerifier;

import java.security.Security;

import static org.junit.jupiter.api.Assertions.assertEquals;

/**
* Exemplifies usage of a custom JCA provider that delegates the signature operation to an external system.
*
* @see <a href="https://github.com/luisgoncalves/xades4j/issues/287">https://github.com/luisgoncalves/xades4j/issues/287</a> for the reasoning
*/
public class CustomSignatureProviderSignerTest extends SignerTestBase
{
static final ExternalSignatureSystemProvider signatureProvider = new ExternalSignatureSystemProvider();

@BeforeAll
static void addProvider()
{
assert Security.addProvider(signatureProvider) != -1;
}

@AfterAll
static void removeProvider()
{
Security.removeProvider(signatureProvider.getName());
}

@Test
void run() throws Exception
{
Document doc = getTestDocument();
Element root = doc.getDocumentElement();

// Sign

KeyingDataProvider keyingDataProvider = signatureProvider.getKeyingDataProvider();
XadesSigner signer = new XadesBesSigningProfile(keyingDataProvider).newSigner();

DataObjectDesc obj1 = new DataObjectReference('#' + root.getAttribute("Id")).withTransform(new EnvelopedSignatureTransform());
SignedDataObjects dataObjs = new SignedDataObjects(obj1);
signer.sign(dataObjs, root);

// Verify

CertificateValidationProvider certificateValidationProvider = ExternalSignatureSystem.getCertificateValidationProvider();
XadesVerifier verifier = new XadesVerificationProfile(certificateValidationProvider).newVerifier();

Element signature = (Element) root.getElementsByTagNameNS(Constants.SignatureSpecNS, Constants._TAG_SIGNATURE).item(0);
XAdESVerificationResult result = verifier.verify(signature, new SignatureSpecificVerificationOptions());
assertEquals(XAdESForm.BES, result.getSignatureForm());
}
}
78 changes: 78 additions & 0 deletions src/test/java/xades4j/production/ExternalSignatureSystem.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
/*
* XAdES4j - A Java library for generation and verification of XAdES signatures.
* Copyright (C) 2024 Luis Goncalves.
*
* XAdES4j is free software; you can redistribute it and/or modify it under
* the terms of the GNU Lesser General Public License as published by the Free
* Software Foundation; either version 3 of the License, or any later version.
*
* XAdES4j is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
* details.
*
* You should have received a copy of the GNU Lesser General Public License along
* with XAdES4j. If not, see <http://www.gnu.org/licenses/>.
*/

package xades4j.production;

import xades4j.providers.CertificateValidationProvider;
import xades4j.verification.VerifierTestBase;

import java.nio.ByteBuffer;
import java.security.PrivateKey;
import java.security.Signature;
import java.security.SignatureException;
import java.security.cert.X509Certificate;

/**
* A fictitious external system that performs signature operations.
* <p>
* The {@link #sign(ByteBuffer, String)} method is the entrypoint and could be implemented in any way.
* The example implementation uses the existing test key material and JCA.
*/
final class ExternalSignatureSystem
{
private static final X509Certificate _certificate;
private static final PrivateKey _key;

static
{
try
{
_certificate = SignerTestBase.keyingProviderMy.getSigningCertificateChain().get(0);
_key = SignerTestBase.keyingProviderMy.getSigningKey(_certificate);
}
catch (Exception e)
{
throw new RuntimeException(e);
}
}

public static X509Certificate getSigningCertificate() throws Exception
{
return _certificate;

}

public static CertificateValidationProvider getCertificateValidationProvider()
{
return VerifierTestBase.validationProviderMySigs;
}

public static byte[] sign(ByteBuffer content, String algorithm) throws SignatureException
{
try
{
Signature s = Signature.getInstance(algorithm);
s.initSign(_key);
s.update(content);
return s.sign();
}
catch (Exception e)
{
throw new SignatureException(e);
}
}
}
164 changes: 164 additions & 0 deletions src/test/java/xades4j/production/ExternalSignatureSystemProvider.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
/*
* XAdES4j - A Java library for generation and verification of XAdES signatures.
* Copyright (C) 2024 Luis Goncalves.
*
* XAdES4j is free software; you can redistribute it and/or modify it under
* the terms of the GNU Lesser General Public License as published by the Free
* Software Foundation; either version 3 of the License, or any later version.
*
* XAdES4j is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
* details.
*
* You should have received a copy of the GNU Lesser General Public License along
* with XAdES4j. If not, see <http://www.gnu.org/licenses/>.
*/

package xades4j.production;

import xades4j.providers.KeyingDataProvider;
import xades4j.providers.impl.DirectKeyingDataProvider;

import java.nio.ByteBuffer;
import java.security.InvalidKeyException;
import java.security.InvalidParameterException;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.Provider;
import java.security.PublicKey;
import java.security.SignatureException;
import java.security.SignatureSpi;
import java.util.Set;

/**
* Provider that delegates signature operations to a fictitious {@link ExternalSignatureSystem}.
*
* @see <a href="https://docs.oracle.com/en/java/javase/11/security/howtoimplaprovider.html">How to Implement a Provider</a>
*/
final class ExternalSignatureSystemProvider extends Provider
{
private static final Set<String> _supportedSignatureAlgorithms = Set.of(
"SHA256withRSA"
);

public ExternalSignatureSystemProvider()
{
super(ExternalSignatureSystemProvider.class.getSimpleName(), "1.0", "");
_supportedSignatureAlgorithms.forEach(alg -> {
putService(new SignatureService(this, alg));
});
}

public KeyingDataProvider getKeyingDataProvider() throws Exception
{
return new DirectKeyingDataProvider(
// The signing certificate to be added to the signature is whatever is used by the external system
ExternalSignatureSystem.getSigningCertificate(),
// The signing key is just an handler/marker that this provider can handle. The key instance could have
// some parameters to control the behavior of the external system (e.g. a key ID).
new MarkerPrivateKey());
}

private static final class SignatureService extends Provider.Service
{
public SignatureService(Provider provider, String algorithm)
{
super(provider, "Signature", algorithm, SignatureAdapter.class.getName(), null, null);
}

@Override
public Object newInstance(Object constructorParameter) throws NoSuchAlgorithmException
{
return new SignatureAdapter(getAlgorithm());
}
}

private static final class MarkerPrivateKey implements PrivateKey
{
@Override
public String getAlgorithm()
{
return "RSA";
}

@Override
public String getFormat()
{
return null;
}

@Override
public byte[] getEncoded()
{
return null;
}
}

/**
* The actual implementation of the Signature operation.
*/
private static final class SignatureAdapter extends SignatureSpi
{
private final String _algorithm;
private final ByteBuffer _buffer;

private SignatureAdapter(String algorithm)
{
_algorithm = algorithm;
_buffer = ByteBuffer.allocate(1024 * 4);
}

@Override
protected void engineInitSign(PrivateKey privateKey) throws InvalidKeyException
{
if (!(privateKey instanceof MarkerPrivateKey))
{
throw new InvalidKeyException();
}
}

@Override
protected void engineUpdate(byte b) throws SignatureException
{
_buffer.put(b);
}

@Override
protected void engineUpdate(byte[] b, int off, int len) throws SignatureException
{
_buffer.put(b, off, len);
}

@Override
protected byte[] engineSign() throws SignatureException
{
_buffer.flip(); // set buffer positions and limit for reading
return ExternalSignatureSystem.sign(_buffer, _algorithm);
}

@Override
protected void engineInitVerify(PublicKey publicKey) throws InvalidKeyException
{
throw new UnsupportedOperationException();
}

@Override
protected boolean engineVerify(byte[] sigBytes) throws SignatureException
{
throw new UnsupportedOperationException();
}

@Override
protected void engineSetParameter(String param, Object value) throws InvalidParameterException
{
throw new InvalidParameterException();
}

@Override
protected Object engineGetParameter(String param) throws InvalidParameterException
{
throw new InvalidParameterException();
}
}
}

0 comments on commit 8683296

Please sign in to comment.