/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package bft.util;

import bft.util.BFTCommon.BFTException;
import com.google.protobuf.ByteString;
import com.google.protobuf.InvalidProtocolBufferException;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectInputStream;
import java.io.ObjectOutput;
import java.io.ObjectOutputStream;
import java.security.InvalidKeyException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.SignatureException;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateException;
import java.security.cert.PKIXCertPathBuilderResult;
import java.security.cert.X509Certificate;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
import org.apache.commons.codec.binary.Hex;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.hyperledger.fabric.protos.common.Common;
import org.hyperledger.fabric.protos.common.Configtx;
import org.hyperledger.fabric.protos.common.Configuration;
import org.hyperledger.fabric.protos.common.MspPrincipal;
import org.hyperledger.fabric.protos.common.Policies;
import org.hyperledger.fabric.protos.msp.Identities;
import org.hyperledger.fabric.protos.msp.MspConfig;
import org.hyperledger.fabric.sdk.exception.CryptoException;

/**
 *
 * @author joao
 */
public class MSPManager {
    
    private static Logger logger;
    //internal state
    private String mspid;
    private String sysChannel;
            
    //fundamental for the state transfer protocol
    private Map<String, Configtx.ConfigEnvelope> chanConfigs = new TreeMap<>(); 
    private Map<String, Long> lastConfigs = new TreeMap<>();
    // these two are fundamental because requie the state machine timestamp everytime they are updated
    private Map<String, OUIdentifier> clientOUs = new TreeMap<>();
    private Map<String, OUIdentifier> peerOUs = new TreeMap<>();
    
    //not necessary for state transfer, but useful for channel management and authentication
    private Map<String,MspConfig.FabricMSPConfig> MSPs = new TreeMap<>();
    private Map<String,Set<String>> chanMSPs = new TreeMap<>();
    private Map<String,Policy> chanWriters = new TreeMap<>();
    private Map<String,Set<X509Certificate>> rootCerts = new TreeMap<>();
    private Map<String,Set<X509Certificate>> intermediateCerts = new TreeMap<>();
    private Map<String,Set<X509Certificate>> adminCerts = new TreeMap<>();
    private Map<String,Set<X509Certificate>> revokedCerts = new TreeMap<>();
    private Map<String,Map<X509Certificate,List<X509Certificate>>> validatedCerts = new TreeMap<>();

    //constants related to channel reconfiguration
    private static final String rootGroupKey = "Channel";
    
    private static final String groupPrefix  = "[Group]  ";
    private static final String valuePrefix  = "[Value]  ";
    private static final String policyPrefix = "[Policy] ";
    private static final String pathSeparator = "/";
    
    private static final String consortiumKey = "Consortium";
    private static final String consortiumsGroupKey = "Consortiums";
    private static final String channelCreationPolicyKey = "ChannelCreationPolicy";
    private static final String applicationGroupKey = "Application";
    private static final String ordererGroupKey = "Orderer";
    private static final String adminsPolicyKey = "Admins";
        
    // Hacky fix constants, used in recurseConfigMap
    private static final String  hackyFixOrdererCapabilities = "[Value]  /Channel/Orderer/Capabilities";
    private static final String  hackyFixNewModPolicy        = "Admins";
    
    private class ConfigItem {
        
        Configtx.ConfigGroup group;
        Configtx.ConfigPolicy policy;
        Configtx.ConfigValue value = null;
        String key = null;
        String[] path = null;
        
        ConfigItem(Configtx.ConfigGroup group, String key, String[] path) {
            
            this.group = group;
            this.key = key;
            this.path = path;
        }
        
        ConfigItem(Configtx.ConfigPolicy policy, String key, String[] path) {
            
            this.policy = policy;
            this.key = key;
            this.path = path;
        }
        
        ConfigItem(Configtx.ConfigValue value, String key, String[] path) {
            
            this.value = value;
            this.key = key;
            this.path = path;
        }
        
