How can we digitally sign a legacy binary MS-Office document (doc, xls, ppt) in Apache POI, or any other open source library?
The Open XML formats are covered at How to
I was able to sign .doc file by creating detached xml signature, then adding it under root directory using POIFSFileSystem, example is below :
public class OfficeDocumentSigner2 {
public static void main(String[] args) {
signClassicOfficeDocuments();
}
private static String sign() {
// First, create a DOM XMLSignatureFactory that will be used to
// generate the XMLSignature and marshal it to DOM.
XMLSignatureFactory fac = XMLSignatureFactory.getInstance("DOM");
OutputStream os = null;
String signedDoc = "C:\\Users\\Desktop\\digitalSign\\signdoc_signed.xml";
try {
// Create a Reference to an external URI that will be digested
// using the SHA1 digest algorithm
Reference ref = fac.newReference(officeFilePath, fac.newDigestMethod(DigestMethod.SHA1, null));
// Create the SignedInfo
SignedInfo si = fac.newSignedInfo(fac.newCanonicalizationMethod(CanonicalizationMethod.INCLUSIVE_WITH_COMMENTS, (C14NMethodParameterSpec) null), fac.newSignatureMethod(SignatureMethod.RSA_SHA1, null),
Collections.singletonList(ref));
// Create the Document that will hold the resulting XMLSignature --> detached
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setNamespaceAware(true); // must be set
Document doc = dbf.newDocumentBuilder().newDocument();
// certificate info
File file = new File("C:\\Users\\Desktop\\digitalSign\\test101\\KeyStore.jks");
// extracting private key and certificate
String alias = "certAlias";
X509Certificate x509 = null;
// loading the keystore
KeyStore keystore = KeyStore.getInstance("JKS");
FileInputStream fis = new FileInputStream(file);
keystore.load(fis, password);
fis.close();
x509 = (X509Certificate) keystore.getCertificate(alias);
KeyStore.PrivateKeyEntry keyEntry = (KeyStore.PrivateKeyEntry) keystore.getEntry(alias, new KeyStore.PasswordProtection(password));
// Create the KeyInfo containing the X509Data.
// ref : http://www.oracle.com/technetwork/articles/javase/dig-signature-api-140772.html
KeyInfoFactory kif = fac.getKeyInfoFactory();
List x509Content = new ArrayList();
x509Content.add(x509.getSubjectX500Principal().getName());
x509Content.add(x509);
X509Data xd = kif.newX509Data(x509Content);
KeyInfo ki = kif.newKeyInfo(Collections.singletonList(xd));
// Create a DOMSignContext and specify the DSA PrivateKey and
// location of the resulting XMLSignature's parent element
DOMSignContext dsc = new DOMSignContext(keyEntry.getPrivateKey(), doc);
dsc.setBaseURI(baseURI);
// Create the XMLSignature (but don't sign it yet)
XMLSignature signature = fac.newXMLSignature(si, ki);
// Marshal, generate (and sign) the enveloped signature
signature.sign(dsc);
// Output the resulting document.
os = new FileOutputStream(signedDoc);
TransformerFactory tf = TransformerFactory.newInstance();
Transformer trans = tf.newTransformer();
trans.transform(new DOMSource(doc), new StreamResult(os));
} catch (Exception e) {
e.printStackTrace();
}
return signedDoc;
}
public static void signClassicOfficeDocuments() {
InputStream is;
try {
//sign document
String signaturePath = sign();
InputStream signatureAsIS = new FileInputStream(signaturePath);
is = new FileInputStream(fileName);
POIFSFileSystem poifs = new POIFSFileSystem(is);
DirectoryEntry dirEntry = poifs.createDirectory("_xmlsignatures"); // create a new DirectoryEntry in the root directory
dirEntry.createDocument("9149", signatureAsIS);
String destPath = "C://Users//Desktop//digitalSign//test_11_24_signedByMe.doc";
OutputStream os = new FileOutputStream(destPath);
poifs.writeFilesystem(os);
} catch (Exception e) {
e.printStackTrace();
}
}
}