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    */
PoloChallengeResponse(Certificate clientCert, Certificate serverCert, DebugLogger logger)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    */
getAlpha(byte[] nonce)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    */
getGamma(byte[] nonce)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    */
extractNonce(byte[] gamma)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    */
checkGamma(byte[] gamma)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    */
removeLeadingNullBytes(byte[] inArray)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 
logDebug(String message)214   private void logDebug(String message) {
215     if (mLogger != null) {
216       mLogger.debug(message);
217     }
218   }
219 
logVerbose(String message)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      */
debug(String message)230     public void debug(String message);
231 
232     /**
233      * Logs verbose debugging information from challenge-response generation.
234      */
verbose(String message)235     public void verbose(String message);
236 
237   }
238 
239 }
240