        long getVersion() {
            
            if (this.group != null) return group.getVersion();
            else if (this.policy != null) return policy.getVersion();
            else return this.value.getVersion();
        }
        
        String getModPolicy() {
            
            if (this.group != null) return group.getModPolicy();
            else if (this.policy != null) return policy.getModPolicy();
            else return this.value.getModPolicy();
        }
    }
    
    public class SignedData {
        
        byte[] data;
        byte[] identity;
        byte[] signature;
        
        SignedData(byte[] data, byte[] identity, byte[] signature) {
            
            this.data = data;
            this.identity = identity;
            this.signature = signature;
        }
    }
    
    public abstract class Policy {
        
        protected abstract BFTCommon.BFTException evaluate(SignedData[] signedData, boolean[] used, String channel, long timestamp);
        public abstract String getPath();
        
        public void evaluate(SignedData[] signedData, String channel, long timestamp) throws BFTCommon.BFTException {
            
            logger.info("Evaluating policy " + getPath());
            
            boolean[] used = new boolean[signedData.length];
            Arrays.fill(used, Boolean.FALSE);
            BFTException ex = evaluate(deduplicate(channel, signedData), used, channel, timestamp);
            if (ex != null) {
                
                logger.info("Evaluation of policy " + getPath() + " failed: " + ex.getLocalizedMessage());
                throw ex;
            }
            
            logger.info("Evaluation of policy " + getPath() + " succeeded");
            
        }
        
        private SignedData[] deduplicate(String channel, SignedData[] signedData) {
            
            Set<String> ids = new HashSet<>();
            LinkedList<SignedData> deduplicated = new LinkedList<>();
            
            for (SignedData sd : signedData) {
                
                Identity id = null;
                
                try {
                    id = deserializeIdentity(channel, sd.identity);
                } catch (Exception ex) {
                    
                    logger.debug("Failed to deserialize principal during deduplication: " + ex.getMessage());
                    continue;
                }
                
                String key = (id.id+id.msp);
                
                if (ids.contains(key)) {
                    
                    logger.debug("De-duplicating identity " + id + " in signature set");
                } else {
                    
                    deduplicated.add(sd);
                    ids.add(key);
                }
            }
            
            SignedData[] result = new SignedData[deduplicated.size()];
            deduplicated.toArray(result);
            return result;
        }
    }
    
    class Identity {
        
            
        String msp;
        String id;
        String channel;
        X509Certificate certificate;
        byte[] serializedCert;
        
        Identity(String msp, String id,String channel, X509Certificate certificate, byte[] serializedCert) {
            
            this.msp = msp;
            this.id = id;
            this.channel = channel;
            this.certificate = certificate;
            this.serializedCert = serializedCert;
        }

        @Override
        public int hashCode() {
            int hash = 7;
            hash = 31 * hash + this.msp.hashCode();
            hash = 31 * hash + this.id.hashCode();
            hash = 31 * hash + this.channel.hashCode();
            return hash;
        }

        @Override
        public boolean equals(Object o) {

            if (this == o) return true;
            if (o == null) return false;
            if (this.getClass() != o.getClass()) return false;
            Identity i = (Identity) o;
            return this.id.equals(i.id) && this.msp.equals(i.msp) && 
                    this.channel.equals(i.channel) && Arrays.equals(this.serializedCert, i.serializedCert);
        }
        
        @Override
        public String toString(){
            
            return "["+this.id + ":" + this.msp+"]";
        }
        
        public boolean verify(byte[] data, byte[] signature) throws CertificateEncodingException, CryptoException, NoSuchAlgorithmException, NoSuchProviderException, InvalidKeyException, SignatureException, BFTException, KeyStoreException {
           
            
            //Fabric v1.1 implementation fetches the signature hash family and supplies it to the hashing function, but the
            //implemetation of the hashing function ignores that option when it type to compute the hash. Nonetheless, X509
            //certificates have information about the signature algorithm, hence we use those.
            
            //String sigHashFamily = MSPs.get(this.msp).getCryptoConfig().getSignatureHashFamily();
            //String defaultFamily = crypto.getProperties().getProperty(Config.SIGNATURE_ALGORITHM);

            return BFTCommon.verifySignature(this.certificate, data, signature);              
            
        }
        
