Convert SSL .pem to .p12 with or without OpenSSL - java

I get external .pem files that need to be converted to .p12 files - I add a username and password in the process. (I need to do this to utilize a third party API.)
Using openssl, the command is...
openssl pkcs12 -export -in xxxx.pem -inkey xxxx.pem -out xxx.p12 -passout pas:newpassword -name "newname"
I can run this from a terminal session and it works perfectly.
However, I will need to do this often and have written a Java class that handles this and more (my application is mostly .jsp with Tomcat and Apache). When I try run the same command from Java using Runtime.exec, I get the dreaded "unable to write 'random state'" error ( Using OpenSSL what does "unable to write 'random state'" mean? ).
I assume that the difference is that, when I run from Java, the user is not "root".
So, is there a better way to convert from pem to .p12 using a Java library rather than executing a command line program (i.e. openssl)?
Otherwise, I guess I need to do some configuration on my server. I can not find any .md file anywhere on the server. The only openssl.cnf file is in a weird directory (/etc/pki/tls). Do I need to create a new openssl.cnf file somewhere else?

This should do what you want to do (using the BouncyCastle PEMReader as suggested above) -- take a PEM-encoded private key + certificate, and output a PKCS#12 file. Uses the same password for the PKCS12 that was used to protect the private key.
public static byte[] pemToPKCS12(final String keyFile, final String cerFile, final String password) throws Exception {
// Get the private key
FileReader reader = new FileReader(keyFile);
PEMReader pem = new PEMReader(reader, new PasswordFinder() {
#Override public char[] getPassword() {
return password.toCharArray();
}
});
PrivateKey key = ((KeyPair)pem.readObject()).getPrivate();
pem.close();
reader.close();
// Get the certificate
reader = new FileReader(cerFile);
pem = new PEMReader(reader);
X509Certificate cert = (X509Certificate)pem.readObject();
pem.close();
reader.close();
// Put them into a PKCS12 keystore and write it to a byte[]
ByteArrayOutputStream bos = new ByteArrayOutputStream();
KeyStore ks = KeyStore.getInstance("PKCS12");
ks.load(null);
ks.setKeyEntry("alias", (Key)key, password.toCharArray(), new java.security.cert.Certificate[]{cert});
ks.store(bos, password.toCharArray());
bos.close();
return bos.toByteArray();
}

Based on #MugglesMerriweather 's answer, an updated version to v1.51 is the following:
public static byte[] convertPEMToPKCS12(final String keyFile, final String cerFile,
final String password)
throws IOException, CertificateException, KeyStoreException, NoSuchAlgorithmException
{
// Get the private key
FileReader reader = new FileReader(keyFile);
PEMParser pem = new PEMParser(reader);
PEMKeyPair pemKeyPair = ((PEMKeyPair)pem.readObject());
JcaPEMKeyConverter jcaPEMKeyConverter = new JcaPEMKeyConverter().setProvider("SC");
KeyPair keyPair = jcaPEMKeyConverter.getKeyPair(pemKeyPair);
PrivateKey key = keyPair.getPrivate();
pem.close();
reader.close();
// Get the certificate
reader = new FileReader(cerFile);
pem = new PEMParser(reader);
X509CertificateHolder certHolder = (X509CertificateHolder) pem.readObject();
java.security.cert.Certificate X509Certificate =
new JcaX509CertificateConverter().setProvider("SC")
.getCertificate(certHolder);
pem.close();
reader.close();
// Put them into a PKCS12 keystore and write it to a byte[]
ByteArrayOutputStream bos = new ByteArrayOutputStream();
KeyStore ks = KeyStore.getInstance("PKCS12");
ks.load(null);
ks.setKeyEntry("alias", (Key) key, password.toCharArray(),
new java.security.cert.Certificate[]{X509Certificate});
ks.store(bos, password.toCharArray());
bos.close();
return bos.toByteArray();
}

In Java, use Bouncycastle but be warned, learning curve is steep and documentation scarce. I strongly recommend you look at the examples which are available as part of the source distribution
Start with the PemReader.

Based on the answers i created a java 7 class, which handles everything for creating a valid SSLContext. Also it creates the necessary chain. TODO: Trustmanager if necessary.
public final class SSL_Context {
private static SSL_Context instance = new SSL_Context();
public static SSL_Context getInstance() {
return instance;
}
private SSLContext sslContext = null;
private SSL_Context() {
try {
sslContext = generateSSLContext();
}
catch (Exception e)
{
ErrorLogger.logException(e);
}
}
final private void dumpKeyStore(KeyStore keyStore)
{
try {
// List the aliases
Enumeration aliases = keyStore.aliases();
for (; aliases.hasMoreElements(); ) {
String alias = (String) aliases.nextElement();
// Does alias refer to a private key?
boolean a = keyStore.isKeyEntry(alias);
// Does alias refer to a trusted certificate?
boolean b = keyStore.isCertificateEntry(alias);
ErrorLogger.log(alias + " " + a + " " + b, 2);
}
} catch (Exception e) {
ErrorLogger.logException(e);
}
}
final private KeyStore convertPEMToPKCS12(final String keyAndPubFile, final String chainFile, final String password) {
try {
Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
PrivateKey key;
Certificate pubCert;
try (FileReader reader = new FileReader(keyAndPubFile);
PEMParser pem = new PEMParser(reader)) {
PEMKeyPair pemKeyPair = ((PEMKeyPair) pem.readObject());
JcaPEMKeyConverter jcaPEMKeyConverter = new JcaPEMKeyConverter().setProvider("BC");
KeyPair keyPair = jcaPEMKeyConverter.getKeyPair(pemKeyPair);
key = keyPair.getPrivate();
X509CertificateHolder certHolder = (X509CertificateHolder) pem.readObject();
pubCert = new JcaX509CertificateConverter().setProvider("BC").getCertificate(certHolder);
}
// Get the certificates
try (FileReader reader = new FileReader(chainFile);
PEMParser pem = new PEMParser(reader)) {
//load all certs
LinkedList<Certificate> certsll = new LinkedList<>();
X509CertificateHolder certHolder = (X509CertificateHolder) pem.readObject();
do {
Certificate X509Certificate = new JcaX509CertificateConverter().setProvider("BC").getCertificate(certHolder);
certsll.add(X509Certificate);
}
while ((certHolder = (X509CertificateHolder) pem.readObject()) != null);
Certificate[] chain = new Certificate[certsll.size()+1];
chain[0] = pubCert;
KeyStore ks = KeyStore.getInstance("PKCS12");
ks.load(null);
int i = 1;
for (Certificate cert : certsll) {
ks.setCertificateEntry("chain" + i, cert);
chain[i] = ks.getCertificate("chain" + i);
i++;
}
ks.setKeyEntry("cert", key, password.toCharArray(), chain);
return ks;
}
}
catch (Exception e)
{
ErrorLogger.logException(e);
}
return null;
}
final private SSLContext generateSSLContext()
{
String keyStorePassword = "";
try {
KeyStore keyStore = convertPEMToPKCS12("ssl/keyandcert.pem", "ssl/ca_bundle.crt", keyStorePassword);
SSLContext sslContext = SSLContext.getInstance("TLSv1");
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
keyManagerFactory.init(keyStore, keyStorePassword.toCharArray());
sslContext.init(keyManagerFactory.getKeyManagers(), null, new SecureRandom());
return sslContext;
} catch (Exception e) {
ErrorLogger.logException(e);
}
return null;
}
final public SSLContext getContext() {
return sslContext;
}
final public static void main(String args[])
{
getInstance().getContext();
}
}

This solutions is an adaptation of #sascha-arthur's to accomodate for:
Handles edge-case where PrivateKey format is not as expected.
Gracefully handle scenario where public key is not available
Fixed a few minor redundancies and formatting
The code:
String alias="myalias";
char[] password = "mypassword".toCharArray();
// Private Key
PEMParser pem = new PEMParser(new FileReader(keyFile));
Object parsedObject = pem.readObject();
PrivateKeyInfo privateKeyInfo = parsedObject instanceof PEMKeyPair ? ((PEMKeyPair)parsedObject).getPrivateKeyInfo() : (PrivateKeyInfo)parsedObject;
PKCS8EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec(privateKeyInfo.getEncoded());
KeyFactory factory = KeyFactory.getInstance("RSA");
PrivateKey key = factory.generatePrivate(privateKeySpec);
List<X509Certificate> certs = new ArrayList<>();
X509CertificateHolder certHolder = (X509CertificateHolder)pem.readObject();
if(certHolder != null) {
certs.add(new JcaX509CertificateConverter().setProvider("BC").getCertificate(certHolder));
}
// Certificate
pem = new PEMParser(new FileReader(certFile));
while((certHolder = (X509CertificateHolder)pem.readObject()) != null) {
certs.add(new JcaX509CertificateConverter().setProvider("BC").getCertificate(certHolder));
}
// Keystore
KeyStore ks = KeyStore.getInstance("PKCS12");
ks.load(null);
for (int i = 0; i < certs.size(); i++) {
ks.setCertificateEntry(alias + "_" + i, certs.get(i));
}
KeyStore keyStore = KeyStore.getInstance("PKCS12");
keyStore.load(null);
keyStore.setKeyEntry(alias, key, password, certs.toArray(new X509Certificate[certs.size()]));
For this to work with a LetsEncrypt certificate, you'll need to use the following files:
privkey.pem
fullchain.pem

Related

Java SSLContext setup not working, SunCertPathBuilderException

I have a working stunnel setup and am trying to use the same pem file containing the client certificate and client key and the server trust X509Certificate in a .crt file.
I consistently get this exception when it tries to write the first heartbeat packet.
javax.net.ssl.SSLHandshakeException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
at java.base/sun.security.ssl.Alert.createSSLException(Alert.java:131)
at java.base/sun.security.ssl.TransportContext.fatal(TransportContext.java:353)
at java.base/sun.security.ssl.TransportContext.fatal(TransportContext.java:296)
at java.base/sun.security.ssl.TransportContext.fatal(TransportContext.java:291)
at java.base/sun.security.ssl.CertificateMessage$T12CertificateConsumer.checkServerCerts(CertificateMessage.java:654)
at java.base/sun.security.ssl.CertificateMessage$T12CertificateConsumer.onCertificate(CertificateMessage.java:473)
at java.base/sun.security.ssl.CertificateMessage$T12CertificateConsumer.consume(CertificateMessage.java:369)
at java.base/sun.security.ssl.SSLHandshake.consume(SSLHandshake.java:392)
at java.base/sun.security.ssl.HandshakeContext.dispatch(HandshakeContext.java:443)
at java.base/sun.security.ssl.SSLEngineImpl$DelegatedTask$DelegatedAction.run(SSLEngineImpl.java:1074)
at java.base/sun.security.ssl.SSLEngineImpl$DelegatedTask$DelegatedAction.run(SSLEngineImpl.java:1061)
at java.base/java.security.AccessController.doPrivileged(Native Method)
at java.base/sun.security.ssl.SSLEngineImpl$DelegatedTask.run(SSLEngineImpl.java:1008)
at tlschannel.impl.TlsChannelImpl.handleTask(TlsChannelImpl.java:232)
at tlschannel.impl.TlsChannelImpl.handshakeLoop(TlsChannelImpl.java:570)
at tlschannel.impl.TlsChannelImpl.writeAndHandshake(TlsChannelImpl.java:540)
at tlschannel.impl.TlsChannelImpl.doHandshake(TlsChannelImpl.java:511)
at tlschannel.impl.TlsChannelImpl.handshake(TlsChannelImpl.java:493)
at tlschannel.impl.TlsChannelImpl.write(TlsChannelImpl.java:362)
at tlschannel.ClientTlsChannel.write(ClientTlsChannel.java:164)
at tlschannel.ClientTlsChannel.write(ClientTlsChannel.java:169)
at tlschannel.ClientTlsChannel.write(ClientTlsChannel.java:174)
Note I am certain this is the same .crt file used by stunnel successfully.
Here is the code that is used to construct the SSLContext with trust and client certificate and key.
// initialize the SSLContext, a configuration holder, reusable object
SSLContext sslContext = SSLContext.getInstance("TLSv1.2");
// Get the location of the key and cert file and read it in
HashMap<String, List<?>> keys = this.readPem(pemPath);
// initialize the key and trust managers
PrivateKey key = (PrivateKey) keys.get("keys").get(0);
List<X509Certificate> certs = (List<X509Certificate>)keys.get("certs");
X509Certificate[] certsArray = new X509Certificate[certs.size()];
certs.toArray(certsArray);
// We use the pemPath as the keystore alias for this key and certs
KeyManager[] km = createKeyStore(key, certsArray, pemPath);
TrustManager[] tm = createTrustStore(trustPath);
sslContext.init(km, tm, null);
final InetSocketAddress isa = new InetSocketAddress(InetAddress.getByName(host), port);
socketChannel = SocketChannel.open();
socketChannel.setOption(StandardSocketOptions.TCP_NODELAY, true);
socketChannel.connect(isa);
socketChannel.setOption(StandardSocketOptions.SO_KEEPALIVE, true);
socketChannel.configureBlocking(true);
// create TlsChannel builder, combining the raw channel
// and the SSLEngine, using minimal options
ClientTlsChannel.Builder builder = ClientTlsChannel.newBuilder(socketChannel, sslContext);
tlsChannel = builder.build();
Here is the readPem method:
private HashMap<String, List<?>> readPem(String filePath) throws FileNotFoundException, IOException,
CertificateException, KeyStoreException, NoSuchAlgorithmException, InvalidKeySpecException {
HashMap<String, List<?>> keys = new HashMap<String, List<?>>();
File file = new File(filePath);
try (Reader reader = new InputStreamReader(new FileInputStream(file), UTF_8)) {
StringBuilder stringBuilder = new StringBuilder();
CharBuffer buffer = CharBuffer.allocate(2048);
while (reader.read(buffer) != -1) {
buffer.flip();
stringBuilder.append(buffer);
buffer.clear();
}
Matcher certMatcher = CERT_PATTERN.matcher(stringBuilder);
CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
List<X509Certificate> certificates = new ArrayList<>();
int start = 0;
while (certMatcher.find(start)) {
byte[] cert_buffer = Base64.getMimeDecoder().decode(certMatcher.group(1).getBytes(UTF_8));
certificates.add((X509Certificate) certificateFactory.generateCertificate(new ByteArrayInputStream(cert_buffer)));
start = certMatcher.end();
}
keys.put("certs", certificates);
Matcher keyMatcher = KEY_PATTERN.matcher(stringBuilder);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
if (!keyMatcher.find()) {
throw new KeyStoreException("found no private key: " + keyMatcher);
}
byte[] encodedKey = Base64.getMimeDecoder().decode(keyMatcher.group(1).getBytes(UTF_8));
List<PrivateKey> keyList = new ArrayList<>();
PKCS8EncodedKeySpec eKey = new PKCS8EncodedKeySpec(encodedKey);
PrivateKey pKey = keyFactory.generatePrivate(eKey);
keyList.add(pKey);
keys.put("keys", keyList);
}
return keys;
}
Here are the 2 methods for creating the keystore and truststore:
private TrustManager[] createTrustStore(String trustPath) throws KeyStoreException,
NoSuchAlgorithmException, CertificateException,
IOException {
InputStream fis = new FileInputStream(new File(trustPath));
KeyStore ksTrust = KeyStore.getInstance("jks");
ksTrust.load(null);
BufferedInputStream bis = new BufferedInputStream(fis);
CertificateFactory cf = CertificateFactory.getInstance("X.509");
while (bis.available() > 0) {
X509Certificate cert = (X509Certificate) cf.generateCertificate(bis);
ksTrust.setCertificateEntry("tradepoint"+bis.available(), cert);
}
// TrustManagers decide whether to allow connections
TrustManagerFactory tmf = TrustManagerFactory.getInstance("PKIX");
tmf.init(ksTrust);
return tmf.getTrustManagers();
}
/**
* Create a KeyStore from standard PEM files
*
* #param key the private key from the PEM file
* #param certs the certificate(s) from the PEM file
* #param name the alias used in the keystore
*/
public static KeyManager[] createKeyStore(PrivateKey key, X509Certificate[] certs, String name)
throws Exception, KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException {
final KeyStore keystore = KeyStore.getInstance("PKCS12");
keystore.load(null);
// Import private key
char[] pwdChar = "password".toCharArray();
keystore.setKeyEntry(name, key, pwdChar, certs);
KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
kmf.init(keystore, pwdChar);
final KeyManager[] km = kmf.getKeyManagers();
return km;
}
Does anyone have an idea what might cause this exception given it is a correct certificate?

