package de.persosim.simulator.protocols.ca; import static de.persosim.simulator.protocols.Tr03110Utils.buildAuthenticationTokenInput; import static org.globaltester.logging.BasicLogger.DEBUG; import static org.globaltester.logging.BasicLogger.TRACE; import static org.globaltester.logging.BasicLogger.log; import java.security.InvalidKeyException; import java.security.KeyPair; import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; import java.security.PublicKey; import java.security.SecureRandom; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashSet; import javax.crypto.KeyAgreement; import javax.crypto.spec.SecretKeySpec; import org.globaltester.cryptoprovider.Crypto; import de.persosim.simulator.apdu.ResponseApdu; import de.persosim.simulator.cardobjects.CardObject; import de.persosim.simulator.cardobjects.CardObjectIdentifier; import de.persosim.simulator.cardobjects.CardObjectUtils; import de.persosim.simulator.cardobjects.KeyIdentifier; import de.persosim.simulator.cardobjects.KeyObject; import de.persosim.simulator.cardobjects.KeyPairObject; import de.persosim.simulator.cardobjects.MasterFile; import de.persosim.simulator.cardobjects.OidIdentifier; import de.persosim.simulator.crypto.CryptoSupport; import de.persosim.simulator.crypto.DomainParameterSet; import de.persosim.simulator.crypto.KeyDerivationFunction; import de.persosim.simulator.crypto.StandardizedDomainParameters; import de.persosim.simulator.exception.CryptoException; import de.persosim.simulator.exception.ProcessingException; import de.persosim.simulator.platform.Iso7816; import de.persosim.simulator.platform.PlatformUtil; import de.persosim.simulator.protocols.AbstractProtocolStateMachine; import de.persosim.simulator.protocols.Oid; import de.persosim.simulator.protocols.ProtocolUpdate; import de.persosim.simulator.protocols.SecInfoPublicity; import de.persosim.simulator.protocols.Tr03110; import de.persosim.simulator.protocols.Tr03110Utils; import de.persosim.simulator.protocols.ta.TerminalAuthenticationMechanism; import de.persosim.simulator.secstatus.SecMechanism; import de.persosim.simulator.secstatus.SecStatus.SecContext; import de.persosim.simulator.secstatus.SecStatusMechanismUpdatePropagation; import de.persosim.simulator.secstatus.SecStatusStoreUpdatePropagation; import de.persosim.simulator.secstatus.SecurityEvent; import de.persosim.simulator.secstatus.SessionContextIdMechanism; import de.persosim.simulator.securemessaging.SmDataProviderTr03110; import de.persosim.simulator.tlv.ConstructedTlvDataObject; import de.persosim.simulator.tlv.PrimitiveTlvDataObject; import de.persosim.simulator.tlv.TlvConstants; import de.persosim.simulator.tlv.TlvDataObject; import de.persosim.simulator.tlv.TlvDataObjectContainer; import de.persosim.simulator.tlv.TlvPath; import de.persosim.simulator.tlv.TlvTag; import de.persosim.simulator.tlv.TlvValue; import de.persosim.simulator.utils.HexString; import de.persosim.simulator.utils.Utils; /** * This class is part of the implementation of the Chip Authentication (CA) * protocol version 2 and implements basic methods to be used by * {@link DefaultCaProtocol}. * * @author slutters * */ //XXX SLS generalize code overlapping with {@link AbstractPaceProtocol} where possible. public abstract class AbstractCaProtocol extends AbstractProtocolStateMachine implements Ca, TlvConstants { protected SecureRandom secureRandom; protected CaOid caOid; protected DomainParameterSet caDomainParameters; protected String keyAgreementAlgorithmName; protected CryptoSupport cryptoSupport; protected int keyReference; protected KeyPair staticKeyPairPicc; protected SecretKeySpec secretKeySpecMAC; protected SecretKeySpec secretKeySpecENC; protected int sessionContextIdentifier; public AbstractCaProtocol() { super("CA"); secureRandom = new SecureRandom(); } @Override public void initialize() { } /** * @param bytes the OID given in the SET AT command * @return the CaOid helper class to be used by this protocol */ protected CaOid getOid(byte [] bytes){ return new CaOid(bytes); } protected CaOid extractCaOidFromCommandData(TlvDataObjectContainer commandData) { /* CA OID */ /* Check for the CA OID for itself */ /* tlvObject will never be null if APDU passed check against APDU specification */ TlvDataObject tlvObject = commandData.getTlvDataObject(TAG_80); CaOid caOid; try { caOid = getOid(tlvObject.getValueField()); } catch (RuntimeException e) { throw new ProcessingException(PlatformUtil.SW_4A80_WRONG_DATA, e.getMessage()); } log(this, "new OID is " + caOid, DEBUG); return caOid; } protected KeyIdentifier extractKeyIdentifierFromCommandData(TlvDataObjectContainer commandData) { /* key reference */ /* tlvObject may be null if key material is to be implicitly selected */ TlvDataObject tlvObject = commandData.getTlvDataObject(TAG_84); KeyIdentifier keyIdentifier; if(tlvObject == null) { keyIdentifier = new KeyIdentifier(); } else{ keyIdentifier = new KeyIdentifier(tlvObject.getValueField()); } return keyIdentifier; } protected KeyObject getkeyObjectForKeyIdentifier(KeyIdentifier keyIdentifier, CardObjectIdentifier... cardObjectIdentifier) { CardObject cardObject; try { cardObject = CardObjectUtils.getSpecificChild(cardState.getMasterFile(), keyIdentifier); } catch (IllegalArgumentException e) { throw new ProcessingException(Iso7816.SW_6A88_REFERENCE_DATA_NOT_FOUND, e.getMessage()); } KeyObject keyObject; if((cardObject instanceof KeyObject)) { keyObject = (KeyObject) cardObject; if(cardObjectIdentifier != null) { for(CardObjectIdentifier coi: cardObjectIdentifier) { if(!coi.matches(keyObject)) { throw new ProcessingException(Iso7816.SW_6985_CONDITIONS_OF_USE_NOT_SATISFIED, "invalid key reference"); } } } return keyObject; } throw new ProcessingException(Iso7816.SW_6A88_REFERENCE_DATA_NOT_FOUND,"no fitting key object found"); } /** * This method searches for the Session Context ID in the MSE: SetAT * @return the ID as int or -1 if no ID was attached to the Command APDU */ protected int extractSessionContextId(){ ConstructedTlvDataObject constructedTlvContextIdentifier = (ConstructedTlvDataObject) processingData.getCommandApdu().getCommandDataObjectContainer().getTlvDataObject(TlvConstants.TAG_E0); TlvDataObject tlvContextIdentifier = null; if (constructedTlvContextIdentifier != null) { tlvContextIdentifier = constructedTlvContextIdentifier.getTlvDataObject(TlvConstants.TAG_81); String hex = HexString.encode(tlvContextIdentifier.getTlvValue().toByteArray()); int sessionContextIdentifier = Integer.decode("0x" + hex); return sessionContextIdentifier; } return -1; // no ID found } /** * This method performs the processing of the CA Set AT command. */ public void processCommandSetAT() { try { //get commandDataContainer TlvDataObjectContainer commandData = processingData.getCommandApdu().getCommandDataObjectContainer(); //extract Session Contect ID from APDU sessionContextIdentifier = extractSessionContextId(); caOid = extractCaOidFromCommandData(commandData); KeyIdentifier keyIdentifier = extractKeyIdentifierFromCommandData(commandData); OidIdentifier caOidIdentifier = new OidIdentifier(caOid); KeyObject keyObject = getkeyObjectForKeyIdentifier(keyIdentifier, caOidIdentifier); if (keyObject instanceof KeyPairObject){ KeyPairObject keyPairObject = (KeyPairObject) keyObject; /* CA domain parameters */ staticKeyPairPicc = keyPairObject.getKeyPair(); caDomainParameters = Tr03110Utils.getDomainParameterSetFromKey(staticKeyPairPicc.getPublic()); } else { ResponseApdu resp = new ResponseApdu(PlatformUtil.SW_4984_REFERENCE_DATA_NOT_USABLE); processingData.updateResponseAPDU(this, "The domain parameters could not be extracted from the referenced key", resp); return; } keyReference = keyObject.getPrimaryIdentifier().getInteger(); /* CA domain parameters */ caDomainParameters = Tr03110Utils.getDomainParameterSetFromKey(staticKeyPairPicc.getPublic()); this.cryptoSupport = caOid.getCryptoSupport(); ResponseApdu resp = new ResponseApdu(Iso7816.SW_9000_NO_ERROR); processingData.updateResponseAPDU(this, "Command Set AT successfully processed", resp); } catch (ProcessingException e) { ResponseApdu resp = new ResponseApdu(PlatformUtil.convertTo4xxxStatusWord(e.getStatusWord())); processingData.updateResponseAPDU(this, e.getMessage(), resp); } } /** * This method reconstructs the PCD's public key sent with General Authenticate * @param publicKeyMaterialPcd encoded kley material of the PCD's public key * @return the PCD's public key */ protected PublicKey reconstructEphemeralPublicKeyPcd(byte[] publicKeyMaterialPcd) { PublicKey ephemeralPublicKeyPcd; try { ephemeralPublicKeyPcd = caDomainParameters.reconstructPublicKey(publicKeyMaterialPcd); log(this, "PCD's ephemeral public " + keyAgreementAlgorithmName + " key is " + new TlvDataObjectContainer(ephemeralPublicKeyPcd.getEncoded()), TRACE); } catch (IllegalArgumentException e) { throw new ProcessingException(Iso7816.SW_6A80_WRONG_DATA, e.getMessage()); } catch (Exception e) { throw new ProcessingException(Iso7816.SW_6FFF_IMPLEMENTATION_ERROR, e.getMessage()); } return ephemeralPublicKeyPcd; } /** * This method checks that the PCD's public key matches the compressed key received during previous TA * @param ephemeralPublicKeyPcd the PCD's public key */ protected void assertEphemeralPublicKeyPcdMatchesCompressedKeyReceivedDuringTa(PublicKey ephemeralPublicKeyPcd) { //compare expected PCD's (compressed) public key with the key previously received during TA byte[] ephemeralPublicKeyPcdCompressedExpected; ephemeralPublicKeyPcdCompressedExpected = caDomainParameters.comp(ephemeralPublicKeyPcd); byte[] ephemeralPublicKeyPcdCompressedReceived = getEphemeralPublicKeyPcdFromTa(); if(ephemeralPublicKeyPcdCompressedReceived == null) { throw new ProcessingException(Iso7816.SW_6982_SECURITY_STATUS_NOT_SATISFIED, "PICC's compressed ephemeral public key from TA is missing. Maybe TA was not performed."); } log(this, "expected compressed PCD's ephemeral public " + keyAgreementAlgorithmName + " key of " + ephemeralPublicKeyPcdCompressedExpected.length + " bytes length is: " + HexString.encode(ephemeralPublicKeyPcdCompressedExpected), DEBUG); log(this, "received compressed PCD's ephemeral public " + keyAgreementAlgorithmName + " key of " + ephemeralPublicKeyPcdCompressedReceived.length + " bytes length is: " + HexString.encode(ephemeralPublicKeyPcdCompressedReceived), DEBUG); if(Arrays.equals(ephemeralPublicKeyPcdCompressedExpected, ephemeralPublicKeyPcdCompressedReceived)) { log(this, "compressed representation of PCD's ephemeral public " + caDomainParameters.getKeyAgreementAlgorithm() + " key matches the one received during previous TA", DEBUG); } else{ throw new ProcessingException(Iso7816.SW_6984_REFERENCE_DATA_NOT_USABLE, "compressed representation of PCD's public " + keyAgreementAlgorithmName + " key does NOT match the one received during previous TA"); } } /** * This method performs the ca key agreement * @param staticPrivateKeyPicc the private key to use * @param ephemeralPublicKeyPcd the public key to use * @return the shared secret */ protected byte[] performKeyAgreement(PrivateKey staticPrivateKeyPicc, PublicKey ephemeralPublicKeyPcd) { //perform key agreement KeyAgreement keyAgreement; byte[] sharedSecret = null; try { keyAgreement = KeyAgreement.getInstance(caOid.getKeyAgreementName(), Crypto.getCryptoProvider()); keyAgreement.init(staticPrivateKeyPicc); keyAgreement.doPhase(ephemeralPublicKeyPcd, true); sharedSecret = keyAgreement.generateSecret(); } catch (InvalidKeyException e) { throw new ProcessingException(Iso7816.SW_6A80_WRONG_DATA, "invalid key"); } catch(NoSuchAlgorithmException | IllegalStateException e) { throw new ProcessingException(Iso7816.SW_6FFF_IMPLEMENTATION_ERROR, e.getMessage()); } log(this, "shared secret K of " + sharedSecret.length + " bytes length is: " + HexString.encode(sharedSecret), DEBUG); return sharedSecret; } /** * This method computes the CA session keys * @param sharedSecret the shared secret used to compute the session keys * @param rPiccNonce the PICC's nonce r used to generate the session keys */ protected void computeSessionKeys(byte[] sharedSecret, byte[] rPiccNonce) { //compute session keys int keyLengthInBytes = caOid.getSymmetricCipherKeyLengthInBytes(); KeyDerivationFunction kdf = new KeyDerivationFunction(keyLengthInBytes); log(this, "computing " + getIDString() + " session keys", DEBUG); log(this, "shared secret is: " + HexString.encode(sharedSecret), DEBUG); log(this, "nonce is : " + HexString.encode(rPiccNonce), DEBUG); log(this, "key length specified by " + getIDString() + " OID " + caOid + " is: " + keyLengthInBytes, DEBUG); byte[] keyMaterialMac = kdf.deriveMAC(sharedSecret, rPiccNonce); byte[] keyMaterialEnc = kdf.deriveENC(sharedSecret, rPiccNonce); log(this, "chip's session key for MAC of " + keyMaterialMac.length + " bytes length is: " + HexString.encode(keyMaterialMac), DEBUG); log(this, "chip's session key for ENC of " + keyMaterialMac.length + " bytes length is: " + HexString.encode(keyMaterialEnc), DEBUG); secretKeySpecMAC = cryptoSupport.generateSecretKeySpecMac(keyMaterialMac); secretKeySpecENC = cryptoSupport.generateSecretKeySpecCipher(keyMaterialEnc); } /** * This method generates the PICC's nonce r * @return the PICC's nonce r */ protected byte[] generateRPiccNonce() { //get nonce r_PICC int nonceSizeInBytes = 8; byte[] rPiccNonce = new byte[nonceSizeInBytes]; this.secureRandom.nextBytes(rPiccNonce); log(this, "nonce r_PICC of " + nonceSizeInBytes + " bytes length is: " + HexString.encode(rPiccNonce), DEBUG); return rPiccNonce; } /** * This method computes the PICC's authentication token * @param caDomainParameters the domain parameters to be used * @param caOid the CA OID to be used * @param ephemeralPublicKeyPcd the PCD's ephemeral public key * @param cryptoSupport the crypto support to be used * @param secretKeySpecMAC the MAC secret key spec to be used * @return the PICC's authentication token */ protected static byte[] computeAuthenticationTokenTpicc(DomainParameterSet caDomainParameters, CaOid caOid, PublicKey ephemeralPublicKeyPcd, CryptoSupport cryptoSupport, SecretKeySpec secretKeySpecMAC) { //compute authentication token T_PICC TlvDataObjectContainer authenticationTokenInput = buildAuthenticationTokenInput(ephemeralPublicKeyPcd, caDomainParameters, caOid); log(AbstractCaProtocol.class, "authentication token raw data " + authenticationTokenInput, DEBUG); byte[] authenticationTokenTpicc = Arrays.copyOf(cryptoSupport.macAuthenticationToken(authenticationTokenInput.toByteArray(), secretKeySpecMAC), 8); log(AbstractCaProtocol.class, "PICC's authentication token T_PICC of " + authenticationTokenTpicc.length + " bytes length is: " + HexString.encode(authenticationTokenTpicc), DEBUG); return authenticationTokenTpicc; } /** * This method computes the PICC's authentication token * @param ephemeralPublicKeyPcd the PCD's ephemeral public key * @return the PICC's authentication token */ protected byte[] computeAuthenticationTokenTpicc(PublicKey ephemeralPublicKeyPcd) { return computeAuthenticationTokenTpicc(caDomainParameters, caOid, ephemeralPublicKeyPcd, cryptoSupport, secretKeySpecMAC); } /** * This method prepares the response data to be sent within the response APDU * @param rPiccNonce the PICC's nonce r * @param authenticationTokenTpicc the PICC's authentication token * @return the response data to be sent within the response APDU */ protected TlvValue prepareResponseData(byte[] rPiccNonce, byte[] authenticationTokenTpicc) { //create and prepare response APDU PrimitiveTlvDataObject primitive81 = new PrimitiveTlvDataObject(TAG_81, rPiccNonce); log(this, "primitive tag 81 is: " + primitive81, TRACE); PrimitiveTlvDataObject primitive82 = new PrimitiveTlvDataObject(TAG_82, authenticationTokenTpicc); log(this, "primitive tag 82 is: " + primitive82, TRACE); ConstructedTlvDataObject constructed7C = new ConstructedTlvDataObject(TAG_7C); constructed7C.addTlvDataObject(primitive81); constructed7C.addTlvDataObject(primitive82); log(this, "response data to be sent is: " + constructed7C, DEBUG); //create and propagate response APDU TlvValue responseData = new TlvDataObjectContainer(constructed7C); return responseData; } /** * This method propagates the session keys to be used for secure messaging */ protected void propagateSessionKeys() { //create and propagate new secure messaging data provider SmDataProviderTr03110 smDataProvider; try { smDataProvider = new SmDataProviderTr03110(this.secretKeySpecENC, this.secretKeySpecMAC); processingData.addUpdatePropagation(this, "init SM after successful CA", smDataProvider); } catch (CryptoException e) { throw new ProcessingException(Iso7816.SW_6FFF_IMPLEMENTATION_ERROR, "Unable to initialize new secure messaging"); } } /** * This method retrieves the PCD's public key material from the received General Authenticate APDU * @return the PCD's public key material */ protected byte[] getPcdPublicKeyMaterialFromApdu() { //retrieve command data TlvDataObjectContainer commandData = processingData.getCommandApdu().getCommandDataObjectContainer(); //retrieve PCD's public key TlvDataObject tlvObject = commandData.getTlvDataObject(new TlvPath(new TlvTag((byte) 0x7C), new TlvTag((byte) 0x80))); byte[] pcdPublicKeyMaterial = tlvObject.getValueField(); keyAgreementAlgorithmName = caDomainParameters.getKeyAgreementAlgorithm(); log(this, "PCD's ephemeral public " + keyAgreementAlgorithmName + " key material of " + pcdPublicKeyMaterial.length + " bytes length is: " + HexString.encode(pcdPublicKeyMaterial), TRACE); return pcdPublicKeyMaterial; } /** * This method performs the processing of the CA General Authenticate * command. */ public void processCommandGeneralAuthenticate() { try { byte[] pcdPublicKeyMaterial = getPcdPublicKeyMaterialFromApdu(); PublicKey ephemeralPublicKeyPcd = reconstructEphemeralPublicKeyPcd(pcdPublicKeyMaterial); assertEphemeralPublicKeyPcdMatchesCompressedKeyReceivedDuringTa(ephemeralPublicKeyPcd); byte[] sharedSecret = performKeyAgreement(staticKeyPairPicc.getPrivate(), ephemeralPublicKeyPcd); byte[] rPiccNonce = generateRPiccNonce(); computeSessionKeys(sharedSecret, rPiccNonce); byte[] authenticationTokenTpicc = computeAuthenticationTokenTpicc(ephemeralPublicKeyPcd); propagateSessionKeys(); //Save Default Session Context processingData.addUpdatePropagation(this, "Inform the SecStatus to store the session context", new SecStatusStoreUpdatePropagation(SecurityEvent.STORE_SESSION_CONTEXT, getSessionContextId())); ChipAuthenticationMechanism mechanism = new ChipAuthenticationMechanism(caOid, keyReference, ephemeralPublicKeyPcd); processingData.addUpdatePropagation(this, "Updated security status with chip authentication information", new SecStatusMechanismUpdatePropagation(SecContext.APPLICATION, mechanism)); TlvValue responseData = prepareResponseData(rPiccNonce, authenticationTokenTpicc); //store the new session context id provided in MSE: setAT if (sessionContextIdentifier>0x00){ SessionContextIdMechanism scim = new SessionContextIdMechanism(sessionContextIdentifier); processingData.addUpdatePropagation(this, "Security status updated with SessionContextIdMechanism", new SecStatusMechanismUpdatePropagation(SecContext.APPLICATION, scim)); } ResponseApdu resp = new ResponseApdu(responseData, Iso7816.SW_9000_NO_ERROR); processingData.updateResponseAPDU(this, "Command General Authenticate successfully processed", resp); /* * Request removal of this instance from the stack. * Protocol either successfully completed or failed. * In either case protocol is completed. */ processingData.addUpdatePropagation(this, "Command General Authenticate successfully processed - Protocol CA completed", new ProtocolUpdate(true)); } catch (ProcessingException e) { ResponseApdu resp = new ResponseApdu(e.getStatusWord()); processingData.updateResponseAPDU(this, e.getMessage(), resp); } } /** * Returns the current Session Context ID to use from the SecStatus * @return the Session Context ID */ protected int getSessionContextId(){ int id = -1; Collection<Class<? extends SecMechanism>> wantedMechanisms = new HashSet<Class<? extends SecMechanism>>(); wantedMechanisms.add(SessionContextIdMechanism.class); Collection<SecMechanism> currentMechanisms = cardState.getCurrentMechanisms(SecContext.APPLICATION, wantedMechanisms); for(SecMechanism secMechanism : currentMechanisms) { if(secMechanism instanceof SessionContextIdMechanism) { id = ((SessionContextIdMechanism) secMechanism).getSessionContextId(); break; } } return id; } /** * This method retrieves the PCD's ephemeral public key material received during TA * @return the PCD's ephemeral public key material */ protected byte[] getEphemeralPublicKeyPcdFromTa() { byte[] ephemeralPublicKeyPcdCompressedReceived = null; Collection<Class<? extends SecMechanism>> wantedMechanisms = new HashSet<Class<? extends SecMechanism>>(); wantedMechanisms.add(TerminalAuthenticationMechanism.class); Collection<SecMechanism> currentMechanisms = cardState.getCurrentMechanisms(SecContext.APPLICATION, wantedMechanisms); for(SecMechanism secMechanism : currentMechanisms) { if(secMechanism instanceof TerminalAuthenticationMechanism) { ephemeralPublicKeyPcdCompressedReceived = ((TerminalAuthenticationMechanism) secMechanism).getCompressedTerminalEphemeralPublicKey(); break; // there is at most one TerminalAuthenticationMechanism } } return ephemeralPublicKeyPcdCompressedReceived; } /** * This method returns the version of this protocol * @return the version of this protocol */ protected byte getVersion() { return 2; } protected ConstructedTlvDataObject constructChipAuthenticationInfoObject(byte[] oidBytes, int keyId) { return constructChipAuthenticationInfoObject(oidBytes, getVersion(), keyId); } /** * construct and add ChipAuthenticationInfo object(s) * * ChipAuthenticationInfo ::= SEQUENCE { * protocol OBJECT IDENTIFIER( * id-CA-DH-3DES-CBC-CBC | * id-CA-DH-AES-CBC-CMAC-128 | * id-CA-DH-AES-CBC-CMAC-192 | * id-CA-DH-AES-CBC-CMAC-256 | * id-CA-ECDH-3DES-CBC-CBC | * id-CA-ECDH-AES-CBC-CMAC-128 | * id-CA-ECDH-AES-CBC-CMAC-192 | * id-CA-ECDH-AES-CBC-CMAC-256), * version INTEGER, -- MUST be 1 for CAv1 or 2 for CAv2 or 3 for CAv3 * keyId INTEGER OPTIONAL * } * * @param oidBytes * @param version * @param keyId * @return */ public static ConstructedTlvDataObject constructChipAuthenticationInfoObject(byte[] oidBytes, byte version, int keyId) { ConstructedTlvDataObject caInfo = new ConstructedTlvDataObject(TAG_SEQUENCE); caInfo.addTlvDataObject(new PrimitiveTlvDataObject(TAG_OID, oidBytes)); caInfo.addTlvDataObject(new PrimitiveTlvDataObject(TAG_INTEGER, new byte[]{version})); //always set keyId even if truly optional/not mandatory //another version of CA may be present so keys are no longer unique and the keyId field becomes mandatory caInfo.addTlvDataObject(new PrimitiveTlvDataObject(TAG_INTEGER, Utils.toShortestUnsignedByteArray(keyId))); return caInfo; } @Override public Collection<TlvDataObject> getSecInfos(SecInfoPublicity publicity, MasterFile mf) { OidIdentifier caOidIdentifier = new OidIdentifier(OID_id_CA); Collection<CardObject> caKeyCardObjects = mf.findChildren( new KeyIdentifier(), caOidIdentifier); ArrayList<TlvDataObject> secInfos = new ArrayList<>(); ArrayList<TlvDataObject> privilegedSecInfos = new ArrayList<>(); ArrayList<TlvDataObject> unprivilegedPublicKeyInfos = new ArrayList<>(); ArrayList<TlvDataObject> privilegedPublicKeyInfos = new ArrayList<>(); for (CardObject curObject : caKeyCardObjects) { if (! (curObject instanceof KeyPairObject)) { continue; } KeyPairObject curKey = (KeyPairObject) curObject; Collection<CardObjectIdentifier> identifiers = curKey.getAllIdentifiers(); //extract keyId int keyId = -1; for (CardObjectIdentifier curIdentifier : identifiers) { if (curIdentifier instanceof KeyIdentifier) { keyId = ((KeyIdentifier) curIdentifier).getKeyReference(); break; } } if (keyId == -1) continue; // skip keys that dont't provide a keyId //cached values byte[] genericCaOidBytes = null; //construct and add ChipAuthenticationInfo object(s) for (CardObjectIdentifier curIdentifier : identifiers) { if (curIdentifier instanceof OidIdentifier) { Oid curOid = ((OidIdentifier) curIdentifier).getOid(); if (curOid.startsWithPrefix(id_CA)) { byte[] oidBytes = curOid.toByteArray(); genericCaOidBytes = Arrays.copyOfRange(oidBytes, 0, 9); ConstructedTlvDataObject caInfo = constructChipAuthenticationInfoObject(oidBytes, keyId); if (curKey.isPrivilegedOnly()) { privilegedSecInfos.add(caInfo); } else { secInfos.add(caInfo); } } } } //extract required data from curKey ConstructedTlvDataObject encKey = new ConstructedTlvDataObject(curKey.getKeyPair().getPublic().getEncoded()); ConstructedTlvDataObject algIdentifier = (ConstructedTlvDataObject) encKey.getTlvDataObject(TAG_SEQUENCE); TlvDataObject subjPubKey = computeSubjectPublicKey(encKey); //using standardized domain parameters if possible algIdentifier = StandardizedDomainParameters.simplifyAlgorithmIdentifier(algIdentifier); /* * add ChipAuthenticationDomainParameterInfo object(s) * * ChipAuthenticationDomainParameterInfo ::= SEQUENCE { * protocol OBJECT IDENTIFIER(id-CA-DH | id-CA-ECDH), * domainParameter AlgorithmIdentifier, * keyId INTEGER OPTIONAL * } */ ConstructedTlvDataObject caDomainInfo = new ConstructedTlvDataObject(TAG_SEQUENCE); caDomainInfo.addTlvDataObject(new PrimitiveTlvDataObject(TAG_OID, genericCaOidBytes)); caDomainInfo.addTlvDataObject(algIdentifier); //always set keyId even if truly optional/not mandatory //another version of CA may be present so keys are no longer unique and the keyId field becomes mandatory caDomainInfo.addTlvDataObject(new PrimitiveTlvDataObject(TAG_INTEGER, Utils.toShortestUnsignedByteArray(keyId))); if (curKey.isPrivilegedOnly()) { privilegedSecInfos.add(caDomainInfo); } else { secInfos.add(caDomainInfo); } //build SubjectPublicKeyInfo ConstructedTlvDataObject subjPubKeyInfo = new ConstructedTlvDataObject(TAG_SEQUENCE); subjPubKeyInfo.addTlvDataObject(algIdentifier); subjPubKeyInfo.addTlvDataObject(subjPubKey); if ((publicity == SecInfoPublicity.AUTHENTICATED) || (publicity == SecInfoPublicity.PRIVILEGED)) { /* * add ChipAuthenticationPublicKeyInfo object(s) * * id-PK OBJECT IDENTIFIER ::= { * bsi-de protocols(2) smartcard(2) 1 * } * * id-PK-DH OBJECT IDENTIFIER ::= {id-PK 1} * id-PK-ECDH OBJECT IDENTIFIER ::= {id-PK 2} * * ChipAuthenticationPublicKeyInfo ::= SEQUENCE { * protocol OBJECT IDENTIFIER(id-PK-DH | id-PK-ECDH), * chipAuthenticationPublicKey SubjectPublicKeyInfo, * keyId INTEGER OPTIONAL * } */ ConstructedTlvDataObject caPublicKeyInfo = new ConstructedTlvDataObject(TAG_SEQUENCE); caPublicKeyInfo.addTlvDataObject(new PrimitiveTlvDataObject(TAG_OID, Utils.concatByteArrays(Tr03110.id_PK, new byte[] {genericCaOidBytes[8]}))); caPublicKeyInfo.addTlvDataObject(subjPubKeyInfo); //always set keyId even if truly optional/not mandatory //another version of CA may be present so keys are no longer unique and the keyId field becomes mandatory caPublicKeyInfo.addTlvDataObject(new PrimitiveTlvDataObject(TAG_INTEGER, new byte[]{(byte) keyId})); addChipAuthenticationPublicKeyInfo(curKey.isPrivilegedOnly(), privilegedPublicKeyInfos, unprivilegedPublicKeyInfos, caPublicKeyInfo); } } // add publicKeys if publicity allows if ((publicity == SecInfoPublicity.AUTHENTICATED) || (publicity == SecInfoPublicity.PRIVILEGED)) { secInfos.addAll(unprivilegedPublicKeyInfos); } //add PrivilegedTerminalInfo if privileged keys are available if (privilegedSecInfos.size() + privilegedPublicKeyInfos.size() > 0) { ConstructedTlvDataObject privilegedTerminalInfo = new ConstructedTlvDataObject(TAG_SEQUENCE); privilegedTerminalInfo.addTlvDataObject(new PrimitiveTlvDataObject(TAG_OID, Tr03110.id_PT)); ConstructedTlvDataObject privilegedTerminaInfoSet = new ConstructedTlvDataObject(TAG_SET); privilegedTerminalInfo.addTlvDataObject(privilegedTerminaInfoSet); // add all privileged infos privilegedTerminaInfoSet.addAll(privilegedSecInfos); // add privileged public keys if publicity allows if ((publicity == SecInfoPublicity.PRIVILEGED)) { privilegedTerminaInfoSet.addAll(privilegedPublicKeyInfos); } secInfos.add(privilegedTerminalInfo); } return secInfos; } protected void addChipAuthenticationPublicKeyInfo(boolean isPrivilegedOnly, ArrayList<TlvDataObject> privilegedPublicKeyInfos, ArrayList<TlvDataObject> unprivilegedPublicKeyInfos, ConstructedTlvDataObject caPublicKeyInfo) { if (isPrivilegedOnly) { privilegedPublicKeyInfos.add(caPublicKeyInfo); } else { // TODO is copying to privileged also needed here? unprivilegedPublicKeyInfos.add(caPublicKeyInfo); } } protected TlvDataObject computeSubjectPublicKey(ConstructedTlvDataObject encKey) { return encKey.getTlvDataObject(TAG_BIT_STRING); } }