//* Licensed Materials - Property of * //* IBM * //* Miracle A/S * //* Alexandra Instituttet A/S * //* * //* eu.abc4trust.pabce.1.34 * //* * //* (C) Copyright IBM Corp. 2014. All Rights Reserved. * //* (C) Copyright Miracle A/S, Denmark. 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.io.ObjectOutput; import java.io.ObjectOutputStream; import java.io.Serializable; import java.io.UnsupportedEncodingException; import java.math.BigInteger; import java.net.URI; import java.nio.ByteBuffer; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Random; import java.util.Set; import java.util.logging.Logger; import javax.crypto.Cipher; import eu.abc4trust.cryptoEngine.user.CredentialSerializer; import eu.abc4trust.cryptoEngine.user.PseudonymSerializer; import eu.abc4trust.xml.Credential; import eu.abc4trust.xml.PseudonymWithMetadata; public class SoftwareSmartcard implements Smartcard, Serializable { static Logger log = Logger.getLogger(SoftwareSmartcard.class.getName()); private static final long serialVersionUID = 1L; private static final int MAX_CREDENTIALS = 8; private static final int MAX_ISSUERS = 6; private static final int MAX_BLOBS = 50; private static final int MAX_URI_LEN_BYTES = 64; private static final int MAX_BLOB_LEN_BYTES = 512; private static final int MAX_PIN_TRIALS = 3; private static final int MAX_PUK_TRIALS = 10; private static final String URI_ENCODING = "UTF-8"; @SuppressWarnings("unused") private static final String MAC_ALGORITHM = "HmacSHA256"; // Token to distinguish different types of signatures @SuppressWarnings("unused") private static final int BACKUP_COURSE_TOKEN = 3; // State of smartcard static transient private Random rand = new SecureRandom(); private boolean factoryInit; private int pin; private int puk; private int pinTrialsLeft; private int pukTrialsLeft; private final Map<URI, SmartcardBlob> blobstore; private SystemParameters params; private BigInteger deviceSecret; private byte[] currentNonce; private RSAVerificationKey schoolKey; private final Map<URI, TrustedIssuerParameters> issuerParameters; private final Map<URI, Integer> issuerIDs; private final Map<Integer, URI> issuerUris; private final Map<URI, CredentialOnSmartcard> credentials; private final Map<Integer, RSAVerificationKey> courseKeys; private short deviceID; private final byte[] macKey = new byte[16]; //hardcoded 16 zeros private transient ZkProofState zkProofState; public SoftwareSmartcard() { this(new SecureRandom()); } public SoftwareSmartcard(Random random) { this.factoryInit = false; rand = random; this.blobstore = new HashMap<URI, SmartcardBlob>(); this.issuerParameters = new HashMap<URI, TrustedIssuerParameters>(); this.issuerIDs = new HashMap<URI, Integer>(); this.issuerUris = new HashMap<Integer, URI>(); this.credentials = new HashMap<URI, CredentialOnSmartcard>(); this.courseKeys = new HashMap<Integer, RSAVerificationKey>(); } /** * Return the ID from the mapping. If no mapping exists, we create a new entry * and return the ID from that mapping. * @param parametersUri * @return */ private int getIssuerIDFromUri(URI parametersUri) { if(!this.issuerIDs.containsKey(parametersUri)){ int id = 1; while(true){ if(id >= 10){ throw new RuntimeException("More than 10 issuers not allowed!"); } if(this.issuerIDs.values().contains(id)){ id++; }else{ this.issuerIDs.put(parametersUri, id); this.issuerUris.put(id, parametersUri); return id; } } }else{ return this.issuerIDs.get(parametersUri); } } private URI getIssuerUriFromID(int ID){ return this.issuerUris.get(ID); } private SmartcardStatusCode validatePassword(String password){ char[] chars = password.toCharArray(); if(chars.length != 8){ System.err.println("Password does not convert to 8 bytes. Please enter a new password"); return SmartcardStatusCode.BAD_REQUEST; } return SmartcardStatusCode.OK; } private SmartcardStatusCode authenticateWithPin(int pin) { if (!this.factoryInit) { return SmartcardStatusCode.NOT_INITIALIZED; } if (this.pinTrialsLeft <= 0) { return SmartcardStatusCode.FORBIDDEN; } if (this.pin != pin) { this.pinTrialsLeft--; if (this.pinTrialsLeft <= 0) { return SmartcardStatusCode.FORBIDDEN; } else { return SmartcardStatusCode.UNAUTHORIZED; } } this.pinTrialsLeft = MAX_PIN_TRIALS; return SmartcardStatusCode.OK; } private SmartcardStatusCode authenticateWithPuk(int puk) { if (!this.factoryInit) { return SmartcardStatusCode.NOT_INITIALIZED; } if (this.pukTrialsLeft <= 0) { return SmartcardStatusCode.FORBIDDEN; } if (this.puk != puk) { this.pukTrialsLeft--; if (this.pukTrialsLeft <= 0) { return SmartcardStatusCode.FORBIDDEN; } else { return SmartcardStatusCode.UNAUTHORIZED; } } this.pukTrialsLeft = MAX_PUK_TRIALS; return SmartcardStatusCode.OK; } private SmartcardStatusCode authenticateWithSignature(RSAKeyPair secretKey, RSAVerificationKey publicKey, ByteArrayOutputStream toSign) { if (!this.factoryInit) { return SmartcardStatusCode.NOT_INITIALIZED; } if (this.currentNonce == null) { return SmartcardStatusCode.STALE_NONCE; } if ((secretKey == null) || (publicKey == null)) { throw new RuntimeException("key was null"); } RSASignature sig = SmartcardCrypto.generateSignature(toSign.toByteArray(), this.currentNonce, secretKey, rand); if (RSASignatureSystem.checkSignature(sig, publicKey, toSign.toByteArray(), this.currentNonce)) { this.currentNonce = null; return SmartcardStatusCode.OK; } else { this.currentNonce = null; return SmartcardStatusCode.UNAUTHORIZED; } } @Override public SmartcardStatusCode changePin(int pin, int newPin) { SmartcardStatusCode pinstatus = this.authenticateWithPin(pin); if (pinstatus != SmartcardStatusCode.OK) { return pinstatus; } this.pin = newPin; return SmartcardStatusCode.OK; } @Override public SmartcardStatusCode deleteBlob(int pin, URI uri) { SmartcardStatusCode pinstatus = this.authenticateWithPin(pin); if (pinstatus != SmartcardStatusCode.OK) { return pinstatus; } if (!this.blobstore.containsKey(uri)) { return SmartcardStatusCode.NOT_FOUND; } this.blobstore.remove(uri); return SmartcardStatusCode.OK; } @Override public SmartcardStatusCode deleteCredential(int pin, URI credentialUri) { SmartcardStatusCode pinstatus = this.authenticateWithPin(pin); if (pinstatus != SmartcardStatusCode.OK) { return pinstatus; } log.info("======= Delete credential "+ credentialUri); if (! this.credentials.containsKey(credentialUri)) { return SmartcardStatusCode.NOT_FOUND; } this.removeCredentialUri(pin, credentialUri); this.credentials.remove(credentialUri); return SmartcardStatusCode.OK; } @Override public void removeCredentialUri(int pin, URI uri){ if(uri.toString().startsWith(HardwareSmartcard.CREDENTIAL_PREFIX)){ URI reloadURI = URI.create(uri.toString()+HardwareSmartcard.UPROVE_RELOAD_URI_POSTFIX); if(reloadURI.toString().contains(":") && !reloadURI.toString().contains("_")){ reloadURI = URI.create(reloadURI.toString().replaceAll(":", "_")); //change all ':' to '_' } this.deleteBlob(pin, reloadURI); log.info("deleted the reload blob of the credential: " + reloadURI); } int i = 1; while(true){ URI tmpUri = URI.create(uri.toString()+"_"+i++); if(this.deleteBlob(pin, tmpUri) != SmartcardStatusCode.OK){ if(i == 1){ //Actual error - we should be able to remove at least 1 blob throw new RuntimeException("Could not delete blob: " + tmpUri); } return; } } } @Override public SmartcardBlob getBlob(int pin, URI uri) { uri = uri == null ? uri : URI.create(uri.toString().replaceAll(":", "_")); SmartcardStatusCode pinstatus = this.authenticateWithPin(pin); if (pinstatus != SmartcardStatusCode.OK) { return null; } // Returns null if the uri is not in the store return this.blobstore.get(uri); } @Override public Map<URI, SmartcardBlob> getBlobs(int pin) { SmartcardStatusCode pinstatus = this.authenticateWithPin(pin); if (pinstatus != SmartcardStatusCode.OK) { return null; } return this.blobstore; } @Override public Set<URI> getBlobUris(int pin) { SmartcardStatusCode pinstatus = this.authenticateWithPin(pin); if (pinstatus != SmartcardStatusCode.OK) { return null; } return this.blobstore.keySet(); } @Override public Course getCourse(int pin, URI issuerUri) { SmartcardStatusCode pinstatus = this.authenticateWithPin(pin); if (pinstatus != SmartcardStatusCode.OK) { return null; } TrustedIssuerParameters tip = this.getIssuerParameters(pin, issuerUri); if(tip==null) { return null; } return tip.course; } @Override public byte[] getNewNonceForSignature() { if (!this.factoryInit) { return null; } this.currentNonce = new byte[this.params.signatureNonceLengthBytes]; rand.nextBytes(this.currentNonce); return this.currentNonce; } @Override public SmartcardStatusCode incrementCourseCounter(int pin, RSAKeyPair key, URI issuerUri, int lectureId) { if (!this.factoryInit) { return SmartcardStatusCode.NOT_INITIALIZED; } if (!this.uriLengthOk(issuerUri)) { return SmartcardStatusCode.REQUEST_URI_TOO_LONG; } RSAVerificationKey vkey = this.schoolKey; Course course = this.getCourse(this.pin, issuerUri); if (course != null) { vkey = this.courseKeys.get(course.getKeyID()); } // Note: we don't return immediately if course == NULL, else the card would leak // which courses the owner attends. this.getNewNonceForSignature(); ByteArrayOutputStream toSign = new ByteArrayOutputStream(); Utils.addToStream(toSign, Utils.INC_COURSE_TOKEN); Utils.addToStream(toSign, issuerUri); Utils.addToStream(toSign, lectureId); SmartcardStatusCode sigstatus = this.authenticateWithSignature(key, vkey, toSign); if (sigstatus != SmartcardStatusCode.OK) { return sigstatus; } if (course == null) { return SmartcardStatusCode.NOT_FOUND; } // Counter will not be incremented if course credential was not issued yet if (course.updateLectureId(lectureId)) { return SmartcardStatusCode.OK; } else { return SmartcardStatusCode.NOT_MODIFIED; } } @Override public Set<Course> listCourses(int pin) { SmartcardStatusCode pinstatus = this.authenticateWithPin(pin); if (pinstatus != SmartcardStatusCode.OK) { return null; } Set<Course> ret = new HashSet<Course>(); for(TrustedIssuerParameters tip: this.issuerParameters.values()) { if(tip.enforceAttendanceCheck) { ret.add(tip.course); } } return ret; } @Override public int pinTrialsLeft() { if (!this.factoryInit) { return 0; } return this.pinTrialsLeft; } @Override public int pukTrialsLeft() { if (!this.factoryInit) { return 0; } return this.pukTrialsLeft; } @Override public SmartcardStatusCode resetPinWithPuk(int puk, int newPin) { SmartcardStatusCode pukstatus = this.authenticateWithPuk(puk); if (pukstatus != SmartcardStatusCode.OK) { return pukstatus; } this.pin = newPin; this.pinTrialsLeft = MAX_PIN_TRIALS; return SmartcardStatusCode.OK; } @Override public boolean smartcardPresent() { return true; } @Override public SmartcardStatusCode storeBlob(int pin, URI uri, SmartcardBlob blob) { String[] forbiddenChars = new String[]{"\u0167", ":", "*", "?", "<", ">", " ", "|"}; if(uri.toString().contains(":") && !uri.toString().contains("_")){ uri = URI.create(uri.toString().replaceAll(":", "_")); //change all ':' to '_' }else{ for (String forbiddenChar : forbiddenChars) { if(uri.toString().contains(forbiddenChar)){ throw new RuntimeException("Cannot store a blob under a URI containing the following char: " +forbiddenChar); } } } SmartcardStatusCode pinstatus = this.authenticateWithPin(pin); if (pinstatus != SmartcardStatusCode.OK) { return pinstatus; } if (! this.uriLengthOk(uri)) { return SmartcardStatusCode.REQUEST_URI_TOO_LONG; } if (blob.getLength() > MAX_BLOB_LEN_BYTES) { return SmartcardStatusCode.REQUEST_ENTITY_TOO_LARGE; } int aftersize = this.blobstore.size(); if (!this.blobstore.containsKey(uri)) { aftersize++; } if (aftersize > MAX_BLOBS) { return SmartcardStatusCode.INSUFFICIENT_STORAGE; } this.blobstore.put(uri, blob); if (aftersize != this.blobstore.size()) { throw new RuntimeException("Assertion failed: aftersize != blobstore.size()"); } return SmartcardStatusCode.OK; } @Override public boolean wasInit() { return this.factoryInit; } @Override public int init(int newPin, SystemParameters sysParams, RSAKeyPair rootKey, short deviceID) { if (this.factoryInit) { return -1; } this.pin = newPin; this.pinTrialsLeft = MAX_PIN_TRIALS; //Do NOT use the random generator. Causes tests to crash! //this.puk = rand.nextInt(100000000); this.puk = 12345678; this.pukTrialsLeft = MAX_PUK_TRIALS; this.blobstore.clear(); this.params = sysParams; this.deviceSecret = new BigInteger(sysParams.deviceSecretSizeBytes * 8, rand); this.currentNonce = null; this.schoolKey = new RSAVerificationKey(); this.schoolKey.n = rootKey.getN(); this.issuerParameters.clear(); this.credentials.clear(); this.deviceID = deviceID; this.factoryInit = true; //Do NOT use the random generator. Causes tests to crash! //rand.nextBytes(macKeyForBackup); this.zkProofState = null; return this.puk; } private boolean uriLengthOk(URI uri) { try { return uri.toString().getBytes(URI_ENCODING).length <= MAX_URI_LEN_BYTES; } catch (UnsupportedEncodingException e) { throw new RuntimeException("Problem with URI encoding"); } } @Override public URI getDeviceURI(int pin) { SmartcardStatusCode pinstatus = this.authenticateWithPin(pin); if (pinstatus != SmartcardStatusCode.OK) { return null; } return URI.create(new String(this.getBlob(pin, Smartcard.device_name).blob)); } @Override public short getDeviceID(int pin){ SmartcardStatusCode pinstatus = this.authenticateWithPin(pin); if (pinstatus != SmartcardStatusCode.OK) { return -1; } return this.deviceID; } @Override public RSAVerificationKey readAuthenticationKey(int pin, int keyID) { SmartcardStatusCode pinstatus = this.authenticateWithPin(pin); if (pinstatus != SmartcardStatusCode.OK) { return null; } return this.courseKeys.get(keyID); } @Override public SystemParameters getSystemParameters(int pin) { SmartcardStatusCode pinstatus = this.authenticateWithPin(pin); if (pinstatus != SmartcardStatusCode.OK) { return null; } return this.params; } @Override public BigInteger computeScopeExclusivePseudonym(int pin, URI scope) { SmartcardStatusCode pinstatus = this.authenticateWithPin(pin); if (pinstatus != SmartcardStatusCode.OK) { return null; } // hash(scope) ^ deviceSecret (mod p) BigInteger base = Utils.baseForScopeExclusivePseudonym(scope, this.params.p, this.params.subgroupOrder); return base.modPow(this.deviceSecret, this.params.p); } @Override public BigInteger computeDevicePublicKey(int pin) { SmartcardStatusCode pinstatus = this.authenticateWithPin(pin); if (pinstatus != SmartcardStatusCode.OK) { return null; } // g^deviceSecret (mod p) return this.params.g.modPow(this.deviceSecret, this.params.p); } @Override public ZkProofCommitment prepareZkProof(int pin, Set<URI> credentialIds, Set<URI> scopeExclusivePseudonyms, boolean includeDevicePublicKeyProof) { SmartcardStatusCode pinstatus = this.authenticateWithPin(pin); if (pinstatus != SmartcardStatusCode.OK) { return null; } for(URI credentialId: credentialIds) { if (! this.credentials.containsKey(credentialId)) { return null; } // Check if attendance for each course is high enough // (or if we can do the proof without attendance check (for issuance)) TrustedIssuerParameters tip = this.issuerParameters.get(this.credentials.get(credentialId).parametersUri); if (tip == null) { throw new RuntimeException("Could not find issuer."); } if (tip.enforceAttendanceCheck) { if (! tip.course.sufficientAttendance() && this.credentials.get(credentialId).issued) { return null; } } } for(URI sep: scopeExclusivePseudonyms) { if (! this.uriLengthOk(sep)) { return null; } } ZkProofSpecification spec = this.getZkProofSpec(pin, credentialIds, scopeExclusivePseudonyms, includeDevicePublicKeyProof); if (spec == null) { return null; } ZkProofWitness wit = this.getZkProofWitness(credentialIds, includeDevicePublicKeyProof); this.zkProofState = ZkProofSystem.firstMove(spec, wit, rand); for(URI credentialId: credentialIds) { // Mark all credentials as being issued this.credentials.get(credentialId).issued = true; // Enable course counter TrustedIssuerParameters tip = this.issuerParameters.get(this.credentials.get(credentialId).parametersUri); if(tip.enforceAttendanceCheck) { tip.course.activate(); } } return this.zkProofState.commitment; } @Override public ZkProofResponse finalizeZkProof(int pin, BigInteger challenge, Set<URI> credentialIds, Set<URI> scopeExclusivePseudonyms) { SmartcardStatusCode pinstatus = this.authenticateWithPin(pin); if (pinstatus != SmartcardStatusCode.OK) { return null; } if(this.zkProofState == null) { return null; } ZkProofResponse response = ZkProofSystem.secondMove(this.zkProofState, challenge, rand); this.zkProofState = null; return response; } @Override public SmartcardBackup backupAttendanceData(int pin, String password) { //As we should emulate the hardware-smartcard, we have to encrypt //stuff in 3 different rounds. One for device-info, one for counters and //one for credentials. Everything else (e.g. blob-store) must be stored manually, and the //credential information kept is only credentialID || issuerID || status || prescount || v //where v is the secret key of the credential. SmartcardStatusCode pinstatus = this.authenticateWithPin(pin); if (pinstatus != SmartcardStatusCode.OK) { return null; } if( this.validatePassword(password) != SmartcardStatusCode.OK){ return null; } MessageDigest sha256 = null; try { sha256 = MessageDigest.getInstance("SHA-256"); } catch (NoSuchAlgorithmException e) { throw new RuntimeException(e); } SmartcardBackup backup = new SmartcardBackup(); sha256.update(this.macKey); sha256.update(Utils.passwordToByteArr(password)); byte[] tmp = sha256.digest(new byte[]{2}); //for counter info label is 2 byte[] key = new byte[16]; System.arraycopy(tmp, 0, key, 0, 16); //Then the counters int noOfCourses = this.listCourses(pin).size(); ByteBuffer coursesInfo = ByteBuffer.allocate(3*4*noOfCourses); //3 ints saved per course for(Course c : this.listCourses(pin)){ int courseID = c.getCourseID(); int lectureCount = c.getLectureCount(); int lastLectureID = c.getLastLectureId(); coursesInfo.putInt(courseID); coursesInfo.putInt(lectureCount); coursesInfo.putInt(lastLectureID); } byte[] toBackup = coursesInfo.array(); backup.macCounters = SmartcardCrypto.backup(toBackup, key, this.deviceID, rand); return backup; } private byte[] pinToByteArr(int pin) { String s = String.valueOf(pin); if(s.length() != 4){ int l = s.length(); int diff = 4-l; String tmp = s; s = ""; for(int i =0; i < diff; i++){ s += "0"; } s+=tmp; } byte[] res = new byte[4]; for(int i = 0; i < 4; i++){ res[i] = (byte)s.charAt(i); } return res; } @Override public SmartcardStatusCode restoreAttendanceData(int pin, String password, SmartcardBackup backup) { if( this.validatePassword(password) != SmartcardStatusCode.OK){ return null; } MessageDigest sha256 = null; try { sha256 = MessageDigest.getInstance("SHA-256"); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } //First, we create the issuers again by looking at the data from the counters sha256.update(this.macKey); sha256.update(Utils.passwordToByteArr(password)); byte[] tmp = sha256.digest(new byte[]{2}); //for counters, the label is 2 byte[] key = new byte[16]; System.arraycopy(tmp, 0, key, 0, 16); byte[][] counterInfo = SmartcardCrypto.restore(backup.macCounters, key); byte[] deviceID_bytes = counterInfo[0]; if(ByteBuffer.wrap(deviceID_bytes).getShort() != this.deviceID){ return SmartcardStatusCode.BAD_REQUEST; } ByteBuffer counterData = ByteBuffer.wrap(counterInfo[1]); int noOfCourses = counterData.remaining()/12; for(int i = 0; i < noOfCourses; i++){ int courseID = counterData.getInt(); int lectureCount = counterData.getInt(); int lastLectureID = counterData.getInt(); log.info("counter id: " + courseID + " issuer: "+this.getIssuerUriFromID(courseID)); this.issuerParameters.get(this.getIssuerUriFromID(courseID)).course.applyBackup(courseID, lectureCount, lastLectureID); } return SmartcardStatusCode.OK; } private byte[] pukToByteArr(int puk) { String s = String.valueOf(puk); if(s.length() != 8){ return null; } byte[] res = new byte[8]; for(int i = 0; i < 8; i++){ res[i] = (byte)(s.charAt(i) & 0xFF); } return res; } @Override public BigInteger computeCredentialFragment(int pin, URI credentialUri) { SmartcardStatusCode pinstatus = this.authenticateWithPin(pin); if (pinstatus != SmartcardStatusCode.OK) { return null; } CredentialOnSmartcard cos = this.credentials.get(credentialUri); if(cos == null) { return null; } TrustedIssuerParameters tos = this.issuerParameters.get(cos.parametersUri); SmartcardParameters sp = tos.groupParams; BigInteger base1 = sp.getBaseForDeviceSecret(); BigInteger base2 = sp.getBaseForCredentialSecretOrNull(); BigInteger p = sp.getModulus(); if (base2 != null) { BigInteger v = cos.v; return base1.modPow(this.deviceSecret, p).multiply(base2.modPow(v, p)).mod(p); } else { return base1.modPow(this.deviceSecret, p); } } private ZkProofSpecification getZkProofSpec(int pin, Set<URI> courseIds, Set<URI> scopeExclusivePseudonyms, boolean includeDevicePublicKeyProof) { SmartcardStatusCode pinstatus = this.authenticateWithPin(pin); if (pinstatus != SmartcardStatusCode.OK) { return null; } ZkProofSpecification zkps = new ZkProofSpecification(this.params); zkps.parametersForPseudonyms = this.params; zkps.credentialBases = new HashMap<URI, SmartcardParameters>(); zkps.credFragment = new HashMap<URI, BigInteger>(); for(URI courseId: courseIds) { if (! this.credentials.containsKey(courseId)) { return null; } CredentialOnSmartcard cos = this.credentials.get(courseId); TrustedIssuerParameters tos = this.issuerParameters.get(cos.parametersUri); zkps.credentialBases.put(courseId, tos.groupParams); BigInteger credFragment = this.computeCredentialFragment(pin, courseId); zkps.credFragment.put(courseId, credFragment); } zkps.scopeExclusivePseudonymValues = new HashMap<URI, BigInteger>(); for (URI scope: scopeExclusivePseudonyms) { BigInteger psValue = this.computeScopeExclusivePseudonym(pin, scope); zkps.scopeExclusivePseudonymValues.put(scope, psValue); } if (includeDevicePublicKeyProof) { zkps.devicePublicKey = this.computeDevicePublicKey(pin); } else { zkps.devicePublicKey = null; } return zkps; } private ZkProofWitness getZkProofWitness(Set<URI> credentialUris, boolean includeDevicePublicKeyProof) { ZkProofWitness wit = new ZkProofWitness(); wit.deviceSecret = this.deviceSecret; wit.courseRandomizer = new HashMap<URI, BigInteger>(); for(URI credId: credentialUris) { if (! this.credentials.containsKey(credId)) { return null; } CredentialOnSmartcard cos = this.credentials.get(credId); wit.courseRandomizer.put(credId, cos.v); } return wit; } @Override public Set<URI> listCredentialsUris(int pin) { SmartcardStatusCode pinstatus = this.authenticateWithPin(pin); if (pinstatus != SmartcardStatusCode.OK) { return null; } Set<URI> ret = new HashSet<URI>(this.credentials.keySet()); return ret; } @Override public TrustedIssuerParameters getIssuerParametersOfCredential(int pin, URI credentialUri) { SmartcardStatusCode pinstatus = this.authenticateWithPin(pin); if (pinstatus != SmartcardStatusCode.OK) { return null; } if (! this.credentials.containsKey(credentialUri)) { return null; } URI parametersUri = this.credentials.get(credentialUri).parametersUri; if (! this.issuerParameters.containsKey(parametersUri)) { throw new RuntimeException("Could not find issuer parameters"); } return this.issuerParameters.get(parametersUri); } @Override public SmartcardStatusCode storeCredential(int pin, URI credentialId, Credential cred, CredentialSerializer serializer){ byte[] credBytes = serializer.serializeCredential(cred); log.fine("CredBytes length: " + credBytes.length); int nextCredBlobUri = 1; SmartcardStatusCode returnCode = SmartcardStatusCode.OK; int bytesLeft = credBytes.length; int i = 0; boolean done = false; while(!done){ SmartcardBlob blob = new SmartcardBlob(); if(bytesLeft > MAX_BLOB_LEN_BYTES){ blob.blob = new byte[MAX_BLOB_LEN_BYTES]; bytesLeft -= MAX_BLOB_LEN_BYTES; System.arraycopy(credBytes, i*MAX_BLOB_LEN_BYTES, blob.blob, 0, MAX_BLOB_LEN_BYTES); }else{ blob.blob = new byte[bytesLeft]; System.arraycopy(credBytes, i*MAX_BLOB_LEN_BYTES, blob.blob, 0, bytesLeft); done = true; //We know we are done as we put the last bytes in the blob. } URI credUri = URI.create(credentialId.toASCIIString()+"_"+nextCredBlobUri++); log.fine("storing a blob of size: " + blob.blob.length + " with uri: " + credUri.toASCIIString()); returnCode = this.storeBlob(pin, credUri, blob); log.fine("Return from storeBlob: " + returnCode); if(returnCode != SmartcardStatusCode.OK){ return returnCode; } i++; } return returnCode; } @Override public SmartcardStatusCode storePseudonym(int pin, URI pseudonymId, PseudonymWithMetadata pseudo, PseudonymSerializer serializer){ byte[] pseudoBytes = serializer.serializePseudonym(pseudo); log.fine("PseudoBytes length: " + pseudoBytes.length); int nextPseudoBlobUri = 1; SmartcardStatusCode returnCode = SmartcardStatusCode.OK; int bytesLeft = pseudoBytes.length; int i = 0; boolean done = false; while(!done){ SmartcardBlob blob = new SmartcardBlob(); if(bytesLeft > MAX_BLOB_LEN_BYTES){ blob.blob = new byte[MAX_BLOB_LEN_BYTES]; bytesLeft -= MAX_BLOB_LEN_BYTES; System.arraycopy(pseudoBytes, i*MAX_BLOB_LEN_BYTES, blob.blob, 0, MAX_BLOB_LEN_BYTES); }else{ blob.blob = new byte[bytesLeft]; System.arraycopy(pseudoBytes, i*MAX_BLOB_LEN_BYTES, blob.blob, 0, bytesLeft); done = true; //We know we are done as we put the last bytes in the blob. } URI credUri = URI.create(pseudonymId.toASCIIString()+"_"+nextPseudoBlobUri++); log.fine("storing a blob of size: " + blob.blob.length + " with uri: " + credUri.toASCIIString()); returnCode = this.storeBlob(pin, credUri, blob); if(returnCode != SmartcardStatusCode.OK){ return returnCode; } i++; } return returnCode; } @Override public PseudonymWithMetadata getPseudonym(int pin, URI pseudonymId, PseudonymSerializer serializer){ ByteArrayOutputStream accumulatedPseuBytes = new ByteArrayOutputStream(); return this.getPseudonym(pin, pseudonymId, 1, accumulatedPseuBytes, serializer); } private PseudonymWithMetadata getPseudonym(int pin, URI pseudonymId, int nextPseuBlobUriId, ByteArrayOutputStream accumulatedPseuBytes, PseudonymSerializer serializer){ URI nextPseuBlobUri = URI.create(pseudonymId.toASCIIString()+"_"+nextPseuBlobUriId); log.fine("getting this uri: " + nextPseuBlobUri.toASCIIString()); SmartcardBlob scBlob = this.getBlob(pin, nextPseuBlobUri); if(scBlob == null){ return serializer.unserializePseudonym(accumulatedPseuBytes.toByteArray(), pseudonymId); } byte[] blob = scBlob.blob; accumulatedPseuBytes.write(blob, 0, blob.length); log.fine("Accumulated this many bytes: " + accumulatedPseuBytes.size()); if(blob.length < MAX_BLOB_LEN_BYTES){ return serializer.unserializePseudonym(accumulatedPseuBytes.toByteArray(), pseudonymId); }else{ //next round return this.getPseudonym(pin, pseudonymId, nextPseuBlobUriId+1, accumulatedPseuBytes, serializer); } } @Override public SmartcardStatusCode deletePseudonym(int pin, URI pseudonymUri){ int i = 1; while(true){ pseudonymUri = URI.create(pseudonymUri.toString()+"_"+i++); SmartcardStatusCode code = this.deleteBlob(pin, pseudonymUri); if(code != SmartcardStatusCode.OK){ return code; } } } @Override public Credential getCredential(int pin, URI credentialId, CredentialSerializer serializer){ ByteArrayOutputStream accumulatedCredBytes = new ByteArrayOutputStream(); return this.getCredential(pin, credentialId, 1, accumulatedCredBytes, serializer); } private Credential getCredential(int pin, URI credentialId, int nextCredBlobUriId, ByteArrayOutputStream accumulatedCredBytes, CredentialSerializer serializer){ URI nextCredBlobUri = URI.create(credentialId.toASCIIString()+"_"+nextCredBlobUriId); log.info("getting this uri: " + nextCredBlobUri.toASCIIString()); SmartcardBlob scBlob = this.getBlob(pin, nextCredBlobUri); if(scBlob == null){ return serializer.unserializeCredential(accumulatedCredBytes.toByteArray(), credentialId, this.getDeviceURI(pin)); } byte[] blob = scBlob.blob; accumulatedCredBytes.write(blob, 0, blob.length); log.fine("Accumulated this many bytes: " + accumulatedCredBytes.size()); if(blob.length < MAX_BLOB_LEN_BYTES){ //return new CredentialSerializerGzipXml().unserializeCredential(accumulatedCredBytes.toByteArray()); return serializer.unserializeCredential(accumulatedCredBytes.toByteArray(), credentialId, this.getDeviceURI(pin)); }else{ //next round return this.getCredential(pin, credentialId, nextCredBlobUriId+1, accumulatedCredBytes, serializer); } } @Override public SmartcardStatusCode allocateCredential(int pin, URI credentialUri, URI issuerParameters) { SmartcardStatusCode pinstatus = this.authenticateWithPin(pin); if (pinstatus != SmartcardStatusCode.OK) { return pinstatus; } TrustedIssuerParameters iparams = this.getIssuerParameters(pin, issuerParameters); if (iparams == null) { log.warning("IssuerParameters not found : " + issuerParameters + " - installed " + this.issuerParameters.keySet()); return SmartcardStatusCode.NOT_FOUND; } if (! this.uriLengthOk(credentialUri)) { return SmartcardStatusCode.REQUEST_URI_TOO_LONG; } if (this.credentials.containsKey(credentialUri)) { return SmartcardStatusCode.NOT_MODIFIED; } if ((this.credentials.size() + 1) > MAX_CREDENTIALS) { return SmartcardStatusCode.INSUFFICIENT_STORAGE; } CredentialOnSmartcard cos = new CredentialOnSmartcard(credentialUri, issuerParameters, rand, iparams.groupParams.getModulus().bitLength(), this.params.zkStatisticalHidingSizeBytes); log.fine("Puts a cred into SC cred store: " + credentialUri); this.credentials.put(credentialUri, cos); return SmartcardStatusCode.OK; } @Override public SmartcardStatusCode addIssuerParametersWithAttendanceCheck(RSAKeyPair rootKey, URI parametersUri, int keyIDForCounter, SmartcardParameters credBases, RSAVerificationKey courseKey, int minimumAttendance) { 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, minimumAttendance); SmartcardStatusCode sigstatus = this.authenticateWithSignature(rootKey, this.schoolKey, baos); if (sigstatus != SmartcardStatusCode.OK) { return sigstatus; } if (! this.uriLengthOk(parametersUri)) { return SmartcardStatusCode.REQUEST_URI_TOO_LONG; } if (this.issuerParameters.containsKey(parametersUri)) { return SmartcardStatusCode.NOT_MODIFIED; } if ((this.issuerParameters.size() + 1) > MAX_ISSUERS) { return SmartcardStatusCode.INSUFFICIENT_STORAGE; } this.courseKeys.put(keyIDForCounter, courseKey); byte issuerID = (byte) this.getIssuerIDFromUri(parametersUri); TrustedIssuerParameters param = new TrustedIssuerParameters(issuerID, parametersUri, credBases, minimumAttendance, keyIDForCounter); this.issuerParameters.put(parametersUri, param); return SmartcardStatusCode.OK; } /* @Override public SmartcardStatusCode addUProveIssuerParametersWithAttendanceCheck(RSAKeyPair rootKey, URI parametersUri, int keyIDForCounter, UProveParams uProveParams, RSAVerificationKey courseKey, int minimumAttendance) { ByteArrayOutputStream baos = new ByteArrayOutputStream(); Utils.addToStream(baos, Utils.NEW_ISSUER_WITH_ATTENDANCE); Utils.addToStream(baos, parametersUri); Utils.addToStream(baos, uProveParams); Utils.addToStream(baos, courseKey); Utils.addToStream(baos, minimumAttendance); SmartcardStatusCode sigstatus = this.authenticateWithSignature(rootKey, this.schoolKey, baos); if (sigstatus != SmartcardStatusCode.OK) { return sigstatus; } if (! this.uriLengthOk(parametersUri)) { return SmartcardStatusCode.REQUEST_URI_TOO_LONG; } if (this.issuerParameters.containsKey(parametersUri)) { return SmartcardStatusCode.NOT_MODIFIED; } if ((this.issuerParameters.size() + 1) > MAX_ISSUERS) { return SmartcardStatusCode.INSUFFICIENT_STORAGE; } this.courseKeys.put(keyIDForCounter, courseKey); byte issuerID = (byte) this.getIssuerIDFromUri(parametersUri); TrustedIssuerParameters param = new TrustedIssuerParameters(issuerID, parametersUri, uProveParams, minimumAttendance, keyIDForCounter); this.issuerParameters.put(parametersUri, param); return SmartcardStatusCode.OK; } */ @Override public SmartcardStatusCode addIssuerParameters(RSAKeyPair rootKey, URI parametersUri, SmartcardParameters credBases) { ByteArrayOutputStream baos = new ByteArrayOutputStream(); Utils.addToStream(baos, Utils.NEW_ISSUER_SIMPLE); Utils.addToStream(baos, parametersUri); Utils.addToStream(baos, credBases); SmartcardStatusCode sigstatus = this.authenticateWithSignature(rootKey, this.schoolKey, baos); if (sigstatus != SmartcardStatusCode.OK) { return sigstatus; } if (! this.uriLengthOk(parametersUri)) { return SmartcardStatusCode.REQUEST_URI_TOO_LONG; } if (this.issuerParameters.containsKey(parametersUri)) { return SmartcardStatusCode.NOT_MODIFIED; } if ((this.issuerParameters.size() + 1) > MAX_ISSUERS) { return SmartcardStatusCode.INSUFFICIENT_STORAGE; } TrustedIssuerParameters param = new TrustedIssuerParameters(parametersUri, credBases); this.issuerParameters.put(parametersUri, param); return SmartcardStatusCode.OK; } /* @Override public SmartcardStatusCode addUProveIssuerParameters(RSAKeyPair rootKey, URI parametersUri, UProveParams uProveParams) { ByteArrayOutputStream baos = new ByteArrayOutputStream(); Utils.addToStream(baos, Utils.NEW_ISSUER_SIMPLE); Utils.addToStream(baos, parametersUri); Utils.addToStream(baos, uProveParams); SmartcardStatusCode sigstatus = this.authenticateWithSignature(rootKey, this.schoolKey, baos); if (sigstatus != SmartcardStatusCode.OK) { return sigstatus; } if (! this.uriLengthOk(parametersUri)) { return SmartcardStatusCode.REQUEST_URI_TOO_LONG; } if (this.issuerParameters.containsKey(parametersUri)) { return SmartcardStatusCode.NOT_MODIFIED; } if ((this.issuerParameters.size() + 1) > MAX_ISSUERS) { return SmartcardStatusCode.INSUFFICIENT_STORAGE; } TrustedIssuerParameters param = new TrustedIssuerParameters(parametersUri, uProveParams); this.issuerParameters.put(parametersUri, param); return SmartcardStatusCode.OK; } */ @Override public TrustedIssuerParameters getIssuerParameters(int pin, URI paramsUri) { SmartcardStatusCode pinstatus = this.authenticateWithPin(pin); if (pinstatus != SmartcardStatusCode.OK) { return null; } if (! this.issuerParameters.containsKey(paramsUri)) { return null; } return this.issuerParameters.get(paramsUri); } @Override public Set<TrustedIssuerParameters> getIssuerParametersList(int pin) { SmartcardStatusCode pinstatus = this.authenticateWithPin(pin); if (pinstatus != SmartcardStatusCode.OK) { return null; } return new HashSet<TrustedIssuerParameters>(this.issuerParameters.values()); } @Override public SmartcardStatusCode deleteIssuer(int pin, URI paramUri, RSAKeyPair rootKey) { if(!this.factoryInit){ return SmartcardStatusCode.NOT_INITIALIZED; } if(!this.issuerParameters.containsKey(paramUri)){ return SmartcardStatusCode.NOT_MODIFIED; } ByteArrayOutputStream toSign = new ByteArrayOutputStream(); Utils.addToStream(toSign, paramUri); this.authenticateWithSignature(rootKey, this.schoolKey, toSign); if (! this.issuerParameters.containsKey(paramUri)) { return SmartcardStatusCode.NOT_FOUND; } for(CredentialOnSmartcard cos: this.credentials.values()) { if (cos.parametersUri.equals(paramUri)) { return SmartcardStatusCode.NOT_MODIFIED; } } this.issuerParameters.remove(paramUri); return SmartcardStatusCode.OK; } @Override public boolean credentialExists(int pin, URI credentialUri) { SmartcardStatusCode pinstatus = this.authenticateWithPin(pin); if (pinstatus != SmartcardStatusCode.OK) { return false; } log.info("Trying to fetch "+credentialUri.toString()); log.fine("\n"); CredentialOnSmartcard cos = this.credentials.get(credentialUri); for(URI cred : this.credentials.keySet()){ log.fine("Credentials on smartcard available: " + cred); } log.fine("\n"); return cos != null; } @Override public int getCounterValue(int pin, URI issuerId) { Course course = this.getCourse(pin, issuerId); return course.getLectureCount(); } public String getHashOfDeviceSecret() { MessageDigest md; try { md = MessageDigest.getInstance("SHA-256"); md.update(this.deviceSecret.toByteArray()); byte[] mdbytes = md.digest(); //convert the byte to hex format method 1 StringBuffer sb = new StringBuffer(); for (byte mdbyte : mdbytes) { sb.append(Integer.toString((mdbyte & 0xff) + 0x100, 16).substring(1)); } return sb.toString(); } catch (NoSuchAlgorithmException ex) { throw new RuntimeException(ex); } } public String getHashOfSystemParameters() { if (this.params != null) { MessageDigest md; try { md = MessageDigest.getInstance("SHA-256"); ByteArrayOutputStream bos = new ByteArrayOutputStream(); ObjectOutput out = null; try { out = new ObjectOutputStream(bos); out.writeObject(this.params); byte[] yourBytes = bos.toByteArray(); md.update(yourBytes); byte[] mdbytes = md.digest(); // convert the byte to hex format method 1 StringBuffer sb = new StringBuffer(); for (byte mdbyte : mdbytes) { sb.append(Integer.toString((mdbyte & 0xff) + 0x100, 16) .substring(1)); } out.close(); bos.close(); return sb.toString(); } catch (IOException ex) { throw new RuntimeException(ex); } finally { try { out.close(); bos.close(); } catch (IOException ex) { ex.printStackTrace(); } } } catch (NoSuchAlgorithmException ex) { throw new RuntimeException(ex); } } return "No smart system params"; } public String getHashOfIssuerParameters() { MessageDigest md; try { md = MessageDigest.getInstance("SHA-256"); for (TrustedIssuerParameters tip : this.issuerParameters.values()) { this.updateDigest(md, tip); } byte[] mdbytes = md.digest(); // convert the byte to hex format method 1 StringBuffer sb = new StringBuffer(); for (byte mdbyte : mdbytes) { sb.append(Integer.toString((mdbyte & 0xff) + 0x100, 16) .substring(1)); } return sb.toString(); } catch (NoSuchAlgorithmException ex) { throw new RuntimeException(ex); } } public String getHashOfCredentialKeys() { MessageDigest md; try { md = MessageDigest.getInstance("SHA-256"); for (CredentialOnSmartcard cos : this.credentials.values()) { this.updateDigest(md, cos); } byte[] mdbytes = md.digest(); // convert the byte to hex format method 1 StringBuffer sb = new StringBuffer(); for (byte mdbyte : mdbytes) { sb.append(Integer.toString((mdbyte & 0xff) + 0x100, 16) .substring(1)); } return sb.toString(); } catch (NoSuchAlgorithmException ex) { throw new RuntimeException(ex); } } private void updateDigest(MessageDigest md, Serializable cos) { ByteArrayOutputStream bos = new ByteArrayOutputStream(); ObjectOutput out = null; try { out = new ObjectOutputStream(bos); out.writeObject(cos); byte[] yourBytes = bos.toByteArray(); md.update(yourBytes); } catch (Exception ex) { throw new RuntimeException(ex); } } }