Reduce Insecurity for free, HTTPS now democratized by LetsEncrypt

 

how-to-install-lets-encrypt-ssl-certificate

I am typing this with my fingers crossed, that I could just get someone to acknowledge that HTTPS is not only prudent but damn easy to setup. Security is not my primary focus, yet I align with most of the InfoSec’s paranoia out there today. A dumb hacker millions of years ago said that, the minimum you could do in security is to use SSL encryption in your communication.

Now that I have uncrossed my fingers. Below is my rough note for setting up a secure instance for which I assume you have an elastic IP in EC2 instance and a DNS pointing using the A-host configuration to this IP. Below is a totally fake xyzminime.org domain name which I do not own and is just used for example. No offence to anyone who owns it, I just think its an awesome name.

URL: https://xyzminime.org

Email: info@xyzminime.org

# This is how I used to generate my insecure self-signed certificate earlier
keytool -genkeypair -dname "CN=xyzminime.org, OU=XYZ, O=XYZ, L=PaloAlto, ST=CA, C=US" -alias xyzminime -keyalg RSA -ext san=ip:xyzminime.org -keystore /opt/tomcat7/.keystore

Since I am not an authorized certificate signing authority, all the browsers just flags my certificate as unsecure and block it by default.

This is where LetsEncrypt came for help with their democratic certificate authority. There were some references that I drew inspiration from, to do this thing as a rough note and not a tutorial.

Ref:    https://certbot.eff.org/#centosrhel6-other
        https://certbot.eff.org/docs/using.html#webroot
        https://melo.myds.me/wordpress/lets-encrypt-for-tomcat-7-on-ds/

 

1.) Pre-requisite is to get the certbot client

# Installation taken care by the certbot-auto client
sudo yum install epel-release wget
wget https://dl.eff.org/certbot-auto
chmod a+x certbot-auto

# Install the certbot on your instance
sudo ./certbot-auto

Certbot dumps its contents in a folder like below, in my ec2-user local path,
/home/ec2-user/.local/share/letsencrypt/bin/letsencrypt certonly

 

2.) Generate a certificate

Keep an email address for notification and validation handy for the enrolment with ACME

If you have a functional webserver that needs to be SSLified then use the webroot way otherwise --standalone is preffered
sudo ./certbot-auto certonly -n --rsa-key-size 2048 --agree-tos --email info@xyzminime.org --webroot -w /opt/tomcat7/webapps/ -d xyzminime.org
IMPORTANT NOTES:
– Congratulations! Your certificate and chain have been saved at
/etc/letsencrypt/live/xyzminime.org/fullchain.pem.
Your cert will expire on 2016-12-22. To obtain a new or tweaked
version of this certificate in the future, simply run certbot-auto
again. To non-interactively renew *all* of your certificates, run
“certbot-auto renew”
– If you like Certbot, please consider supporting our work by:
Donating to ISRG / Let’s Encrypt: https://letsencrypt.org/donate
Donating to EFF: https://eff.org/donate-le
The certificates are written to /etc/letsencrypt/live/xyzminime.org/
export CERT_PATH="/etc/letsencrypt/live/xyzminime.org/"
 - cert.pem
 - chain.pem
 - fullchain.pem
 - privkey.pem

 

3.) Create a keystore for Tomcat

Basically there are only two steps required to get our fullchain.pem and privkey.pem inside a JKS. 
First we bundle both our fullchain and the private key in a PKCS12 keystore. 
We do this, because apparently Java’s keytool (which we use to create our JKS),
is not able to import pre-existing keys and certificates into a JKS, as described here.
sudo openssl pkcs12 -export -in "$CERT_PATH"fullchain.pem -inkey "$CERT_PATH"privkey.pem -out fullchain_and_key.p12 -name xyzminime -password pass:mini#123

