Reading CA Cert Private Key to Sign Certificate - java

I have a requirement as below:
Create a self signed (say CA cert) and save the cert and private key
to files
Load the CA Cert (Created in Step 1) and its private key
Create a end
certificate which is signed using CA Cert and Private Key loaded in
Step 2
My Private Key is Stored to a file as below:
public static void writePrivateKey(PrivateKey privateKey, OutputStream os) throws IOException
{
BufferedOutputStream bos = new BufferedOutputStream(os);
PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(
privateKey.getEncoded());
bos.write(pkcs8EncodedKeySpec.getEncoded());
bos.close();
}
My private Key is loaded back as below:
public PrivateKey loadPrivatekey(InputStream privateKeyInputStream)
throws InvalidKeySpecException, NoSuchAlgorithmException, IOException
{
return KeyFactory.getInstance(algorithamForCreatingAndLoadingKeys)
.generatePrivate(new PKCS8EncodedKeySpec(IOUtils.toByteArray(privateKeyInputStream)));
}
I have my algorithm defined as RSA
private String algorithamForCreatingAndLoadingKeys = "RSA";
Iam signing my certificate as below:
private static X509CertImpl buildAndSignCert(X509CertInfo certInfo, PrivateKey privateKey)
throws CertificateException, NoSuchAlgorithmException, InvalidKeyException,
NoSuchProviderException, SignatureException, IOException
{
X509CertImpl cert = new X509CertImpl(certInfo);
String algorithm = "SHA1withRSA";
// Sign the cert to identify the algorithm that's used.
cert.sign(privateKey, algorithm);
// Update the algorith, and resign.
certInfo.set(CertificateAlgorithmId.NAME + "." + CertificateAlgorithmId.ALGORITHM,
cert.get(X509CertImpl.SIG_ALG));
X509CertImpl newCert = new X509CertImpl(certInfo);
newCert.sign(privateKey, algorithm);
return newCert;
}
Problem: If i create the CA cert and end certificate without saving and loading the key file I was able to validate the end certificate fine:
C:\Workspace.....\src\main\resources>openssl verify -CAfile ca.pem end.pem
end.pem: OK
But If i save and load the key files and verify, I get below error, which clearly says that my end certificate is not signed correct with my ca cert
C:\Workspace.....\src\main\resources>openssl verify -CAfile ca.pem
end.pem
end.pem: C = AU, L = ABC, CN = XYZ
error 20 at 0 depth lookup:unable to get local issuer certificate
So, Iam coming to the conclusion that my saving of private key and loading it is buggered.
Can any one pleas help what Iam doing wrong in saving and reading private keys ?
Many Thanks in advance.

All the code that I have posted above is correct and accurate to create and read a private key file.
I have spotted the problem to be with loading the Created CA Cert itself (not the private key), some how when we generate X509 Certificate as below:
CertificateFactory f = CertificateFactory.getInstance("X.509");
X509Certificate loadedCaCert = (X509Certificate) f
.generateCertificate(CertificateGenerator.class.getResourceAsStream("/ca.pem"));
the serial number that it is producing out of X509Certificate generated above is not the same as the one I passed when creating CA certtificate (Which is another issue but not realted to thsi thread anyways) and as we pass this serial number to subscriber certificate to identify its parent CA for certificate linking its failing.
Thank You

Related

Import elliptic curve Certificate and Private Key into Java Keystore using java.security.KeyStore

