How to convert Curl SSL requests into Java code - java

Curl allows making 2-way SSL requests by specifying the certificate and private key files like the two curl requests below
First get an access token:
$ curl https://connect2.server.com/auth/token
-H "Authorization: Basic $BASIC_AUTH"
--cert ~/certs/certpath/fullchain.pem
--key ~/certs/certpath/privkey.pem
Then use the token to access the API:
$ curl https://connect2.server.com/api/public/preview1/orgs/$ORGUUID/API
-H "Authorization: Bearer $ACCESS_TOKEN"
--cert ~/certs/certpath/fullchain.pem
--key ~/certs/certpath/privkey.pem
Question:
How to implement the above requests in Java? What libraries are required? Java seems to use p12 file, however, we have .pem files.

‌1. You can convert PEM privatekey plus chain to a PKCS12 file using openssl pkcs12 -export. That is not programming or development and no longer ontopic here, but there are dozens of Qs about this here going back many years when topicality was broader, as well as in other Stacks (security.SX, serverfault, superuser, maybe more).
‌2. If you don't have or dislike OpenSSL, you can read those files (among others) into any kind of Java keystore (JCEKS, JKS, PKCS12, and several BouncyCastle variants you probably don't want) using the software from https://www.keystore-explorer.org . That's also offtopic, and I've seen some existing Qs mention it but not many.
‌3. If you want to do this with your own code, which is ontopic, and assuming your curl uses OpenSSL or at least those files are OpenSSL formats:
3.0 Java can read PEM cert sequence with CertificateFactory.getInstance("X.509") then generateCertificates(InputStream) (note s) -- the doc is a bit sketchy but this method actually can handle separate certs as DER or PEM (which you apparently have), or PKCS7 containing certs as a single blob (commonly called p7b or p7c) ditto.
3.1 if that privkey file is PKCS8-unencrypted, i.e. if the PEM labels are BEGIN/END PRIVATE KEY with no other word between, that case can be handled by standard Java, assuming you know what algorithm it is for (which if necessary you can determine from the first=leaf certificate). Delete the BEGIN/END lines and decode the rest from base64 to binary either ignoring linebreaks (with Base64.getMimeDecoder()) or with .getDecoder() after deleting the linebreaks. Put the result in PKCS8EncodedKeySpec and feed it to generatePrivate in a KeyFactory instance for the correct algorithm.
3.2 BouncyCastle (bcpkix+bcprov) can read all the PEM formats for privatekey used by OpenSSL with PEMParser and JcaPEMKeyConverter and if applicable a DecryptorBuilder. There are many existing Qs on this that you can find with that fairly-unique classname. This does mean a dependency on Bouncy.
3.3 if you don't have or don't want Bouncy and have a format other than PKCS8-unencrypted, life gets harder. You could avoid this by using OpenSSL to convert the privkey file to PKCS8-unencrypted putting you back in #3.1, but if you do that you might as well go way back to #1 and use OpenSSL to convert the lot to PKCS12 in one foop.
if you have an OpenSSL 'traditional' algorithm-specific format like BEGIN/END RSA PRIVATE KEY or BEGIN/END EC PRIVATE KEY, and the first two lines after BEGIN are NOT Proc-type: 4 and DEK-info, you can base64-decode the body and convert it to PKCS8 by adding a (DER) prefix in front that specifies the algorithm and 'wraps' the algorithm-specific part. I think there are dupes for this but I can't presently find any; if this case applies and you identify the algorithm I'll add it.
if you have a 'traditional' format that does have Proc-type: 4 and DEK-info, or you have BEGIN/END ENCRYPTED PRIVATE KEY, those are encrypted. Making sense of them with only standard Java is a fair bit of work which I'll do only if you can't use the other options and specify exactly what case you have.

