Monday, July 15, 2013

HTML5 WebSocket Sample with Java Servlet on Server Side


Document  Version 1.0

Copyright © 2012-2013 beijing.beijing.012@gmail.com


Beside Server-Sent Events, WebSocket is an another important feature introduced by HTML5 for browser to update web page content automatically from server.  The most important differences between WebSocket and Server-Sent Events are:  

  • Server-Sent Events is one way communication whereas WebSocket use full-duplex channel.
  • Server-Sent Event is HTTP,  WebSocket introduced new protocol "ws" protocol

Here I am  not going compare WebSocket and Server-Sent Events in detail, I will just show  you a simple WebSecket sample to get a feeling.


In the example below, browser opens a window, send text message to server. Server just send message back to browser, shown in the window. (by the way, the sample could be extended with small effort to work as a chat application ...)


The client is just a html page with embedded javascript. The Server is a Java Servlet web application running on Tomcat 7.0.29. (WebSocket, "ws" protocol support with Tomcat since version 7.0.24 ).



1. Create a simple web application "WebSocketSample"


User your favorite IDE (I use Eclipse) to create a Web Application. Be sure to use servlet 3.x style.  
The sample web application consists of :
  • two java classes, which are the server side code
  • one "html" page which is the client


2.  MyWebSocketServlet.java



The sample uses Tomcat implementation of of "WebSocket", so we need to have "catalina.jar" on class path.
Add servlet-api.jar to class path as well.


package sample;

import java.util.concurrent.ConcurrentHashMap;

import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;

import org.apache.catalina.websocket.StreamInbound;
import org.apache.catalina.websocket.WebSocketServlet;

/**
 * WebSocketServlet is contained in catalina.jar. It also needs servlet-api.jar
 * on build path
 *
 * @author wangs
 *
 */
@WebServlet("/wsocket")
public class MyWebSocketServlet extends WebSocketServlet {

private static final long serialVersionUID = 1L;

// for new clients, <sessionId, streamInBound>
private static ConcurrentHashMap<String, StreamInbound> clients = new ConcurrentHashMap<String, StreamInbound>();

@Override
protected StreamInbound createWebSocketInbound(String protocol,
HttpServletRequest httpServletRequest) {

// Check if exists
HttpSession session = httpServletRequest.getSession();

// find client
StreamInbound client = clients.get(session.getId());
if (null != client) {
return client;

} else {
client = new MyInBound(httpServletRequest);
clients.put(session.getId(), client);
}

return client;
}

public StreamInbound getClient(String sessionId) {
return clients.get(sessionId);
}

public void addClient(String sessionId, StreamInbound streamInBound) {
clients.put(sessionId, streamInBound);
}
}




3.  MyInbund.java


Since Tomcat's WsOutbound has indirect dependency on "tomcat-koyote.jar", please add this jar to class path to resolve compile error like "the hierarchy of the type ... is inconsistent ..."


package sample;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;

import javax.servlet.http.HttpServletRequest;

import org.apache.catalina.websocket.MessageInbound;
import org.apache.catalina.websocket.WsOutbound;

/**
 * Need tomcat-koyote.jar on class path, otherwise has compile error "the hierarchy of the type ... is inconsistent"
 * @author wangs
 *
 */
public class MyInBound extends MessageInbound{

private String name;

private WsOutbound myoutbound;

public MyInBound(HttpServletRequest httpSerbletRequest) {

}
@Override
public void onOpen(WsOutbound outbound) {
System.out.println("on open..");
this.myoutbound = outbound;
try {
this.myoutbound.writeTextMessage(CharBuffer.wrap("hi, what's your name?"));

} catch (Exception e) {
throw new RuntimeException(e);
}

}

@Override
public void onClose(int status) {
System.out.println("Close client");
//remove from list
}

@Override
protected void onBinaryMessage(ByteBuffer arg0) throws IOException {

}

@Override
protected void onTextMessage(CharBuffer inChar) throws IOException {

System.out.println("Accept msg");
CharBuffer outbuf = CharBuffer.wrap("- " + this.name + " says : ");
CharBuffer buf = CharBuffer.wrap(inChar);

if(name != null) {
this.myoutbound.writeTextMessage(outbuf);
this.myoutbound.writeTextMessage(buf);
} else {
this.name = inChar.toString();

CharBuffer welcome = CharBuffer.wrap("== Welcome " + this.name + "!");
this.myoutbound.writeTextMessage(welcome);
}

this.myoutbound.flush();

}

}