I'm currently working on a project that involves getting an Elliptic Curve Certificate / Private Key package in either PEM or DER format from Vault and needing to import it into a Java Keystore. I can find plenty of information on doing this with keytool, but am unable to find information about how to successfully convert a DER encoded string into a PrivateKeyEntry to insert it into the keystore.
Below is my non-working code. certificate , key, and issuingCa are all PEM or DER encoded Strings (I can specify which format I want to get back from the issuer and pass whichever I can get to work)
private KeyStore packKeystore(String certificate, String key, String issuingCa, String name) throws KeyStoreException, CertificateException, NoSuchAlgorithmException, IOException {
// Create the keystore
KeyStore retVal = KeyStore.getInstance(KeyStore.getDefaultType());
retVal.load(null, sslKeystorePassword.toCharArray());
var cf = CertificateFactory.getInstance("X.509", BouncyCastleProvider.PROVIDER_NAME);
var cert = (X509Certificate) cf.generateCertificate(new ByteArrayInputStream(certificate.getBytes()));
retVal.setCertificateEntry(name, cert);
Certificate issuer = null;
if (issuingCa != null) {
issuer = cf.generateCertificate(new ByteArrayInputStream(issuingCa.getBytes(StandardCharsets.UTF_8)));
}
if (key != null) {
var certs = new HashSet<Certificate>();
certs.add(issuer);
certs.add(cert);
PrivateKeyEntry pk = /// How do I create this from what I have????
retVal.setKeyEntry( pk, certs.toArray());
}
return retVal;
}
After some experimentation and research I've learned that the PrivateKey class doesn't like the "old" PEM format where the private key looks like ---- BEGIN EC PRIVATE KEY-----.
I was eventually able to parse a PrivateKey object from a keypair like this:
var parsedKey = new org.bouncycastle.openssl.PEMParser(new StringReader(key)).readObject();
var pair = new org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter().getKeyPair((org.bouncycastle.openssl.PEMKeyPair) parsedKey);
retVal.setKeyEntry(name, pair.getPrivate(), "".toCharArray(), certArray);
From there I get an error on setKeyEntry about the certificate algorithm not matching the private key algorithm. Upon inspection it seems that the Certificate object says the algo is EC and the PK object says the algo is ECDSA.
I eventually solved it this way. Note the key must be in base64 encoded DER format:
private PrivateKey convertECPrivateKeyString(String key) {
...
byte[] keyBytes = null;
if (key != null) {
keyBytes = Base64.getDecoder().decode(key);
}
try (var asnStream = new ASN1InputStream(keyBytes)) {
var primitive = asnStream.readObject();
asnStream.close();
if (primitive instanceof ASN1Sequence) {
var sequence = (ASN1Sequence) primitive;
var pKey = org.bouncycastle.asn1.sec.ECPrivateKey.getInstance(sequence);
var pkInfo = new PrivateKeyInfo(new AlgorithmIdentifier(X9ObjectIdentifiers.id_ecPublicKey, pKey.getParameters()), pKey);
return new JcaPEMKeyConverter().getPrivateKey(pkInfo);
}
}
...
}

Saving Signed X509Certificate and PrivateKey in Android

I would like to save a X509Certificate and its private key into the Android KeyStore, I tought I should 'merge' the X509Certificate (containing the public key) and its private key. The private key is used to create a CSR and then a server party sign the certificate and return to the application, can I merge the cert and the private key into one unique cert? Also I'm using spongycastle (aka bouncycastle's android wrapper).
I have no idea about Android KeyStore, but maybe you can try something like:
PrivateKey privateKey = ... //this is what you already have
X509Certificate certificate = ... //this is what you already have
KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
keyStore.load(null);
Certificate[] certChain = new Certificate[1];
certChain[0] = certificate;
char[] myKeyPassword = "myKeyPassword".toCharArray();
keyStore.setKeyEntry("mykeyalias", (Key)privateKey, myKeyPassword, certChain);
See https://docs.oracle.com/javase/9/docs/api/java/security/KeyStore.html#setKeyEntry-java.lang.String-java.security.Key-char:A-java.security.cert.Certificate:A- for more information about KeyStore.setKeyEntry

Return .p12 file to client without creating keystore file

Is there any way to return a file to client with .p12 extension (base64 encoded string, that is later decoded on the client side and saved with .p12 extension) without storing it to PKCS12 keystore? I have code for creating root certificate, client certificate and setting keyentry to PKCS12 keystore bellow, but I don't want to have .p12 file on the file system, just to generate it and return it to client. Thanks!
Simplified code of creating root certificate:
public static void createRootCertificate(PublicKey pubKey, PrivateKey privKey) {
certGen.setSerialNumber(...);
certGen.setIssuerDN(...);
certGen.setNotBefore(...);
certGen.setNotAfter(...);
certGen.setSubjectDN(...);
certGen.setPublicKey(pubKey);
certGen.setSignatureAlgorithm("SHA1WithRSA");
// add extensions, key identifier, etc.
X509Certificate cert = certGen.generateX509Certificate(privKey);
cert.checkValidity(new Date());
cert.verify(pubKey);
}
The root certificate and its private key is saved to the trusted store after creating.
Than, in the service for generating client certificates, I read root certificate from trusted store and generate client ones:
public static Certificate createClientCertificate(PublicKey pubKey) {
PrivateKey rootPrivateKey = ... //read key from trusted store
X509Certificate rootCertificate = ... //read certificate from trusted store
certGen.setSerialNumber(...);
certGen.setIssuerDN(...); // rootCertificate.getIssuerDN ...
certGen.setNotBefore(...);
certGen.setNotAfter(...);
certGen.setSubjectDN(...);
certGen.setPublicKey(pubKey);
certGen.setSignatureAlgorithm("SHA1WithRSA");
// add extensions, issuer key, etc.
X509Certificate cert = certGen.generateX509Certificate(rootPrivateKey);
cert.checkValidity(new Date());
cert.verify(rootCertificate.getPublicKey(););
return cert;
}
Main class look like this:
public static void main(String[] args) {
// assume I have all needed keys generated
createRootCertificate(rootPubKey, rootPrivKey);
X509Certificate clientCertificate = createClientCertificate(client1PubKey);
KeyStore store = KeyStore.getInstance("PKCS12", "BC");
store.load(null, null);
store.setKeyEntry("Client1_Key", client1PrivKey, passwd, new Certificate[]{clientCertificate});
FileOutputStream fOut = new FileOutputStream("client1.p12");
store.store(fOut, passwd);
}
After the code above, I'm reading client1.p12 and I'm creating Base64 encoded response of that file. When I decode response on my client and save with .p12 extension everything works, I can import it to browser. Can this be done without storing it to file?
I have tried with:
store.setKeyEntry("Client1_Key", client1PrivKey, passwd, new Certificate[]{clientCertificate});
and after that:
Key key = store.getKey("Client1_Key", passwd);
but when encode key variable, send to client and than decode it and save with .p12 extension, browser say invalid or corrupted file.
Thanks in advance!
Simply use a ByteArrayOutputStream instead of FileOutputStream to store the p12:
ByteArrayOutputStream baos = new ByteArrayOutputStream();
store.store(baos, passwd);
byte[] p12Bytes = baos.toByteArray();
String p12Base64 = new String(Base64.encode(p12Bytes));

Generating certification chain