        private List<X509Certificate> validate(long timestamp) throws BFTException {
            
            logger.debug("Verifying certificate chain for identity "+ this);
            
            List<X509Certificate> certPath = verifyCertificate(this.certificate,this.msp,timestamp);
            
            if (MSPs.get(this.msp).getFabricNodeOus().getEnable()) {
                            
                logger.debug("Fetching OU identifiers for identity " + this);

                OUIdentifier[] OUIDs = getOrganizationalUnits(certPath);

                logger.debug("Obtained the following OU identifiers for identity " +this+": " + Arrays.deepToString(OUIDs));

                int counter = 0;

                for (OUIdentifier OU : OUIDs) {

                    OUIdentifier nodeOU = null;

                    if (OU.organizationalUnitIdentifier.equals(clientOUs.get(this.msp).organizationalUnitIdentifier)) {

                        nodeOU = clientOUs.get(this.msp);
                    }

                    else if (OU.organizationalUnitIdentifier.equals(peerOUs.get(this.msp).organizationalUnitIdentifier)) {

                        nodeOU = peerOUs.get(this.msp);
                    }

                    else {
                        continue;
                    }

                    if (nodeOU.certifiersIdentifier.length != 0 && !Arrays.equals(nodeOU.certifiersIdentifier, OU.certifiersIdentifier)) {

                            throw new BFTCommon.BFTException("CID does not match: OUs: " + Arrays.deepToString(OUIDs) +", MSP: " + this.msp);
                    }

                    counter++;

                    if (counter > 1) {
                            break;
                    }

                }

                if (counter != 1) {

                    String msg = null;

                    if (counter > 0) msg = "Identity " + this + " must be a client, a peer or an orderer identity to be valid, not a combination of them.";
                    else msg = "Identity " + this + " does not have a OU matching the identifier for either a client or a peer of the MSP.";

                    throw new BFTCommon.BFTException(msg + " OUs: " + Arrays.deepToString(OUIDs) +", MSP: " + this.msp);
                }
            
            }
                        
            return certPath;
        }
        
        private void parseRoleMember(MspPrincipal.MSPPrincipal principal, long timestamp) throws BFTException {
            
            logger.debug("Parsing MSP principal "+ principal + " as role member");
            
            validate(timestamp);
            
        }
        
        private void parseRoleAdmin(MspPrincipal.MSPPrincipal principal, long timestamp) throws BFTException {
            
            logger.debug("Parsing MSP principal "+ principal + " as role admin");
            
            if (adminCerts.get(this.msp).contains(this.certificate)) {
            
                validate(timestamp);
            
            } else throw new BFTCommon.BFTException("Identity " + this + " is not an admin");
            

        }
        
        private void parseRolePeer(MspPrincipal.MSPPrincipal principal, MspPrincipal.MSPRole mspRole, long timestamp) throws BFTException {
            
            logger.debug("Parsing MSP principal "+ principal + " as role peer");
            
            List<X509Certificate> certPath = validate(timestamp);
                                    
            if (MSPs.get(this.msp).getFabricNodeOus().getEnable()) {
            
                String nodeOUValue = null;
    
                switch (mspRole.getRole()) {
                    
                    case CLIENT:
                        
                        nodeOUValue = clientOUs.get(this.msp).organizationalUnitIdentifier;
                        break;
                        
                    case PEER:
                        
                        nodeOUValue = peerOUs.get(this.msp).organizationalUnitIdentifier;
                        break;
                        
                    default:
                        
                        throw new BFTCommon.BFTException("Invalid MSPRoleType. It must be CLIENT, PEER or ORDERER");
                }
                
                OUIdentifier[] OUs = getOrganizationalUnits(certPath);
                
                for (OUIdentifier OU : OUs) {
                    
                    if (OU.organizationalUnitIdentifier.equals(nodeOUValue)) return;
                }
                
                throw new BFTCommon.BFTException("Identity "+ this +" does not contain OU " + nodeOUValue);
                
            }
        }
        