How to sign a String with an Android X509Certificate certificate A1

Good afternoon folks, I'm new to developing apps for android and after many searches I still can't sign a string with X509Certificate A1 on android. I have the certificate in byte [] and the password, which are returned from a service and I pass as a parameter to the method SubscribeString, but I'm not able to sign. I tried to use the following code:
public String SubscribeString(String texto, byte[] certificateByte, String password)
{
try {
InputStream certificado = new ByteArrayInputStream(certificateByte);
KeyStore rep = KeyStore.getInstance(KeyStore.getDefaultType());
rep.load(certificado, password.toCharArray());
String alias = "TravelTicket";
PrivateKey privateKey = (PrivateKey) rep.getKey(alias, password.toCharArray());
Signature dsa = Signature.getInstance("SHA1withRSA");
/* Initializing the object with a private key */
dsa.initSign(privateKey);
/* Update and sign the data */
dsa.update(texto.getBytes());
byte[] sig = dsa.sign();
texto = new String(Base64.getEncoder().encode(sig));
} catch (Exception ex) {
ex.printStackTrace();
}
return texto;
}

How to send digitally signed mail with attachment using EWS API

I am trying to send digitally signed mail using EWS, I have implemented code for signing the mail. The mail gets sent successfully but the content of it is empty, following is the code, what am I doing wrong. any help is appreciated.
**
public static EmailMessage sign(EmailMessageContent messageContent)
throws Exception {
EmailMessage signedMessage = messageContent.getEmailMessage();
char[] smimePw = messageContent.getPassword().toCharArray();
CMSProcessableByteArray cmsProcessableByteArray = new CMSProcessableByteArray(signedMessage.getBody().toString().getBytes());
try{
if(signer==null){
signer = getSigner(messageContent, smimePw);
}
CMSSignedData cmsSignedData = signer.generate(cmsProcessableByteArray,"BC");
MimeContent content = new MimeContent("UTF-8", cmsSignedData.getEncoded());
signedMessage.setMimeContent(content);
signedMessage.setItemClass("IPM.Note.SMIME");
}catch(Exception exp){
LOGGER.error("In sign() method", exp);
}
return signedMessage;
}
private static CMSSignedDataGenerator getSigner(EmailMessageContent messageContent,
char[] smimePw)
throws KeyStoreException, IOException, NoSuchAlgorithmException,
CertificateException, Exception, UnrecoverableKeyException,
InvalidAlgorithmParameterException, NoSuchProviderException,
CertStoreException, SMIMEException {
InputStream is = null;
CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
try{
is = getCertFile(messageContent.getCertFile());
KeyStore keystore = KeyStore.getInstance("PKCS12");
keystore.load(is, smimePw);
String keyAlias = getAlias(keystore);
if (keyAlias == null) {
System.err.println();
throw new Exception("Can't find an alias.");
}
PrivateKey privateKey = (PrivateKey)keystore.getKey(keyAlias, smimePw);
if (privateKey == null) {
throw new Exception("No private key for alias: " + keyAlias);
}
Certificate[] certificates = getCertificates(keystore, keyAlias);
//Certificate[]
gen.addSigner(privateKey, (X509Certificate) certificates[0], CMSSignedGenerator.DIGEST_MD5);
List<Certificate> certList = Arrays.asList(certificates);
CertStore certs = CertStore.getInstance("Collection", new CollectionCertStoreParameters(certList), "BC");
gen.addCertificatesAndCRLs(certs);
}finally{
if(is!=null)is.close();
}
return gen;
}
**

