feat: updates

This commit is contained in:
2023-10-29 21:51:07 +08:00
parent 29a6e32379
commit e2660a0ccb
10 changed files with 137 additions and 323 deletions

View File

@@ -1,7 +1,7 @@
{
"project": {
"name": "sign-pdf",
"main": "SampleMain",
"main": "me.hatter.tool.signpdf.main.SignPdfMain",
"archiveName": "sign-pdf"
},
"application": false,

View File

@@ -21,7 +21,7 @@ import java.io.OutputStream;
* @author Thomas Chojecki
*/
class CMSProcessableInputStream implements CMSTypedData {
private InputStream in;
private final InputStream in;
private final ASN1ObjectIdentifier contentType;
CMSProcessableInputStream(InputStream is) {

View File

@@ -1,18 +1,18 @@
package me.hatter.tool.signpdf;
import me.hatter.tools.commons.security.cert.X509CertUtil;
import me.hatter.tools.commons.security.key.KeyUtil;
import me.hatter.tool.signpdf.options.SignOptions;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.interactive.digitalsignature.ExternalSigningSupport;
import org.apache.pdfbox.pdmodel.interactive.digitalsignature.PDSignature;
import org.apache.pdfbox.pdmodel.interactive.digitalsignature.SignatureOptions;
import java.io.*;
import java.security.*;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.Calendar;
import java.util.List;
// https://svn.apache.org/viewvc/pdfbox/trunk/examples/src/main/
// java/org/apache/pdfbox/examples/signature/CreateSignature.java?view=co
@@ -29,52 +29,14 @@ import java.util.List;
* @author John Hewson
*/
public class CreateSignature extends CreateSignatureBase {
private final SignOptions signOptions;
/**
* Initialize the signature creator with a keystore and certficate password.
*
* @param keystore the pkcs12 keystore containing the signing certificate
* @param pin the password for recovering the key
* @throws KeyStoreException if the keystore has not been initialized (loaded)
* @throws NoSuchAlgorithmException if the algorithm for recovering the key cannot be found
* @throws UnrecoverableKeyException if the given password is wrong
* @throws CertificateException if the certificate is not valid as signing time
* @throws IOException if no certificate could be found
*/
public CreateSignature(KeyStore keystore, char[] pin)
public CreateSignature(SignOptions signOptions, KeyStore keystore, char[] pin)
throws KeyStoreException, UnrecoverableKeyException, NoSuchAlgorithmException, CertificateException, IOException {
super(keystore, pin);
this.signOptions = signOptions;
}
/**
* Signs the given PDF file. Alters the original file on disk.
*
* @param file the PDF file to sign
* @throws IOException if the file could not be read or written
*/
public void signDetached(File file) throws IOException {
signDetached(file, file, null);
}
/**
* Signs the given PDF file.
*
* @param inFile input PDF file
* @param outFile output PDF file
* @throws IOException if the input file could not be read
*/
public void signDetached(File inFile, File outFile) throws IOException {
signDetached(inFile, outFile, null);
}
/**
* Signs the given PDF file.
*
* @param inFile input PDF file
* @param outFile output PDF file
* @param tsaUrl optional TSA url
* @throws IOException if the input file could not be read
*/
public void signDetached(File inFile, File outFile, String tsaUrl) throws IOException {
if (inFile == null || !inFile.exists()) {
throw new FileNotFoundException("Document for signing does not exist");
@@ -84,28 +46,27 @@ public class CreateSignature extends CreateSignatureBase {
// sign
try (
FileOutputStream fos = new FileOutputStream(outFile);
PDDocument doc = PDDocument.load(inFile)
final FileOutputStream fos = new FileOutputStream(outFile);
final PDDocument doc = PDDocument.load(inFile)
) {
signDetached(doc, fos);
}
}
public void signDetached(PDDocument document, OutputStream output)
throws IOException {
public void signDetached(PDDocument document, OutputStream output) throws IOException {
int accessPermissions = SigUtils.getMDPPermission(document);
if (accessPermissions == 1) {
throw new IllegalStateException("No changes to the document are permitted due to DocMDP transform parameters dictionary");
throw new IllegalStateException(
"No changes to the document are permitted due to DocMDP transform parameters dictionary");
}
// create signature dictionary
PDSignature signature = new PDSignature();
final PDSignature signature = new PDSignature();
signature.setFilter(PDSignature.FILTER_ADOBE_PPKLITE);
signature.setSubFilter(PDSignature.SUBFILTER_ADBE_PKCS7_DETACHED);
signature.setName("Example User");
signature.setLocation("Hangzhou, ZJ");
signature.setReason("Testing");
// TODO extract the above details from the signing certificate? Reason as a parameter?
signature.setName(signOptions.getName());
signature.setLocation(signOptions.getLocation());
signature.setReason(signOptions.getReason());
// the signing date, needed for valid signature
signature.setSignDate(Calendar.getInstance());
@@ -117,13 +78,13 @@ public class CreateSignature extends CreateSignatureBase {
if (isExternalSigning()) {
document.addSignature(signature);
ExternalSigningSupport externalSigning = document.saveIncrementalForExternalSigning(output);
final ExternalSigningSupport externalSigning = document.saveIncrementalForExternalSigning(output);
// invoke external signature service
byte[] cmsSignature = sign(externalSigning.getContent());
final byte[] cmsSignature = sign(externalSigning.getContent());
// set signature bytes received from the service
externalSigning.setSignature(cmsSignature);
} else {
SignatureOptions signatureOptions = new SignatureOptions();
final SignatureOptions signatureOptions = new SignatureOptions();
// Size can vary, but should be enough for purpose.
signatureOptions.setPreferredSignatureSize(SignatureOptions.DEFAULT_SIGNATURE_SIZE * 2);
// register signature dictionary and sign interface
@@ -133,163 +94,4 @@ public class CreateSignature extends CreateSignatureBase {
document.saveIncremental(output);
}
}
public static void main(String[] args) throws Exception {
List<X509Certificate> certs = X509CertUtil.parseX509CertificateList(
"-----BEGIN CERTIFICATE-----\n" +
"MIID8DCCAligAwIBAgIVAO+jr2FNZq/QefgFLTU/+MTMmFiHMA0GCSqGSIb3DQEB\n" +
"CwUAMCQxIjAgBgNVBAMMGVRlc3QgT25seSBJbnRlcm1lZGlhdGUgQ0EwHhcNMTkw\n" +
"OTAzMDAwMDAwWhcNMjQwOTAzMDAwMDAwWjAVMRMwEQYDVQQDDApoYXR0ZXIuaW5r\n" +
"MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAlZbeZOvJ4CGd06Rg3ego\n" +
"tLp1dgqPQSZb7E8zPga6EGXpMKuigfyGyLIlLBi345mq8/BYov+8G3LaDJj++7yP\n" +
"O+FcUrFDGABP6CHZCgmYzGHA41V8pV/qXeq0AeIQ9OaczdigydVqoY9S/rjuMLTU\n" +
"B1Y9MMIlhwZoXuheF++qLZJlqbWAwM9UICgtgRoM+mBTZeaM43D2JeuOxmSqZfQJ\n" +
"DqgirHcSCsClKkxhL9p5Vpaa/Sh/5iR3uCNr60N3dQZmv8fUyZPoZbHDZFoELEjy\n" +
"CJSknF6ISQ6x0EzoT4hHGRbbdZGSNgpJiLMlmkEGDKYRk0V+xkFkKtiPWfWk1hiz\n" +
"cQIDAQABo4GnMIGkMA4GA1UdDwEB/wQEAwIFoDAMBgNVHRMBAf8EAjAAMB0GA1Ud\n" +
"JQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjAlBgNVHREEHjAcggpoYXR0ZXIuaW5r\n" +
"gg53d3cuaGF0dGVyLmluazAdBgNVHQ4EFgQU83K5sj8Al7wIk1qLInjkwfTJRkQw\n" +
"HwYDVR0jBBgwFoAUDx5JXiPyUvc3E6Hkhk5fwgASM9wwDQYJKoZIhvcNAQELBQAD\n" +
"ggGBAEsM17TNw+4YxwjWRxSWHIgakyHpyNrSgG87g0iO671nCnzSEC5UsgtvwVOl\n" +
"IRgOi6iZWdY6UAa0Fp8qtsbmeUchf1RZiD1phkHPCNQ90UY7zBfr+7BJvoSyYLUc\n" +
"85GEdHR9aT2zeIzbYtf1TLeUaxYhGWRFxr+BxWUaenKUUx1MKmE7qYA+NgkLcS0b\n" +
"Emnaulzrt4Jmoa2GJ3WUpvekAiUdaLtLjLe2cTL4cbRgAZu1dS0l/rRpJVnsSR4s\n" +
"3cUIx+cQamfarzTD85PRe+2tlUlE5rNjSCCY9Ev9fRuqMU8x5gr9d1U7/cENpIkv\n" +
"/MxNJtTv3P1e9IL4Nd/84jalN4gRxHvWfREfKMxICjKrhaH4uIkpz2kNwapxu2Rf\n" +
"eL/CtcW9efT1Sgp8pMwV/jv0loKRhGqWDpiRd9JgX+2VZBJuPqvyYxGKo9BHorT0\n" +
"vu6K4y1vb0c0euyrRE+kh4GfuPPWFRWYB9nug7zwxIMdV/o1kA/RrHV6zlA3QBlk\n" +
"I6Syqg==\n" +
"-----END CERTIFICATE-----\n" +
"-----BEGIN CERTIFICATE-----\n" +
"MIIEtDCCApygAwIBAgIUE3K6bToXpBc0QsO/9BWpE4oqNzswDQYJKoZIhvcNAQEL\n" +
"BQAwHDEaMBgGA1UEAwwRVGVzdCBPbmx5IFJvb3QgQ0EwHhcNMTkwODAyMDAwMDAw\n" +
"WhcNMzkwODAyMDAwMDAwWjAkMSIwIAYDVQQDDBlUZXN0IE9ubHkgSW50ZXJtZWRp\n" +
"YXRlIENBMIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEAwoKoXjE/m4pi\n" +
"T6DB5l0J/yRwChUVt1aJ6/qB2dnGSwKSQSOEMMi5/d+JFRinfytc5lMxPiE/A/g2\n" +
"4a9y8+2zyCtWY3UL8xVvJoylhoURmYM2Nx/odMFHoeHESeJkSMDVUN6zZOjGf3eg\n" +
"RYEKjaGIGSlctn8WOejNRbgMxxe4DrKhyAgHaAgiUX9vqX0IHnO5+8Gk260FEa1d\n" +
"CVZ0HInM2/awIjrsTzu3ivXoZ68YijN6JqX0Fu+VSBJhtj6gZrUqJQQIfZja+Z7n\n" +
"OFiV/n4MOOGZdV0FzKdxfk/DKym/7sEdKeZ3TtCwq1M+OohKG5EZcOeMlUXa5Aco\n" +
"bZhVlNxDGZjQ323t17BPSiix2Kr6Y2nJ8B3BO4258nn5K5F0vEYqVs5G4tqONA5T\n" +
"sm54CwGcRQbGXQQkb7Xgn9yg8v42HLQekov6cbnCyIxQqOmkNZzVLcdkqAeSSS6m\n" +
"oGIE+/74GWPn4k+5Wm5bxLqmkeg9MnjXezjDjzBCWJDVQt/GbWHfAgMBAAGjZjBk\n" +
"MA4GA1UdDwEB/wQEAwIBBjASBgNVHRMBAf8ECDAGAQH/AgEAMB0GA1UdDgQWBBQP\n" +
"HkleI/JS9zcToeSGTl/CABIz3DAfBgNVHSMEGDAWgBSNezwcll5FV1zbEHyVFQVE\n" +
"G/Oa/DANBgkqhkiG9w0BAQsFAAOCAgEAGOXsHuJppjLLMUrWmcPd3fGmXgGsf0+Z\n" +
"v60OG39fNfnyxD2UDOmoLRgAixaWGc3Mj6gRRoPK1w2yjazbLhcIL9Cdh+F23cSq\n" +
"dK3RVR+TJk+/YVC+voxxKPXTilMNmoy4yOXKG88U9xwNdSYpwwgjjwhVwMZCpFn4\n" +
"rVc79f4UQ0tlRlSO55LO7RDx/LLSdm0wVAWWZJ/U9aWr2qglwS6Qi3aLDIbOn3l7\n" +
"7JzkLVDOHp3mnpxaOjpK4Ge0OiTIvoeIfEwhf25He2dYsJgrqKtvlkqEZ7J/b2mg\n" +
"mGwi7j7chu7PRNzzOjvzogZApyq8qVk1vn5fcyzMX2qxfhHWQWvWGqIWK2d5fNeg\n" +
"NTGIcxSZfKNBI8iiJ/+Wsp/fGoGaOfx63yiQ+6GsI/02sqz4qJrhsZcUuV+NnpWv\n" +
"e/LZgzSCkw92He0BwkNkAqb9HY5n+VSPuscOHD1xoaj0tovzlvktG4M3kyScqd2o\n" +
"nCO8yD+CZG3xNGIjYH1IKZ3NVE1SaW2RlkzJuzkcAgzpduRaEYPGM3qTIuks6GBR\n" +
"dfuaZ9FjI+c2pQIKUMOcvmYQru286sWXaLFRYF3ZLrNNjdBs0Oawk2YJ9rp4dmMY\n" +
"xVZByucYuEu/b4Y/CzLB8N6cn87lYT+YkUBWPMLkN4nUbykUBckrNvB1DqGNOjXA\n" +
"O1XW8Bs24gI=\n" +
"-----END CERTIFICATE-----\n" +
"-----BEGIN CERTIFICATE-----\n" +
"MIIFLDCCAxSgAwIBAgIVAKBORHv9Qd/+nqn1WtCh46Suwq11MA0GCSqGSIb3DQEB\n" +
"CwUAMBwxGjAYBgNVBAMMEVRlc3QgT25seSBSb290IENBMCAXDTE5MDgwMjAwMDAw\n" +
"MFoYDzIwNzkwODAyMDAwMDAwWjAcMRowGAYDVQQDDBFUZXN0IE9ubHkgUm9vdCBD\n" +
"QTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBALaKEyt8PnET4vzqXcOO\n" +
"yVn2pY5tqg14wHVdtnEu7DGxzHO3lKCLxaOuNQkCJ0hRDutC+O2niqAzZ9uvgFpu\n" +
"wemEaciRVjT+IXgSuFSdqGtnGHeHTm0Pwxpbsg4d9KCMDCRoeSUHDf5/j1ZU79iv\n" +
"CNpidRVYqJQMhrgS/nJM8us/lZ4BIoAeHOyMUBIR3GhdFjgwQxVDPVModNOu8rBV\n" +
"MuJQLXZLbERh1PqAGrJ9yJ8+O8qB+xSf/nJtW7fyRSMSLFNu9sOUUMxaDer/uAkj\n" +
"r6SZN2tszABY25rW3nxChL25s2Hbe2psEmhCCaKVBroEsKWQkvK33+eiy9OhuAWM\n" +
"1KVBztcwX4go0vGFEVu9NbJEnqlPL9Mlwy6tMbwmsktSEn1RHYAwXXdtgmwRWo8S\n" +
"Bykhxoc8ED9aKMtZkdjVa34YLTVqD03WFM68j7dSLw/4ikc8MLsSnjLcXaLwGzHV\n" +
"/C/7pkz0N+VQnp65Ju09XpneJ3H6iYuQHyzO5V949vL8b4EpinOWQBGmw9Zhr1Z5\n" +
"MAv33AW5BP+GzHNLzDJU8Z0vn8mZR7SMzreWqHZ8I1eipvKuibmf9kwApVZthlYr\n" +
"94zUScO9o451zeofJhENLoGK0n8jy8m4uz0+XCd3kxplSmrknWHQBmm3SXLfWZmV\n" +
"ve9zwsPh2sBhB39jH6buQwR1AgMBAAGjYzBhMA4GA1UdDwEB/wQEAwIBBjAPBgNV\n" +
"HRMBAf8EBTADAQH/MB0GA1UdDgQWBBSNezwcll5FV1zbEHyVFQVEG/Oa/DAfBgNV\n" +
"HSMEGDAWgBSNezwcll5FV1zbEHyVFQVEG/Oa/DANBgkqhkiG9w0BAQsFAAOCAgEA\n" +
"U4KMc1cde06QUULlDWg8+vCYc/Ve76zpfHdPSfoh2u9ZXa2ZTCCriX3byUDV8CAL\n" +
"FUBYajtHDSEJS/JZLz9RDPieGz2O7p7u5p/lVis79dUaLChp/FsMuhDMYlyOIZfi\n" +
"sQhRoLqpH2MAuBn9FjyciDPP3d698iOQdj/8pztuFh3GUdTmjkgeTve4i52oxRi9\n" +
"o9GuakE6+rHQi/5jjWtutrqDcnnmzK4mYup9T2eqYoPeWe3kBmALpfoKoabjXDwq\n" +
"61EreV7qfO+0HmcFNhqv5gOJBoa7tQkCMnn4EmJMzEa3tZqXXc3Pd5W9L+Q7gD+p\n" +
"4Hi+exrr6KLl9A6Kht+HKoB3em8E7USD5uU3PJZEsSeYhSKCTeroArKc19S6vmU0\n" +
"PIHWeaZWbwySQrDw1FbLiItw5ZCXGFQLmDDcW4gWw2EP43ZfcJl068nrcOHDHsTU\n" +
"pDP8TzmT/uhp1HRbh4vuyOej2qPVIzUEa0fwyg9JyAuD7+hoH07qZ2Ekc4Ocij6w\n" +
"Hm88RHyD6ObPRg/aZd7fVkgQgz/cE1drH3gO5yUvDQhB3Iz3SFaHwWEfYi3tiPLz\n" +
"wA3rEgaSj6xhZTeOkKVu+aj3u19tcyIRxTXM33Zv1WLSRVUo/CJj7O38qBft4F+L\n" +
"UhQrqeqObukNbcGgWhwy3+PysJFky6peB6IexebbVYk=\n" +
"-----END CERTIFICATE-----");
PrivateKey privateKey = KeyUtil.parsePrivateKeyPEM("-----BEGIN PRIVATE KEY-----\n" +
"MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCVlt5k68ngIZ3T\n" +
"xSGBfudXkCooM8/cjEefv/cI\n" +
"-----END PRIVATE KEY-----");
X509Certificate[] chain = new X509Certificate[]{certs.get(0)};
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
keyStore.load(null);
for (X509Certificate c : certs) {
keyStore.setCertificateEntry(c.getSubjectX500Principal().getName(), c);
}
keyStore.setKeyEntry("", privateKey, new char[0], chain);
System.out.println(keyStore);
CreateSignature signing = new CreateSignature(keyStore, new char[0]);
// signing.setExternalSigning(true);
String tsaUrl = "https://hatter.ink/ca/sign_timestamp.action";
File inFile = new File("/Users/hatterjiang/07-057r7_Web_Map_Tile_Service_Standard.pdf");
String name = inFile.getName();
String substring = name.substring(0, name.lastIndexOf('.'));
File outFile = new File(inFile.getParent(), substring + "_signed.pdf");
signing.signDetached(inFile, outFile, tsaUrl);
}
public static void main_old(String[] args) throws IOException, GeneralSecurityException {
if (args.length < 3) {
usage();
System.exit(1);
}
String tsaUrl = null;
boolean externalSig = false;
for (int i = 0; i < args.length; i++) {
if (args[i].equals("-tsa")) {
i++;
if (i >= args.length) {
usage();
System.exit(1);
}
tsaUrl = args[i];
}
if (args[i].equals("-e")) {
externalSig = true;
}
}
// load the keystore
KeyStore keystore = KeyStore.getInstance("PKCS12");
char[] password = args[1].toCharArray(); // TODO use Java 6 java.io.Console.readPassword
keystore.load(new FileInputStream(args[0]), password);
// TODO alias command line argument
// sign PDF
CreateSignature signing = new CreateSignature(keystore, password);
signing.setExternalSigning(externalSig);
File inFile = new File(args[2]);
String name = inFile.getName();
String substring = name.substring(0, name.lastIndexOf('.'));
File outFile = new File(inFile.getParent(), substring + "_signed.pdf");
signing.signDetached(inFile, outFile, tsaUrl);
}
private static void usage() {
System.err.println("usage: java " + CreateSignature.class.getName() + " " +
"<pkcs12_keystore> <password> <pdf_to_sign>\n" + "" +
"options:\n" +
" -tsa <url> sign timestamp using the given TSA server\n" +
" -e sign using external signature creation scenario");
}
}

View File

@@ -28,30 +28,18 @@ public abstract class CreateSignatureBase implements SignatureInterface {
private String tsaUrl;
private boolean externalSigning;
/**
* Initialize the signature creator with a keystore (pkcs12) and pin that should be used for the
* signature.
*
* @param keystore is a pkcs12 keystore.
* @param pin is the pin for the keystore / private key
* @throws KeyStoreException if the keystore has not been initialized (loaded)
* @throws NoSuchAlgorithmException if the algorithm for recovering the key cannot be found
* @throws UnrecoverableKeyException if the given password is wrong
* @throws CertificateException if the certificate is not valid as signing time
* @throws IOException if no certificate could be found
*/
public CreateSignatureBase(KeyStore keystore, char[] pin)
throws KeyStoreException, UnrecoverableKeyException, NoSuchAlgorithmException, IOException, CertificateException {
// grabs the first alias from the keystore and get the private key. An
// alternative method or constructor could be used for setting a specific
// alias that should be used.
Enumeration<String> aliases = keystore.aliases();
final Enumeration<String> aliases = keystore.aliases();
String alias;
Certificate cert = null;
while (aliases.hasMoreElements()) {
alias = aliases.nextElement();
setPrivateKey((PrivateKey) keystore.getKey(alias, pin));
Certificate[] certChain = keystore.getCertificateChain(alias);
final Certificate[] certChain = keystore.getCertificateChain(alias);
if (certChain == null) {
continue;
}
@@ -87,36 +75,21 @@ public abstract class CreateSignatureBase implements SignatureInterface {
this.tsaUrl = tsaUrl;
}
/**
* SignatureInterface sample implementation.
* <p>
* This method will be called from inside of the pdfbox and create the PKCS #7 signature.
* The given InputStream contains the bytes that are given by the byte range.
* <p>
* This method is for internal use only.
* <p>
* Use your favorite cryptographic library to implement PKCS #7 signature creation.
* If you want to create the hash and the signature separately (e.g. to transfer only the hash
* to an external application), read <a href="https://stackoverflow.com/questions/41767351">this
* answer</a> or <a href="https://stackoverflow.com/questions/56867465">this answer</a>.
*
* @throws IOException
*/
@Override
public byte[] sign(InputStream content) throws IOException {
// cannot be done private (interface)
try {
CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
X509Certificate cert = (X509Certificate) certificateChain[0];
final CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
final X509Certificate cert = (X509Certificate) certificateChain[0];
// TODO use customer signer
ContentSigner contentSigner = new JcaContentSignerBuilder("SHA256WithRSA").build(privateKey);
final ContentSigner contentSigner = new JcaContentSignerBuilder("SHA256WithRSA").build(privateKey);
gen.addSignerInfoGenerator(new JcaSignerInfoGeneratorBuilder(
new JcaDigestCalculatorProviderBuilder().build()).build(contentSigner, cert));
gen.addCertificates(new JcaCertStore(Arrays.asList(certificateChain)));
CMSProcessableInputStream msg = new CMSProcessableInputStream(content);
final CMSProcessableInputStream msg = new CMSProcessableInputStream(content);
CMSSignedData signedData = gen.generate(msg, false);
if (tsaUrl != null && tsaUrl.length() > 0) {
ValidationTimeStamp validation = new ValidationTimeStamp(tsaUrl);
final ValidationTimeStamp validation = new ValidationTimeStamp(tsaUrl);
signedData = validation.addSignedTimeStamp(signedData);
}
return signedData.getEncoded();

View File

@@ -79,8 +79,6 @@ public class SigUtils {
* dictionary. Details are described in the table "Entries in the DocMDP transform parameters
* dictionary" in the PDF specification.
*
* @param doc The document.
* @param signature The signature object.
* @param accessPermissions The permission value (1, 2 or 3).
*/
static public void setMDPPermission(PDDocument doc, PDSignature signature, int accessPermissions) {
@@ -117,9 +115,6 @@ public class SigUtils {
/**
* Log if the certificate is not valid for signature usage. Doing this
* anyway results in Adobe Reader failing to validate the PDF.
*
* @param x509Certificate
* @throws CertificateParsingException
*/
public static void checkCertificateUsage(X509Certificate x509Certificate)
throws CertificateParsingException {
@@ -149,9 +144,6 @@ public class SigUtils {
/**
* Log if the certificate is not valid for timestamping.
*
* @param x509Certificate
* @throws CertificateParsingException
*/
public static void checkTimeStampCertificateUsage(X509Certificate x509Certificate)
throws CertificateParsingException {
@@ -165,9 +157,6 @@ public class SigUtils {
/**
* Log if the certificate is not valid for responding.
*
* @param x509Certificate
* @throws CertificateParsingException
*/
public static void checkResponderCertificateUsage(X509Certificate x509Certificate)
throws CertificateParsingException {
@@ -181,10 +170,6 @@ public class SigUtils {
/**
* Gets the last relevant signature in the document, i.e. the one with the highest offset.
*
* @param document to get its last signature
* @return last signature or null when none found
* @throws IOException
*/
public static PDSignature getLastRelevantSignature(PDDocument document) throws IOException {
SortedMap<Integer, PDSignature> sortedMap = new TreeMap<>();

View File

@@ -1,4 +0,0 @@
package me.hatter.tool.signpdf;
public class SignPdfMain {
}

View File

@@ -34,12 +34,6 @@ public class TSAClient {
private final String password;
private final MessageDigest digest;
/**
* @param url the URL of the TSA service
* @param username user name of TSA
* @param password password of TSA
* @param digest the message digest to use
*/
public TSAClient(URL url, String username, String password, MessageDigest digest) {
this.url = url;
this.username = username;
@@ -47,30 +41,24 @@ public class TSAClient {
this.digest = digest;
}
/**
* @param messageImprint imprint of message contents
* @return the encoded time stamp token
* @throws IOException if there was an error with the connection or data from the TSA server,
* or if the time stamp response could not be validated
*/
public byte[] getTimeStampToken(byte[] messageImprint) throws IOException {
digest.reset();
byte[] hash = digest.digest(messageImprint);
final byte[] hash = digest.digest(messageImprint);
// 32-bit cryptographic nonce
SecureRandom random = new SecureRandom();
int nonce = random.nextInt();
final SecureRandom random = new SecureRandom();
final int nonce = random.nextInt();
// generate TSA request
TimeStampRequestGenerator tsaGenerator = new TimeStampRequestGenerator();
final TimeStampRequestGenerator tsaGenerator = new TimeStampRequestGenerator();
tsaGenerator.setCertReq(true);
ASN1ObjectIdentifier oid = getHashObjectIdentifier(digest.getAlgorithm());
TimeStampRequest request = tsaGenerator.generate(oid, hash, BigInteger.valueOf(nonce));
final ASN1ObjectIdentifier oid = getHashObjectIdentifier(digest.getAlgorithm());
final TimeStampRequest request = tsaGenerator.generate(oid, hash, BigInteger.valueOf(nonce));
// get TSA response
byte[] tsaResponse = getTSAResponse(request.getEncoded());
final byte[] tsaResponse = getTSAResponse(request.getEncoded());
TimeStampResponse response;
final TimeStampResponse response;
try {
response = new TimeStampResponse(tsaResponse);
response.validate(request);
@@ -78,7 +66,7 @@ public class TSAClient {
throw new IOException(e);
}
TimeStampToken token = response.getTimeStampToken();
final TimeStampToken token = response.getTimeStampToken();
if (token == null) {
throw new IOException("Response does not have a time stamp token");
}

View File

@@ -32,24 +32,15 @@ import java.util.List;
public class ValidationTimeStamp {
private TSAClient tsaClient;
/**
* @param tsaUrl The url where TS-Request will be done.
* @throws NoSuchAlgorithmException
* @throws MalformedURLException
*/
public ValidationTimeStamp(String tsaUrl) throws NoSuchAlgorithmException, MalformedURLException {
if (tsaUrl != null) {
MessageDigest digest = MessageDigest.getInstance("SHA-256");
final MessageDigest digest = MessageDigest.getInstance("SHA-256");
this.tsaClient = new TSAClient(new URL(tsaUrl), null, null, digest);
}
}
/**
* Creates a signed timestamp token by the given input stream.
*
* @param content InputStream of the content to sign
* @return the byte[] of the timestamp token
* @throws IOException
*/
public byte[] getTimeStampToken(InputStream content) throws IOException {
return tsaClient.getTimeStampToken(IOUtils.toByteArray(content));
@@ -57,15 +48,10 @@ public class ValidationTimeStamp {
/**
* Extend cms signed data with TimeStamp first or to all signers
*
* @param signedData Generated CMS signed data
* @return CMSSignedData Extended CMS signed data
* @throws IOException
*/
public CMSSignedData addSignedTimeStamp(CMSSignedData signedData)
throws IOException {
SignerInformationStore signerStore = signedData.getSignerInfos();
List<SignerInformation> newSigners = new ArrayList<>();
public CMSSignedData addSignedTimeStamp(CMSSignedData signedData) throws IOException {
final SignerInformationStore signerStore = signedData.getSignerInfos();
final List<SignerInformation> newSigners = new ArrayList<>();
for (SignerInformation signer : signerStore.getSigners()) {
// This adds a timestamp to every signer (into his unsigned attributes) in the signature.
@@ -79,27 +65,22 @@ public class ValidationTimeStamp {
/**
* Extend CMS Signer Information with the TimeStampToken into the unsigned Attributes.
*
* @param signer information about signer
* @return information about SignerInformation
* @throws IOException
*/
private SignerInformation signTimeStamp(SignerInformation signer)
throws IOException {
AttributeTable unsignedAttributes = signer.getUnsignedAttributes();
private SignerInformation signTimeStamp(SignerInformation signer) throws IOException {
final AttributeTable unsignedAttributes = signer.getUnsignedAttributes();
ASN1EncodableVector vector = new ASN1EncodableVector();
if (unsignedAttributes != null) {
vector = unsignedAttributes.toASN1EncodableVector();
}
byte[] token = tsaClient.getTimeStampToken(signer.getSignature());
ASN1ObjectIdentifier oid = PKCSObjectIdentifiers.id_aa_signatureTimeStampToken;
ASN1Encodable signatureTimeStamp = new Attribute(oid,
final byte[] token = tsaClient.getTimeStampToken(signer.getSignature());
final ASN1ObjectIdentifier oid = PKCSObjectIdentifiers.id_aa_signatureTimeStampToken;
final ASN1Encodable signatureTimeStamp = new Attribute(oid,
new DERSet(ASN1Primitive.fromByteArray(token)));
vector.add(signatureTimeStamp);
Attributes signedAttributes = new Attributes(vector);
final Attributes signedAttributes = new Attributes(vector);
// There is no other way changing the unsigned attributes of the signer information.
// result is never null, new SignerInformation always returned,

View File

@@ -0,0 +1,58 @@
package me.hatter.tool.signpdf.main;
import me.hatter.tool.signpdf.CreateSignature;
import me.hatter.tool.signpdf.options.SignOptions;
import me.hatter.tools.commons.security.cert.X509CertUtil;
import me.hatter.tools.commons.security.key.KeyUtil;
import java.io.File;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.cert.X509Certificate;
import java.util.List;
public class SignPdfMain {
public static void main(String[] args) throws Exception {
final SignOptions signOptions = new SignOptions();
signOptions.setName("Example User");
signOptions.setLocation("Hangzhou, ZJ");
signOptions.setReason("Testing");
final String tsaUrl = "https://hatter.ink/ca/sign_timestamp.action";
final List<X509Certificate> certs = X509CertUtil.parseX509CertificateList(
"-----BEGIN CERTIFICATE-----\n" +
"I6Syqg==\n" +
"-----END CERTIFICATE-----\n" +
"-----BEGIN CERTIFICATE-----\n" +
"O1XW8Bs24gI=\n" +
"-----END CERTIFICATE-----\n" +
"-----BEGIN CERTIFICATE-----\n" +
"UhQrqeqObukNbcGgWhwy3+PysJFky6peB6IexebbVYk=\n" +
"-----END CERTIFICATE-----");
final PrivateKey privateKey = KeyUtil.parsePrivateKeyPEM("-----BEGIN PRIVATE KEY-----\n" +
"MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCVlt5k68ngIZ3T\n" +
"xSGBfudXkCooM8/cjEefv/cI\n" +
"-----END PRIVATE KEY-----");
final X509Certificate[] chain = new X509Certificate[]{certs.get(0)};
final KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
keyStore.load(null);
for (X509Certificate c : certs) {
keyStore.setCertificateEntry(c.getSubjectX500Principal().getName(), c);
}
keyStore.setKeyEntry("", privateKey, new char[0], chain);
System.out.println(keyStore);
final CreateSignature signing = new CreateSignature(signOptions, keyStore, new char[0]);
// TODO signing.setExternalSigning(true);
File inFile = new File("/Users/hatterjiang/07-057r7_Web_Map_Tile_Service_Standard.pdf");
final String name = inFile.getName();
final String substring = name.substring(0, name.lastIndexOf('.'));
final File outFile = new File(inFile.getParent(), substring + "_signed.pdf");
signing.signDetached(inFile, outFile, tsaUrl);
}
}

View File

@@ -0,0 +1,31 @@
package me.hatter.tool.signpdf.options;
public class SignOptions {
private String name;
private String location;
private String reason;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getLocation() {
return location;
}
public void setLocation(String location) {
this.location = location;
}
public String getReason() {
return reason;
}
public void setReason(String reason) {
this.reason = reason;
}
}