Now that we have our PKCS12 keystore, we can use Java’s keytool to generate a JKS,
from our PKCS12 file like;
keytool -importkeystore -deststorepass mini#123 -destkeypass mini#123 -destkeystore xyzminime.jks -srckeystore fullchain_and_key.p12 -srcstoretype PKCS12 -srcstorepass mini#123 -alias xyzminime
 
# Backup and place your self-signed keystore in the tomcat home
mv /opt/tomcat7/.keystore .keystore_backup_1
sudo cp xyzminime.jks /opt/tomcat7/.keystore
 
Make sure that the 8443 conector configuration in the conf/server.xml is as follows
<Connector port="8443" 
           keystoreFile="${user.home}/.keystore" 
           keystorePass="mini#123" 
           keyAlias="xyzminime" 
           ...
Run the InstallCert utility for java security ca cert 
Compile the InstallCert using javac
java InstallCert xyzminime.org
sudo cp jssecacerts /usr/java/jdk1.8.0_73/jre/lib/security/

 

4.) Automating renewal

# A test run for renewal
certbot-auto renew --dry-run

# Add the following to the cron or systemmd that should run twice daily in case of any certificate invalidation
certbot-auto renew --quiet

 

Now your tomcat will be able to serve the content over SSL. Verify this by accessing the server on the below URL.

https://xyzminime.org

 

 

 

AspectJ component for my services audit logger

This is a blog that will leverage the advantages of AspectJ aspect oriented programming concept in solving a very basic problem of auditing the visitors to your service and the response times. These parameters are very important when we want to do some operations around these.

Pre-requisite for AspectJ in your pom.xml

 <!-- Spring AspectJ -->
 <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aspects</artifactId>
    <version>${org.springframework-version}</version>
    <scope>compile</scope>
 </dependency>
 <dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>${org.aspectj-version}</version>
    <scope>compile</scope>
 </dependency>
 <dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjrt</artifactId>
    <version>${org.aspectj-version}</version>
 </dependency>
 <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aop</artifactId>
    <version>${org.springframework-version}</version>
 </dependency>
 <dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>3.1</version>
 </dependency>

In your application context add the configuration as per the standard aop proxy. You should also include component-scan and annotation-driven.

<!-- Aspects -->
 <aop:aspectj-autoproxy proxy-target-class="true"/>

Here is the actual code that will intercept around the public calls  in controller. It will print the service url, method, parameters and arguments. We can also obtain the user from the session context.

/**
 * @author Rahul Vishwakarma
 * This class will log all the service calls made to the Generic Application 
 * relying on the @Around advice
 * Ref: http://docs.spring.io/spring/docs/3.0.x/spring-framework-reference/html/aop.html#aop-understanding-aop-proxies 
 */
@Component
@Aspect
public class GenericLoggerAspect {
 /** Logger for this class and subclasses */
 private static final Logger log = LoggerFactory.getLogger(RhlLoggerAspect.class);
 
 public static ConcurrentHashMap<String,RequestData> responseTime = new ConcurrentHashMap<String,RequestData>();
 
 @Autowired 
 Utility utility;
/**
 * Inner class for request info
 * @author Rahul
 *
 */
 public class RequestInfo{
 public int responseTimeMills = 0;
 public Date accessTime = null;
 public String urlPath;
 public String requestType;
 public String args;
 }
 /**
 * Inner class for Data capturing request information
 * @author Rahul
 *
 */
 public class RequestData {
   public RequestInfo requestInfo;
   public String api;
   public UserInfo userInfo;
 
   public RequestData(RequestInfo requestInfo,UserInfo userInfo,String methodSignature){
   this.requestInfo = requestInfo;
   this.userInfo = userInfo;
   this.api = methodSignature;
   }
 }

/**
 * User and client related info
 * @author Rahul
 *
 */
 public class UserInfo{
   public String userName;
   public int userId;
   public String sessionId;
   public String role;
   public String clientIp;
 }
 
