Putting It All Together |
When a Player joins a game by calling themayIPlay
method, the Game generates the cards for the Player to play with. Before sending the cards back to the Player, the Game digitally signs the cards using the APIs in thejava.security
package.Later, when a Player claims to have a winning card, the Game verifies the signature to make sure the card was created by this Game for the current game.
Let's look at the code in the Game that does this. All of the code related to signing and verifying cards is in
NotaryPublic
, an object created and used by theRingMaster
.
Note: This section discusses how BINGO uses the security APIs in the JDK to sign cards and verify signatures. It does not talk about the general use of the security APIs. For that, see our online security trail Java Security 1.1.
Setting Up to Sign and Verify Cards
Following is the constructor for theNotaryPublic
class:This constructor generates a public and private key pair and then assigns each member of the pair to separate member variables. The private key of a key pair is used to generate signatures. The public key of a key pair is used to verify them. TheNotaryPublic() { KeyPair pair = null; try { KeyPairGenerator keyGen = KeyPairGenerator.getInstance("DSA"); keyGen.initialize(1024, new SecureRandom()); pair = keyGen.generateKeyPair(); } catch (Exception e) { ErrorMessages.error("Cannot sign cards. Continuing anyway."); } priv = pair.getPrivate(); pub = pair.getPublic(); }NotaryPublic
uses the same key pair, the one created in the constructor, to generate and verify the signatures for all of the cards created for this Game.Signing the Cards
NotaryPublic
contains a methodsignTheCard
that generates a signature for theCard
passed into it:This method creates aprivate void signTheCard(Card c, int gameNumber) throws NoSuchAlgorithmException, InvalidKeyException, SignatureException { Signature dsa = Signature.getInstance("SHA/DSA"); byte[] values = new byte[Card.SIZE*Card.SIZE+1]; dsa.initSign(priv); for (int i = 0; i < Card.SIZE; i++) for (int j = 0; j < Card.SIZE; j ++) values[Card.SIZE*i + j] = (byte)c.boardValues[i][j].number; values[values.length-1] = (byte)gameNumber; dsa.update(values); c.setSignature(dsa.sign()); }Signature
object and, using the private key from theKeyPair
, initializes theSignature
object for signing. Next, the method fills a byte array with the values from the card and the current game number. The method uses this byte array toupdate
theSignature
object. Then, the method gets the signature from theSignature
object and sets the signature on theCard
to it.Note that each value on the
Card
and the current game number are converted to bytes. Each value on aCard
is in the range 1 to 75 and easily fits into a byte. However, if the game number exceeds 255 (the Game does not protect against this), then the conversion from anint
to abyte
will truncate the [PENDING: check this] high byte of the integer representing the game number. This could allow a sneaky Player to use a card signed for one game in another game.The
Signature
class supports a version of theupdate
method that lets you update the signature a byte at a time. However, this is not efficient (too many method calls), so thesignTheCard
method creates and fills a byte array and updates the signature with single method call instead. Generally speaking this is a better way of updating signatures.The
signTheCard
method gets called whenever a Player registers for a game. The Player remotely calls themayIPlay
method inRegistrarImpl
. If registration is allowed,mayIPlay
generates cards for the player and callssignTheCard
once for eachCard
to sign it. TheCard
s are passed back to the Player along with their signatures.When a player detects a BINGO and clicks the "I Won" button, the Player notifies the Game of the win by remotely calling the
BINGO
method and passing in the winning card. TheCard
contains its signature which must be verified by theNotaryPublic's
verifyTheSignature
method to win.Verifying the Signature
When a Player has a winning card, it calls the remote methodBINGO
inRegistrarImpl
. This method callsRingMaster.verify
which subsequently calls theverifyTheSignature
method in theNotaryPublic
to make sure that theCard
was created and signed by this Game for the current game:Theboolean verifyTheSignature(Card c, int gameNumber) { try { Signature dsa = Signature.getInstance("SHA/DSA"); byte[] values = new byte[Card.SIZE*Card.SIZE+1]; dsa.initVerify(pub); for (int i = 0; i < Card.SIZE; i ++) for (int j = 0; j < Card.SIZE; j ++) values[Card.SIZE*i + j] = (byte)c.boardValues[i][j].number; values[values.length-1] = (byte)gameNumber; dsa.update(values); return dsa.verify(c.getSignature()); } catch (Exception e) { return false; } }verifyTheSignature
method is very similar tosignTheCard
but worth a few notes. This method also creates aSignature
but instead of using the private key from theKeyPair
, it uses the public key, and initializes theSignature
for verification with the public key.As with
signTheCard
, this method creates and fills a byte array with the values from theCard
and the current game number, then updates theSignature
object with the byte array. Finally, the method verifies the signature on the card by passing theCard
's signature to theSignature
object'sverify
method.If the values on the
Card
are different than when the card was signed then the signature won't verify. Also, if the game number is different than when the card was signed the signature won't verify. This protects against Players trying to cheat by generating cards based on the announced balls, or using a card that was signed for a different game.
Putting It All Together |