        private void parseIdentity(String channel, MspPrincipal.MSPPrincipal principal, long timestamp) throws BFTException {
            
            logger.debug("Parsing MSP principal "+ principal + " as identity");
            
            Identity identPrincipal = null;
            try {
                identPrincipal = deserializeIdentity(channel, principal.getPrincipal().toByteArray());
            } catch (Exception ex) {
                
                throw new BFTCommon.BFTException("Unable to deserialize principal's identity: " + ex.getMessage());
            }
            
            if (Arrays.equals(this.serializedCert, identPrincipal.serializedCert)) {
            
                identPrincipal.validate(timestamp);
                
            } else throw new BFTCommon.BFTException("Principal's identity does not match with identity " + this);
        }
        
        private void parseOU(MspPrincipal.MSPPrincipal principal, long timestamp) throws BFTException {
            
            logger.debug("Parsing MSP principal "+ principal + " as OU");
            
            MspPrincipal.OrganizationUnit OU = null;
            
            try {
                OU = MspPrincipal.OrganizationUnit.parseFrom(principal.getPrincipal());
            } catch (InvalidProtocolBufferException ex) {
                
                throw new BFTCommon.BFTException("Could not parse OrganizationUnit from principal " + principal);
            }
            
            if (!OU.getMspIdentifier().equals(this.msp))
                throw new BFTCommon.BFTException("Identity " + this + "is a member of a different MSP (expected " + this.msp + ", got " + OU.getMspIdentifier());
            
            
            List<X509Certificate> certPath = validate(timestamp);
            
            OUIdentifier[] OUs = getOrganizationalUnits(certPath);
            
            for (OUIdentifier ou : OUs) {
                
                if (ou.organizationalUnitIdentifier.equals(OU.getOrganizationalUnitIdentifier()) &&
                        Arrays.equals(ou.certifiersIdentifier, OU.getCertifiersIdentifier().toByteArray())) {
                    
                    return;
                }
                
            }
            throw new BFTCommon.BFTException("The identities do not match");
 
        }
        
        void satisfiesPrincipal(MspPrincipal.MSPPrincipal principal, long timestamp) throws BFTException {
            
            MspPrincipal.MSPPrincipal[] principals = collectPrincipals(principal);
            
            for (MspPrincipal.MSPPrincipal p : principals) {
                
                satisfiesPrincipalInternal(p, timestamp);
            }
        }
        
        private MspPrincipal.MSPPrincipal[] collectPrincipals(MspPrincipal.MSPPrincipal principal) throws BFTException {
            
            switch(principal.getPrincipalClassification()) {
                
                case COMBINED:
                    
                    try {

                        MspPrincipal.CombinedPrincipal principals = MspPrincipal.CombinedPrincipal.parseFrom(principal.getPrincipal());
                        
                        if (principals.getPrincipalsCount() == 0) {
                            
                            throw new BFTCommon.BFTException("No principals in CombinedPrincipal");
                        }
                        
                        LinkedList principalsSlice = new LinkedList();
                        
                        for (MspPrincipal.MSPPrincipal cp : principals.getPrincipalsList()) {
                            
                            MspPrincipal.MSPPrincipal[] internalSlice = collectPrincipals(cp);
                            principalsSlice.addAll(Arrays.asList(internalSlice));
                        }
                        
                        MspPrincipal.MSPPrincipal[] result = new MspPrincipal.MSPPrincipal[principalsSlice.size()];
                        principalsSlice.toArray(result);
                        return result;

                    } catch (InvalidProtocolBufferException ex) {

                        throw new BFTCommon.BFTException("Unable to parse principal: " + ex.getMessage());
                    }
                                
                default:
                    
                    return new MspPrincipal.MSPPrincipal[] { principal };
            }
        }
        