4.  wssockrt.html




<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Tomcat web socket</title>
<script type="text/javascript">
var ws = new WebSocket("ws://localhost:8080/WebSocketSample/wsocket");
ws.onopen = function () {
};

   ws.onmessage = function(message) {
document.getElementById("msgArea").textContent += message.data + "\n";              
   };

   function postToServer() {
ws.send(document.getElementById("msg").value);
document.getElementById("msg").value = "";
}

function closeConnect() {
ws.close();
}
</script>
</head>

<body>
  <div>
<textarea rows="4" cols="100" id="msgArea" readonly></textarea>
</div>
<div>
<input id="msg" type="text"/>
<button type="submit" id="sendButton" onclick="postToServer()">Send</button>
</div>
</body>
</html>


5.  Export ".war" and deploy it on a running Tomcat 



Export the "WebSocketSample.war"and deploy it on a running Tomcat server.

Open the link below with browser:

http://localhost:8080/WebSocketSample/wsocket.html

You will see:



Now type your name and send:

So it woks!


6.  The "WebSocket" Header  


When we take a close look at the headers of the Request and Response above:





We will see, browser is not speaking "http" with server any more,  now it speaks "websocket" protocol.

Tuesday, July 9, 2013

HTML5 Server-Sent Events Sample with Java Servlet as Event Server

Document  Version 1.0

Copyright © 2012-2013 beijing.beijing.012@gmail.com



Server-Sent Events is a HTML5 feature which support browse to update page content automatically.

I will show a Server-Sent Events sample which get update of serve time automatically. Server side implementation is a Java Servlet running on Tomcat.


1. Create a simple web application:







2. Create a 3.X style Java Servlet "SseServer.java"




package sse;

import java.io.IOException;
import java.io.PrintWriter;
import java.util.Date;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 *
 * @author swang
 *
 */
@WebServlet("/SseServer")
public class SseServer extends HttpServlet {
private static final long serialVersionUID = 1L;

protected void doGet(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
doPost(request, response);
}

protected void doPost(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
// Besides "text/event-stream;", Chrome also needs charset, otherwise
// does not work
// "text/event-stream;charset=UTF-8"
response.setContentType("text/event-stream;charset=UTF-8");
response.setHeader("Cache-Control", "no-cache");
response.setHeader("Connection", "keep-alive");

PrintWriter out = response.getWriter();

while (true) {
out.print("id: " + "ServerTime" + "\n");
out.print("data: " + new Date().toLocaleString() + "\n\n");
out.flush();
// out.close(); //Do not close the writer!
try {
Thread.currentThread().sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

}



Make sure in the Servlet, set content type of response as "text/event-stream;charset=UTF-8", and NOT
just "text/event-stream". Otherwise it does not work with Chrome. 



3.  Create a SSE.html file




<!DOCTYPE html>
<html>
<body>
<h1>Current Server Time  : </h1>

<div id="ServerTime"></div>

<script>
if (typeof (EventSource) !== "undefined") {
var source = new EventSource("http://localhost:8080/SSE/SseServer");
source.onmessage = function(event) {
document.getElementById("ServerTime").innerHTML += event.data
+ "<br><br>";
};
} else {
document.getElementById("ServerTime").innerHTML = "Sorry, your browser does not support server-sent events...";
}
</script>

</body>
</html>



4. Deploy the Web Application


Export the Web Application as "SSE.war", and deploy it to a running Tomcat Server.


5. Try accessing the SSE.html use browser


Access the SSE.html using link http://localhost:8080/SSE/SSE.html

Your browser should shown content as below:




When every 5 seconds, there is new line shown in your browser, congratulations, you have made your first Server-Sent Events Application!




Saturday, July 6, 2013

JMX Sample: monitor standalone Application use own JMX Server

Document  Version 1.0
Copyright © 2012-2013 beijing.beijing.012@gmail.com

 When you are familiar with JBoss, you should have known JMX-console. When you want to monitor you application deployed on JBoss, you might go with following step:

  •  wrapp the to be monitored property  in a Mbean,
  •  register the MBean with JBoss' JMX sesrver
  •  check / manage the properties through JMX-Console 
But what if you want to monitor a standalone application, and there is no JMX-server to use?

In such case, your will have to start your own JMX-server.

I will show how to use your own JMX-server, to monitor a standalone application which monitors the application's local time.

1. The MBean


The MBean interface exposes which methods are monitored by JMX.

package mbean;

/**
 *
 * @author swang
 *
 */
public interface MySampleMBean {
String getServerTime();
}



This is the MBean implementation. The property "serverTime"  will be updated every 5 seconds. We expect the upated serverTime will be shown to a JMX client when it connects to JXX-Server.

package mbean;

import java.util.Date;

/**
 *
 * @author swang
 *
 */
public class MySample implements MySampleMBean, Runnable{

private static MySample instance = new MySample();
private String serverTime;

private MySample() {
}

public static MySample getInstance() {
return instance;
}

@Override
public String getServerTime() {
return this.serverTime;
}

public void setServerTime(String serverTime) {
this.serverTime = serverTime;
}

@Override
public void run() {
while(true) {
//Updating serverTime every 5 seconds
setServerTime(new Date().toLocaleString());

try {
Thread.currentThread().sleep(5000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}


2. A JMX Agent


This is the main class of the program, we call it a JMX agent. It starts a JMX-Server, starts the MBean, and regist the MBean with the JMX-Server.

package mbean;

import java.lang.management.ManagementFactory;
import java.rmi.registry.LocateRegistry;
import java.util.HashMap;

import javax.management.MBeanServer;
import javax.management.ObjectName;
import javax.management.remote.JMXConnectorServer;
import javax.management.remote.JMXConnectorServerFactory;
import javax.management.remote.JMXServiceURL;

/**
 *
 * @author swang
 *
 */
public class MyJMXAgent {

private static MBeanServer mbs = null;

public void init() throws Exception {
mbs = ManagementFactory.getPlatformMBeanServer();

ObjectName objName = new ObjectName("TestJMXAgent:name=MySampleMBean");
mbs.registerMBean(MySample.getInstance(), objName);

String hostname = "localhost";
int port = 1717;
LocateRegistry.createRegistry(port);
System.setProperty("java.rmi.server.randomIDs", "true");
System.setProperty("java.rmi.server.hostname", hostname);

HashMap<String, Object> env = new HashMap<String, Object>(3);
env.put("jmx.remote.x.password.file", "jmxremote.password");
env.put("jmx.remote.x.access.file", "jmxremote.access");

JMXServiceURL url = new JMXServiceURL("service:jmx:rmi://" + hostname
+ ":" + port + "/jndi/rmi://" + hostname + ":" + port
+ "/jmxrmi");
JMXConnectorServer cs = JMXConnectorServerFactory
.newJMXConnectorServer(url, env, mbs);

cs.start();

System.out.println("Starting JMXConnectorServer on: " + url);
}

public static void main(String[] args) throws Exception {
System.out.println("jmx agent starting");
// Start updating MBean
new Thread(MySample.getInstance()).start();

MyJMXAgent agent = new MyJMXAgent();
agent.init();

}
}



3. Configuration JMX access:


Create a "jmxremote.access" file:

monitor   readonly


Create a "jmxremote.password" file:

monitor   password


4. Run MyJMXAgent as Java application:



jmx agent starting
host name: localhost
Starting JMXConnectorServer on: service:jmx:rmi://localhost:1717/jndi/rmi://localhost:1717/jmxrmi

A JMX-Server is now running on localhost:1717, and you could use a JMX-client to connect to it.

5. Connect to JMX-Server with jconsole


Start jconsole an connect to MyMXAgent:





Check monitored property "serverTime":






Click "Refresh" button to get the updated "serverTime" :




Tuesday, July 2, 2013

Create Document with Digital Signature using GPG on Linux

Document  Version 1.0
Copyright © 2012-2013 beijing.beijing.012@gmail.com

My friend Tomy has got an offer from GooPle, greate, isn't it?  GooPle!  On the Phone, the HR manager, Mr. Bozz of GooPle says, he will sign the contract and send it to Tomy right away as post. But at the same time, Mr. Bozz also pointed out, that Tomy should sign the contract and send it back to GooPle in 8 hours.  8 hours? Tomy is even not sure if he could receive the post in 8 hours, since he is living in a small village, the post man comes only once a week...

Of course Tomy do not want to miss this chance, by GooPle. "Sorry sir,  but could you sign it and send me as file, and I will sign it with digital signature, so you would get the signed contract in  20 minutes "
"Yes, of course ..."

In the following section, I will show step by step, how does Mr. Bozz prepare the contract, sign it with his digital signature, and how will Tomy verify the signature, and extract the original content of the contract, otherwise the content is encrypted and not readable.

Step 1. Mr. Bozz prepare the original contract, and save the file as "contract.txt" :


"This is a contract between Mr. Tomy and GooPle ..."

Step 2. Mr. Bozz sign the "contract.txt" file with his digital signature


Mr. Bozz's computer is a Linux (Mint 14) machine, he knows there is a tool under linux called "GPG (GNU Privacy Guard)", which could be used to create digital signature.

He opens a terminal and types following command:

bozz@PC007x /home/bozz $ gpg --sign contract.txt

Console output shows:

gpg: directory `/home/tomy/.gnupg' created
gpg: new configuration file `/home/tomy/.gnupg/gpg.conf' created
gpg: WARNING: options in `/home/tomy/.gnupg/gpg.conf' are not yet active during this run
gpg: keyring `/home/tomy/.gnupg/secring.gpg' created
gpg: keyring `/home/tomy/.gnupg/pubring.gpg' created
gpg: no default secret key: secret key not available
gpg: signing failed: secret key not available


GPG complains here it can not find the private and public key of Mr. Bozz, but these keys are need for creating digital signature.

Step3. Mr. Bozz creates his  private and public key:


To generate keys, Mr. Bozz types following command in the terminal:

bozz@PC007x /home/bozz $ gpg --gen-key

Terminal output:

gpg (GnuPG) 1.4.11; Copyright (C) 2010 Free Software Foundation, Inc.
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

Please select what kind of key you want:
   (1) RSA and RSA (default)
   (2) DSA and Elgamal
   (3) DSA (sign only)
   (4) RSA (sign only)
Your selection?

Type 1 in the terminal and return: 


RSA keys may be between 1024 and 4096 bits long.
What keysize do you want? (2048) 

Return to take the default key long.

Please specify how long the key should be valid.
         0 = key does not expire
      <n>  = key expires in n days
      <n>w = key expires in n weeks
      <n>m = key expires in n months
      <n>y = key expires in n years
Key is valid for? (0)

Type return to take the default expire time, i.e. never expires:

Is this correct? (y/N)

Type y and continue with name and email:


You need a user ID to identify your key; the software constructs the user ID
from the Real Name, Comment and Email Address in this form:
    "Heinrich Heine (Der Dichter) <heinrichh@duesseldorf.de>"

Real name: bozz
Name must be at least 5 characters long
Real name: Im Bozz
Email address: 

Email address: bozz@goople.com

Comment: blabla

You selected this USER-ID:
    "Im Bozz (blabla) <bozz@goople.com>"

Change (N)ame, (C)omment, (E)mail or (O)kay/(Q)uit? 

type "O" and return to set 123 as password:

You need a Passphrase to protect your secret key.

gpg: gpg-agent is not available in this session
Enter passphrase: 

123
123

gpg: gpg-agent is not available in this session
We need to generate a lot of random bytes. It is a good idea to perform
some other action (type on the keyboard, move the mouse, utilize the
disks) during the prime generation; this gives the random number
generator a better chance to gain enough entropy.

Not enough random bytes available.  Please do some other work to give
the OS a chance to collect more entropy! (Need 236 more bytes)
..

Move your mouse, or type something with keybord in another terminal, and you will see the gpg process proceeds:

Not enough random bytes available.  Please do some other work to give
the OS a chance to collect more entropy! (Need 104 more bytes)
..+++++
gpg: /home/tomy/.gnupg/trustdb.gpg: trustdb created
gpg: key 324B99FD marked as ultimately trusted
public and secret key created and signed.

gpg: checking the trustdb
gpg: 3 marginal(s) needed, 1 complete(s) needed, PGP trust model
gpg: depth: 0  valid:   1  signed:   0  trust: 0-, 0q, 0n, 0m, 0f, 1u
pub   2048R/324B99FD 2013-07-03
      Key fingerprint = 5799 923C 4123 523E 0567  794F 2E52 B3F5 324B 99FD
uid                  Im Bozz (blabla) <bozz@goople.com>
sub   2048R/A57E911C 2013-07-03

Now the keys are generated.

Step4. Mr. Bozz try sign the contract again:



bozz@PC007x /home/bozz $ gpg --sign contract.txt


You need a passphrase to unlock the secret key for
user: "Im Bozz (blabla) <bozz@goople.com>"
2048-bit RSA key, ID 324B99FD, created 2013-07-03

gpg: gpg-agent is not available in this session
Enter passphrase: 


Enter "123" as password, return. The signed contract file is created as "contract.txt.gpg" 

 bozz@PC007x /home/bozz $ ls
contract.txt  contract.txt.gpg


Step5. Mr. Bozz send the "contract.txt.gpg" file to Tomy.



Step6. Tomy tries to verify file "contract.txt.gpg"



To verify the file is really from Mr. Bozz from GooPle, Tomy can use GPG utilities.  We assume has GPG installed on his machine, and he has created his public and private key as described in Step.3

So Tomy types command below to verify the singnature of Mr. Bozz:

tomy@PC001x /home/tomy $ gpg --verify contract.txt.gpg 
gpg: Signature made Tue 02 Jul 2013 03:00:40 PM CEST using RSA key ID B06521C9
gpg: Can't check signature: public key not found

The above information shows that gpg can not verify the signature, since it is missing the public key of Mr. Bozz public. 

When Tomy tries to read  or show the content of the  contract "contract.txt.gpg", he will find that the file is actuall encrypted, and not readable. 
So now Tomy needs to ask Mr. Bozz  for his public key.


Step7. Mr. Bozz exports his public and send it to Tomy



Mr. Bozz exports his public key as file "bozz.asc" usingm command:

 bozz@PC007x /home/bozz $ gpg -a --export >  bozz.asc


A file "bozz.asc" is genereated with the content below:
===========
-----BEGIN PGP PUBLIC KEY BLOCK-----
Version: GnuPG v1.4.11 (GNU/Linux)

mQENBFHSzPABCADfOFyUJOpm+oMx0MgP6cZy1vMLkPpNNwMFTcdBxswx5t9w77XL
jPR1vw7GytLebmIYQ66NcJX3XETLHHcsSCEZF4ueAGbi/XSeBRWA/hSNGHRORkXa
Mvmq1uTq+aUbP/teOgfnEJCV1Rn5u1IP9W7GfJM7vNmfgBVwTj464zoePJjiF6Le
5Wwhkove2hURokltQJSJxFFfEEkEfrRqmAtzKGxxD2UXfAxp+/g1ULFl6J/1zDHN
Gdv9M6ppT9limlNCAwcRnas+bBvsyNF7sY+5CgX2rc6gusAcqXayqq1Ax5gnkE8V
14IJVyMbjeY+t84iWAi1eiva/Oq4b7W1dg/7ABEBAAG0Gm1vYmlsZWdhdGUgKG1n
KSA8bWdAbWcuZGU+iQE4BBMBAgAiBQJR0szwAhsDBgsJCAcDAgYVCAIJCgsEFgID
AQIeAQIXgAAKCRCYbbznsGUhycDmB/9+xPPIG2+Q5CqlmYqhgG3xsX0mQQW85w0r
LHJ3ix6Ah51u88OjvQ+57lkP+bE8EGOk6SGn/49OPM1DE5rAczDn9jdbfn8tVJ6k
pD1JKIPKC9yDhAmxGcRlzTPATJOYva4DMzNa7xFJTy4e97k2xgibESt0E6sh6EF5
ZtWJHyBAtdvCdZcLQy4bEAW6wOrGPv3T/tr/nW71hD96aGfiluRQ1Y3WfH15nc/u
CVEJlXmGP2NHHqYVjReMbPqEFfDlh+/tQ+0nvwYHc8p/Ixk6FbAUcbDXaNibZTZN
fS3F3CO7Oz/u5LM4CbOpTyqMSK6AlMU4kJECHdAAAcMyA4d2jM/guQENBFHSzPAB
CADAgZmDbU1PkJguIxq/j0fzJirGrE1k/U1rp4asQuKiU3WcVJ07h++yAwcvFz1 J
RHMyi8qm+Vvbz+wc7ZMUgzbFtH4xvYyNjxkF102crjdaWsIRK9aw5okZI48DK3kN
lPR2VnwzU6wr6sP77Nr3avFlcIqTFIRAsj9Z2cuMIh8SQ1yzye38ukcodK5zJl5S
28wyPX59raUOUn6Mm3vlzGAY80m299jgUCLx0TEl/D6nF8T98bZSRqG1xxnwcJGE
EjvsibzgJ1b6EZ66KHQY4200+gDMeo6MN6PB9TbL8jhViBpAaRAMoL+eoLAWTIq0
AFdEpvWmUSbRPXiKdA4UszebABEBAAGJAR8EGAECAAkFAlHSzPACGwwACgkQmG28
57BlIcmY8gf/UJDDYBV+b6Mpwh+/PBQFngnxXiLL4P9Wh3C3M2wHSqS7MWmcUG7B
qKPffqJyPvy4yxtOn5UxfJggE/SZo93PWsiRHggeBYu7E6qJoXBI2lyf9KHfE5sL
O6RuQ9OOl8CXh12klCcLzxQUwd42eT68iSS4prCWEIRb0EpLn6cjGAPUBR/rzoqG
gUG1jP9agh338IdTJOALDCldfHz/pua0ZWay8Oqi0Khi+kbpwCCAnrKppj0WAXlj
ju4feVCuOcmwa0TB5QbOZwjJs4wMITDza6wauo0ugitn2XlarWp97H2YmLzsfTTH
kpcAy6oVFGwyS00xBTy9z9Dx2Gc3mOhjug==
=49J7
-----END PGP PUBLIC KEY BLOCK-----

============

Mr. Bozz sends his public key, i.e. the file "bozz.asc" to Tomy

Step8. Tomy  imports Mr. Bozz into gpg as trusted key:



tomy@PC001x /home/tomy $ gpg --import bozz.asc

Step9. Tomy tries to verify file "contract.txt.gpg" again:



tomy@PC001x /home/tomy $gpg --verify contract.txt.gpg 
gpg: Signature made Tue 02 Jul 2013 03:00:40 PM CEST using RSA key ID B06521C9
gpg: Good signature from "Im Bozz (blabla) <bozz@goople.com>"
gpg: WARNING: This key is not certified with a trusted signature!
gpg:          There is no indication that the signature belongs to the owner.
Primary key fingerprint: 850F 0E98 AF5E 4CD3 D9A9  0E1A 986D BCE7 B065 21C9


Step10. Tomy extracts the content from "contract.ext.gpg: 



tomy@PC001x /home/tomy $ gpg --output my_contract.txt --decrypt contract.txt.gpg 
gpg: Signature made Tue 02 Jul 2013 03:00:40 PM CEST using RSA key ID B06521C9
gpg: Good signature from "Im Bozz (blabla) <bozz@goople.com>"
gpg: WARNING: This key is not certified with a trusted signature!
gpg:          There is no indication that the signature belongs to the owner.
Primary key fingerprint: 850F 0E98 AF5E 4CD3 D9A9  0E1A 986D BCE7 B065 21C9

A new file "my_contrct.txt" is created,  Tomy can just show the file content with less command:
tomy@PC001x /home/tomy $ less  my_contrct.txt

This is a contract between Mr. Tomy and GooPle ...
my_contrct.txt (END)

We are almost done here.
When we take another look at the verification information in step 9, we will see there is a warning says:

"WARNING: This key is not certified with a trusted signature!  There is no indication that the signature belongs to the owner. "

This is because, the GPG utility of Tomy complains the public key of Mr. Bozz, is not certified by a known third party, a CA(Certificate Authority).  The get away with the warning,  Mr. Bozz need to have his public key file again signed by a thirty party, whom Tomy trust, i.e. Tomy had imported the 3 party's public key in his GPG utility.

We assume, Jerry is someone wo Tomy trust, so Tomy will import Jerry's public key by:

tomy@PC001x /home/tomy $ gpg --import jerry.asc

Step11. Mr. Bozz send his public key to Jerry 


Step12. Jerry signs  Mr. Bozz public key


Jerry imports Bozz's public key to his GPG utility:

jerry@PC001x /home/jerry $ gpg --import bozz.asc

Jerry sign Mr. Bozz' public key using :
jerry@PC001x /home/jerry $ gpg --sign-key B06521C9

Here B06521C9 is the id of Mr Bozz' key.

Jerry export Mr. Bozz's key as file:

jerry@PC001x /home/jerry $  gpg -a --export B06521C9 >  bozz_signed.asc


The public key of Mr. Bozz is now signed by jerry, and stored  in file "bozz_signed.asc"


Step13. Mr. Bozz  signe the contract again.import the signed key "bozz_signed.asc"


Mr. Bozz imports the signed key sign the contract again, and send the contract to Tomy

Step14. Tomy verify the new contract file


 tomy@PC001x /home/tomy $  gpg --verify samp.txt.gpg 

gpg: Signature made Wed 03 Jul 2013 11:09:12 AM CEST using RSA key ID B06521C9

gpg: Good signature from "Im Bozz (blabla) <bozz@goople.com>"


Congratulations!