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
17package com.google.polo.pairing;
18
19import com.google.polo.exception.PoloException;
20
21import java.security.MessageDigest;
22import java.security.NoSuchAlgorithmException;
23import java.security.PublicKey;
24import java.security.cert.Certificate;
25import java.security.interfaces.RSAPublicKey;
26import java.util.Arrays;
27
28/**
29 * Class to represent the out-of-band secret transmitted during pairing.
30 */
31public 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