blob: 81095fd06a2148e8012d13c638b07f661a978586 [file] [log] [blame]
/*
* 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);
}
}