I am having an issue in passing a base64 encoded DER cert to a Java application for it to extract the public key from. I can make this work in both objective-c & ruby, but am struggling with an error in Java.
I have the following DER base64 key created in Ruby (simplified):
key = OpenSSL::PKey::RSA.new(2048)
public_key = key.public_key
subject = "/C=BE/O=Test/OU=Test/CN=Test"
cert = OpenSSL::X509::Certificate.new
cert.subject = cert.issuer = OpenSSL::X509::Name.parse(subject)
cert.not_before = Time.now
cert.not_after = Time.now + 365 * 24 * 60 * 60
cert.public_key = public_key
cert.serial = 0x0
cert.version = 2
ef = OpenSSL::X509::ExtensionFactory.new
ef.subject_certificate = cert
ef.issuer_certificate = cert
cert.extensions = [
ef.create_extension("basicConstraints","CA:TRUE", true),
ef.create_extension("subjectKeyIdentifier", "hash"),
# ef.create_extension("keyUsage", "cRLSign,keyCertSign", true),
]
cert.add_extension ef.create_extension("authorityKeyIdentifier",
"keyid:always,issuer:always")
cert.sign key, OpenSSL::Digest::SHA1.new
der => Base64.encode64(cert.to_der)
Here is an example output:
MIIDhzCCAm+gAwIBAgIBADANBgkqhkiG9w0BAQUFADA6MQswCQYDVQQGEwJC\nRTENMAsGA1UECgwEVGVzdDENMAsGA1UECwwEVGVzdDENMAsGA1UEAwwEVGVz\ndDAeFw0xNTA1MjExNDUxNTZaFw0xNjA1MjAxNDUxNTZaMDoxCzAJBgNVBAYT\nAkJFMQ0wCwYDVQQKDARUZXN0MQ0wCwYDVQQLDARUZXN0MQ0wCwYDVQQDDARU\nZXN0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvbC3phpnj/Vg\neIAmWXD3TkGi91kPFBvrrD/LLa4kv83eOuY139vUn/xjZlM9maE36Yix6Ix1\ncF3cGCUl1VZApJYTef404jL13xLg1i9+96/tU91niZMlRkFL0mZWV2XhEzNH\nnA+lRiJZlGdXNwYUKXb9qVRuS2taSAyMwH/SDPu1s17SGVLY1o+7trAQfK7w\ny5w8fyTr+tCdcplb5F/m6R5FXc4eJwD6m9MYnenlmkoW5uM5B1lbQBVz2by3\nVGCMUmLwpC15pi13/fIJ8WzD1SQcYJgIQTauWVRYxSuc+Cg0VvoyMZNqkqOW\n7iKmZXGqwNQKxhLtcc1KNJky7OHLkQIDAQABo4GXMIGUMA8GA1UdEwEB/wQF\nMAMBAf8wHQYDVR0OBBYEFNWHBjgj9rsOBm6Z9+me3I/E1ZrUMGIGA1UdIwRb\nMFmAFNWHBjgj9rsOBm6Z9+me3I/E1ZrUoT6kPDA6MQswCQYDVQQGEwJCRTEN\nMAsGA1UECgwEVGVzdDENMAsGA1UECwwEVGVzdDENMAsGA1UEAwwEVGVzdIIB\nADANBgkqhkiG9w0BAQUFAAOCAQEAbKYmQxaQaGT57Qq8xYzIzODGqjbek3qC\n8kSjUto9H/5o8OCKqDFJfgaYAS9mgEjjazqmMahoDeLvzRkKHkpXLvdjjjv7\nnnMZGIw7I4yOKvtzGDz2eimonlWPePypTwr0NFnnUByQb9nkrOOrpcSKBn7a\nwvIT7b82ISOoMz1+hlyo8dyiZri82J6pKXTP91LcfpSRiC/1W1sXnIL5DSJi\nDtXGMVtDfy9rRgPJhmOPu4xqInl/o+t2A1OXLhA4aDnxP/gbssVau9Do3uIa\nOlyo9eGpatIvkxCMzC4SgBavBy6Gsk2p4KAuWon9TtDzO5vklEI8QKk1tiyJ\nYZBCeK3HwQ==\n
If I was to get the public key out of this in Ruby, I would do this:
der = Base64.decode64(data["certs"]["device_key"])
x509_cert = OpenSSL::X509::Certificate.new der
public_rsa_2048_key = x509_cert.public_key
The same in Java is a little more verbose, but (credit to SO) this is what I have:
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.io.File;
import java.security.cert.CertificateFactory;
import java.io.ByteArrayInputStream;
import java.security.cert.Certificate;
import java.security.PublicKey;
import java.io.FileInputStream;
import java.nio.charset.Charset;
import java.nio.channels.FileChannel;
import java.io.ByteArrayOutputStream;
import javax.xml.bind.DatatypeConverter;
import java.nio.channels.Channels;
import java.io.Console;
public class EncryptionTest{
public static void main(String[] args) throws IOException, GeneralSecurityException {
Console console = System.console();
console.writer().println("Loading base64 key from file");
// get a handle on the base64 encoded key and certificate
// File privateKeyFile = new File("private.der.b64");
File publicKeyFile = new File("public.der.b64");
console.writer().println("Converting to byteArray");
// pull them into arrays
// byte[] privateKeyBytes = toByteArray(privateKeyFile);
byte[] publicKeyBytes = toByteArray(publicKeyFile);
console.writer().println("decoding base64 bytes");
// decode them
// privateKeyBytes = toDecodedBase64ByteArray(privateKeyBytes);
publicKeyBytes = toDecodedBase64ByteArray(publicKeyBytes);
// get the private key
// KeyFactory keyFactory = KeyFactory.getInstance("RSA");
// KeySpec privateKeySpec = new PKCS8EncodedKeySpec(privateKeyBytes);
// PrivateKey privateKey = keyFactory.generatePrivate(privateKeySpec);
console.writer().println("Building Cert");
// get the public key
CertificateFactory certificateFactory = CertificateFactory.getInstance("X509");
Certificate certificate = certificateFactory.generateCertificate(new ByteArrayInputStream(publicKeyBytes));
console.writer().println("Extracting Public Key");
PublicKey publicKey = certificate.getPublicKey();
console.writer().println("Key:" + publicKey.toString());
}
private static byte[] toByteArray(File file) throws IOException {
// java 7's try-with-resources statement
try (FileInputStream in = new FileInputStream(file);
FileChannel channel = in.getChannel()) {
ByteArrayOutputStream out = new ByteArrayOutputStream();
channel.transferTo(0, channel.size(), Channels.newChannel(out));
return out.toByteArray();
}
}
private static byte[] toDecodedBase64ByteArray(byte[] base64EncodedByteArray) {
return DatatypeConverter.parseBase64Binary(
new String(base64EncodedByteArray, Charset.forName("UTF-8")));
}
}
However when I run this I get the following error:
Exception in thread "main" java.security.cert.CertificateException: Unable to initialize, java.io.IOException: DerInputStream.getLength(): lengthTag=105, too big.
at sun.security.x509.X509CertImpl.<init>(X509CertImpl.java:199)
at sun.security.provider.X509Factory.engineGenerateCertificate(X509Factory.java:98)
at java.security.cert.CertificateFactory.generateCertificate(CertificateFactory.java:339)
at EncryptionTest.main(EncryptionTest.java:50)
Caused by: java.io.IOException: DerInputStream.getLength(): lengthTag=105, too big.
at sun.security.util.DerInputStream.getLength(DerInputStream.java:561)
at sun.security.util.DerValue.<init>(DerValue.java:252)
at sun.security.util.DerInputStream.getDerValue(DerInputStream.java:417)
at sun.security.x509.X509CertImpl.parse(X509CertImpl.java:1761)
at sun.security.x509.X509CertImpl.<init>(X509CertImpl.java:196)
Any pointers on how to solve this in Java would be great! Thanks
You need to replace the \n
in your example output by a linefeed character
. Then your code will not fail.
output (long line truncated)
Loading base64 key from file
Converting to byteArray
decoding base64 bytes
Building Cert
Extracting Public Key
Key:Sun RSA public key, 2048 bits
modulus: 239461822256650353755672 ...
public exponent: 65537
edit One possible solution to replace the \n
could be to to replace it by two linefeed characters (only because of not to change the number of bytes in the array).
Amend your method toByteArray
as below.
private static byte[] toByteArray(File file) throws IOException {
byte[] allBytes = Files.readAllBytes(file.toPath());
for (int i = 0; i < allBytes.length; i++) {
if (allBytes[i] == '\\') {
allBytes[i++] = 10;
allBytes[i++] = 10;
}
}
return allBytes;
}