A public key certificate (or simply certificate) is a document to bind a party to a public key which is vouched by usually another party using its private key. In Java, such a certificate is represented by java.security.Certificate class that abstracts the common functionality of different certificate formats such as X.509 and PGP. An X.509 certificate is encapsulated by, java.security.cert. X5 0 9Certificate class which is a subclass of Certificate. To work with certificates, an instance of java.security.cert.CertificateFactory class is created first using its static getInstance() method specifying the certificate format as string.
CertificateFactory cf = CertificateFactory.getInstance(”X.509”);
This creates a certificate factory for X.509 certificates. Once this factory is created, we can work with X.509 certificates.
1. Reading Certificate Information
The generateCertificate() method of CertificateFactory returns a Certificate object and populates it with the data obtained from specified InputStream. The following is a sample program to demonstrate this:
import java.io.*;
import java.security.cert.*;
public class ReadCertificate {
public static void main(String args[]) throws Exception {
CertificateFactory cf = CertificateFactory.getInstance(”X.509”);
Certificate c = cf.generateCertificate(new FileInputStream(args[0]));
System.out.println(c);
}
}
To view the content of a certificate file test.cer, use the following command:
java ReadCertificate test.cer
Note that generateCertificate() method can understand DER (Distinguished Encoding Rules), PEM (Privacy-enhanced Electronic Mail) and PKCS#7 certificate file format. If a file contains multiple certificates (possibly unrelated), use generateCertificates() method which returns a Collection of certificates. Here is an example:
Collection c = cf.generateCertificates(new FileInputStream(args[0]));
for(Iterator i = c.iterator();i.hasNext();) {
Certificate cert = (Certificate)i.next();
System.out.println(cert);
}
2. Creating Certificate
The following program (GenerateCertificate.java) creates a certificate (c.cer) using RSA key generation and sha1 signature algorithm. This program also creates a key store (c.jks) file and a trust store (c.ts) for this certificate.
//GenerateCertificate.java import sun.security.x509.*;
import java.security.cert.*;
import java.security.*;
import java.math.BigInteger;
import java.util.Date;
import java.io.*;
public class GenerateCertificate {
public static void main(String args[]) {
try {
String enAlg = “RSA”, sigAlg = ”SHA1with”+enAlg;
KeyPairGenerator kpg = KeyPairGenerator.getlnstance(enAlg);
KeyPair kp = kpg.generateKeyPair();
PrivateKey priv = kp.getPrivate();
X509CertInfo cR = new X509CertInfo();
//populate certificate info ci.set(X509CertInfo.VERSION, new CertificateVersion(CertificateVersion.V3));
BigInteger sn = new BigInteger(64, new SecureRandom());
ci.set(X509CertInfo.SERIAL_NUMBER, new CertificateSerialNumber(sn));
String dn = ”CN=myName, OU=myOU, O=myO, L=MyL, S=myS, C=myC”;
X500Name issuer = new X500Name(dn);
ci.set(X509CertInfo.ISSUER, new CertificateIssuerName(issuer));
Date from = new Date();
Date to = new Date(from.getTime() + 365*24*60*60*1000l);
CertificateValidity duration = new CertificateValidityfrom, to);
ci.set(X509CertInfo.VALIDITY, duration);
ci.set(X509CertInfo.SUBJECT, new CertificateSubjectName(issuer));
ci.set(X509CertInfo.KEY, new CertificateX509Key(kp.getPublic()));
AlgorithmId aid = new AlgorithmId(AlgorithmId.sha1WithRSAEncryption_oid);
ci.set(X509CertInfo.ALGORITHM_ID, new CertificateAlgorithmId(aid));
X509CertImpl cert = new X509CertImpl(ci);
//self sign
cert.sign(priv, sigAlg);
FileOutputStream os = new FileOutputStream(”c.cer”);
os.write(cert.getEncoded());
os.close();
KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
char[] password = ”123456”.toCharArray();
ks.load(null, password);
java.security.cert.Certificate[] chain = {cert}; ks.setKeyEntry(”abc”, priv, password, chain);
java.io.FileOutputStream fos = new java.io.FileOutputStream(”c.jks”); ks.store(fos, password);
KeyStore ts = KeyStore.getInstance(KeyStore.getDefaultType());
ts.load(null, password);
ts.setCertificateEntry(”abc”, cert);
java.io.FileOutputStream fos1 = new java.io.FileOutputStream(”c.ts”);
ts.store(fos1, password);
}catch(Exception e) {e.printStackTrace();}
}
}
If you want to use any other key generation algorithm and signature algorithm, specify them in the variables enAig and sigAlg.
3. Converting Certificates
The following program converts one certificate file format to another.
// Converter.java import java.io.*; import java.security.cert.*; public class Converter {
public static void main(String args[]) {
try {
InputStream inStream = new FileInputStream(args[1]);
CertificateFactory cf = CertificateFactory.getInstance(”X.509”);
Certificate cert = (X509Certificate)cf.generateCertificate(inStream);
byte[] buf = cert.getEncoded();
if(args[0].equals(”fromder”)) {
Writer wr = new OutputStreamWriter(new FileOutputStream(args[2]));
wr.write(” BEGIN CERTIFICATE—– ”.toCharArray());
wr.write(new sun.misc.BASE64Encoder().encode(buf));
wr.write(”———– END CERTIFICATE—— ”.toCharArray());
wr.flush();
}
if(args[0].equals(”toder”)) {
FileOutputStream os = new FileOutputStream(args[2]);
os.write(buf);
os.close();
}
}catch(Exception e) { e.printStackTrace(); }
}
}
From DER to PEM conversion:
java Converter fromder c.cer c.pem
From DER to PEM conversion:
java Converter toder c.pem c.cer
Using keytool
Convert a DER file (.crt .cer .der) to PEM
keytool -import -keystore temp.jks -storepass 123456 -file c.der
keytool -export -keystore temp.jks -storepass 123456 -rfc -file c.pem
Convert a PEM file to DER
keytool -import -keystore temp.jks -storepass 123456 -file c.pem
keytool -export -keystore temp.jks -storepass 123456 -file c.der
Convert a DER file (.crt .cer .der) to PEM
openssl x509 -inform der -in c.cer -out c.pem
Convert a PEM file to DER
openssl x509 -outform der -in c.pem -out c.cer
However, openssl accepts only trusted certificate.
4. SignedObject
Java java.security.SignedObject class ensures the integrity of serializable objects when they are transferred over or stored on untrusted media. The object, before reusing it, can be checked to see if it was modified during its transit. An instance of the SignedObject encapsulates the serialized representation of another object along with the signature information necessary to validate the wrapped object’s authenticity and integrity. The following constructor is available:
SignedObject(Serializable object, PrivateKey key, Signature engine)
This creates a SignedObject from a specified object, which is signed with the specified private signing key and signature engine. The following is a typical example:
Serializable obj = …
PrivateKey key = …
Signature sig =…
SignedObject so = new SignedObject(obj, key, sig);
Once an instance of signedObject has been created, it can be transferred over or stored on even untrusted media. Upon reconstruction, the integrity of this object can be verified and authenticated using public key associated with the private key that signed the object as follows:
SignedObject so = …
PublicKey pub = …
Signature sig = Signature.getInstance(so.getAlgorithm());
if(so.verify(pub, sg)) {
//the wrapped object remains unchanged, get it and use it Object obj = so.getObject();
}
A signed object contains the deep copy of the original object (in serialized form). Once the original object is wrapped in a signed object, further manipulation of the original object has no side effect on the copy.
Note that to verify the wrapped object’s integrity, the public key is needed. This public key may be obtained in the form of public key certificate. The following program creates a SignedObject from a string and signs it using a private key obtained from a key store test.ks and finally stores
the SignedObject in a file so.dat. import java.security.*; import java.io.*;
public class SignedObjectCreator {
public static void main(String args[]) throws Exception {
char[] password = ”123456”.toCharArray();
KeyStore ks = KeyStore.getInstance(”JKS”);
ks.load(new FileInputStream(”test.ks”), password);
String str = “Hello World!”;
PrivateKey pri = (PrivateKey)ks.getKey(”test”, password);
Signature sig = Signature.getInstance(”SHA1withRSA”);
SignedObject so = new SignedObject(str, pri, sig);
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(”so.dat”));
oos.writeObject(so);
}
}
Create a key store test.ks containing one entry having alias test using the following command:
keytool -genkey -alias test -keyalg RSA -keystore test.ks -storepass 123456 -keypass 123456 -dname ”CN=Test, OU=IT, O=JU, L=Kolkata, ST=WB, C=IN”
Now, create a public certificate file test.cer for this entry using the following command:
keytool -export -alias test -keystore test.ks -storepass 123456 -file test.cer
Now, execute the above Java file using the following command:
Java SignedObjectCreator
This generates a file so.dat containing a signed string object. This so.dat along with the test. cer can now be transferred anywhere else. The following program reconstructs the SignedObject and verifies its wrapped object’s integrity using the public key obtained from the file test.cer.
import java.security.*;
import javax.security.cert.X509Certificate; import java.io.*;
public class SignedObjectVerifier {
public static void main(String args[]) throws Exception {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(”so.dat”));
SignedObject so = (SignedObject)ois.readObject();
PublicKey pub = X509Certificate.getInstance(new
FileInputStream(”test.cer”)).getPublicKey();
Signature sg = Signature.getInstance(so.getAlgorithm());
if (so.verify(pub, sg)) {
String s = (String)so.getObject();
System.out.println(s);
}
}
}
Note that, it is possible to obtain the wrapped object from signedObject without first verifying the sender’s authenticity or the message’s integrity. This implies that SignedObject does not protect the object’s content; it merely allows us to verify its integrity. To protect (hide) an object, SealedObject may be used.
5. SealedObject
The javax.crypto.SealedObject class, unlike signedObject which deals with integrity, protects an object’s confidentiality. A SealedObject contains an encrypted version of another object’s serialized representation. So, unlike SignedObject, wrapped object cannot be retrieved without decryption key. This implies that no one can examine or temper the wrapped object without knowing the decryption key. Moreover, if public key encryption is used, the authenticity of the sender can also be verified. It has the following constructor:
SealedObject(Serializable object, Cipher chiper)
Once an instance of SignedObject has been created, it can be transferred over or stored on even untrusted media. Upon reconstruction, the original object can be retrieved using getObject() method. This method decrypts the encrypted content using the corresponding algorithm and correct decryption key, deserializes it and returns the original object. The getObject() method has the following overloaded versions:
Object getObject(Cipher c)
Object getObject(Key key)
Object getObject(Key key, String provider)
So, any one of the following will work:
String provider = …
Chiper chipper = …
Key decryptionkey = …
so.getObject(cipher);
so.getObject(decryptionkey);
so.getObject(decryptionkey, provider);
Note that to retrieve the original object, the corresponding decryption key is needed. For public key encryption, public key, obtained from the receiver’s certificate may be used for encryption. Receiver can then use its private key for decryption. Since, public key cryptography cannot handle data of an arbitrary size, secret key is usually used for encryption. Since, secret key cryptography uses symmetric key, receiver has to obtain the secret decryption key. How will the receiver obtain the secret decryption key without getting it observed or modified? The following strategies may be employed:
- Generate a symmetric (secret) key with preferred size.
- Get the public key certificate from the intended receiver.
- Encrypt the symmetric key with RSA using receiver’s public key obtained from the certificate and send the encrypted key.
- Decrypt the symmetric key (secret) with the RSA using receiver’s private key.
- Encrypt the data with the symmetric key and send it to the receiver.
- Decrypt the data with the symmetric key.
Steps 1-4 are done only once, whereas steps 5-6 are repeated until the data exchange is over. The following program creates a SealedObject from a String and signs it using a secret key and finally stores the file data. The secret key is encrypted using a public key obtained from (receiver’s) certificate file test.cer, wrapped in another sealed object which is then stored in a file key. Create this test.cer file as described in the previous section. import java.security.*; import java.io.*; import javax.crypto.*;
import javax.security.cert.X509Certificate; public class SealedObjectCreator {
public static void main(String args[]) throws Exception {
String alg = ”DES”, target = “Hello World!”;
PublicKey pub = X509Certificate.getInstance(new
FileInputStream(”test.cer”)).getPublicKey();
Cipher cipher = Cipher.getInstance(pub.getAlgorithm());
cipher.init(Cipher.ENCRYPT_MODE, pub);
KeyGenerator kg = KeyGenerator.getlnstance(alg);
SecretKey sk = kg.generateKey();
SealedObject so = new SealedObject(sk.getEncoded(), cipher);
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(”key”));
oos.writeObject(so);
cipher = Cipher.getlnstance(alg); cipher.init(Cipher.ENCRYPT_MODE, sk);
so = new SealedObject(target, cipher);
oos = new ObjectOutputStream(new FileOutputStream(”data”));
oos.writeObject(so);
oos.close();
}
}
This data along with the key file key can now be transferred anywhere else. The following program first retrieves the secret key using private key obtained from the key store test.ks. This secret key is then used further to retrieve wrapped data objects.
import java.security.*;
import java.io.*;
import javax.crypto.*;
import javax.crypto.spec.*;
public class SealedObjectDecoder {
public static void main(String args[]) throws Exception {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(”key”));
SealedObject so = (SealedObject)ois.readObject();
char[] password = ”123456”.toCharArray();
KeyStore ks = KeyStore.getInstance(”JKS”);
ks.load(new FileInputStream(”test.ks”), password);
PrivateKey pri = (PrivateKey)ks.getKey(”test”, password);
byte[] bytes = (byte[])so.getObject(pri) ;
ois = new ObjectInputStream(new FileInputStream(”data”));
so = (SealedObject)ois.readObject();
SecretKey sk = new SecretKeySpec(bytes, 0, bytes.length, so.getAlgorithm());
System.out.println(so.getObject(sk));
}
}
The following is a pair of sender/receiver socket programs that exchange data using sealed objects. They make use of the basic mechanism used by SSL without using SSL.
import java.security.*; import java.io.*;
import javax.crypto.*;
import javax.security.cert.X509Certificate;
import java.net.*;
public class SealedObjectSender {
public static void main(String args[]) throws Exception {
Socket s = new Socket(args[0], 6789);
ObjectInputStream in = new ObjectInputStream(s.getInputStream());
ObjectOutputStream out = new ObjectOutputStream(s.getOutputStream());
java.security.cert.Certificate c =
(java.security.cert.Certificate)in.readObject();
//authenticate the certificate c here PublicKey pub = c.getPublicKey();
String alg = “DES”;
KeyGenerator kg = KeyGenerator.getInstance(alg);
SecretKey sk = kg.generateKey();
Cipher cipher = Cipher.getInstance(pub.getAlgorithm());
cipher.init(Cipher.ENCRYPT_MODE, pub);
SealedObject so = new SealedObject(sk.getEncoded(), cipher);
out.writeObject(so);
cipher = Cipher.getInstance(alg);
cipher.init(Cipher.ENCRYPT_MODE, sk);
BufferedReader fromUser = new BufferedReader(new InputStreamReader(System.in));
while(true) {
so = new SealedObject(fromUser.readLine(), cipher);
out.writeObject(so);
}
}
}
import java.security.*;
import java.io.*;
import javax.crypto.*;
import javax.crypto.spec.*;
import java.net.*;
public class SealedObjectReceiver {
public static void main(String args[]) throws Exception {
ServerSocket ss = new ServerSocket(6789);
Socket s = ss.accept();
ObjectOutputStream out = new ObjectOutputStream(s.getOutputStream());
ObjectInputStream in = new ObjectInputStream(s.getInputStream());
char[] password = ”123456”.toCharArray();
KeyStore ks = KeyStore.getInstance(”JKS”);
ks.load(new FileInputStreamd’test.ks”), password);
PrivateKey pri = (PrivateKey)ks.getKey(”test”, password);
oRt.writeObject(ks.getCertificate(”test”));
SealedObject so = (SealedObject)in.readObject();
byte[] bytes = (byte[])so.getObject(pri);
so = (SealedObject)in.readObject();
SecretKey sk = new SecretKeySpec(bytes, 0, bytes.length,
so.getAlgorithm()); while(true) {
System.out.println(so.getObject(sk)); so = (SealedObject)in.readObject();
}
}
}
To start this application, start the receiver program as follows:
java SealedObjectReceiver
Now start the sender program as
java SealedObjectSender 172.16.5.81
Here 172.16.5.81 is the IP address of the receiver computer.
6. GuardedObject
The operations on a sensitive object should be governed by a permission. This means, each method call on the object should verify if caller thread has proper permission. This tells us to insert security check (probably redundant) in every method including constructor.
Moreover, the user of an object may not always be its creator. This happens especially when an object is transferred across a network using serialization and deserialization. In this case, the security context of an object’s caller may be different from that of its creator.
Note that when an object is reconstructed using deserialization, constructor is not used. Therefore, security check code (if any) in the constructor may be bypassed during object reconstruction.
So, if every method requires the permission, it makes sense to require it once, at object level. Java java.security.GuardedObject provides object level access control which ensures that permission is checked once. This is done in getObject() method, which a caller must use to gain access to the protected object. The basic idea is as follows:
A Guard object, which guards access to another target object, is first created. Any object can become a Guard provided that the objects class implements the interface Guard. It has only one method checkGuard() that takes an Object argument and performs certain (security) checks. Fortunately,
the java.security.Permission class (and hence its subclasses) implements the Guard interface and can be used as a guard. We create a PropertyPermission object as guard as follows:
Guard guard = new PropertyPermission(”usr.home”, “read”);
This represents a read access to the “usr.home” system property. For simplicity, let us create a String object to be guarded as follows:
String pass = new String(”123456”);
The guarded object should be Serializable if it is intended to pass across the network. A GuardedObject is then created from this pass along with its guard.
GuardedObject go = new GuardedObject(pass, guard);
The object go embeds the pass and guard objects. This GuardedObject object go can be transferred to a different JVM having possibly a different security context. We serialize go and put it in a file pass:
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(”pass”));
oos.writeObject(go);
The complete source code is shown below:
import java.security.*;
import java.io.*;
import java.util.PropertyPermission; public class GuardedObjectWrite {
public static void main(String[] args) throws Exception {
Guard guard = new PropertyPermission(”usr.home”, “read”);
String pass = new String(”123456”);
GuardedObject go = new GuardedObject(pass, guard);
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(”pass”));
oos.writeObject(go);
}
}
We assume that object is reconstructed at the receiver end using a code as follows:
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(”pass”));
GuardedObject go = (GuardedObject)ois.readObject();
To gain access to the guarded object, getObject() method is used:
String ps = (String) go.getObject();
The getObject() method in turn invokes the checkGuard() method on the guard object that was used to create GuardedObject. Our guard object was a PropertyPermission object which allows accessing the guarded object to a thread only if the thread has the read access to the usr.home system property.
The complete source code is shown below:
import java.security.*;
import java.io.*;
public class GuardedObjectRead {
public static void main(String[] args) throws Exception {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(”pass”));
GuardedObject go = (GuardedObject)ois.readObject();
String pass = (String) go.getObject();
System.out.println(”pass = ” + pass);
}
}
Source: Uttam Kumar Roy (2015), Advanced Java programming, Oxford University Press.