Since this is the first implementation of XML signature, I decided to proceed while leaving various notes.
--I want to sign XML in Java --The format of the digital certificate is PKCS12 format
The above operation is required every time the digital certificate is replaced.
What is "normalization" of digital certificates? http://www.atmarkit.co.jp/fxml/tanpatsu/16xmlsecurity/xmlsecurity02.html
RSA key and certificate file format https://qiita.com/kunichiko/items/12cbccaadcbf41c72735
Extract private key, certificate, intermediate CA from PKCS # 12 file https://qiita.com/cs_sonar/items/f2753ffdebb1c67d5302
How to create a self-signed certificate https://qiita.com/akito1986/items/8eb41f5a43bb9421ae79
XML signature using Java's XML digital signature API http://qiita.com/KevinFQ/items/4e2484a659b618530e72
keytool command list http://itref.fc2web.com/java/keytool.html
[Java] Add / delete certificates from the keystore in Java https://blogs.yahoo.co.jp/dk521123/37097725.html
Convert from server certificate pem to crt
$ openssl x509 -outform der in server-crt.pem -out server-crt.crt
Server certificate pem ⇒ Convert to p12
$ openssl pkcs12 -export -out server-crt.p12 -in server-crt.pem -inkey server-privatekey.pem -passin pass:root -passout pass:root
Server certificate p12 ⇒ Converted to crt
$ openssl pkcs12 -in server-crt.p12 -clcerts -nokeys -out server-crt.crt
Server certificate p12 ⇒ Convert to cer
$ openssl pkcs12 -in server-crt.p12 -des -out server-crt.cer
https://jp.globalsign.com/support/faq/331.html
Command reference: https://docs.oracle.com/javase/jp/6/technotes/tools/windows/keytool.html
It seems that Java will not be able to update automatically if cacerts changes. Backup required. http://d.hatena.ne.jp/uunfo/20110813/1313242092
keytool -genkeypair -keysize 2048 -keyalg RSA -sigalg SHA256withRSA -alias testcrt -keystore "C:\Program Files\Java\jreX.X.X_XXX\lib\security\cacerts" -storepass changeit
** Added 2017/10/16 ** Create a keystore file anywhere (When the common name is test1, the organizational unit is test2, the organization is test3, and the two-letter country code is JP)
$ keytool -genkey -dname "cn=test1, ou=test2, o=test3, c=JP" -keystore (keystore filename.keystore) -alias (name that identifies the certificate / key in the KeyStore)-keypass (certificate / key password * 6 characters or more)-storepass (keystore password)-keyalg RSA -keysize 2048 -validity 10000
Reference (per key pair generation) https://docs.oracle.com/javase/jp/8/docs/technotes/tools/unix/keytool.html
I thought it would be a good idea to leave the certificate exposed as an external resource, so I considered importing the certificate into the keystore.
Import cer file into keystore
When I try to import a crt file with the following command, I get an error saying "keytool error: java.lang.Exception: Input is not an X.509 certificate". Import with cer.
$ keytool -import -alias testkey -keystore "C:\Program Files\Java\jreX.X.X_XXX\lib\security\cacerts" -file server-crt.cer
Import p12 file into keystore http://blogger.fastriver.net/2014/10/keytool.html
If you do not specify an alias (distinguishing name), it will be imported with the name "1" by default. If you want to add an alias, add an option such as "-srcalias 1 -destalias (arbitrary alias name)".
$ keytool -importkeystore -keystore "C:\Program Files\Java\jreX.X.X_XXX\lib\security\cacerts" -srckeystore server-crt.p12 -srcstoretype PKCS12 -srcalias 1 -destalias testcrt -srcstorepass root -deststorepass changeit
Check the imported certificate
$ keytool -list -v -alias testcrt -keystore "C:\Program Files\Java\jreX.X.X_XXX\lib\security\cacerts" -keypass root -storepass changeit
$ keytool -delete -alias testcrt -keystore "C:\Program Files\Java\jreX.X.X_XXX\lib\security\cacerts" -keypass root -storepass changeit
$ keytool -changealias -srcalias 11111111 -destalias 22222222 -keystore "C:\Program Files\Java\jreX.X.X_XXX\lib\security\cacerts" -keypass root -storepass changeit
$ keytool -selfcert -alias testcrt -validity 2000 -keystore "C:\Program Files\Java\jreX.X.X_XXX\lib\security\cacerts" -keypass root -storepass changeit
createSignedXml.java
public String createSignedXml(String fileName)
throws Exception {
//Create Document instance
DocumentBuilder documentBuilder = null;
documentBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
Document document = documentBuilder.newDocument();
//DOM generation of XML document (omitted)
//XML signature creation
//p12 file path
File certDir = "XXXXXX";
//Certificate file list under the certificate directory (assuming that only one file exists)
File[] certP12List = certDir.listFiles((FileFilter) new SuffixFileFilter(".p12"));
//Certificate file
File certP12File = null;
//Get the path of the keystore file
String keyStoreFilePath = "/usr/java/jdkX.X.X_XXX/jre/lib/security/testkeystore";
//Get keystore file
File keyStoreFile = new File(keyStoreFilePath);
//Keystore password
String keyStorePass = "root";
//Certificate password
String certificatePass = "changeit";
//Error if multiple p12 files are placed
if (certP12List.length == 1) {
certP12File = certP12List[0];
} else {
throw new IOException();
}
try {
//Get the keystore
KeyStore keyStore = getKeyStore(keyStoreFile, keyStorePass);
//Distinguished name: Obtain a certificate matching XXX from the keystore
X509Certificate x509cert = getCertificate(keyStore, XXX);
//Certificate import judgment flag
boolean importFlg = false;
//For expiration date check
// 0:Imported certificate is within expiration date, 1:Imported certificate has expired,
// 2:Certificate of external resource has expired, 3:Both have expired, 4:Import error
int checkResult = 0;
if (x509cert != null) {
//Expiration check for certificates located on external resources and imported certificates
checkResult = checkCertificateExpired(x509cert, XXX, keyStoreFile, certP12File, keyStorePass, certificatePass);
//Imported certificate has expired
if (checkResult == 1) {
//Distinguished name: XXX certificate deletion
deleteProcessExec(XXX, keyStoreFile, certificatePass, keyStorePass);
importFlg = true;
}
} else {
importFlg = true;
}
//If the certificate imported into the keystore has expired
//Re-import new certificate
if (importFlg) {
//Perform certificate import
importProcessExec(keyStoreFile, XXX, certP12File, certificatePass, keyStorePass);
//Reacquire the keystore
keyStore = getKeyStore(keyStoreFile, keyStorePass);
//Distinguished name: Re-obtain a certificate matching XXX from the keystore
x509cert = getCertificate(keyStore, XXX);
//Expiration date check
checkResult = checkCertificateExpired(x509cert, keyStoreFile, certP12File, keyStorePass, certificatePass);
}
//Both the certificate placed in the external resource and the imported certificate have expired.
//External resource certificate import error
if (checkResult >= 3) {
//Distinguished name: XXX certificate deletion
deleteProcessExec(XXX, keyStoreFile, certificatePass, keyStorePass);
throw new CertificateException();
}
//Get the private key
RSAPrivateKey privateKey = (RSAPrivateKey) keyStore.getKey(XXX, certificatePassArray);
//Creating a signature context
DOMSignContext dsc = new DOMSignContext(privateKey, document.getDocumentElement());
XMLSignatureFactory fac = XMLSignatureFactory.getInstance("DOM");
//Creating a reference element
Reference ref = fac.newReference
("#XXXXXX", fac.newDigestMethod(DigestMethod.SHA256, null),
Collections.singletonList
(fac.newTransform(Transform.ENVELOPED,
(TransformParameterSpec) null)), null, null);
//Creating signature information
SignedInfo si = fac.newSignedInfo
(fac.newCanonicalizationMethod
(CanonicalizationMethod.INCLUSIVE_WITH_COMMENTS,
(C14NMethodParameterSpec) null),
fac.newSignatureMethod("http://www.w3.org/2001/04/xmldsig-more#rsa-sha256", null),
Collections.singletonList(ref));
KeyInfoFactory kif = fac.getKeyInfoFactory();
X509Data x509Data = kif.newX509Data(Collections.singletonList(x509cert));
KeyInfo ki = kif.newKeyInfo(Collections.singletonList(x509Data));
//Sign.
XMLSignature signature = fac.newXMLSignature(si, ki);
signature.sign(dsc);
} catch (XMLSignatureException e) {
throw new XMLSignatureException("An error occurred during XML signature generation or validation process", e);
} catch (MarshalException e) {
throw new MarshalException(e);
} catch (InvalidAlgorithmParameterException e) {
throw new InvalidAlgorithmParameterException(e);
} catch (NoSuchAlgorithmException e) {
throw new NoSuchAlgorithmException(e);
} catch (KeyStoreException e) {
throw new KeyStoreException(e);
} catch (CertificateException e) {
throw new CertificateException(e);
} catch (UnrecoverableKeyException e) {
throw new UnrecoverableKeyException();
}
return this.write(document);
}
/**
*Get the keystore
*
* @param keyStoreFile Keystore file
* @param keyStorePass Keystore password
* @return keyStore keystore object
*/
private KeyStore getKeyStore(File keyStoreFile, String keyStorePass) {
KeyStore keyStore = null;
try (FileInputStream keyStoreStream = new FileInputStream(keyStoreFile)) {
//Keystore type:JKS(For PKCS12, take the argument"PKCS12"To fix)
keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
keyStore.load(keyStoreStream, keyStorePass.toCharArray());
} catch (KeyStoreException | NoSuchAlgorithmException | CertificateException | IOException e) {
//TODO auto-generated catch block
e.printStackTrace();
}
return keyStore;
}
/**
*Obtain a certificate that matches the distinguished name from the keystore
*
* @param keyStore keystore object
* @param alias Distinguished name
* @return certificate certificate object
*/
private X509Certificate getCertificate(KeyStore keyStore, String alias){
X509Certificate certificate = null;
try {
certificate = (X509Certificate) keyStore.getCertificate(alias);
} catch (KeyStoreException e) {
//TODO auto-generated catch block
e.printStackTrace();
}
return certificate;
}
/**
*Certificate expiration check
*
* @param x509cert X509 certificate object
* @param keyStoreFile Keystore file
* @param certFile Certificate file
* @param keyStorePass Keystore password
* @param certificatePass Certificate password
* @param srcAlias Temporary distinguished name immediately after import
* @return 0:Imported certificate is within expiration date, 1:Imported certificate has expired,
* 2:Certificate of external resource has expired, 3:Both have expired, 4:External resource certificate import error
*/
private int checkCertificateExpired(X509Certificate x509cert,
File keyStoreFile, File certFile, String keyStorePass, String certificatePass,
String srcAlias) {
//Processing result
int result = 0;
//Expiration flag for imported certificate
// true:Expired, false:Within the expiration date
boolean importExpiredFlg = false;
//Expiration flag for certificates placed on external resources
boolean extExpiredFlg = false;
//Get the expiration date of the certificate imported into the keystore
Date importExpiredDate = x509cert.getNotAfter();
//Current date and time
Date now = new Date();
/**************************Expiration date check of imported certificate START**************************/
//Determine if the imported certificate has expired
if (now.compareTo(importExpiredDate) > 0) {
//If it has expired
importExpiredFlg = true;
result = 1;
} else {
//Since it is within the expiration date, no further checks are required
return 0;
}
/***************************Expiration date check of imported certificate END***************************/
/***********************Expiration check for certificates placed on external resources START**********************/
try {
//Temporary Distinguished Name
String destAlias = System.currentTimeMillis();
//Import command execution
importProcessExec(keyStoreFile, destAlias, certFile, certificatePass, keyStorePass);
//Get the keystore
KeyStore tmpKeyStore = getKeyStore(keyStoreFile, keyStorePass);
//Distinguished name: Obtain a certificate that matches destAlias from the keystore
X509Certificate tmpCert = getCertificate(tmpKeyStore, destAlias);
//Get the expiration date of the imported certificate
Date extExpiredDate = null;
if (tmpCert != null) {
extExpiredDate = tmpCert.getNotAfter();
} else {
//Import error
return 4;
}
//Compare the current time with the certificate expiration date of the external resource
if (now.compareTo(extExpiredDate) > 0) {
//If it has expired
extExpiredFlg = true;
result = 2;
}
//Execute command to delete temporarily imported certificate
deleteProcessExec(destAlias, keyStoreFile, certificatePass, keyStorePass);
} catch(InterruptedException e) {
//TODO auto-generated catch block
e.printStackTrace();
}
/**********************Expiration check END for certificates placed on external resources*********************/
//Imported certificates and certificates placed on external resources
//If both have expired, result:3
if (importExpiredFlg && extExpiredFlg) {
result = 3;
}
return result;
}
/**
*Execute the certificate deletion command.
*
* @param alias Distinguished name
* @param keyStoreFile Keystore file
* @param certificatePass Certificate password
* @param keyStorePass Keystore password
* @return true:Successful completion, false:Abnormal termination
* @throws InterruptedException
*/
private boolean deleteProcessExec(String alias, File keyStoreFile, String certificatePass,
String keyStorePass)
throws InterruptedException {
//Get the command to delete the certificate imported into the keystore
List<String> command = getDeleteCommand(alias, keyStoreFile, certificatePass, keyStorePass);
//Delete command execution
return processExec(command);
}
/**
*Get the certificate deletion command.
*
* @param alias Distinguished name
* @param keyStoreFile Keystore file
* @param certificatePass Certificate password
* @param keyStorePass Keystore password
* @return resultList delete command
*/
private List<String> getDeleteCommand(String alias, File keyStoreFile, String certificatePass,
String keyStorePass) {
//Command to delete the certificate imported into the keystore
String command = "keytool -delete -alias %alias% -keystore %keyStoreFilePath% -keypass %certificatePass% -storepass %keyStorePass%";
//Split the command into a string array
String[] commandList = command.split(" ");
List<String> resultList = new ArrayList<String>();
for (String cmd : commandList) {
switch (cmd) {
case "%alias%":
cmd = cmd.replace("%alias%", alias);
break;
case "%keyStoreFilePath%":
cmd = cmd.replace("%keyStoreFilePath%", keyStoreFile.getPath());
break;
case "%certificatePass%":
cmd = cmd.replace("%certificatePass%", certificatePass);
break;
case "%keyStorePass%":
cmd = cmd.replace("%keyStorePass%", keyStorePass);
break;
}
resultList.add(cmd);
}
return resultList;
}
/**
*Execute the certificate import command.
*
* @param certFilePath Certificate file
* @param destAlias Distinguished name
* @param keyStoreFilePath Keystore file
* @param certificatePass Certificate password
* @param keyStorePass Keystore password
* @return true:Successful completion, false:Abnormal termination
* @throws InterruptedException
*/
private boolean importProcessExec(File keyStoreFile, String destAlias, File certFile,
String certificatePass,
String keyStorePass) throws InterruptedException {
//Command to import certificate
List<String> command = getImportCommand(keyStoreFile, destAlias, certFile, certificatePass,
keyStorePass);
//Import command execution
return processExec(command);
}
/**
*Get the certificate import command.
*
* @param certFilePath Certificate file
* @param destAlias Distinguished name
* @param keyStoreFilePath Keystore file
* @param certificatePass Certificate password
* @param keyStorePass Keystore password
* @return resultList import command
*/
private List<String> getImportCommand(File keyStoreFile, String destAlias, File certFile,
String certificatePass, String keyStorePass) {
//Command to import certificate
String command = "keytool -importkeystore -keystore %keyStoreFilePath% -srckeystore %certFilePath% -srcstoretype PKCS12 -srcalias 1 -destalias %destalias% -srcstorepass %certificatePass% -deststorepass %keyStorePass%";
//Split the command into a string array
String[] commandList = command.split(" ");
List<String> resultList = new ArrayList<String>();
for (String cmd : commandList) {
switch (cmd) {
case "%keyStoreFilePath%":
cmd = cmd.replace("%keyStoreFilePath%", keyStoreFile.getPath());
break;
case "%certFilePath%":
cmd = cmd.replace("%certFilePath%", certFile.getPath());
break;
case "%destalias%":
cmd = cmd.replace("%destalias%", destAlias);
break;
case "%certificatePass%":
cmd = cmd.replace("%certificatePass%", certificatePass);
break;
case "%keyStorePass%":
cmd = cmd.replace("%keyStorePass%", keyStorePass);
break;
}
resultList.add(cmd);
}
return resultList;
}
/**
*Run an external process.
*
* @param command Command contents
* @return true:Successful completion, false:Abnormal termination
*/
private boolean processExec(List<String> command) {
//Processing result
boolean result = false;
try {
ProcessBuilder processBuilder = new ProcessBuilder(command);
Process Process = processBuilder.start();
//Wait until the process ends normally
if (Process.waitFor() == 0) {
result = true;
log.info("Process Success: " + command.toString());
} else {
log.warn("Process Failed: " + command.toString());
}
//Standard output
String strInput;
BufferedReader ipbr = new BufferedReader(new InputStreamReader(Process.getInputStream()));
while((strInput = ipbr.readLine()) != null) {
log.info(strInput);
}
ipbr.close();
//Error output
String strErr;
BufferedReader erbr = new BufferedReader(new InputStreamReader(Process.getErrorStream()));
while((strErr = erbr.readLine()) != null) {
log.info(strErr);
}
erbr.close();
//InputStream in the background after using ProcessBuilder, OutputStream,ErrorStream is opened.
//Close all streams to avoid running out of resources.
Process.getInputStream().close();
Process.getOutputStream().close();
Process.getErrorStream().close();
} catch (InterruptedException | IOException e) {
//TODO auto-generated catch block
e.printStackTrace();
}
return result;
}
Recommended Posts