Following are the steps & code to add SSL certificates into HTTP Post request.
STEP 1. CONVERT PEM CERTIFICATE TO P12 FORMAT
openssl pkcs12 -export -out cacert.p12 -inkey /etc/letsencrypt/archive/server/privkey21.pem -in /etc/letsencrypt/archive/server/cert21.pem -certfile /etc/letsencrypt/archive/server/chain21.pem -passin pass:PWD -passout pass:PWD
STEP 2. (OPTIONAL NOT REQUIRED) CONVERT CERTIFICATE P12 TO JKS FORMAT
keytool -importkeystore -srckeystore cacert.p12 -srcstoretype pkcs12 -destkeystore cacert.jks
STEP 3. ADD CERTIFICATE TO HTTP POST REQUEST THROUGH SSLSocketFactory
/**
* This function is responsible to create createSSLSocketFactory with SSL certificate
* #return
*/
public static SSLSocketFactory createSSLSocketFactory(){
try
{
FileInputStream f5 = new FileInputStream(new File("/etc/letsencrypt/archive/server/cacert21.p12"));
KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
KeyStore ks1 = KeyStore.getInstance(KeyStore.getDefaultType());
ks1.load(f5, "PWD".toCharArray());
kmf.init(ks1, "PWD".toCharArray());
SSLContext sslContext = SSLContext.getInstance("SSL");
sslContext.init(kmf.getKeyManagers(), null, null);
f5.close();
return sslContext.getSocketFactory();
}
catch (Exception e) {
e.printStackTrace();
return null;
}
}

Related

Using java keytool to encrypt imported paswords with PBEWithHmacSHA256AndAES_128