        private void satisfiesPrincipalInternal(MspPrincipal.MSPPrincipal principal, long timestamp) throws BFTException {
            
            switch (principal.getPrincipalClassification()){
                
                
                case COMBINED:
                
                    throw new BFTCommon.BFTException("SatisfiesPrincipalInternal can not be called with a CombinedPrincipal");
                    
                case ANONYMITY:

                    try {
                        
                        MspPrincipal.MSPIdentityAnonymity anon = MspPrincipal.MSPIdentityAnonymity.parseFrom(principal.getPrincipal());
                        
                        switch (anon.getAnonymityType()) {
                            
                            case ANONYMOUS:
                                
                                throw new BFTCommon.BFTException("Principal is anonymous, but X.509 MSP does not support anonymous identities");
                                
                            case NOMINAL:
                                
                                //Fabric v1.2 performs no validation in this case, so neither will we
                                
                                break;
                                
                            default:
                                
                                throw new BFTCommon.BFTException("Unknown principal anonymity type: " + anon.getAnonymityType());
                        }
                        
                    } catch (InvalidProtocolBufferException ex) {
                        
                        throw new BFTCommon.BFTException("Unable to parse principal: " + ex.getMessage());
                    }
            
                    break;
                    
                case ROLE:
                    
                    MspPrincipal.MSPRole mspRole = null;
            
                    try {
                        mspRole = MspPrincipal.MSPRole.parseFrom(principal.getPrincipal());
                    } catch (InvalidProtocolBufferException ex) {
                        throw new BFTCommon.BFTException("Unable to parse principal: " + ex.getMessage());
                    
                    }
                    
                    logger.debug("Checking identity MSP agaisnt principal MSP (expected "+ this.msp +", got "+ mspRole.getMspIdentifier() +")");
                    
                    if (!mspRole.getMspIdentifier().equals(this.msp)) 
                        throw new BFTCommon.BFTException("Identity is a member of a different MSP (expected " + this.msp + ", got " + mspRole.getMspIdentifier());
                    
                    switch (mspRole.getRole()){
                        
                        case MEMBER:
                            
                            parseRoleMember(principal, timestamp);
                            break;
                            
                        case ADMIN:
                            
                            parseRoleAdmin(principal, timestamp);
                            break;
                            
                        case CLIENT: //same verifications for both cases
                        case PEER:
                            
                            parseRolePeer(principal, mspRole, timestamp);
                            break;
                            
                        default:
                            
                            throw new BFTCommon.BFTException("Invalid MSP role type: "+ mspRole.getRole());
                    }
                    
                    
                    break;
                    
                case IDENTITY:
                
                    parseIdentity(this.channel, principal, timestamp);
                    break;
                    
                case ORGANIZATION_UNIT:
                    
                    parseOU(principal, timestamp);
                    break;
                    
                default:
                    
                    throw new BFTCommon.BFTException("Invalid principal type: "+ principal.getPrincipalClassification());
            }
            
        }
        
        private OUIdentifier[] getOrganizationalUnits(List<X509Certificate> certPath) throws BFTException {
                        
            byte[] cid = BFTCommon.getCertificationChainIdentifierFromChain(
                        MSPs.get(this.msp).getCryptoConfig().getIdentityIdentifierHashFunction(), certPath);
            
            String[] OUs = BFTCommon.extractOUsFronCertificate(this.certificate);
                        
            OUIdentifier[] result = new OUIdentifier[OUs.length];
            
            for (int i = 0; i < OUs.length; i++) {
                
                result[i] = new OUIdentifier(cid, OUs[i]);
                
            }
            
            return result;
            
            
        }
    }
    
    private MSPManager() {
        
    }
    
    private MSPManager(String mspid, String sysChannel) throws NoSuchAlgorithmException, NoSuchProviderException {
        
        this.mspid = mspid;
        this.sysChannel = sysChannel;
        
        logger = LoggerFactory.getLogger(MSPManager.class);

    }
    
    public static MSPManager getInstance(String mspid, String sysChannel) throws NoSuchAlgorithmException, NoSuchProviderException {
        
        return new MSPManager(mspid, sysChannel);
        
        
    }
    
    public Map<String, Configtx.ConfigEnvelope> getChanConfigs() {
        
        return chanConfigs;
    }
    
    public Configtx.ConfigEnvelope getChanConfig(String channel) {
        
        return chanConfigs.get(channel);
    }
    
    public Map<String, Long> getLastConfigs() {
        
        return lastConfigs;
    }
    
    public Long getLastConfig(String channel) {
        
        return lastConfigs.get(channel);
    }
        
    public byte[] serialize() throws IOException {
        
        //serialize channel configs
        ByteArrayOutputStream b = new ByteArrayOutputStream();
        ObjectOutput o = new ObjectOutputStream(b);   
        o.writeObject(chanConfigs);
        o.flush();
        byte[] chanConfs = b.toByteArray();
        b.close();
        o.close();
        
        //serialize channel last configs
        b = new ByteArrayOutputStream();
        o = new ObjectOutputStream(b);
        o.writeObject(lastConfigs);
        o.flush();
        byte[] lastConfs = b.toByteArray();
        b.close();
        o.close();
            
        //serialize clients IO identifiers
        b = new ByteArrayOutputStream();
        o = new ObjectOutputStream(b);
        o.writeObject(clientOUs);
        o.flush();
        byte[] cOUs = b.toByteArray();
        b.close();
        o.close();
        
        //serialize peers IO identifiers
        b = new ByteArrayOutputStream();
        o = new ObjectOutputStream(b);
        o.writeObject(peerOUs);
        o.flush();
        byte[] pOUs = b.toByteArray();
        b.close();
        o.close();
        
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        DataOutputStream out = new DataOutputStream(bos);
                
        out.writeInt(chanConfs.length);
        out.write(chanConfs);

        out.flush();
        bos.flush();
        
        out.writeInt(lastConfs.length);
        out.write(lastConfs);

        out.flush();
        bos.flush();

        out.writeInt(cOUs.length);
        out.write(cOUs);

        out.flush();
        bos.flush();
        
        out.writeInt(pOUs.length);
        out.write(pOUs);

        out.flush();
        bos.flush();
            
        out.close();
        bos.close();
        return bos.toByteArray();
        
    }
    
    public static MSPManager deserialize(String mspid, String sysChannel, byte[] bytes) throws CertificateException, IOException, ClassNotFoundException, NoSuchAlgorithmException, NoSuchProviderException, BFTException {
        

        MSPManager instance = getInstance(mspid, sysChannel);
        
        ByteArrayInputStream bis = new ByteArrayInputStream(bytes);
        DataInputStream in = new DataInputStream(bis);
                
        byte[] chanConfs = new byte[0];
        int n = in.readInt();

        if (n > 0) {

            chanConfs = new byte[n];
            in.read(chanConfs);

        }
        
        byte[] lastConfs = new byte[0];
        n = in.readInt();

        if (n > 0) {

            lastConfs = new byte[n];
            in.read(lastConfs);

        }
        
        byte[] cOUs = new byte[0];
        n = in.readInt();

        if (n > 0) {

            cOUs = new byte[n];
            in.read(cOUs);

        }
        
        byte[] pOUs = new byte[0];
        n = in.readInt();

        if (n > 0) {

            pOUs = new byte[n];
            in.read(pOUs);

        }
        
        in.close();
        bis.close();
        
        ByteArrayInputStream b = new ByteArrayInputStream(chanConfs);
        ObjectInput i = null;
            
        i = new ObjectInputStream(b);
        instance.chanConfigs = (Map<String,Configtx.ConfigEnvelope>) i.readObject();
        i.close();
        b.close();
        
        b = new ByteArrayInputStream(lastConfs);
        i = new ObjectInputStream(b);
        instance.lastConfigs = (Map<String,Long>) i.readObject();
        i.close();
        b.close();
        
        b = new ByteArrayInputStream(cOUs);
        i = new ObjectInputStream(b);
        instance.clientOUs = (Map<String,OUIdentifier>) i.readObject();
        i.close();
        b.close();
        
        b = new ByteArrayInputStream(pOUs);
        i = new ObjectInputStream(b);
        instance.peerOUs = (Map<String,OUIdentifier>) i.readObject();
        i.close();
        b.close();
        
        instance.setup();
    
        return instance;
    }
    
