I have a CA certificate which is generate by openssl with "trustout", so the it begins with "-----BEGIN TRUSTED CERTIFICATE-----", when I tried to read it with Java, exception throws.
Does Java supports this format of certificate? If so, how to read it?
public class TestReadCerts {
public static void main(String[] args) {
// TODO Auto-generated method stub
String sslrootcertfile = "F:\\javaworkspace\\opensource\\certs\\ca.pem";
FileInputStream fis=null;
try {
fis = new FileInputStream(sslrootcertfile); // NOSONAR
} catch (FileNotFoundException ex) {
ex.printStackTrace();
}
try {
CertificateFactory cf = CertificateFactory.getInstance("X.509");
Object[] certs = cf.generateCertificates(fis).toArray(new Certificate[]{});
} catch (Exception e) {
e.printStackTrace();
}
}
}
The exception:
java.security.cert.CertificateException: Unable to initialize,
java.io.IOException: extra data given to DerValue constructor
at java.base/sun.security.x509.X509CertImpl.<init>(X509CertImpl.java:191)
at java.base/sun.security.provider.X509Factory.parseX509orPKCS7Cert(X509Factory.java:476)
at java.base/sun.security.provider.X509Factory.engineGenerateCertificates(X509Factory.java:361)
at java.base/java.security.cert.CertificateFactory.generateCertificates(CertificateFactory.java:478)
at TestReadCerts.main(TestReadCerts.java:21)
Caused by: java.io.IOException: extra data given to DerValue constructor
at java.base/sun.security.util.DerValue.init(DerValue.java:409)
at java.base/sun.security.util.DerValue.<init>(DerValue.java:294)
at java.base/sun.security.util.DerValue.<init>(DerValue.java:305)
at java.base/sun.security.x509.X509CertImpl.<init>(X509CertImpl.java:188)
... 4 more
And the certificate is as below:
-----BEGIN TRUSTED CERTIFICATE-----
MIICATCCAWoCCQDjKSwZBsrQwTANBgkqhkiG9w0BAQsFADBFMQswCQYDVQQGEwJB
VTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0
cyBQdHkgTHRkMB4XDTE5MDQwMzE0NDcwMVoXDTIwMDQwMjE0NDcwMVowRTELMAkG
A1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGEludGVybmV0
IFdpZGdpdHMgUHR5IEx0ZDCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAyRCB
rbB/FqN6e9IAJ86WUUGxM+8vEyfQ7cn2HWca220NB/Ns3Q+QtvztSe48PUzn9w6s
MNOwsDW4+8lenPLd78J32lG59x1P1R1jpjL3GcjNTwuewW1jIsex8jALzfU9hJzO
prraO/6X+UbKbazXt6GiB7mOlUvneKsWuoGpF5MCAwEAATANBgkqhkiG9w0BAQsF
AAOBgQCN1UJF/FdT84bzEn1kmg77b+LCCrU11DsFg/s/ABvo5TKV+OmilBPj1vML
dbZ4GDQSaXKZAOyJiAp0S5BzHXlXz5YfX9sM4mfhaqZt736WAnKVSnzd55CjMlEk
GxW3TkRFL5cVm5my1UQs3Mfg4MC5QPaoer5kc+0UhMHmTlgyvTAMMAoGCCsGAQUF
BwMB
-----END TRUSTED CERTIFICATE-----
PEM type 'TRUSTED CERTIFICATE' is an OpenSSL-specific nonstandard format that Java can't handle out of the box. It actually contains the standard X.509 cert as one DER chunk plus another OpenSSL-defined DER chunk of trust information.
If you have OpenSSL, the simplest method is to convert to the standard 'CERTIFICATE' format with openssl x509 <in >out . You can add -outform DER as Misantorp did but it isn't needed; CertificateFactory can read the standard format in either DER or PEM as OpenSSL inexactly calls them.
If you have or can get and use bcpkix and bcprov from https://www.BouncyCastle.org , they include routines to handle this OpenSSL PEM format (and many others):
// assumes filename in args[0], adjust as needed
Object both = new PEMParser(new FileReader(args[0])).readObject();
// should close the FileReader, maybe using try-resources
byte[] cert = ((X509TrustedCertificateBlock)both).getCertificateHolder().getEncoded();
X509Certificate good = (X509Certificate) CertificateFactory.getInstance("X.509")
.generateCertificate(new ByteArrayInputStream(cert));
System.out.println (good.getSubjectX500Principal().getName());
Otherwise, it is possible to decompose the DER by hand but clumsy and unrobust:
String in1 = new String(Files.readAllBytes(new File(args[0]).toPath()));
byte[] both = Base64.getMimeDecoder().decode(in1.replaceAll("-----[A-Z ]*-----\\r?\\n",""));
if( both[0]!=0x30 || both[1]!=(byte)0x82 ) throw new Exception("wrong!"); // or other handling
byte[] cert = Arrays.copyOf(both, (both[2]<<8 | both[3]&0xFF) + 4);
X509Certificate good = (X509Certificate) CertificateFactory.getInstance("X.509")
.generateCertificate(new ByteArrayInputStream(cert));
System.out.println (good.getSubjectX500Principal().getName());
Please also check out the answer by dave_thompson_085 as it points out some inaccuracies in my understanding and as a result this answer
Your problem is trying to read a PEM encoded certificate with CertificateFactory which expects a DER encoding as stated in the documentation
In the case of a certificate factory for X.509 certificates, the
certificate provided in inStream must be DER-encoded and may be
supplied in binary or printable (Base64) encoding.
I think the quickest way to read the certificate is to convert the certificate into the appropriate encoding. Since you already mentioned using openssl to generate the certificate you can encode your current certificate to DER
$ openssl x509 -in /F/javaworkspace/opensource/certs/ca.pem -outform DER -out /F/javaworkspace/opensource/certs/ca.der
(adjust the path to ca.pem and ca.der as needed)
Lastly, don't forget to update the sslrootcertfile variable
String sslrootcertfile = "F:\\javaworkspace\\opensource\\certs\\ca.der";
Related
I generated a ca-certificate and a key with openssl.
openssl genrsa -aes256 -out $CANAME.key 4096
openssl req -x509 -new -nodes -key $CANAME.key -sha256 -days 1826 -out $CANAME.crt -subj '/CN=MyOrg Root CA/C=AT/ST=Vienna/L=Vienna/O=MyOrg'
Now I want to sign CSRs with those.
I found this question, but I can't use the accepted answer, because the class PKCS10CertificationRequestHolder seems not to exist anymore.
Based on the seconds answer I created this service.
public class SignService {
private PrivateKey privateKey;
private X509CertificateHolder certificateHolder;
public SignService(
String caPathKey,
String caCert,
String caKeyPassword
) throws IOException, NoSuchAlgorithmException, InvalidKeySpecException {
Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
loadPrivateKey(caPathKey, caKeyPassword);
PEMParser pemParser = new PEMParser(new FileReader(caCert));
this.certificateHolder = (X509CertificateHolder) pemParser.readObject();
}
private void loadPrivateKey(String caPathKey, String caKeyPassword) throws IOException {
PEMParser pemParser = new PEMParser(new FileReader(caPathKey));
Object object = pemParser.readObject();
JcaPEMKeyConverter converter = new JcaPEMKeyConverter().setProvider("BC");
KeyPair kp;
if (object instanceof PEMEncryptedKeyPair)
{
// Encrypted key - we will use provided password
PEMEncryptedKeyPair ckp = (PEMEncryptedKeyPair) object;
PEMDecryptorProvider decProv = new JcePEMDecryptorProviderBuilder().build(caKeyPassword.toCharArray());
kp = converter.getKeyPair(ckp.decryptKeyPair(decProv));
}
else
{
// Unencrypted key - no password needed
PEMKeyPair ukp = (PEMKeyPair) object;
kp = converter.getKeyPair(ukp);
}
this.privateKey = kp.getPrivate();
}
public String signCRT(String crs_str) throws NoSuchProviderException, IOException, KeyStoreException, NoSuchAlgorithmException, OperatorCreationException, CMSException {
PemReader p = new PemReader(new StringReader(crs_str));
PemObject pemObject = p.readPemObject();
PKCS10CertificationRequest csr = new PKCS10CertificationRequest(pemObject.getContent());
AlgorithmIdentifier sigAlgId = new DefaultSignatureAlgorithmIdentifierFinder().find("SHA1withRSA");
AlgorithmIdentifier digAlgId = new DefaultDigestAlgorithmIdentifierFinder().find(sigAlgId);
X500Name issuer = certificateHolder.getIssuer();
BigInteger serial = new BigInteger(32, new SecureRandom());
Date from = new Date();
Date to = new Date(System.currentTimeMillis() + (365 * 86400000L));
X509v3CertificateBuilder certgen = new X509v3CertificateBuilder(issuer, serial, from, to, csr.getSubject(), csr.getSubjectPublicKeyInfo());
certgen.addExtension(X509Extension.basicConstraints, false, new BasicConstraints(false));
certgen.addExtension(X509Extension.subjectKeyIdentifier, false, new SubjectKeyIdentifier(csr.getSubjectPublicKeyInfo().getEncoded()));
certgen.addExtension(X509Extension.authorityKeyIdentifier, false, new AuthorityKeyIdentifier(new GeneralNames(new GeneralName(this.certificateHolder.getSubject())), certificateHolder.getSerialNumber()));
ContentSigner signer = new BcRSAContentSignerBuilder(sigAlgId, digAlgId).build(PrivateKeyFactory.createKey(this.privateKey.getEncoded()));
X509CertificateHolder holder = certgen.build(signer);
byte[] certencoded = holder.toASN1Structure().getEncoded();
CMSSignedDataGenerator generator = new CMSSignedDataGenerator();
signer = new JcaContentSignerBuilder("SHA1withRSA").build(this.privateKey);
generator.addSignerInfoGenerator(new JcaSignerInfoGeneratorBuilder(new JcaDigestCalculatorProviderBuilder().build()).build(signer, this.certificateHolder));
generator.addCertificate(new X509CertificateHolder(certencoded));
generator.addCertificate(new X509CertificateHolder(this.certificateHolder.getEncoded()));
CMSTypedData content = new CMSProcessableByteArray(certencoded);
CMSSignedData signeddata = generator.generate(content, true);
ByteArrayOutputStream out = new ByteArrayOutputStream();
out.write("-----BEGIN PKCS #7 SIGNED DATA-----\n".getBytes("ISO-8859-1"));
byte[] cert_encoded = Base64.encode(signeddata.getEncoded());
for (int i = 0; i < cert_encoded.length; i++) {
if (i > 0 && i % 63 == 0) {
out.write('\n');
}
out.write(cert_encoded[i]);
}
out.write("\n-----END PKCS #7 SIGNED DATA-----\n".getBytes("ISO-8859-1"));
out.close();
return new String(out.toByteArray(), "ISO-8859-1");
}
}
The method signCrt does not throw any exceptions and returns a string (I am not sure what).
I can't verify it or even show any information about the certificate.
This openssl command works with the same csr file, I want to do the same thing from java.
openssl x509 -req -in test.csr -CA $CANAME.crt -CAkey $CANAME.key -CAcreateserial -out openssl-signed.crt -days 730 -sha256
The certificate created with openssl is much smaller the of my java method (Don't know of this is a useful information).
The answer you linked tells you what the code returns: "a valid PEM-encoded signedData object containing a signed Certificate chain (of the type that can be imported by keytool)". Actually it's not quite valid: (1) it doesn't put any linebreaks in the base64 as PEM officially requires -- which you fixed, although you did it at 63 not 64 as standard; (2) it uses BEGIN/END PKCS #7 SIGNED DATA whereas the standard is BEGIN/END PKCS7 -- or in most cases BEGIN/END CMS because CMS is basically a superset of PKCS7. keytool ignores both of these flaws when reading a 'CA reply' (i.e. -importcert to an existing privateKey entry), but other software doesn't -- like BouncyCastle and OpenSSL. You already added line breaks; if you change the BEGIN/END labels to PKCS7 and put the result in a file you can read it with among other things
openssl pkcs7 -in $file -print_certs # add -text for expanded details
However, calling this 'the type that can be imported by keytool' implies it is the only type which is false. keytool can read PKCS7 containing certs -- either PEM or DER -- but it can also read 'bare' X.509 certificates in either PEM or DER, and that is usually more convenient. To do that, replace everything from CMSSignedDataGenerator onward with something like for DER:
Files.write(Paths.get("outputfile"), certencoded)
or for PEM since you have Bouncy:
try( PemWriter w = new PemWriter(new FileWriter("outputfile")) ){
w.writeObject(new PemObject("CERTIFICATE",certencoded));
}
or if you prefer to do it yourself:
try( Writer w = new FileWriter("outputfile") ){
w.write("-----BEGIN CERTIFICATE-----\r\n"
+ Base64.getMimeEncoder().encodeToString(certencoded)
// MimeEncoder does linebreaks at 76 by default which
// is close enough and less work than doing it by hand
+ "\r\n-----END CERTIFICATE-----\r\n");
}
Note: you may not need the \r -- PEM doesn't actually specify CRLF vs LF linebreaks -- but MimeEncoder uses them on the interior breaks and I like to be consistent.
This -- just the certificate -- is what your openssl x509 -req -CA* alternative produces, and that's why it's much smaller -- one cert is smaller than three certs plus some overhead.
Several more points:
PEMParser can handle CSR just fine; you don't need the PemReader+PemObject detour
using the entire SubjectPublicKeyInfo for the SubjectKeyIdentifier extension is wrong. OTOH nothing uses the SKI extension in a leaf cert anyway, so simplest to just delete this rather than fix it.
in certgen.build() if you use JcaContentSignerBuilder instead of the BcRSA variant, you can pass cakey directly without going through PrivateKeyFactory and the signature scheme directly without going through two AlgorithmIdentifierFinders -- as the /*cms-sd*/generator.addSignerInfoGenerator later in the code already does.
speaking of the signature scheme, don't use SHA1 for cert signatures (or most others). It was already deprecated in 2013 when that answer was written, and in 2017 was publicly broken for collision (see https://shattered.io) after which many systems either reject it entirely or nag mercilessly for such use, and some related ones like git. In fact many authorities and checklisters now prohibit it for any use, even though some (like HMAC and PBKDF) aren't actually broken. Your openssl x509 -req alternative used SHA256 which is fine*.
and lastly, issuing a cert is NOT 'sign[ing] {the|a} CSR'. You can just look at the cert contents and see it's not at all the same as the CSR or even the CSR body. You create (or generate as used in the Bouncy names) a cert and sign the cert -- in response to a CSR.
* at least for now; if and when quantum cryptanalysis works a lot of currently used schemes, including probably SHA256-RSA signature, will be in trouble and will have to be replaced by 'post-quantum' schemes which people are now working to develop. But if this happens, you'll see it on every news channel and site in existence.
I have this code to generate a CER file using the alias:
public class TestFromAliasToCER {
public static final int KEY_SIZE = 1024;
public static final String BEGIN_CERT = "-----BEGIN CERTIFICATE-----";
public static final String END_CERT = "-----END CERTIFICATE-----";
public final static String LINE_SEPARATOR = System.getProperty("line.separator");
public static void main(String[] args) throws FileNotFoundException, IOException, NoSuchAlgorithmException, NoSuchProviderException, KeyStoreException, CertificateException {
KeyStore keyStore = KeyStore.getInstance ("Windows-MY");
keyStore.load (null, null);
Enumeration<String> aux = keyStore.aliases();
String alias = aux.nextElement();
X509Certificate certificate = (X509Certificate) keyStore.getCertificate (alias);
String certString = formatCrtFileContents(certificate);
PrintWriter out = new PrintWriter("cert.CER");
out.println(certString);
out.close();
}
public static String formatCrtFileContents(final Certificate certificate) throws CertificateEncodingException {
final Base64.Encoder encoder = Base64.getMimeEncoder(64, LINE_SEPARATOR.getBytes());
final byte[] rawCrtText = certificate.getEncoded();
final String encodedCertText = new String(encoder.encode(rawCrtText));
final String prettified_cert = BEGIN_CERT + LINE_SEPARATOR + encodedCertText + LINE_SEPARATOR + END_CERT;
return prettified_cert;
}
}
This creates the cer file with
-----BEGIN CERTIFICATE-----
data
-----END CERTIFICATE-----
I want to be able to create a PEM Certificate with the private key included, is it possible? If not, why?
I'm not restricted to Java only and free to use any Java API, but preferable with the least user interaction as possible.
Although I don't see it documented, according to the source the SunMSCAPI provider implements only a stub for getEncoded and cannot export Windows privatekey so you can't do this with JCA.
You could of course write JNI or JNA to call Windows CAPI, but that's not simple.
To use existing tools without user interaction you can use Runtime or ProcessBuilder to
run certutil with arguments -exportpfx -user -p password certid filename
run powershell and tell it to select an object in cert:\currentuser\my and invoke the Export('PFX','password') method -- examples for machine rather than user cert here
or in (only) recent powershell use Export-PFXCertificate cmdlet documentation here
and after any of these, extract from pkcs12 to PEM with openssl pkcs12, or if you prefer with Java by:
load the PKCS12 keystore and get the PrivateKey entry
call getEncoded and encode the result in folded (MIME) base64 like you did for the certificate except use -----BEGIN/END PRIVATE KEY-----
Warning: Java produces an unencrypted (PKCS8) privatekey, so make certain no unauthorized user or program ever has access to this file, your disk/filesystem or any backup(s).
A digital certificate doesn't have the private key inside it (the private key is not part of the certificate fields). The certificate and the private key are separate entities, although they're related (one can't exist without the other).
If you take a look at the certificate fields in RFC 5280, you'll see that only the public key is part of it:
Certificate ::= SEQUENCE {
tbsCertificate TBSCertificate,
signatureAlgorithm AlgorithmIdentifier,
signatureValue BIT STRING }
TBSCertificate ::= SEQUENCE {
... lots of fields
subjectPublicKeyInfo SubjectPublicKeyInfo,
... lots of other fields
}
The subjectPublicKeyInfo is the public key, and there's no field for the private key.
That's because certificates are meant to be public (you can have more details on why they're public taking a look at how a Public Key Infrastructure works).
Although the certificate is public, there's always a correspondent private key somewhere, usually held by the certificate's owner (and ideally by no one else).
Anyway, the file you've got (with BEGIN CERTIFICATE and END CERTIFICATE headers) in only the digital certificate (but not the private key).
If you have the private key and the corresponding certificate, you can create a file that contains both. The most common formats for such file are: JKS (also known as Keystore) and PFX.
There are also another "format": the Windows repository (the one you're reading when you do KeyStore.getInstance("Windows-MY")). I don't know exactly in what format its files are, but the KeyStore class abstracts it.
If the private key is present, it will be together with its corresponding certificate, in the same alias. You can check if the key is present with this code:
String alias = aux.nextElement();
if (keyStore.isKeyEntry(alias)) { // alias contains a private key
Key key = keyStore.getKey(alias, "password".toCharArray()); // need to know the password
// key is the private key
// cert is the key's corresponding certificate
X509Certificate cert = (X509Certificate) keyStore.getCertificate(alias);
} else if (keyStore.isCertificateEntry(alias)) { // alias doesn't contain a key
X509Certificate cert = (X509Certificate) keyStore.getCertificate(alias);
}
After having the key, you can save it to another keystore with the following code:
// create another keystore
KeyStore output = KeyStore.getInstance("JKS");
// "alias" - choose to whatever name you want
// privateKey is the object you've got from keyStore.getKey()
// "password" is the password for this alias
// cert will be stored in the same alias
output.setKeyEntry("alias", privateKey, "password".toCharArray(), new Certificate[] { cert });
// save the keystore to a file
output.store(new FileOutputStream("outputfile.jks"), "keystore password".toCharArray());
The code above creates the file outputfile.jks containing the certificate and the private key.
If you want the file to be a PFX, you can change the code above to:
// PKCS12 == PFX format
KeyStore output = KeyStore.getInstance("PKCS12");
// alternative: in pfx, I think that alias can't have specific passwords
// so you can use this as it doesn't require a password for the alias entry
output.setKeyEntry("alias", privateKey.getEncoded(), new Certificate[] { cert });
// change file extension to ".pfx"
output.store(new FileOutputStream("outputfile.pfx"), "keystore password".toCharArray());
I currently have been assigned the task to verify digital signatures generated using openssl , The files I recieve are basically two files one being the xml data and the other being the corresponding signature of that xml data , The key used to sign the data is RSA with an algorithm of SHA1 , the openssl command used is : openssl smime -sign -binary -in {datafile} -out {signaturefile} -outform der -inkey {privatekey} -signer {publickey}
where {datafile} = The input file, in either CSV or XML format, containing the data to be signed {signaturefile} = The output file containing the digital signature only in PKCS#7 format, same filename as datafile but with .sig extension {privatekey} = The private key part of the key to be used for signing {publickey} = The public key part of the key to be used for signing , I have written a class to verify these files but the result always returns a false meaning the verification has failed. below is the code i have written: Could someone please help me on how to verify openssl detached signatures using java?
public PublicKey pubTest(String path) throws Exception
{
FileInputStream fin = new FileInputStream(path);
CertificateFactory f = CertificateFactory.getInstance("X.509");
X509Certificate certificate = (X509Certificate)f.generateCertificate(fin);
PublicKey pk = certificate.getPublicKey();
System.out.println(pk);
return pk;
}
public byte[] signature(String sigPath) throws Exception
{
FileInputStream sigfis = new FileInputStream(sigPath);
byte[] sigToVerify = new byte[sigfis.available()];
sigfis.read(sigToVerify);
sigfis.close();
System.out.println(sigToVerify.length);
return sigToVerify ;
}
public boolean verification(PublicKey pubKey , String dataPath , byte[] sigToVerify ) throws Exception, NoSuchProviderException ,SignatureException
{
Signature sig = Signature.getInstance("SHA1withRSA");
sig.initVerify(pubKey);
FileInputStream datafis = new FileInputStream(dataPath);
BufferedInputStream bufin = new BufferedInputStream(datafis);
byte[] buffer = new byte[1024];
int len;
while (bufin.available() != 0)
{
len = bufin.read(buffer);
sig.update(buffer, 0, len);
}
System.out.println(buffer.length);
bufin.close();
boolean verifies = sig.verify(sigToVerify);
System.out.println("signature verifies: " + verifies);
return verifies ;
}
Late now but if anyone else comes across it:
openssl smime -sign by default generates an "indirect" detached signature, that is, one which does not contain the data in encapContentInfo but does use signedAttrs. See https://www.ietf.org/rfc/rfc3369.txt 5.3 through 5.6; you need to compare a hash of the data to the message-digest attribute in the signedAttrs, and then verify the signature against the signedAttrs (with the IMPLICIT tagging reverted to basic).
Standard Java crypto does not (currently?) do CMS/PKCS#7 but BouncyCastle does if that's an option. Consider Correct way to sign and verify signature using bouncycastle .
Trying to generate a X509 with BouncyCastle api. Here is my piece of code.
try {
Security.addProvider(new BouncyCastleProvider()); // adding provider to
String pathtoSave = "D://sureshtest.cer";
KeyPair keyPair = generateKeypair();
PublicKey publicKey = keyPair.getPublic();
PrivateKey privateKey = keyPair.getPrivate();
X509Certificate trustCert = createCertificate(null,"CN=DigiCorp",
"CN=Nextenders", publicKey, privateKey);
// Create an input stream from the file containing the certificate.
InputStream is =new FileInputStream(new File("D://validcertFormCa.pfx"));
/*
* CertificateFactory object is used for reading Certificates, CRL and
* CertPaths. Create a factory object using the standard SPI pattern
* used in JCA.
*/
CertificateFactory factory =
CertificateFactory.getInstance("X.509", "BC");
/*
* Generate a X509 Certificate initialized with the data read from the
* input stream.
*/
X509Certificate mastercert =
(X509Certificate) factory.generateCertificate(is);
java.security.cert.Certificate[] outChain = { trustCert,mastercert };
trustCert.checkValidity();
mastercert.checkValidity();
KeyStore outStore = KeyStore.getInstance("PKCS12");
outStore.load(null, null);
outStore.setKeyEntry("my own certificate", privateKey,
"admin123".toCharArray(), outChain);
OutputStream outputStream = new FileOutputStream(pathtoSave);
outStore.store(outputStream, "admin123".toCharArray());
outputStream.flush();
outputStream.close();
} catch (Exception e) {
e.printStackTrace();
}
And run into the exception
org.bouncycastle.jcajce.provider.asymmetric.x509.CertificateFactory$ExCertificateException
at org.bouncycastle.jcajce.provider.asymmetric.x509.CertificateFactory.engineGenerateCertificate(Unknown Source)
at java.security.cert.CertificateFactory.generateCertificate(Unknown Source)
at com.nextenders.certificategeenrator.CertificateGenerator.testGenerateSignCertWithKeyStore(CertificateGenerator.java:119)
at com.nextenders.facadeimplementation.facade.JUnitFacade.main(JUnitFacade.java:11)
Caused by: java.lang.IllegalArgumentException: unknown object in getInstance: org.bouncycastle.asn1.ASN1Integer
at org.bouncycastle.asn1.ASN1Sequence.getInstance(Unknown Source)
at org.bouncycastle.asn1.x509.TBSCertificate.getInstance(Unknown Source)
at org.bouncycastle.asn1.x509.Certificate.<init>(Unknown Source)
at org.bouncycastle.asn1.x509.Certificate.getInstance(Unknown Source)
at org.bouncycastle.jcajce.provider.asymmetric.x509.CertificateFactory.readDERCertificate(Unknown Source)
... 4 more
What is mastercert supposed to be?
According to the docs for generateCertificate(), it expects that a "certificate provided in inStream must be DER-encoded and may be supplied in binary or printable (Base64) encoding". In other words, a DER or PEM encoded X509 certificate.
What you're providing it via that InputStream is a PFX file (a PKCS#12 file), not a DER or PEM encoded certificate.
My advice is to use openssl pkcs12 to extract the necessary certificate from the PKCS#12 file, and place it into a separate file, then change the code to load that instead of your PFX file.
So my problem is as follows,
Basically I want to create a certificate chain using bouncy castle (jdk16 version 1.46). I am rather new to bouncy castle and java.security in general so if my approach might be completely wrong, but anyway this is what I did:
So far I am able to create a self signed certificate which I use as the root certificate. This is done using the following code:
//-----create CA certificate with key
KeyPair caPair = Signing.generateKeyPair("DSA", 1024, null, null);
This basically creates the keypair, the two null options are for a provider and a secure random, if needed.
Map<ASN1ObjectIdentifier, Entry<Boolean, DEREncodable>> caMap = new HashMap<ASN1ObjectIdentifier, Entry<Boolean, DEREncodable>>();
caMap.put(X509Extensions.BasicConstraints, new AbstractMap.SimpleEntry<Boolean, DEREncodable>(true, new BasicConstraints(true)));
//------this creates the self signed certificate
X509Certificate caCert = X509CertificateGenerator.generateX509Certificate(serial, "CN=CA", "CN=CA", start, end, "SHA1withDSA", caPair.getPrivate(), caPair.getPublic(), null, caMap);
This will create the a certificate with the provided attributes.
serial = simply the current time in milliseconds
start = same as serial basically (may have 1 or 2 milliseconds difference)
end = start + 2 days
The map simply adds the basic contraint to set the certificate to be a CA. I use a map here since I want to be able to add additional X509Extensions if need be.
//-----save ca certificate in PEM format
X509CertificateGenerator.savePemX509Certificate(caCert, caPair.getPrivate(), caWriter);
This will store the certificate and private key in a pem file using the bouncy caste pem writer.
After that the file is generated and I can install the file as well (I use IE and then install it via the Internet Options as a trusted CA. The certificate is also shown to be valid).
After that I create the intermediate certificate, using the following code (note the above code is in the same scope so those variables are available as well)
KeyPair intermediatePair = Signing.generateKeyPair("DSA", 1024, null, null);
Map<ASN1ObjectIdentifier, Entry<Boolean, DEREncodable>> intermediateMap = new HashMap<ASN1ObjectIdentifier, Entry<Boolean, DEREncodable>>();
intermediateMap.put(X509Extensions.AuthorityKeyIdentifier, new AbstractMap.SimpleEntry<Boolean, DEREncodable>(false, new AuthorityKeyIdentifierStructure(caCert)));
intermediateMap.put(X509Extensions.SubjectKeyIdentifier, new AbstractMap.SimpleEntry<Boolean, DEREncodable>(false, new SubjectKeyIdentifierStructure(intermediatePair.getPublic())));
X509Certificate intermediateCert = X509CertificateGenerator.generateX509Certificate(serial.add(BigInteger.valueOf(1l)), "CN=intermediate", caCert.getSubjectX500Principal().toString(), start, end, "SHA1withDSA", caPair.getPrivate(), intermediatePair.getPublic(), null, intermediateMap);
//-----save intermediate certificate in PEM format
X509CertificateGenerator.savePemX509Certificate(intermediateCert, intermediatePair.getPrivate(), intermediateWriter);
The procedure is bascially the same, however I add additional X509Extensions:
X509Extensions.AuthorityKeyIdentifier = sets the CA certificate as the intermediates parent
X509Extensions.SubjectKeyIdentifier = uses the generates public key for the certificate
furthermore the CA is used as the issuer and the CA private key is used to create the intermediate certificate.
This also works and I can install the intermediate certificate (using IE again), it is also shown that the parent certififcate is the generated CA certificate and that the certificate is valid.
Now comes the tricky part where I am making a mistake I guess. I now create a new certificate using the intermediate certificate, using the following code.
KeyPair endPair = Signing.generateKeyPair("DSA", 1024, null, null);
Map<ASN1ObjectIdentifier, Entry<Boolean, DEREncodable>> endMap = new HashMap<ASN1ObjectIdentifier, Entry<Boolean, DEREncodable>>();
endMap.put(X509Extensions.AuthorityKeyIdentifier, new AbstractMap.SimpleEntry<Boolean, DEREncodable>(false, new AuthorityKeyIdentifierStructure(intermediateCert)));
endMap.put(X509Extensions.SubjectKeyIdentifier, new AbstractMap.SimpleEntry<Boolean, DEREncodable>(false, new SubjectKeyIdentifierStructure(endPair.getPublic())));
X509Certificate endCert = X509CertificateGenerator.generateX509Certificate(serial.add(BigInteger.valueOf(1l)), "CN=end", intermediateCert.getSubjectX500Principal().toString(), start, end, "SHA1withDSA", intermediatePair.getPrivate(), endPair.getPublic(), null, endMap);
X509CertificateGenerator.savePemX509Certificate(endCert, endPair.getPrivate(), endWriter);
Essentially it is the same as creating the intermediate certificate. However I now use the following X509Extension settings:
X509Extensions.AuthorityKeyIdentifier = sets the intermediate certificate as the certificates parent
X509Extensions.SubjectKeyIdentifier = uses the generates public key for the certificate
Also the intermediate certificate is used as the issuer and its private key is used to create the certificate.
I can also install the new certificate but when I examine if (again IE), it shows that the certificate is however invalid because "This CA is either not entitled to issue certificates or the certificate can not be used as an end-entity."
So I somehow need to enable the intermediate certificate to be able to create new certificates as well, by adding some KeyUsages/ExtendedKeyUsage I assume.
Does someone know how I enable the intermediate certificate to do what I need it to do or if I do something wrong in general ?
EDIT 1:
So okay I forgot to provide the code for the method which created the certificate and the one that saved it in PEM format (I renamed it to savePemX509Certificate since the old one was misguiding).
Code for the certificate generation:
public static X509Certificate generateX509Certificate(BigInteger serialnumber, String subject, String issuer, Date start , Date end, String signAlgorithm, PrivateKey privateKey, PublicKey publicKey, String provider, Map<ASN1ObjectIdentifier, Entry<Boolean, DEREncodable>> map) throws CertificateEncodingException, InvalidKeyException, IllegalStateException, NoSuchProviderException, NoSuchAlgorithmException, SignatureException
{
if(serialnumber!=null && subject!=null && issuer!=null && start!=null && end!=null && signAlgorithm !=null && privateKey!=null && publicKey!=null)
{
//-----GENERATE THE X509 CERTIFICATE
X509V3CertificateGenerator certGen = new X509V3CertificateGenerator();
X509Principal dnSubject = new X509Principal(subject);
X509Principal dnIssuer = new X509Principal(issuer);
certGen.setSerialNumber(serialnumber);
certGen.setSubjectDN(dnSubject);
certGen.setIssuerDN(dnIssuer);
certGen.setNotBefore(start);
certGen.setNotAfter(end);
certGen.setPublicKey(publicKey);
certGen.setSignatureAlgorithm(signAlgorithm);
//-----insert extension if needed
if(map!=null)
for(ASN1ObjectIdentifier extension : map.keySet())
certGen.addExtension(extension, map.get(extension).getKey(), map.get(extension).getValue());
return certGen.generate(privateKey, provider);
}
return null;
}
Code for the saveing of the certificate and key:
public static boolean savePemX509Certificate(X509Certificate cert, PrivateKey key, Writer writer) throws NoSuchAlgorithmException, NoSuchProviderException, CertificateEncodingException, SignatureException, InvalidKeyException, IOException
{
if(cert!=null && key!=null && writer!=null)
{
PEMWriter pemWriter = new PEMWriter(writer);
pemWriter.writeObject(cert);
pemWriter.flush();
if(key!=null)
{
pemWriter.writeObject(key);
pemWriter.flush();
}
pemWriter.close();
return true;
}
return false;
}
As you can see I basically put the certificate and the key in the file, thats all. The result is the following and seems good to me.
-----BEGIN CERTIFICATE-----
MIICdjCCAjagAwIBAgIGAUDuXLRLMAkGByqGSM44BAMwDTELMAkGA1UEAwwCQ0Ew
HhcNMTMwOTA1MTM0MzA3WhcNMTMwOTA3MTM0MzA3WjANMQswCQYDVQQDDAJDQTCC
AbcwggEsBgcqhkjOOAQBMIIBHwKBgQD9f1OBHXUSKVLfSpwu7OTn9hG3UjzvRADD
Hj+AtlEmaUVdQCJR+1k9jVj6v8X1ujD2y5tVbNeBO4AdNG/yZmC3a5lQpaSfn+gE
exAiwk+7qdf+t8Yb+DtX58aophUPBPuD9tPFHsMCNVQTWhaRMvZ1864rYdcq7/Ii
Axmd0UgBxwIVAJdgUI8VIwvMspK5gqLrhAvwWBz1AoGBAPfhoIXWmz3ey7yrXDa4
V7l5lK+7+jrqgvlXTAs9B4JnUVlXjrrUWU/mcQcQgYC0SRZxI+hMKBYTt88JMozI
puE8FnqLVHyNKOCjrh4rs6Z1kW6jfwv6ITVi8ftiegEkO8yk8b6oUZCJqIPf4Vrl
nwaSi2ZegHtVJWQBTDv+z0kqA4GEAAKBgAeFoGATLbIr8+QNuxcbYJ7RhbefKWSC
Br67Pp4Ynikxx8FZN4kCjGX7pwT1KffN3gta7jxIXNM5G3IFbs4XnYljh5TbdnjP
9Ge3kxpwncsbMQfCqIwHh8T5gh55KaxH7yYV2mrtEEqj7NBL4thQhJe2WGwgkB9U
NxNmLoMq3m4poyMwITAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAJ
BgcqhkjOOAQDAy8AMCwCFFm5ybLY09y8y2uGsEnpceffy2KaAhQIyshgy3ohCLxQ
q3CmnvC+cfT2VQ==
-----END CERTIFICATE-----
-----BEGIN DSA PRIVATE KEY-----
MIIBuwIBAAKBgQD9f1OBHXUSKVLfSpwu7OTn9hG3UjzvRADDHj+AtlEmaUVdQCJR
+1k9jVj6v8X1ujD2y5tVbNeBO4AdNG/yZmC3a5lQpaSfn+gEexAiwk+7qdf+t8Yb
+DtX58aophUPBPuD9tPFHsMCNVQTWhaRMvZ1864rYdcq7/IiAxmd0UgBxwIVAJdg
UI8VIwvMspK5gqLrhAvwWBz1AoGBAPfhoIXWmz3ey7yrXDa4V7l5lK+7+jrqgvlX
TAs9B4JnUVlXjrrUWU/mcQcQgYC0SRZxI+hMKBYTt88JMozIpuE8FnqLVHyNKOCj
rh4rs6Z1kW6jfwv6ITVi8ftiegEkO8yk8b6oUZCJqIPf4VrlnwaSi2ZegHtVJWQB
TDv+z0kqAoGAB4WgYBMtsivz5A27FxtgntGFt58pZIIGvrs+nhieKTHHwVk3iQKM
ZfunBPUp983eC1ruPEhc0zkbcgVuzhediWOHlNt2eM/0Z7eTGnCdyxsxB8KojAeH
xPmCHnkprEfvJhXaau0QSqPs0Evi2FCEl7ZYbCCQH1Q3E2YugyrebikCFDJCJHtt
NWB4LWYc4y4QvJ/l46ap
-----END DSA PRIVATE KEY-----
So after gtrig provided me with the correct way to create the certificate, I ended up using this method to create either a normal or self signed (if the private key is from the same keyPair as the public key that is) certificate
public static X509Certificate createX509V3Certificate(X500Principal name, BigInteger serial, Date start, Date end, PublicKey pubKey, String algorithm, PrivateKey privateKey, Map<ASN1ObjectIdentifier, Entry<Boolean, ASN1Object>> map, X509Certificate parentCert) throws IOException, OperatorCreationException, CertificateException
{
if(serial!=null && start!=null && end!=null && name!=null && pubKey!=null && algorithm!=null && privateKey!=null)
{
ContentSigner signer = new JcaContentSignerBuilder(algorithm).build(privateKey);
X509v3CertificateBuilder certBldr = null;
if(parentCert==null)
certBldr = new JcaX509v3CertificateBuilder(name, serial, start, end, name, pubKey);
else
certBldr = new JcaX509v3CertificateBuilder(parentCert, serial, start, end, name, pubKey);
if(map!=null)
for(ASN1ObjectIdentifier extension : map.keySet())
certBldr.addExtension(extension, map.get(extension).getKey(), map.get(extension).getValue());
return new JcaX509CertificateConverter().setProvider("BC").getCertificate(certBldr.build(signer));
}
return null;
}
Something looks wrong with the way you're creating the PEM files. You're using a method called, generateSelfSignedPemX509Certificate, but you don't really want a self-signed certificate, you want an end certificate signed by the intermediate private key, and you want an intermediate certificate signed by the CA private key.
Also, you need basic constraints and key usage extensions on your certificates.
For creating certificates signed by other entities (non-self-signed), I use these methods from Bouncy Castle to create an "end" certificate.
ASN1Sequence seq=
(ASN1Sequence) new ASN1InputStream(parentPubKey.getEncoded()).readObject();
SubjectPublicKeyInfo parentPubKeyInfo = new SubjectPublicKeyInfo(seq);
ContentSigner signer = new JcaContentSignerBuilder(algorithm).build(parentPrivKey);
X509v3CertificateBuilder certBldr =
new JcaX509v3CertificateBuilder(
parentCert,
serialNum,
startDate,
endDate,
distName,
pubKey)
.addExtension(
new ASN1ObjectIdentifier("2.5.29.35"),
false,
new AuthorityKeyIdentifier(parentPubKeyInfo))
.addExtension(
new ASN1ObjectIdentifier("2.5.29.19"),
false,
new BasicConstraints(false)) // true if it is allowed to sign other certs
.addExtension(
new ASN1ObjectIdentifier("2.5.29.15"),
true,
new X509KeyUsage(
X509KeyUsage.digitalSignature |
X509KeyUsage.nonRepudiation |
X509KeyUsage.keyEncipherment |
X509KeyUsage.dataEncipherment));
// Build/sign the certificate.
X509CertificateHolder certHolder = certBldr.build(signer);
X509Certificate cert = new JcaX509CertificateConverter().setProvider(BC)
.getCertificate(certHolder);
For a CA or intermediate certificate, you'll need to add a SubjectKeyIdentifier extension. Also, BasicConstraints should be true, and KeyUsage should be:
new X509KeyUsage(
X509KeyUsage.keyCertSign|
X509KeyUsage.cRLSign));