(SSL) Loading a private key into android keystore

I have my file, key.key which i now used OpenSSL to convert to PKCS8 format, this is the key template
-----BEGIN PRIVATE KEY-----
some letters and numbers
-----END PRIVATE KEY-----
When im trying to load it into my key store as so:
PrivateKey privateKey = kf.generatePrivate(new PKCS8EncodedKeySpec(privateKeyBytes));
I get the Error
com.android.org.conscrypt.OpenSSLX509CertificateFactory$ParsingException: Error parsing private key
(im using OkHttpChannel builder and Conscrypt as a security provider).
I tried changing the key's format, which is why it is now in PKCS8 since iv'e read its the eaziest one for android java to read.
Update::
Even after loading the key and the two certificates i still get the
------------------Untrusted chain: ---------------------- error, any help ?
The code used:
private static byte[] getBytesFromInputStream(InputStream is) throws IOException {
ByteArrayOutputStream os = new ByteArrayOutputStream();
byte[] buffer = new byte[0xFFFF];
for (int len = is.read(buffer); len != -1; len = is.read(buffer)) {
os.write(buffer, 0, len);
}
return os.toByteArray();
}
// build a key store from a set of raw certificates
private static KeyStore createKeyStore(Resources resources, int certsId, boolean ca) {
KeyStore ks = null;
try {
CertificateFactory cf = CertificateFactory.getInstance("X.509");
if (ca) {
ks = KeyStore.getInstance(KeyStore.getDefaultType());
ks.load(null, null);
InputStream certIS = resources.openRawResource(R.raw.ca_crt);
X509Certificate cert = (X509Certificate) cf.generateCertificate(certIS);
ks.setCertificateEntry("ca", cert);
} else {
ks = KeyStore.getInstance("AndroidKeyStore");
ks.load(null, null);
InputStream certIS = resources.openRawResource(R.raw.client_crt);
Certificate cert = cf.generateCertificate(certIS);
InputStream privateKeyIS = resources.openRawResource(R.raw.client_pkcs8);
byte[] privateKeyBytes = RNGrpcChannelBuilder.getBytesFromInputStream(privateKeyIS);
KeyFactory kf = KeyFactory.getInstance("RSA");
PrivateKey privateKey = kf.generatePrivate(new PKCS8EncodedKeySpec(privateKeyBytes));
Certificate[] certChain = new Certificate[1];
certChain[0] = cert;
ks.setKeyEntry("client", privateKey, null, certChain);
}
} catch (Exception e) {
throw new RuntimeException(e);
}
return ks;
}
public static ManagedChannel build(String host, int port, Resources resources,
#Nullable String serverHostOverride) {
KeyStore ca = RNGrpcChannelBuilder.createKeyStore(resources, R.array.certs, true);
KeyStore client = RNGrpcChannelBuilder.createKeyStore(resources, R.array.certs, false);
OkHttpChannelBuilder channelBuilder =
OkHttpChannelBuilder.forAddress(host, port);
if (serverHostOverride != null) {
// Force the hostname to match the cert the server uses.
channelBuilder.overrideAuthority(serverHostOverride);
}
try {
channelBuilder.negotiationType(io.grpc.okhttp.NegotiationType.TLS);
channelBuilder.useTransportSecurity();
channelBuilder.sslSocketFactory(getSslSocketFactory(ca, client));
} catch (Exception e) {
throw new RuntimeException(e);
}
return channelBuilder.build();
}
private static SSLSocketFactory getSslSocketFactory(KeyStore ca, KeyStore client)
throws Exception {
KeyManagerFactory kmf =
KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
String password = "";
kmf.init(client, password.toCharArray());
// initialize trust manager factor from certs keystore
TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(ca);
// initialize SSL context from trust manager factory
SSLContext context = SSLContext.getInstance("TLS");
context.init(kmf.getKeyManagers(), tmf.getTrustManagers(), new SecureRandom());
// return socket factory from the SSL context
return context.getSocketFactory();
}