 private String getRepresentation(Object [] params){
   StringBuilder sb = new StringBuilder();
   if(params!=null)
   {
   String value = null;
   for(int i=0;i<params.length;i++){
   value = params[i] + ",";
   if(value.contains("@")){
     value = "";
   }
     sb.append(value);
   }
    if(sb.length()>0)
    return sb.substring(0, sb.length() - 1);
  }
  return sb.toString();
}
enum IssueType{
  ISSUE_URL_SUFFIX, OTHER, NONE
}
@Around("execution(@*..RequestMapping * * (..))")
public Object log_around(ProceedingJoinPoint pjp) throws Throwable {
 
  Object obj = null;
  IssueType issueType = IssueType.NONE;
  String error="Error in GenericLoggerAspect";
 try{
  String methodSignature = pjp.getSignature()+"";
  StringBuffer args = new StringBuffer(); //getRepresentation(pjp.getArgs());
 
  //Append arguments
  Object[] arg = pjp.getArgs();
  for (int i = 0; i < arg.length; i++) {
    args.append(arg[i]).append(",");
  }
  if (arg.length > 0) {
   args.deleteCharAt(args.length() - 1);
  }
 
  log.info("\tSTART {}-{}", methodSignature+" ["+Thread.currentThread().getId()+"]",args.toString());

  UserInfo userInfo = new UserInfo();
  UserSecure userSecure = utility.getUserInfo();
  if(userSecure != null){
   userInfo.clientIp = userSecure.getClientIp();
   userInfo.userId = userSecure.getUserId();
   userInfo.sessionId = userSecure.getSessionId();
   userInfo.userName = userSecure.getUserName();
  if(userSecure.getAuthorities() != null)
   userInfo.role = userSecure.getAuthorities().toString();
  }
  ServletRequestAttributes sra = (ServletRequestAttributes)RequestContextHolder.getRequestAttributes();
 
  String urlPath=""; 
 
  if(sra!=null){
    HttpServletRequest req = sra.getRequest();
  if(req!=null){
    urlPath = req.getServletPath();
  } 
 }
 
 long start = System.currentTimeMillis();
 if(urlPath.endsWith("/")){
   issueType = IssueType.ISSUE_URL_SUFFIX;
   obj = null;
 } else
   obj = pjp.proceed();
 
 String requestType = "ajax";
 if(obj!=null && (obj instanceof ModelAndView)){
   requestType = "page";
 }
 int elapsedTime = (int) (System.currentTimeMillis() - start); 
 RequestInfo requestInfo = new RequestInfo();
 requestInfo.accessTime = (new Date());
 requestInfo.requestType = (requestType);
 requestInfo.responseTimeMills = (elapsedTime);
 requestInfo.urlPath = (urlPath);
 requestInfo.args = (args.toString());
 
 RequestData requestData = new RequestData(requestInfo, userInfo,methodSignature);
 log.info(userInfo.sessionId + " REQ {} by "+userInfo.userName +"@"+userInfo.clientIp+", time {} mills; args ["+args.toString()+"]", urlPath + " ["+ Thread.currentThread().getId() +"]" , elapsedTime);
 responseTime.put(methodSignature, requestData);
 //addUserAuditLog(requestData);
 
 }catch(Exception ex){
 issueType = IssueType.OTHER;
 error = ex.getMessage();
 log.error(ex.getMessage());
}
 
if(obj==null){
 switch(issueType){
 case ISSUE_URL_SUFFIX:throw new GenericException("URL ends with trailling /"); 
 case OTHER:throw new GenericException(error);
 default:
 break;
 } 
}
return obj;
}
/**
 * @param exceptions
 */
@AfterThrowing(pointcut="execution(public * com.generic.controller.*.*(..))",throwing="ex") 
 public void MethodError(Exception ex){ 
   log.error("@Exception {}", ex.toString()); 
 } 
 
@Pointcut("execution(public * *(..))")
private void anyPublicOperation() {
   log.info("Testing the public Execution call");
 }
}

When we run the system we expect the following response in the log which does the @Around joint point.

Before:

17-Jul-2014 03:25:35,067-INFO – GenericLoggerAspect:130 –       START List com.humesis.generic.controller.UserController.getUsers(HttpServletResponse)

After:

17-Jul-2014 03:25:35,106-INFO – GenericLoggerAspect:176 – 674B8F114926E5A3BB143E7126D828C7 REQ /users [28] by rahul@0:0:0:0:0:0:0:1, time 36 mills; args [HttpSessionSecurityContextRepository]

This data can Asynchronously be audited or logged for generating the access pattern or hotspots in the service access.

 

CAS-ify and Implement Single sign on in your application, Oh what a rellief…

 

Ah, there comes a time in a developers life when the application they develop requires to be actually used and in this particular case I am talking about multiple applications in the eco-system. Now this developer in discussion appears to be an enterprise scale geek. In order to use these applications, people need to be scrutinized by a central or single sign on like security entity. Here comes a Central Authentication Service to rescue your CASe.

 

I will take you through a set of steps here,

 

A. Configure CAS single sign on Server on a Tomcat with SSL configured.

Steps are detailed here.

B.. Create a service application to actually authenticate with this CAS server and service your request.

RESTful client for CAS secured services. A sample example is available here

 

C. Generate your SSL trusted certificates so that This CAS Server and your Service application can actually interact

Setup Certificates:   The SSL related certificates used for development are self-signed in nature and are restricted to IP on which server and services are running. The keytool command provisioned by the JDK is used for this purpose.

Self-Signed Certificate Setup steps for CAS:

  1. Configure: https://wiki.jasig.org/display/CASUM/RESTful+API
  2. Test : Using commons http client
  3.  Workflow,
  • - Get the TicketGrantingTicket from server = "https://localhost:8443/cas/v1/tickets";
  • - Get the ServiceTicket service = "https://localhost:8443/cas-sample/secure";
  • - Based on the service ticket GET access to the secured REST API service

D. Certification Path Exception for SSL handshake:
Reference: http://www.mkyong.com/webservices/jax-ws/suncertpathbuilderexception-unable-to-find-valid-certification-path-to-requested-target/
Source: https://github.com/vishwakarmarhl/javahelper/blob/master/InstallCert.java
Command: java InstallCert localhost:8443 // Also add trust for the service and cas_server IP

E. Use a Http Test client to authenticate and call the service.

 

Setup a CASified Secure Single sign on using the Central Authentication Service

The single sign on CAS server is a native java and spring based application and its best to build it yourself and deploy the WAR artifact to a SSL secured tomcat container.

A. Here are the pre-requisite for this setup:

  1. Java JDK 1.7
  2. Apache Tomcat 7
  3. Maven

B. Configure Tomcat 7 with the SSL configuration

  1. Configure the %TOMCAT_HOME%/conf/server.xml with the relevant path and password of the keystore as per the tag below. Here “${tomcat.base}/.keystore” is the path where the .keystore file needs to be saved. Comment out the AprLifecycleListener from the same configuration to avoid the “java.lang.Exception: Connector attribute SSLCertificateFile must be defined when using SSL with APR”
<Connector port="8443" protocol="HTTP/1.1" SSLEnabled="true" maxThreads="150" scheme="https" secure="true" clientAuth="false" sslProtocol="TLS"  keystoreFile="${tomcat.base}/.keystore" keystorePass="casmanion"/>

C. Generate the trust keystore

  • CD to the tomcat base folder ~/apache-tomcat-7.0.54/ and use the keytool to generate a .keystore file with the password configured in tomcat
  • keytool -genkeypair -dname "CN=127.0.0.1, OU=Vishwakarma, O=Rahul, L=Bangalore, ST=Karnataka, C=IN"  -alias rahul_casserver -keyalg RSA -ext san=ip:127.0.0.1 -keystore .keystore
  • Verify the store certificates by using, keytool -list -keystore .keystore
  • Start the tomcat server and see if it initializes properly after reading the SSL configuration

