Home | History | Annotate | Download | only in pairing
      1 /*
      2  * Copyright (C) 2009 Google Inc.  All rights reserved.
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.google.polo.pairing;
     18 
     19 import com.google.polo.exception.PoloException;
     20 
     21 import java.security.MessageDigest;
     22 import java.security.NoSuchAlgorithmException;
     23 import java.security.PublicKey;
     24 import java.security.cert.Certificate;
     25 import java.security.interfaces.RSAPublicKey;
     26 import java.util.Arrays;
     27 
     28 /**
     29  * Class to represent the out-of-band secret transmitted during pairing.
     30  */
     31 public class PoloChallengeResponse {
     32 
     33   /**
     34    * Hash algorithm to generate secret.
     35    */
     36   private static final String HASH_ALGORITHM = "SHA-256";
     37 
     38   /**
     39    * Optional handler for debug log messages.
     40    */
     41   private DebugLogger mLogger;
     42 
     43   /**
     44    * Certificate of the local peer in the protocol.
     45    */
     46   private Certificate mClientCertificate;
     47 
     48   /**
     49    * Certificate of the remote peer in the protocol.
     50    */
     51   private Certificate mServerCertificate;
     52 
     53   /**
     54    * Creates a new callenge-response generator object.
     55    *
     56    * @param clientCert  the certificate of the client node
     57    * @param serverCert  the certificate of the server node
     58    * @param logger      a listener for debugging messages; may be null
     59    */
     60   public PoloChallengeResponse(Certificate clientCert, Certificate serverCert,
     61       DebugLogger logger) {
     62     mClientCertificate = clientCert;
     63     mServerCertificate = serverCert;
     64     mLogger = logger;
     65   }
     66 
     67   /**
     68    * Returns the alpha value to be used in pairing.
     69    * <p>
     70    * From the Polo design document, `alpha` is the value h(K_a | K_b | R_a):
     71    * for an RSA public key, that is:
     72    * <ul>
     73    * <li>the client key's modulus,</li>
     74    * <li>the client key's public exponent,</li>
     75    * <li>the server key's modulus,</li>
     76    * <li>the server key's public exponent,</li>
     77    * <li>the random nonce.</li>
     78    *
     79    * @param   nonce          the nonce to use for computation
     80    * @return                 the alpha value, as a byte array
     81    * @throws  PoloException  if the secret could not be computed
     82    */
     83   public byte[] getAlpha(byte[] nonce) throws PoloException {
     84     PublicKey clientPubKey = mClientCertificate.getPublicKey();
     85     PublicKey serverPubKey = mServerCertificate.getPublicKey();
     86 
     87     logDebug("getAlpha, nonce=" + PoloUtil.bytesToHexString(nonce));
     88 
     89     if (!(clientPubKey instanceof RSAPublicKey) ||
     90         !(serverPubKey instanceof RSAPublicKey)) {
     91       throw new PoloException("Polo only supports RSA public keys");
     92     }
     93 
     94     RSAPublicKey clientPubRsa = (RSAPublicKey) clientPubKey;
     95     RSAPublicKey serverPubRsa = (RSAPublicKey) serverPubKey;
     96 
     97     MessageDigest digest;
     98     try {
     99       digest = MessageDigest.getInstance(HASH_ALGORITHM);
    100     } catch (NoSuchAlgorithmException e) {
    101       throw new PoloException("Could not get digest algorithm", e);
    102     }
    103 
    104     byte[] digestBytes;
    105     byte[] clientModulus = clientPubRsa.getModulus().abs().toByteArray();
    106     byte[] clientExponent =
    107         clientPubRsa.getPublicExponent().abs().toByteArray();
    108     byte[] serverModulus = serverPubRsa.getModulus().abs().toByteArray();
    109     byte[] serverExponent =
    110         serverPubRsa.getPublicExponent().abs().toByteArray();
    111 
    112     // Per "Polo Implementation Overview", section 6.1, leading null bytes must
    113     // be removed prior to hashing the key material.
    114     clientModulus = removeLeadingNullBytes(clientModulus);
    115     clientExponent = removeLeadingNullBytes(clientExponent);
    116     serverModulus = removeLeadingNullBytes(serverModulus);
    117     serverExponent = removeLeadingNullBytes(serverExponent);
    118 
    119     logVerbose("Hash inputs, in order: ");
    120     logVerbose("   client modulus: " + PoloUtil.bytesToHexString(clientModulus));
    121     logVerbose("  client exponent: " + PoloUtil.bytesToHexString(clientExponent));
    122     logVerbose("   server modulus: " + PoloUtil.bytesToHexString(serverModulus));
    123     logVerbose("  server exponent: " + PoloUtil.bytesToHexString(serverExponent));
    124     logVerbose("            nonce: " + PoloUtil.bytesToHexString(nonce));
    125 
    126     // Per "Polo Implementation Overview", section 6.1, client key material is
    127     // hashed first, followed by the server key material, followed by the
    128     // nonce.
    129     digest.update(clientModulus);
    130     digest.update(clientExponent);
    131     digest.update(serverModulus);
    132     digest.update(serverExponent);
    133     digest.update(nonce);
    134 
    135     digestBytes = digest.digest();
    136     logDebug("Generated hash: " + PoloUtil.bytesToHexString(digestBytes));
    137     return digestBytes;
    138   }
    139 
    140   /**
    141    * Returns the gamma value to be used in pairing, i.e. the concatenation
    142    * of the alpha value with the nonce.
    143    * <p>
    144    * The returned value with be twice the byte length of the nonce.
    145    *
    146    * @throws PoloException  if the secret could not be computed
    147    */
    148   public byte[] getGamma(byte[] nonce) throws PoloException {
    149       byte[] alphaBytes = getAlpha(nonce);
    150       assert(alphaBytes.length >= nonce.length);
    151 
    152       byte[] result = new byte[nonce.length * 2];
    153 
    154       System.arraycopy(alphaBytes, 0, result, 0, nonce.length);
    155       System.arraycopy(nonce, 0, result, nonce.length, nonce.length);
    156 
    157       return result;
    158   }
    159 
    160   /**
    161    * Extracts and returns the nonce portion of a given gamma value.
    162    */
    163   public byte[] extractNonce(byte[] gamma) {
    164     if ((gamma.length < 2) || (gamma.length % 2 != 0)) {
    165       throw new IllegalArgumentException();
    166     }
    167     int nonceLength = gamma.length / 2;
    168     byte[] nonce = new byte[nonceLength];
    169     System.arraycopy(gamma, nonceLength, nonce, 0, nonceLength);
    170     return nonce;
    171   }
    172 
    173   /**
    174    * Returns {@code true} if the gamma value matches the locally computed value.
    175    * <p>
    176    * The computed value is determined by extracting the nonce portion of the
    177    * gamma value.
    178    *
    179    * @throws PoloException  if the value could not be computed
    180    */
    181   public boolean checkGamma(byte[] gamma) throws PoloException {
    182 
    183     byte[] nonce;
    184     try {
    185       nonce = extractNonce(gamma);
    186     } catch (IllegalArgumentException e) {
    187       logDebug("Illegal nonce value.");
    188       return false;
    189     }
    190     logDebug("Nonce is: " + PoloUtil.bytesToHexString(nonce));
    191     logDebug("User gamma is: " + PoloUtil.bytesToHexString(gamma));
    192     logDebug("Generated gamma is: " + PoloUtil.bytesToHexString(getGamma(nonce)));
    193     return Arrays.equals(gamma, getGamma(nonce));
    194   }
    195 
    196   /**
    197    * Strips leading null bytes from a byte array, returning a new copy.
    198    * <p>
    199    * As a special case, if the input array consists entirely of null bytes,
    200    * then an array with a single null element will be returned.
    201    */
    202   private byte[] removeLeadingNullBytes(byte[] inArray) {
    203     int offset = 0;
    204     while (offset < inArray.length & inArray[offset] == 0) {
    205       offset += 1;
    206     }
    207     byte[] result = new byte[inArray.length - offset];
    208     for (int i=offset; i < inArray.length; i++) {
    209       result[i - offset] = inArray[i];
    210     }
    211     return result;
    212   }
    213 
    214   private void logDebug(String message) {
    215     if (mLogger != null) {
    216       mLogger.debug(message);
    217     }
    218   }
    219 
    220   private void logVerbose(String message) {
    221     if (mLogger != null) {
    222       mLogger.verbose(message);
    223     }
    224   }
    225 
    226   public static interface DebugLogger {
    227     /**
    228      * Logs debugging information from challenge-response generation.
    229      */
    230     public void debug(String message);
    231 
    232     /**
    233      * Logs verbose debugging information from challenge-response generation.
    234      */
    235     public void verbose(String message);
    236 
    237   }
    238 
    239 }
    240