I need to generate certification chain in my java application becouse its needed when storing privatekey to keystore? Can anybody help me out. I have no idea how to do it..
I need to generate RSA keypair and then store it to keystore. Right now my code looks like this:
public static void main(String[] args)
{
String issuerDN = null;
String addKeyName = "mynewkey";
String delKeyName = null;
String password = "2222";
boolean listStore = true;
boolean deleteKeysAftherWrap = false;
try
{
/* make sure that we have access to the eracom provider */
Provider p = new ERACOMProvider();
Security.addProvider(p);
int keySize = 1024;
KeyPair keyPair = null;
/* get the eracom keystore - access to the adapter */
KeyStore keyStore = KeyStore.getInstance("CRYPTOKI", p.getName());
/* LOAD the keystore from the adapter */
keyStore.load(null, password.toCharArray());
if (addKeyName != null)
{
/* This key cannot be added to the keystore if it already exists */
if (keyStore.containsAlias(addKeyName))
{
println("");
println("Key name already exists");
println("");
}
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA", p.getName());
keyPairGenerator.initialize(keySize);
keyPair = keyPairGenerator.generateKeyPair();
PublicKey pubKey = keyPair.getPublic();
PrivateKey privKey = keyPair.getPrivate();
keyStore.setKeyEntry("newpub", pubKey, null, null);
keyStore.setKeyEntry("newpriv", privKey, null, null});
}
the keys are generated but it asks certification chain for storing private key.
And that is the problem right now. How can i generate the certification chain, do i have to generate certifications first, when yes then how?
Not sure what are you trying to achieve, but some time ago I've used this little app (source code included) to insert an existing private key into a keystore. Hopefully you'll find this useful: http://www.agentbob.info/agentbob/79-AB.html
I believe the post http://www.pixelstech.net/article/1406726666-Generate-certificate-in-Java----2 will show you how to generate certificate chain with pure Java. It doesn't require you to use Bouncy Castle.
This post will show you how to generate a certificate chain which has a length longer than 1. While most posts on the Internet will show you creating a certificate chain of length 1 or using BC.

How to read a private key for use with OpenSAML?

OK, this is another of those "I have no real idea where to start" questions, so hopefully the answer is simple. However, I don't really know what to search for, and my attempts so far haven't turned up much of use.
I want to read a private key from a (currently on-disk) file. Ultimately the key will reside in a database, but this will be good enough for the moment and that difference should have no real bearing on parsing the key material. I have been able to create a Credential instance that holds the public part of the key (confirmed by debugger), but I can't seem to figure out how to read the private part. The key pair was generated as:
openssl genrsa 512 > d:\host.key
openssl req -new -x509 -nodes -sha1 -days 365 -key d:\host.key > d:\host.cert
(Yes, I know that 512 bit RSA keys were broken long ago. However, for trying to get the API to work, I see no reason to exhaust the system entropy supply needlessly.)
The code thus far is:
import org.opensaml.xml.security.credential.Credential;
import org.opensaml.xml.security.x509.BasicX509Credential;
private Credential getSigningCredential()
throws java.security.cert.CertificateException, IOException {
BasicX509Credential credential = new BasicX509Credential();
credential.setUsageType(UsageType.SIGNING);
// read public key
InputStream inStream = new FileInputStream("d:\\host.cert");
CertificateFactory cf = CertificateFactory.getInstance("X.509");
X509Certificate cert = (X509Certificate)cf.generateCertificate(inStream);
inStream.close();
credential.setEntityCertificate(cert);
// TODO: read private key
// done.
return credential;
}
But how do I read the file host.key into the private key portion of credential, so I can use the generated Credential instance to sign data?
BasicX509Credential is not part from standard Java; I suppose you are talking about org.opensaml.xml.security.x509.BasicX509Credential from OpenSAML.
You want a PrivateKey which you will set with credential.setPrivateKey(). To get a PrivateKey, you must first convert the private key into a format that Java can read, namely PKCS#8:
openssl pkcs8 -topk8 -nocrypt -outform DER < D:\host.key > D:\host.pk8
Then, from Java:
RandomAccessFile raf = new RandomAccessFile("d:\\host.pk8", "r");
byte[] buf = new byte[(int)raf.length()];
raf.readFully(buf);
raf.close();
PKCS8EncodedKeySpec kspec = new PKCS8EncodedKeySpec(buf);
KeyFactory kf = KeyFactory.getInstance("RSA");
PrivateKey privKey = kf.generatePrivate(kspec);
and voilĂ ! you have your PrivateKey.
By default, openssl writes key in its own format (for RSA keys, PKCS#8 happens to be a wrapper around that format), and it encodes them in PEM, which Base64 with a header and a footer. Both characteristics are unsupported by plain Java, hence the conversion to PKCS#8. The -nocrypt option is because PKCS#8 supports optional password-based encryption of private key.
Warning: you really really want to use a longer RSA key. 512 bits are weak; a 512-bit RSA key was broken in 1999 with a few hundred computers. In 2011, with 12 years of technological advances, one should assume that a 512-bit RSA key can be broken by almost anybody. Therefore, use 1024-bit RSA keys at least (preferably, 2048-bit; the computational overhead when using the key is not that bad, you will still be able to perform hundred of signatures per second).
This question is related to SAML and is also relevant for someone who wants to retrieve a private key for signing an XMLObject. The answer above works great and it also possible to retrieve a private key from a keystore as well:
Properties sigProperties = new Properties();
sigProperties.put("org.apache.ws.security.crypto.provider","org.apache.ws.security.components.crypto.Merlin");
sigProperties.put("org.apache.ws.security.crypto.merlin.keystore.type","jks");
sigProperties.put("org.apache.ws.security.crypto.merlin.keystore.password","keypass");
sigProperties.put("org.apache.ws.security.crypto.merlin.keystore.alias","keyalias");
sigProperties.put("org.apache.ws.security.crypto.merlin.keystore.file","keystore.jks");
Crypto issuerCrypto = CryptoFactory.getInstance(sigProperties);
String issuerKeyName = (String) sigProperties.get("org.apache.ws.security.crypto.merlin.keystore.alias");
//See http://ws.apache.org/wss4j/xref/org/apache/ws/security/saml/ext/AssertionWrapper.html 'signAssertion' method
// prepare to sign the SAML token
CryptoType cryptoType = new CryptoType(CryptoType.TYPE.ALIAS);
cryptoType.setAlias(issuerKeyName);
X509Certificate[] issuerCerts = issuerCrypto.getX509Certificates(cryptoType);
if (issuerCerts == null) {
throw new WSSecurityException(
"No issuer certs were found to sign the SAML Assertion using issuer name: "
+ issuerKeyName);
}
String password = ADSUnitTestUtils.getPrivateKeyPasswordFromAlias(issuerKeyName);
PrivateKey privateKey = null;
try {
privateKey = issuerCrypto.getPrivateKey(issuerKeyName, password);
} catch (Exception ex) {
throw new WSSecurityException(ex.getMessage(), ex);
}
BasicX509Credential signingCredential = new BasicX509Credential();
signingCredential.setEntityCertificate(issuerCerts[0]);
signingCredential.setPrivateKey(privateKey);
signature.setSigningCredential(signingCredential);
This is more code than the original query requested, but it looks they are trying to get at a BasicX509Credential.
Thanks,
Yogesh

Categories