I am looking to store sensitive passwords in a java keystore using java keytool's importpass. I am using Oracle java version 1.8.0_212, and cannot upgrade from java 8 at the moment.
I have created a PKCS12 keystore with the following command:
keytool -keystore test-keystore.p12 -genkey -storetype PKCS12 -alias test
I then imported a password into the keystore using:
keytool -importpass -storetype pkcs12 -alias protectedPass -keystore test-keystore.p12
This worked, but this uses the encryption algorithm "PBEWithMD5AndDES" by default, which isn't particularly secure. I am now trying to use "PBEWithHmacSHA256AndAES_128" from the Java Security Standard Algorithm Names doc but having issues getting this to work.
I've tried specifying the keyalg like this:
keytool -importpass -keyalg PBEWithHmacSHA256AndAES_128 -storetype pkcs12 -alias protectedPass -keystore test-keystore.p12 -v
and while this doesn't cause an error, it doesn't seem to actually affect the output. The secret is key still generated with PBEWithMD5AndDES:
D:\temp>keytool -importpass -keyalg PBEWithHmacSHA256AndAES_128 -storetype pkcs12 -alias protectedPass -keystore test-keystore.p12 -v
Enter keystore password:
Enter the password to be stored:
Re-enter password:
Generated PBEWithMD5AndDES secret key
[Storing test-keystore.p12]
I can see examples, such as in Java Keystores the Gory Details, of people using KeyStore.PasswordProtection to use algorithms like this, but I wanted to use the keytool if possible.
Am I missing something key here or trying to do something silly?
EDIT:
Tried to do something similar programmatically to figure out what I'm doing wrong and had no luck using PKCS12 keystores with this algorithm. However, jceks seems to work. I think this is to do with it using the SunJCE provider instead. Is there some parameter I am missing to get this algorithm working with pcks12? Or is there some other approach I could take?
I have provided some simple demo code below. If you swap "JCEKS" for "PKCS12" it will throw java.security.NoSuchAlgorithmException: unrecognized algorithm name: PBEWithHmacSHA256AndAES_128.
Demo Code:
static void encrypt() throws Exception {
KeyStore keyStore = KeyStore.getInstance("JCEKS");
keyStore.load(null, "changeit".toCharArray());
KeyStore.PasswordProtection keyStorePP = new KeyStore.PasswordProtection("changeit".toCharArray());
SecretKeyFactory pbeKeyFactory = SecretKeyFactory.getInstance("PBEWithHmacSHA256AndAES_128");
SecretKey pbeKey = pbeKeyFactory.generateSecret(new PBEKeySpec("testpassword".toCharArray()));
keyStore.setEntry(SECRET_KEY_ALIAS, new KeyStore.SecretKeyEntry(
pbeKey), keyStorePP);
FileOutputStream outputStream = new FileOutputStream(FILE_PATH);
keyStore.store(outputStream, "changeit".toCharArray());
}
static void decrypt() throws Exception {
KeyStore keyStore = KeyStore.getInstance("JCEKS");
FileInputStream fileInputStream = new FileInputStream(FILE_PATH);
keyStore.load(fileInputStream, "changeit".toCharArray());
KeyStore.PasswordProtection keyStorePP = new KeyStore.PasswordProtection("changeit".toCharArray());
KeyStore.SecretKeyEntry ske =
(KeyStore.SecretKeyEntry)keyStore.getEntry(SECRET_KEY_ALIAS, keyStorePP);
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBEWithHmacSHA256AndAES_128");
PBEKeySpec keySpec = (PBEKeySpec)factory.getKeySpec(
ske.getSecretKey(),
PBEKeySpec.class);
System.out.println(new String(keySpec.getPassword()));
}
You need to keep separate the algorithm a key is for and the way it is protected.
A Java keystore (depending on type, including JCEKS and PKCS12 but not JKS) can contain three kinds of entries. A SecretKeyEntry in general contains a secret key which is typically valid for a particular cipher algorithm, identified in the SecretKey object within the entry. But for password-based algorithms, the 'key' object is actually a generic 'PBEKey' which contains a password and AFAICT can be used for any PBE algorithm. It appears the 'algorithm' for such keys is actually stored as PBEwithMD5andDES, I suspect because that's the only scheme from original PKCS5 (i.e. PBES1) Java implements. However, my Oracle 8u212 keytool says more generically Generated PBE secret key in this case, not PBEwithMD5andDES as you show.
The stored secret key (and stored private key also) can be and normally is 'protected' in the keystore by being encrypted with a password-based encryption algorithm; this 'protection' algorithm is different from and in general not related to the algorithm(s) that the key or password will be used with after retrieval.
keytool -importpass -keyalg PBEWithHmacSHA256AndAES_128 attempts to specify the algorithm the key-really-password is nominally to be used with when retrieved, but not how it is protected, which for PKCS12 in most older versions including yours is actually PBE-SHA1-TripleDES (the same as for privatekeys) -- you can see this, with a little work, using OpenSSL. And in my 8u212 keytool specifying -keyalg fails for PKCS12 with an exception claiming it can't protect because there are no parameters for that algorithm (although I think the problem is serializing, not actually encrypting) much as you say your code does for PKCS12, but Java does have these parameters -- although in a different provider (SunJCE vs SunJSSE) which might be messing it up somehow; as you say JCEKS is in SunJCE.
Similarly your code generates a key to be used with that algorithm, but protected the default way. To change the protection you need to specify it in PasswordProtection, as stated in the page you link; see https://stackoverflow.com/a/47389384/ (by the same author) for an example.
Recent versions of Java -- 8u301 and 11.0.12 up -- have a new feature to configure the protection algorithms for PKCS12 with security properties in the java.security file which is in JRE/lib/security for 8 down or JRE/conf/security for 9 up. This applies to both code you write if not overridden as above, and to keytool (which can't override). However, those versions (except for 12 through 16, which are now EOL) also now default to PBEwithHmacSHA256andAES_256 which is already better than you asked for, so you probably don't need to change anything -- just use 8u301 up.
Note JCEKS uses a Sun-defined PBE-MD5-TripleDES similar to, but not, PBES1, while JKS doesn't support secretkey entries but for privatekey entries uses a deliberately weak (ITAR-friendly back in the 1990s) Sun-custom algorithm; these cannot be changed.

How do you convert PKCS#12 String to Certificate and PrivateKey?

I am receiving the following String from a certificate stored in Azure Key Vault. I am using the Secret API in order to retrieve both the certificate and the private key related to this cert.
Initially the certificate was uploaded using a .pfx file to Azure Key vault. Now I need to create a Certificate and a PrivateKey to allow client authentication to a 3rd party system and I am using the given String retrieved from the API, however I am note sure how to get around that in Java.
I took some hints from this link in C# however I am pretty certain that this method doesn't work like that in Java. In particular an X509Certificate or a Certificate in general doesn't hold any information about the PrivateKey in Java, unlike C#, and I am not sure how to extract that information from given String in Java.
This works as expected to retrieve the certificate from the String retrieved from the API
String secret = azureSecret.getValue();
byte[] certkey = Base64.getDecoder().decode(secret);
ByteArrayInputStream inputStream = new ByteArrayInputStream(certkey);
CertificateFactory cf = CertificateFactory.getInstance("X.509");
Certificate cert = cf.generateCertificate(inputStream);
The azureSecret.getValue() format is like the following however I am not sure how to get PrivateKey out of the given String
MIIKvgIBaaZd6Euf3EYwYdHrIIKYzCC...
YES, Java X509Certificate and Certificate is only the certificate. Instead use KeyStore which can contain multiple entries each of which is either a 'trusted' certificate (for someone else), or a privatekey plus certificate plus other chain cert(s) (if applicable) for yourself, or (not relevant here) a 'secret' (symmetric) key. PKCS12 is supported as one type of KeyStore along with others not relevant here, so after the base64-decoding you already have do something like:
KeyStore ks = KeyStore.getInstance("PKCS12");
ks.load(inputstreamfromvaultvalue, password);
// then
PrivateKey pkey = (PrivateKey) ks.getKey(alias, password);
// and
Certificate cert = ks.getCertificate(alias); // if you only need the leaf cert
// or
Certificate[] chain = ks.getCertificateChain(alias); // usually
But if you want to do client authentication in TLS/SSL (including HTTPS), you give the JSSE KeyManager the whole keystore object not the individual pieces (privatekey and certificates). Similarly to verify the peer in TLS/SSL, you give TrustManager a keystore containing trusted certificates, usually root CAs and often defaulted to a built-in set of public root CAs.

Convert X509Certificate to PEM or ASN.1/DER

I have a chain of certificates (X509Certificate []), but I have only one certificate in the chain. I need to get the complete chain.
I have tried the openssl command, but that is not useful here. Can someone please tell me how to:
Convert this X509Certificate to PEM or ASN.1/DER that I can save in my file storage?
Get the complete chain using this certificate?
Edit:
So, code-wise what I'm trying to achieve is something like:
protected static String convertToPem(X509Certificate cert) {
Base64 encoder = new Base64(64);
String cert_begin = "-----BEGIN CERTIFICATE-----\n";
String end_cert = "-----END CERTIFICATE-----";
byte[] derCert = cert.getEncoded();
String pemCertPre = new String(Base64.encodeBase64(derCert));
String pemCert = cert_begin + pemCertPre + end_cert;
return pemCert;
}
But, this is not working. Basically, I'm looking for a method that takes a X509Certificate object and then converts it to a .pem etc, that is saved on the device.
Convert this X509Certificate object to .cer/ .per/ .der that I can save in my file storage?
See, for example, the answer at OpenSSL's rsautl cannot load public key created with PEM_write_RSAPublicKey. It tells you how to convert keys to/from PEM and ASN.1/DER format, and includes a treatment of Traditional Format (a.k.a. SubjectPublicKeyInfo).
If you are not doing it programmatically, then you should search for the answer. There are plenty of off-topic question on how to use the openssl command to convert between ASN.1/DER and PEM. Or ask on Super User, where they specialize in commands and their use.
Get the complete chain using this certificate?
This is a well known problem in PKI called the Which Directory problem. The solution is to have the server or service provide the missing intermediate CA certificates. If you can't validate a web server or service's identity because you are missing intermediate CA certificates, then the server is misconfigured.
Once you have the intermediate CA certificates, you still have to root trust somewhere. You can use the self-signed CA, or one of the intermediates signed by the self-signed CA.
This answer is helpful in troubleshooting a misconfugred server using OpenSSL's s_client: SSL site and browser warning.
Related: if there was a global directory of certificates like the ITU envisioned in X.500, then you would not have the second problem. A relying party or user agent would just fetch the certificate it needed from the directory.
But we lack a central directory, so relying parties and user agents often use the CA Zoo (a.k.a., the local Trust Store or cacerts.pem). This has its own set of problems, like the wrong CA certifying a site or service.
One of the off-shoots is the CA Cartel, where browser are in partnership with the CAs at the CA/Browser Forum. Browser have requirements for inclusion, but they often can't punish a misbehaving CA like Trustwave.
And the browsers have managed to box themselves into a position where the Internet of Things (IoT) will not work because of the browser's reliance/requirements on server certificates signed by a CA.

Digital signature: sample code for verification and for extracting certification information

I use a third party tool to verify signature and to get certificate detail(like serial number, CA etc..) from signature. The problem with this utility is it is licensed and works on certain machines only.
Can i validate the signature against the data using simple java or .net code?(instead of using paid application). I dont have private key to extract certificate information from signed data.
Or if someone can suggest sample code in java or .net to extract certificate detail if i have pfx file. Of from signed data.
Data is signed with asymmetric encryption.
To extract detail from certificate:
Make a string which keeps certificate data. Just ensure it has -----BEGIN CERTIFICATE----- in starting and -----END CERTIFICATE----- in end.
Now use the following code in Java to extract certificate detail.
InputStream inStream = new ByteArrayInputStream(certString.toString().getBytes("UTF-8"));
BufferedInputStream bis = new BufferedInputStream(inStream);
CertificateFactory cf = CertificateFactory.getInstance("X.509");
Certificate cert = cf.generateCertificate(bis);
X509Certificate xCert = (X509Certificate)cert;
System.out.println("Certificate Type: "+cert.getType());
System.out.println("Public Key: \n"+cert.getPublicKey());
try{
System.out.println("Signature Algorithm"+xCert.getSigAlgName());
System.out.println("IssuerDN : "+xCert.getIssuerDN());
System.out.println("Serial Number : "+xCert.getSerialNumber());
System.out.println("SubjectDN : "+xCert.getSubjectDN());
}catch(Exception exp){
:
}
If you are having the PFX file, then that may contain the public key certificate which will be required to verify the signature.
Alternatively, if your signature is a PKCS#7 signature, then the signature itself will hold the data, signature and the certificate. Assuming PKCS#7 is not detached.
You need to ask your signer, how is he transferring his certificate for validation.

How to verify PEM format certificate in Java

I have PEM format file, How can verify the signature in Java, as I followed http://download.oracle.com/javase/tutorial/security/apisign/versig.html but found that Java doesnt support PEM
You can read a certificate in a PEM file using BouncyCastle's PEMReader. If the content is an X.509 certificate, you should get an instance of X509Certificate and verify it as you want from there.
EDIT: Here is what the code should look like (not tried):
// The key with which you want to verify the cert.
// This is probably a CA certificate's public key.
PublicKey publicKey = ...;
PEMReader reader = new PEMReader(new FileReader("/path/to/file.pem"));
Object pemObject = reader.readObject();
if (pemObject instanceof X509Certificate) {
X509Certificate cert = (X509Certificate)pemObject;
cert.checkValidity(); // to check it's valid in time
cert.verify(publicKey); // verify the sig. using the issuer's public key
}
(Of course, as with any I/O operations, you'll need to close the reader perhaps with try/finally.)
Note that checkValidity and verify don't return anything: instead, they throw exceptions if when they fail.

Categories