C. Create a Database and a user table to accomodate the user information in a MySQL server in root@localhost:3306

CREATE DATABASE IF NOT EXISTS `casdb` 
USE `casdb`;
DROP TABLE IF EXISTS `users`;
CREATE TABLE `users` (
 `user_id` int(11) NOT NULL AUTO_INCREMENT,
 `user_password` varchar(45) NOT NULL,
 `user_name` varchar(45) NOT NULL,
 `user_email` varchar(255) DEFAULT NULL,
 `user_creation_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP,
 PRIMARY KEY (`user_id`),
 UNIQUE KEY `user_name_UNIQUE` (`user_name`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;'

INSERT INTO `casdb`.`users` (`user_id`, `user_password`, `user_name`, `user_email`) VALUES ('1', 'rahul', 'rahul', 'rahul_3766@yahoo.com');

D. Build and Deploy CAS with JDBC and restlets

  1. Obtain the latest release CAS Server from JASIG CAS download
  2. Rename the ~\CAS_Example\cas-server-4.0.0\modules\cas-server-webapp-4.0.0.war to cas.war
  3. Deploy this on tomcat webpps folder and start the server to test the bare deployment and then stop the server
  4. Add the JDBC and Restlet API authentication support. Copy cas-server-integration-restlet-4.0.0.jarcas-server-support-jdbc-4.0.0.jar to the ~/cas/WEB-INF/lib folder of the tomcat webapp deployment.
  5. Add the MySQL JDBC driver mysql-connector-java-5.1.6.jar and apache libraries org.apache.commons.dbcp.jar and org.apache.commons.pool.jar to ~/cas/WEB-INF/lib folder
  6. Time to configure the ~\apache-tomcat-7.0.54\webapps\cas\WEB-INF\deployerConfigContext.xml , Adding the JDBC authentication handler that will authenticate the entered credentials with the user & pass in the configured mysql datasource.
<!-- Adding the JDBC authentication related configuration --> 
<bean id="searchModeSearchDatabaseAuthenticationHandler" class="org.jasig.cas.adaptors.jdbc.SearchModeSearchDatabaseAuthenticationHandler" abstract="false" lazy-init="default" autowire="default" > 
   <property name="tableUsers"><value>users</value></property> 
   <property name="fieldUser"><value>user_name</value></property> 
   <property name="fieldPassword"><value>user_password</value></property> 
   <!--<property name="passwordEncoder" ref="defaultPasswordEncoder"/>--> 
   <property name="dataSource" ref="dataSource"/> 
</bean>
 <!-- Data source definition -->
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
<property name="driverClassName"> <value>com.mysql.jdbc.Driver</value> </property>
<property name="url">
<value>jdbc:mysql://localhost:3306/casdb</value>
</property>
<property name="username"><value>root</value></property>
<property name="password"><value></value></property>
</bean>
<bean id="defaultPasswordEncoder" class="org.jasig.cas.authentication.handler.DefaultPasswordEncoder">
<constructor-arg value="SHA-256" />
</bean>
Also, We need to Add the reference to this bean in the authenticationManager beans constructor-arg map entry as follows:

<entry key-ref="searchModeSearchDatabaseAuthenticationHandler" value-ref="primaryPrincipalResolver" />

E. Test your standalone CAS Server for authentication

  1. Start your tomcat and make sure there is no error on the startup and all the dependencies are resolved
  2. Test the login using https://127.0.0.1:8443/cas/login and user/pass as rahul/rahul
  3. Test the logout using https://127.0.0.1:8443/cas/logout
  4. Here is a curl test that needs to be verified. curl -X POST –data “username=rahul&password=rahul” https://127.0.0.1:8443/cas/v1/tickets –cacert “C:\Dev\CAS_Example\apache-tomcat-7.0.54\.keystore”

F. The deployed artifact “cas” can be found as a zip at GITHUB: https://github.com/vishwakarmarhl/CASifiedExamples.git