Signing CSR with Java does not validate using OpenSSL

I'm signing a CSR to an intermediate certificate, both of which were generated using OpenSSL. The signing code uses Java Bouncy Castle APIs, and the code successfully generates a cert. When inspecting the cert, everything appears fine. The issuer and other data show up correctly in the dump.
However, when executing an openssl verify command, it fails with:
error 20 at 0 depth lookup:unable to get local issuer certificate
Signing an OpenSSL generated CSR to this same intermediate certificate verifies correctly.
The verify is successful when checked with the Java code:
cert.verify(cacert.getPublicKey())
-----BEGIN CERTIFICATE-----
MIIDEjCCArmgAwIBAgIFAJl/JBYwCgYIKoZIzj0EAwIwdTETMBEGA1UEDRMKMTUw
MzkxNzkxNDEVMBMGA1UEAwwMcGh5enpsaXRlLTAxMQ4wDAYDVQQLDAVQaHl6ejEd
MBsGA1UECgwUTWltb3NhIE5ldHdvcmtzLCBJbmMxCzAJBgNVBAgMAkNBMQswCQYD
VQQGEwJVUzAeFw0xNzA4MzAwMDA4MzhaFw0yNzAzMzEwMDA4MzhaMIG8Me0wGwYD
VQQKExRNaW1vc2EgTmV0d39ya3MsIEluYzEjMCEGA2UEAxMaMDEyMzcyOEI4MDAw
MTAwMTA3QkM0RkREQUMxEzARBgNVBAUTCjMwNzE1NDE4MjIxGjAYBgNVBCoTETIw
OmI1OmM2OjBmOmQ7OjNiMRowGAYDVQQEExEyMDpiNTpjNjowZjpkNjozETEaMBgG
A1UEKRMRMjA5YjU6YzY6MGY6ZDY6M2MxDTALBgNVBAwTBDMzMzkwWTATBgcqhkjO
PQIBBggqhkjOPQMBBwNCAARUdhMYLbb94GlWSh8b01lVfKL7+6sCw7hZdiMy9JIF
YBnTjLyGm1HjoRKl6ItuEzjdNFXMnFlMMuCbUTsij4L2o4HtMIHqMAwGA1UdEwQF
MAMBAf8wHQYDVR0OBBYEFPjbF9wIOB3uq2C7Yf6l8iMSU7SDMIGlBgNVHSMEgZ0w
gZqAFAshdhvN+xIrKpHeFG4o/TrJt6i/oXykejB4MQswCQYDVQQGEwJVUzELMAkG
A1UECBMCQ0ExHTAbBgNVBAoTFE1pbW9zYSBOZXR3b3JrcywgSW5jMQ4wDAYDVQQL
EwVQaHl6ejEYMBYGA1UEAxMPaW50ZXJjZWRpYRXlMTMzMRMwEQYDVQQNEwoxNDEy
OTU0Nzk1ggQFoABWMBMGA1UdEQQMMAqICCsGAQQBgtJcMAoGCCqGSM49BAMCA0cA
MEQCIFK2FycAFextGiAQPozuT2LFR/AtPDHpGyXn6z3ccUCVAiBFkwn/YBVz5yof
r5YHxgoz0LIJ+XUqLACNTHJhHstDCA==
-----END CERTIFICATE-----
public static X509Certificate certFromFile(String path) {
X509Certificate cert;
try {
CertificateFactory fact = CertificateFactory.getInstance("X.509");
FileInputStream is = new FileInputStream(path);
cert = (X509Certificate) fact.generateCertificate(is);
} catch (CertificateException | FileNotFoundException e) {
String error = e.getMessage();
System.err.println(error);
return null;
}
return cert;
}
public static String DumpCert(X509Certificate cert) {
ByteArrayOutputStream out = new ByteArrayOutputStream();
try {
out.write("-----BEGIN CERTIFICATE-----\n".getBytes());
out.write(Base64.encode(cert.getEncoded()));
out.write("\n-----END CERTIFICATE-----\n".getBytes());
out.close();
} catch (IOException | CertificateEncodingException e) {
System.out.println(e.getMessage());
}
String certpem = null;
try {
certpem = new String(out.toByteArray(), "ISO-8859-1");
} catch (UnsupportedEncodingException e) {
System.out.println(e.getMessage());
}
return certpem;
}
public static ContentSigner createSigner(PrivateKey privateKey) {
try {
ContentSigner sig = new JcaContentSignerBuilder("SHA256withECDSA").build(privateKey);
return sig;
} catch (Exception e) {
throw new RuntimeException("Could not create content signer.", e);
}
}
public static String signCSR(PKCS10CertificationRequest csr, GeneralName san, int validity,
X509Certificate cacert, KeyStore keystore, String alias) throws Exception {
Date from = new Date();
Date to = new Date(System.currentTimeMillis() + (validity * 86400000L));
PrivateKey cakey = null;
try {
cakey = (PrivateKey) keystore.getKey(alias, null);
} catch (Exception e) {
System.out.println(e.getMessage());
}
GeneralNames subjectAltNames = new GeneralNames(san);
org.bouncycastle.asn1.x500.X500Name csrSubject = csr.getSubject();
X500Name issuer = new X500Name(cacert.getSubjectX500Principal().getName());
X500Name caName = X500Name.getInstance(PrincipalUtil.getIssuerX509Principal(cacert).getEncoded());
GeneralNames gn = new GeneralNames(new GeneralName(caName));
BigInteger serial = new BigInteger(32, new SecureRandom());
SubjectPublicKeyInfo keyinfo = csr.getSubjectPublicKeyInfo();
DigestCalculator digCalc = new BcDigestCalculatorProvider().get(new AlgorithmIdentifier(OIWObjectIdentifiers.idSHA1));
X509ExtensionUtils x509ExtensionUtils = new X509ExtensionUtils(digCalc);
X509v3CertificateBuilder certgen = new X509v3CertificateBuilder(issuer, serial, from, to, csrSubject, keyinfo);
BigInteger serialcert = cacert.getSerialNumber();
Boolean buildCACert = true;
PublicKey caKey = cacert.getPublicKey();
SubjectPublicKeyInfo keyinfoCA = SubjectPublicKeyInfo.getInstance(caKey.getEncoded());
AuthorityKeyIdentifier akiMain = x509ExtensionUtils.createAuthorityKeyIdentifier(keyinfoCA);
AuthorityKeyIdentifier aki = new AuthorityKeyIdentifier(akiMain.getKeyIdentifier(), gn, serialcert);
certgen.addExtension(Extension.basicConstraints, false, new BasicConstraints(buildCACert));
certgen.addExtension(Extension.subjectKeyIdentifier, false, x509ExtensionUtils.createSubjectKeyIdentifier(keyinfo));
certgen.addExtension(Extension.authorityKeyIdentifier, false, aki);
certgen.addExtension(Extension.subjectAlternativeName, false, subjectAltNames);
ContentSigner signer = createSigner(cakey);
X509CertificateHolder holder = certgen.build(signer);
X509Certificate cert = null;
try {
CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
InputStream in = new ByteArrayInputStream(holder.getEncoded());
cert = (X509Certificate) certFactory.generateCertificate(in);
} catch (CertificateException | IOException e) {
System.out.println(e.getMessage());
}
return DumpCert(cert);
}
public static GeneralName getSubjectAlternativeName(PKCS10CertificationRequest csr) {
org.bouncycastle.asn1.pkcs.Attribute[] certAttributes = csr.getAttributes();
for (org.bouncycastle.asn1.pkcs.Attribute attribute : certAttributes) {
if (attribute.getAttrType().equals(PKCSObjectIdentifiers.pkcs_9_at_extensionRequest)) {
Extensions extensions = Extensions.getInstance(attribute.getAttrValues().getObjectAt(0));
GeneralNames gns = GeneralNames.fromExtensions(extensions, Extension.subjectAlternativeName);
GeneralName name = gns.getNames()[0];
return name;
}
}
return null;
}
public static String PrepAndSignCSR(String raw_csr, String certPath, String keystore) {
KeyStore ks = null;
Object parsedObject = null;
String alias = "alias";
String s = null;
X509Certificate caCert = null;
StringReader sr = new StringReader(raw_csr);
PEMParser pemParser = new PEMParser(sr);
try {
parsedObject = pemParser.readObject();
System.out.println("PemParser returned : " + parsedObject);
} catch (IOException e) {
String error = e.getMessage();
System.err.println(error);
}
PKCS10CertificationRequest CSR = (PKCS10CertificationRequest) parsedObject;
caCert = certFromFile(certPath);
try {
ks = KeyStore.getInstance("ncipher.sworld", "nCipherKM");
FileInputStream in = new FileInputStream(keystore);
ks.load(in, null);
} catch (KeyStoreException |
NoSuchAlgorithmException |
CertificateException |
IOException |
NoSuchProviderException e) {
System.err.println(e.getMessage());
}
GeneralName san = getSubjectAlternativeName(CSR);
try {
String cert = signCSR(CSR, san, 3500, caCert, ks, alias);
return cert;
} catch (Exception e) {
String error = e.getMessage();
System.err.println(error);
}
return null;
}
public static void main(String[] args) {
.
.
.
String fini = PrepAndSignCSR(csr, caCertPath, keystore);
System.out.println(fini);
}
}
After some debug sessions within openssl, I found the issuer name was not matching. Replaced this line with getEncoded...
X500Name issuer = newX500Name(cacert.getSubjectX500Principal().getName());
X500Name issuer = X500Name.getInstance(cacert.getSubjectX500Principal().getEncoded());
The signed certs are now verifying with openssl verify command.
You're comparing apples and oranges.
java.security.cert.Certificate.verify() does nothing more than check the digital signature against the supplied public key.
openssl x509 verify checks the entire certificate chain, looks for the trust anchor, and verifies the digital signature and expiry dates of every certificate in the chain.
You need to specify the -CAfile or -CApath options to openssl. See the man page.

Categories