Created
October 19, 2012 17:23
-
-
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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