Skip to content

Instantly share code, notes, and snippets.

@jonfhancock
Created October 19, 2012 17:23
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jonfhancock/3919458 to your computer and use it in GitHub Desktop.
Save jonfhancock/3919458 to your computer and use it in GitHub Desktop.
A pair of servlets to manage nonces and signature verfication for Android in-app billing.
public class GetNonceServlet extends HttpServlet{
public final static String KNOWN_NONCES = "KNOWN_NONCES";
@Override
public void doGet(HttpServletRequest req, HttpServletResponse resp)
throws IOException {
resp.setContentType("text/plain");
// We need a session object to store temporary data for the user.
HttpSession session = req.getSession();
// The session might already have some known nonces. We don't want to overwrite them.
HashSet<Long> knownNonces = (HashSet<Long>) session.getAttribute(KNOWN_NONCES);
// If there weren't already nonces, we'll start fresh
if(knownNonces == null){
knownNonces = new HashSet<Long>();
}
// Go time. We need a new nonce. The next two lines do the work.
SecureRandom random = new SecureRandom();
long newNonce = random.nextLong();
// We save the nonce to our list of nonces, and stuff it back into the session.
knownNonces.add(newNonce);
req.getSession().setAttribute(KNOWN_NONCES, knownNonces);
// Now we can send the nonce back to the client. Our work is done.
resp.getWriter().print(newNonce);
}
}
public class VerifyServlet extends HttpServlet{
private final static String KNOWN_NONCES = "KNOWN_NONCES";
private final static ObjectMapper MAPPER = new ObjectMapper();
@Override
public void doGet(HttpServletRequest req, HttpServletResponse resp)
throws IOException {
resp.setContentType("application/json");
// We need a session object to store temporary data for the user.
HttpSession session = req.getSession();
// The session might already have some known nonces. We don't want to overwrite them.
HashSet<Long> knownNonces = (HashSet<Long>) session.getAttribute(KNOWN_NONCES);
// If there weren't already nonces, we'll start fresh
if(knownNonces == null){
knownNonces = new HashSet<Long>();
}
// The request object should have the data and the signature we need.
String signedData = req.getParameter("signed-data");
String signature = req.getParameter("signature");
// If either of these is null, we don't care about anything else.
if(signedData != null && signature != null){
// This is just a Java object representation of the JSON the Play Store sends us
Notification notification = MAPPER.readValue(signedData, Notification.class);
// We pull out the nonce so we can check it
long nonce = notification.nonce;
if (knownNonces.contains(nonce)) {
// The nonce has been used. Now KILL IT WITH FIRE!
knownNonces.remove(nonce);
req.getSession().setAttribute(KNOWN_NONCES, knownNonces);
boolean status = verify(signedData, signature);
if (status) {
resp.getOutputStream().print(MAPPER.writeValueAsString(notification));
} else {
resp.sendError(HttpStatus.SC_FORBIDDEN, "SIGNATURE_INVALID");
}
} else {
resp.sendError(HttpStatus.SC_FORBIDDEN, "NO_MATCHING_NONCE");
}
} else{
resp.sendError(HttpStatus.SC_BAD_REQUEST,"NO_SIGNATURE_ORSIGNED_DATA");
}
}
public boolean verify(String signedData, String signature) {
final String encodedPublicKey = "bGtlid2luZndrdm4wYmhh...";
try {
byte[] decodedKey = Base64.decode(encodedPublicKey);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PublicKey publicKey =
keyFactory.generatePublic(new X509EncodedKeySpec(decodedKey));
Signature sig = Signature.getInstance("SHA1withRSA");
sig.initVerify(publicKey);
sig.update(signedData.getBytes());
if (!sig.verify(Base64.decode(signature))) {
// The signature verification failed.
return false;
}
return true;
} catch (Exception e) {
// CATCH ALL THE ERRORS!!
}
return false;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment