//* Licensed Materials - Property of *
//* IBM *
//* Alexandra Instituttet A/S *
//* *
//* eu.abc4trust.pabce.1.34 *
//* *
//* (C) Copyright IBM Corp. 2014. All Rights Reserved. *
//* (C) Copyright Alexandra Instituttet A/S, Denmark. 2014. All *
//* Rights Reserved. *
//* US Government Users Restricted Rights - Use, duplication or *
//* disclosure restricted by GSA ADP Schedule Contract with IBM Corp. *
//* *
//* This file is 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 eu.abc4trust.smartcard;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.math.BigInteger;
import java.net.URI;
import java.nio.ByteBuffer;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Random;
import com.ibm.zurich.idmx.interfaces.util.BigIntFactory;
import com.ibm.zurich.idmx.interfaces.util.group.Group;
import com.ibm.zurich.idmx.util.bigInt.BigIntFactoryImpl;
import com.ibm.zurich.idmx.util.group.GroupFactoryImpl;
/**
* Lots of useful functions.
* @author enr
*
*/
public class Utils {
private static final String ENCODING = "UTF-8";
private static final String HASH_FUNCTION = "SHA-256";
private static final int HASH_LENGTH_BITS = 256;
// Token to distinguish different types of signatures
public static final int NEW_ISSUER_SIMPLE = 0;
public static final int NEW_ISSUER_WITH_ATTENDANCE = 1;
public static final int INC_COURSE_TOKEN = 2;
/**
* Takes a password which has to be representable by 8 bytes, each which is a char.
* @param password
* @return
*/
public static byte[] passwordToByteArr(String password){
char[] chars = password.toCharArray();
if(chars.length != 8){
return null;
}
byte[] res = new byte[8];
for(int i = 0; i < 8; i++){
res[i] = (byte) chars[i];
}
return res;
}
public static void addToStream(ByteArrayOutputStream baos, SmartcardParameters params){
// Order for Idemix is N, R0, S
// Order for UProve is P, Q, G, F
// We settle for order: modulus, (order), base1, (base2), (cofactor)
addToStream(baos, params.getModulus());
if(params.getOrderOrNull() != null) {
addToStream(baos, params.getOrderOrNull());
}
addToStream(baos, params.getBaseForDeviceSecret());
if(params.getBaseForCredentialSecretOrNull() != null) {
addToStream(baos, params.getBaseForCredentialSecretOrNull());
}
if(params.getCofactorOrNull() != null) {
addToStream(baos, params.getCofactorOrNull());
}
}
/**
* Serialize the integer n into the stream.
* This function will first write the length of the representation of n in bytes
* (as a 4-byte big-endian integer) followed by the 2s-complement big-endian representation
* of n.
* @param baos
* @param n
*/
public static void addToStream(ByteArrayOutputStream baos, BigInteger n) {
try {
byte[] bytes = n.toByteArray();
addRawIntToStream(baos, bytes.length);
baos.write(bytes);
} catch (IOException e) {
throw new RuntimeException("Problem with writing bigInteger", e);
}
}
/**
* Serialize the string s into the stream.
* This function will first write the length of the UTF-8 encoded string in bytes
* (as a 4-byte big-endian integer) followed by the UTF-8 encoded string.
* @param baos
* @param s
*/
public static void addToStream(ByteArrayOutputStream baos, String s) {
try {
byte[] bytes = s.getBytes(ENCODING);
addRawIntToStream(baos, bytes.length);
baos.write(bytes);
} catch (IOException e) {
throw new RuntimeException("Problem with writing string", e);
}
}
public static void addToStream(ByteArrayOutputStream baos, URI s) {
addToStream(baos, s.toString());
}
/**
* Serialize the byte array b into the stream.
* This function will first write the length of b in bytes
* (as a 4-byte big-endian integer) followed by b.
* @param baos
* @param s
*/
public static void addToStream(ByteArrayOutputStream baos, byte[] b) {
try {
addRawIntToStream(baos, b.length);
baos.write(b);
} catch (IOException e) {
throw new RuntimeException("Problem with writing bytes[]", e);
}
}
/**
* Serialize the integer n into the stream.
* This function will first write 4 (as a 4-byte big-endian integer) followed by the
* 2s-complement big-endian representation of n.
* This should be compatible with the corresponding function that writes BigIntegers.
* @param baos
* @param n
*/
public static void addToStream(ByteArrayOutputStream baos, int n) {
try {
byte[] bytes = ByteBuffer.allocate(4).putInt(n).array();
addRawIntToStream(baos, bytes.length);
baos.write(bytes);
} catch (IOException e) {
throw new RuntimeException("Problem with writing integer", e);
}
}
// At runtime Java makes no distinction between Map<URI, BigInteger> and
// Map<URI, SmartcardBlob>.
public static void addToStream(ByteArrayOutputStream baos, Map<URI, ? extends Object> objs) {
addToStream(baos, objs.size());
for(Entry<URI, ? extends Object> entry: objs.entrySet()) {
addToStream(baos, entry.getKey());
if (entry.getValue() instanceof BigInteger) {
addToStream(baos, (BigInteger) entry.getValue());
} else if (entry.getValue() instanceof SmartcardBlob) {
addToStream(baos, (SmartcardBlob) entry.getValue());
} else {
throw new RuntimeException("Trying to add unsupported object to stream.");
}
}
}
public static void addToStream(ByteArrayOutputStream baos, SmartcardBlob blob) {
addToStream(baos, blob.blob);
}
public static void addToStream(ByteArrayOutputStream baos, RSAVerificationKey vk) {
addToStream(baos, vk.n);
addToStream(baos, vk.sizeModulusBytes);
}
private static void addRawIntToStream(ByteArrayOutputStream baos, int k) throws IOException {
baos.write(ByteBuffer.allocate(4).putInt(k).array());
}
/**
* Hashes the first argument to an integer of size bytes*8 bits. The SHA-256 hash
* function is used to generate the bytes of the returned integer as follows:
* resbytes = SHA256(0x00 || toHash) || SHA256( 0x01 || toHash) || SHA256( 0x02 || toHash) || ...
* resbytes is then trimmed to bytes
* resbytes is then parsed as an integer represented in big-endian byte order.
*
* @param toSign
* @return
*/
public static BigInteger hashToBigIntegerWithSize(byte[] toHash, int bytes) {
return hashToBigIntegerWithSize_new(toHash, bytes);
//return hashToBigIntegerWithSize_original(toHash, bytes);
}
private static BigInteger hashToBigIntegerWithSize_new(byte[] toHash, int bytes){
try{
MessageDigest md = MessageDigest.getInstance(HASH_FUNCTION);
byte[] digest = md.digest(toHash);
if(digest.length <= bytes){
return new BigInteger(1, digest);
}else{
//need to truncate to bytes
byte[] truncated = new byte[bytes];
System.arraycopy(digest, 0, truncated, 0, bytes);
return new BigInteger(1, truncated);
}
} catch(NoSuchAlgorithmException e) {
throw new RuntimeException("Unknown hash algorithm", e);
}
}
private static BigInteger hashToBigIntegerWithSize_original(byte[] toHash, int bytes){
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
int numHashes = (bytes * 8 + HASH_LENGTH_BITS - 1) / HASH_LENGTH_BITS;
if (numHashes > 127) {
throw new RuntimeException("Size too large");
}
for (int i = 0; i < numHashes; ++i) {
MessageDigest md = MessageDigest.getInstance(HASH_FUNCTION);
md.update((byte) i);
md.update(toHash);
byte[] digest = md.digest();
baos.write(digest);
}
byte[] resbytes = new byte[bytes];
System.arraycopy(baos.toByteArray(), 0, resbytes, 0, bytes);
BigInteger ret = new BigInteger(1, resbytes);
if (ret.compareTo(BigInteger.ZERO) < 0) {
throw new RuntimeException("expected a positive number");
}
return ret;
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("Unknown hash algorithm", e);
} catch (IOException e) {
throw new RuntimeException("IOException", e);
}
}
public static BigInteger hashToBigIntegerWithSize(ByteArrayOutputStream baos, int bytes) {
return hashToBigIntegerWithSize(baos.toByteArray(), bytes);
}
public static BigInteger baseForScopeExclusivePseudonym(URI scope, BigInteger modulus,
BigInteger order) {
ByteArrayOutputStream encodedScope = new ByteArrayOutputStream();
//addToStream(encodedScope, scope);
try {
encodedScope.write(scope.toString().getBytes(ENCODING));
} catch (Exception e) {
throw new RuntimeException(e);
}
int byteLengthOfBase = (modulus.bitLength() + 7) / 8;
byteLengthOfBase--;
// Compute the cofactor of the order of the subgroup:
BigInteger cofactor = modulus.subtract(BigInteger.ONE).divide(order);
// Since modulus is a safe prime, the square of the hash will have order exactly (modulus-1)/2
return hashToBigIntegerWithSize(encodedScope, byteLengthOfBase).modPow(cofactor, modulus);
}
public static RSASignature generateSignatureToAddIssuer(RSAKeyPair sk, URI parametersUri,
SmartcardParameters credBases, byte[] nonce, Random rand) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
Utils.addToStream(baos, Utils.NEW_ISSUER_SIMPLE);
Utils.addToStream(baos, parametersUri);
Utils.addToStream(baos, credBases);
return RSASignatureSystem.generateSignature(sk, baos.toByteArray(), nonce, rand);
}
public static RSASignature generateSignatureToAddIssuer(RSAKeyPair sk, URI parametersUri,
SmartcardParameters credBases, RSAVerificationKey courseKey, int minAttendance, byte[] nonce, Random rand) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
Utils.addToStream(baos, Utils.NEW_ISSUER_WITH_ATTENDANCE);
Utils.addToStream(baos, parametersUri);
Utils.addToStream(baos, credBases);
Utils.addToStream(baos, courseKey);
Utils.addToStream(baos, minAttendance);
return RSASignatureSystem.generateSignature(sk, baos.toByteArray(), nonce, rand);
}
public static RSASignature generateSignatureForAttendance(RSAKeyPair csk, URI credUri,
int lectureId, byte[] nonce, Random rand) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
Utils.addToStream(baos, Utils.INC_COURSE_TOKEN);
Utils.addToStream(baos, credUri);
Utils.addToStream(baos, lectureId);
return RSASignatureSystem.generateSignature(csk, baos.toByteArray(), nonce, rand);
}
public static ZkNonceCommitmentOpening newNonceCommitment(int nonceSizeBytes, int openSizeBytes,
Random r) {
ZkNonceCommitmentOpening nco = new ZkNonceCommitmentOpening();
nco.nonce = new byte[nonceSizeBytes];
nco.opening = new byte[openSizeBytes];
r.nextBytes(nco.nonce);
r.nextBytes(nco.opening);
return nco;
}
public static byte[] computeCommitment(ZkNonceCommitmentOpening nco) {
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
Utils.addToStream(baos, nco.nonce);
Utils.addToStream(baos, nco.opening);
MessageDigest md = MessageDigest.getInstance(HASH_FUNCTION);
md.update(baos.toByteArray());
byte[] digest = md.digest();
return digest;
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
}
public static byte[] hashConcat(List<byte[]> list) {
// int len = list.get(0).length;
// byte[] ret = new byte[len];
//
// for(byte[] a: list) {
// // Assert all byte arrays are the same length
// if (a.length != len) {
// System.err.println("Invalid length of nonce expected: " + len + " actual: " + a.length);
// return null;
// }
//
// for (int i=0;i<len;++i) {
// ret[i] = (byte) (ret[i] ^ a[i]);
// }
// }
//
// return ret;
try {
MessageDigest md = MessageDigest.getInstance(HASH_FUNCTION);
for(byte[] a : list){
md.update(a);
}
return md.digest();
} catch (NoSuchAlgorithmException e) {
return null;
}
}
@Deprecated
public static byte[] checkCommitmentsAndGetNonce(List<byte[]> commitments,
List<ZkNonceCommitmentOpening> openings,
byte[] myCommitment) {
boolean hasMine = false;
List<byte[]> nonces = new ArrayList<byte[]>();
if(commitments.size() != openings.size()) {
System.err.println("Commitments and opening sizes different");
return null;
}
Iterator<ZkNonceCommitmentOpening> openingIter = openings.iterator();
for(byte[] expected: commitments) {
ZkNonceCommitmentOpening op = openingIter.next();
byte[] actual = computeCommitment(op);
if (! Arrays.equals(expected, actual)) {
System.err.println("Invalid opening");
return null;
}
if (!hasMine && Arrays.equals(expected, myCommitment)) {
hasMine = true;
}
nonces.add(op.nonce);
}
if (!hasMine) {
System.err.println("Commitment list does not contain commitment of this smartcard");
return null;
}
return hashConcat(nonces);
}
}