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