    private void setup() throws CertificateException, IOException, BFTException {
        
        this.MSPs = new TreeMap<>();
        this.chanWriters = new TreeMap<>();
        this.adminCerts = new TreeMap<>();
        this.revokedCerts = new TreeMap<>();
        this.intermediateCerts = new TreeMap<>();
        this.rootCerts = new TreeMap<>();
        this.validatedCerts = new TreeMap<>();
                
            
        Set<String> chans = this.chanConfigs.keySet();

        for (String chan : chans) {

            Configtx.ConfigEnvelope conf = this.chanConfigs.get(chan);

            Set<MspConfig.FabricMSPConfig> msps = extractMSPs(conf.getConfig(), chan.equals(sysChannel));

            Set<String> mspIDs = new TreeSet<>();

            for (MspConfig.FabricMSPConfig msp : msps) {

                mspIDs.add(msp.getName());

                MSPs.put(msp.getName(), msp);
                adminCerts.put(msp.getName(), BFTCommon.extractCertificates(msp.getAdminsList()));
                revokedCerts.put(msp.getName(), BFTCommon.extractCertificates(msp.getRevocationListList()));
                intermediateCerts.put(msp.getName(), BFTCommon.extractCertificates(msp.getIntermediateCertsList()));               
                rootCerts.put(msp.getName(), BFTCommon.selectSelfSigned(BFTCommon.extractCertificates(msp.getRootCertsList())));
                validatedCerts.put(msp.getName(), new HashMap<>());

            }
            
            Map<String,ConfigItem> confMap = configGroupToConfigMap(conf.getConfig().getChannelGroup(), rootGroupKey);
            
            String path = policyPrefix + pathSeparator + rootGroupKey;
                        
            Policy policy = parsePolicy(chan, path, path + pathSeparator + "Writers", "Writers", confMap);

            chanWriters.put(chan, policy);
            
            chanMSPs.put(chan, mspIDs);

        }        
        
    }
    
    public void newChannel(String channelID, long sequence, Configtx.ConfigEnvelope conf, long timestamp) throws BFTException  {

        Set<MspConfig.FabricMSPConfig> msps = extractMSPs(conf.getConfig(), channelID.equals(sysChannel));
        Set<String> mspIDs = new TreeSet<>();
        
        try {
            
            for (MspConfig.FabricMSPConfig msp : msps) {

                mspIDs.add(msp.getName());

                this.MSPs.put(msp.getName(), msp);
                this.adminCerts.put(msp.getName(), BFTCommon.extractCertificates(msp.getAdminsList()));
                this.revokedCerts.put(msp.getName(), BFTCommon.extractCertificates(msp.getRevocationListList()));
                this.intermediateCerts.put(msp.getName(), BFTCommon.extractCertificates(msp.getIntermediateCertsList()));
                this.rootCerts.put(msp.getName(), BFTCommon.selectSelfSigned(BFTCommon.extractCertificates(msp.getRootCertsList())));
                this.validatedCerts.put(msp.getName(), new HashMap<>());

                for (X509Certificate adminCert : this.adminCerts.get(msp.getName())) {

                    logger.debug("Verifying admin certificate for channel " + channelID + " and MSP " + msp.getName());
                    
                    this.verifyCertificate(adminCert, msp.getName(), timestamp);

                }

