/**************************************************************************** * Copyright (C) 2012 HS Coburg. * All rights reserved. * Contact: ecsec GmbH (info@ecsec.de) * * This file is part of the Open eCard App. * * GNU General Public License Usage * This file may be used under the terms of the GNU General Public * License version 3.0 as published by the Free Software Foundation * and appearing in the file LICENSE.GPL included in the packaging of * this file. Please review the following information to ensure the * GNU General Public License version 3.0 requirements will be met: * http://www.gnu.org/copyleft/gpl.html. * * Other Usage * Alternatively, this file may be used in accordance with the terms * and conditions contained in a signed written agreement between * you and ecsec GmbH. * ***************************************************************************/ package org.openecard.crypto.common.asn1.cvc; import java.util.Iterator; import java.util.Map; import java.util.TreeMap; import org.openecard.common.tlv.TLV; import org.openecard.common.tlv.TLVException; import org.openecard.common.tlv.TagClass; import org.openecard.common.util.ByteUtils; import org.openecard.crypto.common.asn1.eac.oid.CVCertificatesObjectIdentifier; import org.openecard.crypto.common.asn1.utils.ObjectIdentifierUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Implements the Certificate Holder Authorization Template (CHAT) * * See BSI-TR-03110, version 2.10, part 3, section C.4. * * @author Moritz Horsch <horsch@cdc.informatik.tu-darmstadt.de> * @author Dirk Petrautzki <petrautzki@hs-coburg.de> */ public final class CHAT { private static final Logger _logger = LoggerFactory.getLogger(CHAT.class); private String oid; private Role role; private byte[] discretionaryData; private TreeMap<DataGroup, Boolean> writeAccess = new TreeMap<DataGroup, Boolean>() { { DataGroup[] data = DataGroup.values(); for (int i = 16; i < 21; i++) { put(data[i], false); } } }; private TreeMap<DataGroup, Boolean> readAccess = new TreeMap<DataGroup, Boolean>() { { DataGroup[] data = DataGroup.values(); for (int i = 0; i < 21; i++) { put(data[i], false); } } }; private TreeMap<SpecialFunction, Boolean> specialFunctions = new TreeMap<SpecialFunction, Boolean>() { { SpecialFunction[] data = SpecialFunction.values(); for (int i = 0; i < data.length; i++) { put(data[i], false); } } }; private TreeMap<AccessRight, Boolean> accessRights = new TreeMap<AccessRight, Boolean>() { { AccessRight[] data = AccessRight.values(); for (int i = 0; i < data.length; i++) { put(data[i], false); } } }; /** * Represents the roles. * See BSI-TR-03110, version 2.10, part 3, section C.4.1. * See BSI-TR-03110, version 2.10, part 3, section C.4.2. * See BSI-TR-03110, version 2.10, part 3, section C.4.3. */ public enum Role { CVCA, DV_OFFICIAL, DV_NON_OFFICIAL, AUTHENTICATION_TERMINAL, INSPECTION_TERMINAL, SIGNATURE_TERMINAL } /** * Represents the special functions. * See BSI-TR-03110, version 2.10, part 3, section C.4.2. */ public enum SpecialFunction { INSTALL_QUALIFIED_CERTIFICATE, INSTALL_CERTIFICATE, PIN_MANAGEMENT, CAN_ALLOWED, PRIVILEGED_TERMINAL, RESTRICTED_IDENTIFICATION, COMMUNITY_ID_VERIFICATION, AGE_VERIFICATION; } /** * Represents the data groups. * See BSI-TR-03110, version 2.10, part 3, section C.4.2. * See BSI-TR-03110, version 2.10, part 2, section A.1. */ public enum DataGroup { DG01, DG02, DG03, DG04, DG05, DG06, DG07, DG08, DG09, DG10, DG11, DG12, DG13, DG14, DG15, DG16, DG17, DG18, DG19, DG20, DG21; } /** * Represents the access rights. * See BSI-TR-03110, version 2.10, part 3, section C.4.1. * See BSI-TR-03110, version 2.10, part 3, section C.4.3. */ public enum AccessRight { DG03, DG04, GENERATE_SIGNATURE, GENERATE_QUALIFIED_SIGNATURE; } /** * Creates a new CHAT. * * @param chat CHAT * @throws TLVException */ public CHAT(byte[] chat) throws TLVException { this(TLV.fromBER(chat)); } /** * Creates a new CHAT. * * @param tlv TLV * @throws TLVException */ public CHAT(TLV tlv) throws TLVException { oid = ObjectIdentifierUtils.toString(tlv.findChildTags(0x06).get(0).getValue()); discretionaryData = tlv.findChildTags(0x53).get(0).getValue(); if (oid.equals(CVCertificatesObjectIdentifier.id_IS)) { // Inspection systems parseRole(discretionaryData[0]); parseAccessRights(discretionaryData[0]); } else if (oid.equals(CVCertificatesObjectIdentifier.id_AT)) { // Authentication terminal parseRole(discretionaryData[0]); parseWriteAccess(discretionaryData); parseReadAccess(discretionaryData); parseSpecialFunctions(discretionaryData); } else if (oid.equals(CVCertificatesObjectIdentifier.id_ST)) { // Signature terminal parseRole(discretionaryData[0]); parseAccessRights(discretionaryData[0]); } } /** * Parse the role of the CHAT. * * @param roleByte Role */ private void parseRole(byte roleByte) { roleByte = (byte) (roleByte & (byte) 0xC0); switch (roleByte) { case (byte) 0xC0: role = Role.CVCA; break; case (byte) 0x80: role = Role.DV_OFFICIAL; break; case (byte) 0x40: role = Role.DV_NON_OFFICIAL; break; case (byte) 0x00: if (oid.equals(CVCertificatesObjectIdentifier.id_IS)) { role = Role.INSPECTION_TERMINAL; } else if (oid.equals(CVCertificatesObjectIdentifier.id_AT)) { role = Role.AUTHENTICATION_TERMINAL; } else if (oid.equals(CVCertificatesObjectIdentifier.id_ST)) { role = Role.SIGNATURE_TERMINAL; } break; default: break; } } /** * Parse the access rights of the CHAT. * * @param accessRightsByte Access rights */ private void parseAccessRights(byte accessRightsByte) { if (role.equals(Role.INSPECTION_TERMINAL)) { if (ByteUtils.isBitSet(6, new byte[]{accessRightsByte})) { // Read access to ePassport application: DG 4 (Iris) accessRights.put(AccessRight.DG04, Boolean.TRUE); } if (ByteUtils.isBitSet(7, new byte[]{accessRightsByte})) { // Read access to ePassport application: DG 3 (Fingerprint) accessRights.put(AccessRight.DG03, Boolean.TRUE); } } else if (role.equals(Role.SIGNATURE_TERMINAL)) { if (ByteUtils.isBitSet(6, new byte[]{accessRightsByte})) { // Generate qualified electronic signature accessRights.put(AccessRight.GENERATE_QUALIFIED_SIGNATURE, Boolean.TRUE); } if (ByteUtils.isBitSet(7, new byte[]{accessRightsByte})) { // Generate electronic signature accessRights.put(AccessRight.GENERATE_SIGNATURE, Boolean.TRUE); } } } /** * Parse the write access of the CHAT. * * @param discretionaryData Discretionary data */ private void parseWriteAccess(byte[] discretionaryData) { Iterator<DataGroup> it = writeAccess.keySet().iterator(); for (int i = 2; i < 6; i++) { DataGroup item = it.next(); writeAccess.put(item, ByteUtils.isBitSet(i, discretionaryData)); } } /** * Parse the read access of the CHAT. * * @param discretionaryData Discretionary data */ private void parseReadAccess(byte[] discretionaryData) { Iterator<DataGroup> it = readAccess.keySet().iterator(); for (int i = 31; i > 11; i--) { DataGroup item = it.next(); readAccess.put(item, ByteUtils.isBitSet(i, discretionaryData)); } } /** * Parse the special functions of the CHAT. * * @param discretionaryData Discretionary data */ private void parseSpecialFunctions(byte[] discretionaryData) { Iterator<SpecialFunction> it = specialFunctions.keySet().iterator(); for (int i = 32; i < 40; i++) { SpecialFunction item = it.next(); specialFunctions.put(item, ByteUtils.isBitSet(i, discretionaryData)); } } /** * Returns the role of the CHAT. * * @return Write access */ public Role getRole() { return role; } /** * Returns the write access of the CHAT. * * @return Write access */ public TreeMap<DataGroup, Boolean> getWriteAccess() { return writeAccess; } /** * Sets the write access of the CHAT. * * @param dataGroup Data group * @param selected Selected * @return True if data group is set, otherwise false */ public boolean setWriteAccess(DataGroup dataGroup, boolean selected) { if (writeAccess.containsKey(dataGroup)) { writeAccess.put(dataGroup, selected); return true; } return false; } /** * Sets the write access of the CHAT. * * @param dataGroup Data Group * @param selected Selected * @return True if data group is set, otherwise false */ public boolean setWriteAccess(String dataGroup, boolean selected) { return setWriteAccess(DataGroup.valueOf(dataGroup), selected); } /** * Sets the write access of the CHAT. * * @param writeAccess Write access */ public void setWriteAccess(TreeMap<DataGroup, Boolean> writeAccess) { Iterator<DataGroup> it = this.writeAccess.keySet().iterator(); while (it.hasNext()) { DataGroup item = it.next(); this.writeAccess.put(item, writeAccess.get(item)); } } /** * Returns the read access of the CHAT. * * @return Read access */ public TreeMap<DataGroup, Boolean> getReadAccess() { return readAccess; } /** * Sets the read access of the CHAT. * * @param dataGroup Data group * @param selected Selected * @return True if the data group is set, otherwise false */ public boolean setReadAccess(DataGroup dataGroup, boolean selected) { if (readAccess.containsKey(dataGroup)) { readAccess.put(dataGroup, selected); return true; } return false; } /** * Sets the read access of the CHAT. * * @param dataGroup Data group * @param selected Selected * @return True if the data group is set, otherwise false */ public boolean setReadAccess(String dataGroup, boolean selected) { return setReadAccess(DataGroup.valueOf(dataGroup), selected); } /** * Sets the read access of the CHAT. * * @param readAccess Read access */ public void setReadAccess(TreeMap<DataGroup, Boolean> readAccess) { Iterator<DataGroup> it = this.readAccess.keySet().iterator(); while (it.hasNext()) { DataGroup item = it.next(); this.readAccess.put(item, readAccess.get(item)); } } /** * Returns the special function of the CHAT. * * @return Special functions */ public TreeMap<SpecialFunction, Boolean> getSpecialFunctions() { return specialFunctions; } /** * Sets the special functions of the CHAT. * * @param specialFunction Special functions * @param selected Selected * @return True if the special function is set, otherwise false */ public boolean setSpecialFunctions(SpecialFunction specialFunction, boolean selected) { if (specialFunctions.containsKey(specialFunction)) { specialFunctions.put(specialFunction, selected); return true; } return false; } /** * Sets the given special function of the CHAT. * * @param specialFunction Special function * @param selected Selected * @return True if the special function is set, otherwise false */ public boolean setSpecialFunction(String specialFunction, boolean selected) { return setSpecialFunctions(SpecialFunction.valueOf(specialFunction), selected); } /** * Sets the special functions of the CHAT. * * @param specialFunctions Special functions */ public void setSpecialFunctions(TreeMap<SpecialFunction, Boolean> specialFunctions) { Iterator<SpecialFunction> it = this.specialFunctions.keySet().iterator(); while (it.hasNext()) { SpecialFunction item = it.next(); this.specialFunctions.put(item, specialFunctions.get(item)); } } /** * Returns the access rights of the CHAT. * * @return Access rights */ public TreeMap<AccessRight, Boolean> getAccessRights() { return accessRights; } /** * Sets the special functions of the CHAT. * * @param accessRight Access right * @param selected Selected * @return True if the access right is set, otherwise false */ public boolean setAccessRights(AccessRight accessRight, boolean selected) { if (accessRights.containsKey(accessRight)) { accessRights.put(accessRight, selected); return true; } return false; } /** * Sets the special functions of the CHAT. * * @param accessRight Access right * @param selected Selected * @return True if the access right is set, otherwise false */ public boolean setAccessRights(String accessRight, boolean selected) { return setAccessRights(AccessRight.valueOf(accessRight), selected); } /** * Sets the special functions of the CHAT. * * @param accessRights Access right */ public void setAccessRights(TreeMap<AccessRight, Boolean> accessRights) { Iterator<AccessRight> it = this.accessRights.keySet().iterator(); while (it.hasNext()) { AccessRight item = it.next(); this.accessRights.put(item, accessRights.get(item)); } } /** * Restricts this CHAT by using the given CHAT as a mask. * * @param mask CHAT to use as mask. */ public void restrictAccessRights(CHAT mask) { removeRights(readAccess, mask.readAccess); removeRights(writeAccess, mask.writeAccess); removeRights(specialFunctions, mask.specialFunctions); removeRights(accessRights, mask.accessRights); } private static <T> void removeRights(TreeMap<T, Boolean> orig, final TreeMap<T, Boolean> mask) { for (Map.Entry<T, Boolean> entry : mask.entrySet()) { if (entry.getValue() == false) { orig.put(entry.getKey(), false); } } } /** * Returns the CHAT as a byte array. * * @return CHAT * @throws TLVException */ public byte[] toByteArray() throws TLVException { byte[] data = new byte[5]; // Decode role in bit 0 to 1. switch (role) { case CVCA: data[0] |= 0xC0; break; case DV_OFFICIAL: data[0] |= 0x80; break; case DV_NON_OFFICIAL: data[0] |= 0x40; break; default: break; } // Decode write access in bit 2 to 6. Iterator<DataGroup> it1 = writeAccess.keySet().iterator(); for (int i = 2; i < 6; i++) { DataGroup item = it1.next(); if (writeAccess.get(item)) { ByteUtils.setBit(i, data); } } // Decode read access in bit 11 to 31. Iterator<DataGroup> it2 = readAccess.keySet().iterator(); for (int i = 31; i > 11; i--) { DataGroup item = it2.next(); if (readAccess.get(item)) { ByteUtils.setBit(i, data); } } // Decode special functions in bit 32 to 40. Iterator<SpecialFunction> it3 = specialFunctions.keySet().iterator(); for (int i = 32; i < 40; i++) { SpecialFunction item = it3.next(); if (specialFunctions.get(item)) { ByteUtils.setBit(i, data); } } // Decode access rights in bit 2 to 7. Iterator<AccessRight> it4 = accessRights.keySet().iterator(); for (int i = 6; i < 7; i++) { AccessRight item = it4.next(); if (accessRights.get(item)) { ByteUtils.setBit(i, data); } } TLV discretionaryDataObject = new TLV(); discretionaryDataObject.setTagNumWithClass((byte) 0x53); discretionaryDataObject.setValue(data); TLV oidObject = new TLV(); oidObject.setTagNumWithClass((byte) 0x06); oidObject.setValue(ObjectIdentifierUtils.getValue(oid)); oidObject.addToEnd(discretionaryDataObject); TLV chatObject = new TLV(); chatObject.setTagNum((byte) 0x4C); chatObject.setTagClass(TagClass.APPLICATION); chatObject.setChild(oidObject); return chatObject.toBER(true); } /** * Compares the CHAT. * * @param chat CHAT * @return True if the CHATs are equal, otherwise false */ public boolean compareTo(CHAT chat) { try { return ByteUtils.compare(toByteArray(), chat.toByteArray()); } catch (Exception e) { return false; } } /** * Returns the CHAT as a hex encoded string. * * @return CHAT */ public String toHexString() { try { return ByteUtils.toHexString(toByteArray(), true); } catch (TLVException ex) { _logger.error(ex.getMessage(), ex); return null; } } @Override public String toString() { try { return ByteUtils.toHexString(toByteArray()); } catch (TLVException ex) { _logger.error(ex.getMessage(), ex); return null; } } }