In the first part of this article i described how to setup a simple Certification Authority to issue new certificates and check for their validity. Now i’m going to explain how to create the client side.
First of all, what we want to do is:
- create a new certificate
- renew an existing certificate
- revoke a certificate
- check if a certificate is valid or not
and also we agreed to use the Certificate Management Protocol to obtain X.509 certificates.
Let’s start with the “hardest” part, create a new certificate in “path_to_certificate”:
public void requestCert(String host, int port) {
try {
context = crypt.CreateContext(crypt.UNUSED, crypt.ALGO_RSA);
crypt.SetAttributeString(context, crypt.CTXINFO_LABEL, "a_significative_label");
crypt.GenerateKey(context);
client_keyset = crypt.KeysetOpen(crypt.UNUSED, crypt.KEYSET_FILE, "path_to_certificate", crypt.KEYOPT_CREATE);
crypt.AddPrivateKey(client_keyset, context, "secret_password");
int certificate_request = crypt.CreateCert(crypt.UNUSED, crypt.CERTTYPE_REQUEST_CERT);
crypt.SetAttribute(certificate_request, crypt.CERTINFO_SUBJECTPUBLICKEYINFO, context);
crypt.SetAttribute(certificate_request, crypt.CERTINFO_KEYUSAGE, crypt.KEYUSAGE_DIGITALSIGNATURE | crypt.KEYUSAGE_NONREPUDIATION | crypt.KEYUSAGE_KEYENCIPHERMENT);
crypt.SignCert(certificate_request, context);
int session = crypt.CreateSession(crypt.UNUSED, crypt.SESSION_CMP);
crypt.SetAttributeString(session, crypt.SESSINFO_SERVER_NAME, host);
crypt.SetAttribute(session, crypt.SESSINFO_SERVER_PORT, port);
crypt.SetAttribute(session, crypt.SESSINFO_CMP_REQUESTTYPE, crypt.REQUESTTYPE_INITIALISATION);
crypt.SetAttributeString(session, crypt.SESSINFO_USERNAME, username);
crypt.SetAttributeString(session, crypt.SESSINFO_PASSWORD, password);
int CA_keyset = crypt.KeysetOpen(crypt.UNUSED, crypt.KEYSET_FILE, CA_certificate_path, crypt.KEYOPT_READONLY);
CA_certificate = crypt.GetPublicKey(CA_keyset, crypt.KEYID_NAME, "CA_Private_Key");
crypt.SetAttribute(session, crypt.SESSINFO_CACERTIFICATE, CA_certificate);
crypt.KeysetClose(CA_keyset);
crypt.SetAttribute(session, crypt.SESSINFO_REQUEST, certificate_request);
crypt.SetAttribute(session, crypt.SESSINFO_ACTIVE, 1);
client_certificate = crypt.GetAttribute(session, crypt.SESSINFO_RESPONSE);
crypt.AddPublicKey(client_keyset, client_certificate);
crypt.KeysetClose(client_keyset);
crypt.DestroySession(session);
} catch (CryptException ex2) {
Logger.getLogger(Client.class.getName()).log(Level.SEVERE, null, ex2);
}
}
Two important notes here:
- at lines 22-23 you have to set a username and password for the session; these strings are created by the CA upon the registration of a new PKIUser (refer to the manual for more info) and should be agreed by both parts before the actual communication begins.
- lines 25-28 are there to get the CA’s public key since the test project was running on a single machine. If you want to make it truly a client/server application you need to get the CA’s public key in another way.
To ask for a certificate renewal, instead:
public void renewCertificate(String host, int port) {
try {
int certificate_renew = crypt.CreateCert(crypt.UNUSED, crypt.CERTTYPE_REQUEST_CERT);
crypt.SetAttribute(certificate_renew, crypt.CERTINFO_CERTIFICATE, old_certificate);
crypt.DeleteAttribute(certificate_renew, crypt.CERTINFO_COUNTRYNAME);
crypt.SetAttributeString(certificate_renew, crypt.CERTINFO_COUNTRYNAME, "new_country_name");
crypt.DeleteAttribute(certificate_renew, crypt.CERTINFO_LOCALITYNAME);
crypt.SetAttributeString(certificate_renew, crypt.CERTINFO_LOCALITYNAME, "new_locality_name");
crypt.DeleteAttribute(certificate_renew, crypt.CERTINFO_ORGANIZATIONNAME);
crypt.SetAttributeString(certificate_renew, crypt.CERTINFO_ORGANIZATIONNAME, "new_org_name");
crypt.DeleteAttribute(certificate_renew, crypt.CERTINFO_ORGANIZATIONALUNITNAME);
crypt.SetAttributeString(certificate_renew, crypt.CERTINFO_ORGANIZATIONALUNITNAME, "new_orgunit_name");
crypt.DeleteAttribute(certificate_renew, crypt.CERTINFO_COMMONNAME);
crypt.SetAttributeString(certificate_renew, crypt.CERTINFO_COMMONNAME, "new_common_name");
crypt.SignCert(certificate_renew, context);
int session = crypt.CreateSession(crypt.UNUSED, crypt.SESSION_CMP);
crypt.SetAttributeString(session, crypt.SESSINFO_SERVER_NAME, host);
crypt.SetAttribute(session, crypt.SESSINFO_SERVER_PORT, port);
crypt.SetAttribute(session, crypt.SESSINFO_CMP_REQUESTTYPE, crypt.REQUESTTYPE_KEYUPDATE);
int keyset = crypt.KeysetOpen(crypt.UNUSED, crypt.KEYSET_FILE, path, crypt.KEYOPT_NONE);
int priv_key = crypt.GetPrivateKey(keyset, crypt.KEYID_NAME, "a_significative_label", "secret_password");
crypt.SetAttribute(session, crypt.SESSINFO_PRIVATEKEY, priv_key);
crypt.SetAttribute(session, crypt.SESSINFO_CACERTIFICATE, CA_certificate);
crypt.SetAttribute(session, crypt.SESSINFO_REQUEST, certificate_renew);
crypt.SetAttribute(session, crypt.SESSINFO_ACTIVE, 1);
client_certificate = crypt.GetAttribute(session, crypt.SESSINFO_RESPONSE);
crypt.DeleteKey(keyset, crypt.KEYID_NAME, label);
crypt.AddPrivateKey(keyset, context, password);
crypt.AddPublicKey(keyset, client_certificate);
crypt.KeysetClose(keyset);
crypt.DestroySession(session);
} catch (CryptException ex) {
Logger.getLogger(Client.class.getName()).log(Level.SEVERE, null, ex);
}
}
Lines 6-15 of course are optional, we just need to make sure we sign the certificate update with the same private key of our first request (line 17).
The private key is stored inside the context, which must be saved somewhere when we create a new certificate request.
To revoke a certificate, we can use:
public void revokeCert(String host, int port) {
try {
int certificate_revoke = crypt.CreateCert(crypt.UNUSED, crypt.CERTTYPE_REQUEST_REVOCATION);
crypt.SetAttribute(certificate_revoke, crypt.CERTINFO_CERTIFICATE, certificate_to_be_revoked);
crypt.SetAttribute(certificate_revoke, crypt.CERTINFO_CRLREASON, "a_valid_reason");
int session = crypt.CreateSession(crypt.UNUSED, crypt.SESSION_CMP);
crypt.SetAttributeString(session, crypt.SESSINFO_SERVER_NAME, host);
crypt.SetAttribute(session, crypt.SESSINFO_SERVER_PORT, port);
crypt.SetAttribute(session, crypt.SESSINFO_CMP_REQUESTTYPE, crypt.REQUESTTYPE_REVOCATION);
client_keyset = crypt.KeysetOpen(crypt.UNUSED, crypt.KEYSET_FILE, path, crypt.KEYOPT_READONLY);
int priv_key = crypt.GetPrivateKey(client_keyset, crypt.KEYID_NAME, label, password);
crypt.SetAttribute(session, crypt.SESSINFO_PRIVATEKEY, priv_key);
int CA_keyset = crypt.KeysetOpen(crypt.UNUSED, crypt.KEYSET_FILE, CA_certificate_path, crypt.KEYOPT_NONE);
CA_certificate = crypt.GetPublicKey(CA_keyset, crypt.KEYID_NAME, "CA Private Key");
crypt.SetAttribute(session, crypt.SESSINFO_CACERTIFICATE, CA_certificate);
crypt.KeysetClose(CA_keyset);
crypt.SetAttribute(session, crypt.SESSINFO_REQUEST, certificate_revoke);
crypt.SetAttribute(session, crypt.SESSINFO_ACTIVE, 1);
crypt.KeysetClose(client_keyset);
crypt.DestroySession(session);
} catch (CryptException ex) {
Logger.getLogger(Client.class.getName()).log(Level.SEVERE, null, ex);
}
}
In this code snippet there is a big problem; the most common reason to revoke a certificate is that you simply lost your private key or anyway you are not sure it is still private.
As of version 3.3.2, the only way to get a certificate revocation request to be accepted by the CA, is to sign it with your old private key! No matter what they say in the manual, i tested all the possibilities ![]()
Also, for lines 16-19, the same consideration about obtaining the CA’s public key, holds.
Finally, to check if a certificate is valid or not, we need to issue a RTCS request to the server:
public void verifyCert(String host) {
try {
int session = crypt.CreateSession(crypt.UNUSED, crypt.SESSION_RTCS);
crypt.SetAttributeString(session, crypt.SESSINFO_SERVER_NAME, "localhost");
crypt.SetAttribute(session, crypt.SESSINFO_SERVER_PORT, 4000);
int keyset = crypt.KeysetOpen(crypt.UNUSED, crypt.KEYSET_FILE, path_to_certificate, crypt.KEYOPT_READONLY);
int certificate_to_check = crypt.GetPublicKey(keyset, crypt.KEYID_NAME, "a_significative_label");
try {
crypt.CheckCert(certificate_to_check, session);
// If this is the next instruction, my certificate is valid
} catch(CryptException e) {
// If an exception is raised, then my certificate has been revoked;
}
crypt.DestroySession(session);
} catch (CryptException ex) {
Logger.getLogger(Client.class.getName()).log(Level.SEVERE, null, ex);
}
}
There we are, basic communication should be established now. I hope this article can help to clarify the basic cryptlib functions.
