/* * Copyright (C) 2009 Google Inc. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.polo.pairing; import com.google.polo.exception.PoloException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.PublicKey; import java.security.cert.Certificate; import java.security.interfaces.RSAPublicKey; import java.util.Arrays; /** * Class to represent the out-of-band secret transmitted during pairing. */ public class PoloChallengeResponse { /** * Hash algorithm to generate secret. */ private static final String HASH_ALGORITHM = "SHA-256"; /** * Optional handler for debug log messages. */ private DebugLogger mLogger; /** * Certificate of the local peer in the protocol. */ private Certificate mClientCertificate; /** * Certificate of the remote peer in the protocol. */ private Certificate mServerCertificate; /** * Creates a new callenge-response generator object. * * @param clientCert the certificate of the client node * @param serverCert the certificate of the server node * @param logger a listener for debugging messages; may be null */ public PoloChallengeResponse(Certificate clientCert, Certificate serverCert, DebugLogger logger) { mClientCertificate = clientCert; mServerCertificate = serverCert; mLogger = logger; } /** * Returns the alpha value to be used in pairing. * <p> * From the Polo design document, `alpha` is the value h(K_a | K_b | R_a): * for an RSA public key, that is: * <ul> * <li>the client key's modulus,</li> * <li>the client key's public exponent,</li> * <li>the server key's modulus,</li> * <li>the server key's public exponent,</li> * <li>the random nonce.</li> * * @param nonce the nonce to use for computation * @return the alpha value, as a byte array * @throws PoloException if the secret could not be computed */ public byte[] getAlpha(byte[] nonce) throws PoloException { PublicKey clientPubKey = mClientCertificate.getPublicKey(); PublicKey serverPubKey = mServerCertificate.getPublicKey(); logDebug("getAlpha, nonce=" + PoloUtil.bytesToHexString(nonce)); if (!(clientPubKey instanceof RSAPublicKey) || !(serverPubKey instanceof RSAPublicKey)) { throw new PoloException("Polo only supports RSA public keys"); } RSAPublicKey clientPubRsa = (RSAPublicKey) clientPubKey; RSAPublicKey serverPubRsa = (RSAPublicKey) serverPubKey; MessageDigest digest; try { digest = MessageDigest.getInstance(HASH_ALGORITHM); } catch (NoSuchAlgorithmException e) { throw new PoloException("Could not get digest algorithm", e); } byte[] digestBytes; byte[] clientModulus = clientPubRsa.getModulus().abs().toByteArray(); byte[] clientExponent = clientPubRsa.getPublicExponent().abs().toByteArray(); byte[] serverModulus = serverPubRsa.getModulus().abs().toByteArray(); byte[] serverExponent = serverPubRsa.getPublicExponent().abs().toByteArray(); // Per "Polo Implementation Overview", section 6.1, leading null bytes must // be removed prior to hashing the key material. clientModulus = removeLeadingNullBytes(clientModulus); clientExponent = removeLeadingNullBytes(clientExponent); serverModulus = removeLeadingNullBytes(serverModulus); serverExponent = removeLeadingNullBytes(serverExponent); logVerbose("Hash inputs, in order: "); logVerbose(" client modulus: " + PoloUtil.bytesToHexString(clientModulus)); logVerbose(" client exponent: " + PoloUtil.bytesToHexString(clientExponent)); logVerbose(" server modulus: " + PoloUtil.bytesToHexString(serverModulus)); logVerbose(" server exponent: " + PoloUtil.bytesToHexString(serverExponent)); logVerbose(" nonce: " + PoloUtil.bytesToHexString(nonce)); // Per "Polo Implementation Overview", section 6.1, client key material is // hashed first, followed by the server key material, followed by the // nonce. digest.update(clientModulus); digest.update(clientExponent); digest.update(serverModulus); digest.update(serverExponent); digest.update(nonce); digestBytes = digest.digest(); logDebug("Generated hash: " + PoloUtil.bytesToHexString(digestBytes)); return digestBytes; } /** * Returns the gamma value to be used in pairing, i.e. the concatenation * of the alpha value with the nonce. * <p> * The returned value with be twice the byte length of the nonce. * * @throws PoloException if the secret could not be computed */ public byte[] getGamma(byte[] nonce) throws PoloException { byte[] alphaBytes = getAlpha(nonce); assert(alphaBytes.length >= nonce.length); byte[] result = new byte[nonce.length * 2]; System.arraycopy(alphaBytes, 0, result, 0, nonce.length); System.arraycopy(nonce, 0, result, nonce.length, nonce.length); return result; } /** * Extracts and returns the nonce portion of a given gamma value. */ public byte[] extractNonce(byte[] gamma) { if ((gamma.length < 2) || (gamma.length % 2 != 0)) { throw new IllegalArgumentException(); } int nonceLength = gamma.length / 2; byte[] nonce = new byte[nonceLength]; System.arraycopy(gamma, nonceLength, nonce, 0, nonceLength); return nonce; } /** * Returns {@code true} if the gamma value matches the locally computed value. * <p> * The computed value is determined by extracting the nonce portion of the * gamma value. * * @throws PoloException if the value could not be computed */ public boolean checkGamma(byte[] gamma) throws PoloException { byte[] nonce; try { nonce = extractNonce(gamma); } catch (IllegalArgumentException e) { logDebug("Illegal nonce value."); return false; } logDebug("Nonce is: " + PoloUtil.bytesToHexString(nonce)); logDebug("User gamma is: " + PoloUtil.bytesToHexString(gamma)); logDebug("Generated gamma is: " + PoloUtil.bytesToHexString(getGamma(nonce))); return Arrays.equals(gamma, getGamma(nonce)); } /** * Strips leading null bytes from a byte array, returning a new copy. * <p> * As a special case, if the input array consists entirely of null bytes, * then an array with a single null element will be returned. */ private byte[] removeLeadingNullBytes(byte[] inArray) { int offset = 0; while (offset < inArray.length & inArray[offset] == 0) { offset += 1; } byte[] result = new byte[inArray.length - offset]; for (int i=offset; i < inArray.length; i++) { result[i - offset] = inArray[i]; } return result; } private void logDebug(String message) { if (mLogger != null) { mLogger.debug(message); } } private void logVerbose(String message) { if (mLogger != null) { mLogger.verbose(message); } } public static interface DebugLogger { /** * Logs debugging information from challenge-response generation. */ public void debug(String message); /** * Logs verbose debugging information from challenge-response generation. */ public void verbose(String message); } }